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 {
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;
});
beforeEach(() => {
wrapper = createComponent({ data: () => ({ vulnerabilityGrades }) });
describe('when loading the project severity component for group level dashboard', () => {
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', () => {
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