Commit 8a83d964 authored by Alexander Turinske's avatar Alexander Turinske Committed by Ash McKenzie

Add new security charts page

- create new group/security/vulnerabilities#index route
- create new controller to serve up vulnerabilities page
- create create page for when this feature is unavailable
- create new nav link
- remove now redundant charts on vulnerabilities page
- update tests
parent 3da460b8
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( initSecurityCharts(document.getElementById('js-group-security-dashboard'), DASHBOARD_TYPES.GROUP);
document.getElementById('js-group-security-dashboard'),
DASHBOARD_TYPES.GROUP,
);
}); });
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-group-vulnerabilities'),
DASHBOARD_TYPES.GROUP,
);
});
...@@ -3,18 +3,13 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,18 +3,13 @@ import { GlLoadingIcon } from '@gitlab/ui';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import GroupSecurityVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.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 VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import CsvExportButton from './csv_export_button.vue'; import CsvExportButton from './csv_export_button.vue';
import VulnerabilitySeverity from './vulnerability_severity.vue';
import vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.query.graphql';
import DashboardNotConfigured from './empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfigured from './empty_states/group_dashboard_not_configured.vue';
export default { export default {
components: { components: {
SecurityDashboardLayout, SecurityDashboardLayout,
GroupSecurityVulnerabilities, GroupSecurityVulnerabilities,
VulnerabilitySeverity,
VulnerabilityChart,
Filters, Filters,
CsvExportButton, CsvExportButton,
DashboardNotConfigured, DashboardNotConfigured,
...@@ -25,10 +20,6 @@ export default { ...@@ -25,10 +20,6 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
vulnerableProjectsEndpoint: {
type: String,
required: true,
},
vulnerabilitiesExportEndpoint: { vulnerabilitiesExportEndpoint: {
type: String, type: String,
required: true, required: true,
...@@ -39,7 +30,6 @@ export default { ...@@ -39,7 +30,6 @@ export default {
filters: {}, filters: {},
projects: [], projects: [],
projectsWereFetched: false, projectsWereFetched: false,
vulnerabilityHistoryQuery,
}; };
}, },
computed: { computed: {
...@@ -80,14 +70,6 @@ export default { ...@@ -80,14 +70,6 @@ export default {
:filters="filters" :filters="filters"
@projectFetch="handleProjectsFetch" @projectFetch="handleProjectsFetch"
/> />
<template #aside>
<vulnerability-chart
:query="vulnerabilityHistoryQuery"
:group-full-path="groupFullPath"
class="mb-4"
/>
<vulnerability-severity :endpoint="vulnerableProjectsEndpoint" />
</template>
</security-dashboard-layout> </security-dashboard-layout>
</div> </div>
</template> </template>
# frozen_string_literal: true
module Groups
module Security
class VulnerabilitiesController < Groups::ApplicationController
layout 'group'
def index
render :unavailable unless dashboard_available?
end
private
def dashboard_available?
group.feature_available?(:security_dashboard) &&
can?(current_user, :read_group_security_dashboard, group)
end
end
end
end
- breadcrumb_title _("Vulnerability Report")
- page_title _("Vulnerability Report")
#js-group-vulnerabilities{ data: group_level_security_dashboard_data(@group) }
- breadcrumb_title _("Vulnerability Report")
- page_title _("Vulnerability Report")
#js-group-vulnerabilities{ data: { is_unavailable: "true",
empty_state_svg_path: image_path('illustrations/security-dashboard-empty-state.svg'),
dashboard_documentation: help_page_path('user/application_security/security_dashboard/index') } }
- main_path = primary_group_level_security_feature_path(@group) - main_path = primary_group_level_security_feature_path(@group)
- if main_path.present? - if main_path.present?
= nav_link(path: %w[dashboard#show compliance_dashboards#show credentials#index]) do = nav_link(path: %w[dashboard#show vulnerabilities#index compliance_dashboards#show credentials#index]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' } do = link_to main_path, data: { qa_selector: 'security_compliance_link' } do
.nav-icon-container .nav-icon-container
= sprite_icon('shield') = sprite_icon('shield')
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
= link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do = link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security') %span= _('Security')
- if group_level_security_dashboard_available?(@group)
= nav_link(path: 'vulnerabilities#index') do
= link_to group_security_vulnerabilities_path(@group), title: _('Vulnerability Report') do
%span= _('Vulnerability Report')
- if group_level_compliance_dashboard_available?(@group) - if group_level_compliance_dashboard_available?(@group)
= nav_link(path: 'compliance_dashboards#show') do = nav_link(path: 'compliance_dashboards#show') do
= link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do = link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do
......
---
title: Add new security charts page
merge_request: 38088
author:
type: changed
...@@ -147,6 +147,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do ...@@ -147,6 +147,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
namespace :security do namespace :security do
resource :dashboard, only: [:show], controller: :dashboard resource :dashboard, only: [:show], controller: :dashboard
resources :vulnerabilities, only: [:index], controller: :vulnerabilities
resource :compliance_dashboard, only: [:show] resource :compliance_dashboard, only: [:show]
resources :vulnerable_projects, only: [:index] resources :vulnerable_projects, only: [:index]
resource :discover, only: [:show], controller: :discover resource :discover, only: [:show], controller: :discover
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::Security::VulnerabilitiesController do
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
sign_in(user)
end
describe 'GET index' do
subject { get :index, params: { group_id: group.to_param } }
context 'when security dashboard feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
context 'and user is allowed to access group security vulnerabilities' do
before do
group.add_developer(user)
end
it { is_expected.to have_gitlab_http_status(:ok) }
end
context 'when user is not allowed to access group security vulnerabilities' do
it { is_expected.to have_gitlab_http_status(:ok) }
it { is_expected.to render_template(:unavailable) }
end
end
context 'when security dashboard feature is disabled' do
it { is_expected.to have_gitlab_http_status(:ok) }
it { is_expected.to render_template(:unavailable) }
end
end
end
...@@ -159,6 +159,7 @@ RSpec.describe 'Group navbar' do ...@@ -159,6 +159,7 @@ RSpec.describe 'Group navbar' do
nav_item: _('Security & Compliance'), nav_item: _('Security & Compliance'),
nav_sub_items: [ nav_sub_items: [
_('Security'), _('Security'),
_('Vulnerability Report'),
_('Compliance') _('Compliance')
] ]
} }
......
...@@ -3,8 +3,6 @@ import { GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,8 +3,6 @@ import { GlLoadingIcon } from '@gitlab/ui';
import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue'; import SecurityDashboardLayout from 'ee/security_dashboard/components/security_dashboard_layout.vue';
import FirstClassGroupDashboard from 'ee/security_dashboard/components/first_class_group_security_dashboard.vue'; import FirstClassGroupDashboard from 'ee/security_dashboard/components/first_class_group_security_dashboard.vue';
import FirstClassGroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue'; import FirstClassGroupVulnerabilities from 'ee/security_dashboard/components/first_class_group_security_dashboard_vulnerabilities.vue';
import VulnerabilitySeverity from 'ee/security_dashboard/components/vulnerability_severity.vue';
import VulnerabilityChart from 'ee/security_dashboard/components/first_class_vulnerability_chart.vue';
import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.vue'; import DashboardNotConfigured from 'ee/security_dashboard/components/empty_states/group_dashboard_not_configured.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';
...@@ -20,8 +18,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -20,8 +18,6 @@ describe('First Class Group Dashboard Component', () => {
const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout); const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout);
const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities); const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findCsvExportButton = () => wrapper.find(CsvExportButton); const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findFilters = () => wrapper.find(Filters); const findFilters = () => wrapper.find(Filters);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
...@@ -83,10 +79,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -83,10 +79,6 @@ describe('First Class Group Dashboard Component', () => {
expect(findFilters().exists()).toBe(true); expect(findFilters().exists()).toBe(true);
}); });
it('has the vulnerability history chart', () => {
expect(findVulnerabilityChart().props('groupFullPath')).toBe(groupFullPath);
});
it('responds to the projectFetch event', () => { it('responds to the projectFetch event', () => {
const projects = [{ id: 1, name: 'GitLab Org' }]; const projects = [{ id: 1, name: 'GitLab Org' }];
findGroupVulnerabilities().vm.$listeners.projectFetch(projects); findGroupVulnerabilities().vm.$listeners.projectFetch(projects);
...@@ -104,10 +96,6 @@ describe('First Class Group Dashboard Component', () => { ...@@ -104,10 +96,6 @@ describe('First Class Group 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,
......
...@@ -25,6 +25,10 @@ RSpec.describe 'Group routing', "routing" do ...@@ -25,6 +25,10 @@ RSpec.describe 'Group routing', "routing" do
it 'shows group dashboard' do it 'shows group dashboard' do
expect(get('/groups/gitlabhq/-/security/dashboard')).to route_to('groups/security/dashboard#show', group_id: 'gitlabhq') expect(get('/groups/gitlabhq/-/security/dashboard')).to route_to('groups/security/dashboard#show', group_id: 'gitlabhq')
end end
it 'shows vulnerability list' do
expect(get('/groups/gitlabhq/-/security/vulnerabilities')).to route_to('groups/security/vulnerabilities#index', group_id: 'gitlabhq')
end
end end
describe 'dependency proxy for containers' do describe 'dependency proxy for containers' do
......
...@@ -27025,6 +27025,9 @@ msgstr "" ...@@ -27025,6 +27025,9 @@ msgstr ""
msgid "Vulnerabilities over time" msgid "Vulnerabilities over time"
msgstr "" msgstr ""
msgid "Vulnerability Report"
msgstr ""
msgid "Vulnerability remediated. Review before resolving." msgid "Vulnerability remediated. Review before resolving."
msgstr "" msgstr ""
......
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