Commit 5ceede3c authored by Alexander Turinske's avatar Alexander Turinske

Create vulnerabilities route/page for instance

- similar to how groups/dashboard and groups/vulnerabilities
  is separated, now there is a security/dashboard and
  security/vulnerabilities
- separate instance security charts and vulns
- move the instance-level vulnerabilities into a separate page
- show the charts by default
- remove charts from the vulnerability page
- update CSS to use gl- styles
- add tests
- update documentation
parent fdf7f6a1
...@@ -122,24 +122,28 @@ branches of all the projects you configure to display on the dashboard. It inclu ...@@ -122,24 +122,28 @@ branches of all the projects you configure to display on the dashboard. It inclu
[group Security Dashboard's](#group-security-dashboard) [group Security Dashboard's](#group-security-dashboard)
features. features.
![Instance Security Dashboard with projects](img/instance_security_dashboard_v13_4.png)
You can access the Instance Security Dashboard from the menu You can access the Instance Security Dashboard from the menu
bar at the top of the page. Under **More**, select **Security**. bar at the top of the page. Under **More**, select **Security**.
![Instance Security Dashboard navigation link](img/instance_security_dashboard_link_v12_4.png) ![Instance Security Dashboard navigation link](img/instance_security_dashboard_link_v12_4.png)
Before adding projects to the dashboard, you will see the empty state
![Uninitialized Instance Security Dashboard](img/instance_security_dashboard_empty_v13_4.png)
### Adding projects to the dashboard ### Adding projects to the dashboard
To add projects to the dashboard: To add projects to the dashboard:
1. Click **Settings** in the left navigation bar. 1. Click **Settings** in the left navigation bar or click the **Add projects** button.
1. Search for and add one or more projects using the **Search your projects** field. 1. Search for and add one or more projects using the **Search your projects** field.
1. Click the **Add projects** button. 1. Click the **Add projects** button.
After you add projects, the Security Dashboard displays the vulnerabilities found in those projects' After you add projects, the Security Dashboard displays the vulnerabilities found in those projects'
default branches. default branches.
![Uninitialized Instance Security Dashboard](img/instance_security_dashboard_empty_v13_3.png)
## Export vulnerabilities ## Export vulnerabilities
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213014) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213014) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
...@@ -150,6 +154,8 @@ is built, the CSV report downloads to your local machine. The report contains al ...@@ -150,6 +154,8 @@ is built, the CSV report downloads to your local machine. The report contains al
vulnerabilities for the projects defined in the **Security Dashboard**, vulnerabilities for the projects defined in the **Security Dashboard**,
as filters don't apply to the export function. as filters don't apply to the export function.
![Export vulnerabilities](img/instance_security_dashboard_export_csv_v13_4.png)
NOTE: **Note:** NOTE: **Note:**
It may take several minutes for the download to start if your project contains It may take several minutes for the download to start if your project contains
thousands of vulnerabilities. Do not close the page until the download finishes. thousands of vulnerabilities. Do not close the page until the download finishes.
...@@ -208,7 +214,7 @@ To create an issue associated with the vulnerability, click the **Create Issue** ...@@ -208,7 +214,7 @@ To create an issue associated with the vulnerability, click the **Create Issue**
Once you create the issue, the vulnerability list contains a link to the issue and an icon whose Once you create the issue, the vulnerability list contains a link to the issue and an icon whose
color indicates the issue's status (green for open issues, blue for closed issues). color indicates the issue's status (green for open issues, blue for closed issues).
![Display attached issues](img/vulnerability_list_table_v13_1.png) ![Display attached issues](img/vulnerability_list_table_v13_4.png)
<!-- ## Troubleshooting <!-- ## Troubleshooting
......
import initFirstClassSecurityDashboard from 'ee/security_dashboard/first_class_init'; import initSecurityCharts from 'ee/security_dashboard/security_charts_init';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants'; import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initFirstClassSecurityDashboard(document.getElementById('js-security'), DASHBOARD_TYPES.INSTANCE); initSecurityCharts(document.getElementById('js-security'), DASHBOARD_TYPES.INSTANCE);
}); });
import initFirstClassSecurityDashboard from 'ee/security_dashboard/first_class_init';
import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
document.addEventListener('DOMContentLoaded', () => {
initFirstClassSecurityDashboard(
document.getElementById('js-vulnerabilities'),
DASHBOARD_TYPES.INSTANCE,
);
});
<script> <script>
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import VulnerabilitySeverities from 'ee/security_dashboard/components/first_class_vulnerability_severities.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import projectsQuery from 'ee/security_dashboard/graphql/get_instance_security_dashboard_projects.query.graphql'; import projectsQuery from 'ee/security_dashboard/graphql/get_instance_security_dashboard_projects.query.graphql';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { createProjectLoadingError } from '../helpers'; import { createProjectLoadingError } from '../helpers';
import InstanceSecurityVulnerabilities from './first_class_instance_security_dashboard_vulnerabilities.vue'; import InstanceSecurityVulnerabilities from './first_class_instance_security_dashboard_vulnerabilities.vue';
import CsvExportButton from './csv_export_button.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'; import DashboardNotConfigured from './empty_states/instance_dashboard_not_configured.vue';
export default { export default {
...@@ -17,8 +13,6 @@ export default { ...@@ -17,8 +13,6 @@ export default {
CsvExportButton, CsvExportButton,
SecurityDashboardLayout, SecurityDashboardLayout,
InstanceSecurityVulnerabilities, InstanceSecurityVulnerabilities,
VulnerabilitySeverities,
VulnerabilityChart,
Filters, Filters,
DashboardNotConfigured, DashboardNotConfigured,
}, },
...@@ -42,8 +36,6 @@ export default { ...@@ -42,8 +36,6 @@ export default {
data() { data() {
return { return {
filters: {}, filters: {},
vulnerabilityHistoryQuery,
vulnerabilityGradesQuery,
projects: [], projects: [],
}; };
}, },
...@@ -72,8 +64,8 @@ export default { ...@@ -72,8 +64,8 @@ export default {
<template> <template>
<security-dashboard-layout> <security-dashboard-layout>
<template #header> <template #header>
<header class="page-title-holder flex-fill d-flex align-items-center"> <header class="page-title-holder gl-flex-fill-1 gl-display-flex gl-align-items-center">
<h2 class="page-title flex-grow">{{ s__('SecurityReports|Security Dashboard') }}</h2> <h2 class="page-title gl-flex-grow-1">{{ s__('SecurityReports|Vulnerability Report') }}</h2>
<csv-export-button <csv-export-button
v-if="shouldShowDashboard" v-if="shouldShowDashboard"
:vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint" :vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint"
...@@ -89,11 +81,5 @@ export default { ...@@ -89,11 +81,5 @@ export default {
:filters="filters" :filters="filters"
/> />
<dashboard-not-configured v-else-if="shouldShowEmptyState" /> <dashboard-not-configured v-else-if="shouldShowEmptyState" />
<template #aside>
<template v-if="shouldShowDashboard">
<vulnerability-chart :query="vulnerabilityHistoryQuery" class="mb-4" />
<vulnerability-severities :query="vulnerabilityGradesQuery" />
</template>
</template>
</security-dashboard-layout> </security-dashboard-layout>
</template> </template>
# frozen_string_literal: true
module Security
class VulnerabilitiesController < ::Security::ApplicationController
layout 'instance_security'
end
end
...@@ -13,6 +13,12 @@ ...@@ -13,6 +13,12 @@
= sprite_icon('dashboard') = sprite_icon('dashboard')
%span.nav-item-name %span.nav-item-name
= _('Security Dashboard') = _('Security Dashboard')
= nav_link(path: %w[vulnerabilities#index]) do
= link_to security_vulnerabilities_path, class: 'shortcuts-project rspec-project-link' do
.nav-icon-container
= sprite_icon('list-bulleted')
%span.nav-item-name
= _('Vulnerability Report')
= nav_link(path: %w[dashboard#settings]) do = nav_link(path: %w[dashboard#settings]) do
= link_to security_settings_dashboard_path, class: 'shortcuts-project rspec-project-link' do = link_to security_settings_dashboard_path, class: 'shortcuts-project rspec-project-link' do
.nav-icon-container .nav-icon-container
......
- page_title _('Vulnerability Report')
#js-vulnerabilities{ data: instance_security_dashboard_data }
---
title: Create vulnerabilities route/page for instance-level security dashboard
merge_request: 41156
author:
type: added
...@@ -5,4 +5,5 @@ namespace :security do ...@@ -5,4 +5,5 @@ namespace :security do
get 'dasboard/settings', to: 'dashboard#settings', as: :settings_dashboard get 'dasboard/settings', to: 'dashboard#settings', as: :settings_dashboard
resources :projects, only: [:index, :create, :destroy] resources :projects, only: [:index, :create, :destroy]
resources :vulnerabilities, only: [:index]
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Security::VulnerabilitiesController do
let_it_be(:user) { create(:user) }
describe 'GET #index' do
subject { get :index }
it_behaves_like Security::ApplicationController do
let(:security_application_controller_child_action) do
get :index
end
end
context 'when security dashboard feature' do
before do
sign_in(user)
end
context 'is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
it { is_expected.to render_template(:instance_security) }
end
context 'is disabled' do
it { is_expected.to have_gitlab_http_status(:not_found) }
it { is_expected.to render_template('errors/not_found') }
end
end
end
end
...@@ -2,8 +2,6 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,8 +2,6 @@ import { shallowMount } from '@vue/test-utils';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import FirstClassInstanceDashboard from 'ee/security_dashboard/components/first_class_instance_security_dashboard.vue'; import FirstClassInstanceDashboard from 'ee/security_dashboard/components/first_class_instance_security_dashboard.vue';
import FirstClassInstanceVulnerabilities from 'ee/security_dashboard/components/first_class_instance_security_dashboard_vulnerabilities.vue'; import FirstClassInstanceVulnerabilities from 'ee/security_dashboard/components/first_class_instance_security_dashboard_vulnerabilities.vue';
import VulnerabilitySeverity from 'ee/security_dashboard/components/first_class_vulnerability_severities.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue'; import CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue'; import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue'; import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/instance_dashboard_not_configured.vue';
...@@ -18,8 +16,6 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -18,8 +16,6 @@ describe('First Class Instance Dashboard Component', () => {
const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports'; const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports';
const findInstanceVulnerabilities = () => wrapper.find(FirstClassInstanceVulnerabilities); const findInstanceVulnerabilities = () => wrapper.find(FirstClassInstanceVulnerabilities);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findCsvExportButton = () => wrapper.find(CsvExportButton); const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findEmptyState = () => wrapper.find(DashboardNotConfigured); const findEmptyState = () => wrapper.find(DashboardNotConfigured);
const findFilters = () => wrapper.find(Filters); const findFilters = () => wrapper.find(Filters);
...@@ -64,10 +60,6 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -64,10 +60,6 @@ describe('First Class Instance Dashboard Component', () => {
expect(findFilters().exists()).toBe(true); expect(findFilters().exists()).toBe(true);
}); });
it('does not pass down a groupFullPath to the vulnerability chart', () => {
expect(findVulnerabilityChart().props('groupFullPath')).toBeUndefined();
});
it('responds to the filterChange event', () => { it('responds to the filterChange event', () => {
const filters = { severity: 'critical' }; const filters = { severity: 'critical' };
findFilters().vm.$listeners.filterChange(filters); findFilters().vm.$listeners.filterChange(filters);
...@@ -77,10 +69,6 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -77,10 +69,6 @@ describe('First Class Instance Dashboard Component', () => {
}); });
}); });
it('displays the vulnerability severity in an aside', () => {
expect(findVulnerabilitySeverity().exists()).toBe(true);
});
it('displays the csv export button', () => { it('displays the csv export button', () => {
expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe( expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe(
vulnerabilitiesExportEndpoint, vulnerabilitiesExportEndpoint,
...@@ -127,10 +115,6 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -127,10 +115,6 @@ describe('First Class Instance Dashboard Component', () => {
it('has no filters', () => { it('has no filters', () => {
expect(findFilters().exists()).toBe(false); expect(findFilters().exists()).toBe(false);
}); });
it('does not display the vulnerability severity in an aside', () => {
expect(findVulnerabilitySeverity().exists()).toBe(false);
});
}); });
describe('always', () => { describe('always', () => {
...@@ -139,7 +123,7 @@ describe('First Class Instance Dashboard Component', () => { ...@@ -139,7 +123,7 @@ describe('First Class Instance Dashboard Component', () => {
}); });
it('has the security dashboard title', () => { it('has the security dashboard title', () => {
expect(wrapper.find('.page-title').text()).toBe('Security Dashboard'); expect(wrapper.find('.page-title').text()).toBe('Vulnerability Report');
}); });
}); });
}); });
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