diff --git a/api/controllers/build-task.js b/api/controllers/build-task.js index d12098f58..9b20e68f6 100644 --- a/api/controllers/build-task.js +++ b/api/controllers/build-task.js @@ -101,29 +101,11 @@ module.exports = wrapHandlers({ const { params, user } = req; const { task_id: taskId, sub_page: subPage } = params; - if (process.env.FEATURE_LOCAL_BUILD_REPORTS === 'active') { - const task = localSiteBuildTasks.find(t => t.id.toString() === taskId); - const file = subPage || 'index'; - const reportPath = join( - __dirname, - `../../services/local/tasks/${task.type}/${file}.json` - ); - - const raw = await readFile(reportPath, 'utf-8'); - const report = JSON.parse(raw); - - return res.json({ - type: task.type, - siteId: task.id, - buildId: task.id, - report, - }); - } - const task = await BuildTask.findOne({ where: { id: taskId }, include: [ BuildTaskType, + SiteBuildTask, { model: Build, include: Site, @@ -137,15 +119,27 @@ module.exports = wrapHandlers({ // return identical responses for missing tasks and unauthorized tasks return res.notFound(); } - const taskJSON = buildTaskSerializer.serialize(task); // add report data to the task let report = null; - const key = `${task.artifact}${subPage || 'index'}.json`; - const reportReponse = await getObject(task.Build.Site, key); - const reportString = await reportReponse.Body.transformToString(); - report = JSON.parse(reportString); + // for local seeded reports + if (process.env.FEATURE_LOCAL_BUILD_REPORTS === 'active') { + const localTask = localSiteBuildTasks.find(t => t.id.toString() === taskId); + const file = subPage || 'index'; + const reportPath = join( + __dirname, + `../../services/local/tasks/${localTask.type}/${file}.json` + ); + + const raw = await readFile(reportPath, 'utf-8'); + report = JSON.parse(raw); + } else { + const key = `${task.artifact}${subPage || 'index'}.json`; + const reportReponse = await getObject(task.Build.Site, key); + const reportString = await reportReponse.Body.transformToString(); + report = JSON.parse(reportString); + } const type = appMatch(task.BuildTaskType); diff --git a/api/serializers/build-task.js b/api/serializers/build-task.js index 6e7022e6d..4b59fa2c8 100644 --- a/api/serializers/build-task.js +++ b/api/serializers/build-task.js @@ -2,12 +2,26 @@ const BaseSerializer = require('./base'); const bttSerializer = require('./build-task-type'); function miniBuildSerializer({ - branch, clonedCommitSha, requestedCommitSha, username, user, createdAt, + branch, + clonedCommitSha, + requestedCommitSha, + username, + user, + createdAt, }) { return { - branch, clonedCommitSha, requestedCommitSha, username, user, createdAt, + branch, + clonedCommitSha, + requestedCommitSha, + username, + user, + createdAt, }; } + +function miniSiteBuildTaskSerializer({ id, branch, metadata }) { + return { id, branch, metadata }; +} const attributes = { id: '', artifact: '', @@ -17,6 +31,7 @@ const attributes = { createdAt: 'date', updatedAt: 'date', siteBuildTaskId: '', + SiteBuildTask: sbt => sbt && miniSiteBuildTaskSerializer(sbt.toJSON()), BuildTaskType: btt => btt && bttSerializer.serialize(btt), Build: build => build && miniBuildSerializer(build.toJSON()), buildId: '', diff --git a/frontend/pages/reports/ShowReport.jsx b/frontend/pages/reports/ShowReport.jsx index 7c245c4c6..bbb970d93 100644 --- a/frontend/pages/reports/ShowReport.jsx +++ b/frontend/pages/reports/ShowReport.jsx @@ -16,12 +16,31 @@ export default function Report() { if (!data) return ; const { report, siteId, buildId } = data; + const sbtCustomRules = data.SiteBuildTask?.metadata?.rules || []; + const sbtId = data.SiteBuildTask?.id || null; switch (data.type) { case 'owasp-zap': - return ; + case 'zap': + return ( + + ); case 'a11y': - return ; + return ( + + ); default: return ; } diff --git a/frontend/pages/reports/ShowReportSubPage.jsx b/frontend/pages/reports/ShowReportSubPage.jsx index 2d135af14..b582b842f 100644 --- a/frontend/pages/reports/ShowReportSubPage.jsx +++ b/frontend/pages/reports/ShowReportSubPage.jsx @@ -15,9 +15,20 @@ export default function Report() { if (!data) return ; const { report, siteId, buildId } = data; + const sbtCustomRules = data.SiteBuildTask?.metadata?.rules || []; + const sbtId = data.SiteBuildTask?.id || null; - if (data.type === 'a11y') { - return ; + if (data.type === 'a11y' && report) { + return ( + + ); } return ; diff --git a/frontend/pages/reports/components/A11yScanChildLayout.jsx b/frontend/pages/reports/components/A11yScanChildLayout.jsx index f0b8e8e54..a5ac05785 100644 --- a/frontend/pages/reports/components/A11yScanChildLayout.jsx +++ b/frontend/pages/reports/components/A11yScanChildLayout.jsx @@ -8,11 +8,12 @@ import ScanFindings from './ScanFindings'; import BackToTopButton from './BackToTopButton'; import About from './about'; -export default function A11yScanChild({ data, siteId, buildId }) { +export default function A11yScanChild({ + report, sbtCustomRules = [], siteId, buildId, sbtId, sbtType, +}) { const scanTitle = 'Accessibility'; - const pageTitle = `Pages | ${scanTitle} report for ${data.url} on ${datetime.dateAndTimeSimple(data.timestamp)} for build id ${buildId}`; - - const allResults = Object.values(data.groupedViolations).flat(1); + const pageTitle = `Pages | ${scanTitle} report for ${report.url} on ${datetime.dateAndTimeSimple(report.timestamp)} for build id ${buildId}`; + const allResults = Object.values(report.groupedViolations).flat(1); const ignoreFn = finding => finding.ignore || (utils.getSeverityThemeToken(finding.impact, 'a11y') == null); // const suppressed = allResults.filter(ignoreFn); const unsuppressed = allResults.filter(r => !ignoreFn(r)); @@ -22,14 +23,14 @@ export default function A11yScanChild({ data, siteId, buildId }) { ...group, label: group.label, usePill: true, - count: data.groupedViolations[group?.name]?.length || 0, + count: report.groupedViolations[group?.name]?.length || 0, }) ); navGroups.push( // TODO: split into suppressed/unsuppressed items { label: 'Total results', - count: data?.violationsCount, + count: report?.violationsCount, }, { label: 'All unsuppressed results', @@ -38,7 +39,7 @@ export default function A11yScanChild({ data, siteId, buildId }) { }, { label: 'Total passes', - count: data?.passes?.length, + count: report?.passes?.length, } ); @@ -53,9 +54,9 @@ export default function A11yScanChild({ data, siteId, buildId }) { Accessibility report for {' '}
- {data.url} + {report.url} - + open page @@ -72,7 +73,7 @@ export default function A11yScanChild({ data, siteId, buildId }) {
- +
- +

This report was generated for {' '} - {data.url} + {report.url} {' from '} build # @@ -135,7 +138,7 @@ export default function A11yScanChild({ data, siteId, buildId }) { {' '} scanned on {' '} - {datetime.dateAndTimeSimple(data.timestamp)} + {datetime.dateAndTimeSimple(report.timestamp)}

@@ -202,7 +205,11 @@ A11yPassed.propTypes = { A11yScanChild.propTypes = { // eslint-disable-next-line react/forbid-prop-types - data: PropTypes.object.isRequired, + report: PropTypes.object.isRequired, + // eslint-disable-next-line react/forbid-prop-types + sbtCustomRules: PropTypes.array, siteId: PropTypes.number.isRequired, + sbtId: PropTypes.number.isRequired, + sbtType: PropTypes.string.isRequired, buildId: PropTypes.number.isRequired, }; diff --git a/frontend/pages/reports/components/A11yScanIndexLayout.jsx b/frontend/pages/reports/components/A11yScanIndexLayout.jsx index 47d1a01ff..bab38238d 100644 --- a/frontend/pages/reports/components/A11yScanIndexLayout.jsx +++ b/frontend/pages/reports/components/A11yScanIndexLayout.jsx @@ -8,13 +8,13 @@ import ScanPagePathAndReportLink from './ScanPagePathReportLink'; import ScanFindingsSummary from './ScanFindingsSummary'; import About from './about'; -export default function A11yScanIndex({ data, siteId, buildId }) { +export default function A11yScanIndex({ report, siteId, buildId }) { const scanTitle = 'Accessibility'; - const pageTitle = `Pages | ${scanTitle} report index for ${data.baseurl} on ${datetime.dateAndTimeSimple(data.reportPages[0].timestamp)} for build id #${buildId}`; + const pageTitle = `Pages | ${scanTitle} report index for ${report.baseurl} on ${datetime.dateAndTimeSimple(report.reportPages[0].timestamp)} for build id #${buildId}`; function findReportsPerURLs(url) { - return data.reportPages.find(page => page.absoluteURL === url)?.path || ''; + return report.reportPages.find(page => page.absoluteURL === url)?.path || ''; } - const summarizedResults = [...data.violatedRules].map(result => ({ + const summarizedResults = [...report.violatedRules].map(result => ({ ...result, name: result.help, ref: result.helpUrl, @@ -39,7 +39,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) { {' '}
- {data.baseurl} + {report.baseurl} {' '} (all pages) @@ -84,7 +84,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) { {' '} ( - {data.violatedRules.length} + {report.violatedRules.length} ) @@ -93,7 +93,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) {
@@ -106,7 +106,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) { {' '} ( - {data.reportPages.length} + {report.reportPages.length} ) @@ -115,8 +115,8 @@ export default function A11yScanIndex({ data, siteId, buildId }) {

This report was generated for @@ -128,7 +128,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) { {' '} scanned on {' '} - {datetime.dateAndTimeSimple(data.reportPages[0]?.timestamp)} + {datetime.dateAndTimeSimple(report.reportPages[0]?.timestamp)} .

@@ -139,7 +139,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) {

This report was generated for {' '} - {data.baseurl} + {report.baseurl} {' from '} build # @@ -148,7 +148,7 @@ export default function A11yScanIndex({ data, siteId, buildId }) { {' '} scanned on {' '} - {datetime.dateAndTimeSimple(data.reportPages[0]?.timestamp)} + {datetime.dateAndTimeSimple(report.reportPages[0]?.timestamp)}

@@ -243,7 +243,8 @@ ScanResultsChildPages.propTypes = { A11yScanIndex.propTypes = { // eslint-disable-next-line react/forbid-prop-types - data: PropTypes.object.isRequired, + report: PropTypes.object.isRequired, + // eslint-disable-next-line react/forbid-prop-types siteId: PropTypes.number.isRequired, buildId: PropTypes.number.isRequired, }; diff --git a/frontend/pages/reports/components/FindingSuppression.jsx b/frontend/pages/reports/components/FindingSuppression.jsx new file mode 100644 index 000000000..e056dacdc --- /dev/null +++ b/frontend/pages/reports/components/FindingSuppression.jsx @@ -0,0 +1,132 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Link } from 'react-router-dom'; +import api from '@util/federalistApi'; +import notificationActions from '@actions/notificationActions'; + +function FindingSuppression({ + ruleId = '', + suppressed = false, + suppressedBy = null, + siteId, + sbtId, + sbtType, + sbtCustomRules = [], +}) { + const [customerRules, setCustomerRules] = useState(sbtCustomRules); + const willBeSuppressed = customerRules.find(r => r.id === ruleId); + + function handleUpdate(newRules) { + return api.updateSiteBuildTask(siteId, sbtId, { rules: newRules }) + .then(() => notificationActions.success('Successfully saved report configuration.')) + .catch(() => notificationActions.success('Error saving report configuration.')); + } + + function addNewRule() { + const newRule = { id: ruleId, type: sbtType }; + setCustomerRules(customerRules.concat([newRule])); + handleUpdate(customerRules.concat([newRule])); + } + + function deleteRule() { + const newRules = customerRules.filter(r => r.id !== ruleId); + setCustomerRules(newRules); + handleUpdate(newRules); + } + return ( + <> + { !suppressed && willBeSuppressed && ( +

+ Your configuration will suppress this finding in future reports. + {' '} + +

+ )} + { !suppressed && !willBeSuppressed && ( +

+ False positive? + {' '} + + {' '} + to suppress this result in future reports. +

+ )} + {suppressed && ( +
+
+

+ This result was suppressed by  + {suppressedBy || 'customer criteria'} + . +

+ { suppressedBy !== 'Pages' && willBeSuppressed && ( +

+ Need to re-enable this rule? + {' '} + + {' '} + to stop suppressing this result in future reports. +

+ )} + { suppressedBy !== 'Pages' && !willBeSuppressed && ( +

+ This finding will not be suppressed in future reports. + {' '} + + {' '} + to continue suppressing this finding. +

+ )} +
+
+ + Why was this result  + suppressed + ? + +

+ { /* eslint-disable-next-line max-len */} + { suppressedBy && 'Pages automatically suppresses certain results in this report which are irrelevant for statically hosted websites, based on unconfigurable server settings, or frequently produce ‘false positive’ findings for our customers. '} + { /* eslint-disable-next-line max-len */} + { (!suppressedBy || suppressedBy === 'multiple criteria') && 'Customers can specify criteria to suppress during report generation to silence ‘false positive’ results.'} + { ' ' /* eslint-disable-next-line max-len */} + While still visible in the report, the suppressed results don’t count towards your total issue count. + { ' ' /* eslint-disable-next-line max-len */} + Review the report rules and criteria that are suppressed during report generation in your   + Site Settings Report Configuration + . +

+

+ For a full list of what Pages excludes from your results, review the + {' '} + + Automated Site Reports documentation + + . +

+
+
+
+
+ )} + + ); +} + +FindingSuppression.propTypes = { + ruleId: PropTypes.string.isRequired, + sbtId: PropTypes.number.isRequired, + sbtType: PropTypes.string.isRequired, + suppressed: PropTypes.bool, + suppressedBy: PropTypes.string, + siteId: PropTypes.number.isRequired, + // eslint-disable-next-line react/forbid-prop-types + sbtCustomRules: PropTypes.array, +}; + +export default FindingSuppression; +export { FindingSuppression }; diff --git a/frontend/pages/reports/components/ScanFinding.jsx b/frontend/pages/reports/components/ScanFinding.jsx index e90fab6ae..d713b01a1 100644 --- a/frontend/pages/reports/components/ScanFinding.jsx +++ b/frontend/pages/reports/components/ScanFinding.jsx @@ -1,12 +1,12 @@ import React, { useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import Highlight from 'react-highlight'; -import { Link, useLocation } from 'react-router-dom'; - +import { useLocation } from 'react-router-dom'; import { plural, getSuccessCriteria, getWCAGRuleURLs } from '@util/reports'; +import FindingSuppression from './FindingSuppression'; const ScanFinding = ({ - finding, groupColor, groupLabel, scanType = 'zap', siteId, + finding, groupColor, groupLabel, siteId, sbtId, sbtCustomRules, sbtType, }) => { const ref = useRef(null); const { hash } = useLocation(); @@ -20,8 +20,9 @@ const ScanFinding = ({ let locations = []; let criteria = []; let hasMoreInfo = ''; + let ruleId = ''; - if (scanType === 'zap') { + if ((sbtType === 'zap') || (sbtType === 'owasp-zap')) { ({ name: title, solution, description, } = finding); @@ -30,8 +31,9 @@ const ScanFinding = ({ count = locations.length; references = finding.referenceURLs || []; hasMoreInfo = finding.otherinfo; + ruleId = finding.alertRef; } - if (scanType === 'a11y') { + if (sbtType === 'a11y') { title = `${finding.help}.`; anchor = `finding-${finding.id}${ignore ? '-suppressed' : ''}`; locations = finding.nodes || []; @@ -40,6 +42,7 @@ const ScanFinding = ({ solution = finding.nodes[0]?.failureSummary || []; criteria = getSuccessCriteria(finding); references = [...getWCAGRuleURLs(finding.id), ...criteria.map(c => c.url), finding.helpUrl]; + ruleId = finding.id; } useEffect(() => { @@ -60,21 +63,29 @@ const ScanFinding = ({ groupColor={groupColor} count={count} references={references} - scanType={scanType} + sbtType={sbtType} anchor={anchor} criteria={criteria.map(c => c.short)} + suppressed={ignore} />
+ - - + +

@@ -88,7 +99,10 @@ ScanFinding.propTypes = { groupColor: PropTypes.string, groupLabel: PropTypes.string, siteId: PropTypes.number.isRequired, - scanType: PropTypes.string, + sbtId: PropTypes.number.isRequired, + sbtType: PropTypes.string.isRequired, + // eslint-disable-next-line react/forbid-prop-types + sbtCustomRules: PropTypes.array, }; const FindingTitle = ({ @@ -97,8 +111,10 @@ const FindingTitle = ({ groupColor, count, criteria = [], - scanType, + sbtType, anchor, + children, + suppressed = false, }) => (

@@ -106,13 +122,14 @@ const FindingTitle = ({ #

+ {suppressed ? 'Suppressed ' : '' } {groupLabel} {' '} finding {' '} - {scanType === 'a11y' && criteria.length > 0 && ( + {sbtType === 'a11y' && criteria.length > 0 && ( <> that violates  { new Intl.ListFormat('en-US').format(criteria)} @@ -127,6 +144,7 @@ const FindingTitle = ({ {plural(count, 'place')} {'. '} + {children}

); @@ -138,60 +156,19 @@ FindingTitle.propTypes = { count: PropTypes.number, // eslint-disable-next-line react/forbid-prop-types criteria: PropTypes.array, - scanType: PropTypes.string.isRequired, + sbtType: PropTypes.string.isRequired, anchor: PropTypes.string.isRequired, - + children: PropTypes.node, + suppressed: PropTypes.bool, }; const FindingDescription = ({ description, - scanType, - siteId, - ignore = false, - ignoreSource = null, + sbtType, moreInfo = null, }) => (
- {ignore && ( -
-
-

- This result was suppressed by  - {ignoreSource || 'customer criteria'} -

-
-
- - Why was this result  - suppressed - ? - -

- { /* eslint-disable-next-line max-len */} - { ignoreSource && 'Pages automatically suppresses certain results in this report which are irrelevant for statically hosted websites, based on unconfigurable server settings, or frequently produce ‘false positive’ findings for our customers. '} - { /* eslint-disable-next-line max-len */} - { (!ignoreSource || ignoreSource === 'multiple criteria') && 'Customers can specify criteria to suppress during report generation to silence ‘false positive’ results.'} - { ' ' /* eslint-disable-next-line max-len */} - While still visible in the report, the suppressed results don’t count towards your total issue count. - { ' ' /* eslint-disable-next-line max-len */} - Review the report rules and criteria that are suppressed during report generation in your   - Site Settings Report Configuration - . -

-

- For a full list of what Pages excludes from your results, review the - {' '} - - Automated Site Reports documentation - - . -

-
-
-
-
- )} - { scanType === 'zap' && ( + { sbtType === 'owasp-zap' && ( // eslint-disable-next-line react/no-danger
)} @@ -201,26 +178,23 @@ const FindingDescription = ({ )} - { scanType !== 'zap' &&

{description}

} + { sbtType !== 'owasp-zap' &&

{description}

}
); FindingDescription.propTypes = { description: PropTypes.string.isRequired, - scanType: PropTypes.string.isRequired, + sbtType: PropTypes.string.isRequired, moreInfo: PropTypes.string, - ignore: PropTypes.bool, - ignoreSource: PropTypes.string, - siteId: PropTypes.number.isRequired, }; -const FindingRecommendation = ({ anchor, solution, scanType }) => ( +const FindingRecommendation = ({ anchor, solution, sbtType }) => (
- { (scanType === 'zap') && ( + { (sbtType === 'owasp-zap') && ( <>

Recommendation(s): @@ -232,7 +206,7 @@ const FindingRecommendation = ({ anchor, solution, scanType }) => (

)} - { (scanType === 'a11y') && ( + { (sbtType === 'a11y') && ( <> {solution.split('\n\n').map((fixList, listindex) => ( // eslint-disable-next-line react/no-array-index-key @@ -264,7 +238,7 @@ const FindingRecommendation = ({ anchor, solution, scanType }) => ( FindingRecommendation.propTypes = { anchor: PropTypes.string.isRequired, solution: PropTypes.string.isRequired, - scanType: PropTypes.string.isRequired, + sbtType: PropTypes.string.isRequired, }; const FindingReferences = ({ references = [] }) => { @@ -296,7 +270,7 @@ const FindingReference = ({ url }) => ( FindingReference.propTypes = { url: PropTypes.string.isRequired }; -const FindingLocations = ({ locations = [], anchor, scanType }) => ( +const FindingLocations = ({ locations = [], anchor, sbtType }) => ( <>

Evidence for this result was found: @@ -305,9 +279,9 @@ const FindingLocations = ({ locations = [], anchor, scanType }) => (
    {locations.map((location, locationIndex) => { const { uri: url } = location; - const code = scanType === 'zap' ? location.evidence : location.html; - const target = scanType === 'zap' ? location.param : location.target; - const moreInfo = scanType === 'zap' ? location.otherinfo : null; + const code = sbtType === 'owasp-zap' ? location.evidence : location.html; + const target = sbtType === 'owasp-zap' ? location.param : location.target; + const moreInfo = sbtType === 'owasp-zap' ? location.otherinfo : null; const locationAnchor = `${anchor}-location-${locationIndex + 1}`; return (
  1. @@ -334,7 +308,7 @@ FindingLocations.propTypes = { // eslint-disable-next-line react/forbid-prop-types locations: PropTypes.array.isRequired, anchor: PropTypes.string.isRequired, - scanType: PropTypes.string.isRequired, + sbtType: PropTypes.string.isRequired, }; const FindingLocationURL = ({ url }) => ( @@ -394,7 +368,7 @@ const FindingLocationMoreInfo = ({ info = null, children = null }) => ( FindingLocationMoreInfo.propTypes = { info: PropTypes.string, - children: PropTypes.string, + children: PropTypes.node, }; export default ScanFinding; diff --git a/frontend/pages/reports/components/ScanFindings.jsx b/frontend/pages/reports/components/ScanFindings.jsx index 4272ffd77..cf32958ea 100644 --- a/frontend/pages/reports/components/ScanFindings.jsx +++ b/frontend/pages/reports/components/ScanFindings.jsx @@ -6,15 +6,18 @@ import ScanFinding from './ScanFinding'; const ScanFindings = ({ count, groupedFindings, - scanType, siteId, + sbtId, + sbtType, + sbtCustomRules, }) => { - const groupKey = scanType === 'zap' ? 'riskCode' : 'name'; + const scanGroup = sbtType === 'owasp-zap' ? 'zap' : 'a11y'; + const groupKey = scanGroup === 'zap' ? 'riskCode' : 'name'; if (count && groupedFindings) { return ( <> {( - severity[scanType].map(({ [groupKey]: group, label, color }, groupIndex) => ( + severity[scanGroup].map(({ [groupKey]: group, label, color }, groupIndex) => ( {groupedFindings[group] && groupedFindings[group]?.length > 0 && ( <> @@ -38,8 +41,10 @@ const ScanFindings = ({ groupColor={color} groupLabel={label} groupIndex={groupIndex} - scanType={scanType} siteId={siteId} + sbtId={sbtId} + sbtType={sbtType} + sbtCustomRules={sbtCustomRules} /> ))}

@@ -58,8 +63,11 @@ ScanFindings.propTypes = { count: PropTypes.number.isRequired, // eslint-disable-next-line react/forbid-prop-types groupedFindings: PropTypes.object.isRequired, - scanType: PropTypes.string.isRequired, siteId: PropTypes.number.isRequired, + sbtId: PropTypes.number.isRequired, + // eslint-disable-next-line react/forbid-prop-types + sbtCustomRules: PropTypes.array, + sbtType: PropTypes.string.isRequired, }; export default ScanFindings; diff --git a/frontend/pages/reports/components/ScanFindingsSummary.jsx b/frontend/pages/reports/components/ScanFindingsSummary.jsx index e23b44df2..21024b971 100644 --- a/frontend/pages/reports/components/ScanFindingsSummary.jsx +++ b/frontend/pages/reports/components/ScanFindingsSummary.jsx @@ -121,7 +121,7 @@ const ScanFindingsSummary = ({ suppressedFindings = [], unsuppressedFindings = [ ) -
+

@@ -132,9 +132,10 @@ const ScanFindingsSummary = ({ suppressedFindings = [], unsuppressedFindings = [ , which don’t count towards your total issues. +
For more information about excluded results and suppression rules, review the {' '} - + Automated Site Reports documentation . diff --git a/frontend/pages/reports/components/Zap.jsx b/frontend/pages/reports/components/Zap.jsx index b27a8b6ef..d998ae2e3 100644 --- a/frontend/pages/reports/components/Zap.jsx +++ b/frontend/pages/reports/components/Zap.jsx @@ -10,29 +10,36 @@ import ScanFindings from './ScanFindings'; import ScanFindingsSummary from './ScanFindingsSummary'; import BackToTopButton from './BackToTopButton'; -export default function Zap({ data, buildId, siteId }) { +export default function Zap({ + report, + sbtCustomRules = [], + buildId, + siteId, + sbtId, + sbtType, +}) { const scanTitle = 'Vulnerability'; - const pageTitle = `Pages | ${scanTitle} report for ${data.site['@name']} on ${data.generated} for build id ${buildId}`; + const pageTitle = `Pages | ${scanTitle} report for ${report.site['@name']} on ${report.generated} for build id ${buildId}`; const navGroups = [...severity.zap].map(group => ({ ...group, usePill: true, - count: data.site.groupedAlerts[group?.riskCode]?.length || 0, + count: report.site.groupedAlerts[group?.riskCode]?.length || 0, })); navGroups.push( { label: 'Total results', - count: data.site.alerts?.length, + count: report.site.alerts?.length, }, { label: 'All unsuppressed results', - count: data.site?.issueCount, + count: report.site?.issueCount, boldMe: true, } ); - const summarizedResults = [...data.site.alerts].map(result => ({ + const summarizedResults = [...report.site.alerts].map(result => ({ ...result, anchor: result.alertRef, severity: getSeverityThemeToken(result.riskcode, 'zap'), @@ -57,7 +64,7 @@ export default function Zap({ data, buildId, siteId }) { {' report for '}
- {data.site['@name']} + {report.site['@name']} (all pages) @@ -80,7 +87,7 @@ export default function Zap({ data, buildId, siteId }) {

This report was generated for {' '} - {data.site['@name']} + {report.site['@name']} {' from '} build # @@ -140,7 +149,7 @@ export default function Zap({ data, buildId, siteId }) { {' '} scanned on {' '} - {data.generated} + {report.generated}

@@ -152,7 +161,11 @@ export default function Zap({ data, buildId, siteId }) { Zap.propTypes = { // eslint-disable-next-line react/forbid-prop-types - data: PropTypes.object.isRequired, + report: PropTypes.object.isRequired, + // eslint-disable-next-line react/forbid-prop-types + sbtCustomRules: PropTypes.array, siteId: PropTypes.number.isRequired, + sbtId: PropTypes.number.isRequired, + sbtType: PropTypes.string.isRequired, buildId: PropTypes.number.isRequired, }; diff --git a/frontend/pages/reports/components/about.jsx b/frontend/pages/reports/components/about.jsx index 14f1c4613..05a36e6ab 100644 --- a/frontend/pages/reports/components/about.jsx +++ b/frontend/pages/reports/components/about.jsx @@ -27,7 +27,7 @@ export default function About({ scanType, siteId, children }) {

For a full list of what Pages excludes from your results, review the {' '} - + Automated Site Reports documentation . diff --git a/frontend/pages/sites/components/SiteSettings/ReportConfigs.jsx b/frontend/pages/sites/components/SiteSettings/ReportConfigs.jsx index 47d7cb634..6773ee955 100644 --- a/frontend/pages/sites/components/SiteSettings/ReportConfigs.jsx +++ b/frontend/pages/sites/components/SiteSettings/ReportConfigs.jsx @@ -271,7 +271,7 @@ function ReportConfigs({ siteId: id }) { target="_blank" rel="noopener noreferrer" title="Pages documentation on site reports" - href="https://cloud.gov/pages/documentation/automated-site-reports/#configuration" + href="https://cloud.gov/pages/documentation/automated-site-reports/#configuration#configuration" > documentation diff --git a/public/styles/report.css b/public/styles/report.css index 956e556ab..dbe0a6fd7 100644 --- a/public/styles/report.css +++ b/public/styles/report.css @@ -156,8 +156,18 @@ a.bg-risk-low:hover { background: #7a591a} a.bg-risk-info:hover { background: #00687d} a.bg-risk-other:hover { background: #565c65} - - +@supports ((-webkit-mask: url("")) or (mask: url(""))) { + .usa-alert--suppressed .usa-alert__body:before { + mask-image: url(../img/usa-icons/notifications_off.svg), linear-gradient(transparent, transparent); + background-color: #13171f77; + } + .usa-alert--suppressed, + .usa-alert--suppressed .usa-alert__body { + background-color: #f5f6f7; + border-left-color:#dfe1e2; + color: #13171f; + } +} /* https://github.com/highlightjs/highlight.js/tree/main/src/styles */ diff --git a/scripts/create-dev-data.js b/scripts/create-dev-data.js index 18d94e272..37575d4c2 100644 --- a/scripts/create-dev-data.js +++ b/scripts/create-dev-data.js @@ -546,9 +546,20 @@ async function createData() { startsWhen: 'build', url: 'https://cloud.gov/pages/documentation/build-scans/', }); + + // task "hook" for each site + const sbtNodeType1 = await SiteBuildTask.create({ + siteId: nodeSite.id, + buildTaskTypeId: taskType1.id, + branch: 'test', + metadata: { + runDay: 27, // should be a day of the month + }, + }); await BuildTask.create({ buildId: nodeSiteBuilds[0].id, buildTaskTypeId: taskType1.id, + siteBuildTaskId: sbtNodeType1.id, name: 'type', status: 'created', artifact: null, @@ -557,6 +568,7 @@ async function createData() { await BuildTask.create({ buildId: nodeSiteBuilds[2].id, buildTaskTypeId: taskType1.id, + siteBuildTaskId: sbtNodeType1.id, name: 'type', status: 'processing', artifact: null, @@ -566,6 +578,7 @@ async function createData() { await BuildTask.create({ buildId: nodeSiteBuilds[4].id, buildTaskTypeId: taskType1.id, + siteBuildTaskId: sbtNodeType1.id, name: 'type', status: 'cancelled', artifact: null, @@ -575,6 +588,7 @@ async function createData() { const btZap1 = await BuildTask.create({ buildId: nodeSiteBuilds[5].id, buildTaskTypeId: taskType1.id, + siteBuildTaskId: sbtNodeType1.id, name: 'type', status: 'success', artifact: null, @@ -586,6 +600,7 @@ async function createData() { const btZap2 = await BuildTask.create({ buildId: nodeSiteBuilds[6].id, buildTaskTypeId: taskType1.id, + siteBuildTaskId: sbtNodeType1.id, name: 'type', status: 'success', artifact: null, @@ -604,9 +619,21 @@ async function createData() { startsWhen: 'build', url: 'https://cloud.gov/pages/documentation/build-scans/', }); + + // task "hook" for each site + const sbtNodeType2 = await SiteBuildTask.create({ + siteId: nodeSite.id, + buildTaskTypeId: taskType2.id, + branch: 'test', + metadata: { + runDay: 1, // should be a day of the month + }, + }); + await BuildTask.create({ buildId: nodeSiteBuilds[0].id, buildTaskTypeId: taskType2.id, + siteBuildTaskId: sbtNodeType2.id, name: 'type', status: 'created', artifact: null, @@ -615,6 +642,7 @@ async function createData() { await BuildTask.create({ buildId: nodeSiteBuilds[2].id, buildTaskTypeId: taskType2.id, + siteBuildTaskId: sbtNodeType2.id, name: 'type', status: 'processing', artifact: null, @@ -624,6 +652,7 @@ async function createData() { await BuildTask.create({ buildId: nodeSiteBuilds[4].id, buildTaskTypeId: taskType2.id, + siteBuildTaskId: sbtNodeType2.id, name: 'type', status: 'cancelled', artifact: null, @@ -633,6 +662,7 @@ async function createData() { await BuildTask.create({ buildId: nodeSiteBuilds[5].id, buildTaskTypeId: taskType2.id, + siteBuildTaskId: sbtNodeType2.id, name: 'type', status: 'error', artifact: null, @@ -642,6 +672,7 @@ async function createData() { const btA11y1 = await BuildTask.create({ buildId: nodeSiteBuilds[6].id, buildTaskTypeId: taskType2.id, + siteBuildTaskId: sbtNodeType2.id, name: 'type', status: 'success', artifact: null, @@ -653,25 +684,6 @@ async function createData() { // write localSiteBuildTasks.json file for viewing out reports await writeFile(localSiteBuildTasksFile, JSON.stringify(localSiteBuildTasks), 'utf-8'); - // task "hook" for each site - await SiteBuildTask.create({ - siteId: nodeSite.id, - buildTaskTypeId: taskType1.id, - branch: 'test', - metadata: { - runDay: 27, // should be a day of the month - }, - }); - // task "hook" for each site - await SiteBuildTask.create({ - siteId: nodeSite.id, - buildTaskTypeId: taskType2.id, - branch: 'test', - metadata: { - runDay: 1, // should be a day of the month - }, - }); - const goSiteBuilds = await Promise.all([ Build.create({ branch: goSite.defaultBranch,