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
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