Commit b2cec84c authored by Alan (Maciej) Paruszewski's avatar Alan (Maciej) Paruszewski Committed by Andrew Fontaine

Use GraphQL to get list of vulnerable projects for group

This change changes the API that loads vulnerable projects for group
security dashboard to use new vulnerabilityGrades type.
parent fe915758
...@@ -42,6 +42,11 @@ export default { ...@@ -42,6 +42,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
groupFullPath: {
type: String,
required: false,
default: undefined,
},
}, },
data() { data() {
return { return {
...@@ -54,6 +59,11 @@ export default { ...@@ -54,6 +59,11 @@ export default {
query() { query() {
return this.query; return this.query;
}, },
variables() {
return {
fullPath: this.groupFullPath,
};
},
update(results) { update(results) {
return this.processRawData(results); return this.processRawData(results);
}, },
...@@ -102,7 +112,9 @@ export default { ...@@ -102,7 +112,9 @@ export default {
return mostSevereVulnerability; return mostSevereVulnerability;
}, },
processRawData(results) { processRawData(results) {
const { vulnerabilityGrades } = results.instanceSecurityDashboard; const { vulnerabilityGrades } = this.groupFullPath
? results.group
: results.instanceSecurityDashboard;
return vulnerabilityGrades.reduce((acc, v) => { return vulnerabilityGrades.reduce((acc, v) => {
acc[v.grade] = v.projects.nodes; acc[v.grade] = v.projects.nodes;
...@@ -152,8 +164,11 @@ export default { ...@@ -152,8 +164,11 @@ export default {
:disabled="shouldAccordionItemBeDisabled(severityGroup)" :disabled="shouldAccordionItemBeDisabled(severityGroup)"
:max-height="$options.accordionItemsContentMaxHeight" :max-height="$options.accordionItemsContentMaxHeight"
> >
<template #title="{ isExpanded, isDisabled }" <template #title="{ isExpanded, isDisabled }">
><h5 class="gl-display-flex gl-align-items-center gl-font-weight-normal gl-p-0 gl-m-0"> <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 <span
v-gl-tooltip v-gl-tooltip
:title="severityGroup.description" :title="severityGroup.description"
......
<script> <script>
import VulnerabilityChart from './first_class_vulnerability_chart.vue'; 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 vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.query.graphql';
import vulnerabilityGradesQuery from '../graphql/group_vulnerability_grades.query.graphql';
import SecurityChartsLayout from './security_charts_layout.vue'; import SecurityChartsLayout from './security_charts_layout.vue';
export default { export default {
components: { components: {
SecurityChartsLayout, SecurityChartsLayout,
VulnerabilitySeverity, VulnerabilitySeverities,
VulnerabilityChart, VulnerabilityChart,
}, },
props: { props: {
...@@ -15,14 +16,11 @@ export default { ...@@ -15,14 +16,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
vulnerableProjectsEndpoint: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
vulnerabilityHistoryQuery, vulnerabilityHistoryQuery,
vulnerabilityGradesQuery,
}; };
}, },
}; };
...@@ -31,6 +29,6 @@ export default { ...@@ -31,6 +29,6 @@ export default {
<template> <template>
<security-charts-layout> <security-charts-layout>
<vulnerability-chart :query="vulnerabilityHistoryQuery" :group-full-path="groupFullPath" /> <vulnerability-chart :query="vulnerabilityHistoryQuery" :group-full-path="groupFullPath" />
<vulnerability-severity :endpoint="vulnerableProjectsEndpoint" /> <vulnerability-severities :query="vulnerabilityGradesQuery" :group-full-path="groupFullPath" />
</security-charts-layout> </security-charts-layout>
</template> </template>
...@@ -44,7 +44,6 @@ export default (el, dashboardType) => { ...@@ -44,7 +44,6 @@ export default (el, dashboardType) => {
} else if (dashboardType === DASHBOARD_TYPES.GROUP) { } else if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = FirstClassGroupSecurityDashboard; component = FirstClassGroupSecurityDashboard;
props.groupFullPath = el.dataset.groupFullPath; props.groupFullPath = el.dataset.groupFullPath;
props.vulnerableProjectsEndpoint = el.dataset.vulnerableProjectsEndpoint;
} else if (dashboardType === DASHBOARD_TYPES.INSTANCE) { } else if (dashboardType === DASHBOARD_TYPES.INSTANCE) {
component = FirstClassInstanceSecurityDashboard; 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) => { ...@@ -33,7 +33,6 @@ export default (el, dashboardType) => {
if (dashboardType === DASHBOARD_TYPES.GROUP) { if (dashboardType === DASHBOARD_TYPES.GROUP) {
component = GroupSecurityCharts; component = GroupSecurityCharts;
props.groupFullPath = el.dataset.groupFullPath; props.groupFullPath = el.dataset.groupFullPath;
props.vulnerableProjectsEndpoint = el.dataset.vulnerableProjectsEndpoint;
} }
const router = createRouter(); 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', () => { ...@@ -13,7 +13,6 @@ describe('First Class Group Dashboard Component', () => {
const dashboardDocumentation = 'dashboard-documentation'; const dashboardDocumentation = 'dashboard-documentation';
const emptyStateSvgPath = 'empty-state-path'; const emptyStateSvgPath = 'empty-state-path';
const groupFullPath = 'group-full-path'; const groupFullPath = 'group-full-path';
const vulnerableProjectsEndpoint = '/vulnerable/projects';
const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports'; const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports';
const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout); const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout);
...@@ -29,7 +28,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -29,7 +28,6 @@ describe('First Class Group Dashboard Component', () => {
dashboardDocumentation, dashboardDocumentation,
emptyStateSvgPath, emptyStateSvgPath,
groupFullPath, groupFullPath,
vulnerableProjectsEndpoint,
vulnerabilitiesExportEndpoint, vulnerabilitiesExportEndpoint,
}, },
data, data,
......
...@@ -19,10 +19,26 @@ describe('Vulnerability Severity component', () => { ...@@ -19,10 +19,26 @@ describe('Vulnerability Severity component', () => {
B: [projects[4]], B: [projects[4]],
A: [projects[5], projects[6]], 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, { return shallowMount(VulnerabilitySeverity, {
$apollo,
propsData: { propsData: {
query: {}, query: {},
helpPagePath, helpPagePath,
...@@ -47,11 +63,37 @@ describe('Vulnerability Severity component', () => { ...@@ -47,11 +63,37 @@ describe('Vulnerability Severity component', () => {
wrapper = null; wrapper = null;
}); });
beforeEach(() => { describe('when loading the project severity component for group level dashboard', () => {
wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) }); beforeEach(() => {
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', () => { describe('for all cases', () => {
beforeEach(() => {
wrapper = createComponent({});
});
it('has the link to the help page', () => { it('has the link to the help page', () => {
expect(findHelpLink().attributes('href')).toBe(helpPagePath); expect(findHelpLink().attributes('href')).toBe(helpPagePath);
}); });
...@@ -81,6 +123,7 @@ describe('Vulnerability Severity component', () => { ...@@ -81,6 +123,7 @@ describe('Vulnerability Severity component', () => {
let text; let text;
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) });
accordion = findAccordionItemByGrade(grade); accordion = findAccordionItemByGrade(grade);
text = trimText(accordion.text()); text = trimText(accordion.text());
}); });
......
...@@ -3,7 +3,7 @@ import { TEST_HOST } from 'jest/helpers/test_constants'; ...@@ -3,7 +3,7 @@ import { TEST_HOST } from 'jest/helpers/test_constants';
import GroupSecurityCharts from 'ee/security_dashboard/components/group_security_charts.vue'; import GroupSecurityCharts from 'ee/security_dashboard/components/group_security_charts.vue';
import SecurityChartsLayout from 'ee/security_dashboard/components/security_charts_layout.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 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', () => ({})); jest.mock('ee/security_dashboard/graphql/group_vulnerability_history.query.graphql', () => ({}));
...@@ -11,15 +11,14 @@ describe('Group Security Charts component', () => { ...@@ -11,15 +11,14 @@ describe('Group Security Charts component', () => {
let wrapper; let wrapper;
const groupFullPath = `${TEST_HOST}/group/5`; const groupFullPath = `${TEST_HOST}/group/5`;
const vulnerableProjectsEndpoint = `${TEST_HOST}/group/5/projects`;
const findSecurityChartsLayoutComponent = () => wrapper.find(SecurityChartsLayout); const findSecurityChartsLayoutComponent = () => wrapper.find(SecurityChartsLayout);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart); const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity); const findVulnerabilitySeverities = () => wrapper.find(VulnerabilitySeverities);
const createWrapper = () => { const createWrapper = () => {
wrapper = shallowMount(GroupSecurityCharts, { wrapper = shallowMount(GroupSecurityCharts, {
propsData: { groupFullPath, vulnerableProjectsEndpoint }, propsData: { groupFullPath },
}); });
}; };
...@@ -35,13 +34,11 @@ describe('Group Security Charts component', () => { ...@@ -35,13 +34,11 @@ describe('Group Security Charts component', () => {
it('renders the default page', () => { it('renders the default page', () => {
const securityChartsLayout = findSecurityChartsLayoutComponent(); const securityChartsLayout = findSecurityChartsLayoutComponent();
const vulnerabilityChart = findVulnerabilityChart(); const vulnerabilityChart = findVulnerabilityChart();
const vulnerabilitySeverity = findVulnerabilitySeverity(); const vulnerabilitySeverities = findVulnerabilitySeverities();
expect(securityChartsLayout.exists()).toBe(true); expect(securityChartsLayout.exists()).toBe(true);
expect(vulnerabilityChart.props()).toEqual({ query: {}, groupFullPath }); expect(vulnerabilityChart.props()).toEqual({ query: {}, groupFullPath });
expect(vulnerabilitySeverity.props()).toEqual({ expect(vulnerabilitySeverities.exists()).toBe(true);
endpoint: vulnerableProjectsEndpoint, expect(vulnerabilitySeverities.props().groupFullPath).toEqual(groupFullPath);
helpPagePath: '',
});
}); });
}); });
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