Skip to content

Commit cf5c95d

Browse files
authored
feat: referral traffic enablement and backfill during llmo-onboard (#1147)
1 parent 29c9a8b commit cf5c95d

File tree

2 files changed

+96
-5
lines changed

2 files changed

+96
-5
lines changed

src/support/slack/commands/llmo-onboard.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import { isValidUrl } from '@adobe/spacecat-shared-utils';
13+
import { getLastNumberOfWeeks, isValidUrl } from '@adobe/spacecat-shared-utils';
1414

1515
import {
1616
extractURLFromSlackInput,
@@ -19,8 +19,32 @@ import {
1919

2020
import BaseCommand from './base.js';
2121

22+
const REFERRAL_TRAFFIC_AUDIT = 'llmo-referral-traffic';
23+
const REFERRAL_TRAFFIC_IMPORT = 'traffic-analysis';
24+
2225
const PHRASES = ['onboard-llmo'];
2326

27+
async function triggerReferralTrafficBackfill(context, configuration, siteId) {
28+
const { log, sqs } = context;
29+
30+
const last4Weeks = getLastNumberOfWeeks(4);
31+
32+
for (const last4Week of last4Weeks) {
33+
const { week, year } = last4Week;
34+
const message = {
35+
type: REFERRAL_TRAFFIC_IMPORT,
36+
siteId,
37+
auditContext: {
38+
auditType: REFERRAL_TRAFFIC_AUDIT,
39+
week,
40+
year,
41+
},
42+
};
43+
sqs.sendMessage(configuration.getQueues().imports, message);
44+
log.info(`Successfully triggered import ${REFERRAL_TRAFFIC_IMPORT} with message: ${JSON.stringify(message)}`);
45+
}
46+
}
47+
2448
/**
2549
* Factory function to create the LlmoOnboardCommand object.
2650
*
@@ -40,7 +64,7 @@ function LlmoOnboardCommand(context) {
4064
const {
4165
dataAccess, log,
4266
} = context;
43-
const { Site } = dataAccess;
67+
const { Site, Configuration } = dataAccess;
4468

4569
/**
4670
* Handles LLMO onboarding for a single site.
@@ -109,10 +133,20 @@ function LlmoOnboardCommand(context) {
109133
// Set the config directly as a plain object
110134
site.setConfig(updatedConfigData);
111135

136+
// enable the traffic-analysis import for referral-traffic
137+
siteConfig.enableImport(REFERRAL_TRAFFIC_IMPORT);
138+
139+
// enable the llmo-referral-traffic import for referral-traffic
140+
const configuration = await Configuration.findLatest();
141+
configuration.enableHandlerForSite(REFERRAL_TRAFFIC_AUDIT, site);
142+
112143
try {
144+
await configuration.save();
113145
await site.save();
114146
log.info(`Successfully updated LLMO config for site ${siteId}`);
115147

148+
await triggerReferralTrafficBackfill(context, configuration, siteId);
149+
116150
const message = `:white_check_mark: *LLMO onboarding completed successfully!*
117151
118152
:link: *Site:* ${baseURL}

test/support/slack/commands/llmo-onboard.test.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('LlmoOnboardCommand', () => {
2727
let mockConfig;
2828
let mockDataAccess;
2929
let mockLog;
30+
let mockConfiguration;
3031
let slackContext;
3132

3233
beforeEach(() => {
@@ -40,6 +41,7 @@ describe('LlmoOnboardCommand', () => {
4041
getContentAiConfig: sinon.stub().returns({}),
4142
getImports: sinon.stub().returns([]),
4243
getCdnLogsConfig: sinon.stub().returns({}),
44+
enableImport: sinon.stub(),
4345
};
4446

4547
// Create mock site
@@ -50,11 +52,21 @@ describe('LlmoOnboardCommand', () => {
5052
save: sinon.stub().resolves(),
5153
};
5254

55+
// Mock global Configuration
56+
mockConfiguration = {
57+
enableHandlerForSite: sinon.stub(),
58+
save: sinon.stub().resolves(),
59+
getQueues: sinon.stub().returns({ imports: 'queue-imports' }),
60+
};
61+
5362
// Create mock data access
5463
mockDataAccess = {
5564
Site: {
5665
findByBaseURL: sinon.stub().resolves(mockSite),
5766
},
67+
Configuration: {
68+
findLatest: sinon.stub().resolves(mockConfiguration),
69+
},
5870
};
5971

6072
// Create mock log
@@ -67,6 +79,9 @@ describe('LlmoOnboardCommand', () => {
6779
mockContext = {
6880
dataAccess: mockDataAccess,
6981
log: mockLog,
82+
sqs: {
83+
sendMessage: sinon.stub(),
84+
},
7085
};
7186

7287
// Create slack context
@@ -130,12 +145,38 @@ describe('LlmoOnboardCommand', () => {
130145
);
131146
});
132147

133-
it('should successfully onboard LLMO for a valid site', async () => {
148+
it('should successfully onboard LLMO for a valid site and enable referral traffic processing + backfill', async () => {
134149
await command.handleExecution(['https://example.com', 'adobe', 'Adobe'], slackContext);
135150

151+
// site lookup and config update
136152
expect(mockDataAccess.Site.findByBaseURL).to.have.been.calledWith('https://example.com');
137153
expect(mockSite.setConfig).to.have.been.called;
138-
expect(mockSite.save).to.have.been.called;
154+
155+
// enable traffic-analysis import on the site config
156+
expect(mockConfig.enableImport).to.have.been.calledWith('traffic-analysis');
157+
158+
// enable handler for site in Configuration and save it
159+
expect(mockDataAccess.Configuration.findLatest).to.have.been.calledOnce;
160+
expect(mockConfiguration.enableHandlerForSite).to.have.been.calledWith('llmo-referral-traffic', mockSite);
161+
expect(mockConfiguration.save).to.have.been.calledOnce;
162+
163+
// save site config after configuration saved
164+
expect(mockSite.save).to.have.been.calledOnce;
165+
sinon.assert.callOrder(mockConfiguration.save, mockSite.save);
166+
167+
// referral-traffic backfill should enqueue 4 messages (last 4 weeks)
168+
expect(mockContext.sqs.sendMessage.callCount).to.equal(4);
169+
const calls = mockContext.sqs.sendMessage.getCalls();
170+
calls.forEach((call) => {
171+
const [queue, payload] = call.args;
172+
expect(queue).to.equal('queue-imports');
173+
expect(payload).to.include({ type: 'traffic-analysis', siteId: 'test-site-id' });
174+
expect(payload.auditContext).to.include({ auditType: 'llmo-referral-traffic' });
175+
expect(payload.auditContext.week).to.be.a('number');
176+
expect(payload.auditContext.year).to.be.a('number');
177+
});
178+
179+
// success message
139180
expect(slackContext.say).to.have.been.calledWith(
140181
sinon.match.string.and(sinon.match(/LLMO onboarding completed successfully/)),
141182
);
@@ -165,7 +206,7 @@ describe('LlmoOnboardCommand', () => {
165206
expect(mockSite.save).to.have.been.called;
166207
});
167208

168-
it('should handle save errors gracefully', async () => {
209+
it('should handle site save errors gracefully', async () => {
169210
const saveError = new Error('Database error');
170211
mockSite.save.rejects(saveError);
171212

@@ -179,6 +220,22 @@ describe('LlmoOnboardCommand', () => {
179220
);
180221
});
181222

223+
it('should handle configuration save errors gracefully', async () => {
224+
const saveError = new Error('Conf DB error');
225+
mockConfiguration.save.rejects(saveError);
226+
227+
await command.handleExecution(['https://example.com', 'adobe', 'Adobe'], slackContext);
228+
229+
expect(mockLog.error).to.have.been.calledWith(
230+
sinon.match(/Error saving LLMO config for site test-site-id/),
231+
);
232+
expect(slackContext.say).to.have.been.calledWith(
233+
':x: Failed to save LLMO configuration: Conf DB error',
234+
);
235+
// site.save should not be called when configuration.save fails
236+
expect(mockSite.save).to.not.have.been.called;
237+
});
238+
182239
it('sends a message for all other errors', async () => {
183240
const error = new Error('Unexpected error');
184241
mockDataAccess.Site.findByBaseURL.rejects(error);

0 commit comments

Comments
 (0)