Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions src/accessibility/utils/data-processing.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { isoCalendarWeek } from '@adobe/spacecat-shared-utils';
import { getObjectFromKey, getObjectKeysUsingPrefix } from '../../utils/s3-utils.js';
import { extractMainDomainName } from '../../support/utils.js';
import { warnOnInvalidSuggestionData } from '../../utils/data-access.js';
import {
createReportOpportunitySuggestionInstance,
createInDepthReportOpportunity,
Expand Down Expand Up @@ -652,6 +653,7 @@ export async function createOrUpdateDeviceSpecificSuggestion(
// Update only the suggestionValue field to avoid ElectroDB timestamp conflicts
const newData = { ...currentData, suggestionValue: suggestions[0].data.suggestionValue };

warnOnInvalidSuggestionData(newData, opportunity.getType(), log);
existingSuggestion.setData(newData);
await existingSuggestion.save();

Expand Down
2 changes: 2 additions & 0 deletions src/accessibility/utils/generate-individual-opportunities.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { createAccessibilityAssistiveOpportunity, createAccessibilityColorContra
import {
syncSuggestions,
keepSameDataFunction,
warnOnInvalidSuggestionData,
} from '../../utils/data-access.js';
import {
successCriteriaLinks, accessibilityOpportunitiesMap, URL_SOURCE_SEPARATOR, issueTypesForCodeFix,
Expand Down Expand Up @@ -1106,6 +1107,7 @@ export async function handleAccessibilityRemediationGuidance(message, context) {
};

// Update the suggestion
warnOnInvalidSuggestionData(updatedSuggestionData, opportunity.getType(), log);
targetSuggestion.setData(updatedSuggestionData);
processingPromises.push({
promise: targetSuggestion.save(),
Expand Down
8 changes: 5 additions & 3 deletions src/backlinks/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { AuditBuilder } from '../common/audit-builder.js';
import calculateKpiMetrics from './kpi-metrics.js';
import { convertToOpportunity } from '../common/opportunity.js';
import { createOpportunityData } from './opportunity-data-mapper.js';
import { syncSuggestionsWithPublishDetection } from '../utils/data-access.js';
import { syncSuggestionsWithPublishDetection, warnOnInvalidSuggestionData } from '../utils/data-access.js';
import { filterByAuditScope, extractPathPrefix } from '../internal-links/subpath-filter.js';
import {
filterBrokenSuggestedUrls,
Expand Down Expand Up @@ -420,11 +420,13 @@ export const generateSuggestionData = async (context) => {
return;
}

suggestion.setData({
const updatedData = {
...suggestion.getData(),
urlsSuggested,
aiRationale: `The suggested URL is chosen based on top search results for closely matching keywords from the broken URL. Keywords used: "${keywords}".`,
});
};
warnOnInvalidSuggestionData(updatedData, opportunity.getType(), log);
suggestion.setData(updatedData);

await suggestion.save();
resolvedByBrightData.add(brokenLink.suggestionId);
Expand Down
7 changes: 5 additions & 2 deletions src/broken-links-guidance/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { badRequest, notFound, ok } from '@adobe/spacecat-shared-http-utils';
import { isValidUrl } from '@adobe/spacecat-shared-utils';
import { filterBrokenSuggestedUrls } from '../utils/url-utils.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

export default async function handler(message, context) {
const { log, dataAccess } = context;
Expand Down Expand Up @@ -103,11 +104,13 @@ export default async function handler(message, context) {
aiRationale = '';
}

suggestion.setData({
const updatedData = {
...suggestion.getData(),
urlsSuggested: filteredSuggestedUrls,
aiRationale,
});
};
warnOnInvalidSuggestionData(updatedData, opportunity.getType(), log);
suggestion.setData(updatedData);

return suggestion.save();
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { notFound, ok } from '@adobe/spacecat-shared-http-utils';
import { Suggestion as SuggestionModel } from '@adobe/spacecat-shared-data-access';
import { convertToOpportunityEntity } from './opportunity-data-mapper.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';
/**
* Checks if any suggestions in the array were manually modified (updatedBy !== 'system')
* @param {Array} suggestions - Array of suggestion objects
Expand Down Expand Up @@ -96,6 +97,7 @@ export default async function handler(message, context) {
},
};

warnOnInvalidSuggestionData(suggestionData.data, opportunity.getType(), log);
await Suggestion.create(suggestionData);

return ok();
Expand Down
5 changes: 5 additions & 0 deletions src/image-alt-text/opportunityHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
CPC, PENALTY_PER_IMAGE, RUM_INTERVAL, ALT_TEXT_GUIDANCE_TYPE, ALT_TEXT_OBSERVATION,
MYSTIQUE_BATCH_SIZE,
} from './constants.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const AUDIT_TYPE = AuditModel.AUDIT_TYPES.ALT_TEXT;

Expand Down Expand Up @@ -53,6 +54,8 @@ export async function syncAltTextSuggestions({ opportunity, newSuggestionDTOs, l

// Add new suggestions to oppty
if (isNonEmptyArray(suggestionsToAdd)) {
const opportunityType = opportunity.getType?.();
suggestionsToAdd.forEach((s) => warnOnInvalidSuggestionData(s.data, opportunityType, log));
const updateResult = await opportunity.addSuggestions(suggestionsToAdd);

if (isNonEmptyArray(updateResult.errorItems)) {
Expand Down Expand Up @@ -251,6 +254,8 @@ export async function addAltTextSuggestions({ opportunity, newSuggestionDTOs, lo
return;
}

const opportunityType = opportunity.getType?.();
newSuggestionDTOs.forEach((s) => warnOnInvalidSuggestionData(s.data, opportunityType, log));
const updateResult = await opportunity.addSuggestions(newSuggestionDTOs);

if (isNonEmptyArray(updateResult.errorItems)) {
Expand Down
7 changes: 5 additions & 2 deletions src/internal-links/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
import { createAuditLogger } from '../common/context-logger.js';
import BrightDataClient from '../support/bright-data-client.js';
import { sleep } from '../support/utils.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const { AUDIT_STEP_DESTINATIONS } = Audit;
const INTERVAL = 30; // days
Expand Down Expand Up @@ -573,11 +574,13 @@ export const opportunityAndSuggestionsStep = async (context) => {
return;
}

suggestion.setData({
const updatedData = {
...suggestion.getData(),
urlsSuggested,
aiRationale: `The suggested URL is chosen based on top search results for closely matching keywords from the broken URL. Keywords used: "${keywords}".`,
});
};
warnOnInvalidSuggestionData(updatedData, opportunity.getType(), log);
suggestion.setData(updatedData);

await suggestion.save();
resolvedByBrightData.add(brokenLink.suggestionId);
Expand Down
7 changes: 5 additions & 2 deletions src/metatags-guidance/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { badRequest, notFound, ok } from '@adobe/spacecat-shared-http-utils';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

export default async function handler(message, context) {
const { log, dataAccess } = context;
Expand Down Expand Up @@ -75,11 +76,13 @@ export default async function handler(message, context) {
);
}

suggestion.setData({
const updatedData = {
...suggestion.getData(),
aiSuggestion: aiSuggestion || '',
aiRationale: aiRationale || '',
});
};
warnOnInvalidSuggestionData(updatedData, opportunity.getType(), log);
suggestion.setData(updatedData);

return suggestion.save();
}));
Expand Down
2 changes: 2 additions & 0 deletions src/no-cta-above-the-fold/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
mapToSuggestion,
} from './guidance-opportunity-mapper.js';
import { createPaidLogger } from '../paid/paid-log.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const GUIDANCE_TYPE = 'no-cta-above-the-fold';

Expand Down Expand Up @@ -87,6 +88,7 @@ export default async function handler(message, context) {
guidanceParsed,
);

warnOnInvalidSuggestionData(suggestionData.data, opportunity.getType(), log);
await Suggestion.create(suggestionData);
paidLog.createdSuggestion(opportunity.getId(), siteId, url, auditId);

Expand Down
2 changes: 2 additions & 0 deletions src/paid-cookie-consent/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ok, notFound } from '@adobe/spacecat-shared-http-utils';
import { mapToPaidOpportunity, mapToPaidSuggestion, isLowSeverityGuidanceBody } from './guidance-opportunity-mapper.js';
import { getAuditData } from './audit-data-provider.js';
import { createPaidLogger } from '../paid/paid-log.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const GUIDANCE_TYPE = 'paid-cookie-consent';

Expand Down Expand Up @@ -67,6 +68,7 @@ export default async function handler(message, context) {
url,
guidanceParsed,
);
warnOnInvalidSuggestionData(suggestionData.data, opportunity.getType(), log);
await Suggestion.create(suggestionData);
paidLog.createdSuggestion(opportunity.getId(), siteId, url, auditId);

Expand Down
2 changes: 2 additions & 0 deletions src/paid-keyword-optimizer/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isLowSeverityGuidanceBody,
} from './guidance-opportunity-mapper.js';
import { createPaidLogger } from '../paid/paid-log.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const GUIDANCE_TYPE = 'ad-intent-mismatch';

Expand Down Expand Up @@ -70,6 +71,7 @@ export default async function handler(message, context) {
opportunity.getId(),
message,
);
warnOnInvalidSuggestionData(suggestionData.data, opportunity.getType(), log);
await Suggestion.create(suggestionData);
paidLog.createdSuggestion(opportunity.getId(), siteId, url, auditId);

Expand Down
12 changes: 8 additions & 4 deletions src/paid-traffic-analysis/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { randomUUID } from 'crypto';
import { Suggestion as SuggestionModel } from '@adobe/spacecat-shared-data-access';
import { DATA_SOURCES } from '../common/constants.js';
import { createPaidLogger } from '../paid/paid-log.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const GUIDANCE_TYPE = 'guidance:traffic-analysis';
const TRAFFIC_OPP_TYPE = 'paid-traffic';
Expand Down Expand Up @@ -129,10 +130,13 @@ export default async function handler(message, context) {
? SuggestionModel.STATUSES.PENDING_VALIDATION
: SuggestionModel.STATUSES.NEW;

await Promise.all(suggestions.map((s) => Suggestion.create({
...s,
status,
})));
await Promise.all(suggestions.map((s) => {
warnOnInvalidSuggestionData(s.data, opportunity.getType(), log);
return Suggestion.create({
...s,
status,
});
}));
}

// Only after successful opportunity and suggestions creation, ignore previous ones
Expand Down
2 changes: 2 additions & 0 deletions src/prerender/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import { badRequest, notFound, ok } from '@adobe/spacecat-shared-http-utils';
import { isPaidLLMOCustomer } from './utils/utils.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const LOG_PREFIX = 'Prerender -';

Expand Down Expand Up @@ -192,6 +193,7 @@ export default async function handler(message, context) {
valuable: isValuable,
};

warnOnInvalidSuggestionData(updatedData, opportunity.getType(), log);
existing.setData(updatedData);
suggestionsToSave.push(existing);
});
Expand Down
2 changes: 2 additions & 0 deletions src/readability/opportunities/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { ok, notFound } from '@adobe/spacecat-shared-http-utils';
import { warnOnInvalidSuggestionData } from '../../utils/data-access.js';

/**
* Enriches suggestion data with fileds required for auto-optimize.
Expand Down Expand Up @@ -181,6 +182,7 @@ export default async function handler(message, context) {
// Enrich with auto-optimize data only after validating improvedText
const enrichedData = enrichSuggestionDataForAutoOptimize(updatedData);

warnOnInvalidSuggestionData(enrichedData, readabilityOpportunity.getType(), log);
await matchingSuggestion.setData(enrichedData);
await matchingSuggestion.save();

Expand Down
7 changes: 5 additions & 2 deletions src/structured-data/guidance-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { badRequest, notFound, ok } from '@adobe/spacecat-shared-http-utils';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

export default async function handler(message, context) {
const { log, dataAccess } = context;
Expand Down Expand Up @@ -53,7 +54,7 @@ export default async function handler(message, context) {
return notFound('Suggestion not found');
}

suggestion.setData({
const updatedData = {
...suggestion.getData(),
errors: (remediations || []).map((remediation) => (
{
Expand All @@ -62,7 +63,9 @@ export default async function handler(message, context) {
errorTitle: remediation.error_title,
fix: remediation.corrected_markup,
})),
});
};
warnOnInvalidSuggestionData(updatedData, opportunity.getType(), log);
suggestion.setData(updatedData);
await suggestion.save();
return ok();
}
26 changes: 25 additions & 1 deletion src/utils/data-access.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@ export const AUTHOR_ONLY_OPPORTUNITY_TYPES = [
'security-permissions',
];

/**
* Validates suggestion data against the Joi schema for the given opportunity type.
* Logs a warning if validation fails but does not block the operation.
*
* @param {Object} data - Suggestion data to validate.
* @param {string} opportunityType - The opportunity type from OPPORTUNITY_TYPES enum.
* @param {Object} log - Logger object.
*/
export function warnOnInvalidSuggestionData(data, opportunityType, log) {
try {
SuggestionDataAccess.validateData(data, opportunityType);
} catch (error) {
log.warn(`Suggestion data validation warning [${opportunityType}]: ${error.message}`);
}
}

/**
* Safely stringify an object for logging, truncating large arrays to prevent
* exceeding JavaScript's maximum string length.
Expand Down Expand Up @@ -351,6 +367,8 @@ export async function syncSuggestions({

log.debug(`Existing suggestions = ${existingSuggestions.length}: ${safeStringify(existingSuggestions)}`);

const opportunityType = opportunity.getType?.();

// Update existing suggestions - O(N) with Map lookup
await Promise.all(
existingSuggestions
Expand All @@ -361,7 +379,9 @@ export async function syncSuggestions({
.map((existing) => {
const existingKey = buildKey(existing.getData());
const newDataItem = newDataByKey.get(existingKey);
existing.setData(mergeDataFunction(existing.getData(), newDataItem));
const mergedData = mergeDataFunction(existing.getData(), newDataItem);
warnOnInvalidSuggestionData(mergedData, opportunityType, log);
existing.setData(mergedData);

// Use the merge status function to determine if status should change
const newStatus = mergeStatusFunction(existing, newDataItem, context);
Expand All @@ -387,6 +407,10 @@ export async function syncSuggestions({
status: requiresValidation ? SuggestionDataAccess.STATUSES.PENDING_VALIDATION
: SuggestionDataAccess.STATUSES.NEW,
};
})
.map((suggestion) => {
warnOnInvalidSuggestionData(suggestion.data, opportunityType, log);
return suggestion;
});

// Add new suggestions if any
Expand Down
7 changes: 5 additions & 2 deletions src/vulnerabilities-code-fix/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { badRequest, notFound, ok } from '@adobe/spacecat-shared-http-utils';
import { Audit } from '@adobe/spacecat-shared-data-access';
import { isNonEmptyArray } from '@adobe/spacecat-shared-utils';
import { getObjectFromKey } from '../utils/s3-utils.js';
import { warnOnInvalidSuggestionData } from '../utils/data-access.js';

const AUDIT_TYPE = Audit.AUDIT_TYPES.SECURITY_VULNERABILITIES;

Expand Down Expand Up @@ -191,11 +192,13 @@ export default async function handler(message, context) {
return;
}

suggestion.setData({
const updatedData = {
...suggestion.getData(),
patchContent: reportData.diff,
isCodeChangeAvailable: true,
});
};
warnOnInvalidSuggestionData(updatedData, AUDIT_TYPE, log);
suggestion.setData(updatedData);

suggestion.save();
}));
Expand Down
1 change: 1 addition & 0 deletions test/audits/accessibility/data-processing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5529,6 +5529,7 @@ describe('data-processing utility functions', () => {
mockOpportunity = {
getSuggestions: sandbox.stub().resolves([mockExistingSuggestion]),
addSuggestions: sandbox.stub().resolves({ id: 'new-sugg' }),
getType: () => 'accessibility',
};
});

Expand Down
Loading