Commit 2cfc665e authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch...

Merge branch '262109-fe-add-severity-counters-to-group-and-instance-vulnerability-reports-refactor' into 'master'

Refactor severity vulnerabilities count to support group- and instance- reports

See merge request gitlab-org/gitlab!47743
parents 2f8d34fb a18ff69d
...@@ -9,6 +9,7 @@ import SecurityDashboardLayout from './security_dashboard_layout.vue'; ...@@ -9,6 +9,7 @@ import SecurityDashboardLayout from './security_dashboard_layout.vue';
import VulnerabilitiesCountList from './vulnerability_count_list.vue'; import VulnerabilitiesCountList from './vulnerability_count_list.vue';
import Filters from './first_class_vulnerability_filters.vue'; import Filters from './first_class_vulnerability_filters.vue';
import CsvExportButton from './csv_export_button.vue'; import CsvExportButton from './csv_export_button.vue';
import { vulnerabilitiesSeverityCountScopes } from '../constants';
export const BANNER_COOKIE_KEY = 'hide_vulnerabilities_introduction_banner'; export const BANNER_COOKIE_KEY = 'hide_vulnerabilities_introduction_banner';
...@@ -48,7 +49,7 @@ export default { ...@@ -48,7 +49,7 @@ export default {
shoudShowAutoFixUserCallout, shoudShowAutoFixUserCallout,
}; };
}, },
inject: ['dashboardDocumentation', 'autoFixDocumentation'], inject: ['dashboardDocumentation', 'autoFixDocumentation', 'projectFullPath'],
methods: { methods: {
handleFilterChange(filters) { handleFilterChange(filters) {
this.filters = filters; this.filters = filters;
...@@ -58,6 +59,7 @@ export default { ...@@ -58,6 +59,7 @@ export default {
this.shoudShowAutoFixUserCallout = false; this.shoudShowAutoFixUserCallout = false;
}, },
}, },
vulnerabilitiesSeverityCountScopes,
}; };
</script> </script>
...@@ -71,12 +73,17 @@ export default { ...@@ -71,12 +73,17 @@ export default {
/> />
<security-dashboard-layout> <security-dashboard-layout>
<template #header> <template #header>
<div class="mt-4 d-flex"> <div class="gl-mt-6 gl-display-flex">
<h4 class="flex-grow mt-0 mb-0">{{ __('Vulnerabilities') }}</h4> <h4 class="gl-flex-grow-1 gl-my-0">{{ __('Vulnerabilities') }}</h4>
<csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" /> <csv-export-button :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" />
</div> </div>
<project-pipeline-status :pipeline="pipeline" /> <project-pipeline-status :pipeline="pipeline" />
<vulnerabilities-count-list :filters="filters" /> <vulnerabilities-count-list
class="gl-mt-6"
:scope="$options.vulnerabilitiesSeverityCountScopes.project"
:full-path="projectFullPath"
:filters="filters"
/>
</template> </template>
<template #sticky> <template #sticky>
<filters @filterChange="handleFilterChange" /> <filters @filterChange="handleFilterChange" />
......
<script> <script>
import vulnerabilitySeveritiesCountQuery from '../graphql/project_vulnerability_severities_count.graphql'; import vulnerabilitySeveritiesCountQuery from '../graphql/vulnerability_severities_count.graphql';
import VulnerabilityCountListLayout from './vulnerability_count_list_layout.vue'; import VulnerabilityCountListLayout from './vulnerability_count_list_layout.vue';
import { vulnerabilitiesSeverityCountScopes } from '../constants';
export default { export default {
components: { components: {
VulnerabilityCountListLayout, VulnerabilityCountListLayout,
}, },
inject: ['projectFullPath'],
props: { props: {
scope: {
type: String,
required: true,
validator: value => Object.values(vulnerabilitiesSeverityCountScopes).includes(value),
},
fullPath: {
type: String,
required: false,
default: '',
},
filters: { filters: {
type: Object, type: Object,
required: false, required: false,
...@@ -27,12 +38,19 @@ export default { ...@@ -27,12 +38,19 @@ export default {
vulnerabilitiesCount: { vulnerabilitiesCount: {
query: vulnerabilitySeveritiesCountQuery, query: vulnerabilitySeveritiesCountQuery,
variables() { variables() {
const { scope, fullPath } = this;
const { instance, group, project } = vulnerabilitiesSeverityCountScopes;
return { return {
fullPath: this.projectFullPath, fullPath,
isInstance: scope === instance,
isGroup: scope === group,
isProject: scope === project,
...this.filters, ...this.filters,
}; };
}, },
update: ({ project }) => project?.vulnerabilitySeveritiesCount || {}, update(data) {
return data[this.scope]?.vulnerabilitySeveritiesCount || {};
},
result() { result() {
this.queryError = false; this.queryError = false;
}, },
......
...@@ -46,7 +46,7 @@ export default { ...@@ -46,7 +46,7 @@ export default {
</script> </script>
<template> <template>
<div class="vulnerabilities-count-list mt-4"> <div class="vulnerabilities-count-list">
<gl-alert v-if="showAlert" class="mb-4" variant="danger" @dismiss="onErrorDismiss"> <gl-alert v-if="showAlert" class="mb-4" variant="danger" @dismiss="onErrorDismiss">
{{ {{
s__( s__(
......
export const COLLAPSE_SECURITY_REPORTS_SUMMARY_LOCAL_STORAGE_KEY = export const COLLAPSE_SECURITY_REPORTS_SUMMARY_LOCAL_STORAGE_KEY =
'hide_pipelines_security_reports_summary_details'; 'hide_pipelines_security_reports_summary_details';
export default () => ({}); export const vulnerabilitiesSeverityCountScopes = {
instance: 'instance',
group: 'group',
project: 'project',
};
#import "ee/security_dashboard/graphql/project.fragment.graphql" #import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerability_severities_count.fragment.graphql" #import "ee/security_dashboard/graphql/project_vulnerability_severities_count.fragment.graphql"
query projectsQuery { query projectsQuery {
instanceSecurityDashboard { instanceSecurityDashboard {
projects { projects {
nodes { nodes {
...Project ...Project
...VulnerabilitySeveritiesCount ...ProjectVulnerabilitySeveritiesCount
} }
} }
} }
......
#import "ee/security_dashboard/graphql/project.fragment.graphql" #import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerability_severities_count.fragment.graphql" #import "ee/security_dashboard/graphql/project_vulnerability_severities_count.fragment.graphql"
query groupVulnerabilityGrades($fullPath: ID!) { query groupVulnerabilityGrades($fullPath: ID!) {
group(fullPath: $fullPath) { group(fullPath: $fullPath) {
...@@ -8,7 +8,7 @@ query groupVulnerabilityGrades($fullPath: ID!) { ...@@ -8,7 +8,7 @@ query groupVulnerabilityGrades($fullPath: ID!) {
projects { projects {
nodes { nodes {
...Project ...Project
...VulnerabilitySeveritiesCount ...ProjectVulnerabilitySeveritiesCount
} }
} }
} }
......
#import "ee/security_dashboard/graphql/project.fragment.graphql" #import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerability_severities_count.fragment.graphql" #import "ee/security_dashboard/graphql/project_vulnerability_severities_count.fragment.graphql"
query instanceVulnerabilityGrades { query instanceVulnerabilityGrades {
instanceSecurityDashboard { instanceSecurityDashboard {
...@@ -8,7 +8,7 @@ query instanceVulnerabilityGrades { ...@@ -8,7 +8,7 @@ query instanceVulnerabilityGrades {
projects { projects {
nodes { nodes {
...Project ...Project
...VulnerabilitySeveritiesCount ...ProjectVulnerabilitySeveritiesCount
} }
} }
} }
......
#import "ee/security_dashboard/graphql/vulnerability_severities_count.fragment.graphql"
fragment ProjectVulnerabilitySeveritiesCount on Project {
vulnerabilitySeveritiesCount {
...VulnerabilitySeveritiesCount
}
}
query vulnerabilitySeveritiesCount(
$fullPath: ID!
$severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!]
$scanner: [String!]
$state: [VulnerabilityState!]
) {
project(fullPath: $fullPath) {
vulnerabilitySeveritiesCount(
severity: $severity
reportType: $reportType
scanner: $scanner
state: $state
) {
critical
high
low
medium
info
unknown
}
}
}
fragment VulnerabilitySeveritiesCount on Project { fragment VulnerabilitySeveritiesCount on VulnerabilitySeveritiesCount {
vulnerabilitySeveritiesCount { critical
critical high
high info
info low
low medium
medium unknown
unknown
}
} }
#import "ee/security_dashboard/graphql/vulnerability_severities_count.fragment.graphql"
query vulnerabilitySeveritiesCount(
$fullPath: ID = ""
$projectId: [ID!]
$severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!]
$scanner: [String!]
$state: [VulnerabilityState!]
$isGroup: Boolean = false
$isProject: Boolean = false
$isInstance: Boolean = false
) {
instance: instanceSecurityDashboard @include(if: $isInstance) {
vulnerabilitySeveritiesCount(
projectId: $projectId
severity: $severity
reportType: $reportType
scanner: $scanner
state: $state
) {
...VulnerabilitySeveritiesCount
}
}
group(fullPath: $fullPath) @include(if: $isGroup) {
vulnerabilitySeveritiesCount(
projectId: $projectId
severity: $severity
reportType: $reportType
scanner: $scanner
state: $state
) {
...VulnerabilitySeveritiesCount
}
}
project(fullPath: $fullPath) @include(if: $isProject) {
vulnerabilitySeveritiesCount(
severity: $severity
reportType: $reportType
scanner: $scanner
state: $state
) {
...VulnerabilitySeveritiesCount
}
}
}
...@@ -84,7 +84,8 @@ describe('First class Project Security Dashboard component', () => { ...@@ -84,7 +84,8 @@ describe('First class Project Security Dashboard component', () => {
it('should pass down the properties correctly to the vulnerability count list', () => { it('should pass down the properties correctly to the vulnerability count list', () => {
expect(findVulnerabilityCountList().props()).toEqual({ expect(findVulnerabilityCountList().props()).toEqual({
projectFullPath: props.projectFullPath, scope: 'project',
fullPath: provide.projectFullPath,
filters, filters,
}); });
}); });
......
...@@ -7,11 +7,9 @@ describe('Vulnerabilities count list component', () => { ...@@ -7,11 +7,9 @@ describe('Vulnerabilities count list component', () => {
const findVulnerabilityLayout = () => wrapper.find(VulnerabilityCountListLayout); const findVulnerabilityLayout = () => wrapper.find(VulnerabilityCountListLayout);
const createWrapper = ({ query } = {}) => { const createWrapper = ({ query = { isLoading: false }, props = { scope: 'project' } } = {}) => {
return shallowMount(VulnerabilityCountList, { return shallowMount(VulnerabilityCountList, {
provide: { propsData: props,
projectFullPath: '/root/security-project',
},
mocks: { mocks: {
$apollo: { queries: { vulnerabilitiesCount: query } }, $apollo: { queries: { vulnerabilitiesCount: query } },
}, },
...@@ -60,6 +58,27 @@ describe('Vulnerabilities count list component', () => { ...@@ -60,6 +58,27 @@ describe('Vulnerabilities count list component', () => {
}); });
}); });
describe.each`
givenScope | expectedContainedQueryVariables
${'instance'} | ${{ isInstance: true, isGroup: false, isProject: false }}
${'group'} | ${{ isInstance: false, isGroup: true, isProject: false }}
${'project'} | ${{ isInstance: false, isGroup: false, isProject: true }}
`(
'when the scope prop is set to "$givenScope"',
({ givenScope, expectedContainedQueryVariables }) => {
beforeEach(() => {
wrapper = createWrapper({ props: { scope: givenScope } });
return wrapper.vm.$nextTick();
});
it('should pass the correct variables to the GraphQL query', () => {
expect(
wrapper.vm.$options.apollo.vulnerabilitiesCount.variables.call(wrapper.vm),
).toMatchObject(expectedContainedQueryVariables);
});
},
);
describe('when there is an error', () => { describe('when there is an error', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper({ query: {} }); wrapper = createWrapper({ query: {} });
......
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