Commit 1f951eae authored by Denys Mishunov's avatar Denys Mishunov

Merge branch '347133-scanner-names-translations' into 'master'

Ensure all scanners are translated in alerts

See merge request gitlab-org/gitlab!76940
parents 6434c8c5 a24d1feb
......@@ -22,6 +22,7 @@ import configureSecretDetectionMutation from '../graphql/configure_secret_detect
/**
* Translations & helpPagePaths for Security Configuration Page
* Make sure to add new scanner translations to the SCANNER_NAMES_MAP below.
*/
export const SAST_NAME = __('Static Application Security Testing (SAST)');
......@@ -138,6 +139,18 @@ export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath(
'user/compliance/license_compliance/index',
);
export const SCANNER_NAMES_MAP = {
SAST: SAST_SHORT_NAME,
SAST_IAC: SAST_IAC_NAME,
DAST: DAST_SHORT_NAME,
API_FUZZING: API_FUZZING_NAME,
CONTAINER_SCANNING: CONTAINER_SCANNING_NAME,
CLUSTER_IMAGE_SCANNING: CLUSTER_IMAGE_SCANNING_NAME,
COVERAGE_FUZZING: COVERAGE_FUZZING_NAME,
SECRET_DETECTION: SECRET_DETECTION_NAME,
DEPENDENCY_SCANNING: DEPENDENCY_SCANNING_NAME,
};
export const securityFeatures = [
{
name: SAST_NAME,
......
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';
export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => {
const featuresByType = features.reduce((acc, feature) => {
......@@ -24,3 +25,13 @@ export const augmentFeatures = (securityFeatures, complianceFeatures, features =
augmentedComplianceFeatures: complianceFeatures.map((feature) => augmentFeature(feature)),
};
};
/**
* Converts a list of security scanner IDs (such as SAST_IAC) into a list of their translated
* names defined in the SCANNER_NAMES_MAP constant (eg. IaC Scanning).
*
* @param {String[]} scannerNames
* @returns {String[]}
*/
export const translateScannerNames = (scannerNames = []) =>
scannerNames.map((scannerName) => SCANNER_NAMES_MAP[scannerName] || scannerName);
......@@ -8,8 +8,8 @@ import vulnerabilitiesQuery from 'ee/security_dashboard/graphql/queries/project_
import { preparePageInfo } from 'ee/security_dashboard/helpers';
import { VULNERABILITIES_PER_PAGE } from 'ee/security_dashboard/store/constants';
import { parseBoolean } from '~/lib/utils/common_utils';
import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { translateScannerNames } from '~/security_configuration/utils';
import VulnerabilityList from '../shared/vulnerability_list.vue';
import SecurityScannerAlert from './security_scanner_alert.vue';
......@@ -92,13 +92,11 @@ export default {
},
update({ project = {} }) {
const { available = [], enabled = [], pipelineRun = [] } = project?.securityScanners || {};
const translateScannerName = (scannerName) =>
this.$options.i18n[scannerName] || scannerName;
return {
available: available.map(translateScannerName),
enabled: enabled.map(translateScannerName),
pipelineRun: pipelineRun.map(translateScannerName),
available: translateScannerNames(available),
enabled: translateScannerNames(enabled),
pipelineRun: translateScannerNames(pipelineRun),
};
},
},
......@@ -164,14 +162,6 @@ export default {
},
},
SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY: 'vulnerability_list_scanner_alert_dismissed',
i18n: {
API_FUZZING: __('API Fuzzing'),
CONTAINER_SCANNING: __('Container Scanning'),
CLUSTER_IMAGE_SCANNING: s__('ciReport|Cluster Image Scanning'),
COVERAGE_FUZZING: __('Coverage Fuzzing'),
SECRET_DETECTION: __('Secret Detection'),
DEPENDENCY_SCANNING: __('Dependency Scanning'),
},
};
</script>
......
......@@ -4,14 +4,7 @@ import { difference } from 'lodash';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import {
API_FUZZING_NAME,
CLUSTER_IMAGE_SCANNING_NAME,
CONTAINER_SCANNING_NAME,
COVERAGE_FUZZING_NAME,
DEPENDENCY_SCANNING_NAME,
SECRET_DETECTION_NAME,
} from '~/security_configuration/components/constants';
import { translateScannerNames } from '~/security_configuration/utils';
import ReportNotConfiguredProject from '../shared/empty_states/report_not_configured_project.vue';
import VulnerabilityReportTabs from '../shared/vulnerability_report/vulnerability_report_tabs.vue';
import projectVulnerabilitiesQuery from '../../graphql/queries/project_vulnerabilities.query.graphql';
......@@ -47,13 +40,11 @@ export default {
},
update({ project = {} }) {
const { available = [], enabled = [], pipelineRun = [] } = project?.securityScanners || {};
const translateScannerName = (scannerName) =>
this.$options.i18n[scannerName] || scannerName;
return {
available: available.map(translateScannerName),
enabled: enabled.map(translateScannerName),
pipelineRun: pipelineRun.map(translateScannerName),
available: translateScannerNames(available),
enabled: translateScannerNames(enabled),
pipelineRun: translateScannerNames(pipelineRun),
};
},
},
......@@ -90,14 +81,6 @@ export default {
projectVulnerabilitiesQuery,
autoFixUserCalloutCookieName: 'auto_fix_user_callout_dismissed',
SCANNER_ALERT_DISMISSED_LOCAL_STORAGE_KEY: 'vulnerability_list_scanner_alert_dismissed',
i18n: {
API_FUZZING: API_FUZZING_NAME,
CONTAINER_SCANNING: CONTAINER_SCANNING_NAME,
CLUSTER_IMAGE_SCANNING: CLUSTER_IMAGE_SCANNING_NAME,
COVERAGE_FUZZING: COVERAGE_FUZZING_NAME,
SECRET_DETECTION: SECRET_DETECTION_NAME,
DEPENDENCY_SCANNING: DEPENDENCY_SCANNING_NAME,
},
};
</script>
......
......@@ -11,6 +11,7 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';
import { generateVulnerabilities } from '../mock_data';
const localVue = createLocalVue();
......@@ -286,6 +287,25 @@ describe('Vulnerabilities app component', () => {
});
});
describe.each(Object.keys(SCANNER_NAMES_MAP))(
'When %s is available but not enabled',
(scanner) => {
const translatedScannerName = SCANNER_NAMES_MAP[scanner];
beforeEach(() => {
createWrapperForScannerAlerts({
securityScanners: { available: [scanner], enabled: [], pipelineRun: [] },
});
});
it(`passes the translated scanner's name to the alert (${translatedScannerName})`, () => {
expect(findSecurityScannerAlert().props('notEnabledScanners')[0]).toBe(
translatedScannerName,
);
});
},
);
describe('dismissal', () => {
beforeEach(() => {
return createWrapperForScannerAlerts({
......
......@@ -13,6 +13,7 @@ import AutoFixUserCallout from 'ee/security_dashboard/components/shared/auto_fix
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';
const localVue = createLocalVue();
localVue.use(VueApollo);
......@@ -141,6 +142,25 @@ describe('Project vulnerability report app component', () => {
);
});
});
describe.each(Object.keys(SCANNER_NAMES_MAP))(
'When %s is available but not enabled',
(scanner) => {
const translatedScannerName = SCANNER_NAMES_MAP[scanner];
beforeEach(() => {
createWrapper({
securityScanners: { available: [scanner], enabled: [], pipelineRun: [] },
});
});
it(`passes the translated scanner's name to the alert (${translatedScannerName})`, () => {
expect(findSecurityScannerAlert().props('notEnabledScanners')[0]).toBe(
translatedScannerName,
);
});
},
);
});
describe('auto fix user callout component', () => {
......
import { augmentFeatures } from '~/security_configuration/utils';
const mockSecurityFeatures = [
{
name: 'SAST',
type: 'SAST',
},
];
const mockComplianceFeatures = [
{
name: 'LICENSE_COMPLIANCE',
type: 'LICENSE_COMPLIANCE',
},
];
const mockFeaturesWithSecondary = [
{
name: 'DAST',
type: 'DAST',
secondary: {
type: 'DAST PROFILES',
name: 'DAST PROFILES',
import { augmentFeatures, translateScannerNames } from '~/security_configuration/utils';
import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';
describe('augmentFeatures', () => {
const mockSecurityFeatures = [
{
name: 'SAST',
type: 'SAST',
},
},
];
const mockInvalidCustomFeature = [
{
foo: 'bar',
},
];
const mockValidCustomFeature = [
{
name: 'SAST',
type: 'SAST',
customField: 'customvalue',
},
];
const mockValidCustomFeatureSnakeCase = [
{
name: 'SAST',
type: 'SAST',
custom_field: 'customvalue',
},
];
const expectedOutputDefault = {
augmentedSecurityFeatures: mockSecurityFeatures,
augmentedComplianceFeatures: mockComplianceFeatures,
};
const expectedOutputSecondary = {
augmentedSecurityFeatures: mockSecurityFeatures,
augmentedComplianceFeatures: mockFeaturesWithSecondary,
};
const expectedOutputCustomFeature = {
augmentedSecurityFeatures: mockValidCustomFeature,
augmentedComplianceFeatures: mockComplianceFeatures,
};
describe('returns an object with augmentedSecurityFeatures and augmentedComplianceFeatures when', () => {
it('given an empty array', () => {
expect(augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, [])).toEqual(
expectedOutputDefault,
);
];
const mockComplianceFeatures = [
{
name: 'LICENSE_COMPLIANCE',
type: 'LICENSE_COMPLIANCE',
},
];
const mockFeaturesWithSecondary = [
{
name: 'DAST',
type: 'DAST',
secondary: {
type: 'DAST PROFILES',
name: 'DAST PROFILES',
},
},
];
const mockInvalidCustomFeature = [
{
foo: 'bar',
},
];
const mockValidCustomFeature = [
{
name: 'SAST',
type: 'SAST',
customField: 'customvalue',
},
];
const mockValidCustomFeatureSnakeCase = [
{
name: 'SAST',
type: 'SAST',
custom_field: 'customvalue',
},
];
const expectedOutputDefault = {
augmentedSecurityFeatures: mockSecurityFeatures,
augmentedComplianceFeatures: mockComplianceFeatures,
};
const expectedOutputSecondary = {
augmentedSecurityFeatures: mockSecurityFeatures,
augmentedComplianceFeatures: mockFeaturesWithSecondary,
};
const expectedOutputCustomFeature = {
augmentedSecurityFeatures: mockValidCustomFeature,
augmentedComplianceFeatures: mockComplianceFeatures,
};
describe('returns an object with augmentedSecurityFeatures and augmentedComplianceFeatures when', () => {
it('given an empty array', () => {
expect(augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, [])).toEqual(
expectedOutputDefault,
);
});
it('given an invalid populated array', () => {
expect(
augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockInvalidCustomFeature),
).toEqual(expectedOutputDefault);
});
it('features have secondary key', () => {
expect(augmentFeatures(mockSecurityFeatures, mockFeaturesWithSecondary, [])).toEqual(
expectedOutputSecondary,
);
});
it('given a valid populated array', () => {
expect(
augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockValidCustomFeature),
).toEqual(expectedOutputCustomFeature);
});
});
it('given an invalid populated array', () => {
expect(
augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockInvalidCustomFeature),
).toEqual(expectedOutputDefault);
describe('returns an object with camelcased keys', () => {
it('given a customfeature in snakecase', () => {
expect(
augmentFeatures(
mockSecurityFeatures,
mockComplianceFeatures,
mockValidCustomFeatureSnakeCase,
),
).toEqual(expectedOutputCustomFeature);
});
});
});
it('features have secondary key', () => {
expect(augmentFeatures(mockSecurityFeatures, mockFeaturesWithSecondary, [])).toEqual(
expectedOutputSecondary,
);
describe('translateScannerNames', () => {
it.each(['', undefined, null, 1, 'UNKNOWN_SCANNER_KEY'])('returns %p as is', (key) => {
expect(translateScannerNames([key])).toEqual([key]);
});
it('given a valid populated array', () => {
expect(
augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockValidCustomFeature),
).toEqual(expectedOutputCustomFeature);
it('returns an empty array if no input is provided', () => {
expect(translateScannerNames([])).toEqual([]);
});
});
describe('returns an object with camelcased keys', () => {
it('given a customfeature in snakecase', () => {
expect(
augmentFeatures(
mockSecurityFeatures,
mockComplianceFeatures,
mockValidCustomFeatureSnakeCase,
),
).toEqual(expectedOutputCustomFeature);
it('returns translated scanner names', () => {
expect(translateScannerNames(Object.keys(SCANNER_NAMES_MAP))).toEqual(
Object.values(SCANNER_NAMES_MAP),
);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment