Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,265 changes: 1,800 additions & 1,465 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 9 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/spacecat-api-service",
"version": "1.208.1",
"version": "1.202.1",
"description": "SpaceCat API Service",
"main": "src/index.js",
"type": "module",
Expand All @@ -21,12 +21,12 @@
"build": "hedy -v --test-bundle",
"deploy": "hedy -v --deploy --aws-deploy-bucket=spacecat-prod-deploy --pkgVersion=latest",
"deploy-stage": "hedy -v --deploy --aws-deploy-bucket=spacecat-stage-deploy --pkgVersion=latest",
"deploy-dev": "hedy -v --deploy --pkgVersion=latest$CI_BUILD_NUM -l latest --aws-deploy-bucket=spacecat-dev-deploy --cleanup-ci=24h --aws-api vldld6qz1d",
"deploy-dev": "hedy -v --deploy --pkgVersion=latest$CI_BUILD_NUM -l dipratap --aws-deploy-bucket=spacecat-dev-deploy --cleanup-ci=24h --aws-api vldld6qz1d",
"deploy-secrets": "hedy --aws-update-secrets --params-file=secrets/secrets.env",
"docs": "npm run docs:lint && npm run docs:build",
"docs:build": "npx @redocly/cli build-docs -o ./docs/index.html --config docs/openapi/redocly-config.yaml",
"docs:lint": "npx @redocly/cli lint --config docs/openapi/redocly-config.yaml",
"docs:serve": "npx @redocly/cli preview --project-dir docs/openapi --product redoc",
"docs:serve": "npx @redocly/cli preview-docs --config docs/openapi/redocly-config.yaml",
"prepare": "husky"
},
"wsk": {
Expand Down Expand Up @@ -74,14 +74,15 @@
"@adobe/spacecat-helix-content-sdk": "1.4.24",
"@adobe/spacecat-shared-athena-client": "1.3.5",
"@adobe/spacecat-shared-brand-client": "1.1.24",
"@adobe/spacecat-shared-data-access": "2.73.1",
"@adobe/spacecat-shared-data-access": "https://gitpkg.now.sh/adobe/spacecat-shared/packages/spacecat-shared-data-access?tokowaka-json",
"@adobe/spacecat-shared-gpt-client": "1.6.5",
"@adobe/spacecat-shared-http-utils": "1.17.7",
"@adobe/spacecat-shared-http-utils": "1.17.6",
"@adobe/spacecat-shared-ims-client": "1.8.13",
"@adobe/spacecat-shared-rum-api-client": "2.38.2",
"@adobe/spacecat-shared-rum-api-client": "2.38.1",
"@adobe/spacecat-shared-scrape-client": "2.1.5",
"@adobe/spacecat-shared-slack-client": "1.5.26",
"@adobe/spacecat-shared-tier-client": "1.2.0",
"@adobe/spacecat-shared-tokowaka-client": "https://gitpkg.now.sh/adobe/spacecat-shared/packages/spacecat-shared-tokowaka-client?tokowaka-json",
"@adobe/spacecat-shared-utils": "1.59.4",
"@aws-sdk/client-s3": "3.893.0",
"@aws-sdk/client-sfn": "3.893.0",
Expand All @@ -107,11 +108,11 @@
"zod": "3.25.76"
},
"devDependencies": {
"@adobe/eslint-config-helix": "3.0.12",
"@adobe/eslint-config-helix": "3.0.11",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to downgrade?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a previous package.json that I used to deploy on personal namespace. I have updated the latest package.json now.

"@adobe/helix-deploy": "https://gitpkg.now.sh/alinarublea/helix-deploy?main",
"@adobe/helix-universal": "5.2.3",
"@adobe/helix-universal-devserver": "1.1.141",
"@adobe/semantic-release-coralogix": "1.1.40",
"@adobe/semantic-release-coralogix": "1.1.39",
"@adobe/semantic-release-skms-cmr": "1.1.5",
"@eslint/config-helpers": "0.3.1",
"@redocly/cli": "2.1.5",
Expand Down
150 changes: 150 additions & 0 deletions src/controllers/suggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '@adobe/spacecat-shared-utils';

import { ValidationError, Suggestion as SuggestionModel, Site as SiteModel } from '@adobe/spacecat-shared-data-access';
import TokowakaClient from '@adobe/spacecat-shared-tokowaka-client';
import { SuggestionDto } from '../dto/suggestion.js';
import { FixDto } from '../dto/fix.js';
import { sendAutofixMessage, getCSPromiseToken, ErrorWithStatusCode } from '../support/utils.js';
Expand Down Expand Up @@ -704,9 +705,158 @@ function SuggestionsController(ctx, sqs, env) {
}
};

