Commit 6f6aedb5 authored by Kerri Miller's avatar Kerri Miller

Merge branch '341364-dast-view-scans-remove-tabs' into 'master'

Removes unnecessary tabs from DAST view scans

See merge request gitlab-org/gitlab!72875
parents b8a73c38 95771320
...@@ -4,28 +4,9 @@ import { s__ } from '~/locale'; ...@@ -4,28 +4,9 @@ import { s__ } from '~/locale';
import ConfigurationPageLayout from 'ee/security_configuration/components/configuration_page_layout.vue'; import ConfigurationPageLayout from 'ee/security_configuration/components/configuration_page_layout.vue';
import { HELP_PAGE_PATH } from '../constants'; 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 FinishedTab from './tabs/finished.vue';
import ScheduledTab from './tabs/scheduled.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
const TABS = {
all: {
component: AllTab,
},
running: {
component: RunningTab,
},
finished: {
component: FinishedTab,
},
scheduled: {
component: ScheduledTab,
},
};
export default { export default {
TABS,
HELP_PAGE_PATH, HELP_PAGE_PATH,
components: { components: {
GlButton, GlButton,
...@@ -34,22 +15,36 @@ export default { ...@@ -34,22 +15,36 @@ export default {
GlTabs, GlTabs,
ConfigurationPageLayout, ConfigurationPageLayout,
AllTab, AllTab,
RunningTab,
FinishedTab,
ScheduledTab,
EmptyState, EmptyState,
}, },
inject: ['newDastScanPath'], inject: ['newDastScanPath'],
props: {
pipelinesCount: {
type: Number,
required: false,
default: 0,
},
},
data() { data() {
return { return {
activeTabIndex: 0, activeTabIndex: 0,
hasData: false,
}; };
}, },
computed: { computed: {
hasData() {
return this.pipelinesCount > 0;
},
tabs() {
return {
all: {
component: AllTab,
itemsCount: this.pipelinesCount,
},
};
},
activeTab: { activeTab: {
set(newTabIndex) { set(newTabIndex) {
const newTabId = Object.keys(TABS)[newTabIndex]; const newTabId = Object.keys(this.tabs)[newTabIndex];
if (this.$route.params.tabId !== newTabId) { if (this.$route.params.tabId !== newTabId) {
this.$router.push(`/${newTabId}`); this.$router.push(`/${newTabId}`);
} }
...@@ -61,7 +56,7 @@ export default { ...@@ -61,7 +56,7 @@ export default {
}, },
}, },
created() { created() {
const tabIndex = Object.keys(TABS).findIndex((tab) => tab === this.$route.params.tabId); const tabIndex = Object.keys(this.tabs).findIndex((tab) => tab === this.$route.params.tabId);
if (tabIndex !== -1) { if (tabIndex !== -1) {
this.activeTabIndex = tabIndex; this.activeTabIndex = tabIndex;
} }
...@@ -98,9 +93,9 @@ export default { ...@@ -98,9 +93,9 @@ export default {
<gl-tabs v-model="activeTab"> <gl-tabs v-model="activeTab">
<component <component
:is="tab.component" :is="tab.component"
v-for="(tab, key) in $options.TABS" v-for="(tab, key) in tabs"
:key="key" :key="key"
:item-count="0" :items-count="tab.itemsCount"
/> />
</gl-tabs> </gl-tabs>
</configuration-page-layout> </configuration-page-layout>
......
...@@ -13,7 +13,7 @@ export default { ...@@ -13,7 +13,7 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
itemCount: { itemsCount: {
type: Number, type: Number,
required: true, required: true,
}, },
...@@ -35,7 +35,7 @@ export default { ...@@ -35,7 +35,7 @@ export default {
<gl-tab v-bind="$attrs"> <gl-tab v-bind="$attrs">
<template #title> <template #title>
{{ title }} {{ title }}
<gl-badge size="sm" class="gl-tab-counter-badge">{{ itemCount }}</gl-badge> <gl-badge size="sm" class="gl-tab-counter-badge">{{ itemsCount }}</gl-badge>
</template> </template>
<empty-state :title="emptyStateTitle" :text="emptyStateText" no-primary-button /> <empty-state :title="emptyStateTitle" :text="emptyStateText" no-primary-button />
</gl-tab> </gl-tab>
......
<script>
import { __, s__ } from '~/locale';
import BaseTab from './base_tab.vue';
export default {
components: {
BaseTab,
},
i18n: {
title: __('Finished'),
emptyStateTitle: s__('OnDemandScans|There are no finished scans.'),
emptyStateText: s__(
'OnDemandScans|%{learnMoreLinkStart}Lean more about on-demand scans%{learnMoreLinkEnd}.',
),
},
};
</script>
<template>
<base-tab
:title="$options.i18n.title"
:empty-state-title="$options.i18n.emptyStateTitle"
:empty-state-text="$options.i18n.emptyStateText"
v-bind="$attrs"
/>
</template>
<script>
import { __, s__ } from '~/locale';
import BaseTab from './base_tab.vue';
export default {
components: {
BaseTab,
},
i18n: {
title: __('Running'),
emptyStateTitle: s__('OnDemandScans|There are no running scans.'),
emptyStateText: s__(
'OnDemandScans|%{learnMoreLinkStart}Lean more about on-demand scans%{learnMoreLinkEnd}.',
),
},
};
</script>
<template>
<base-tab
:title="$options.i18n.title"
:empty-state-title="$options.i18n.emptyStateTitle"
:empty-state-text="$options.i18n.emptyStateText"
v-bind="$attrs"
/>
</template>
<script>
import { __, s__ } from '~/locale';
import BaseTab from './base_tab.vue';
export default {
components: {
BaseTab,
},
i18n: {
title: __('Scheduled'),
emptyStateTitle: s__('OnDemandScans|There are no scheduled scans.'),
emptyStateText: s__(
'OnDemandScans|%{learnMoreLinkStart}Lean more about on-demand scans%{learnMoreLinkEnd}.',
),
},
};
</script>
<template>
<base-tab
:title="$options.i18n.title"
:empty-state-title="$options.i18n.emptyStateTitle"
:empty-state-text="$options.i18n.emptyStateText"
v-bind="$attrs"
/>
</template>
...@@ -8,17 +8,22 @@ export default () => { ...@@ -8,17 +8,22 @@ export default () => {
return null; return null;
} }
const { newDastScanPath, emptyStateSvgPath } = el.dataset; const { pipelinesCount, projectPath, newDastScanPath, emptyStateSvgPath } = el.dataset;
return new Vue({ return new Vue({
el, el,
router: createRouter(), router: createRouter(),
provide: { provide: {
projectPath,
newDastScanPath, newDastScanPath,
emptyStateSvgPath, emptyStateSvgPath,
}, },
render(h) { render(h) {
return h(OnDemandScans); return h(OnDemandScans, {
props: {
pipelinesCount: Number(pipelinesCount),
},
});
}, },
}); });
}; };
# frozen_string_literal: true # frozen_string_literal: true
module Projects::OnDemandScansHelper module Projects::OnDemandScansHelper
# rubocop: disable CodeReuse/ActiveRecord
def on_demand_scans_data(project) def on_demand_scans_data(project)
{ common_data(project).merge({
'pipelines-count' => project.all_pipelines.where(source: Enums::Ci::Pipeline.sources[:ondemand_dast_scan]).count,
'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
# rubocop: enable CodeReuse/ActiveRecord
def on_demand_scans_form_data(project) def on_demand_scans_form_data(project)
{ common_data(project).merge({
'default-branch' => project.default_branch, 'default-branch' => project.default_branch,
'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),
'scanner-profiles-library-path' => project_security_configuration_dast_scans_path(project, anchor: 'scanner-profiles'), 'scanner-profiles-library-path' => project_security_configuration_dast_scans_path(project, anchor: 'scanner-profiles'),
'site-profiles-library-path' => project_security_configuration_dast_scans_path(project, anchor: 'site-profiles'), 'site-profiles-library-path' => project_security_configuration_dast_scans_path(project, anchor: 'site-profiles'),
'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 common_data(project)
{
'project-path' => project.path_with_namespace
} }
end end
end end
import { GlTabs, GlSprintf } from '@gitlab/ui'; import { GlSprintf, GlTabs } from '@gitlab/ui';
import { merge } from 'lodash';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; 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 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 FinishedTab from 'ee/on_demand_scans/components/tabs/finished.vue';
import ScheduledTab from 'ee/on_demand_scans/components/tabs/scheduled.vue';
import EmptyState from 'ee/on_demand_scans/components/empty_state.vue'; import EmptyState from 'ee/on_demand_scans/components/empty_state.vue';
describe('OnDemandScans', () => { describe('OnDemandScans', () => {
...@@ -21,13 +19,13 @@ describe('OnDemandScans', () => { ...@@ -21,13 +19,13 @@ describe('OnDemandScans', () => {
const findHelpPageLink = () => wrapper.findByTestId('help-page-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 findFinishedTab = () => wrapper.findComponent(FinishedTab);
const findScheduledTab = () => wrapper.findComponent(ScheduledTab);
const findEmptyState = () => wrapper.findComponent(EmptyState); const findEmptyState = () => wrapper.findComponent(EmptyState);
const createComponent = () => { const createComponent = (options = {}) => {
wrapper = shallowMountExtended(OnDemandScans, { wrapper = shallowMountExtended(
OnDemandScans,
merge(
{
router, router,
provide: { provide: {
newDastScanPath, newDastScanPath,
...@@ -35,8 +33,12 @@ describe('OnDemandScans', () => { ...@@ -35,8 +33,12 @@ describe('OnDemandScans', () => {
stubs: { stubs: {
ConfigurationPageLayout, ConfigurationPageLayout,
GlSprintf, GlSprintf,
GlTabs,
}, },
}); },
options,
),
);
}; };
beforeEach(() => { beforeEach(() => {
...@@ -55,8 +57,11 @@ describe('OnDemandScans', () => { ...@@ -55,8 +57,11 @@ describe('OnDemandScans', () => {
describe('when there is data', () => { describe('when there is data', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent({
wrapper.setData({ hasData: true }); propsData: {
pipelinesCount: 12,
},
});
}); });
it('renders a link to the docs', () => { it('renders a link to the docs', () => {
...@@ -75,19 +80,13 @@ describe('OnDemandScans', () => { ...@@ -75,19 +80,13 @@ describe('OnDemandScans', () => {
expect(link.attributes('href')).toBe(newDastScanPath); expect(link.attributes('href')).toBe(newDastScanPath);
}); });
it('renders the tabs if there is data', async () => { it('renders the tabs', () => {
expect(findAllTab().exists()).toBe(true); expect(findAllTab().exists()).toBe(true);
expect(findRunningTab().exists()).toBe(true);
expect(findFinishedTab().exists()).toBe(true);
expect(findScheduledTab().exists()).toBe(true);
}); });
it('updates the route when the active tab changes', async () => { it('sets the initial route to /all', () => {
const finishedTabIndex = 2; expect(findTabs().props('value')).toBe(0);
findTabs().vm.$emit('input', finishedTabIndex); expect(router.currentRoute.path).toBe('/all');
await wrapper.vm.$nextTick();
expect(router.currentRoute.path).toBe('/finished');
}); });
}); });
}); });
...@@ -16,12 +16,12 @@ describe('AllTab', () => { ...@@ -16,12 +16,12 @@ describe('AllTab', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
itemCount: 12, itemsCount: 12,
}); });
}); });
it('renders the base tab with the correct props', () => { it('renders the base tab with the correct props', () => {
expect(findBaseTab().props('title')).toBe('All'); expect(findBaseTab().props('title')).toBe('All');
expect(findBaseTab().props('itemCount')).toBe(12); expect(findBaseTab().props('itemsCount')).toBe(12);
}); });
}); });
...@@ -32,7 +32,7 @@ describe('BaseTab', () => { ...@@ -32,7 +32,7 @@ describe('BaseTab', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
title: 'All', title: 'All',
itemCount: 12, itemsCount: 12,
}); });
}); });
......
import { shallowMount } from '@vue/test-utils';
import RunningTab from 'ee/on_demand_scans/components/tabs/running.vue';
import FinishedTab from 'ee/on_demand_scans/components/tabs/finished.vue';
import ScheduledTab from 'ee/on_demand_scans/components/tabs/scheduled.vue';
import BaseTab from 'ee/on_demand_scans/components/tabs/base_tab.vue';
describe.each`
title | component
${'Running'} | ${RunningTab}
${'Finished'} | ${FinishedTab}
${'Scheduled'} | ${ScheduledTab}
`('$title tab', ({ title, component }) => {
let wrapper;
// Props
const itemCount = 12;
// Finders
const findBaseTab = () => wrapper.findComponent(BaseTab);
const createComponent = (propsData) => {
wrapper = shallowMount(component, {
propsData,
});
};
beforeEach(() => {
createComponent({
itemCount,
});
});
it('renders the base tab with the correct props', () => {
expect(findBaseTab().props('title')).toBe(title);
expect(findBaseTab().props('itemCount')).toBe(itemCount);
expect(findBaseTab().props('emptyStateTitle')).toBe(wrapper.vm.$options.i18n.emptyStateTitle);
expect(findBaseTab().props('emptyStateText')).toBe(wrapper.vm.$options.i18n.emptyStateText);
});
});
...@@ -5,11 +5,21 @@ require 'spec_helper' ...@@ -5,11 +5,21 @@ require 'spec_helper'
RSpec.describe Projects::OnDemandScansHelper do RSpec.describe Projects::OnDemandScansHelper do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
before do
allow(project).to receive(:path_with_namespace).and_return("foo/bar")
end
describe '#on_demand_scans_data' do describe '#on_demand_scans_data' do
before do
create_list(:ci_pipeline, 12, project: project, ref: 'master', source: :ondemand_dast_scan)
end
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(
'project-path' => "foo/bar",
'new-dast-scan-path' => "/#{project.full_path}/-/on_demand_scans/new", 'new-dast-scan-path' => "/#{project.full_path}/-/on_demand_scans/new",
'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'),
'pipelines-count' => 12
) )
end end
end end
...@@ -19,7 +29,6 @@ RSpec.describe Projects::OnDemandScansHelper do ...@@ -19,7 +29,6 @@ RSpec.describe Projects::OnDemandScansHelper do
before do before do
allow(project).to receive(:default_branch).and_return("default-branch") allow(project).to receive(:default_branch).and_return("default-branch")
allow(project).to receive(:path_with_namespace).and_return("foo/bar")
allow(helper).to receive(:timezone_data).with(format: :full).and_return(timezones) allow(helper).to receive(:timezone_data).with(format: :full).and_return(timezones)
end end
......
...@@ -23802,9 +23802,6 @@ msgstr "" ...@@ -23802,9 +23802,6 @@ msgstr ""
msgid "OnCallSchedules|Your schedule has been successfully created. To add individual users to this schedule, use the Add a rotation button. To enable notifications for this schedule, you must also create an %{linkStart}escalation policy%{linkEnd}." msgid "OnCallSchedules|Your schedule has been successfully created. To add individual users to this schedule, use the Add a rotation button. To enable notifications for this schedule, you must also create an %{linkStart}escalation policy%{linkEnd}."
msgstr "" msgstr ""
msgid "OnDemandScans|%{learnMoreLinkStart}Lean more about on-demand scans%{learnMoreLinkEnd}."
msgstr ""
msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later." msgid "OnDemandScans|Could not fetch scanner profiles. Please refresh the page, or try again later."
msgstr "" msgstr ""
...@@ -23889,15 +23886,6 @@ msgstr "" ...@@ -23889,15 +23886,6 @@ msgstr ""
msgid "OnDemandScans|Start time" msgid "OnDemandScans|Start time"
msgstr "" msgstr ""
msgid "OnDemandScans|There are no finished scans."
msgstr ""
msgid "OnDemandScans|There are no running scans."
msgstr ""
msgid "OnDemandScans|There are no scheduled scans."
msgstr ""
msgid "OnDemandScans|Use existing scanner profile" msgid "OnDemandScans|Use existing scanner profile"
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