Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
541f0ba
fix: align form accessibility suggestion creation with sites
dmaurya929 Aug 31, 2025
de62758
fix: source extraction
dmaurya929 Aug 31, 2025
95eab58
fix: add understanding url
dmaurya929 Sep 1, 2025
76e77e6
fix: failing test
dmaurya929 Sep 1, 2025
7021d5c
fix: add form specific rules
dmaurya929 Sep 2, 2025
8563fd7
feat: add AccessibilityCodeChangeHandler to process code change updates
dmaurya929 Sep 8, 2025
d26e7e4
fix: mystique message handling
dmaurya929 Sep 12, 2025
fc19a67
Merge branch 'main' of https://github.com/adobe/spacecat-audit-worker…
dmaurya929 Sep 12, 2025
88be018
fix: failing test
dmaurya929 Sep 12, 2025
2af79e7
fix: update handler name
dmaurya929 Sep 17, 2025
4bc3a88
fix: move code update logic to separate Pr
dmaurya929 Oct 1, 2025
583bf44
Merge branch 'main' of https://github.com/adobe/spacecat-audit-worker…
dmaurya929 Oct 1, 2025
cab7534
refactor: update accessibility opportunities handling
dmaurya929 Oct 1, 2025
e0a428c
refactor: revert unnecessary changes
dmaurya929 Oct 1, 2025
832ee78
refactor: separate method to send message to mystique for extensibility
dmaurya929 Oct 3, 2025
e134cd9
feat: codefix handler to update suggestions with patch content
dmaurya929 Oct 3, 2025
93cf961
Merge branch 'main' of https://github.com/adobe/spacecat-audit-worker…
dmaurya929 Oct 16, 2025
83d6830
feat: make codefix handlers generic
dmaurya929 Oct 16, 2025
9652f0d
fix: add handler
dmaurya929 Oct 17, 2025
9414f4e
fix: add deliverytype
dmaurya929 Oct 17, 2025
0763966
Merge branch 'main' of https://github.com/adobe/spacecat-audit-worker…
dmaurya929 Oct 30, 2025
643c98c
fix: test failure
dmaurya929 Oct 30, 2025
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
106 changes: 86 additions & 20 deletions src/accessibility/utils/generate-individual-opportunities.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,30 @@ import {
syncSuggestions,
keepSameDataFunction,
} from '../../utils/data-access.js';
import { successCriteriaLinks, accessibilityOpportunitiesMap } from './constants.js';
import { successCriteriaLinks, accessibilityOpportunitiesMap, URL_SOURCE_SEPARATOR } from './constants.js';
import { getAuditData } from './data-processing.js';
import { processSuggestionsForMystique } from '../guidance-utils/mystique-data-processing.js';
import { isAuditEnabledForSite } from '../../common/audit-utils.js';
import { saveMystiqueValidationMetricsToS3, saveOpptyWithRetry } from './scrape-utils.js';

/**
* Extracts the 'source' query parameter from a URL and returns a clean URL
* without the source parameter
*
* @param {string} url - The original URL that may contain a source parameter
* @returns {Object} An object containing the clean URL and extracted source
* @returns {string} returns.url - The URL without the source parameter
* @returns {string|null} returns.source - The extracted source value, or null
*/
function extractSourceFromUrl(url) {
if (url.includes(URL_SOURCE_SEPARATOR)) {
const [pageUrl, source] = url.split(URL_SOURCE_SEPARATOR);
return { url: pageUrl, source };
}

return { url, source: null };
}

