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';
document.addEventListener('DOMContentLoaded', () => {
initFirstClassSecurityDashboard(
document.getElementById('js-group-security-dashboard'),
DASHBOARD_TYPES.GROUP,
);
initSecurityCharts(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';
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 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 VulnerabilitySeverity from './vulnerability_severity.vue';
import vulnerabilityHistoryQuery from '../graphql/group_vulnerability_history.query.graphql';
import DashboardNotConfigured from './empty_states/group_dashboard_not_configured.vue';
export default {
components: {
SecurityDashboardLayout,
GroupSecurityVulnerabilities,
VulnerabilitySeverity,
VulnerabilityChart,
Filters,
CsvExportButton,
DashboardNotConfigured,
......@@ -25,10 +20,6 @@ export default {
type: String,
required: true,
},
vulnerableProjectsEndpoint: {
type: String,
required: true,
},
vulnerabilitiesExportEndpoint: {
type: String,
required: true,
......@@ -39,7 +30,6 @@ export default {
filters: {},
projects: [],
projectsWereFetched: false,
vulnerabilityHistoryQuery,
};
},
computed: {
......@@ -80,14 +70,6 @@ export default {
:filters="filters"
@projectFetch="handleProjectsFetch"
/>
<template #aside>
<vulnerability-chart
:query="vulnerabilityHistoryQuery"
:group-full-path="groupFullPath"
class="mb-4"
/>
<vulnerability-severity :endpoint="vulnerableProjectsEndpoint" />
</template>
</security-dashboard-layout>
</div>
</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)
- 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
.nav-icon-container
= sprite_icon('shield')
......@@ -12,6 +12,11 @@
= link_to group_security_dashboard_path(@group), title: _('Security'), data: { qa_selector: 'security_dashboard_link' } do
%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)
= nav_link(path: 'compliance_dashboards#show') 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
namespace :security do
resource :dashboard, only: [:show], controller: :dashboard
resources :vulnerabilities, only: [:index], controller: :vulnerabilities
resource :compliance_dashboard, only: [:show]
resources :vulnerable_projects, only: [:index]
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
nav_item: _('Security & Compliance'),
nav_sub_items: [
_('Security'),
_('Vulnerability Report'),
_('Compliance')
]
}
......
......@@ -3,8 +3,6 @@ import { GlLoadingIcon } from '@gitlab/ui';
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 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 CsvExportButton from 'ee/security_dashboard/components/csv_export_button.vue';
import Filters from 'ee/security_dashboard/components/first_class_vulnerability_filters.vue';
......@@ -20,8 +18,6 @@ describe('First Class Group Dashboard Component', () => {
const findDashboardLayout = () => wrapper.find(SecurityDashboardLayout);
const findGroupVulnerabilities = () => wrapper.find(FirstClassGroupVulnerabilities);
const findVulnerabilitySeverity = () => wrapper.find(VulnerabilitySeverity);
const findVulnerabilityChart = () => wrapper.find(VulnerabilityChart);
const findCsvExportButton = () => wrapper.find(CsvExportButton);
const findFilters = () => wrapper.find(Filters);
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
......@@ -83,10 +79,6 @@ describe('First Class Group Dashboard Component', () => {
expect(findFilters().exists()).toBe(true);
});
it('has the vulnerability history chart', () => {
expect(findVulnerabilityChart().props('groupFullPath')).toBe(groupFullPath);
});
it('responds to the projectFetch event', () => {
const projects = [{ id: 1, name: 'GitLab Org' }];
findGroupVulnerabilities().vm.$listeners.projectFetch(projects);
......@@ -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', () => {
expect(findCsvExportButton().props('vulnerabilitiesExportEndpoint')).toBe(
vulnerabilitiesExportEndpoint,
......
......@@ -25,6 +25,10 @@ RSpec.describe 'Group routing', "routing" do
it 'shows group dashboard' do
expect(get('/groups/gitlabhq/-/security/dashboard')).to route_to('groups/security/dashboard#show', group_id: 'gitlabhq')
end
it 'shows vulnerability list' do
expect(get('/groups/gitlabhq/-/security/vulnerabilities')).to route_to('groups/security/vulnerabilities#index', group_id: 'gitlabhq')
end
end
describe 'dependency proxy for containers' do
......
......@@ -27025,6 +27025,9 @@ msgstr ""
msgid "Vulnerabilities over time"
msgstr ""
msgid "Vulnerability Report"
msgstr ""
msgid "Vulnerability remediated. Review before resolving."
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