/**
* Deploys suggestions through Tokowaka edge delivery
* @param {Object} context of the request
* @returns {Promise<Response>} Deployment response
*/
const deploySuggestionToEdge = async (context) => {
const siteId = context.params?.siteId;
const opportunityId = context.params?.opportunityId;

if (!isValidUUID(siteId)) {
return badRequest('Site ID required');
}

if (!isValidUUID(opportunityId)) {
return badRequest('Opportunity ID required');
}

// validate request body
if (!isNonEmptyObject(context.data)) {
return badRequest('No data provided');
}
const { suggestionIds } = context.data;
if (!isArray(suggestionIds) || suggestionIds.length === 0) {
return badRequest('Request body must contain a non-empty array of suggestionIds');
}

const site = await Site.findById(siteId);
if (!site) {
return notFound('Site not found');
}

if (!await accessControlUtil.hasAccess(site)) {
return forbidden('User does not belong to the organization');
}

const opportunity = await Opportunity.findById(opportunityId);
if (!opportunity || opportunity.getSiteId() !== siteId) {
return notFound('Opportunity not found');
}

// Fetch all suggestions for this opportunity
const allSuggestions = await Suggestion.allByOpportunityId(opportunityId);

// Track valid, failed, and missing suggestions
const validSuggestions = [];
const failedSuggestions = [];

// Check each requested suggestion (basic validation only)
suggestionIds.forEach((suggestionId, index) => {
const suggestion = allSuggestions.find((s) => s.getId() === suggestionId);

if (!suggestion) {
failedSuggestions.push({
uuid: suggestionId,
index,
message: 'Suggestion not found',
statusCode: 404,
});
} else if (suggestion.getStatus() !== SuggestionModel.STATUSES.NEW) {
failedSuggestions.push({
uuid: suggestionId,
index,
message: 'Suggestion is not in NEW status',
statusCode: 400,
});
} else {
validSuggestions.push(suggestion);
}
});

let succeededSuggestions = [];

// Only attempt deployment if we have valid suggestions
if (isNonEmptyArray(validSuggestions)) {
try {
const tokowakaClient = TokowakaClient.createFrom(context);
const deploymentResult = await tokowakaClient.deploySuggestions(
site,
opportunity,
validSuggestions,
);

// Process deployment results
const {
succeededSuggestions: deployedSuggestions,
failedSuggestions: ineligibleSuggestions,
} = deploymentResult;

// Update successfully deployed suggestions with deployment timestamp
const deploymentTimestamp = Date.now();
succeededSuggestions = await Promise.all(
deployedSuggestions.map(async (suggestion) => {
const currentData = suggestion.getData();
suggestion.setData({
...currentData,
tokowakaDeployed: deploymentTimestamp,
});
suggestion.setUpdatedBy('tokowaka-deployment');
return suggestion.save();
}),
);

// Add ineligible suggestions to failed list
ineligibleSuggestions.forEach((item) => {
failedSuggestions.push({
uuid: item.suggestion.getId(),
index: suggestionIds.indexOf(item.suggestion.getId()),
message: item.reason,
statusCode: 400,
});
});

context.log.info(`Successfully deployed ${succeededSuggestions.length} suggestions to Edge`);
} catch (error) {
context.log.error(`Error deploying to Tokowaka: ${error.message}`, error);
// If deployment fails, mark all valid suggestions as failed
validSuggestions.forEach((suggestion) => {
failedSuggestions.push({
uuid: suggestion.getId(),
index: suggestionIds.indexOf(suggestion.getId()),
message: 'Deployment failed: Internal server error',
statusCode: 500,
});
});
}
}

const response = {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make sure these suggestion props are not overwritten by audit worker while updating the suggestions.

suggestions: [
...succeededSuggestions.map((suggestion) => ({
uuid: suggestion.getId(),
index: suggestionIds.indexOf(suggestion.getId()),
statusCode: 200,
suggestion: SuggestionDto.toJSON(suggestion),
})),
...failedSuggestions,
],
metadata: {
total: suggestionIds.length,
success: succeededSuggestions.length,
failed: failedSuggestions.length,
},
};
response.suggestions.sort((a, b) => a.index - b.index);

return createResponse(response, 207);
};

return {
autofixSuggestions,
createSuggestions,
deploySuggestionToEdge,
getAllForOpportunity,
getByID,
getByStatus,
Expand Down
1 change: 1 addition & 0 deletions src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ export default function getRouteHandlers(
'DELETE /sites/:siteId/opportunities/:opportunityId': opportunitiesController.removeOpportunity,
'GET /sites/:siteId/opportunities/:opportunityId/suggestions': suggestionsController.getAllForOpportunity,
'PATCH /sites/:siteId/opportunities/:opportunityId/suggestions/auto-fix': suggestionsController.autofixSuggestions,
'POST /sites/:siteId/opportunities/:opportunityId/suggestions/edge-deploy': suggestionsController.deploySuggestionToEdge,
'GET /sites/:siteId/opportunities/:opportunityId/suggestions/by-status/:status': suggestionsController.getByStatus,
'GET /sites/:siteId/opportunities/:opportunityId/suggestions/:suggestionId': suggestionsController.getByID,
'GET /sites/:siteId/opportunities/:opportunityId/suggestions/:suggestionId/fixes': suggestionsController.getSuggestionFixes,
Expand Down
10 changes: 10 additions & 0 deletions test/controllers/audits.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -668,6 +669,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -706,6 +708,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -747,6 +750,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -828,6 +832,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -870,6 +875,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -910,6 +916,7 @@ describe('Audits Controller', () => {
getImports: () => [],
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -949,6 +956,7 @@ describe('Audits Controller', () => {
getImports: () => [],
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -987,6 +995,7 @@ describe('Audits Controller', () => {
getHandlers: () => (({ [auditType]: {} })),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
});

const result = await auditsController.patchAuditForSite(context);
Expand Down Expand Up @@ -1051,6 +1060,7 @@ describe('Audits Controller', () => {
getBrandConfig: () => ({ brandId: 'test-brand' }),
getCdnLogsConfig: () => ({}),
getLlmoConfig: () => ({}),
getTokowakaConfig: () => ({}),
};

site.getConfig = () => siteConfig;
Expand Down
Loading