Skip to content
代码片段 群组 项目
未验证 提交 9d973fe7 编辑于 作者: Fernando Cardenas's avatar Fernando Cardenas 提交者: GitLab
浏览文件

Add cotainer registry tab

* Add behind feature flag
上级 e1f75670
分支
标签
无相关合并请求
......@@ -84,6 +84,7 @@ export const FIELD_PRESETS = {
FIELDS.TOOL,
FIELDS.ACTIVITY,
],
CONTAINER_REGISTRY: [...BASE_FIELDS.START, FIELDS.IDENTIFIER, ...BASE_FIELDS.END],
};
const BASE_FILTERS = { START: [FILTERS.STATUS, FILTERS.SEVERITY], END: [FILTERS.ACTIVITY] };
......@@ -95,9 +96,14 @@ export const FILTER_PRESETS = {
OPERATIONAL: [...BASE_FILTERS.START, FILTERS.PROJECT, ...BASE_FILTERS.END],
AGENT: [...BASE_FILTERS.START, FILTERS.IMAGE, ...BASE_FILTERS.END],
PIPELINE: [FILTERS.PIPELINE_STATUS, FILTERS.SEVERITY, FILTERS.TOOL_PIPELINE],
CONTAINER_REGISTRY: [...BASE_FILTERS.START, FILTERS.PROJECT, ...BASE_FILTERS.END],
CONTAINER_REGISTRY_PROJECT: [...BASE_FILTERS.START, FILTERS.IMAGE, ...BASE_FILTERS.END],
};
export const REPORT_TYPE_PRESETS = {
DEVELOPMENT: Object.keys(REPORT_TYPES_WITH_MANUALLY_ADDED).map((type) => type.toUpperCase()),
OPERATIONAL: [REPORT_TYPE_CLUSTER_IMAGE_SCANNING.toUpperCase()],
CONTAINER_REGISTRY: Object.keys(REPORT_TYPES_WITH_MANUALLY_ADDED).map((type) =>
type.toUpperCase(),
),
};
......@@ -3,24 +3,29 @@ import { GlTabs, GlCard } from '@gitlab/ui';
import { omit, isEqual } from 'lodash';
import { s__ } from '~/locale';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import VulnerabilityReportHeader from './vulnerability_report_header.vue';
import VulnerabilityReportTab from './vulnerability_report_tab.vue';
import { FIELD_PRESETS, FILTER_PRESETS, REPORT_TYPE_PRESETS } from './constants';
export const TAB_NAMES = {
OPERATIONAL: 'OPERATIONAL',
CONTAINER_REGISTRY: 'CONTAINER_REGISTRY',
};
const TAB_NAME_TO_INDEX = {
[TAB_NAMES.OPERATIONAL]: 1,
[TAB_NAMES.CONTAINER_REGISTRY]: 2,
};
const TAB_INDEX_TO_NAME = {
1: TAB_NAMES.OPERATIONAL,
2: TAB_NAMES.CONTAINER_REGISTRY,
};
export default {
components: { GlTabs, GlCard, VulnerabilityReportHeader, VulnerabilityReportTab },
mixins: [glFeatureFlagsMixin()],
inject: ['dashboardType'],
computed: {
tabIndex: {
......@@ -58,6 +63,13 @@ export default {
return FILTER_PRESETS.OPERATIONAL;
},
filterDropdownsContainerRegistry() {
if (this.isProjectReport) {
return FILTER_PRESETS.CONTAINER_REGISTRY_PROJECT;
}
return FILTER_PRESETS.CONTAINER_REGISTRY;
},
},
methods: {
transformFiltersOperational(filters) {
......@@ -66,10 +78,17 @@ export default {
reportType: REPORT_TYPE_PRESETS.OPERATIONAL,
};
},
transformFiltersContainerRegistry(filters) {
return {
...filters,
reportType: REPORT_TYPE_PRESETS.CONTAINER_REGISTRY,
};
},
},
i18n: {
developmentTab: s__('SecurityReports|Development vulnerabilities'),
operationalTab: s__('SecurityReports|Operational vulnerabilities'),
containerRegistryTab: s__('SecurityReports|Container registry vulnerabilities'),
operationalTabMessage: s__(
'SecurityReports|These vulnerabilities were detected in external sources. They are not necessarily tied to your GitLab project. For example, running containers, URLs, and so on.',
),
......@@ -105,6 +124,19 @@ export default {
<gl-card body-class="gl-p-6">{{ $options.i18n.operationalTabMessage }}</gl-card>
</template>
</vulnerability-report-tab>
<vulnerability-report-tab
v-if="glFeatures.containerScanningForRegistry"
:title="$options.i18n.containerRegistryTab"
:fields="$options.FIELD_PRESETS.CONTAINER_REGISTRY"
:filter-dropdowns="filterDropdownsContainerRegistry"
:filter-fn="transformFiltersContainerRegistry"
:is-active-tab="tabIndex === 2"
>
<template #header>
<gl-card body-class="gl-p-6">{{ $options.i18n.containerRegistryTab }}</gl-card>
</template>
</vulnerability-report-tab>
</gl-tabs>
</div>
</template>
......@@ -15,6 +15,7 @@ class VulnerabilitiesController < Groups::ApplicationController
push_frontend_feature_flag(:activity_filter_has_mr, @project)
push_frontend_feature_flag(:activity_filter_has_remediations, @project)
push_frontend_feature_flag(:group_level_vulnerability_report_grouping, @group)
push_frontend_feature_flag(:container_scanning_for_registry)
end
def index
......
......@@ -11,6 +11,7 @@ class VulnerabilityReportController < Projects::ApplicationController
push_frontend_feature_flag(:activity_filter_has_remediations, @project)
push_frontend_feature_flag(:vulnerability_report_advanced_filtering, @project, type: :beta)
push_frontend_feature_flag(:vulnerability_report_owasp_2021, @project, type: :beta)
push_frontend_feature_flag(:container_scanning_for_registry)
end
feature_category :vulnerability_management
......
......@@ -13,15 +13,22 @@ import {
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
Vue.use(VueRouter);
const router = new VueRouter();
describe('Vulnerability report tabs component', () => {
let wrapper;
let router;
const createWrapper = ({ dashboardType = DASHBOARD_TYPES.PROJECT, slots, featureFlags } = {}) => {
router = new VueRouter();
const createWrapper = ({ dashboardType = DASHBOARD_TYPES.PROJECT, slots } = {}) => {
wrapper = shallowMount(VulnerabilityReportTabs, {
router,
provide: { dashboardType },
provide: {
dashboardType,
glFeatures: {
...featureFlags,
},
},
stubs: { VulnerabilityReportTab },
slots,
});
......@@ -31,7 +38,126 @@ describe('Vulnerability report tabs component', () => {
const findAllReportTabs = () => wrapper.findAllComponents(VulnerabilityReportTab);
const findDevelopmentTab = () => findAllReportTabs().at(0);
const findOperationalTab = () => findAllReportTabs().at(1);
const findContainerRegistryTab = () => findAllReportTabs().at(2);
describe('with container registry flag on', () => {
describe('tabs root component', () => {
it.each`
queryParam | tabIndex
${undefined} | ${0}
${TAB_NAMES.OPERATIONAL} | ${1}
${TAB_NAMES.CONTAINER_REGISTRY} | ${2}
`(
'shows tab with tabIndex $tabIndex when querystring is "$queryParam"',
({ queryParam, tabIndex }) => {
router = new VueRouter();
router.replace({ query: { tab: queryParam } });
createWrapper({ featureFlags: { containerScanningForRegistry: true } });
expect(findTabsComponent().props('value')).toBe(tabIndex);
expect(findDevelopmentTab().props('isActiveTab')).toBe(tabIndex === 0);
expect(findOperationalTab().props('isActiveTab')).toBe(tabIndex === 1);
expect(findContainerRegistryTab().props('isActiveTab')).toBe(tabIndex === 2);
},
);
it.each`
tabIndex | queryParam
${0} | ${undefined}
${1} | ${TAB_NAMES.OPERATIONAL}
${2} | ${TAB_NAMES.CONTAINER_REGISTRY}
`(
'changes the tab when tabIndex $tabIndex is clicked and sets querystring to "$queryParam"',
async ({ tabIndex, queryParam }) => {
createWrapper({ featureFlags: { containerScanningForRegistry: true } });
findTabsComponent().vm.$emit('input', tabIndex);
await nextTick();
expect(findTabsComponent().props('value')).toBe(tabIndex);
expect(router.currentRoute.query.tab).toBe(queryParam);
},
);
});
describe('vulnerability report tabs', () => {
it('shows 3 tabs', () => {
createWrapper({ featureFlags: { containerScanningForRegistry: true } });
expect(findAllReportTabs()).toHaveLength(3);
});
it.each`
dashboardType | expectedDevelopmentDropdowns | expectedOperationalDropdowns | expectedContainerRegistryDropdowns
${DASHBOARD_TYPES.PROJECT} | ${FILTER_PRESETS.DEVELOPMENT_PROJECT} | ${FILTER_PRESETS.OPERATIONAL_PROJECT} | ${FILTER_PRESETS.CONTAINER_REGISTRY_PROJECT}
${DASHBOARD_TYPES.GROUP} | ${FILTER_PRESETS.DEVELOPMENT} | ${FILTER_PRESETS.OPERATIONAL} | ${FILTER_PRESETS.CONTAINER_REGISTRY}
${DASHBOARD_TYPES.INSTANCE} | ${FILTER_PRESETS.DEVELOPMENT} | ${FILTER_PRESETS.OPERATIONAL} | ${FILTER_PRESETS.CONTAINER_REGISTRY}
`(
'gets the expected props at the the $dashboardType level',
({
dashboardType,
expectedDevelopmentDropdowns,
expectedOperationalDropdowns,
expectedContainerRegistryDropdowns,
}) => {
createWrapper({ dashboardType, featureFlags: { containerScanningForRegistry: true } });
expect(findDevelopmentTab().props()).toMatchObject({
title: VulnerabilityReportTabs.i18n.developmentTab,
fields: FIELD_PRESETS.DEVELOPMENT,
filterDropdowns: expectedDevelopmentDropdowns,
});
expect(findOperationalTab().props()).toMatchObject({
title: VulnerabilityReportTabs.i18n.operationalTab,
fields: FIELD_PRESETS.OPERATIONAL,
filterDropdowns: expectedOperationalDropdowns,
filterFn: wrapper.vm.transformFiltersOperational,
});
expect(findContainerRegistryTab().props()).toMatchObject({
title: VulnerabilityReportTabs.i18n.containerRegistryTab,
fields: FIELD_PRESETS.CONTAINER_REGISTRY,
filterDropdowns: expectedContainerRegistryDropdowns,
filterFn: wrapper.vm.transformFiltersContainerRegistry,
});
},
);
it('passes the slot content to the development tab', () => {
createWrapper({
slots: {
'header-development': 'header slot content',
},
});
expect(findDevelopmentTab().text()).toContain('header slot content');
});
it('shows the operational tab message in the operational tab', () => {
createWrapper({ featureFlags: { containerScanningForRegistry: true } });
expect(findOperationalTab().text()).toContain(
VulnerabilityReportTabs.i18n.operationalTabMessage,
);
});
it('changes tab when the query parameter changes', async () => {
createWrapper({ featureFlags: { containerScanningForRegistry: true } });
expect(findDevelopmentTab().props('isActiveTab')).toBe(true);
expect(findOperationalTab().props('isActiveTab')).toBe(false);
expect(findContainerRegistryTab().props('isActiveTab')).toBe(false);
router.replace({ query: { tab: undefined } });
await nextTick();
expect(findDevelopmentTab().props('isActiveTab')).toBe(true);
expect(findOperationalTab().props('isActiveTab')).toBe(false);
expect(findContainerRegistryTab().props('isActiveTab')).toBe(false);
});
});
});
describe('with container registry flag off', () => {
describe('tabs root component', () => {
it.each`
queryParam | tabIndex
......@@ -40,6 +166,7 @@ describe('Vulnerability report tabs component', () => {
`(
'shows tab with tabIndex $tabIndex when querystring is "$queryParam"',
({ queryParam, tabIndex }) => {
router = new VueRouter();
router.replace({ query: { tab: queryParam } });
createWrapper();
......@@ -83,13 +210,13 @@ describe('Vulnerability report tabs component', () => {
createWrapper({ dashboardType });
expect(findDevelopmentTab().props()).toMatchObject({
title: wrapper.vm.$options.i18n.developmentTab,
title: VulnerabilityReportTabs.i18n.developmentTab,
fields: FIELD_PRESETS.DEVELOPMENT,
filterDropdowns: expectedDevelopmentDropdowns,
});
expect(findOperationalTab().props()).toMatchObject({
title: wrapper.vm.$options.i18n.operationalTab,
title: VulnerabilityReportTabs.i18n.operationalTab,
fields: FIELD_PRESETS.OPERATIONAL,
filterDropdowns: expectedOperationalDropdowns,
filterFn: wrapper.vm.transformFiltersOperational,
......@@ -110,14 +237,16 @@ describe('Vulnerability report tabs component', () => {
it('shows the operational tab message in the operational tab', () => {
createWrapper();
expect(findOperationalTab().text()).toContain(wrapper.vm.$options.i18n.operationalTabMessage);
expect(findOperationalTab().text()).toContain(
VulnerabilityReportTabs.i18n.operationalTabMessage,
);
});
it('changes tab when the query parameter changes', async () => {
createWrapper();
expect(findDevelopmentTab().props('isActiveTab')).toBe(false);
expect(findOperationalTab().props('isActiveTab')).toBe(true);
expect(findDevelopmentTab().props('isActiveTab')).toBe(true);
expect(findOperationalTab().props('isActiveTab')).toBe(false);
router.replace({ query: { tab: undefined } });
await nextTick();
......@@ -127,3 +256,4 @@ describe('Vulnerability report tabs component', () => {
});
});
});
});
......@@ -46980,6 +46980,9 @@ msgstr ""
msgid "SecurityReports|Confirm dismissal"
msgstr ""
 
msgid "SecurityReports|Container registry vulnerabilities"
msgstr ""
msgid "SecurityReports|Create Jira issue"
msgstr ""
 
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
想要评论请 注册