SpaceCat Audit Worker for auditing edge delivery sites.
$ npm install @adobe/spacecat-audit-worker
See the API documentation.
$ npm install
$ npm test
$ npm run lint
Audit worker consumes the AUDIT_JOBS_QUEUE
queue, performs the requested audit, then queues the result to AUDIT_RESULTS_QUEUE
for the interested parties to consume later on.
Expected message body format in AUDIT_JOBS_QUEUE
is:
{
"type": "string",
"url": "string",
"auditContext": "object"
}
Output message body format sent to AUDIT_RESULTS_QUEUE
is:
{
"type": "string",
"url": "string",
"auditContext": "object",
"auditResult": "object"
}
Currently, audit worker requires a couple of env variables:
AUDIT_RESULTS_QUEUE_URL=url of the queue to send audit results to
RUM_DOMAIN_KEY=global domain key for the rum api
PAGESPEED_API_BASE_URL = URL of the pagespeed api
DYNAMO_TABLE_NAME_SITES = name of the dynamo table to store site data
DYNAMO_TABLE_NAME_AUDITS = name of the dynamo table to store audit data
DYNAMO_TABLE_NAME_LATEST_AUDITS = name of the dynamo table to store latest audit data
DYNAMO_INDEX_NAME_ALL_SITES = name of the dynamo index to query all sites
DYNAMO_INDEX_NAME_ALL_LATEST_AUDIT_SCORES = name of the dynamo index to query all latest audits by scores
A Spacecat audit is an operation designed for various purposes, including inspection, data collection, verification, and more, all performed on a given URL
.
Spacecat audits run periodically: weekly, daily, and even hourly. By default, the results of these audits are automatically stored in DynamoDB and sent to the audit-results-queue
. The results can then be queried by type via the Spacecat API.
A Spacecat audit consists of seven steps, six of which are provided by default. The only step that typically changes between different audits is the core runner, which contains the business logic.
siteId
information and retrieves the site object from the database. By default, the defaultSiteProvider
reads the site object from the Star Catalogue. This step can be overridden.defaultUrlResolver
sends an HTTP request to the site's baseURL
and returns the finalURL
after following the redirects. This step can be overridden.auditResult
, which holds the audit result, and fullAuditRef
, a string that holds a reference (often a URL) to the audit.auditResult
, fullAuditRef
, and the audit metadata. By default, the defaultPersister
stores the information back in the Star Catalogue.defaultMessageSender
sends the audit result to the audit-results-queue
in Spacecat.To create a new audit, you'll need to create an audit handler function. This function should accept a url
and a context
(see HelixUniversal ) object as parameters, and it should return an auditResult
along with fullAuditRef
. Here's an example:
export async function auditRunner(url, context) {
// your audit logic goes here...
return {
auditResult: results,
fullAuditRef: baseURL,
};
}
export default new AuditBuilder()
.withRunner(auditRunner)
.build();
All audits share common components, such as persisting audit results to a database or sending them to SQS for downstream components to consume. These common functionalities are managed by default functions. However, if desired, you can override them as follows:
export async function auditRunner(url, context) {
// your audit logic goes here...
return {
auditResult: results,
fullAuditRef: baseURL,
};
}
export async function differentUrlResolver(site) {
// logic to override to default behavior of the audit step
return 'url';
}
export default new AuditBuilder()
.withUrlResolver(differentUrlResolver)
.withRunner(auditRunner)
.build();
Using a noop messageSender, audit results might not be sent to the audit results SQS queue:
export async function auditRunner(url, context) {
// your audit logic goes here...
return {
auditResult: results,
fullAuditRef: baseURL,
};
}
export default new AuditBuilder()
.withRunner(auditRunner)
.withMessageSender(() => {}) // no-op message sender
.build();
You can add a post-processing step for your audit using AuditBuilder
's withPostProcessors
function. The list of post-processing functions will be executed sequentially after the audit run.
Post-processor functions take two params: auditUrl
and auditData
as following. auditData
object contains following properties:
auditData = {
siteId: string,
isLive: boolean,
auditedAt: string,
auditType: string,
auditResult: object,
fullAuditRef: string,
};
Here's the full example:
export async function auditRunner(url, context) {
// your audit logic goes here...
return {
auditResult: results,
fullAuditRef: baseURL,
};
}
async function postProcessor(auditUrl, auditData, context) {
// your post-processing logic goes here
// you can obtain the dataAccess from context
// { dataAccess } = context;
}
export default new AuditBuilder()
.withRunner(auditRunner)
.withPostProcessors([ postProcessor ]) // you can submit multiple post processors
.build();
import { syncSuggestions } from '../utils/data-access.js';
export async function auditRunner(url, context) {
// your audit logic goes here...
return {
auditResult: results,
fullAuditRef: baseURL,
};
}
async function convertToOpportunity(auditUrl, auditData, context) {
const { dataAccess } = context;
const opportunities = await dataAccess.Opportunity.allBySiteIdAndStatus(auditData.siteId, 'NEW');
const opportunity = opportunities.find((oppty) => oppty.getType() === 'audit-type');
if (!opportunity) {
const opportunityData = {
siteId: auditData.siteId,
auditId: auditData.id,
runbook: 'link-to-runbook',
type: 'audit-type',
origin: 'AUTOMATON',
title: 'Opportunity Title',
description: 'Opportunity Description',
guidance: {
steps: [
'Step 1',
'Step 2',
],
},
tags: ['tag1', 'tag2'],
};
opportunity = await dataAccess.Opportunity.create(opportunityData);
} else {
opportunity.setAuditId(auditData.id);
await opportunity.save();
}
// this logic changes based on the audit type
const buildKey = (auditData) => `${auditData.property}|${auditData.anotherProperty}`;
await syncSuggestions({
opportunity,
newData: auditData,
buildKey,
mapNewSuggestion: (issue) => ({
opportunityId: opportunity.getId(),
type: 'SUGGESTION_TYPE',
rank: issue.rankMetric,
// data changes based on the audit type
data: {
property: issue.property,
anotherProperty: issue.anotherProperty
}
}),
log
});
}
export default new AuditBuilder()
.withRunner(auditRunner)
.withPostProcessors([ convertToOpportunity ])
.build();