/**
* Creates a Mystique message object
*
Expand Down Expand Up @@ -213,11 +231,22 @@ export function formatIssue(type, issueData, severity) {
}];
}

// Extract understanding URL
let understandingUrl = '';
if (rawWcagRule && rawWcagRule.startsWith('wcag')) {
const numberPart = rawWcagRule.replace('wcag', '');
const ruleInfo = successCriteriaLinks[numberPart];
if (ruleInfo && ruleInfo.understandingUrl) {
understandingUrl = ruleInfo.understandingUrl;
}
}

return {
type,
description: issueData.description || '',
wcagRule,
wcagLevel: issueData.level || '', // AA, AAA, etc.
understandingUrl,
severity,
occurrences: (issueData.htmlWithIssues && issueData.htmlWithIssues.length) || 0,
htmlWithIssues,
Expand All @@ -237,22 +266,25 @@ export function formatIssue(type, issueData, severity) {
* @param {Object} accessibilityData[url] - Per-URL accessibility data
* @returns {Object} Object with data array containing URLs and their issues
*/
export function aggregateAccessibilityIssues(accessibilityData) {
export function aggregateAccessibilityIssues(
accessibilityData,
opportunitiesMap = accessibilityOpportunitiesMap,
) {
if (!accessibilityData) {
return { data: [] };
}

// Create reverse mapping (unchanged)
const issueTypeToOpportunityMap = {};
for (const [opportunityType, issuesList] of Object.entries(accessibilityOpportunitiesMap)) {
for (const [opportunityType, issuesList] of Object.entries(opportunitiesMap)) {
for (const issueType of issuesList) {
issueTypeToOpportunityMap[issueType] = opportunityType;
}
}

// Initialize grouped data structure (unchanged)
const groupedData = {};
for (const [opportunityType] of Object.entries(accessibilityOpportunitiesMap)) {
for (const [opportunityType] of Object.entries(opportunitiesMap)) {
groupedData[opportunityType] = [];
}

Expand All @@ -268,9 +300,13 @@ export function aggregateAccessibilityIssues(accessibilityData) {
target: issueData.target ? issueData.target[index] : '',
};

// Extract source from URL and clean the URL
const { url: pageUrl, source } = extractSourceFromUrl(url);

const urlObject = {
type: 'url',
url,
url: pageUrl,
...(source && { source }),
issues: [formatIssue(issueType, singleElementIssueData, severity)],
};

Expand Down Expand Up @@ -369,7 +405,11 @@ export async function createIndividualOpportunitySuggestions(
if (issues.length === 0) {
return data.url;
}
return `${data.url}|${issues[0].type}|${issues[0]?.htmlWithIssues[0]?.target_selector || ''}`;
let key = `${data.url}|${issues[0].type}|${issues[0]?.htmlWithIssues[0]?.target_selector || ''}`;
if (data.source) {
key += `|${data.source}`;
}
return key;
};

log.info(`[A11yIndividual] ${aggregatedData.data.length} issues aggregated for opportunity ${opportunity.getId()}`);
Expand All @@ -390,13 +430,39 @@ export async function createIndividualOpportunitySuggestions(
url: urlData.url,
type: urlData.type,
issues: urlData.issues, // Array of formatted accessibility issues
...(urlData.source && { source: urlData.source }),
jiraLink: '',
},
}),
mergeDataFunction: keepSameDataFunction,
statusToSetForOutdated: SuggestionDataAccess.STATUSES.FIXED,
});

return { success: true };
} catch (e) {
log.error(`[A11yProcessingError] Failed to create suggestions for opportunity ${opportunity.getId()}: ${e.message}`);
throw new Error(e.message);
}
}

/**
* Sends messages to Mystique for remediation
*
* This function handles the responsibility of sending sqs messages to Mystique for remediation.
* It processes the opportunity's suggestions,
* and sends them to the Mystique queue for AI-powered remediation guidance.
*
* @param {Object} opportunity - The opportunity object with suggestions
* @param {Object} context - Audit context containing sqs, env, site, and other utilities
* @param {Object} log - Logger instance
* @returns {Object} Success status object
*/
export async function sendMessageToMystiqueForRemediation(
opportunity,
context,
log,
) {
try {
// Check if mystique suggestions are enabled for this site
const isMystiqueEnabled = await isAuditEnabledForSite('a11y-mystique-auto-suggest', context.site, context);
if (!isMystiqueEnabled) {
Expand Down Expand Up @@ -461,7 +527,6 @@ export async function createIndividualOpportunitySuggestions(
env,
log,
}));

// Wait for all messages to be sent (successfully or with errors)
const results = await Promise.allSettled(messagePromises);

Expand All @@ -476,7 +541,7 @@ export async function createIndividualOpportunitySuggestions(

return { success: true };
} catch (e) {
log.error(`[A11yProcessingError] Failed to create suggestions for opportunity ${opportunity.getId()}: ${e.message}`);
log.error(`[A11yProcessingError] Failed to send messages to Mystique for opportunity ${opportunity.getId()}: ${e.message}`);
throw new Error(e.message);
}
}
Expand Down Expand Up @@ -643,18 +708,19 @@ export async function createAccessibilityIndividualOpportunities(accessibilityDa

// Step 3: Update suggestions for this opportunity type using enhanced sync
const typeSpecificData = { data: typeData };
try {
await createIndividualOpportunitySuggestions(
opportunity,
typeSpecificData,
context,
log,
);
} catch (error) {
const errorMsg = `Failed to update individual accessibility opportunity suggestions for ${opportunityType}: ${error.message}`;
log.error(`[A11yProcessingError] ${errorMsg}`);
throw new Error(error.message);
}
await createIndividualOpportunitySuggestions(
opportunity,
typeSpecificData,
context,
log,
);

// Step 4: Send messages to Mystique for remediation
await sendMessageToMystiqueForRemediation(
opportunity,
context,
log,
);

// Calculate metrics for this opportunity type
const typeMetrics = calculateAccessibilityMetrics(typeSpecificData);
Expand Down
Loading