Commit 31deede6 authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Natalia Tepluhina

Use GraphQL to get list of vulnerable projects

This change changes the API that loads vulnerable projects for instance
security dashboard to use new vulnerabilityGrades type.
parent b5a9fea5
...@@ -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