Skip to content

Commit f4b4397

Browse files
[SITES-36263] Spacecat A11y Autofix pipeline integration
1 parent 9414f4e commit f4b4397

File tree

12 files changed

+1352
-472
lines changed

12 files changed

+1352
-472
lines changed

package-lock.json

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

src/accessibility/auto-optimization-handlers/codefix-handler.js

Lines changed: 6 additions & 237 deletions
Original file line numberDiff line numberDiff line change
@@ -10,245 +10,14 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import { createHash } from 'crypto';
14-
import {
15-
ok, badRequest, notFound, internalServerError,
16-
} from '@adobe/spacecat-shared-http-utils';
17-
import { isNonEmptyArray } from '@adobe/spacecat-shared-utils';
18-
import { getObjectFromKey } from '../../utils/s3-utils.js';
19-
20-
/**
21-
* Generates a hash for the given URL and source combination.
22-
* @param {string} url - The URL to hash
23-
* @param {string} source - The source to hash
24-
* @returns {string} - The generated hash (first 16 characters of MD5)
25-
*/
26-
function generateUrlSourceHash(url, source) {
27-
const combined = `${url}_${source}`;
28-
return createHash('md5').update(combined).digest('hex').substring(0, 16);
29-
}
30-
31-
/**
32-
* Reads code change report from S3 bucket
33-
* @param {Object} s3Client - The S3 client instance
34-
* @param {string} bucketName - The S3 bucket name
35-
* @param {string} siteId - The site ID
36-
* @param {string} url - The page URL
37-
* @param {string} source - The source (optional)
38-
* @param {string} type - The issue type (e.g., 'color-contrast')
39-
* @param {Object} log - Logger instance
40-
* @returns {Promise<Object|null>} - The report data or null if not found
41-
*/
42-
async function readCodeChangeReport(s3Client, bucketName, siteId, url, source, type, log) {
43-
try {
44-
const urlSourceHash = generateUrlSourceHash(url, source || '');
45-
const reportKey = `fixes/${siteId}/${urlSourceHash}/${type}/report.json`;
46-
47-
log.info(`Reading code change report from S3: ${reportKey}`);
48-
49-
const reportData = await getObjectFromKey(s3Client, bucketName, reportKey, log);
50-
51-
if (!reportData) {
52-
log.warn(`No code change report found for key: ${reportKey}`);
53-
return null;
54-
}
55-
56-
log.info(`Successfully read code change report from S3: ${reportKey}`);
57-
return reportData;
58-
} catch (error) {
59-
log.error(`Error reading code change report from S3: ${error.message}`, error);
60-
return null;
61-
}
62-
}
63-
64-
/**
65-
* Updates suggestions with code change data
66-
* @param {Array} suggestions - Array of suggestion objects
67-
* @param {string} url - The page URL to match
68-
* @param {string} source - The source to match (optional)
69-
* @param {string} ruleId - The WCAG rule ID to match
70-
* @param {Object} reportData - The code change report data
71-
* @param {Object} log - Logger instance
72-
* @returns {Promise<Array>} - Array of updated suggestions
73-
*/
74-
async function updateSuggestionsWithCodeChange(suggestions, url, source, ruleId, reportData, log) {
75-
const updatedSuggestions = [];
76-
77-
try {
78-
const promises = [];
79-
for (const suggestion of suggestions) {
80-
const suggestionData = suggestion.getData();
81-
82-
// Check if this suggestion matches the criteria
83-
const suggestionUrl = suggestionData.url;
84-
const suggestionSource = suggestionData.source;
85-
const suggestionRuleId = suggestionData.issues[0]?.type;
86-
87-
if (suggestionUrl === url
88-
&& (!source || suggestionSource === source)
89-
&& suggestionRuleId === ruleId
90-
&& !!reportData.diff) {
91-
log.info(`Updating suggestion ${suggestion.getId()} with code change data`);
92-
93-
// Update suggestion data with diff content and availability flag
94-
const updatedData = {
95-
...suggestionData,
96-
patchContent: reportData.diff,
97-
isCodeChangeAvailable: true,
98-
};
99-
100-
suggestion.setData(updatedData);
101-
suggestion.setUpdatedBy('system');
102-
103-
promises.push(suggestion.save());
104-
updatedSuggestions.push(suggestion);
105-
106-
log.info(`Successfully updated suggestion ${suggestion.getId()}`);
107-
}
108-
}
109-
await Promise.all(promises);
110-
} catch (error) {
111-
log.error(`Error updating suggestions with code change data: ${error.message}`, error);
112-
throw error;
113-
}
114-
115-
return updatedSuggestions;
116-
}
117-
11813
/**
119-
* AccessibilityCodeFixHandler - Updates suggestions with code changes from S3
14+
* Forms Accessibility Code Fix Handler
12015
*
121-
* Expected message format:
122-
* {
123-
* "siteId": "<site-id>",
124-
* "type": "codefix:accessibility",
125-
* "data": {
126-
* "opportunityId": "<uuid>",
127-
* "updates": [
128-
* {
129-
* "url": "<page url>",
130-
* "source": "<source>", // optional
131-
* "type": ["color-contrast", "select-name"]
132-
* }
133-
* ]
134-
* }
135-
* }
16+
* This is a legacy entry point that now delegates to the common code fix response handler.
17+
* Kept for backward compatibility with existing message routing.
13618
*
137-
* @param {Object} message - The SQS message
138-
* @param {Object} context - The context object containing dataAccess, log, s3Client, etc.
139-
* @returns {Promise<Response>} - HTTP response
19+
* @deprecated Use the common codeFixResponseHandler directly
14020
*/
141-
export default async function accessibilityCodeFixHandler(message, context) {
142-
const {
143-
log, dataAccess, s3Client, env,
144-
} = context;
145-
const { Opportunity } = dataAccess;
146-
const { siteId, data } = message;
147-
148-
if (!data) {
149-
log.error('AccessibilityCodeFixHandler: No data provided in message');
150-
return badRequest('No data provided in message');
151-
}
152-
153-
const { opportunityId, updates } = data;
154-
155-
if (!opportunityId) {
156-
log.error('[AccessibilityCodeFixHandler] No opportunityId provided');
157-
return badRequest('No opportunityId provided');
158-
}
159-
160-
if (!isNonEmptyArray(updates)) {
161-
log.error('[AccessibilityCodeFixHandler] No updates provided or updates is empty');
162-
return badRequest('No updates provided or updates is empty');
163-
}
164-
165-
log.info(`[AccessibilityCodeFixHandler] Processing message for siteId: ${siteId}, opportunityId: ${opportunityId}`);
166-
167-
try {
168-
// Find the opportunity
169-
const opportunity = await Opportunity.findById(opportunityId);
170-
171-
if (!opportunity) {
172-
log.error(`[AccessibilityCodeFixHandler] Opportunity not found for ID: ${opportunityId}`);
173-
return notFound('Opportunity not found');
174-
}
175-
176-
// Verify the opportunity belongs to the correct site
177-
if (opportunity.getSiteId() !== siteId) {
178-
const errorMsg = `[AccessibilityCodeFixHandler] Site ID mismatch. Expected: ${siteId}, Found: ${opportunity.getSiteId()}`;
179-
log.error(errorMsg);
180-
return badRequest('Site ID mismatch');
181-
}
182-
183-
// Get all suggestions for the opportunity
184-
const suggestions = await opportunity.getSuggestions();
185-
186-
if (!isNonEmptyArray(suggestions)) {
187-
log.warn(`[AccessibilityCodeFixHandler] No suggestions found for opportunity: ${opportunityId}`);
188-
return ok('No suggestions found for opportunity');
189-
}
190-
191-
const bucketName = env.S3_MYSTIQUE_BUCKET_NAME;
192-
193-
if (!bucketName) {
194-
log.error('AccessibilityCodeFixHandler: S3_MYSTIQUE_BUCKET_NAME environment variable not set');
195-
return internalServerError('S3 bucket name not configured');
196-
}
197-
198-
let totalUpdatedSuggestions = 0;
199-
200-
// Process each update
201-
await Promise.all(updates.map(async (update) => {
202-
const { url, source, type: types } = update;
203-
204-
if (!url) {
205-
log.warn('[AccessibilityCodeFixHandler] Skipping update without URL');
206-
return;
207-
}
208-
209-
if (!isNonEmptyArray(types)) {
210-
log.warn(`[AccessibilityCodeFixHandler] Skipping update for URL ${url} without types`);
211-
return;
212-
}
213-
214-
log.info(`[AccessibilityCodeFixHandler] Processing update for URL: ${url}, source: ${source || 'N/A'}, types: ${types.join(', ')}`);
215-
216-
// For each type in the update, try to read the code change report
217-
await Promise.all(types.map(async (ruleId) => {
218-
let reportData = await readCodeChangeReport(
219-
s3Client,
220-
bucketName,
221-
siteId,
222-
url,
223-
source,
224-
ruleId,
225-
log,
226-
);
227-
228-
if (!reportData) {
229-
log.warn(`[AccessibilityCodeFixHandler] No code change report found for URL: ${url}, source: ${source}, type: ${ruleId}`);
230-
return;
231-
}
232-
233-
reportData = JSON.parse(reportData);
234-
235-
// Update matching suggestions with the code change data
236-
const updatedSuggestions = await updateSuggestionsWithCodeChange(
237-
suggestions,
238-
url,
239-
source,
240-
ruleId,
241-
reportData,
242-
log,
243-
);
244-
totalUpdatedSuggestions += updatedSuggestions.length;
245-
}));
246-
}));
21+
import codeFixResponseHandler from '../../common/codefix-response-handler.js';
24722

248-
log.info(`[AccessibilityCodeFixHandler] Successfully processed all updates. Total suggestions updated: ${totalUpdatedSuggestions}`);
249-
return ok();
250-
} catch (error) {
251-
log.error(`[AccessibilityCodeFixHandler] Error processing message: ${error.message}`, error);
252-
return internalServerError(`Error processing message: ${error.message}`);
253-
}
254-
}
23+
export default codeFixResponseHandler;

src/accessibility/utils/constants.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,6 +732,22 @@ export const accessibilityOpportunitiesMap = {
732732
],
733733
};
734734

735+
/**
736+
* Accessibility issue types that should be sent to Mystique for automatic code fix
737+
*/
738+
export const issueTypesForCodeFix = [
739+
'aria-allowed-attr',
740+
'aria-prohibited-attr',
741+
'aria-roles',
742+
'aria-hidden-focus',
743+
'aria-required-attr',
744+
'aria-valid-attr-value',
745+
'button-name',
746+
'link-name',
747+
'select-name',
748+
'aria-required-parent',
749+
];
750+
735751
/**
736752
* Accessibility issue types that should be sent to Mystique for remediation guidance
737753
*/

0 commit comments

Comments
 (0)