diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize.js index 8df5d70195464b85e86a02ee05ffddaf6f208578..fdb41917501b9aebeaf63488242e40c5e95eeca9 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize.js +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize.js @@ -6,6 +6,8 @@ import { BRANCH_TYPE_KEY, HUMANIZED_BRANCH_TYPE_TEXT_DICT, SCAN_RESULT_BRANCH_TYPE_OPTIONS, + GREATER_THAN_OPERATOR, + LESS_THAN_OPERATOR, } from '../../constants'; import { createHumanizedScanners } from '../../utils'; import { @@ -136,6 +138,35 @@ const humanizeVulnerabilityStates = (vulnerabilitiesStates) => { .join(divider); }; +/** + * Create a human-readable version of vulnerability age + * @param {Object} vulnerabilityAge + * @returns {String} + */ +const humanizeVulnerabilityAge = (vulnerabilityAge) => { + const { value, operator } = vulnerabilityAge; + + const strMap = { + day: (number) => n__('%d day', '%d days', number), + week: (number) => n__('%d week', '%d weeks', number), + month: (number) => n__('%d month', '%d months', number), + year: (number) => n__('%d year', '%d years', number), + }; + + const baseStr = { + [GREATER_THAN_OPERATOR]: sprintf( + s__('SecurityOrchestration|Vulnerability age is greater than %{vulnerabilityAge}.'), + { vulnerabilityAge: strMap[vulnerabilityAge.interval](value) }, + ), + [LESS_THAN_OPERATOR]: sprintf( + s__('SecurityOrchestration|Vulnerability age is less than %{vulnerabilityAge}.'), + { vulnerabilityAge: strMap[vulnerabilityAge.interval](value) }, + ), + }; + + return baseStr[operator]; +}; + /** * Create a human-readable version of the scanners * @param {Array} scanners @@ -232,20 +263,31 @@ const humanizeRule = (rule) => { }; } - const criteriaList = [ - rule.severity_levels.length - ? sprintf(s__('SecurityOrchestration|Severity is %{severity}.'), { - severity: humanizeItems({ - items: rule.severity_levels, - }), - }) - : null, - rule.vulnerability_states.length - ? sprintf(s__('SecurityOrchestration|Vulnerabilities are %{vulnerabilityStates}.'), { - vulnerabilityStates: humanizeVulnerabilityStates(rule.vulnerability_states), - }) - : null, - ].filter((criteria) => Boolean(criteria)); + const criteriaList = []; + + const addCriteria = (predicate, compileCriteria) => { + if (predicate) { + criteriaList.push(compileCriteria()); + } + }; + + addCriteria(rule.severity_levels.length, () => + sprintf(s__('SecurityOrchestration|Severity is %{severity}.'), { + severity: humanizeItems({ + items: rule.severity_levels, + }), + }), + ); + + addCriteria(rule.vulnerability_states.length, () => + sprintf(s__('SecurityOrchestration|Vulnerabilities are %{vulnerabilityStates}.'), { + vulnerabilityStates: humanizeVulnerabilityStates(rule.vulnerability_states), + }), + ); + + addCriteria(Object.keys(rule.vulnerability_age || {}).length, () => + humanizeVulnerabilityAge(rule.vulnerability_age), + ); return { summary: sprintf( diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js index 9338468e2356b6235cbb54e25f252c359b617d54..22972ec5966199ecc142cbe6b6419c89a27742a1 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants.js @@ -38,6 +38,9 @@ export const FILTERS = [ ]; export const AGE_DAY = 'day'; +export const AGE_WEEK = 'week'; +export const AGE_MONTH = 'month'; +export const AGE_YEAR = 'year'; export const AGE_INTERVALS = [ { value: AGE_DAY, text: s__('ApprovalRule|day(s)') }, diff --git a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize_spec.js b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize_spec.js index 3bae2ee74dc05a016d2cb553c2bb4fb18aba4fd5..f0f97668b674270e2d0eeadc866cb9ac4e5476c7 100644 --- a/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize_spec.js +++ b/ee/spec/frontend/security_orchestration/components/policy_editor/scan_result_policy/lib/humanize_spec.js @@ -12,7 +12,13 @@ import { INVALID_RULE_MESSAGE, NO_RULE_MESSAGE, PROJECT_DEFAULT_BRANCH, + GREATER_THAN_OPERATOR, + LESS_THAN_OPERATOR, } from 'ee/security_orchestration/components/policy_editor/constants'; +import { + AGE_MONTH, + AGE_WEEK, +} from 'ee/security_orchestration/components/policy_editor/scan_result_policy/scan_filters/constants'; jest.mock('~/locale', () => ({ getPreferredLocales: jest.fn().mockReturnValue(['en']), @@ -56,11 +62,12 @@ const noVulnerabilityStatesSecurityScannerRule = { branches: ['main'], scanners: ['sast'], severity_levels: ['critical'], + vulnerability_age: { operator: LESS_THAN_OPERATOR, value: 1, interval: AGE_WEEK }, }, humanized: { summary: 'When SAST scanner finds any vulnerabilities in an open merge request targeting the main branch and all the following apply:', - criteriaList: ['Severity is critical.'], + criteriaList: ['Severity is critical.', 'Vulnerability age is less than 1 week.'], }, }; @@ -72,6 +79,7 @@ const multipleValuedSecurityScannerRule = { vulnerabilities_allowed: 2, severity_levels: ['info', 'critical'], vulnerability_states: ['resolved'], + vulnerability_age: { operator: GREATER_THAN_OPERATOR, value: 2, interval: AGE_MONTH }, }, humanized: { summary: @@ -79,6 +87,7 @@ const multipleValuedSecurityScannerRule = { criteriaList: [ 'Severity is info or critical.', 'Vulnerabilities are previously existing and resolved.', + 'Vulnerability age is greater than 2 months.', ], }, }; diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4914b25461890687099691788534a8b5f285f3d4..b5c62773cd9c73e3ed26651adab9323f3e102554 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -347,6 +347,11 @@ msgid_plural "%d minutes" msgstr[0] "" msgstr[1] "" +msgid "%d month" +msgid_plural "%d months" +msgstr[0] "" +msgstr[1] "" + msgid "%d more comment" msgid_plural "%d more comments" msgstr[0] "" @@ -472,11 +477,21 @@ msgid_plural "%d warnings found:" msgstr[0] "" msgstr[1] "" +msgid "%d week" +msgid_plural "%d weeks" +msgstr[0] "" +msgstr[1] "" + msgid "%d work item" msgid_plural "%d work items" msgstr[0] "" msgstr[1] "" +msgid "%d year" +msgid_plural "%d years" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" @@ -42074,6 +42089,12 @@ msgstr "" msgid "SecurityOrchestration|Vulnerabilities are %{vulnerabilityStates}." msgstr "" +msgid "SecurityOrchestration|Vulnerability age is greater than %{vulnerabilityAge}." +msgstr "" + +msgid "SecurityOrchestration|Vulnerability age is less than %{vulnerabilityAge}." +msgstr "" + msgid "SecurityOrchestration|Vulnerability age requires previously existing vulnerability states (detected, confirmed, resolved, or dismissed)" msgstr ""