Commit e1be4b08 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '233536-project-security-status-report' into 'master'

Use GraphQL to get list of vulnerable projects

See merge request gitlab-org/gitlab!38808
parents 4f1ad487 31deede6
...@@ -11,6 +11,7 @@ import projectsQuery from 'ee/security_dashboard/graphql/get_instance_security_d ...@@ -11,6 +11,7 @@ import projectsQuery from 'ee/security_dashboard/graphql/get_instance_security_d
import ProjectManager from './first_class_project_manager/project_manager.vue'; import ProjectManager from './first_class_project_manager/project_manager.vue';
import CsvExportButton from './csv_export_button.vue'; import CsvExportButton from './csv_export_button.vue';
import vulnerabilityHistoryQuery from '../graphql/instance_vulnerability_history.query.graphql'; import vulnerabilityHistoryQuery from '../graphql/instance_vulnerability_history.query.graphql';
import vulnerabilityGradesQuery from '../graphql/instance_vulnerability_grades.query.graphql';
import DashboardNotConfigured from './empty_states/instance_dashboard_not_configured.vue'; import DashboardNotConfigured from './empty_states/instance_dashboard_not_configured.vue';
export default { export default {
...@@ -48,6 +49,7 @@ export default { ...@@ -48,6 +49,7 @@ export default {
filters: {}, filters: {},
showProjectSelector: false, showProjectSelector: false,
vulnerabilityHistoryQuery, vulnerabilityHistoryQuery,
vulnerabilityGradesQuery,
projects: [], projects: [],
isManipulatingProjects: false, isManipulatingProjects: false,
}; };
...@@ -133,7 +135,7 @@ export default { ...@@ -133,7 +135,7 @@ export default {
<template #aside> <template #aside>
<template v-if="shouldShowDashboard"> <template v-if="shouldShowDashboard">
<vulnerability-chart :query="vulnerabilityHistoryQuery" class="mb-4" /> <vulnerability-chart :query="vulnerabilityHistoryQuery" class="mb-4" />
<vulnerability-severities :projects="projects" /> <vulnerability-severities :query="vulnerabilityGradesQuery" />
</template> </template>
</template> </template>
</security-dashboard-layout> </security-dashboard-layout>
......
<script> <script>
import { GlLink, GlTooltipDirective, GlIcon } from '@gitlab/ui'; import { GlLink, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import { sum } from '~/lib/utils/number_utils';
import { isNumber } from 'lodash';
import { Accordion, AccordionItem } from 'ee/vue_shared/components/accordion'; import { Accordion, AccordionItem } from 'ee/vue_shared/components/accordion';
import { import {
severityGroupTypes, severityGroupTypes,
...@@ -40,11 +38,30 @@ export default { ...@@ -40,11 +38,30 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
projects: { query: {
type: Array, type: Object,
required: true, required: true,
}, },
}, },
data() {
return {
vulnerabilityGrades: {},
errorLoadingVulnerabilitiesGrades: false,
};
},
apollo: {
vulnerabilityGrades: {
query() {
return this.query;
},
update(results) {
return this.processRawData(results);
},
error() {
this.errorLoadingVulnerabilitiesGrades = true;
},
},
},
computed: { computed: {
severityGroups() { severityGroups() {
return SEVERITY_GROUPS.map(group => ({ return SEVERITY_GROUPS.map(group => ({
...@@ -55,25 +72,15 @@ export default { ...@@ -55,25 +72,15 @@ export default {
}, },
methods: { methods: {
findProjectsForGroup(group) { findProjectsForGroup(group) {
if (group.type === severityGroupTypes.A) { if (!this.vulnerabilityGrades[group.type]) {
return this.projects.filter( return [];
project =>
Object.values(project.vulnerabilitySeveritiesCount || {})
.filter(i => isNumber(i))
.reduce(sum, 0) === 0,
);
} }
return this.projects return this.vulnerabilityGrades[group.type].map(project => ({
.filter(project => ...project,
group.severityLevels.some(level => project.vulnerabilitySeveritiesCount?.[level] > 0), mostSevereVulnerability: this.findMostSevereVulnerabilityForGroup(project, group),
) }));
.map(project => ({
...project,
mostSevereVulnerability: this.findMostSevereVulnerabilityForGroup(project, group),
}));
}, },
findMostSevereVulnerabilityForGroup(project, group) { findMostSevereVulnerabilityForGroup(project, group) {
const mostSevereVulnerability = {}; const mostSevereVulnerability = {};
...@@ -94,6 +101,14 @@ export default { ...@@ -94,6 +101,14 @@ export default {
return mostSevereVulnerability; return mostSevereVulnerability;
}, },
processRawData(results) {
const { vulnerabilityGrades } = results.instanceSecurityDashboard;
return vulnerabilityGrades.reduce((acc, v) => {
acc[v.grade] = v.projects.nodes;
return acc;
}, {});
},
shouldAccordionItemBeDisabled({ projects }) { shouldAccordionItemBeDisabled({ projects }) {
return projects?.length < 1; return projects?.length < 1;
}, },
......
#import "ee/security_dashboard/graphql/project.fragment.graphql" #import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerablity_severities_count.fragment.graphql" #import "./vulnerability_severities_count.fragment.graphql"
query projectsQuery { query projectsQuery {
instanceSecurityDashboard { instanceSecurityDashboard {
......
#import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerability_severities_count.fragment.graphql"
query instanceVulnerabilityGrades {
instanceSecurityDashboard {
vulnerabilityGrades {
grade
projects {
nodes {
...Project
...VulnerabilitySeveritiesCount
}
}
}
}
}
---
title: Use GraphQL to get list of vulnerable projects
merge_request: 38808
author:
type: changed
...@@ -5,61 +5,26 @@ import { trimText } from 'helpers/text_helper'; ...@@ -5,61 +5,26 @@ import { trimText } from 'helpers/text_helper';
import { severityGroupTypes } from 'ee/security_dashboard/store/modules/vulnerable_projects/constants'; import { severityGroupTypes } from 'ee/security_dashboard/store/modules/vulnerable_projects/constants';
import { Accordion, AccordionItem } from 'ee/vue_shared/components/accordion'; import { Accordion, AccordionItem } from 'ee/vue_shared/components/accordion';
import VulnerabilitySeverity from 'ee/security_dashboard/components/first_class_vulnerability_severities.vue'; import VulnerabilitySeverity from 'ee/security_dashboard/components/first_class_vulnerability_severities.vue';
import { generateProjectsWithSeverityCounts } from './mock_data';
describe('Vulnerability Severity component', () => { describe('Vulnerability Severity component', () => {
let wrapper; let wrapper;
const helpPagePath = 'http://localhost/help-me'; const helpPagePath = 'http://localhost/help-me';
const projects = [ const projects = generateProjectsWithSeverityCounts();
{ const vulnerabilityGrades = {
id: 'gid://gitlab/Project/11', F: [projects[0]],
name: 'Security Reports Internal', D: [projects[1]],
nameWithNamespace: 'Administrator / Security Reports Internal', C: [projects[2], projects[3]],
fullPath: 'root/security-reports-internal', B: [projects[4]],
vulnerabilitySeveritiesCount: { A: [projects[5], projects[6]],
critical: 2, };
high: 1,
info: 0,
low: 2,
medium: 5,
unknown: 3,
},
},
{
id: 'gid://gitlab/Project/1',
name: 'Gitlab Test',
nameWithNamespace: 'Gitlab Org / Gitlab Test',
fullPath: 'gitlab-org/gitlab-test',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
info: 1,
low: 0,
medium: 4,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/2',
name: 'Gitlab Test',
nameWithNamespace: 'Gitlab Org / Gitlab Test - 2',
fullPath: 'gitlab-org/gitlab-test',
vulnerabilitySeveritiesCount: {},
},
{
id: 'gid://gitlab/Project/3',
name: 'Gitlab Test',
nameWithNamespace: 'Gitlab Org / Gitlab Test - 3',
fullPath: 'gitlab-org/gitlab-test',
vulnerabilitySeveritiesCount: {
critical: 0,
},
},
];
const createWrapper = ({ propsData }) => { const createComponent = ({ $apollo, propsData, data }) => {
return shallowMount(VulnerabilitySeverity, { return shallowMount(VulnerabilitySeverity, {
$apollo,
propsData: { propsData: {
query: {},
helpPagePath, helpPagePath,
...propsData, ...propsData,
}, },
...@@ -67,6 +32,7 @@ describe('Vulnerability Severity component', () => { ...@@ -67,6 +32,7 @@ describe('Vulnerability Severity component', () => {
Accordion, Accordion,
AccordionItem, AccordionItem,
}, },
data,
}); });
}; };
...@@ -82,7 +48,7 @@ describe('Vulnerability Severity component', () => { ...@@ -82,7 +48,7 @@ describe('Vulnerability Severity component', () => {
}); });
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper({ propsData: { projects } }); wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) });
}); });
describe('for all cases', () => { describe('for all cases', () => {
...@@ -104,10 +70,10 @@ describe('Vulnerability Severity component', () => { ...@@ -104,10 +70,10 @@ describe('Vulnerability Severity component', () => {
describe.each` describe.each`
grade | relatedProjects | correspondingMostSevereVulnerability | levels grade | relatedProjects | correspondingMostSevereVulnerability | levels
${severityGroupTypes.F} | ${[projects[0]]} | ${['2 Critical']} | ${'Critical'} ${severityGroupTypes.F} | ${[projects[0]]} | ${['2 Critical']} | ${'Critical'}
${severityGroupTypes.D} | ${[projects[0]]} | ${['1 High']} | ${'High or unknown'} ${severityGroupTypes.D} | ${[projects[1]]} | ${['1 High']} | ${'High or unknown'}
${severityGroupTypes.C} | ${[projects[0], projects[1]]} | ${['5 Medium', '4 Medium']} | ${'Medium'} ${severityGroupTypes.C} | ${[projects[2], projects[3]]} | ${['5 Medium', '4 Medium']} | ${'Medium'}
${severityGroupTypes.B} | ${[projects[0]]} | ${['2 Low']} | ${'Low'} ${severityGroupTypes.B} | ${[projects[4]]} | ${['2 Low']} | ${'Low'}
${severityGroupTypes.A} | ${[projects[2], projects[3]]} | ${['No vulnerabilities present', 'No vulnerabilities present']} | ${'No'} ${severityGroupTypes.A} | ${[projects[5], projects[6]]} | ${['No vulnerabilities present', 'No vulnerabilities present']} | ${'No'}
`( `(
'for grade $grade', 'for grade $grade',
({ grade, relatedProjects, correspondingMostSevereVulnerability, levels }) => { ({ grade, relatedProjects, correspondingMostSevereVulnerability, levels }) => {
......
...@@ -93,3 +93,106 @@ export const generateVulnerabilities = () => [ ...@@ -93,3 +93,106 @@ export const generateVulnerabilities = () => [
]; ];
export const vulnerabilities = generateVulnerabilities(); export const vulnerabilities = generateVulnerabilities();
export const generateProjectsWithSeverityCounts = () => [
{
id: 'gid://gitlab/Project/1',
name: 'Gitlab Test 1',
nameWithNamespace: 'Gitlab Org / Gitlab Test 1',
fullPath: 'gitlab-org/gitlab-test-1',
vulnerabilitySeveritiesCount: {
critical: 2,
high: 0,
info: 0,
low: 0,
medium: 0,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/2',
name: 'Gitlab Test 2',
nameWithNamespace: 'Gitlab Org / Gitlab Test 2',
fullPath: 'gitlab-org/gitlab-test-2',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 1,
info: 0,
low: 0,
medium: 0,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/3',
name: 'Gitlab Test 3',
nameWithNamespace: 'Gitlab Org / Gitlab Test 3',
fullPath: 'gitlab-org/gitlab-test-3',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
info: 0,
low: 0,
medium: 5,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/4',
name: 'Gitlab Test 4',
nameWithNamespace: 'Gitlab Org / Gitlab Test 4',
fullPath: 'gitlab-org/gitlab-test-4',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
info: 0,
low: 0,
medium: 4,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/5',
name: 'Gitlab Test 5',
nameWithNamespace: 'Gitlab Org / Gitlab Test 5',
fullPath: 'gitlab-org/gitlab-test-5',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
info: 0,
low: 2,
medium: 0,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/6',
name: 'Gitlab Test 6',
nameWithNamespace: 'Gitlab Org / Gitlab Test 6',
fullPath: 'gitlab-org/gitlab-test-6',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
info: 0,
low: 0,
medium: 0,
unknown: 0,
},
},
{
id: 'gid://gitlab/Project/7',
name: 'Gitlab Test 7',
nameWithNamespace: 'Gitlab Org / Gitlab Test 7',
fullPath: 'gitlab-org/gitlab-test-7',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
info: 2,
low: 0,
medium: 0,
unknown: 0,
},
},
];
export const projectsWithSeverityCounts = generateProjectsWithSeverityCounts();
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