Skip to content
Snippets Groups Projects
Commit c1156ad0 authored by Artur Fedorov's avatar Artur Fedorov :three: Committed by Kushal Pandya
Browse files

This MR changes layout of new/edit DAST scanner configuration

This MR changes layout of new/edit DAST scanner configuration.
It now looks similar to Security Configuration.
It has two columns layout and scan schedule is updated
according to design.

Changelog: changed
MR: gitlab-org/gitlab!84906
EE: true
parent 5838b08d
No related branches found
No related tags found
1 merge request!84906Update layout for the "New DAST scan" page
Showing
with 368 additions and 175 deletions
......@@ -3,12 +3,12 @@ import { GlTab, GlTabs, GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
export const i18n = {
......@@ -173,7 +173,7 @@ export default {
@dismiss="dismissAutoDevopsEnabledAlert"
/>
<section-layout :heading="$options.i18n.securityTesting">
<section-layout class="gl-border-b-0" :heading="$options.i18n.securityTesting">
<template #description>
<p>
<span data-testid="latest-pipeline-info-security">
......
<script>
import SectionLoader from './section_loader.vue';
export default {
name: 'SectionLayout',
components: {
SectionLoader,
},
props: {
heading: {
type: String,
required: true,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<div class="row gl-line-height-20 gl-pt-6">
<div class="col-lg-4">
<div class="row gl-m-0 gl-border-b gl-line-height-20 gl-py-6">
<div class="col-lg-4 gl-pl-0 gl-pr-9">
<h2 class="gl-font-size-h2 gl-mt-0">{{ heading }}</h2>
<slot name="description"></slot>
</div>
<div class="col-lg-8">
<slot name="features"></slot>
<div class="col-lg-8 gl-pr-0 gl-pl-0">
<section-loader v-if="isLoading" />
<slot v-else name="features"></slot>
</div>
</div>
</template>
<script>
import { GlCard, GlSkeletonLoader } from '@gitlab/ui';
export default {
name: 'SectionLoader',
components: {
GlCard,
GlSkeletonLoader,
},
};
</script>
<template>
<div>
<gl-skeleton-loader :width="1248" :height="180">
<rect x="0" y="0" width="100" height="15" rx="4" />
<rect x="0" y="24" width="460" height="32" rx="4" />
<rect x="0" y="71" width="100" height="15" rx="4" />
<rect x="0" y="95" width="460" height="72" rx="4" />
</gl-skeleton-loader>
<gl-card v-for="i in 2" :key="i" class="gl-mb-5">
<template #header>
<gl-skeleton-loader :width="1248" :height="15">
<rect x="0" y="0" width="300" height="15" rx="4" />
</gl-skeleton-loader>
</template>
<gl-skeleton-loader :width="1248" :height="15">
<rect x="0" y="0" width="600" height="15" rx="4" />
</gl-skeleton-loader>
<gl-skeleton-loader :width="1248" :height="15">
<rect x="0" y="0" width="300" height="15" rx="4" />
</gl-skeleton-loader>
</gl-card>
</div>
</template>
......@@ -8,6 +8,9 @@ export const HELP_PAGE_PATH = helpPagePath('user/application_security/dast/index
export const LEARN_MORE_TEXT = s__(
'OnDemandScans|%{learnMoreLinkStart}Learn more about on-demand scans%{learnMoreLinkEnd}.',
);
export const DAST_CONFIGURATION_HELP_PATH = helpPagePath('user/application_security/dast/index', {
anchor: 'on-demand-scans',
});
export const PIPELINE_TABS_KEYS = ['all', 'running', 'finished', 'scheduled', 'saved'];
export const PIPELINES_PER_PAGE = 20;
......
......@@ -2,13 +2,12 @@
import {
GlAlert,
GlButton,
GlCard,
GlForm,
GlIcon,
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlLink,
GlSkeletonLoader,
GlSprintf,
GlSafeHtmlDirective,
GlTooltipDirective,
......@@ -26,7 +25,8 @@ import RefSelector from '~/ref/components/ref_selector.vue';
import { REF_TYPE_BRANCHES } from '~/ref/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import validation from '~/vue_shared/directives/validation';
import { HELP_PAGE_PATH } from 'ee/on_demand_scans/constants';
import { HELP_PAGE_PATH, DAST_CONFIGURATION_HELP_PATH } from 'ee/on_demand_scans/constants';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
import dastProfileCreateMutation from '../graphql/dast_profile_create.mutation.graphql';
import dastProfileUpdateMutation from '../graphql/dast_profile_update.mutation.graphql';
import {
......@@ -69,6 +69,46 @@ export default {
saveAndRunScanBtnId: 'scan-submit-button',
saveScanBtnId: 'scan-save-button',
helpPagePath: HELP_PAGE_PATH,
dastConfigurationHelpPath: DAST_CONFIGURATION_HELP_PATH,
i18n: {
newOnDemandScanHeader: s__('OnDemandScans|New on-demand scan'),
newOnDemandScanHeaderDescription: s__(
'OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}',
),
editOnDemandScanHeader: s__('OnDemandScans|Edit on-demand scan'),
branchSelectorHelpText: s__(
'OnDemandScans|Scan results will be associated with the selected branch.',
),
dastConfigurationHeader: s__('OnDemandScans|DAST configuration'),
dastConfigurationDescription: s__(
"OnDemandScans|DAST scans for vulnerabilities in your project's running application, website, or API. For details of all configuration options, see the %{linkStart}GitLab DAST documentation%{linkEnd}.",
),
scanConfigurationNameLabel: s__('OnDemandScans|Scan name'),
scanConfigurationNamePlaceholder: s__('OnDemandScans|My daily scan'),
scanConfigurationDescriptionLabel: s__('OnDemandScans|Description (optional)'),
scanConfigurationDescriptionPlaceholder: s__(
`OnDemandScans|For example: Tests the login page for SQL injections`,
),
scanConfigurationHeader: s__('OnDemandScans|Scan configuration'),
scanConfigurationDescription: s__(
'OnDemandScans|Define the fundamental configuration options for your on-demand scan.',
),
scanConfigurationDefaultBranchLabel: s__(
'OnDemandScans|You must create a repository within your project to run an on-demand scan.',
),
scanTypeHeader: s__('OnDemandScans|Scan type'),
scanTypeText: s__('OnDemandScans|Dynamic Application Security Testing (DAST)'),
scanTypeTooltip: s__(
'OnDemandScans|Analyze a deployed version of your web application for known vulnerabilities by examining it from the outside in. DAST works by simulating external attacks on your application while it is running.',
),
scanScheduleHeader: s__('OnDemandScans|Scan schedule'),
scanScheduleDescription: s__(
'OnDemandScans|Add a schedule to run this scan at a specified date and time or on a recurring basis. Scheduled scans are automatically saved to scan library.',
),
saveAndRunScanButton: s__('OnDemandScans|Save and run scan'),
saveScanButton: s__('OnDemandScans|Save scan'),
cancelButton: s__('OnDemandScans|Cancel'),
},
components: {
RefSelector,
ProfileConflictAlert,
......@@ -77,15 +117,15 @@ export default {
ScanSchedule,
GlAlert,
GlButton,
GlCard,
GlForm,
GlIcon,
GlFormGroup,
GlFormInput,
GlFormTextarea,
GlLink,
GlSkeletonLoader,
GlSprintf,
LocalStorageSync,
SectionLayout,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
......@@ -153,8 +193,8 @@ export default {
},
title() {
return this.isEdit
? s__('OnDemandScans|Edit on-demand DAST scan')
: s__('OnDemandScans|New on-demand DAST scan');
? this.$options.i18n.editOnDemandScanHeader
: this.$options.i18n.newOnDemandScanHeader;
},
selectedScannerProfile() {
return this.selectedScannerProfileId
......@@ -329,21 +369,12 @@ export default {
:value="formFieldValues"
@input="updateFromStorage"
/>
<header class="gl-mb-6">
<header class="gl-border-b gl-pb-6">
<div class="gl-mt-6 gl-display-flex">
<h2 class="gl-flex-grow-1 gl-my-0">{{ title }}</h2>
<gl-button :href="onDemandScansPath" data-testid="manage-profiles-link">
{{ s__('OnDemandScans|Manage DAST scans') }}
</gl-button>
<h1 class="gl-font-size-h1 gl-flex-grow-1 gl-my-0">{{ title }}</h1>
</div>
<p>
<gl-sprintf
:message="
s__(
'OnDemandScans|On-demand scans run outside the DevOps cycle and find vulnerabilities in your projects. %{learnMoreLinkStart}Learn more%{learnMoreLinkEnd}',
)
"
>
<gl-sprintf :message="$options.i18n.newOnDemandScanHeaderDescription">
<template #learnMoreLink="{ content }">
<gl-link :href="$options.helpPagePath" data-testid="help-page-link">
{{ content }}
......@@ -366,104 +397,128 @@ export default {
<li v-for="error in errors" :key="error" v-safe-html="error"></li>
</ul>
</gl-alert>
<section-layout
v-if="!failedToLoadProfiles"
:heading="$options.i18n.scanConfigurationHeader"
:is-loading="isLoadingProfiles"
>
<template #description>
<p>{{ $options.i18n.scanConfigurationDescription }}</p>
</template>
<template #features>
<gl-form-group
class="gl-mb-6"
:label="$options.i18n.scanConfigurationNameLabel"
:invalid-feedback="form.fields.name.feedback"
>
<gl-form-input
v-model="form.fields.name.value"
v-validation:[form.showValidation]
data-testid="dast-scan-name-input"
type="text"
:placeholder="$options.i18n.scanConfigurationNamePlaceholder"
:state="form.fields.name.state"
name="name"
required
/>
</gl-form-group>
<template v-if="isLoadingProfiles">
<gl-skeleton-loader :width="1248" :height="180">
<rect x="0" y="0" width="100" height="15" rx="4" />
<rect x="0" y="24" width="460" height="32" rx="4" />
<rect x="0" y="71" width="100" height="15" rx="4" />
<rect x="0" y="95" width="460" height="72" rx="4" />
</gl-skeleton-loader>
<gl-card v-for="i in 2" :key="i" class="gl-mb-5">
<template #header>
<gl-skeleton-loader :width="1248" :height="15">
<rect x="0" y="0" width="300" height="15" rx="4" />
</gl-skeleton-loader>
</template>
<gl-skeleton-loader :width="1248" :height="15">
<rect x="0" y="0" width="600" height="15" rx="4" />
</gl-skeleton-loader>
<gl-skeleton-loader :width="1248" :height="15">
<rect x="0" y="0" width="300" height="15" rx="4" />
</gl-skeleton-loader>
</gl-card>
</template>
<template v-else-if="!failedToLoadProfiles">
<gl-form-group
:label="s__('OnDemandScans|Scan name')"
:invalid-feedback="form.fields.name.feedback"
>
<gl-form-input
v-model="form.fields.name.value"
v-validation:[form.showValidation]
class="mw-460"
data-testid="dast-scan-name-input"
type="text"
:placeholder="s__('OnDemandScans|My daily scan')"
:state="form.fields.name.state"
name="name"
required
/>
</gl-form-group>
<gl-form-group :label="s__('OnDemandScans|Description (optional)')">
<gl-form-textarea
v-model="form.fields.description.value"
class="mw-460"
data-testid="dast-scan-description-input"
:placeholder="s__(`OnDemandScans|For example: Tests the login page for SQL injections`)"
name="description"
:state="form.fields.description.state"
/>
</gl-form-group>
<gl-form-group class="gl-mb-6" :label="$options.i18n.scanConfigurationDescriptionLabel">
<gl-form-textarea
v-model="form.fields.description.value"
data-testid="dast-scan-description-input"
:placeholder="$options.i18n.scanConfigurationDescriptionPlaceholder"
name="description"
:state="form.fields.description.state"
/>
</gl-form-group>
<gl-form-group :label="__('Branch')">
<ref-selector
v-model="selectedBranch"
data-testid="dast-scan-branch-input"
no-flip
:enabled-ref-types="$options.enabledRefTypes"
:project-id="projectPath"
:translations="{
dropdownHeader: __('Select a branch'),
searchPlaceholder: __('Search'),
noRefSelected: __('No available branches'),
noResults: __('No available branches'),
}"
<gl-form-group class="gl-mb-6" :label="$options.i18n.scanTypeHeader">
<span>{{ $options.i18n.scanTypeText }}</span>
<gl-icon
v-gl-tooltip="$options.i18n.scanTypeTooltip"
name="question-o"
class="gl-ml-2 gl-link gl-cursor-pointer"
/>
</gl-form-group>
<gl-form-group class="gl-mb-3" :label="__('Branch')">
<small class="form-text text-gl-muted gl-mt-0 gl-mb-5">
{{ $options.i18n.branchSelectorHelpText }}
</small>
<ref-selector
v-model="selectedBranch"
data-testid="dast-scan-branch-input"
no-flip
:enabled-ref-types="$options.enabledRefTypes"
:project-id="projectPath"
:translations="{
dropdownHeader: __('Select a branch'),
searchPlaceholder: __('Search'),
noRefSelected: __('No available branches'),
noResults: __('No available branches'),
}"
/>
<div v-if="!defaultBranch" class="gl-text-red-500 gl-mt-3">
{{ $options.i18n.scanConfigurationDefaultBranchLabel }}
</div>
</gl-form-group>
</template>
</section-layout>
<section-layout
v-if="!failedToLoadProfiles"
:heading="$options.i18n.dastConfigurationHeader"
:is-loading="isLoadingProfiles"
>
<template #description>
<gl-sprintf :message="$options.i18n.dastConfigurationDescription">
<template #link="{ content }">
<gl-link :href="$options.dastConfigurationHelpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</template>
<template #features>
<scanner-profile-selector
v-model="selectedScannerProfileId"
class="gl-mb-6"
:profiles="scannerProfiles"
:selected-profile="selectedScannerProfile"
:has-conflict="hasProfilesConflict"
:dast-scan-id="dastScanId"
/>
<div v-if="!defaultBranch" class="gl-text-red-500 gl-mt-3">
{{
s__(
'OnDemandScans|You must create a repository within your project to run an on-demand scan.',
)
}}
</div>
</gl-form-group>
<scanner-profile-selector
v-model="selectedScannerProfileId"
class="gl-mb-5"
:profiles="scannerProfiles"
:selected-profile="selectedScannerProfile"
:has-conflict="hasProfilesConflict"
:dast-scan-id="dastScanId"
/>
<site-profile-selector
v-model="selectedSiteProfileId"
class="gl-mb-5"
:profiles="siteProfiles"
:selected-profile="selectedSiteProfile"
:has-conflict="hasProfilesConflict"
:dast-scan-id="dastScanId"
/>
<site-profile-selector
v-model="selectedSiteProfileId"
class="gl-mb-3"
:profiles="siteProfiles"
:selected-profile="selectedSiteProfile"
:has-conflict="hasProfilesConflict"
:dast-scan-id="dastScanId"
/>
</template>
</section-layout>
<scan-schedule v-model="profileSchedule" class="gl-mb-5" />
<section-layout
v-if="!failedToLoadProfiles"
:heading="$options.i18n.scanScheduleHeader"
:is-loading="isLoadingProfiles"
>
<template #description>
<p>{{ $options.i18n.scanScheduleDescription }}</p>
</template>
<template #features>
<scan-schedule v-model="profileSchedule" />
<profile-conflict-alert
v-if="hasProfilesConflict"
data-testid="on-demand-scans-profiles-conflict-alert"
/>
<profile-conflict-alert
v-if="hasProfilesConflict"
data-testid="on-demand-scans-profiles-conflict-alert"
/>
</template>
</section-layout>
<div class="gl-mt-6 gl-pt-6">
<div v-if="!failedToLoadProfiles">
<div class="gl-pt-6">
<gl-button
type="submit"
variant="confirm"
......@@ -472,7 +527,7 @@ export default {
:disabled="isSubmitButtonDisabled"
:loading="loading === $options.saveAndRunScanBtnId"
>
{{ s__('OnDemandScans|Save and run scan') }}
{{ $options.i18n.saveAndRunScanButton }}
</gl-button>
<gl-button
variant="confirm"
......@@ -482,16 +537,16 @@ export default {
:loading="loading === $options.saveScanBtnId"
@click="onSubmit({ runAfter: false, button: $options.saveScanBtnId })"
>
{{ s__('OnDemandScans|Save scan') }}
{{ $options.i18n.saveScanButton }}
</gl-button>
<gl-button
data-testid="on-demand-scan-cancel-button"
:disabled="Boolean(loading)"
@click="onCancelClicked"
>
{{ __('Cancel') }}
{{ $options.i18n.cancelButton }}
</gl-button>
</div>
</template>
</div>
</gl-form>
</template>
<script>
import { GlCard, GlDatepicker, GlFormCheckbox, GlFormGroup } from '@gitlab/ui';
import { GlDatepicker, GlFormGroup, GlToggle } from '@gitlab/ui';
import { s__ } from '~/locale';
import DropdownInput from 'ee/security_configuration/components/dropdown_input.vue';
import {
dateAndTimeToISOString,
......@@ -12,10 +13,16 @@ import { toGraphQLCadence, fromGraphQLCadence } from '../utils';
export default {
name: 'ScanSchedule',
i18n: {
scanScheduleToggleText: s__('OnDemandScans|Enable scan schedule'),
scanStartTimeLabel: s__('OnDemandScans|Start time'),
scanScheduleRepeatLabel: s__('OnDemandScans|at'),
scanScheduleRepeatDefaultLabel: s__('OnDemandScans|Repeats'),
scanScheduleTimezoneLabel: s__('OnDemandScans|Timezone'),
},
components: {
GlCard,
GlDatepicker,
GlFormCheckbox,
GlToggle,
GlFormGroup,
DropdownInput,
TimezoneDropdown,
......@@ -91,30 +98,23 @@ export default {
</script>
<template>
<gl-card class="gl-bg-gray-10">
<div class="row">
<div class="col-12 col-md-6">
<gl-form-checkbox v-model="form.isScheduledScan" class="gl-mb-3" @input="handleInput">
<span class="gl-font-weight-bold">{{ s__('OnDemandScans|Schedule scan') }}</span>
</gl-form-checkbox>
<gl-form-group
class="gl-pl-6"
data-testid="profile-schedule-form-group"
:disabled="!form.isScheduledScan"
>
<div class="row">
<div class="col-12 col-md-6">
<gl-toggle
v-model="form.isScheduledScan"
class="gl-mb-3"
:label="$options.i18n.scanScheduleToggleText"
@change="handleInput"
/>
<transition name="fade">
<gl-form-group v-if="form.isScheduledScan" data-testid="profile-schedule-form-group">
<div class="gl-font-weight-bold gl-mb-3">
{{ s__('OnDemandScans|Start time') }}
{{ $options.i18n.scanStartTimeLabel }}
</div>
<timezone-dropdown
v-model="timezone"
:timezone-data="timezones"
:disabled="!form.isScheduledScan"
@input="handleInput"
/>
<div class="gl-display-flex gl-align-items-center">
<div class="gl-display-flex gl-align-items-center gl-mb-5">
<gl-datepicker v-model="form.startDate" @input="handleInput" />
<span class="gl-px-3">
{{ __('at') }}
{{ $options.i18n.scanScheduleRepeatLabel }}
</span>
<input
v-model="form.startTime"
......@@ -123,19 +123,30 @@ export default {
@input="handleInput"
/>
</div>
<div class="gl-font-weight-bold gl-mb-3">
{{ $options.i18n.scanScheduleTimezoneLabel }}
</div>
<timezone-dropdown
v-model="timezone"
:timezone-data="timezones"
:disabled="!form.isScheduledScan"
@input="handleInput"
/>
<dropdown-input
v-model="form.cadence"
:label="__('Repeats')"
:default-text="__('Repeats')"
:label="$options.i18n.scanScheduleRepeatDefaultLabel"
:default-text="$options.i18n.scanScheduleRepeatDefaultLabel"
:options="$options.SCAN_CADENCE_OPTIONS"
:disabled="!form.isScheduledScan"
field="repeat-input"
class="gl-mt-5"
data-testid="schedule-cadence-input"
@input="handleInput"
/>
</gl-form-group>
</div>
</transition>
</div>
</gl-card>
</div>
</template>
- add_to_breadcrumbs s_('OnDemandScans|On-demand Scans'), project_on_demand_scans_path(@project, anchor: 'saved')
- breadcrumb_title s_('OnDemandScans|Edit on-demand DAST scan')
- page_title s_('OnDemandScans|Edit on-demand DAST scan')
- @content_class = "limit-container-width" unless fluid_layout
#js-on-demand-scans-form{ data: on_demand_scans_form_data(@project).merge({ dast_scan: @dast_profile.to_json }) }
- add_to_breadcrumbs s_('OnDemandScans|On-demand Scans'), project_on_demand_scans_path(@project, anchor: 'saved')
- breadcrumb_title s_('OnDemandScans|New on-demand DAST scan')
- page_title s_('OnDemandScans|New on-demand DAST scan')
- @content_class = "limit-container-width" unless fluid_layout
#js-on-demand-scans-form{ data: on_demand_scans_form_data(@project) }
......@@ -26,8 +26,9 @@
end
it 'shows new scan page', :aggregate_failures, :js do
expect(page).to have_content 'New on-demand DAST scan'
expect(page).to have_link 'Manage DAST scans'
expect(page).to have_content 'New on-demand scan'
expect(page).to have_content 'Scan configuration'
expect(page).to have_content 'DAST configuration'
expect(page).to have_button 'Save and run scan'
expect(page).to have_button 'Save scan'
......
......@@ -9,6 +9,8 @@ import OnDemandScansForm from 'ee/on_demand_scans_form/components/on_demand_scan
import ScannerProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/scanner_profile_selector.vue';
import SiteProfileSelector from 'ee/on_demand_scans_form/components/profile_selector/site_profile_selector.vue';
import ScanSchedule from 'ee/on_demand_scans_form/components/scan_schedule.vue';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
import SectionLoader from '~/vue_shared/security_configuration/components/section_loader.vue';
import dastProfileCreateMutation from 'ee/on_demand_scans_form/graphql/dast_profile_create.mutation.graphql';
import dastProfileUpdateMutation from 'ee/on_demand_scans_form/graphql/dast_profile_update.mutation.graphql';
import dastScannerProfilesQuery from 'ee/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql';
......@@ -177,6 +179,8 @@ describe('OnDemandScansForm', () => {
RefSelector: RefSelectorStub,
LocalStorageSync,
ScanSchedule: true,
SectionLayout,
SectionLoader,
},
},
{ ...options, localVue, apolloProvider },
......@@ -214,7 +218,7 @@ describe('OnDemandScansForm', () => {
it('renders properly', () => {
createComponent();
expect(wrapper.text()).toContain('New on-demand DAST scan');
expect(wrapper.text()).toContain('New on-demand scan');
expect(wrapper.findComponent(ScanSchedule).exists()).toBe(true);
});
......@@ -289,7 +293,7 @@ describe('OnDemandScansForm', () => {
});
it('sets the title properly', () => {
expect(wrapper.text()).toContain('Edit on-demand DAST scan');
expect(wrapper.text()).toContain('Edit on-demand scan');
});
it('populates the fields with passed values', () => {
......
import { GlDatepicker, GlFormCheckbox, GlFormGroup } from '@gitlab/ui';
import { GlDatepicker, GlFormGroup, GlToggle } from '@gitlab/ui';
import { merge } from 'lodash';
import { nextTick } from 'vue';
import mockTimezones from 'test_fixtures/timezones/full.json';
......@@ -15,7 +15,7 @@ describe('ScanSchedule', () => {
let wrapper;
// Finders
const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findToggle = () => wrapper.findComponent(GlToggle);
const findProfileScheduleFormGroup = () => wrapper.findByTestId('profile-schedule-form-group');
const findTimezoneDropdown = () => wrapper.findComponent(TimezoneDropdown);
const findDatepicker = () => wrapper.findComponent(GlDatepicker);
......@@ -42,8 +42,8 @@ describe('ScanSchedule', () => {
GlFormGroup: stubComponent(GlFormGroup, {
props: ['disabled'],
}),
GlFormCheckbox: stubComponent(GlFormCheckbox, {
props: ['checked'],
GlToggle: stubComponent(GlToggle, {
props: ['value'],
}),
TimezoneDropdown: stubComponent(TimezoneDropdown, {
props: ['disabled', 'timezoneData', 'value'],
......@@ -64,14 +64,17 @@ describe('ScanSchedule', () => {
createComponent();
});
it('by default, checkbox is unchecked and fields are disabled', () => {
expect(findCheckbox().props('checked')).toBe(false);
expect(findProfileScheduleFormGroup().props('disabled')).toBe(true);
expect(findTimezoneDropdown().props('disabled')).toBe(true);
expect(findCadenceInput().props('disabled')).toBe(true);
it('by default, checkbox is unchecked and fields are hidden', () => {
expect(findToggle().props('value')).toBe(false);
expect(findProfileScheduleFormGroup().exists()).toBe(false);
expect(findTimezoneDropdown().exists()).toBe(false);
expect(findCadenceInput().exists()).toBe(false);
});
it('initializes timezone dropdown properly', () => {
it('initializes timezone dropdown properly', async () => {
findToggle().vm.$emit('change', true);
await nextTick();
const timezoneDropdown = findTimezoneDropdown();
expect(timezoneDropdown.props('timezoneData')).toEqual(mockTimezones);
......@@ -82,14 +85,14 @@ describe('ScanSchedule', () => {
describe('once schedule is activated', () => {
beforeEach(() => {
createComponent();
findCheckbox().vm.$emit('input', true);
findToggle().vm.$emit('change', true);
});
it('enables fields', () => {
expect(findTimezoneDropdown().attributes('disabled')).toBeUndefined();
expect(findProfileScheduleFormGroup().props('disabled')).toBe(false);
expect(findTimezoneDropdown().props('disabled')).toBe(false);
expect(findCadenceInput().props('disabled')).toBe(false);
it('shows fields', () => {
expect(findTimezoneDropdown().exists()).toBe(true);
expect(findProfileScheduleFormGroup().exists()).toBe(true);
expect(findTimezoneDropdown().exists()).toBe(true);
expect(findCadenceInput().exists()).toBe(true);
});
it('emits input payload', () => {
......@@ -142,7 +145,7 @@ describe('ScanSchedule', () => {
});
it('deactives schedule when checkbox is unchecked', async () => {
findCheckbox().vm.$emit('input', false);
findToggle().vm.$emit('change', false);
await nextTick();
expect(wrapper.emitted().input).toHaveLength(2);
......@@ -175,7 +178,7 @@ describe('ScanSchedule', () => {
},
});
expect(findCheckbox().props('checked')).toBe(true);
expect(findToggle().props('value')).toBe(true);
expect(findDatepicker().props('value')).toEqual(new Date(schedule.startsAt));
expect(findTimeInput().element.value).toBe('08:45');
expect(findCadenceInput().props('value')).toBe(SCAN_CADENCE_OPTIONS[3].value);
......
......@@ -26266,9 +26266,18 @@ msgstr ""
msgid "OnDemandScans|%{learnMoreLinkStart}Learn more about on-demand scans%{learnMoreLinkEnd}."
msgstr ""
 
msgid "OnDemandScans|Add a schedule to run this scan at a specified date and time or on a recurring basis. Scheduled scans are automatically saved to scan library."
msgstr ""
msgid "OnDemandScans|Analyze a deployed version of your web application for known vulnerabilities by examining it from the outside in. DAST works by simulating external attacks on your application while it is running."
msgstr ""
msgid "OnDemandScans|Are you sure you want to delete this scan?"
msgstr ""
 
msgid "OnDemandScans|Cancel"
msgstr ""
msgid "OnDemandScans|Could not delete saved scan. Please refresh the page, or try again later."
msgstr ""
 
......@@ -26290,22 +26299,37 @@ msgstr ""
msgid "OnDemandScans|Create new site profile"
msgstr ""
 
msgid "OnDemandScans|DAST configuration"
msgstr ""
msgid "OnDemandScans|DAST scans for vulnerabilities in your project's running application, website, or API. For details of all configuration options, see the %{linkStart}GitLab DAST documentation%{linkEnd}."
msgstr ""
msgid "OnDemandScans|Define the fundamental configuration options for your on-demand scan."
msgstr ""
msgid "OnDemandScans|Delete profile"
msgstr ""
 
msgid "OnDemandScans|Description (optional)"
msgstr ""
 
msgid "OnDemandScans|Dynamic Application Security Testing (DAST)"
msgstr ""
msgid "OnDemandScans|Edit on-demand DAST scan"
msgstr ""
 
msgid "OnDemandScans|Edit on-demand scan"
msgstr ""
msgid "OnDemandScans|Edit profile"
msgstr ""
 
msgid "OnDemandScans|For example: Tests the login page for SQL injections"
msgid "OnDemandScans|Enable scan schedule"
msgstr ""
 
msgid "OnDemandScans|Manage DAST scans"
msgid "OnDemandScans|For example: Tests the login page for SQL injections"
msgstr ""
 
msgid "OnDemandScans|Manage scanner profiles"
......@@ -26320,6 +26344,9 @@ msgstr ""
msgid "OnDemandScans|New on-demand DAST scan"
msgstr ""
 
msgid "OnDemandScans|New on-demand scan"
msgstr ""
msgid "OnDemandScans|New scan"
msgstr ""
 
......@@ -26353,19 +26380,25 @@ msgstr ""
msgid "OnDemandScans|Save scan"
msgstr ""
 
msgid "OnDemandScans|Scan configuration"
msgstr ""
msgid "OnDemandScans|Scan library"
msgstr ""
 
msgid "OnDemandScans|Scan name"
msgstr ""
 
msgid "OnDemandScans|Scan type"
msgid "OnDemandScans|Scan results will be associated with the selected branch."
msgstr ""
 
msgid "OnDemandScans|Scanner profile"
msgid "OnDemandScans|Scan schedule"
msgstr ""
 
msgid "OnDemandScans|Schedule scan"
msgid "OnDemandScans|Scan type"
msgstr ""
msgid "OnDemandScans|Scanner profile"
msgstr ""
 
msgid "OnDemandScans|Select one of the existing profiles"
......@@ -26398,6 +26431,9 @@ msgstr ""
msgid "OnDemandScans|There are no scheduled scans."
msgstr ""
 
msgid "OnDemandScans|Timezone"
msgstr ""
msgid "OnDemandScans|Use existing scanner profile"
msgstr ""
 
......@@ -26410,6 +26446,9 @@ msgstr ""
msgid "OnDemandScans|You must create a repository within your project to run an on-demand scan."
msgstr ""
 
msgid "OnDemandScans|at"
msgstr ""
msgid "Once imported, repositories can be mirrored over SSH. Read more %{link_start}here%{link_end}."
msgstr ""
 
......@@ -31614,9 +31653,6 @@ msgstr ""
msgid "Reopens this %{quick_action_target}."
msgstr ""
 
msgid "Repeats"
msgstr ""
msgid "Replace"
msgstr ""
 
......
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SectionLayout from '~/security_configuration/components/section_layout.vue';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
import SectionLoader from '~/vue_shared/security_configuration/components/section_loader.vue';
describe('Section Layout component', () => {
let wrapper;
......@@ -18,6 +19,7 @@ describe('Section Layout component', () => {
};
const findHeading = () => wrapper.find('h2');
const findLoader = () => wrapper.findComponent(SectionLoader);
afterEach(() => {
wrapper.destroy();
......@@ -46,4 +48,11 @@ describe('Section Layout component', () => {
});
});
});
describe('loading state', () => {
it('should show loaders when loading', () => {
createComponent({ heading: 'testheading', isLoading: true });
expect(findLoader().exists()).toBe(true);
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment