Commit 3958c78d authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Merge branch '230412-separate-instance-security-charts-and-vulns' into 'master'

Create vulnerabilities route/page for instance

See merge request gitlab-org/gitlab!41156
parents 56bcfe59 77d7825a
...@@ -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)
The dashboard is empty before you add projects to it.
![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
subject
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