Skip to content

Commit 66de1a1

Browse files
anihamani hammond
andauthored
feat: read daily brand presence files for llmo (#1407)
Co-authored-by: ani hammond <[email protected]>
1 parent 07d65c5 commit 66de1a1

File tree

6 files changed

+227
-4
lines changed

6 files changed

+227
-4
lines changed

docs/openapi/api.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ paths:
274274
$ref: './llmo-api.yaml#/llmo-sheet-data'
275275
/sites/{siteId}/llmo/sheet-data/{sheetType}/{dataSource}:
276276
$ref: './llmo-api.yaml#/llmo-sheet-data-with-type'
277+
/sites/{siteId}/llmo/sheet-data/{sheetType}/{week}/{dataSource}:
278+
$ref: './llmo-api.yaml#/llmo-sheet-data-with-type-and-week'
277279
/sites/{siteId}/llmo/global-sheet-data/{configName}:
278280
$ref: './llmo-api.yaml#/llmo-global-sheet-data'
279281
/sites/{siteId}/llmo/config:

docs/openapi/llmo-api.yaml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,94 @@ llmo-sheet-data-with-type:
145145
security:
146146
- api_key: [ ]
147147

148+
llmo-sheet-data-with-type-and-week:
149+
parameters:
150+
- $ref: './parameters.yaml#/siteId'
151+
- name: sheetType
152+
in: path
153+
required: true
154+
description: The sheet type identifier to fetch from the external endpoint
155+
schema:
156+
type: string
157+
example: 'analytics'
158+
- name: week
159+
in: path
160+
required: true
161+
description: The week identifier (e.g., w01, w02) to fetch time-series data
162+
schema:
163+
type: string
164+
pattern: '^w\d{2}$'
165+
example: 'w01'
166+
- name: dataSource
167+
in: path
168+
required: true
169+
description: The data source identifier to fetch from the external endpoint
170+
schema:
171+
type: string
172+
example: 'questions'
173+
get:
174+
tags:
175+
- llmo
176+
summary: Get LLMO sheet data with sheet type and week
177+
description: |
178+
Retrieves data from the external LLMO data endpoint for a specific site with a specified sheet type and week.
179+
This endpoint proxies data from the external HLX API based on the site's LLMO configuration.
180+
The data is fetched from the path: {dataFolder}/{sheetType}/{week}/{dataSource}.json
181+
This is useful for fetching time-series data organized by week.
182+
operationId: getLlmoSheetDataWithTypeAndWeek
183+
responses:
184+
'200':
185+
description: LLMO sheet data retrieved successfully
186+
content:
187+
application/json:
188+
schema:
189+
type: object
190+
description: The data returned from the external LLMO endpoint
191+
additionalProperties: true
192+
'400':
193+
$ref: './responses.yaml#/400'
194+
'401':
195+
$ref: './responses.yaml#/401'
196+
'500':
197+
$ref: './responses.yaml#/500'
198+
security:
199+
- api_key: [ ]
200+
post:
201+
tags:
202+
- llmo
203+
summary: Query LLMO sheet data with sheet type and week using filters, exclusions, and grouping
204+
description: |
205+
Retrieves and processes data from the external LLMO data endpoint for a specific site with a specified sheet type,
206+
week, and advanced querying capabilities. This endpoint allows filtering data with case-insensitive exact matching,
207+
excluding specific fields from the response, and grouping data by specified attributes.
208+
The data is fetched from the path: {dataFolder}/{sheetType}/{week}/{dataSource}.json
209+
The endpoint fetches all available data (up to 1M records) to apply the query operations effectively.
210+
This is useful for fetching and analyzing time-series data organized by week.
211+
operationId: queryLlmoSheetDataWithTypeAndWeek
212+
requestBody:
213+
required: false
214+
content:
215+
application/json:
216+
schema:
217+
$ref: './schemas.yaml#/LlmoSheetDataQuery'
218+
responses:
219+
'200':
220+
description: LLMO sheet data queried and processed successfully
221+
content:
222+
application/json:
223+
schema:
224+
type: object
225+
description: The processed data returned from the external LLMO endpoint after applying filters, exclusions, and grouping
226+
additionalProperties: true
227+
'400':
228+
$ref: './responses.yaml#/400'
229+
'401':
230+
$ref: './responses.yaml#/401'
231+
'500':
232+
$ref: './responses.yaml#/500'
233+
security:
234+
- api_key: [ ]
235+
148236
llmo-global-sheet-data:
149237
parameters:
150238
- $ref: './parameters.yaml#/siteId'

src/controllers/llmo/llmo.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,21 @@ function LlmoController(ctx) {
105105
// Handles requests to the LLMO sheet data endpoint
106106
const getLlmoSheetData = async (context) => {
107107
const { log } = context;
108-
const { siteId, dataSource, sheetType } = context.params;
108+
const {
109+
siteId, dataSource, sheetType, week,
110+
} = context.params;
109111
const { env } = context;
110112
try {
111113
const { llmoConfig } = await getSiteAndValidateLlmo(context);
112-
const sheetURL = sheetType ? `${llmoConfig.dataFolder}/${sheetType}/${dataSource}.json` : `${llmoConfig.dataFolder}/${dataSource}.json`;
114+
// Construct the sheet URL based on which parameters are provided
115+
let sheetURL;
116+
if (sheetType && week) {
117+
sheetURL = `${llmoConfig.dataFolder}/${sheetType}/${week}/${dataSource}.json`;
118+
} else if (sheetType) {
119+
sheetURL = `${llmoConfig.dataFolder}/${sheetType}/${dataSource}.json`;
120+
} else {
121+
sheetURL = `${llmoConfig.dataFolder}/${dataSource}.json`;
122+
}
113123

114124
// Add limit, offset and sheet query params to the url
115125
const url = new URL(`${LLMO_SHEETDATA_SOURCE_URL}/${sheetURL}`);
@@ -156,7 +166,9 @@ function LlmoController(ctx) {
156166
// with query capabilities (filtering, exclusions, grouping)
157167
const queryLlmoSheetData = async (context) => {
158168
const { log } = context;
159-
const { siteId, dataSource, sheetType } = context.params;
169+
const {
170+
siteId, dataSource, sheetType, week,
171+
} = context.params;
160172
const { env } = context;
161173

162174
// Start timing for the entire method
@@ -191,7 +203,15 @@ function LlmoController(ctx) {
191203

192204
try {
193205
const { llmoConfig } = await getSiteAndValidateLlmo(context);
194-
const sheetURL = sheetType ? `${llmoConfig.dataFolder}/${sheetType}/${dataSource}.json` : `${llmoConfig.dataFolder}/${dataSource}.json`;
206+
// Construct the sheet URL based on which parameters are provided
207+
let sheetURL;
208+
if (sheetType && week) {
209+
sheetURL = `${llmoConfig.dataFolder}/${sheetType}/${week}/${dataSource}.json`;
210+
} else if (sheetType) {
211+
sheetURL = `${llmoConfig.dataFolder}/${sheetType}/${dataSource}.json`;
212+
} else {
213+
sheetURL = `${llmoConfig.dataFolder}/${dataSource}.json`;
214+
}
195215

196216
// Add limit, offset and sheet query params to the url
197217
const url = new URL(`${LLMO_SHEETDATA_SOURCE_URL}/${sheetURL}`);

src/routes/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,10 @@ export default function getRouteHandlers(
260260
// LLMO Specific Routes
261261
'GET /sites/:siteId/llmo/sheet-data/:dataSource': llmoController.getLlmoSheetData,
262262
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource': llmoController.getLlmoSheetData,
263+
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource': llmoController.getLlmoSheetData,
263264
'POST /sites/:siteId/llmo/sheet-data/:dataSource': llmoController.queryLlmoSheetData,
264265
'POST /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource': llmoController.queryLlmoSheetData,
266+
'POST /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource': llmoController.queryLlmoSheetData,
265267
'GET /sites/:siteId/llmo/config': llmoController.getLlmoConfig,
266268
'PATCH /sites/:siteId/llmo/config': llmoController.updateLlmoConfig,
267269
'POST /sites/:siteId/llmo/config': llmoController.updateLlmoConfig,

test/controllers/llmo/llmo.test.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,49 @@ describe('LlmoController', () => {
434434
expect(result.status).to.equal(200);
435435
expect(await result.json()).to.deep.equal({ data: 'test-data' });
436436
});
437+
438+
it('should handle week parameter in URL construction', async () => {
439+
const mockResponse = createMockResponse({ data: 'weekly-data' });
440+
tracingFetchStub.resolves(mockResponse);
441+
mockContext.params.sheetType = 'analytics';
442+
mockContext.params.week = 'w01';
443+
444+
await controller.getLlmoSheetData(mockContext);
445+
446+
expect(tracingFetchStub).to.have.been.calledWith(
447+
`${EXTERNAL_API_BASE_URL}/${TEST_FOLDER}/analytics/w01/test-data.json`,
448+
sinon.match.object,
449+
);
450+
});
451+
452+
it('should handle week parameter with query params', async () => {
453+
const mockResponse = createMockResponse({ data: 'weekly-data' });
454+
tracingFetchStub.resolves(mockResponse);
455+
mockContext.params.sheetType = 'analytics';
456+
mockContext.params.week = 'w02';
457+
mockContext.data = { limit: '50', offset: '10' };
458+
459+
await controller.getLlmoSheetData(mockContext);
460+
461+
expect(tracingFetchStub).to.have.been.calledWith(
462+
`${EXTERNAL_API_BASE_URL}/${TEST_FOLDER}/analytics/w02/test-data.json?limit=50&offset=10`,
463+
sinon.match.object,
464+
);
465+
});
466+
467+
it('should ignore week parameter when sheetType is not provided', async () => {
468+
const mockResponse = createMockResponse({ data: 'test-data' });
469+
tracingFetchStub.resolves(mockResponse);
470+
mockContext.params.week = 'w01';
471+
delete mockContext.params.sheetType;
472+
473+
await controller.getLlmoSheetData(mockContext);
474+
475+
expect(tracingFetchStub).to.have.been.calledWith(
476+
`${EXTERNAL_API_BASE_URL}/${TEST_FOLDER}/test-data.json`,
477+
sinon.match.object,
478+
);
479+
});
437480
});
438481

439482
describe('getLlmoGlobalSheetData', () => {
@@ -895,6 +938,68 @@ describe('LlmoController', () => {
895938
sinon.match.object,
896939
);
897940
});
941+
942+
it('should handle week parameter in URL construction', async () => {
943+
tracingFetchStub.resolves(createMockResponse({ ':type': 'sheet', data: [] }));
944+
mockContext.params.sheetType = 'analytics';
945+
mockContext.params.week = 'w01';
946+
mockContext.data = null;
947+
948+
const result = await controller.queryLlmoSheetData(mockContext);
949+
950+
expect(result.status).to.equal(200);
951+
expect(tracingFetchStub).to.have.been.calledWith(
952+
`${EXTERNAL_API_BASE_URL}/${TEST_FOLDER}/analytics/w01/test-data.json?limit=1000000`,
953+
sinon.match.object,
954+
);
955+
});
956+
957+
it('should handle week parameter with filters and grouping', async () => {
958+
const mockResponseData = {
959+
':type': 'sheet',
960+
data: [
961+
{
962+
id: 1, status: 'active', week: 'w01', value: 100,
963+
},
964+
{
965+
id: 2, status: 'inactive', week: 'w01', value: 200,
966+
},
967+
],
968+
};
969+
tracingFetchStub.resolves(createMockResponse(mockResponseData));
970+
mockContext.params.sheetType = 'analytics';
971+
mockContext.params.week = 'w01';
972+
mockContext.data = {
973+
filters: { status: 'active' },
974+
groupBy: ['status'],
975+
};
976+
977+
const result = await controller.queryLlmoSheetData(mockContext);
978+
979+
expect(result.status).to.equal(200);
980+
expect(tracingFetchStub).to.have.been.calledWith(
981+
`${EXTERNAL_API_BASE_URL}/${TEST_FOLDER}/analytics/w01/test-data.json?limit=1000000`,
982+
sinon.match.object,
983+
);
984+
const responseBody = await result.json();
985+
expect(responseBody.data).to.have.length(1);
986+
expect(responseBody.data[0].status).to.equal('active');
987+
});
988+
989+
it('should ignore week parameter when sheetType is not provided', async () => {
990+
tracingFetchStub.resolves(createMockResponse({ ':type': 'sheet', data: [] }));
991+
mockContext.params.week = 'w01';
992+
delete mockContext.params.sheetType;
993+
mockContext.data = null;
994+
995+
const result = await controller.queryLlmoSheetData(mockContext);
996+
997+
expect(result.status).to.equal(200);
998+
expect(tracingFetchStub).to.have.been.calledWith(
999+
`${EXTERNAL_API_BASE_URL}/${TEST_FOLDER}/test-data.json?limit=1000000`,
1000+
sinon.match.object,
1001+
);
1002+
});
8981003
});
8991004

9001005
describe('getLlmoConfig', () => {

test/routes/index.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,10 @@ describe('getRouteHandlers', () => {
441441
'PATCH /sites/:siteId/config/cdn-logs',
442442
'GET /sites/:siteId/llmo/sheet-data/:dataSource',
443443
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource',
444+
'GET /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource',
444445
'POST /sites/:siteId/llmo/sheet-data/:dataSource',
445446
'POST /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource',
447+
'POST /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource',
446448
'GET /sites/:siteId/llmo/config',
447449
'PATCH /sites/:siteId/llmo/config',
448450
'POST /sites/:siteId/llmo/config',
@@ -568,6 +570,8 @@ describe('getRouteHandlers', () => {
568570
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:dataSource'].paramNames).to.deep.equal(['siteId', 'dataSource']);
569571
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource'].handler).to.equal(mockLlmoController.getLlmoSheetData);
570572
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource'].paramNames).to.deep.equal(['siteId', 'sheetType', 'dataSource']);
573+
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource'].handler).to.equal(mockLlmoController.getLlmoSheetData);
574+
expect(dynamicRoutes['GET /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource'].paramNames).to.deep.equal(['siteId', 'sheetType', 'week', 'dataSource']);
571575
expect(dynamicRoutes['GET /sites/:siteId/llmo/config'].handler).to.equal(mockLlmoController.getLlmoConfig);
572576
expect(dynamicRoutes['GET /sites/:siteId/llmo/config'].paramNames).to.deep.equal(['siteId']);
573577
expect(dynamicRoutes['GET /sites/:siteId/llmo/questions'].handler).to.equal(mockLlmoController.getLlmoQuestions);
@@ -604,5 +608,7 @@ describe('getRouteHandlers', () => {
604608
expect(dynamicRoutes['POST /sites/:siteId/llmo/sheet-data/:dataSource'].paramNames).to.deep.equal(['siteId', 'dataSource']);
605609
expect(dynamicRoutes['POST /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource'].handler).to.equal(mockLlmoController.queryLlmoSheetData);
606610
expect(dynamicRoutes['POST /sites/:siteId/llmo/sheet-data/:sheetType/:dataSource'].paramNames).to.deep.equal(['siteId', 'sheetType', 'dataSource']);
611+
expect(dynamicRoutes['POST /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource'].handler).to.equal(mockLlmoController.queryLlmoSheetData);
612+
expect(dynamicRoutes['POST /sites/:siteId/llmo/sheet-data/:sheetType/:week/:dataSource'].paramNames).to.deep.equal(['siteId', 'sheetType', 'week', 'dataSource']);
607613
});
608614
});

0 commit comments

Comments
 (0)