Commit fe58a584 authored by Miguel Rincon's avatar Miguel Rincon

Merge branch '341359-dast-view-scans-create-scan-link' into 'master'

Add on-demands scans index page header

See merge request gitlab-org/gitlab!72180
parents cd60a5bf bab7124d
<script> <script>
import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { HELP_PAGE_PATH } from '../constants';
export default { export default {
HELP_PAGE_PATH,
components: { components: {
GlEmptyState, GlEmptyState,
GlSprintf, GlSprintf,
GlLink, GlLink,
}, },
inject: ['newDastScanPath', 'helpPagePath', 'emptyStateSvgPath'], inject: ['newDastScanPath', 'emptyStateSvgPath'],
props: { props: {
title: { title: {
type: String, type: String,
...@@ -19,7 +21,7 @@ export default { ...@@ -19,7 +21,7 @@ export default {
type: String, type: String,
required: false, required: false,
default: s__( default: s__(
'OnDemandScans|On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Lean more%{learnMoreLinkEnd}.', 'OnDemandScans|On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}.',
), ),
}, },
noPrimaryButton: { noPrimaryButton: {
...@@ -54,7 +56,7 @@ export default { ...@@ -54,7 +56,7 @@ export default {
<template #description> <template #description>
<gl-sprintf :message="text"> <gl-sprintf :message="text">
<template #learnMoreLink="{ content }"> <template #learnMoreLink="{ content }">
<gl-link :href="helpPagePath">{{ content }}</gl-link> <gl-link :href="$options.HELP_PAGE_PATH">{{ content }}</gl-link>
</template> </template>
</gl-sprintf> </gl-sprintf>
</template> </template>
......
<script> <script>
import { GlTabs } from '@gitlab/ui'; import { GlButton, GlLink, GlSprintf, GlTabs } from '@gitlab/ui';
import { s__ } from '~/locale';
import ConfigurationPageLayout from 'ee/security_configuration/components/configuration_page_layout.vue';
import { HELP_PAGE_PATH } from '../constants';
import AllTab from './tabs/all.vue'; import AllTab from './tabs/all.vue';
import RunningTab from './tabs/running.vue'; import RunningTab from './tabs/running.vue';
import FinishedTab from './tabs/finished.vue'; import FinishedTab from './tabs/finished.vue';
...@@ -23,14 +26,20 @@ const TABS = { ...@@ -23,14 +26,20 @@ const TABS = {
export default { export default {
TABS, TABS,
HELP_PAGE_PATH,
components: { components: {
GlButton,
GlLink,
GlSprintf,
GlTabs, GlTabs,
ConfigurationPageLayout,
AllTab, AllTab,
RunningTab, RunningTab,
FinishedTab, FinishedTab,
ScheduledTab, ScheduledTab,
EmptyState, EmptyState,
}, },
inject: ['newDastScanPath'],
data() { data() {
return { return {
activeTabIndex: 0, activeTabIndex: 0,
...@@ -57,12 +66,43 @@ export default { ...@@ -57,12 +66,43 @@ export default {
this.activeTabIndex = tabIndex; this.activeTabIndex = tabIndex;
} }
}, },
i18n: {
title: s__('OnDemandScans|On-demand scans'),
newScanButtonLabel: s__('OnDemandScans|New DAST scan'),
description: s__(
'OnDemandScans|On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}.',
),
},
}; };
</script> </script>
<template> <template>
<gl-tabs v-if="hasData" v-model="activeTab"> <configuration-page-layout v-if="hasData">
<component :is="tab.component" v-for="(tab, key) in $options.TABS" :key="key" :item-count="0" /> <template #heading>
{{ $options.i18n.title }}
</template>
<template #actions>
<gl-button variant="confirm" :href="newDastScanPath" data-testid="new-scan-link">
{{ $options.i18n.newScanButtonLabel }}
</gl-button>
</template>
<template #description>
<gl-sprintf :message="$options.i18n.description">
<template #learnMoreLink="{ content }">
<gl-link :href="$options.HELP_PAGE_PATH" data-testid="help-page-link">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</template>
<gl-tabs v-model="activeTab">
<component
:is="tab.component"
v-for="(tab, key) in $options.TABS"
:key="key"
:item-count="0"
/>
</gl-tabs> </gl-tabs>
</configuration-page-layout>
<empty-state v-else /> <empty-state v-else />
</template> </template>
import { helpPagePath } from '~/helpers/help_page_helper';
export const HELP_PAGE_PATH = helpPagePath('user/application_security/dast/index', {
anchor: 'on-demand-scans',
});
...@@ -8,14 +8,13 @@ export default () => { ...@@ -8,14 +8,13 @@ export default () => {
return null; return null;
} }
const { newDastScanPath, helpPagePath, emptyStateSvgPath } = el.dataset; const { newDastScanPath, emptyStateSvgPath } = el.dataset;
return new Vue({ return new Vue({
el, el,
router: createRouter(), router: createRouter(),
provide: { provide: {
newDastScanPath, newDastScanPath,
helpPagePath,
emptyStateSvgPath, emptyStateSvgPath,
}, },
render(h) { render(h) {
......
...@@ -37,6 +37,7 @@ import { ...@@ -37,6 +37,7 @@ import {
SCANNER_PROFILES_QUERY, SCANNER_PROFILES_QUERY,
SITE_PROFILES_QUERY, SITE_PROFILES_QUERY,
} from '../settings'; } from '../settings';
import { HELP_PAGE_PATH } from '../../on_demand_scans/constants';
import ProfileConflictAlert from './profile_selector/profile_conflict_alert.vue'; import ProfileConflictAlert from './profile_selector/profile_conflict_alert.vue';
import ScannerProfileSelector from './profile_selector/scanner_profile_selector.vue'; import ScannerProfileSelector from './profile_selector/scanner_profile_selector.vue';
import SiteProfileSelector from './profile_selector/site_profile_selector.vue'; import SiteProfileSelector from './profile_selector/site_profile_selector.vue';
...@@ -68,6 +69,7 @@ export default { ...@@ -68,6 +69,7 @@ export default {
enabledRefTypes: [REF_TYPE_BRANCHES], enabledRefTypes: [REF_TYPE_BRANCHES],
saveAndRunScanBtnId: 'scan-submit-button', saveAndRunScanBtnId: 'scan-submit-button',
saveScanBtnId: 'scan-save-button', saveScanBtnId: 'scan-save-button',
helpPagePath: HELP_PAGE_PATH,
components: { components: {
RefSelector, RefSelector,
ProfileConflictAlert, ProfileConflictAlert,
...@@ -104,7 +106,7 @@ export default { ...@@ -104,7 +106,7 @@ export default {
SITE_PROFILES_QUERY, SITE_PROFILES_QUERY,
), ),
}, },
inject: ['projectPath', 'helpPagePath', 'profilesLibraryPath'], inject: ['projectPath', 'profilesLibraryPath'],
props: { props: {
defaultBranch: { defaultBranch: {
type: String, type: String,
...@@ -346,7 +348,7 @@ export default { ...@@ -346,7 +348,7 @@ export default {
" "
> >
<template #learnMoreLink="{ content }"> <template #learnMoreLink="{ content }">
<gl-link :href="helpPagePath"> <gl-link :href="$options.helpPagePath" data-testid="help-page-link">
{{ content }} {{ content }}
</gl-link> </gl-link>
</template> </template>
......
...@@ -16,7 +16,6 @@ export default () => { ...@@ -16,7 +16,6 @@ export default () => {
siteProfilesLibraryPath, siteProfilesLibraryPath,
newSiteProfilePath, newSiteProfilePath,
newScannerProfilePath, newScannerProfilePath,
helpPagePath,
} = el.dataset; } = el.dataset;
const dastScan = el.dataset.dastScan ? JSON.parse(el.dataset.dastScan) : null; const dastScan = el.dataset.dastScan ? JSON.parse(el.dataset.dastScan) : null;
const timezones = JSON.parse(el.dataset.timezones); const timezones = JSON.parse(el.dataset.timezones);
...@@ -26,7 +25,6 @@ export default () => { ...@@ -26,7 +25,6 @@ export default () => {
apolloProvider, apolloProvider,
provide: { provide: {
projectPath, projectPath,
helpPagePath,
profilesLibraryPath, profilesLibraryPath,
scannerProfilesLibraryPath, scannerProfilesLibraryPath,
siteProfilesLibraryPath, siteProfilesLibraryPath,
......
<template> <template>
<article> <article>
<slot name="alert"></slot> <slot name="alert"></slot>
<header class="gl-my-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid"> <header
<h4> class="gl-my-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-display-flex gl-justify-content-space-between"
>
<div>
<h4 class="gl-mt-0">
<slot name="heading"></slot> <slot name="heading"></slot>
</h4> </h4>
<p> <p>
<slot name="description"></slot> <slot name="description"></slot>
</p> </p>
</div>
<div>
<slot name="actions"></slot>
</div>
</header> </header>
<slot></slot> <slot></slot>
</article> </article>
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
module Projects::OnDemandScansHelper module Projects::OnDemandScansHelper
def on_demand_scans_data(project) def on_demand_scans_data(project)
base_data.merge({ {
'new-dast-scan-path' => new_project_on_demand_scan_path(project), 'new-dast-scan-path' => new_project_on_demand_scan_path(project),
'empty-state-svg-path' => image_path('illustrations/empty-state/ondemand-scan-empty.svg') 'empty-state-svg-path' => image_path('illustrations/empty-state/ondemand-scan-empty.svg')
}) }
end end
def on_demand_scans_form_data(project) def on_demand_scans_form_data(project)
base_data.merge({ {
'default-branch' => project.default_branch, 'default-branch' => project.default_branch,
'project-path' => project.path_with_namespace, 'project-path' => project.path_with_namespace,
'profiles-library-path' => project_security_configuration_dast_scans_path(project), 'profiles-library-path' => project_security_configuration_dast_scans_path(project),
...@@ -18,14 +18,6 @@ module Projects::OnDemandScansHelper ...@@ -18,14 +18,6 @@ module Projects::OnDemandScansHelper
'new-scanner-profile-path' => new_project_security_configuration_dast_scans_dast_scanner_profile_path(project), 'new-scanner-profile-path' => new_project_security_configuration_dast_scans_dast_scanner_profile_path(project),
'new-site-profile-path' => new_project_security_configuration_dast_scans_dast_site_profile_path(project), 'new-site-profile-path' => new_project_security_configuration_dast_scans_dast_site_profile_path(project),
'timezones' => timezone_data(format: :full).to_json 'timezones' => timezone_data(format: :full).to_json
})
end
private
def base_data
{
'help-page-path' => help_page_path('user/application_security/dast/index', anchor: 'on-demand-scans')
} }
end end
end end
...@@ -35,9 +35,9 @@ exports[`EmptyState renders properly 1`] = ` ...@@ -35,9 +35,9 @@ exports[`EmptyState renders properly 1`] = `
On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects. On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects.
<a <a
class="gl-link" class="gl-link"
href="/help/page/path" href="/help/user/application_security/dast/index#on-demand-scans"
> >
Lean more Learn more
</a> </a>
. .
</p> </p>
......
...@@ -15,7 +15,6 @@ describe('EmptyState', () => { ...@@ -15,7 +15,6 @@ describe('EmptyState', () => {
wrapper = mount(EmptyState, { wrapper = mount(EmptyState, {
provide: { provide: {
newDastScanPath: '/on_demand_scans/new', newDastScanPath: '/on_demand_scans/new',
helpPagePath: '/help/page/path',
emptyStateSvgPath: '/empty/state/svg/path', emptyStateSvgPath: '/empty/state/svg/path',
}, },
propsData, propsData,
......
import { shallowMount } from '@vue/test-utils'; import { GlTabs, GlSprintf } from '@gitlab/ui';
import { GlTabs } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import OnDemandScans from 'ee/on_demand_scans/components/on_demand_scans.vue'; import OnDemandScans from 'ee/on_demand_scans/components/on_demand_scans.vue';
import ConfigurationPageLayout from 'ee/security_configuration/components/configuration_page_layout.vue';
import { createRouter } from 'ee/on_demand_scans/router'; import { createRouter } from 'ee/on_demand_scans/router';
import AllTab from 'ee/on_demand_scans/components/tabs/all.vue'; import AllTab from 'ee/on_demand_scans/components/tabs/all.vue';
import RunningTab from 'ee/on_demand_scans/components/tabs/running.vue'; import RunningTab from 'ee/on_demand_scans/components/tabs/running.vue';
...@@ -12,7 +13,12 @@ describe('OnDemandScans', () => { ...@@ -12,7 +13,12 @@ describe('OnDemandScans', () => {
let wrapper; let wrapper;
let router; let router;
// Props
const newDastScanPath = '/on_demand_scans/new';
// Finders // Finders
const findNewScanLink = () => wrapper.findByTestId('new-scan-link');
const findHelpPageLink = () => wrapper.findByTestId('help-page-link');
const findTabs = () => wrapper.findComponent(GlTabs); const findTabs = () => wrapper.findComponent(GlTabs);
const findAllTab = () => wrapper.findComponent(AllTab); const findAllTab = () => wrapper.findComponent(AllTab);
const findRunningTab = () => wrapper.findComponent(RunningTab); const findRunningTab = () => wrapper.findComponent(RunningTab);
...@@ -21,8 +27,15 @@ describe('OnDemandScans', () => { ...@@ -21,8 +27,15 @@ describe('OnDemandScans', () => {
const findEmptyState = () => wrapper.findComponent(EmptyState); const findEmptyState = () => wrapper.findComponent(EmptyState);
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(OnDemandScans, { wrapper = shallowMountExtended(OnDemandScans, {
router, router,
provide: {
newDastScanPath,
},
stubs: {
ConfigurationPageLayout,
GlSprintf,
},
}); });
}; };
...@@ -46,6 +59,22 @@ describe('OnDemandScans', () => { ...@@ -46,6 +59,22 @@ describe('OnDemandScans', () => {
wrapper.setData({ hasData: true }); wrapper.setData({ hasData: true });
}); });
it('renders a link to the docs', () => {
const link = findHelpPageLink();
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(
'/help/user/application_security/dast/index#on-demand-scans',
);
});
it('renders a link to create a new scan', () => {
const link = findNewScanLink();
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(newDastScanPath);
});
it('renders the tabs if there is data', async () => { it('renders the tabs if there is data', async () => {
expect(findAllTab().exists()).toBe(true); expect(findAllTab().exists()).toBe(true);
expect(findRunningTab().exists()).toBe(true); expect(findRunningTab().exists()).toBe(true);
......
...@@ -21,7 +21,6 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; ...@@ -21,7 +21,6 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import * as responses from '../mocks/apollo_mocks'; import * as responses from '../mocks/apollo_mocks';
import { scannerProfiles, siteProfiles } from '../mocks/mock_data'; import { scannerProfiles, siteProfiles } from '../mocks/mock_data';
const helpPagePath = '/application_security/dast/index#on-demand-scans';
const dastSiteValidationDocsPath = '/application_security/dast/index#dast-site-validation'; const dastSiteValidationDocsPath = '/application_security/dast/index#dast-site-validation';
const projectPath = 'group/project'; const projectPath = 'group/project';
const defaultBranch = 'main'; const defaultBranch = 'main';
...@@ -68,6 +67,7 @@ describe('OnDemandScansForm', () => { ...@@ -68,6 +67,7 @@ describe('OnDemandScansForm', () => {
const findForm = () => wrapper.find(GlForm); const findForm = () => wrapper.find(GlForm);
const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`); const findByTestId = (testId) => wrapper.find(`[data-testid="${testId}"]`);
const findHelpPageLink = () => findByTestId('help-page-link');
const findNameInput = () => findByTestId('dast-scan-name-input'); const findNameInput = () => findByTestId('dast-scan-name-input');
const findBranchInput = () => findByTestId('dast-scan-branch-input'); const findBranchInput = () => findByTestId('dast-scan-branch-input');
const findDescriptionInput = () => findByTestId('dast-scan-description-input'); const findDescriptionInput = () => findByTestId('dast-scan-description-input');
...@@ -156,7 +156,6 @@ describe('OnDemandScansForm', () => { ...@@ -156,7 +156,6 @@ describe('OnDemandScansForm', () => {
mocks: defaultMocks, mocks: defaultMocks,
provide: { provide: {
projectPath, projectPath,
helpPagePath,
profilesLibraryPath, profilesLibraryPath,
scannerProfilesLibraryPath, scannerProfilesLibraryPath,
siteProfilesLibraryPath, siteProfilesLibraryPath,
...@@ -210,6 +209,16 @@ describe('OnDemandScansForm', () => { ...@@ -210,6 +209,16 @@ describe('OnDemandScansForm', () => {
expect(wrapper.findComponent(ScanSchedule).exists()).toBe(true); expect(wrapper.findComponent(ScanSchedule).exists()).toBe(true);
}); });
it('renders a link to the docs', () => {
createComponent();
const link = findHelpPageLink();
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(
'/help/user/application_security/dast/index#on-demand-scans',
);
});
it('populates the branch input with the default branch', () => { it('populates the branch input with the default branch', () => {
createComponent(); createComponent();
......
...@@ -4,15 +4,23 @@ exports[`Security Configuration Page Layout component matches the snapshot 1`] = ...@@ -4,15 +4,23 @@ exports[`Security Configuration Page Layout component matches the snapshot 1`] =
<article> <article>
Page alert Page alert
<header <header
class="gl-my-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid" class="gl-my-5 gl-border-b-1 gl-border-b-gray-100 gl-border-b-solid gl-display-flex gl-justify-content-space-between"
>
<div>
<h4
class="gl-mt-0"
> >
<h4>
Page title Page title
</h4> </h4>
<p> <p>
Scanner description Scanner description
</p> </p>
</div>
<div>
Action
</div>
</header> </header>
<div> <div>
......
...@@ -9,6 +9,7 @@ describe('Security Configuration Page Layout component', () => { ...@@ -9,6 +9,7 @@ describe('Security Configuration Page Layout component', () => {
slots: { slots: {
alert: 'Page alert', alert: 'Page alert',
heading: 'Page title', heading: 'Page title',
actions: 'Action',
description: 'Scanner description', description: 'Scanner description',
default: '<div>form</div>', default: '<div>form</div>',
}, },
......
...@@ -9,7 +9,6 @@ RSpec.describe Projects::OnDemandScansHelper do ...@@ -9,7 +9,6 @@ RSpec.describe Projects::OnDemandScansHelper do
it 'returns proper data' do it 'returns proper data' do
expect(helper.on_demand_scans_data(project)).to match( expect(helper.on_demand_scans_data(project)).to match(
'new-dast-scan-path' => "/#{project.full_path}/-/on_demand_scans/new", 'new-dast-scan-path' => "/#{project.full_path}/-/on_demand_scans/new",
'help-page-path' => "/help/user/application_security/dast/index#on-demand-scans",
'empty-state-svg-path' => match_asset_path('/assets/illustrations/empty-state/ondemand-scan-empty.svg') 'empty-state-svg-path' => match_asset_path('/assets/illustrations/empty-state/ondemand-scan-empty.svg')
) )
end end
...@@ -26,7 +25,6 @@ RSpec.describe Projects::OnDemandScansHelper do ...@@ -26,7 +25,6 @@ RSpec.describe Projects::OnDemandScansHelper do
it 'returns proper data' do it 'returns proper data' do
expect(helper.on_demand_scans_form_data(project)).to match( expect(helper.on_demand_scans_form_data(project)).to match(
'help-page-path' => "/help/user/application_security/dast/index#on-demand-scans",
'default-branch' => "default-branch", 'default-branch' => "default-branch",
'project-path' => "foo/bar", 'project-path' => "foo/bar",
'profiles-library-path' => "/#{project.full_path}/-/security/configuration/dast_scans", 'profiles-library-path' => "/#{project.full_path}/-/security/configuration/dast_scans",
......
...@@ -11,8 +11,4 @@ RSpec.describe "projects/on_demand_scans/index", type: :view do ...@@ -11,8 +11,4 @@ RSpec.describe "projects/on_demand_scans/index", type: :view do
it 'renders Vue app root' do it 'renders Vue app root' do
expect(rendered).to have_selector('#js-on-demand-scans') expect(rendered).to have_selector('#js-on-demand-scans')
end 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 end
...@@ -23802,7 +23802,7 @@ msgstr "" ...@@ -23802,7 +23802,7 @@ msgstr ""
msgid "OnDemandScans|On-demand scans" msgid "OnDemandScans|On-demand scans"
msgstr "" msgstr ""
msgid "OnDemandScans|On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Lean more%{learnMoreLinkEnd}." msgid "OnDemandScans|On-demand scans run outside of DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}."
msgstr "" msgstr ""
msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}" msgid "OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}"
......
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