Commit abd31dba authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Douglas Barbosa Alexandre

Create basic page for on-demand scans

- Created a basic landing page for the upcoming on-demand scans feature
- Created a minimal Vue for displaying an empty state in the new page
- Added specs
parent 6840b554
<script>
import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
export default {
name: 'OnDemandScansApp',
components: {
GlEmptyState,
GlLink,
GlSprintf,
},
props: {
helpPagePath: {
type: String,
required: true,
},
emptyStateSvgPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div>
<gl-empty-state
:svg-path="emptyStateSvgPath"
:title="s__('OnDemandScans|On-demand Scans')"
:primary-button-text="s__('OnDemandScans|Create new DAST scan')"
primary-button-link="#"
>
<template #description>
<gl-sprintf
:message="
s__(
'OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}',
)
"
>
<template #helpLink="{ content }">
<gl-link :href="helpPagePath">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</template>
</gl-empty-state>
</div>
</template>
import Vue from 'vue';
import OnDemandScansApp from './components/on_demand_scans_app.vue';
export default () => {
const el = document.querySelector('#js-on-demand-scans-app');
if (!el) {
return;
}
const { helpPagePath, emptyStateSvgPath } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
render(h) {
return h(OnDemandScansApp, {
props: {
helpPagePath,
emptyStateSvgPath,
},
});
},
});
};
import initOnDemanScans from 'ee/on_demand_scans';
document.addEventListener('DOMContentLoaded', initOnDemanScans);
# frozen_string_literal: true
module Projects
class OnDemandScansController < Projects::ApplicationController
before_action :authorize_read_on_demand_scans!
def index
end
private
def authorize_read_on_demand_scans!
access_denied! unless can?(current_user, :read_on_demand_scans, project)
end
end
end
......@@ -150,6 +150,7 @@ module EE
%w[
projects/security/configuration#show
projects/security/dashboard#index
projects/on_demand_scans#index
projects/dependencies#index
projects/licenses#index
projects/threat_monitoring#show
......@@ -280,6 +281,10 @@ module EE
nav_tabs << :security_configuration
end
if can?(current_user, :read_on_demand_scans, @project)
nav_tabs << :on_demand_scans
end
if can?(current_user, :read_dependencies, project)
nav_tabs << :dependencies
end
......
# frozen_string_literal: true
module Projects::OnDemandScansHelper
def on_demand_scans_data
{
'help-page-path' => help_page_path('user/application_security/dast/index', anchor: 'on-demand-scans'),
'empty-state-svg-path' => image_path('illustrations/empty-state/ondemand-scan-empty.svg')
}
end
end
......@@ -128,6 +128,7 @@ class License < ApplicationRecord
sast
secret_detection
security_dashboard
security_on_demand_scans
status_page
subepics
threat_monitoring
......
......@@ -136,6 +136,12 @@ module EE
@subject.feature_available?(:security_dashboard)
end
with_scope :subject
condition(:on_demand_scans_enabled) do
::Feature.enabled?(:security_on_demand_scans_feature_flag, project) &&
@subject.feature_available?(:security_on_demand_scans)
end
with_scope :subject
condition(:license_scanning_enabled) do
@subject.feature_available?(:license_scanning)
......@@ -236,6 +242,8 @@ module EE
rule { security_dashboard_enabled & can?(:developer_access) }.enable :read_vulnerability
rule { on_demand_scans_enabled & can?(:developer_access) }.enable :read_on_demand_scans
rule { can?(:read_merge_request) & can?(:read_pipeline) }.enable :read_merge_train
rule { can?(:read_vulnerability) }.policy do
......
......@@ -21,6 +21,11 @@
= link_to project_security_dashboard_index_path(@project), title: _('Security Dashboard') do
%span= _('Security Dashboard')
- if project_nav_tab?(:on_demand_scans)
= nav_link(path: 'projects/on_demand_scans#index') do
= link_to project_on_demand_scans_path(@project), title: s_('OnDemandScans|On-demand Scans'), data: { qa_selector: 'on_demand_scans_link' } do
%span= s_('OnDemandScans|On-demand Scans')
- if project_nav_tab?(:dependencies)
= nav_link(path: 'projects/dependencies#index') do
= link_to project_dependencies_path(@project), title: _('Dependency List'), data: { qa_selector: 'dependency_list_link' } do
......
- breadcrumb_title s_('OnDemandScans|On-demand Scans')
- page_title s_('OnDemandScans|On-demand Scans')
#js-on-demand-scans-app{ data: on_demand_scans_data }
......@@ -93,6 +93,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :vulnerability_feedback, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
resources :dependencies, only: [:index]
resources :licenses, only: [:index, :create, :update]
resources :on_demand_scans, only: [:index], controller: :on_demand_scans
end
# End of the /-/ scope.
......
......@@ -39,7 +39,7 @@ RSpec.describe 'Project navbar' do
context 'when security dashboard is available' do
before do
stub_licensed_features(security_dashboard: true)
stub_licensed_features(security_dashboard: true, security_on_demand_scans: true)
insert_after_nav_item(
_('CI / CD'),
......@@ -47,6 +47,7 @@ RSpec.describe 'Project navbar' do
nav_item: _('Security & Compliance'),
nav_sub_items: [
_('Security Dashboard'),
s_('OnDemandScans|On-demand Scans'),
_('Configuration')
]
}
......
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'helpers/test_constants';
import { GlEmptyState } from '@gitlab/ui';
import OnDemandScansApp from 'ee/on_demand_scans/components/on_demand_scans_app.vue';
const helpPagePath = `${TEST_HOST}/application_security/dast/index#on-demand-scans`;
const emptyStateSvgPath = `${TEST_HOST}/assets/illustrations/alert-management-empty-state.svg`;
describe('OnDemandScansApp', () => {
let wrapper;
const findEmptyState = () => wrapper.find(GlEmptyState);
const createComponent = (props = {}) => {
wrapper = shallowMount(OnDemandScansApp, {
propsData: {
helpPagePath,
emptyStateSvgPath,
...props,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('empty state', () => {
it('renders empty state', () => {
expect(wrapper.contains(GlEmptyState)).toBe(true);
});
it('passes correct props to GlEmptyState', () => {
expect(findEmptyState().props()).toMatchObject({
svgPath: emptyStateSvgPath,
title: 'On-demand Scans',
primaryButtonText: 'Create new DAST scan',
primaryButtonLink: '#',
});
});
});
});
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::OnDemandScansHelper do
describe '#on_demand_scans_data' do
it 'returns proper data' do
expect(helper.on_demand_scans_data).to match(
'help-page-path' => help_page_path('user/application_security/dast/index', anchor: 'on-demand-scans'),
'empty-state-svg-path' => match_asset_path('/assets/illustrations/empty-state/ondemand-scan-empty.svg')
)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::OnDemandScansController, type: :request do
let(:project) { create(:project) }
let(:user) { create(:user) }
describe 'GET #index' do
context 'feature available' do
before do
stub_feature_flags(security_on_demand_scans_feature_flag: true)
stub_licensed_features(security_on_demand_scans: true)
end
context 'user authorized' do
before do
project.add_developer(user)
login_as(user)
end
it "can access page" do
get project_on_demand_scans_path(project)
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'user not authorized' do
before do
project.add_guest(user)
login_as(user)
end
it "sees a 404 error" do
get project_on_demand_scans_path(project)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'feature not available' do
before do
project.add_developer(user)
login_as(user)
end
it "sees a 404 error if the feature flag is disabled" do
stub_feature_flags(security_on_demand_scans_feature_flag: false)
stub_licensed_features(security_on_demand_scans: true)
get project_on_demand_scans_path(project)
expect(response).to have_gitlab_http_status(:not_found)
end
it "sees a 404 error if the license doesn't support the feature" do
stub_feature_flags(security_on_demand_scans_feature_flag: true)
stub_licensed_features(security_on_demand_scans: false)
get project_on_demand_scans_path(project)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe "projects/on_demand_scans/index", type: :view do
before do
render
end
it 'renders Vue app root' do
expect(rendered).to have_selector('#js-on-demand-scans-app')
end
it 'passes on-demand scans docs page URL' do
expect(rendered).to include '/help/user/application_security/dast/index#on-demand-scans'
end
end
......@@ -15121,6 +15121,15 @@ msgstr ""
msgid "On track"
msgstr ""
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
msgid "OnDemandScans|On-demand Scans"
msgstr ""
msgid "OnDemandScans|Schedule or run scans immediately against target sites. Currently available on-demand scan type: DAST. %{helpLinkStart}More information%{helpLinkEnd}"
msgstr ""
msgid "Onboarding"
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