Skip to content

Commit 59180fd

Browse files
authored
Merge branch 'main' into consumer-controller
2 parents 0ca1693 + a581ef9 commit 59180fd

File tree

16 files changed

+1699
-151
lines changed

16 files changed

+1699
-151
lines changed

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
# [1.326.0](https://github.com/adobe/spacecat-api-service/compare/v1.325.1...v1.326.0) (2026-02-27)
2+
3+
4+
### Features
5+
6+
* add slack for redirects ([#1830](https://github.com/adobe/spacecat-api-service/issues/1830)) ([2c3d2a5](https://github.com/adobe/spacecat-api-service/commit/2c3d2a57f2ee0f3e5f9b655e799bcb1600422d03))
7+
8+
## [1.325.1](https://github.com/adobe/spacecat-api-service/compare/v1.325.0...v1.325.1) (2026-02-27)
9+
10+
11+
### Bug Fixes
12+
13+
* **deps:** update dependency @adobe/spacecat-shared-utils to v1.98.0 ([#1872](https://github.com/adobe/spacecat-api-service/issues/1872)) ([9468258](https://github.com/adobe/spacecat-api-service/commit/94682580813c080b26a19997a019ff8ef9859213))
14+
15+
# [1.325.0](https://github.com/adobe/spacecat-api-service/compare/v1.324.2...v1.325.0) (2026-02-27)
16+
17+
18+
### Bug Fixes
19+
20+
* remove stale llmo-customer-analysis trigger assertion and revert deploy-dev label ([#1876](https://github.com/adobe/spacecat-api-service/issues/1876)) ([2921e30](https://github.com/adobe/spacecat-api-service/commit/2921e30be216fef4af3762cffa9c8ecd25368aa4)), closes [#1781](https://github.com/adobe/spacecat-api-service/issues/1781) [#1781](https://github.com/adobe/spacecat-api-service/issues/1781)
21+
22+
23+
### Features
24+
25+
* trigger DRS prompt generation during LLMO onboarding ([#1781](https://github.com/adobe/spacecat-api-service/issues/1781)) ([7ebc38f](https://github.com/adobe/spacecat-api-service/commit/7ebc38f95e24d886ca8ae8cbdc41e8a750479777))
26+
27+
## [1.324.2](https://github.com/adobe/spacecat-api-service/compare/v1.324.1...v1.324.2) (2026-02-27)
28+
29+
30+
### Bug Fixes
31+
32+
* move llmo onboarding publish trigger off request path ([#1870](https://github.com/adobe/spacecat-api-service/issues/1870)) ([f9a94be](https://github.com/adobe/spacecat-api-service/commit/f9a94be67041432dba5cee83fe226b3938fcc7ff))
33+
134
## [1.324.1](https://github.com/adobe/spacecat-api-service/compare/v1.324.0...v1.324.1) (2026-02-26)
235

336

package-lock.json

Lines changed: 479 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adobe/spacecat-api-service",
3-
"version": "1.324.1",
3+
"version": "1.326.0",
44
"description": "SpaceCat API Service",
55
"main": "src/index.js",
66
"type": "module",
@@ -87,7 +87,7 @@
8787
"@adobe/spacecat-shared-slack-client": "1.6.0",
8888
"@adobe/spacecat-shared-tier-client": "1.3.12",
8989
"@adobe/spacecat-shared-tokowaka-client": "1.10.0",
90-
"@adobe/spacecat-shared-utils": "1.96.3",
90+
"@adobe/spacecat-shared-utils": "1.98.0",
9191
"@aws-sdk/client-s3": "3.996.0",
9292
"@aws-sdk/client-secrets-manager": "3.996.0",
9393
"@aws-sdk/client-sfn": "3.996.0",

src/controllers/hooks.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import { isHelixSite } from '../support/utils.js';
3333
import { getHlxConfigMessagePart } from '../utils/slack/base.js';
3434

3535
const CDN_HOOK_SECRET_NAME = 'INCOMING_WEBHOOK_SECRET_CDN';
36-
3736
export const BUTTON_LABELS = {
3837
APPROVE_CUSTOMER: 'As Customer',
3938
APPROVE_FRIENDS_FAMILY: 'As Friends/Family',

src/controllers/llmo/llmo-onboarding.js

Lines changed: 55 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { composeBaseURL, tracingFetch as fetch, isNonEmptyArray } from '@adobe/s
1919
import AhrefsAPIClient from '@adobe/spacecat-shared-ahrefs-client';
2020
import { parse as parseDomain } from 'tldts';
2121
import { postSlackMessage } from '../../utils/slack/base.js';
22+
import DrsClient from '../../support/drs-client.js';
2223

2324
// LLMO Constants
2425
const LLMO_PRODUCT_CODE = EntitlementModel.PRODUCT_CODES.LLMO;
@@ -39,6 +40,7 @@ export const BASIC_AUDITS = [
3940
export const ASO_DEMO_ORG = '66331367-70e6-4a49-8445-4f6d9c265af9';
4041

4142
export const ASO_CRITICAL_SITES = [];
43+
const LLMO_ONBOARDING_PUBLISH_TRIGGER = 'trigger:llmo-onboarding-publish';
4244

4345
/**
4446
* Generates the data folder name from a domain.
@@ -254,53 +256,6 @@ export async function validateSiteNotOnboarded(baseURL, imsOrgId, dataFolder, co
254256
}
255257
}
256258

257-
/**
258-
* Publishes a file to admin.hlx.page.
259-
* @param {string} filename - The filename to publish
260-
* @param {string} outputLocation - The output location
261-
* @param {object} log - Logger instance
262-
*/
263-
async function publishToAdminHlx(filename, outputLocation, log) {
264-
try {
265-
const org = 'adobe';
266-
const site = 'project-elmo-ui-data';
267-
const ref = 'main';
268-
const jsonFilename = `${filename.replace(/\.[^/.]+$/, '')}.json`;
269-
const path = `${outputLocation}/${jsonFilename}`;
270-
const headers = { Cookie: `auth_token=${process.env.HLX_ADMIN_TOKEN}` };
271-
272-
if (!process.env.HLX_ADMIN_TOKEN) {
273-
log.warn('LLMO onboarding: HLX_ADMIN_TOKEN is not set');
274-
}
275-
276-
const baseUrl = 'https://admin.hlx.page';
277-
const endpoints = [
278-
{ name: 'preview', url: `${baseUrl}/preview/${org}/${site}/${ref}/${path}` },
279-
{ name: 'live', url: `${baseUrl}/live/${org}/${site}/${ref}/${path}` },
280-
];
281-
282-
for (const [index, endpoint] of endpoints.entries()) {
283-
log.debug(`Publishing Excel report via admin API (${endpoint.name}): ${endpoint.url}`);
284-
285-
// eslint-disable-next-line no-await-in-loop
286-
const response = await fetch(endpoint.url, { method: 'POST', headers });
287-
288-
if (!response.ok) {
289-
throw new Error(`${endpoint.name} failed: ${response.status} ${response.statusText}`);
290-
}
291-
292-
log.debug(`Excel report successfully published to ${endpoint.name}`);
293-
294-
if (index === 0) {
295-
// eslint-disable-next-line no-await-in-loop,max-statements-per-line
296-
await new Promise((resolve) => { setTimeout(resolve, 2000); });
297-
}
298-
}
299-
} catch (publishError) {
300-
log.error(`Failed to publish via admin.hlx.page: ${publishError.message}`);
301-
}
302-
}
303-
304259
/**
305260
* Starts a bulk status job for a given path.
306261
* @param {string} path - The folder path to get status for
@@ -545,9 +500,6 @@ export async function copyFilesToSharepoint(dataFolder, context, say = () => {})
545500
log.warn(`Warning: Query index at ${dataFolder} already exists. Skipping creation.`);
546501
await say(`Query index in ${dataFolder} already exists. Skipping creation.`);
547502
}
548-
549-
log.debug('Publishing query-index to admin.hlx.page');
550-
await publishToAdminHlx('query-index', dataFolder, log);
551503
}
552504

553505
/**
@@ -999,6 +951,22 @@ export async function triggerAudits(audits, context, site) {
999951
);
1000952
}
1001953

954+
export async function enqueueLlmoOnboardingPublish(context, site, dataFolder) {
955+
const { sqs, dataAccess, log } = context;
956+
const { Configuration } = dataAccess;
957+
const configuration = await Configuration.findLatest();
958+
959+
await sqs.sendMessage(configuration.getQueues().audits, {
960+
type: LLMO_ONBOARDING_PUBLISH_TRIGGER,
961+
siteId: site.getId(),
962+
auditContext: {
963+
dataFolder,
964+
},
965+
});
966+
967+
log.info(`Queued ${LLMO_ONBOARDING_PUBLISH_TRIGGER} for site ${site.getId()}`);
968+
}
969+
1002970
/**
1003971
* Complete LLMO onboarding process.
1004972
* @param {object} params - Onboarding parameters
@@ -1039,6 +1007,12 @@ export async function performLlmoOnboarding(params, context, say = () => {}) {
10391007
// Copy files to SharePoint
10401008
await copyFilesToSharepoint(dataFolder, context, say);
10411009

1010+
try {
1011+
await enqueueLlmoOnboardingPublish(context, site, dataFolder);
1012+
} catch (error) {
1013+
log.warn(`Failed to enqueue ${LLMO_ONBOARDING_PUBLISH_TRIGGER} for site ${site.getId()}: ${error.message}`);
1014+
}
1015+
10421016
// Update index config
10431017
await updateIndexConfig(dataFolder, context, say);
10441018

@@ -1083,8 +1057,37 @@ export async function performLlmoOnboarding(params, context, say = () => {}) {
10831057
site.setConfig(Config.toDynamoItem(siteConfig));
10841058
await site.save();
10851059

1086-
// Trigger audits
1087-
await triggerAudits([...BASIC_AUDITS, 'llmo-customer-analysis', 'wikipedia-analysis'], context, site);
1060+
// Trigger audits (llmo-customer-analysis is NOT triggered here; it will be triggered
1061+
// after the DRS prompt generation job completes, via SNS → audit-worker. LLMO-1819)
1062+
await triggerAudits([...BASIC_AUDITS, 'wikipedia-analysis'], context, site);
1063+
1064+
// Submit DRS prompt generation job (non-blocking)
1065+
try {
1066+
const drsClient = DrsClient(context);
1067+
if (drsClient.isConfigured()) {
1068+
// Try to get audience from brand profile, fall back to default
1069+
const brandProfile = siteConfig.getBrandProfile?.();
1070+
const audience = brandProfile?.main_profile?.target_audience
1071+
|| `General consumers interested in ${brandName} products and services`;
1072+
1073+
const drsJob = await drsClient.submitPromptGenerationJob({
1074+
baseUrl: baseURL,
1075+
brandName: brandName.trim(),
1076+
audience,
1077+
region: 'US',
1078+
numPrompts: 42,
1079+
siteId: site.getId(),
1080+
imsOrgId,
1081+
});
1082+
log.info(`Started DRS prompt generation: job=${drsJob.job_id}`);
1083+
say(`:robot_face: Started DRS prompt generation job: ${drsJob.job_id}`);
1084+
} else {
1085+
log.debug('DRS client not configured, skipping prompt generation');
1086+
}
1087+
} catch (drsError) {
1088+
log.error(`Failed to start DRS prompt generation: ${drsError.message}`);
1089+
say(':warning: Failed to start DRS prompt generation (will need manual trigger)');
1090+
}
10881091

10891092
return {
10901093
site,

src/support/drs-client.js

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2026 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import { tracingFetch as fetch } from '@adobe/spacecat-shared-utils';
14+
15+
/**
16+
* Client for interacting with the Data Retrieval Service (DRS) API.
17+
* Used to submit prompt generation jobs for LLMO onboarding.
18+
*
19+
* @param {object} context - The request context
20+
* @returns {object} DRS client with methods for submitting jobs
21+
*/
22+
export default function DrsClient(context) {
23+
const { env, log } = context;
24+
const drsApiUrl = env.DRS_API_URL;
25+
const drsApiKey = env.DRS_API_KEY;
26+
27+
/**
28+
* Checks if the DRS client is properly configured.
29+
* @returns {boolean} True if DRS_API_URL and DRS_API_KEY are set
30+
*/
31+
function isConfigured() {
32+
return Boolean(drsApiUrl && drsApiKey);
33+
}
34+
35+
/**
36+
* Submits a prompt generation job to DRS.
37+
*
38+
* @param {object} params - Job parameters
39+
* @param {string} params.baseUrl - The base URL of the site
40+
* @param {string} params.brandName - The brand name
41+
* @param {string} params.audience - Target audience description
42+
* @param {string} [params.region='US'] - Geographic region for prompts
43+
* @param {number} [params.numPrompts=42] - Number of prompts to generate
44+
* @param {string} params.siteId - The SpaceCat site ID
45+
* @param {string} params.imsOrgId - The Adobe IMS organization ID
46+
* @returns {Promise<object>} Job submission result with job_id
47+
* @throws {Error} If job submission fails or DRS is not configured
48+
*/
49+
async function submitPromptGenerationJob({
50+
baseUrl,
51+
brandName,
52+
audience,
53+
region = 'US',
54+
numPrompts = 42,
55+
siteId,
56+
imsOrgId,
57+
}) {
58+
if (!isConfigured()) {
59+
throw new Error('DRS client is not configured. Set DRS_API_URL and DRS_API_KEY environment variables.');
60+
}
61+
62+
const payload = {
63+
provider_id: 'prompt_generation_base_url',
64+
source: 'onboarding',
65+
parameters: {
66+
base_url: baseUrl,
67+
brand_name: brandName,
68+
audience,
69+
region,
70+
num_prompts: numPrompts,
71+
model: 'gpt-5-nano',
72+
metadata: {
73+
site_id: siteId,
74+
imsOrgId,
75+
base_url: baseUrl,
76+
brand: brandName,
77+
region,
78+
},
79+
},
80+
};
81+
82+
log.info(`Submitting DRS prompt generation job for site ${siteId}`, {
83+
baseUrl,
84+
brandName,
85+
region,
86+
numPrompts,
87+
});
88+
89+
const response = await fetch(`${drsApiUrl}/jobs`, {
90+
method: 'POST',
91+
headers: {
92+
'Content-Type': 'application/json',
93+
'x-api-key': drsApiKey,
94+
},
95+
body: JSON.stringify(payload),
96+
});
97+
98+
if (!response.ok) {
99+
const errorText = await response.text();
100+
throw new Error(`DRS job submission failed: ${response.status} - ${errorText}`);
101+
}
102+
103+
const result = await response.json();
104+
log.info(`DRS job submitted successfully: ${result.job_id}`, {
105+
siteId,
106+
jobId: result.job_id,
107+
});
108+
109+
return result;
110+
}
111+
112+
return {
113+
isConfigured,
114+
submitPromptGenerationJob,
115+
};
116+
}

src/support/slack/commands.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import revokeEntitlementImsOrg from './commands/revoke-entitlement-imsorg.js';
4646
import detectBotBlocker from './commands/detect-bot-blocker.js';
4747
import runPageCitability from './commands/run-page-citability.js';
4848
import runA11yCodefix from './commands/run-a11y-codefix.js';
49+
import identifyRedirects from './commands/identify-redirects.js';
4950

5051
/**
5152
* Returns all commands.
@@ -90,4 +91,5 @@ export default (context) => [
9091
detectBotBlocker(context),
9192
runPageCitability(context),
9293
runA11yCodefix(context),
94+
identifyRedirects(context),
9395
];

0 commit comments

Comments
 (0)