diff --git a/config/feature_flags/development/group_level_vulnerability_report_grouping.yml b/config/feature_flags/development/group_level_vulnerability_report_grouping.yml new file mode 100644 index 0000000000000000000000000000000000000000..163d46f6b62aece7184a03ab99cf8328fe3fd9a1 --- /dev/null +++ b/config/feature_flags/development/group_level_vulnerability_report_grouping.yml @@ -0,0 +1,8 @@ +--- +name: group_level_vulnerability_report_grouping +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137778 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432778 +milestone: '16.7' +type: development +group: group::threat insights +default_enabled: false diff --git a/ee/app/assets/javascripts/security_dashboard/components/group/group_vulnerability_report.vue b/ee/app/assets/javascripts/security_dashboard/components/group/group_vulnerability_report.vue index a83f1e9f919de4d1ec359a5ccd1a45cf5d3a1f94..67e2ba579093666991d17e9f5b2daa233d3dbdbc 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/group/group_vulnerability_report.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/group/group_vulnerability_report.vue @@ -8,6 +8,9 @@ export default { VulnerabilityReportTabs, }, inject: ['hasProjects'], + provide: { + isGroupVulnerabilityReport: true, + }, }; </script> diff --git a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_report.vue b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_report.vue index f8e6acdaab2d10d11ff3373696c5a1fd52ac4617..36b0451ccead62933629dc81ecc2bc95255cd199 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_report.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/shared/vulnerability_report/vulnerability_report.vue @@ -11,6 +11,7 @@ import { createAlert } from '~/alert'; import countsQuery from 'ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql'; import SummaryHighlights from 'ee/vue_shared/security_reports/components/summary_highlights.vue'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { __, s__ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { scrollToElement, contentTop } from '~/lib/utils/common_utils'; @@ -54,10 +55,14 @@ export default { GlSprintf, UserCalloutDismisser, }, + mixins: [glFeatureFlagsMixin()], inject: { fullPath: { default: '', }, + isGroupVulnerabilityReport: { + default: false, + }, isProjectVulnerabilityReport: { default: false, }, @@ -109,7 +114,18 @@ export default { return this.filterDropdowns.includes(FILTERS.PROJECT); }, shouldShowGroupByButton() { - return this.isProjectVulnerabilityReport && this.isVisible; + if (!this.isVisible) { + return false; + } + + if ( + this.isGroupVulnerabilityReport && + this.glFeatures.groupLevelVulnerabilityReportGrouping + ) { + return true; + } + + return this.isProjectVulnerabilityReport; }, groups() { if (!this.groupBy) { @@ -310,8 +326,8 @@ export default { const queryName = `groupCounts.${value}`; const variables = { fullPath: this.fullPath, - isProject: true, - isGroup: false, + isProject: this.isProjectVulnerabilityReport, + isGroup: this.isGroupVulnerabilityReport, isInstance: false, ...this.graphqlFilters, ...this.formatGroupFilters(value), @@ -329,8 +345,10 @@ export default { return variables; }, update(data) { - this.$set(this.groupCounts, value, data.project?.vulnerabilitySeveritiesCount); - return data.project?.vulnerabilitySeveritiesCount; + const level = this.isProjectVulnerabilityReport ? 'project' : 'group'; + + this.$set(this.groupCounts, value, data[level]?.vulnerabilitySeveritiesCount); + return data[level]?.vulnerabilitySeveritiesCount; }, error() { createAlert({ diff --git a/ee/app/controllers/groups/security/vulnerabilities_controller.rb b/ee/app/controllers/groups/security/vulnerabilities_controller.rb index 38eeaed1ea15cd8cec2b4103274ee7c4fcc2f623..ac219c8b990a2e48bb5af0b5b863161fddfb7116 100644 --- a/ee/app/controllers/groups/security/vulnerabilities_controller.rb +++ b/ee/app/controllers/groups/security/vulnerabilities_controller.rb @@ -14,6 +14,7 @@ class VulnerabilitiesController < Groups::ApplicationController before_action do 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, @project) end def index diff --git a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_report_spec.js b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_report_spec.js index 0a5a88ca1ea165269e46e395a2e14a0e5bc626db..9e879f1be7ccf64b49a52e96ff413d60ff1411be 100644 --- a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_report_spec.js +++ b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/vulnerability_report_spec.js @@ -40,7 +40,9 @@ describe('Vulnerability report component', () => { fullPath, filterFn, scanners, + isGroupVulnerabilityReport, isProjectVulnerabilityReport = true, + glFeatures, shouldShowCallout = false, } = {}) => { router = new VueRouter({ @@ -54,9 +56,11 @@ describe('Vulnerability report component', () => { apolloProvider, router, provide: { + isGroupVulnerabilityReport, isProjectVulnerabilityReport, fullPath, scanners, + glFeatures, }, propsData: { query, @@ -212,13 +216,14 @@ describe('Vulnerability report component', () => { }); }); - describe('group by', () => { + describe.each` + dashboardType + ${DASHBOARD_TYPES.PROJECT} + ${DASHBOARD_TYPES.GROUP} + `('group by: $dashboardType', ({ dashboardType }) => { const counts = { critical: 1, high: 2, medium: 5, low: 4, info: 3, unknown: 6 }; - const getCountsRequestHandler = ({ - data = counts, - dashboardType = DASHBOARD_TYPES.PROJECT, - } = {}) => { + const getCountsRequestHandler = ({ data = counts } = {}) => { return jest.fn().mockResolvedValue({ data: { [dashboardType]: { @@ -249,10 +254,17 @@ describe('Vulnerability report component', () => { } = {}) => { createWrapper({ apolloProvider: createMockApollo([[countsQuery, getCountsRequestHandler()]]), - isProjectVulnerabilityReport: true, + isProjectVulnerabilityReport: dashboardType === DASHBOARD_TYPES.PROJECT, + isGroupVulnerabilityReport: dashboardType === DASHBOARD_TYPES.GROUP, fullPath: 'gitlab-org/gitlab', shouldShowCallout, scanners, + glFeatures: + DASHBOARD_TYPES.GROUP === dashboardType + ? { + groupLevelVulnerabilityReportGrouping: true, + } + : {}, }); // Reset filters @@ -267,26 +279,6 @@ describe('Vulnerability report component', () => { await waitForPromises(); }; - it.each` - isProjectVulnerabilityReport | isVisible | shouldDisplay - ${false} | ${true} | ${false} - ${true} | ${true} | ${true} - ${true} | ${false} | ${false} - `( - 'displays the group by button correctly ' + - 'project: $isProjectVulnerabilityReport, ' + - 'isVisible: $isVisible, ' + - 'should display: $shouldDisplay', - ({ isProjectVulnerabilityReport, isVisible, shouldDisplay }) => { - createWrapper({ - isProjectVulnerabilityReport, - isVisible, - }); - - expect(findGroupByButton().exists()).toBe(shouldDisplay); - }, - ); - it('displays the group by label', () => { createWrapperWithFilters(); expect(wrapper.findByText('Group by:').exists()).toBe(true);