Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
66ecf82c
Commit
66ecf82c
authored
Sep 07, 2021
by
Paul Gascou-Vaillancourt
Committed by
Igor Drozdov
Sep 07, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add scheduling options to DAST profiles
parent
7bfa0963
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
462 additions
and
28 deletions
+462
-28
app/assets/javascripts/lib/utils/datetime/date_format_utility.js
...ets/javascripts/lib/utils/datetime/date_format_utility.js
+13
-7
app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
...s/javascripts/vue_shared/components/timezone_dropdown.vue
+1
-1
ee/app/assets/javascripts/on_demand_scans/components/on_demand_scans_form.vue
...ripts/on_demand_scans/components/on_demand_scans_form.vue
+19
-1
ee/app/assets/javascripts/on_demand_scans/components/scan_schedule.vue
.../javascripts/on_demand_scans/components/scan_schedule.vue
+165
-0
ee/app/assets/javascripts/on_demand_scans/index.js
ee/app/assets/javascripts/on_demand_scans/index.js
+4
-3
ee/app/assets/javascripts/on_demand_scans/settings.js
ee/app/assets/javascripts/on_demand_scans/settings.js
+20
-1
ee/app/controllers/projects/on_demand_scans_controller.rb
ee/app/controllers/projects/on_demand_scans_controller.rb
+4
-0
ee/app/helpers/projects/on_demand_scans_helper.rb
ee/app/helpers/projects/on_demand_scans_helper.rb
+2
-1
ee/spec/frontend/on_demand_scans/components/on_demand_scans_form_spec.js
...d/on_demand_scans/components/on_demand_scans_form_spec.js
+24
-5
ee/spec/frontend/on_demand_scans/components/scan_schedule_spec.js
...frontend/on_demand_scans/components/scan_schedule_spec.js
+182
-0
ee/spec/helpers/projects/on_demand_scans_helper_spec.rb
ee/spec/helpers/projects/on_demand_scans_helper_spec.rb
+2
-1
locale/gitlab.pot
locale/gitlab.pot
+18
-0
spec/frontend/lib/utils/datetime/date_format_utility_spec.js
spec/frontend/lib/utils/datetime/date_format_utility_spec.js
+8
-8
No files found.
app/assets/javascripts/lib/utils/datetime/date_format_utility.js
View file @
66ecf82c
...
...
@@ -299,8 +299,12 @@ export const dateToYearMonthDate = (date) => {
// eslint-disable-next-line @gitlab/require-i18n-strings
throw
new
Error
(
'
Argument should be a Date instance
'
);
}
const
[
year
,
month
,
day
]
=
date
.
toISOString
().
replace
(
/T.*$/
,
''
).
split
(
'
-
'
);
return
{
year
,
month
,
day
};
const
[
month
,
day
]
=
padWithZeros
(
date
.
getMonth
()
+
1
,
date
.
getDate
());
return
{
year
:
`
${
date
.
getFullYear
()}
`
,
month
,
day
,
};
};
/**
...
...
@@ -328,13 +332,15 @@ export const timeToHoursMinutes = (time = '') => {
* @param {String} offset An optional Date-compatible offset.
* @returns {String} The combined Date's ISO string representation.
*/
export
const
dateAndTimeTo
UTC
String
=
(
date
,
time
,
offset
=
''
)
=>
{
export
const
dateAndTimeTo
ISO
String
=
(
date
,
time
,
offset
=
''
)
=>
{
const
{
year
,
month
,
day
}
=
dateToYearMonthDate
(
date
);
const
{
hours
,
minutes
}
=
timeToHoursMinutes
(
time
);
return
new
Date
(
`
${
year
}
-
${
month
}
-
${
day
}
T
${
hours
}
:
${
minutes
}
:00.000
${
offset
||
'
Z
'
}
`
,
).
toISOString
();
const
dateString
=
`
${
year
}
-
${
month
}
-
${
day
}
T
${
hours
}
:
${
minutes
}
:00.000
${
offset
||
'
Z
'
}
`
;
if
(
Number
.
isNaN
(
Date
.
parse
(
dateString
)))
{
// eslint-disable-next-line @gitlab/require-i18n-strings
throw
new
Error
(
'
Could not initialize date
'
);
}
return
dateString
;
};
/**
...
...
app/assets/javascripts/vue_shared/components/timezone_dropdown.vue
View file @
66ecf82c
...
...
@@ -66,7 +66,7 @@ export default {
};
</
script
>
<
template
>
<gl-dropdown
:text=
"selectedTimezoneLabel"
block
lazy
menu-class=
"gl-w-full!"
>
<gl-dropdown
:text=
"selectedTimezoneLabel"
block
lazy
menu-class=
"gl-w-full!"
v-bind=
"$attrs"
>
<gl-search-box-by-type
v-model.trim=
"searchTerm"
v-autofocusonshow
autofocus
/>
<gl-dropdown-item
v-for=
"timezone in filteredResults"
...
...
ee/app/assets/javascripts/on_demand_scans/components/on_demand_scans_form.vue
View file @
66ecf82c
...
...
@@ -26,6 +26,7 @@ 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
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
dastProfileCreateMutation
from
'
../graphql/dast_profile_create.mutation.graphql
'
;
import
dastProfileUpdateMutation
from
'
../graphql/dast_profile_update.mutation.graphql
'
;
import
{
...
...
@@ -38,6 +39,7 @@ import {
}
from
'
../settings
'
;
import
ScannerProfileSelector
from
'
./profile_selector/scanner_profile_selector.vue
'
;
import
SiteProfileSelector
from
'
./profile_selector/site_profile_selector.vue
'
;
import
ScanSchedule
from
'
./scan_schedule.vue
'
;
export
const
ON_DEMAND_SCANS_STORAGE_KEY
=
'
on-demand-scans-new-form
'
;
...
...
@@ -69,6 +71,7 @@ export default {
RefSelector
,
ScannerProfileSelector
,
SiteProfileSelector
,
ScanSchedule
,
GlAlert
,
GlButton
,
GlCard
,
...
...
@@ -86,6 +89,7 @@ export default {
GlTooltip
:
GlTooltipDirective
,
validation
:
validation
(),
},
mixins
:
[
glFeatureFlagMixin
()],
apollo
:
{
scannerProfiles
:
createProfilesApolloOptions
(
'
scannerProfiles
'
,
...
...
@@ -130,6 +134,7 @@ export default {
selectedBranch
:
this
.
dastScan
?.
branch
?.
name
??
this
.
defaultBranch
,
selectedScannerProfileId
:
this
.
dastScan
?.
dastScannerProfile
.
id
||
null
,
selectedSiteProfileId
:
this
.
dastScan
?.
dastSiteProfile
.
id
||
null
,
profileSchedule
:
this
.
dastScan
?.
dastProfileSchedule
,
loading
:
false
,
errorType
:
null
,
errors
:
[],
...
...
@@ -198,12 +203,18 @@ export default {
return
isFormInvalid
||
(
loading
&&
loading
!==
saveScanBtnId
);
},
formFieldValues
()
{
const
{
selectedScannerProfileId
,
selectedSiteProfileId
,
selectedBranch
}
=
this
;
const
{
selectedScannerProfileId
,
selectedSiteProfileId
,
selectedBranch
,
profileSchedule
,
}
=
this
;
return
{
...
serializeFormObject
(
this
.
form
.
fields
),
selectedScannerProfileId
,
selectedSiteProfileId
,
selectedBranch
,
profileSchedule
,
};
},
storageKey
()
{
...
...
@@ -236,6 +247,9 @@ export default {
dastScannerProfileId
:
this
.
selectedScannerProfile
.
id
,
dastSiteProfileId
:
this
.
selectedSiteProfile
.
id
,
branchName
:
this
.
selectedBranch
,
...(
this
.
glFeatures
.
dastOnDemandScansScheduler
?
{
dastProfileSchedule
:
this
.
profileSchedule
}
:
{}),
...(
this
.
isEdit
?
{
id
:
this
.
dastScan
.
id
}
:
{}),
...
serializeFormObject
(
this
.
form
.
fields
),
[
this
.
isEdit
?
'
runAfterUpdate
'
:
'
runAfterCreate
'
]:
runAfter
,
...
...
@@ -286,6 +300,7 @@ export default {
const
{
selectedSiteProfileId
,
selectedScannerProfileId
,
profileSchedule
,
name
,
description
,
selectedBranch
,
...
...
@@ -297,6 +312,7 @@ export default {
// precedence is given to profile IDs passed from the query params
this
.
selectedSiteProfileId
=
this
.
selectedSiteProfileId
??
selectedSiteProfileId
;
this
.
selectedScannerProfileId
=
this
.
selectedScannerProfileId
??
selectedScannerProfileId
;
this
.
profileSchedule
=
this
.
profileSchedule
??
profileSchedule
;
},
},
};
...
...
@@ -436,6 +452,8 @@ export default {
:has-conflict=
"hasProfilesConflict"
/>
<scan-schedule
v-if=
"glFeatures.dastOnDemandScansScheduler"
v-model=
"profileSchedule"
/>
<gl-alert
v-if=
"hasProfilesConflict"
:title=
"s__('OnDemandScans|You cannot run an active scan against an unvalidated site.')"
...
...
ee/app/assets/javascripts/on_demand_scans/components/scan_schedule.vue
0 → 100644
View file @
66ecf82c
<
script
>
import
{
GlCard
,
GlDatepicker
,
GlFormCheckbox
,
GlFormGroup
}
from
'
@gitlab/ui
'
;
import
DropdownInput
from
'
ee/security_configuration/components/dropdown_input.vue
'
;
import
{
dateAndTimeToISOString
,
stripTimezoneFromISODate
,
dateToTimeInputValue
,
}
from
'
~/lib/utils/datetime/date_format_utility
'
;
import
TimezoneDropdown
from
'
~/vue_shared/components/timezone_dropdown.vue
'
;
import
{
SCAN_CADENCE_OPTIONS
}
from
'
../settings
'
;
/**
* Converts a cadence option string into the proper schedule parameter.
* @param {String} str Cadence option's string representation.
* @returns {Object} Corresponding schedule parameter.
*/
const
toGraphQLCadence
=
(
str
)
=>
{
if
(
!
str
)
{
return
''
;
}
const
[
unit
,
duration
]
=
str
.
split
(
'
_
'
);
return
{
unit
,
duration
:
Number
(
duration
)
};
};
/**
* Converts a schedule parameter into the corresponding string option.
* @param {Object} obj Schedule paramter.
* @returns {String} Corresponding cadence option's string representation.
*/
const
fromGraphQLCadence
=
(
obj
)
=>
{
if
(
!
obj
)
{
return
''
;
}
return
`
${
obj
.
unit
}
_
${
obj
.
duration
}
`
.
toUpperCase
();
};
export
default
{
name
:
'
ScanSchedule
'
,
components
:
{
GlCard
,
GlDatepicker
,
GlFormCheckbox
,
GlFormGroup
,
DropdownInput
,
TimezoneDropdown
,
},
inject
:
[
'
timezones
'
],
props
:
{
value
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
},
data
()
{
return
{
form
:
{
isScheduledScan
:
this
.
value
.
active
??
false
,
selectedTimezone
:
this
.
value
.
timezone
??
null
,
startDate
:
null
,
startTime
:
null
,
cadence
:
fromGraphQLCadence
(
this
.
value
.
cadence
)
??
SCAN_CADENCE_OPTIONS
[
0
].
value
,
},
};
},
computed
:
{
timezone
:
{
set
(
timezone
)
{
this
.
form
.
selectedTimezone
=
timezone
.
identifier
;
},
get
()
{
return
this
.
selectedTimezoneData
?.
name
??
''
;
},
},
selectedTimezoneData
()
{
return
this
.
form
.
selectedTimezone
?
this
.
timezones
.
find
(({
identifier
})
=>
identifier
===
this
.
form
.
selectedTimezone
)
:
null
;
},
},
created
()
{
const
date
=
this
.
value
.
startsAt
??
null
;
if
(
date
!==
null
)
{
const
localeDate
=
new
Date
(
stripTimezoneFromISODate
(
date
,
this
.
selectedTimezoneData
?.
offset
),
);
this
.
form
.
startDate
=
localeDate
;
this
.
form
.
startTime
=
date
?
dateToTimeInputValue
(
localeDate
)
:
null
;
}
},
methods
:
{
handleInput
()
{
const
{
startDate
,
startTime
,
cadence
}
=
this
.
form
;
let
startsAt
;
try
{
startsAt
=
dateAndTimeToISOString
(
startDate
,
startTime
,
this
.
selectedTimezoneData
?.
formatted_offset
,
);
}
catch
(
e
)
{
startsAt
=
null
;
}
const
input
=
{
active
:
this
.
form
.
isScheduledScan
,
startsAt
,
cadence
:
toGraphQLCadence
(
cadence
),
timezone
:
this
.
selectedTimezoneData
?.
identifier
??
null
,
};
this
.
$emit
(
'
input
'
,
input
);
},
},
SCAN_CADENCE_OPTIONS
,
};
</
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=
"gl-font-weight-bold gl-mb-3"
>
{{
s__
(
'
OnDemandScans|Start time
'
)
}}
</div>
<timezone-dropdown
v-model=
"timezone"
:timezone-data=
"timezones"
:disabled=
"!form.isScheduledScan"
@
input=
"handleInput"
/>
<div
class=
"gl-display-flex gl-align-items-center"
>
<gl-datepicker
v-model=
"form.startDate"
@
input=
"handleInput"
/>
<span
class=
"gl-px-3"
>
{{
__
(
'
at
'
)
}}
</span>
<input
v-model=
"form.startTime"
type=
"time"
class=
"gl-form-input form-control"
@
input=
"handleInput"
/>
</div>
<dropdown-input
v-model=
"form.cadence"
:label=
"__('Repeats')"
:default-text=
"__('Repeats')"
: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>
</div>
</gl-card>
</
template
>
ee/app/assets/javascripts/on_demand_scans/index.js
View file @
66ecf82c
import
Vue
from
'
vue
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
OnDemandScansForm
from
'
./components/on_demand_scans_form.vue
'
;
import
apolloProvider
from
'
./graphql/provider
'
;
...
...
@@ -19,8 +18,9 @@ export default () => {
newSiteProfilePath
,
newScannerProfilePath
,
helpPagePath
,
dastScan
,
}
=
el
.
dataset
;
const
dastScan
=
el
.
dataset
.
dastScan
?
JSON
.
parse
(
el
.
dataset
.
dastScan
)
:
null
;
const
timezones
=
JSON
.
parse
(
el
.
dataset
.
timezones
);
return
new
Vue
({
el
,
...
...
@@ -34,12 +34,13 @@ export default () => {
newScannerProfilePath
,
newSiteProfilePath
,
dastSiteValidationDocsPath
,
timezones
,
},
render
(
h
)
{
return
h
(
OnDemandScansForm
,
{
props
:
{
defaultBranch
,
dastScan
:
dastScan
?
convertObjectPropsToCamelCase
(
JSON
.
parse
(
dastScan
))
:
null
,
dastScan
,
},
});
},
...
...
ee/app/assets/javascripts/on_demand_scans/settings.js
View file @
66ecf82c
import
dastScannerProfilesQuery
from
'
ee/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql
'
;
import
dastSiteProfilesQuery
from
'
ee/security_configuration/dast_profiles/graphql/dast_site_profiles.query.graphql
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
__
,
s__
}
from
'
~/locale
'
;
export
const
ERROR_RUN_SCAN
=
'
ERROR_RUN_SCAN
'
;
export
const
ERROR_FETCH_SCANNER_PROFILES
=
'
ERROR_FETCH_SCANNER_PROFILES
'
;
...
...
@@ -27,3 +27,22 @@ export const SITE_PROFILES_QUERY = {
fetchQuery
:
dastSiteProfilesQuery
,
fetchError
:
ERROR_FETCH_SITE_PROFILES
,
};
/* eslint-disable @gitlab/require-i18n-strings */
const
DAY_1
=
'
DAY_1
'
;
const
WEEK_1
=
'
WEEK_1
'
;
const
MONTH_1
=
'
MONTH_1
'
;
const
MONTH_3
=
'
MONTH_3
'
;
const
MONTH_6
=
'
MONTH_6
'
;
const
YEAR_1
=
'
YEAR_1
'
;
/* eslint-enable @gitlab/require-i18n-strings */
export
const
SCAN_CADENCE_OPTIONS
=
[
{
value
:
''
,
text
:
__
(
'
Never
'
)
},
{
value
:
DAY_1
,
text
:
__
(
'
Every day
'
)
},
{
value
:
WEEK_1
,
text
:
__
(
'
Every week
'
)
},
{
value
:
MONTH_1
,
text
:
__
(
'
Every month
'
)
},
{
value
:
MONTH_3
,
text
:
__
(
'
Every 3 months
'
)
},
{
value
:
MONTH_6
,
text
:
__
(
'
Every 6 months
'
)
},
{
value
:
YEAR_1
,
text
:
__
(
'
Every year
'
)
},
];
ee/app/controllers/projects/on_demand_scans_controller.rb
View file @
66ecf82c
...
...
@@ -8,6 +8,10 @@ module Projects
before_action
:authorize_read_on_demand_scans!
,
only: :index
before_action
:authorize_create_on_demand_dast_scan!
,
only:
[
:new
,
:edit
]
before_action
do
push_frontend_feature_flag
(
:dast_on_demand_scans_scheduler
,
@project
,
default_enabled: :yaml
)
end
feature_category
:dynamic_application_security_testing
def
index
...
...
ee/app/helpers/projects/on_demand_scans_helper.rb
View file @
66ecf82c
...
...
@@ -12,7 +12,8 @@ module Projects::OnDemandScansHelper
'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'
),
'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
}
end
end
ee/spec/frontend/on_demand_scans/components/on_demand_scans_form_spec.js
View file @
66ecf82c
...
...
@@ -5,6 +5,7 @@ import VueApollo from 'vue-apollo';
import
OnDemandScansForm
from
'
ee/on_demand_scans/components/on_demand_scans_form.vue
'
;
import
ScannerProfileSelector
from
'
ee/on_demand_scans/components/profile_selector/scanner_profile_selector.vue
'
;
import
SiteProfileSelector
from
'
ee/on_demand_scans/components/profile_selector/site_profile_selector.vue
'
;
import
ScanSchedule
from
'
ee/on_demand_scans/components/scan_schedule.vue
'
;
import
dastProfileCreateMutation
from
'
ee/on_demand_scans/graphql/dast_profile_create.mutation.graphql
'
;
import
dastProfileUpdateMutation
from
'
ee/on_demand_scans/graphql/dast_profile_update.mutation.graphql
'
;
import
dastScannerProfilesQuery
from
'
ee/security_configuration/dast_profiles/graphql/dast_scanner_profiles.query.graphql
'
;
...
...
@@ -44,11 +45,12 @@ const dastScan = {
};
useLocalStorageSpy
();
jest
.
mock
(
'
~/lib/utils/url_utility
'
,
()
=>
({
isAbsolute
:
jest
.
requireActual
(
'
~/lib/utils/url_utility
'
).
isAbsolute
,
queryToObject
:
jest
.
requireActual
(
'
~/lib/utils/url_utility
'
).
queryToObject
,
redirectTo
:
jest
.
fn
(),
}));
jest
.
mock
(
'
~/lib/utils/url_utility
'
,
()
=>
{
return
{
...
jest
.
requireActual
(
'
~/lib/utils/url_utility
'
),
redirectTo
:
jest
.
fn
(),
};
});
const
LOCAL_STORAGE_KEY
=
'
group/project/on-demand-scans-new-form
'
;
...
...
@@ -161,11 +163,15 @@ describe('OnDemandScansForm', () => {
newScannerProfilePath
,
newSiteProfilePath
,
dastSiteValidationDocsPath
,
glFeatures
:
{
dastOnDemandScansScheduler
:
true
,
},
},
stubs
:
{
GlFormInput
:
GlFormInputStub
,
RefSelector
:
RefSelectorStub
,
LocalStorageSync
,
ScanSchedule
:
true
,
},
},
{
...
options
,
localVue
,
apolloProvider
},
...
...
@@ -201,6 +207,7 @@ describe('OnDemandScansForm', () => {
createComponent
();
expect
(
wrapper
.
text
()).
toContain
(
'
New on-demand DAST scan
'
);
expect
(
wrapper
.
findComponent
(
ScanSchedule
).
exists
()).
toBe
(
true
);
});
it
(
'
populates the branch input with the default branch
'
,
()
=>
{
...
...
@@ -657,4 +664,16 @@ describe('OnDemandScansForm', () => {
);
});
});
it
(
'
does not render scan schedule when the feature flag is disabled
'
,
()
=>
{
createComponent
({
provide
:
{
glFeatures
:
{
dastOnDemandScansScheduler
:
false
,
},
},
});
expect
(
wrapper
.
findComponent
(
ScanSchedule
).
exists
()).
toBe
(
false
);
});
});
ee/spec/frontend/on_demand_scans/components/scan_schedule_spec.js
0 → 100644
View file @
66ecf82c
import
{
GlDatepicker
,
GlFormCheckbox
,
GlFormGroup
}
from
'
@gitlab/ui
'
;
import
{
merge
}
from
'
lodash
'
;
import
ScanSchedule
from
'
ee/on_demand_scans/components/scan_schedule.vue
'
;
import
{
SCAN_CADENCE_OPTIONS
}
from
'
ee/on_demand_scans/settings
'
;
import
DropdownInput
from
'
ee/security_configuration/components/dropdown_input.vue
'
;
import
{
stubComponent
}
from
'
helpers/stub_component
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
TimezoneDropdown
from
'
~/vue_shared/components/timezone_dropdown.vue
'
;
const
mockTimezones
=
getJSONFixture
(
'
timezones/full.json
'
);
const
timezoneSST
=
mockTimezones
[
2
];
describe
(
'
ScanSchedule
'
,
()
=>
{
let
wrapper
;
// Finders
const
findCheckbox
=
()
=>
wrapper
.
findComponent
(
GlFormCheckbox
);
const
findProfileScheduleFormGroup
=
()
=>
wrapper
.
findByTestId
(
'
profile-schedule-form-group
'
);
const
findTimezoneDropdown
=
()
=>
wrapper
.
findComponent
(
TimezoneDropdown
);
const
findDatepicker
=
()
=>
wrapper
.
findComponent
(
GlDatepicker
);
const
findTimeInput
=
()
=>
wrapper
.
find
(
'
input[type="time"]
'
);
const
findCadenceInput
=
()
=>
wrapper
.
findComponent
(
DropdownInput
);
// Helpers
const
setTimeInputValue
=
(
value
)
=>
{
const
input
=
findTimeInput
();
input
.
element
.
value
=
value
;
input
.
trigger
(
'
input
'
);
return
wrapper
.
vm
.
$nextTick
();
};
const
createComponent
=
(
options
=
{})
=>
{
wrapper
=
shallowMountExtended
(
ScanSchedule
,
merge
(
{
provide
:
{
timezones
:
mockTimezones
,
},
stubs
:
{
GlFormGroup
:
stubComponent
(
GlFormGroup
,
{
props
:
[
'
disabled
'
],
}),
GlFormCheckbox
:
stubComponent
(
GlFormCheckbox
,
{
props
:
[
'
checked
'
],
}),
TimezoneDropdown
:
stubComponent
(
TimezoneDropdown
,
{
props
:
[
'
disabled
'
,
'
timezoneData
'
,
'
value
'
],
}),
},
},
options
,
),
);
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
default state
'
,
()
=>
{
beforeEach
(()
=>
{
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
(
'
initializes timezone dropdown properly
'
,
()
=>
{
const
timezoneDropdown
=
findTimezoneDropdown
();
expect
(
timezoneDropdown
.
props
(
'
timezoneData
'
)).
toEqual
(
mockTimezones
);
expect
(
timezoneDropdown
.
props
(
'
value
'
)).
toBe
(
''
);
});
});
describe
(
'
once schedule is activated
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
();
findCheckbox
().
vm
.
$emit
(
'
input
'
,
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
(
'
emits input payload
'
,
()
=>
{
expect
(
wrapper
.
emitted
().
input
).
toHaveLength
(
1
);
expect
(
wrapper
.
emitted
().
input
[
0
]).
toEqual
([
{
active
:
true
,
cadence
:
SCAN_CADENCE_OPTIONS
[
0
].
value
,
startsAt
:
null
,
timezone
:
null
,
},
]);
});
it
(
'
computes start date when datepicker and time input are changed
'
,
async
()
=>
{
findDatepicker
().
vm
.
$emit
(
'
input
'
,
new
Date
(
'
2021-08-12
'
));
await
setTimeInputValue
(
'
11:00
'
);
expect
(
wrapper
.
emitted
().
input
).
toHaveLength
(
3
);
expect
(
wrapper
.
emitted
().
input
[
2
]).
toEqual
([
{
active
:
true
,
cadence
:
SCAN_CADENCE_OPTIONS
[
0
].
value
,
startsAt
:
'
2021-08-12T11:00:00.000Z
'
,
timezone
:
null
,
},
]);
});
it
(
'
nullyfies start date if date is invalid
'
,
async
()
=>
{
findDatepicker
().
vm
.
$emit
(
'
input
'
,
new
Date
(
'
2021-08-12
'
));
await
setTimeInputValue
(
''
);
expect
(
wrapper
.
emitted
().
input
).
toHaveLength
(
3
);
expect
(
wrapper
.
emitted
().
input
[
2
]).
toEqual
([
{
active
:
true
,
cadence
:
SCAN_CADENCE_OPTIONS
[
0
].
value
,
startsAt
:
null
,
timezone
:
null
,
},
]);
});
it
(
'
emits computed cadence value
'
,
async
()
=>
{
findCadenceInput
().
vm
.
$emit
(
'
input
'
,
SCAN_CADENCE_OPTIONS
[
5
].
value
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
emitted
().
input
[
1
][
0
].
cadence
).
toEqual
({
unit
:
'
MONTH
'
,
duration
:
6
});
});
it
(
'
deactives schedule when checkbox is unchecked
'
,
async
()
=>
{
findCheckbox
().
vm
.
$emit
(
'
input
'
,
false
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
emitted
().
input
).
toHaveLength
(
2
);
expect
(
wrapper
.
emitted
().
input
[
1
]).
toEqual
([
{
active
:
false
,
cadence
:
SCAN_CADENCE_OPTIONS
[
0
].
value
,
startsAt
:
null
,
timezone
:
null
,
},
]);
});
});
describe
(
'
editing a schedule
'
,
()
=>
{
const
startsAt
=
'
2001-09-27T08:45:00.000Z
'
;
beforeEach
(()
=>
{
createComponent
({
propsData
:
{
value
:
{
active
:
true
,
startsAt
,
cadence
:
{
unit
:
'
MONTH
'
,
duration
:
1
},
timezone
:
timezoneSST
.
identifier
,
},
},
});
});
it
(
'
initializes fields with provided values
'
,
()
=>
{
expect
(
findCheckbox
().
props
(
'
checked
'
)).
toBe
(
true
);
expect
(
findDatepicker
().
props
(
'
value
'
)).
toEqual
(
new
Date
(
startsAt
));
expect
(
findTimeInput
().
element
.
value
).
toBe
(
'
08:45
'
);
expect
(
findCadenceInput
().
props
(
'
value
'
)).
toBe
(
SCAN_CADENCE_OPTIONS
[
3
].
value
);
});
});
});
ee/spec/helpers/projects/on_demand_scans_helper_spec.rb
View file @
66ecf82c
...
...
@@ -17,7 +17,8 @@ RSpec.describe Projects::OnDemandScansHelper do
'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'
),
'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'
=>
helper
.
timezone_data
(
format: :full
).
to_json
)
end
end
...
...
locale/gitlab.pot
View file @
66ecf82c
...
...
@@ -13477,6 +13477,12 @@ msgstr ""
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
msgid "Every 3 months"
msgstr ""
msgid "Every 6 months"
msgstr ""
msgid "Every day"
msgstr ""
...
...
@@ -13503,6 +13509,9 @@ msgstr[1] ""
msgid "Every week (%{weekday} at %{time})"
msgstr ""
msgid "Every year"
msgstr ""
msgid "Everyone"
msgstr ""
...
...
@@ -23427,12 +23436,18 @@ msgstr ""
msgid "OnDemandScans|Scanner profile"
msgstr ""
msgid "OnDemandScans|Schedule scan"
msgstr ""
msgid "OnDemandScans|Select one of the existing profiles"
msgstr ""
msgid "OnDemandScans|Site profile"
msgstr ""
msgid "OnDemandScans|Start time"
msgstr ""
msgid "OnDemandScans|Use existing scanner profile"
msgstr ""
...
...
@@ -28080,6 +28095,9 @@ msgstr ""
msgid "Reopens this %{quick_action_target}."
msgstr ""
msgid "Repeats"
msgstr ""
msgid "Replace"
msgstr ""
...
...
spec/frontend/lib/utils/datetime/date_format_utility_spec.js
View file @
66ecf82c
...
...
@@ -69,35 +69,35 @@ describe('date_format_utility.js', () => {
});
});
describe
(
'
dateAndTimeTo
UTC
String
'
,
()
=>
{
describe
(
'
dateAndTimeTo
ISO
String
'
,
()
=>
{
it
(
'
computes the date properly
'
,
()
=>
{
expect
(
utils
.
dateAndTimeTo
UTC
String
(
new
Date
(
'
2021-08-16
'
),
'
10:00
'
)).
toBe
(
expect
(
utils
.
dateAndTimeTo
ISO
String
(
new
Date
(
'
2021-08-16
'
),
'
10:00
'
)).
toBe
(
'
2021-08-16T10:00:00.000Z
'
,
);
});
it
(
'
computes the date properly with an offset
'
,
()
=>
{
expect
(
utils
.
dateAndTimeTo
UTC
String
(
new
Date
(
'
2021-08-16
'
),
'
10:00
'
,
'
-04:00
'
)).
toBe
(
'
2021-08-16T1
4:00:00.000Z
'
,
expect
(
utils
.
dateAndTimeTo
ISO
String
(
new
Date
(
'
2021-08-16
'
),
'
10:00
'
,
'
-04:00
'
)).
toBe
(
'
2021-08-16T1
0:00:00.000-04:00
'
,
);
});
it
(
'
throws if date in invalid
'
,
()
=>
{
expect
(()
=>
utils
.
dateAndTimeTo
UTC
String
(
'
Invalid date
'
,
'
10:00
'
)).
toThrow
(
expect
(()
=>
utils
.
dateAndTimeTo
ISO
String
(
'
Invalid date
'
,
'
10:00
'
)).
toThrow
(
'
Argument should be a Date instance
'
,
);
});
it
(
'
throws if time in invalid
'
,
()
=>
{
expect
(()
=>
utils
.
dateAndTimeTo
UTC
String
(
new
Date
(
'
2021-08-16
'
),
''
)).
toThrow
(
expect
(()
=>
utils
.
dateAndTimeTo
ISO
String
(
new
Date
(
'
2021-08-16
'
),
''
)).
toThrow
(
'
Invalid time provided
'
,
);
});
it
(
'
throws if offset is invalid
'
,
()
=>
{
expect
(()
=>
utils
.
dateAndTimeTo
UTC
String
(
new
Date
(
'
2021-08-16
'
),
'
10:00
'
,
'
not an offset
'
),
).
toThrow
(
'
Invalid time valu
e
'
);
utils
.
dateAndTimeTo
ISO
String
(
new
Date
(
'
2021-08-16
'
),
'
10:00
'
,
'
not an offset
'
),
).
toThrow
(
'
Could not initialize dat
e
'
);
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment