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..9060b88e4 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,10 @@ 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 outputLocation = `${site.getConfig().getLlmoDataFolder()}/brand-presence`; + if (configVersion != null && configVersion !== '') { + outputLocation = `${outputLocation}/config_${configVersion}`; + } const xlsxName = ( /;\s*content=(brandpresence-.*$)/.exec(sheetUrl.searchParams.get('response-content-disposition') ?? '')?.[1] ?? sheetUrl.pathname.replace(/.*[/]/, '') 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, }, }; 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); +}