Skip to content

Commit 862b341

Browse files
authored
Merge branch 'main' into feat/oppty-workspace-email-notify
2 parents 5509211 + 7ebc38f commit 862b341

File tree

12 files changed

+1261
-160
lines changed

12 files changed

+1261
-160
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
## [1.324.2](https://github.com/adobe/spacecat-api-service/compare/v1.324.1...v1.324.2) (2026-02-27)
2+
3+
4+
### Bug Fixes
5+
6+
* 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))
7+
8+
## [1.324.1](https://github.com/adobe/spacecat-api-service/compare/v1.324.0...v1.324.1) (2026-02-26)
9+
10+
11+
### Bug Fixes
12+
13+
* **deps:** update adobe fixes ([#1862](https://github.com/adobe/spacecat-api-service/issues/1862)) ([051f780](https://github.com/adobe/spacecat-api-service/commit/051f7802b80c0d063022af281f334b36deb6108a))
14+
15+
# [1.324.0](https://github.com/adobe/spacecat-api-service/compare/v1.323.2...v1.324.0) (2026-02-26)
16+
17+
18+
### Features
19+
20+
* auto-resolve author URL and code config from RUM bundles during onboarding ([#1856](https://github.com/adobe/spacecat-api-service/issues/1856)) ([36a164b](https://github.com/adobe/spacecat-api-service/commit/36a164beac32801f966f3d818c080f620804b85f))
21+
122
## [1.323.2](https://github.com/adobe/spacecat-api-service/compare/v1.323.1...v1.323.2) (2026-02-25)
223

324

package-lock.json

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

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@adobe/spacecat-api-service",
3-
"version": "1.323.2",
3+
"version": "1.324.2",
44
"description": "SpaceCat API Service",
55
"main": "src/index.js",
66
"type": "module",
@@ -22,7 +22,7 @@
2222
"build": "hedy -v --test-bundle",
2323
"deploy": "hedy -v --deploy --aws-deploy-bucket=spacecat-prod-deploy --pkgVersion=latest",
2424
"deploy-stage": "hedy -v --deploy --aws-deploy-bucket=spacecat-stage-deploy --pkgVersion=latest",
25-
"deploy-dev": "hedy -v --deploy --pkgVersion=latest$CI_BUILD_NUM -l latest --aws-deploy-bucket=spacecat-dev-deploy --cleanup-ci=24h",
25+
"deploy-dev": "hedy -v --deploy --pkgVersion=latest$CI_BUILD_NUM -l dzehnder --aws-deploy-bucket=spacecat-dev-deploy --cleanup-ci=24h",
2626
"deploy-secrets": "hedy --aws-update-secrets --params-file=secrets/secrets.env",
2727
"docs": "npm run docs:lint && npm run docs:build",
2828
"docs:build": "npx @redocly/cli build-docs -o ./docs/index.html --config docs/openapi/redocly-config.yaml",
@@ -77,7 +77,7 @@
7777
"@adobe/spacecat-shared-ahrefs-client": "1.10.5",
7878
"@adobe/spacecat-shared-athena-client": "1.9.4",
7979
"@adobe/spacecat-shared-brand-client": "1.1.36",
80-
"@adobe/spacecat-shared-data-access": "3.2.0",
80+
"@adobe/spacecat-shared-data-access": "3.4.0",
8181
"@adobe/spacecat-shared-data-access-v2": "npm:@adobe/spacecat-shared-data-access@2.109.0",
8282
"@adobe/spacecat-shared-gpt-client": "1.6.17",
8383
"@adobe/spacecat-shared-http-utils": "1.21.0",
@@ -86,7 +86,7 @@
8686
"@adobe/spacecat-shared-scrape-client": "2.5.1",
8787
"@adobe/spacecat-shared-slack-client": "1.6.0",
8888
"@adobe/spacecat-shared-tier-client": "1.3.12",
89-
"@adobe/spacecat-shared-tokowaka-client": "1.9.0",
89+
"@adobe/spacecat-shared-tokowaka-client": "1.10.0",
9090
"@adobe/spacecat-shared-utils": "1.96.3",
9191
"@aws-sdk/client-s3": "3.996.0",
9292
"@aws-sdk/client-secrets-manager": "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+
}

0 commit comments

Comments
 (0)