From 74cbddeb1c973b8b828ec3af622957a43ba485ea Mon Sep 17 00:00:00 2001 From: David Aurelio <158859+davidaurelio@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:01:47 +0200 Subject: [PATCH 1/4] feat: geo brand presence passes config version to mystique --- src/geo-brand-presence/handler.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/geo-brand-presence/handler.js b/src/geo-brand-presence/handler.js index f12d2a63a..4955d6129 100644 --- a/src/geo-brand-presence/handler.js +++ b/src/geo-brand-presence/handler.js @@ -16,6 +16,7 @@ import { parquetReadObjects } from 'hyparquet'; import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { randomUUID } from 'node:crypto'; +import { llmoConfig } from '@adobe/spacecat-shared-utils'; import { AuditBuilder } from '../common/audit-builder.js'; import { wwwUrlResolver } from '../common/index.js'; @@ -79,6 +80,8 @@ export async function sendToMystique(context, getPresignedUrl = getSignedUrl) { return; } + const config = await llmoConfig.readConfig(siteId, s3Client, { s3Bucket: bucket }); + const url = await asPresignedJsonUrl(prompts, bucket, { ...context, getPresignedUrl }); log.info('GEO BRAND PRESENCE: Presigned URL for prompts for site id %s (%s): %s', siteId, baseURL, url); await Promise.all(OPPTY_TYPES.map(async (opptyType) => { @@ -92,6 +95,7 @@ export async function sendToMystique(context, getPresignedUrl = getSignedUrl) { week: calendarWeek.week, year: calendarWeek.year, data: { + config_version: config.version, url, }, }; From b6d88d231e62a81e9416bb1960ea5ef01c9b1029 Mon Sep 17 00:00:00 2001 From: David Aurelio <158859+davidaurelio@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:39:41 +0200 Subject: [PATCH 2/4] feat: geo brand presence writes sheets with config version dir --- .../detect-geo-brand-presence-handler.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/geo-brand-presence/detect-geo-brand-presence-handler.js b/src/geo-brand-presence/detect-geo-brand-presence-handler.js index d1652f7f9..410b014be 100644 --- a/src/geo-brand-presence/detect-geo-brand-presence-handler.js +++ b/src/geo-brand-presence/detect-geo-brand-presence-handler.js @@ -36,6 +36,7 @@ export default async function handler(message, context) { return notFound(); } + const configVersion = data.config_version; const sheetUrl = URL.parse(data.presigned_url); if (!sheetUrl || !sheetUrl.href) { log.error(`GEO BRAND PRESENCE: Invalid presigned URL: ${data.presigned_url}`); @@ -48,7 +49,11 @@ export default async function handler(message, context) { // upload to sharepoint & publish via hlx admin api const sharepointClient = await createLLMOSharepointClient(context); - const outputLocation = `${site.getConfig().getLlmoDataFolder()}/brand-presence`; + let outputBase = site.getConfig().getLlmoDataFolder(); + if (configVersion != null && configVersion !== '') { + outputBase = `${outputBase}/config_${configVersion}`; + } + const outputLocation = `${outputBase}/brand-presence`; const xlsxName = ( /;\s*content=(brandpresence-.*$)/.exec(sheetUrl.searchParams.get('response-content-disposition') ?? '')?.[1] ?? sheetUrl.pathname.replace(/.*[/]/, '') From 4bbc679d2cf5ce2237693a7dca578210382bb48b Mon Sep 17 00:00:00 2001 From: David Aurelio <158859+davidaurelio@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:46:26 +0200 Subject: [PATCH 3/4] tests --- test/audits/geo-brand-presence.test.js | 39 +++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/test/audits/geo-brand-presence.test.js b/test/audits/geo-brand-presence.test.js index 601e6f9a4..a4a94e5ff 100755 --- a/test/audits/geo-brand-presence.test.js +++ b/test/audits/geo-brand-presence.test.js @@ -18,6 +18,7 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { parquetWriteBuffer } from 'hyparquet-writer'; import { keywordPromptsImportStep, sendToMystique } from '../../src/geo-brand-presence/handler.js'; +import { llmoConfig } from '@adobe/spacecat-shared-utils'; use(sinonChai); @@ -56,6 +57,7 @@ describe('Geo Brand Presence Handler', () => { s3Client = { send: sinon.stub().throws(new Error('no stubbed response')), }; + s3Client.send.withArgs(matchS3Cmd('PutObjectCommand')).resolves({}); getPresignedUrl = sandbox.stub(); context = { log, @@ -160,7 +162,8 @@ describe('Geo Brand Presence Handler', () => { it('should send message to Mystique for all opportunity types when keywordQuestions are found', async () => { // Mock S3 client method used by getStoredMetrics (AWS SDK v3 style) - fakeS3Response(fakeData()); + fakeS3ParquetResponse(fakeData()); + fakeS3ConfigResponse('v12345'); getPresignedUrl.resolves('https://example.com/presigned-url'); @@ -183,6 +186,7 @@ describe('Geo Brand Presence Handler', () => { deliveryType: site.getDeliveryType(), }); expect(brandPresenceMessage.data).deep.equal({ + config_version: 'v12345', web_search_provider: 'chatgpt', url: 'https://example.com/presigned-url', }); @@ -203,7 +207,7 @@ describe('Geo Brand Presence Handler', () => { }); it('should skip sending message to Mystique when no keywordQuestions', async () => { - fakeS3Response([]); + fakeS3ParquetResponse([]); await sendToMystique({ ...context, auditContext: { @@ -214,7 +218,7 @@ describe('Geo Brand Presence Handler', () => { expect(sqs.sendMessage).to.not.have.been.called; }); - function fakeS3Response(response) { + function fakeS3ParquetResponse(response) { const columnData = { prompt: { data: [], name: 'prompt', type: 'STRING' }, region: { data: [], name: 'region', type: 'STRING' }, @@ -236,7 +240,9 @@ describe('Geo Brand Presence Handler', () => { const buffer = parquetWriteBuffer({ columnData: Object.values(columnData) }); - s3Client.send.resolves({ + s3Client.send.withArgs( + matchS3Cmd('GetObjectCommand', { Key: sinon.match(/\/data\.parquet$/) }) + ).resolves({ Body: { async transformToByteArray() { return new Uint8Array(buffer); @@ -245,6 +251,19 @@ describe('Geo Brand Presence Handler', () => { }); } + function fakeS3ConfigResponse(version) { + s3Client.send.withArgs( + matchS3Cmd('GetObjectCommand', { Key: llmoConfig.llmoConfigPath(site.getId()) }) + ).resolves({ + Body: { + async transformToString() { + return JSON.stringify(llmoConfig.defaultConfig()); + }, + }, + VersionId: version, + }); + } + function fakeData(mapFn) { const data = [ { @@ -312,3 +331,15 @@ describe('Geo Brand Presence Handler', () => { return mapFn ? data.map(mapFn) : data; } }); + +/** + * @param {string} name - The name of the S3 command to match. + * @param {Record} [input] - The partial input of the S3 command to match. + */ +function matchS3Cmd(name, input) { + const match = { constructor: sinon.match({ name }) }; + if (input) { + match.input = sinon.match(input); + } + return sinon.match(match); +} From 5bb134dfa7d657b8554e5dde0968c8d062cd56e3 Mon Sep 17 00:00:00 2001 From: David Aurelio <158859+davidaurelio@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:53:30 +0200 Subject: [PATCH 4/4] fix output location --- src/geo-brand-presence/detect-geo-brand-presence-handler.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/geo-brand-presence/detect-geo-brand-presence-handler.js b/src/geo-brand-presence/detect-geo-brand-presence-handler.js index 410b014be..9060b88e4 100644 --- a/src/geo-brand-presence/detect-geo-brand-presence-handler.js +++ b/src/geo-brand-presence/detect-geo-brand-presence-handler.js @@ -49,11 +49,10 @@ export default async function handler(message, context) { // upload to sharepoint & publish via hlx admin api const sharepointClient = await createLLMOSharepointClient(context); - let outputBase = site.getConfig().getLlmoDataFolder(); + let outputLocation = `${site.getConfig().getLlmoDataFolder()}/brand-presence`; if (configVersion != null && configVersion !== '') { - outputBase = `${outputBase}/config_${configVersion}`; + outputLocation = `${outputLocation}/config_${configVersion}`; } - const outputLocation = `${outputBase}/brand-presence`; const xlsxName = ( /;\s*content=(brandpresence-.*$)/.exec(sheetUrl.searchParams.get('response-content-disposition') ?? '')?.[1] ?? sheetUrl.pathname.replace(/.*[/]/, '')