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
[group Security Dashboard's](#group-security-dashboard)
features.
![Instance Security Dashboard with projects](img/instance_security_dashboard_v13_4.png)
You can access the Instance Security Dashboard from the menu
bar at the top of the page. Under **More**, select **Security**.
![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
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. Click the **Add projects** button.
After you add projects, the Security Dashboard displays the vulnerabilities found in those projects'
default branches.
![Uninitialized Instance Security Dashboard](img/instance_security_dashboard_empty_v13_3.png)
## Export vulnerabilities
> [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
vulnerabilities for the projects defined in the **Security Dashboard**,
as filters don't apply to the export function.
![Export vulnerabilities](img/instance_security_dashboard_export_csv_v13_4.png)
NOTE: **Note:**
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.
......@@ -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
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
......
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';
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>
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 projectsQuery from 'ee/security_dashboard/graphql/get_instance_security_dashboard_projects.query.graphql';
import createFlash from '~/flash';
import { createProjectLoadingError } from '../helpers';
import InstanceSecurityVulnerabilities from './first_class_instance_security_dashboard_vulnerabilities.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 {
......@@ -17,8 +13,6 @@ export default {
CsvExportButton,
SecurityDashboardLayout,
InstanceSecurityVulnerabilities,
VulnerabilitySeverities,
VulnerabilityChart,
Filters,
DashboardNotConfigured,
},
......@@ -42,8 +36,6 @@ export default {
data() {
return {
filters: {},
vulnerabilityHistoryQuery,
vulnerabilityGradesQuery,
projects: [],
};
},
......@@ -72,8 +64,8 @@ export default {
<template>
<security-dashboard-layout>
<template #header>
<header class="page-title-holder flex-fill d-flex align-items-center">
<h2 class="page-title flex-grow">{{ s__('SecurityReports|Security Dashboard') }}</h2>
<header class="page-title-holder gl-flex-fill-1 gl-display-flex gl-align-items-center">
<h2 class="page-title gl-flex-grow-1">{{ s__('SecurityReports|Vulnerability Report') }}</h2>
<csv-export-button
v-if="shouldShowDashboard"
:vulnerabilities-export-endpoint="vulnerabilitiesExportEndpoint"
......@@ -89,11 +81,5 @@ export default {
:filters="filters"
/>
<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>
</template>
# frozen_string_literal: true
module Security
class VulnerabilitiesController < ::Security::ApplicationController
layout 'instance_security'
end
end
......@@ -13,6 +13,12 @@
= sprite_icon('dashboard')
%span.nav-item-name
= _('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
= link_to security_settings_dashboard_path, class: 'shortcuts-project rspec-project-link' do
.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
get 'dasboard/settings', to: 'dashboard#settings', as: :settings_dashboard
resources :projects, only: [:index, :create, :destroy]
resources :vulnerabilities, only: [:index]
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';
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 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 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';
......@@ -18,8 +16,6 @@ describe('First Class Instance Dashboard Component', () => {
const vulnerabilitiesExportEndpoint = '/vulnerabilities/exports';
const findInstanceVulnerabilities = () => wrapper.find(FirstClassInstanceVulnerabilities);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findEmptyState = () => wrapper.find(DashboardNotConfigured);
const findFilters = () => wrapper.find(Filters);
......@@ -64,10 +60,6 @@ describe('First Class Instance Dashboard Component', () => {
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', () => {
const filters = { severity: 'critical' };
findFilters().vm.$listeners.filterChange(filters);
......@@ -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', () => {
expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe(
vulnerabilitiesExportEndpoint,
......@@ -127,10 +115,6 @@ describe('First Class Instance Dashboard Component', () => {
it('has no filters', () => {
expect(findFilters().exists()).toBe(false);
});
it('does not display the vulnerability severity in an aside', () => {
expect(findVulnerabilitySeverity().exists()).toBe(false);
});
});
describe('always', () => {
......@@ -139,7 +123,7 @@ describe('First Class Instance Dashboard Component', () => {
});
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