Commit 5adf8f0a authored by Andrew Fontaine's avatar Andrew Fontaine

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

Use GraphQL to get list of vulnerable projects for group

See merge request gitlab-org/gitlab!38878
parents e6e504ac b2cec84c
......@@ -42,6 +42,11 @@ export default {
type: Object,
required: true,
},
groupFullPath: {
type: String,
required: false,
default: undefined,
},
},
data() {
return {
......@@ -54,6 +59,11 @@ export default {
query() {
return this.query;
},
variables() {
return {
fullPath: this.groupFullPath,
};
},
update(results) {
return this.processRawData(results);
},
......@@ -102,7 +112,9 @@ export default {
return mostSevereVulnerability;
},
processRawData(results) {
const { vulnerabilityGrades } = results.instanceSecurityDashboard;
const { vulnerabilityGrades } = this.groupFullPath
? results.group
: results.instanceSecurityDashboard;
return vulnerabilityGrades.reduce((acc, v) => {
acc[v.grade] = v.projects.nodes;
......@@ -152,8 +164,11 @@ export default {
:disabled="shouldAccordionItemBeDisabled(severityGroup)"
:max-height="$options.accordionItemsContentMaxHeight"
>
<template #title="{ isExpanded, isDisabled }"
><h5 class="gl-display-flex gl-align-items-center gl-font-weight-normal gl-p-0 gl-m-0">
<template #title="{ isExpanded, isDisabled }">
<h5
class="gl-display-flex gl-align-items-center gl-font-weight-normal gl-p-0 gl-m-0"
data-testid="vulnerability-severity-groups"
>
<span
v-gl-tooltip
:title="severityGroup.description"
......
<script>
import VulnerabilityChart from './first_class_vulnerability_chart.vue';
import VulnerabilitySeverity from './vulnerability_severity.vue';
import VulnerabilitySeverities from './first_class_vulnerability_severities.vue';
import vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.query.graphql';
import vulnerabilityGradesQuery from '../graphql/group_vulnerability_grades.query.graphql';
import SecurityChartsLayout from './security_charts_layout.vue';
export default {
components: {
SecurityChartsLayout,
VulnerabilitySeverity,
VulnerabilitySeverities,
VulnerabilityChart,
},
props: {
......@@ -15,14 +16,11 @@ export default {
type: String,
required: true,
},
vulnerableProjectsEndpoint: {
type: String,
required: true,
},
},
data() {
return {
vulnerabilityHistoryQuery,
vulnerabilityGradesQuery,
};
},
};
......@@ -31,6 +29,6 @@ export default {
<template>
<security-charts-layout>
<vulnerability-chart :query="vulnerabilityHistoryQuery" :group-full-path="groupFullPath" />
<vulnerability-severity :endpoint="vulnerableProjectsEndpoint" />
<vulnerability-severities :query="vulnerabilityGradesQuery" :group-full-path="groupFullPath" />
</security-charts-layout>
</template>
......@@ -44,7 +44,6 @@ export default (el, dashboardType) => {
} else if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = FirstClassGroupSecurityDashboard;
props.groupFullPath = el.dataset.groupFullPath;
props.vulnerableProjectsEndpoint = el.dataset.vulnerableProjectsEndpoint;
} else if (dashboardType === DASHBOARD_TYPES.INSTANCE) {
component = FirstClassInstanceSecurityDashboard;
}
......
#import "ee/security_dashboard/graphql/project.fragment.graphql"
#import "./vulnerability_severities_count.fragment.graphql"
query groupVulnerabilityGrades($fullPath: ID!) {
group(fullPath: $fullPath) {
vulnerabilityGrades {
grade
projects {
nodes {
...Project
...VulnerabilitySeveritiesCount
}
}
}
}
}
......@@ -33,7 +33,6 @@ export default (el, dashboardType) => {
if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = GroupSecurityCharts;
props.groupFullPath = el.dataset.groupFullPath;
props.vulnerableProjectsEndpoint = el.dataset.vulnerableProjectsEndpoint;
}
const router = createRouter();
......
---
title: Change data source for Vulnerable Projects to GraphQL
merge_request: 38878
author:
type: performance
......@@ -13,7 +13,6 @@ describe('First Class Group Dashboard Component', () => {
const dashboardDocumentation = 'dashboard-documentation';
const emptyStateSvgPath = 'empty-state-path';
const groupFullPath = 'group-full-path';
const vulnerableProjectsEndpoint = '/vulnerable/projects';
const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports';
const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout);
......@@ -29,7 +28,6 @@ describe('First Class Group Dashboard Component', () => {
dashboardDocumentation,
emptyStateSvgPath,
groupFullPath,
vulnerableProjectsEndpoint,
vulnerabilitiesExportEndpoint,
},
data,
......
......@@ -19,10 +19,26 @@ describe('Vulnerability Severity component', () => {
B: [projects[4]],
A: [projects[5], projects[6]],
};
const responseData = {
vulnerabilityGrades: Object.entries(vulnerabilityGrades).map(([grade, gradeProjects]) => ({
grade,
projects: { nodes: gradeProjects },
})),
};
const findAccordionItemsText = () =>
wrapper
.findAll('[data-testid="vulnerability-severity-groups"]')
.wrappers.map(item => trimText(item.text()));
const createComponent = ({ $apollo, propsData, data }) => {
const mockAccordianItemsText = () =>
Object.entries(vulnerabilityGrades).map(
([grade, relatedProjects]) =>
`${grade} ${n__('%d project', '%d projects', relatedProjects.length)}`,
);
const createComponent = ({ propsData, data }) => {
return shallowMount(VulnerabilitySeverity, {
$apollo,
propsData: {
query: {},
helpPagePath,
......@@ -47,11 +63,37 @@ describe('Vulnerability Severity component', () => {
wrapper = null;
});
describe('when loading the project severity component for group level dashboard', () => {
beforeEach(() => {
wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) });
wrapper = createComponent({ propsData: { groupFullPath: 'gitlab-org' } });
});
it('should process the data returned from GraphQL properly', async () => {
wrapper.setData({ vulnerabilityGrades: wrapper.vm.processRawData({ group: responseData }) });
await wrapper.vm.$nextTick();
expect(findAccordionItemsText()).toEqual(mockAccordianItemsText());
});
});
describe('when loading the project severity component for instance level dashboard', () => {
beforeEach(() => {
wrapper = createComponent({});
});
it('should process the data returned from GraphQL properly', async () => {
wrapper.setData({
vulnerabilityGrades: wrapper.vm.processRawData({ instanceSecurityDashboard: responseData }),
});
await wrapper.vm.$nextTick();
expect(findAccordionItemsText()).toEqual(mockAccordianItemsText());
});
});
describe('for all cases', () => {
beforeEach(() => {
wrapper = createComponent({});
});
it('has the link to the help page', () => {
expect(findHelpLink().attributes('href')).toBe(helpPagePath);
});
......@@ -81,6 +123,7 @@ describe('Vulnerability Severity component', () => {
let text;
beforeEach(() => {
wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) });
accordion = findAccordionItemByGrade(grade);
text = trimText(accordion.text());
});
......
......@@ -3,7 +3,7 @@ import { TEST_HOST } from 'jest/helpers/test_constants';
import GroupSecurityCharts from 'ee/security_dashboard/components/group_security_charts.vue';
import SecurityChartsLayout from 'ee/security_dashboard/components/security_charts_layout.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import VulnerabilitySeverity from 'ee/security_dashboard/components/vulnerability_severity.vue';
import VulnerabilitySeverities from 'ee/security_dashboard/components/first_class_vulnerability_severities.vue';
jest.mock('ee/security_dashboard/graphql/group_vulnerability_history.query.graphql', () => ({}));
......@@ -11,15 +11,14 @@ describe('Group Security Charts component', () => {
let wrapper;
const groupFullPath = `${TEST_HOST}/group/5`;
const vulnerableProjectsEndpoint = `${TEST_HOST}/group/5/projects`;
const findSecurityChartsLayoutComponent = () => wrapper.find(SecurityChartsLayout);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilitySeverities = () => wrapper.find(VulnerabilitySeverities);
const createWrapper = () => {
wrapper = shallowMount(GroupSecurityCharts, {
propsData: { groupFullPath, vulnerableProjectsEndpoint },
propsData: { groupFullPath },
});
};
......@@ -35,13 +34,11 @@ describe('Group Security Charts component', () => {
it('renders the default page', () => {
const securityChartsLayout = findSecurityChartsLayoutComponent();
const vulnerabilityChart = findVulnerabilityChart();
const vulnerabilitySeverity = findVulnerabilitySeverity();
const vulnerabilitySeverities = findVulnerabilitySeverities();
expect(securityChartsLayout.exists()).toBe(true);
expect(vulnerabilityChart.props()).toEqual({ query: {}, groupFullPath });
expect(vulnerabilitySeverity.props()).toEqual({
endpoint: vulnerableProjectsEndpoint,
helpPagePath: '',
});
expect(vulnerabilitySeverities.exists()).toBe(true);
expect(vulnerabilitySeverities.props().groupFullPath).toEqual(groupFullPath);
});
});
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