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
import ProjectManager from './first_class_project_manager/project_manager.vue';
import CsvExportButton from './csv_export_button.vue';
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';
export default {
......@@ -48,6 +49,7 @@ export default {
filters: {},
showProjectSelector: false,
vulnerabilityHistoryQuery,
vulnerabilityGradesQuery,
projects: [],
isManipulatingProjects: false,
};
......@@ -133,7 +135,7 @@ export default {
<template #aside>
<template v-if="shouldShowDashboard">
<vulnerability-chart :query="vulnerabilityHistoryQuery" class="mb-4" />
<vulnerability-severities :projects="projects" />
<vulnerability-severities :query="vulnerabilityGradesQuery" />
</template>
</template>
</security-dashboard-layout>
......
<script>
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 {
severityGroupTypes,
......@@ -40,11 +38,30 @@ export default {
required: false,
default: '',
},
projects: {
type: Array,
query: {
type: Object,
required: true,
},
},
data() {
return {
vulnerabilityGrades: {},
errorLoadingVulnerabilitiesGrades: false,
};
},
apollo: {
vulnerabilityGrades: {
query() {
return this.query;
},
update(results) {
return this.processRawData(results);
},
error() {
this.errorLoadingVulnerabilitiesGrades = true;
},
},
},
computed: {
severityGroups() {
return SEVERITY_GROUPS.map(group => ({
......@@ -55,25 +72,15 @@ export default {
},
methods: {
findProjectsForGroup(group) {
if (group.type === severityGroupTypes.A) {
return this.projects.filter(
project =>
Object.values(project.vulnerabilitySeveritiesCount || {})
.filter(i => isNumber(i))
.reduce(sum, 0) === 0,
);
if (!this.vulnerabilityGrades[group.type]) {
return [];
}
return this.projects
.filter(project =>
group.severityLevels.some(level => project.vulnerabilitySeveritiesCount?.[level] > 0),
)
.map(project => ({
...project,
mostSevereVulnerability: this.findMostSevereVulnerabilityForGroup(project, group),
}));
return this.vulnerabilityGrades[group.type].map(project => ({
...project,
mostSevereVulnerability: this.findMostSevereVulnerabilityForGroup(project, group),
}));
},
findMostSevereVulnerabilityForGroup(project, group) {
const mostSevereVulnerability = {};
......@@ -94,6 +101,14 @@ export default {
return mostSevereVulnerability;
},
processRawData(results) {
const { vulnerabilityGrades } = results.instanceSecurityDashboard;
return vulnerabilityGrades.reduce((acc, v) => {
acc[v.grade] = v.projects.nodes;
return acc;
}, {});
},
shouldAccordionItemBeDisabled({ projects }) {
return projects?.length < 1;
},
......
#import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerablity_severities_count.fragment.graphql"
#import "./vulnerability_severities_count.fragment.graphql"
query projectsQuery {
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';
import { severityGroupTypes } from 'ee/security_dashboard/store/modules/vulnerable_projects/constants';
import { Accordion, AccordionItem } from 'ee/vue_shared/components/accordion';
import VulnerabilitySeverity from 'ee/security_dashboard/components/first_class_vulnerability_severities.vue';
import { generateProjectsWithSeverityCounts } from './mock_data';
describe('Vulnerability Severity component', () => {
let wrapper;
const helpPagePath = 'http://localhost/help-me';
const projects = [
{
id: 'gid://gitlab/Project/11',
name: 'Security Reports Internal',
nameWithNamespace: 'Administrator / Security Reports Internal',
fullPath: 'root/security-reports-internal',
vulnerabilitySeveritiesCount: {
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 projects = generateProjectsWithSeverityCounts();
const vulnerabilityGrades = {
F: [projects[0]],
D: [projects[1]],
C: [projects[2], projects[3]],
B: [projects[4]],
A: [projects[5], projects[6]],
};
const createWrapper = ({ propsData }) => {
const createComponent = ({ $apollo, propsData, data }) => {
return shallowMount(VulnerabilitySeverity, {
$apollo,
propsData: {
query: {},
helpPagePath,
...propsData,
},
......@@ -67,6 +32,7 @@ describe('Vulnerability Severity component', () => {
Accordion,
AccordionItem,
},
data,
});
};
......@@ -82,7 +48,7 @@ describe('Vulnerability Severity component', () => {
});
beforeEach(() => {
wrapper = createWrapper({ propsData: { projects } });
wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) });
});
describe('for all cases', () => {
......@@ -104,10 +70,10 @@ describe('Vulnerability Severity component', () => {
describe.each`
grade | relatedProjects | correspondingMostSevereVulnerability | levels
${severityGroupTypes.F} | ${[projects[0]]} | ${['2 Critical']} | ${'Critical'}
${severityGroupTypes.D} | ${[projects[0]]} | ${['1 High']} | ${'High or unknown'}
${severityGroupTypes.C} | ${[projects[0], projects[1]]} | ${['5 Medium', '4 Medium']} | ${'Medium'}
${severityGroupTypes.B} | ${[projects[0]]} | ${['2 Low']} | ${'Low'}
${severityGroupTypes.A} | ${[projects[2], projects[3]]} | ${['No vulnerabilities present', 'No vulnerabilities present']} | ${'No'}
${severityGroupTypes.D} | ${[projects[1]]} | ${['1 High']} | ${'High or unknown'}
${severityGroupTypes.C} | ${[projects[2], projects[3]]} | ${['5 Medium', '4 Medium']} | ${'Medium'}
${severityGroupTypes.B} | ${[projects[4]]} | ${['2 Low']} | ${'Low'}
${severityGroupTypes.A} | ${[projects[5], projects[6]]} | ${['No vulnerabilities present', 'No vulnerabilities present']} | ${'No'}
`(
'for grade $grade',
({ grade, relatedProjects, correspondingMostSevereVulnerability, levels }) => {
......
......@@ -93,3 +93,106 @@ export const 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