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
68e7f44b
Commit
68e7f44b
authored
May 31, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab master
parents
20dc9f87
99a8861f
Changes
30
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
648 additions
and
128 deletions
+648
-128
app/assets/javascripts/jobs/components/table/constants.js
app/assets/javascripts/jobs/components/table/constants.js
+9
-0
app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
...s/components/table/graphql/queries/get_jobs.query.graphql
+9
-2
app/assets/javascripts/jobs/components/table/jobs_table_app.vue
...sets/javascripts/jobs/components/table/jobs_table_app.vue
+54
-6
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
...merge_request_widget/components/states/ready_to_merge.vue
+2
-6
app/assets/javascripts/vue_merge_request_widget/constants.js
app/assets/javascripts/vue_merge_request_widget/constants.js
+1
-0
app/assets/stylesheets/page_bundles/escalation_policies.scss
app/assets/stylesheets/page_bundles/escalation_policies.scss
+8
-7
app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
...erge_requests/_close_reopen_draft_report_toggle.html.haml
+3
-4
ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue
...n_policies/components/add_edit_escalation_policy_form.vue
+55
-9
ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue
..._policies/components/add_edit_escalation_policy_modal.vue
+72
-4
ee/app/assets/javascripts/escalation_policies/components/escalation_rule.vue
...cripts/escalation_policies/components/escalation_rule.vue
+140
-54
ee/app/assets/javascripts/escalation_policies/constants.js
ee/app/assets/javascripts/escalation_policies/constants.js
+1
-4
ee/app/assets/javascripts/escalation_policies/graphql/mutations/create_escalation_policy.mutation.graphql
...aphql/mutations/create_escalation_policy.mutation.graphql
+18
-0
ee/app/assets/javascripts/escalation_policies/graphql/queries/get_oncall_schedules.query.graphql
...licies/graphql/queries/get_oncall_schedules.query.graphql
+10
-0
ee/app/assets/javascripts/escalation_policies/index.js
ee/app/assets/javascripts/escalation_policies/index.js
+23
-0
ee/app/assets/javascripts/escalation_policies/utils.js
ee/app/assets/javascripts/escalation_policies/utils.js
+10
-0
ee/app/helpers/incident_management/escalation_policy_helper.rb
...p/helpers/incident_management/escalation_policy_helper.rb
+3
-2
ee/app/views/projects/incident_management/escalation_policies/index.html.haml
...s/incident_management/escalation_policies/index.html.haml
+1
-1
ee/lib/ee/sidebars/projects/menus/monitor_menu.rb
ee/lib/ee/sidebars/projects/menus/monitor_menu.rb
+1
-1
ee/spec/features/merge_request/user_merges_immediately_spec.rb
...ec/features/merge_request/user_merges_immediately_spec.rb
+1
-1
ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js
...calation_policies/add_edit_escalation_policy_form_spec.js
+40
-5
ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js
...alation_policies/add_edit_escalation_policy_modal_spec.js
+57
-1
ee/spec/frontend/escalation_policies/escalation_rule_spec.js
ee/spec/frontend/escalation_policies/escalation_rule_spec.js
+20
-1
ee/spec/helpers/incident_management/escalation_policy_helper_spec.rb
...pers/incident_management/escalation_policy_helper_spec.rb
+4
-1
ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb
ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb
+1
-1
ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+3
-3
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/features/merge_request/user_merges_immediately_spec.rb
spec/features/merge_request/user_merges_immediately_spec.rb
+1
-1
spec/features/merge_request/user_sees_merge_widget_spec.rb
spec/features/merge_request/user_sees_merge_widget_spec.rb
+2
-2
spec/frontend/jobs/components/table/job_table_app_spec.js
spec/frontend/jobs/components/table/job_table_app_spec.js
+84
-6
spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
...widget/components/states/mr_widget_ready_to_merge_spec.js
+6
-6
No files found.
app/assets/javascripts/jobs/components/table/constants.js
0 → 100644
View file @
68e7f44b
export
const
GRAPHQL_PAGE_SIZE
=
30
;
export
const
initialPaginationState
=
{
currentPage
:
1
,
prevPageCursor
:
''
,
nextPageCursor
:
''
,
first
:
GRAPHQL_PAGE_SIZE
,
last
:
null
,
};
app/assets/javascripts/jobs/components/table/graphql/queries/get_jobs.query.graphql
View file @
68e7f44b
query
getJobs
(
$fullPath
:
ID
!,
$statuses
:
[
CiJobStatus
!])
{
query
getJobs
(
$fullPath
:
ID
!
$first
:
Int
$last
:
Int
$after
:
String
$before
:
String
$statuses
:
[
CiJobStatus
!]
)
{
project
(
fullPath
:
$fullPath
)
{
jobs
(
first
:
20
,
statuses
:
$statuses
)
{
jobs
(
after
:
$after
,
before
:
$before
,
first
:
$first
,
last
:
$last
,
statuses
:
$statuses
)
{
pageInfo
{
endCursor
hasNextPage
...
...
app/assets/javascripts/jobs/components/table/jobs_table_app.vue
View file @
68e7f44b
<
script
>
import
{
GlAlert
,
GlSkeletonLoader
}
from
'
@gitlab/ui
'
;
import
{
GlAlert
,
Gl
Pagination
,
Gl
SkeletonLoader
}
from
'
@gitlab/ui
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
GRAPHQL_PAGE_SIZE
,
initialPaginationState
}
from
'
./constants
'
;
import
GetJobs
from
'
./graphql/queries/get_jobs.query.graphql
'
;
import
JobsTable
from
'
./jobs_table.vue
'
;
import
JobsTableEmptyState
from
'
./jobs_table_empty_state.vue
'
;
...
...
@@ -12,6 +13,7 @@ export default {
},
components
:
{
GlAlert
,
GlPagination
,
GlSkeletonLoader
,
JobsTable
,
JobsTableEmptyState
,
...
...
@@ -28,10 +30,18 @@ export default {
variables
()
{
return
{
fullPath
:
this
.
fullPath
,
first
:
this
.
pagination
.
first
,
last
:
this
.
pagination
.
last
,
after
:
this
.
pagination
.
nextPageCursor
,
before
:
this
.
pagination
.
prevPageCursor
,
};
},
update
({
project
})
{
return
project
?.
jobs
?.
nodes
||
[];
update
(
data
)
{
const
{
jobs
:
{
nodes
:
list
=
[],
pageInfo
=
{}
}
=
{}
}
=
data
.
project
||
{};
return
{
list
,
pageInfo
,
};
},
error
()
{
this
.
hasError
=
true
;
...
...
@@ -40,10 +50,11 @@ export default {
},
data
()
{
return
{
jobs
:
null
,
jobs
:
{}
,
hasError
:
false
,
isAlertDismissed
:
false
,
scope
:
null
,
pagination
:
initialPaginationState
,
};
},
computed
:
{
...
...
@@ -51,7 +62,16 @@ export default {
return
this
.
hasError
&&
!
this
.
isAlertDismissed
;
},
showEmptyState
()
{
return
this
.
jobs
.
length
===
0
&&
!
this
.
scope
;
return
this
.
jobs
.
list
.
length
===
0
&&
!
this
.
scope
;
},
prevPage
()
{
return
Math
.
max
(
this
.
pagination
.
currentPage
-
1
,
0
);
},
nextPage
()
{
return
this
.
jobs
.
pageInfo
?.
hasNextPage
?
this
.
pagination
.
currentPage
+
1
:
null
;
},
showPaginationControls
()
{
return
Boolean
(
this
.
prevPage
||
this
.
nextPage
)
&&
!
this
.
$apollo
.
loading
;
},
},
methods
:
{
...
...
@@ -60,6 +80,24 @@ export default {
this
.
$apollo
.
queries
.
jobs
.
refetch
({
statuses
:
scope
});
},
handlePageChange
(
page
)
{
const
{
startCursor
,
endCursor
}
=
this
.
jobs
.
pageInfo
;
if
(
page
>
this
.
pagination
.
currentPage
)
{
this
.
pagination
=
{
...
initialPaginationState
,
nextPageCursor
:
endCursor
,
currentPage
:
page
,
};
}
else
{
this
.
pagination
=
{
last
:
GRAPHQL_PAGE_SIZE
,
first
:
null
,
prevPageCursor
:
startCursor
,
currentPage
:
page
,
};
}
},
},
};
</
script
>
...
...
@@ -97,6 +135,16 @@ export default {
<jobs-table-empty-state
v-else-if=
"showEmptyState"
/>
<jobs-table
v-else
:jobs=
"jobs"
/>
<jobs-table
v-else
:jobs=
"jobs.list"
/>
<gl-pagination
v-if=
"showPaginationControls"
:value=
"pagination.currentPage"
:prev-page=
"prevPage"
:next-page=
"nextPage"
align=
"center"
class=
"gl-mt-3"
@
input=
"handlePageChange"
/>
</div>
</
template
>
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
View file @
68e7f44b
...
...
@@ -22,7 +22,7 @@ import { __ } from '~/locale';
import
SmartInterval
from
'
~/smart_interval
'
;
import
glFeatureFlagMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
MergeRequest
from
'
../../../merge_request
'
;
import
{
AUTO_MERGE_STRATEGIES
,
DANGER
,
INFO
,
WARNING
}
from
'
../../constants
'
;
import
{
AUTO_MERGE_STRATEGIES
,
DANGER
,
CONFIRM
,
WARNING
}
from
'
../../constants
'
;
import
eventHub
from
'
../../event_hub
'
;
import
mergeRequestQueryVariablesMixin
from
'
../../mixins/merge_request_query_variables
'
;
import
MergeRequestStore
from
'
../../stores/mr_widget_store
'
;
...
...
@@ -227,11 +227,7 @@ export default {
return
DANGER
;
}
if
(
this
.
status
===
PIPELINE_PENDING_STATE
)
{
return
INFO
;
}
return
PIPELINE_SUCCESS_STATE
;
return
CONFIRM
;
},
iconClass
()
{
if
(
this
.
shouldRenderMergeTrainHelperText
&&
!
this
.
mr
.
preventMerge
)
{
...
...
app/assets/javascripts/vue_merge_request_widget/constants.js
View file @
68e7f44b
...
...
@@ -4,6 +4,7 @@ export const SUCCESS = 'success';
export
const
WARNING
=
'
warning
'
;
export
const
DANGER
=
'
danger
'
;
export
const
INFO
=
'
info
'
;
export
const
CONFIRM
=
'
confirm
'
;
export
const
MWPS_MERGE_STRATEGY
=
'
merge_when_pipeline_succeeds
'
;
export
const
MTWPS_MERGE_STRATEGY
=
'
add_to_merge_train_when_pipeline_succeeds
'
;
...
...
app/assets/stylesheets/page_bundles/escalation_policies.scss
View file @
68e7f44b
...
...
@@ -2,13 +2,14 @@
width
:
640px
;
}
.escalation-policy-rules
{
.rule-control
{
width
:
240px
;
}
.rule-control
{
width
:
240px
;
}
.rule-elapsed-minutes
{
width
:
56px
;
}
.rule-elapsed-minutes
{
width
:
56px
;
}
.rule-close-icon
{
right
:
1rem
;
}
app/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml
View file @
68e7f44b
-
display_issuable_type
=
issuable_display_type
(
@merge_request
)
-
button_action_class
=
@merge_request
.
closed?
?
'btn-default'
:
'btn-warning btn-warning-secondary'
-
button_class
=
"btn gl-button
#{
!
@merge_request
.
closed?
&&
'js-draft-toggle-button'
}
"
-
toggle_class
=
"btn gl-button dropdown-toggle"
.float-left.btn-group.gl-ml-3.gl-display-none.gl-md-display-flex
=
link_to
@merge_request
.
closed?
?
reopen_issuable_path
(
@merge_request
)
:
toggle_draft_merge_request_path
(
@merge_request
),
method: :put
,
class:
"
#{
button_class
}
#{
button_action_class
}
"
do
=
link_to
@merge_request
.
closed?
?
reopen_issuable_path
(
@merge_request
)
:
toggle_draft_merge_request_path
(
@merge_request
),
method: :put
,
class:
"
#{
button_class
}
btn-confirm-secondary
"
do
-
if
@merge_request
.
closed?
=
_
(
'Reopen'
)
=
display_issuable_type
...
...
@@ -12,9 +11,9 @@
=
@merge_request
.
work_in_progress?
?
_
(
'Mark as ready'
)
:
_
(
'Mark as draft'
)
-
if
!
@merge_request
.
closed?
||
!
issuable_author_is_current_user
(
@merge_request
)
=
button_tag
type:
'button'
,
class:
"
#{
toggle_class
}
#{
button_action_class
}
"
,
data:
{
'toggle'
=>
'dropdown'
}
do
=
button_tag
type:
'button'
,
class:
"
#{
toggle_class
}
btn-confirm-secondary btn-icon
"
,
data:
{
'toggle'
=>
'dropdown'
}
do
%span
.gl-sr-only
=
_
(
'Toggle dropdown'
)
=
sprite_icon
"
angle-down"
,
size:
12
=
sprite_icon
"
chevron-down"
,
size:
12
,
css_class:
"gl-button-icon"
%ul
.dropdown-menu.dropdown-menu-right
-
if
@merge_request
.
open?
...
...
ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_form.vue
View file @
68e7f44b
<
script
>
import
{
GlLink
,
GlForm
,
GlFormGroup
,
GlFormInput
}
from
'
@gitlab/ui
'
;
import
{
cloneDeep
}
from
'
lodash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
defaultEscalationRule
}
from
'
../constants
'
;
import
getOncallSchedulesQuery
from
'
../graphql/queries/get_oncall_schedules.query.graphql
'
;
import
EscalationRule
from
'
./escalation_rule.vue
'
;
export
const
i18n
=
{
...
...
@@ -19,6 +21,7 @@ export const i18n = {
},
},
addRule
:
s__
(
'
EscalationPolicies|+ Add an additional rule
'
),
failedLoadingSchedules
:
s__
(
'
EscalationPolicies|Failed to load oncall-schedules
'
),
};
export
default
{
...
...
@@ -30,6 +33,7 @@ export default {
GlFormInput
,
EscalationRule
,
},
inject
:
[
'
projectPath
'
],
props
:
{
form
:
{
type
:
Object
,
...
...
@@ -42,12 +46,50 @@ export default {
},
data
()
{
return
{
rules
:
[
cloneDeep
(
defaultEscalationRule
)],
schedules
:
[],
rules
:
[],
uid
:
0
,
};
},
apollo
:
{
schedules
:
{
query
:
getOncallSchedulesQuery
,
variables
()
{
return
{
projectPath
:
this
.
projectPath
,
};
},
update
(
data
)
{
const
nodes
=
data
.
project
?.
incidentManagementOncallSchedules
?.
nodes
??
[];
return
nodes
;
},
error
(
error
)
{
createFlash
({
message
:
i18n
.
failedLoadingSchedules
,
captureError
:
true
,
error
});
},
},
},
mounted
()
{
this
.
rules
.
push
({
...
cloneDeep
(
defaultEscalationRule
),
key
:
this
.
getUid
()
});
},
methods
:
{
addRule
()
{
this
.
rules
.
push
(
cloneDeep
(
defaultEscalationRule
));
this
.
rules
.
push
({
...
cloneDeep
(
defaultEscalationRule
),
key
:
this
.
getUid
()
});
this
.
emitUpdate
();
},
updateEscalationRules
(
index
,
rule
)
{
this
.
rules
[
index
]
=
rule
;
this
.
emitUpdate
();
},
removeEscalationRule
(
index
)
{
this
.
rules
.
splice
(
index
,
1
);
this
.
emitUpdate
();
},
emitUpdate
()
{
this
.
$emit
(
'
update-escalation-policy-form
'
,
{
field
:
'
rules
'
,
value
:
this
.
rules
});
},
getUid
()
{
this
.
uid
+=
1
;
return
this
.
uid
;
},
},
};
...
...
@@ -92,13 +134,17 @@ export default {
</gl-form-group>
</div>
<gl-form-group
class=
"escalation-policy-rules"
:label=
"$options.i18n.fields.rules.title"
label-size=
"sm"
:state=
"validationState.rules"
>
<escalation-rule
v-for=
"(rule, index) in rules"
:key=
"index"
:rule=
"rule"
/>
<gl-form-group
class=
"gl-mb-3"
:label=
"$options.i18n.fields.rules.title"
label-size=
"sm"
>
<escalation-rule
v-for=
"(rule, index) in rules"
:key=
"rule.key"
:rule=
"rule"
:index=
"index"
:schedules=
"schedules"
:is-valid=
"validationState.rules[index]"
@
update-escalation-rule=
"updateEscalationRules"
@
remove-escalation-rule=
"removeEscalationRule"
/>
</gl-form-group>
<gl-link
@
click=
"addRule"
>
<span>
{{
$options
.
i18n
.
addRule
}}
</span>
...
...
ee/app/assets/javascripts/escalation_policies/components/add_edit_escalation_policy_modal.vue
View file @
68e7f44b
<
script
>
import
{
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlModal
,
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
set
}
from
'
lodash
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
addEscalationPolicyModalId
}
from
'
../constants
'
;
import
{
isNameFieldValid
}
from
'
../utils
'
;
import
createEscalationPolicyMutation
from
'
../graphql/mutations/create_escalation_policy.mutation.graphql
'
;
import
{
isNameFieldValid
,
getRulesValidationState
}
from
'
../utils
'
;
import
AddEditEscalationPolicyForm
from
'
./add_edit_escalation_policy_form.vue
'
;
export
const
i18n
=
{
...
...
@@ -17,8 +18,10 @@ export default {
addEscalationPolicyModalId
,
components
:
{
GlModal
,
GlAlert
,
AddEditEscalationPolicyForm
,
},
inject
:
[
'
projectPath
'
],
props
:
{
escalationPolicy
:
{
type
:
Object
,
...
...
@@ -32,11 +35,13 @@ export default {
form
:
{
name
:
this
.
escalationPolicy
.
name
,
description
:
this
.
escalationPolicy
.
description
,
rules
:
[],
},
validationState
:
{
name
:
true
,
rules
:
true
,
rules
:
[]
,
},
error
:
null
,
};
},
computed
:
{
...
...
@@ -56,7 +61,15 @@ export default {
};
},
isFormValid
()
{
return
Object
.
values
(
this
.
validationState
).
every
(
Boolean
);
return
this
.
validationState
.
name
&&
this
.
validationState
.
rules
.
every
(
Boolean
);
},
serializedData
()
{
const
rules
=
this
.
form
.
rules
.
map
(({
status
,
elapsedTimeSeconds
,
oncallScheduleIid
})
=>
({
status
,
elapsedTimeSeconds
,
oncallScheduleIid
,
}));
return
{
...
this
.
form
,
rules
};
},
},
methods
:
{
...
...
@@ -64,10 +77,59 @@ export default {
set
(
this
.
form
,
field
,
value
);
this
.
validateForm
(
field
);
},
createEscalationPolicy
()
{
this
.
loading
=
true
;
const
{
projectPath
}
=
this
;
this
.
$apollo
.
mutate
({
mutation
:
createEscalationPolicyMutation
,
variables
:
{
input
:
{
projectPath
,
...
this
.
serializedData
,
},
},
})
.
then
(
({
data
:
{
escalationPolicyCreate
:
{
errors
:
[
error
],
},
},
})
=>
{
if
(
error
)
{
throw
error
;
}
this
.
$refs
.
addUpdateEscalationPolicyModal
.
hide
();
this
.
$emit
(
'
policyCreated
'
);
this
.
clearForm
();
},
)
.
catch
((
error
)
=>
{
this
.
error
=
error
;
})
.
finally
(()
=>
{
this
.
loading
=
false
;
});
},
validateForm
(
field
)
{
if
(
field
===
'
name
'
)
{
this
.
validationState
.
name
=
isNameFieldValid
(
this
.
form
.
name
);
}
if
(
field
===
'
rules
'
)
{
this
.
validationState
.
rules
=
getRulesValidationState
(
this
.
form
.
rules
);
}
},
hideErrorAlert
()
{
this
.
error
=
null
;
},
clearForm
()
{
this
.
form
=
{
name
:
''
,
description
:
''
,
rules
:
[],
};
},
},
};
...
...
@@ -75,12 +137,18 @@ export default {
<
template
>
<gl-modal
ref=
"addUpdateEscalationPolicyModal"
class=
"escalation-policy-modal"
:modal-id=
"$options.addEscalationPolicyModalId"
:title=
"$options.i18n.addEscalationPolicy"
:action-primary=
"actionsProps.primary"
:action-cancel=
"actionsProps.cancel"
@
primary.prevent=
"createEscalationPolicy"
@
cancel=
"clearForm"
>
<gl-alert
v-if=
"error"
variant=
"danger"
class=
"gl-mt-n3 gl-mb-3"
@
dismiss=
"hideErrorAlert"
>
{{
error
}}
</gl-alert>
<add-edit-escalation-policy-form
:validation-state=
"validationState"
:form=
"form"
...
...
ee/app/assets/javascripts/escalation_policies/components/escalation_rule.vue
View file @
68e7f44b
<
script
>
import
{
GlFormInput
,
GlDropdown
,
GlDropdownItem
,
GlCard
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
GlFormGroup
,
GlFormInput
,
GlDropdown
,
GlDropdownItem
,
GlCard
,
GlIcon
,
GlSprintf
,
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
ACTIONS
,
ALERT_STATUSES
}
from
'
../constants
'
;
...
...
@@ -9,6 +17,9 @@ export const i18n = {
condition
:
s__
(
'
EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes
'
),
action
:
s__
(
'
EscalationPolicies|THEN %{doAction} %{schedule}
'
),
selectSchedule
:
s__
(
'
EscalationPolicies|Select schedule
'
),
validationMsg
:
s__
(
'
EscalationPolicies|A schedule is required for adding an escalation policy.
'
,
),
},
},
};
...
...
@@ -18,10 +29,12 @@ export default {
ALERT_STATUSES
,
ACTIONS
,
components
:
{
GlFormGroup
,
GlFormInput
,
GlDropdown
,
GlDropdownItem
,
GlCard
,
GlIcon
,
GlSprintf
,
},
props
:
{
...
...
@@ -34,65 +47,138 @@ export default {
required
:
false
,
default
:
()
=>
[],
},
index
:
{
type
:
Number
,
required
:
true
,
},
isValid
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
data
()
{
const
{
status
,
elapsedTimeSeconds
,
action
,
oncallScheduleIid
}
=
this
.
rule
;
return
{
status
,
elapsedTimeSeconds
,
action
,
oncallScheduleIid
,
};
},
computed
:
{
scheduleDropdownTitle
()
{
return
this
.
oncallScheduleIid
?
this
.
schedules
.
find
(({
iid
})
=>
iid
===
this
.
oncallScheduleIid
)?.
name
:
i18n
.
fields
.
rules
.
selectSchedule
;
},
},
methods
:
{
setOncallSchedule
({
iid
})
{
this
.
oncallScheduleIid
=
this
.
oncallScheduleIid
===
iid
?
null
:
iid
;
this
.
emitUpdate
();
},
setStatus
(
status
)
{
this
.
status
=
status
;
this
.
emitUpdate
();
},
emitUpdate
()
{
this
.
$emit
(
'
update-escalation-rule
'
,
this
.
index
,
{
oncallScheduleIid
:
parseInt
(
this
.
oncallScheduleIid
,
10
),
action
:
this
.
action
,
status
:
this
.
status
,
elapsedTimeSeconds
:
parseInt
(
this
.
elapsedTimeSeconds
,
10
),
});
},
},
};
</
script
>
<
template
>
<gl-card
class=
"gl-border-gray-400 gl-bg-gray-10 gl-mb-3"
>
<div
class=
"gl-display-flex gl-align-items-center"
>
<gl-sprintf
:message=
"$options.i18n.fields.rules.condition"
>
<template
#alertStatus
>
<gl-dropdown
class=
"rule-control gl-mx-3"
:text=
"$options.ALERT_STATUSES[rule.status]"
data-testid=
"alert-status-dropdown"
>
<gl-dropdown-item
v-for=
"(label, status) in $options.ALERT_STATUSES"
:key=
"status"
:is-checked=
"rule.status === status"
is-check-item
<gl-card
class=
"gl-border-gray-400 gl-bg-gray-10 gl-mb-3 gl-relative"
>
<gl-icon
v-if=
"index !== 0"
name=
"close"
class=
"gl-absolute rule-close-icon"
@
click=
"$emit('remove-escalation-rule', index)"
/>
<gl-form-group
:invalid-feedback=
"$options.i18n.fields.rules.validationMsg"
:state=
"isValid"
class=
"gl-mb-0"
>
<div
class=
"gl-display-flex gl-align-items-center"
>
<gl-sprintf
:message=
"$options.i18n.fields.rules.condition"
>
<template
#alertStatus
>
<gl-dropdown
class=
"rule-control gl-mx-3"
:text=
"$options.ALERT_STATUSES[status]"
data-testid=
"alert-status-dropdown"
>
<gl-dropdown-item
v-for=
"(label, alertStatus) in $options.ALERT_STATUSES"
:key=
"alertStatus"
:is-checked=
"status === alertStatus"
is-check-item
@
click=
"setStatus(alertStatus)"
>
{{
label
}}
</gl-dropdown-item>
</gl-dropdown>
</
template
>
<
template
#minutes
>
<gl-form-input
v-model=
"elapsedTimeSeconds"
class=
"gl-mx-3 gl-inset-border-1-gray-200! rule-elapsed-minutes"
type=
"number"
min=
"0"
@
change=
"emitUpdate"
/>
</
template
>
</gl-sprintf>
</div>
<div
class=
"gl-display-flex gl-align-items-center gl-mt-3"
>
<gl-sprintf
:message=
"$options.i18n.fields.rules.action"
>
<
template
#doAction
>
<gl-dropdown
class=
"rule-control gl-mx-3"
:text=
"$options.ACTIONS[rule.action]"
data-testid=
"action-dropdown"
>
{{
label
}}
</gl-dropdown-item>
</gl-dropdown>
</
template
>
<
template
#minutes
>
<gl-form-input
class=
"gl-mx-3 rule-elapsed-minutes"
:value=
"0"
/>
</
template
>
</gl-sprintf>
</div>
<div
class=
"gl-display-flex gl-align-items-center gl-mt-3"
>
<gl-sprintf
:message=
"$options.i18n.fields.rules.action"
>
<
template
#doAction
>
<gl-dropdown
class=
"rule-control gl-mx-3"
:text=
"$options.ACTIONS[rule.action]"
data-testid=
"action-dropdown"
>
<gl-dropdown-item
v-for=
"(label, action) in $options.ACTIONS"
:key=
"action"
:is-checked=
"rule.action === action"
is-check-item
<gl-dropdown-item
v-for=
"(label, ruleAction) in $options.ACTIONS"
:key=
"ruleAction"
:is-checked=
"rule.action === ruleAction"
is-check-item
>
{{
label
}}
</gl-dropdown-item>
</gl-dropdown>
</
template
>
<
template
#schedule
>
<gl-dropdown
class=
"rule-control"
:text=
"scheduleDropdownTitle"
data-testid=
"schedules-dropdown"
>
{{
label
}}
</gl-dropdown-item>
</gl-dropdown>
</
template
>
<
template
#schedule
>
<gl-dropdown
class=
"rule-control gl-mx-3"
:text=
"$options.i18n.fields.rules.selectSchedule"
data-testid=
"schedules-dropdown"
>
<gl-dropdown-item
v-for=
"schedule in schedules"
:key=
"schedule.id"
is-check-item
>
{{
schedule
.
name
}}
</gl-dropdown-item>
</gl-dropdown>
</
template
>
</gl-sprintf>
</div>
<template
#button-text
>
<span
:class=
"
{ 'gl-text-gray-400': !oncallScheduleIid }">
{{
scheduleDropdownTitle
}}
</span>
</
template
>
<gl-dropdown-item
v-for=
"schedule in schedules"
:key=
"schedule.iid"
:is-checked=
"schedule.iid === oncallScheduleIid"
is-check-item
@
click=
"setOncallSchedule(schedule)"
>
{{ schedule.name }}
</gl-dropdown-item>
</gl-dropdown>
</template>
</gl-sprintf>
</div>
</gl-form-group>
</gl-card>
</template>
ee/app/assets/javascripts/escalation_policies/constants.js
View file @
68e7f44b
...
...
@@ -13,10 +13,7 @@ export const defaultEscalationRule = {
status
:
'
ACKNOWLEDGED
'
,
elapsedTimeSeconds
:
0
,
action
:
'
EMAIL_ONCALL_SCHEDULE_USER
'
,
oncallSchedule
:
{
iid
:
null
,
name
:
null
,
},
oncallScheduleIid
:
null
,
};
export
const
addEscalationPolicyModalId
=
'
addEscalationPolicyModal
'
;
ee/app/assets/javascripts/escalation_policies/graphql/mutations/create_escalation_policy.mutation.graphql
0 → 100644
View file @
68e7f44b
mutation
escalationPolicyCreate
(
$input
:
EscalationPolicyCreateInput
!)
{
escalationPolicyCreate
(
input
:
$input
)
{
escalationPolicy
{
id
name
description
rules
{
status
elapsedTimeSeconds
oncallSchedule
{
iid
name
}
}
}
errors
}
}
ee/app/assets/javascripts/escalation_policies/graphql/queries/get_oncall_schedules.query.graphql
0 → 100644
View file @
68e7f44b
query
getOncallSchedules
(
$projectPath
:
ID
!)
{
project
(
fullPath
:
$projectPath
)
{
incidentManagementOncallSchedules
{
nodes
{
iid
name
}
}
}
}
ee/app/assets/javascripts/escalation_policies/index.js
View file @
68e7f44b
import
{
defaultDataIdFromObject
}
from
'
apollo-cache-inmemory
'
;
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
EscalationPoliciesWrapper
from
'
./components/escalation_policies_wrapper.vue
'
;
Vue
.
use
(
VueApollo
);
const
apolloProvider
=
new
VueApollo
({
defaultClient
:
createDefaultClient
(
{},
{
cacheConfig
:
{
dataIdFromObject
:
(
object
)
=>
{
// eslint-disable-next-line no-underscore-dangle
if
(
object
.
__typename
===
'
IncidentManagementOncallSchedule
'
)
{
return
object
.
iid
;
}
return
defaultDataIdFromObject
(
object
);
},
},
},
),
});
export
default
()
=>
{
const
el
=
document
.
querySelector
(
'
.js-escalation-policies
'
);
...
...
@@ -10,6 +32,7 @@ export default () => {
return
new
Vue
({
el
,
apolloProvider
,
provide
:
{
projectPath
,
emptyEscalationPoliciesSvgPath
,
...
...
ee/app/assets/javascripts/escalation_policies/utils.js
View file @
68e7f44b
...
...
@@ -7,3 +7,13 @@
export
const
isNameFieldValid
=
(
name
)
=>
{
return
Boolean
(
name
?.
length
);
};
/**
* Returns an array of booleans - validation state for each rule
* @param {Array} rules
*
* @returns {Array}
*/
export
const
getRulesValidationState
=
(
rules
)
=>
{
return
rules
.
map
((
rule
)
=>
Boolean
(
rule
.
oncallScheduleIid
));
};
ee/app/helpers/incident_management/escalation_policy_helper.rb
View file @
68e7f44b
...
...
@@ -2,9 +2,10 @@
module
IncidentManagement
module
EscalationPolicyHelper
def
escalation_policy_data
def
escalation_policy_data
(
project
)
{
'empty_escalation_policies_svg_path'
=>
image_path
(
'illustrations/empty-state/empty-escalation.svg'
)
'project-path'
=>
project
.
full_path
,
'empty_escalation_policies_svg_path'
=>
image_path
(
'illustrations/empty-state/empty-escalation.svg'
)
}
end
end
...
...
ee/app/views/projects/incident_management/escalation_policies/index.html.haml
View file @
68e7f44b
-
page_title
_
(
'Escalation policies'
)
-
add_page_specific_style
'page_bundles/escalation_policies'
.js-escalation-policies
{
data:
escalation_policy_data
}
.js-escalation-policies
{
data:
escalation_policy_data
(
@project
)
}
ee/lib/ee/sidebars/projects/menus/monitor_menu.rb
View file @
68e7f44b
...
...
@@ -38,7 +38,7 @@ module EE
end
::
Sidebars
::
MenuItem
.
new
(
title:
_
(
'Escalation
p
olicies'
),
title:
_
(
'Escalation
P
olicies'
),
link:
project_incident_management_escalation_policies_path
(
context
.
project
),
active_routes:
{
controller: :escalation_policies
},
item_id: :escalation_policies
...
...
ee/spec/features/merge_request/user_merges_immediately_spec.rb
View file @
68e7f44b
...
...
@@ -35,7 +35,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do
context
'when the merge request is on the merge train'
do
def
merge_button
find
(
'.mr-widget-body .accept-merge-request.btn-
info
'
)
find
(
'.mr-widget-body .accept-merge-request.btn-
confirm
'
)
end
def
open_warning_dialog
...
...
ee/spec/frontend/escalation_policies/add_edit_escalation_policy_form_spec.js
View file @
68e7f44b
...
...
@@ -6,7 +6,6 @@ import AddEscalationPolicyForm, {
import
EscalationRule
from
'
ee/escalation_policies/components/escalation_rule.vue
'
;
import
{
defaultEscalationRule
}
from
'
ee/escalation_policies/constants
'
;
import
{
extendedWrapper
}
from
'
helpers/vue_test_utils_helper
'
;
import
mockPolicy
from
'
./mocks/mockPolicy.json
'
;
describe
(
'
AddEscalationPolicyForm
'
,
()
=>
{
...
...
@@ -23,6 +22,7 @@ describe('AddEscalationPolicyForm', () => {
},
validationState
:
{
name
:
true
,
rules
:
[],
},
...
props
,
},
...
...
@@ -46,13 +46,15 @@ describe('AddEscalationPolicyForm', () => {
const
findAddRuleLink
=
()
=>
wrapper
.
findComponent
(
GlLink
);
describe
(
'
Escalation policy form validation
'
,
()
=>
{
it
(
'
should s
how feedback for an invalid name input validation state
'
,
async
()
=>
{
it
(
'
should s
et correct validation state for validated controls
'
,
async
()
=>
{
createComponent
({
props
:
{
validationState
:
{
name
:
false
},
validationState
:
{
name
:
false
,
rules
:
[
false
]
},
},
});
expect
(
findPolicyName
().
attributes
(
'
state
'
)).
toBeFalsy
();
await
wrapper
.
vm
.
$nextTick
();
expect
(
findPolicyName
().
attributes
(
'
state
'
)).
toBeUndefined
();
expect
(
findRules
().
at
(
0
).
attributes
(
'
is-valid
'
)).
toBeUndefined
();
});
});
...
...
@@ -72,7 +74,40 @@ describe('AddEscalationPolicyForm', () => {
await
wrapper
.
vm
.
$nextTick
();
const
rules
=
findRules
();
expect
(
rules
.
length
).
toBe
(
2
);
expect
(
rules
.
at
(
1
).
props
(
'
rule
'
)).
toEqual
(
defaultEscalationRule
);
expect
(
rules
.
at
(
1
).
props
(
'
rule
'
)).
toMatchObject
(
defaultEscalationRule
);
});
it
(
'
should emit updates when rule is added
'
,
async
()
=>
{
findAddRuleLink
().
vm
.
$emit
(
'
click
'
);
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
emitted
(
'
update-escalation-policy-form
'
)[
0
]).
toMatchObject
([
{
field
:
'
rules
'
,
value
:
[
expect
.
objectContaining
(
defaultEscalationRule
),
expect
.
objectContaining
(
defaultEscalationRule
),
],
},
]);
});
it
(
'
on rule update emitted should update rules array and emit updates up
'
,
()
=>
{
const
updatedRule
=
{
status
:
'
TRIGGERED
'
,
elapsedTimeSeconds
:
30
,
oncallScheduleIid
:
2
,
};
findRules
().
at
(
0
).
vm
.
$emit
(
'
update-escalation-rule
'
,
0
,
updatedRule
);
expect
(
wrapper
.
emitted
(
'
update-escalation-policy-form
'
)[
0
]).
toEqual
([
{
field
:
'
rules
'
,
value
:
[
updatedRule
]
},
]);
});
it
(
'
on rule removal emitted should update rules array and emit updates up
'
,
()
=>
{
findRules
().
at
(
0
).
vm
.
$emit
(
'
remove-escalation-rule
'
,
0
);
expect
(
wrapper
.
emitted
(
'
update-escalation-policy-form
'
)[
0
]).
toEqual
([
{
field
:
'
rules
'
,
value
:
[]
},
]);
});
});
});
ee/spec/frontend/escalation_policies/add_edit_escalation_policy_modal_spec.js
View file @
68e7f44b
import
{
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlModal
,
GlAlert
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
AddEscalationPolicyForm
from
'
ee/escalation_policies/components/add_edit_escalation_policy_form.vue
'
;
import
AddEscalationPolicyModal
,
{
i18n
,
}
from
'
ee/escalation_policies/components/add_edit_escalation_policy_modal.vue
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
mockPolicy
from
'
./mocks/mockPolicy.json
'
;
describe
(
'
AddEscalationPolicyModal
'
,
()
=>
{
let
wrapper
;
const
projectPath
=
'
group/project
'
;
const
mockHideModal
=
jest
.
fn
();
const
mutate
=
jest
.
fn
();
const
createComponent
=
({
escalationPolicy
,
data
}
=
{})
=>
{
wrapper
=
shallowMount
(
AddEscalationPolicyModal
,
{
data
()
{
return
{
form
:
mockPolicy
,
...
data
,
};
},
...
...
@@ -22,7 +27,14 @@ describe('AddEscalationPolicyModal', () => {
provide
:
{
projectPath
,
},
mocks
:
{
$apollo
:
{
mutate
,
},
},
});
wrapper
.
vm
.
$refs
.
addUpdateEscalationPolicyModal
.
hide
=
mockHideModal
;
};
beforeEach
(()
=>
{
createComponent
();
...
...
@@ -34,6 +46,7 @@ describe('AddEscalationPolicyModal', () => {
const
findModal
=
()
=>
wrapper
.
findComponent
(
GlModal
);
const
findEscalationPolicyForm
=
()
=>
wrapper
.
findComponent
(
AddEscalationPolicyForm
);
const
findAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
describe
(
'
renders create modal with the correct information
'
,
()
=>
{
it
(
'
renders modal title
'
,
()
=>
{
...
...
@@ -43,6 +56,49 @@ describe('AddEscalationPolicyModal', () => {
it
(
'
renders the form inside the modal
'
,
()
=>
{
expect
(
findEscalationPolicyForm
().
exists
()).
toBe
(
true
);
});
it
(
'
makes a request with form data to create an escalation policy
'
,
()
=>
{
mutate
.
mockResolvedValueOnce
({});
findModal
().
vm
.
$emit
(
'
primary
'
,
{
preventDefault
:
jest
.
fn
()
});
expect
(
mutate
).
toHaveBeenCalledWith
(
expect
.
objectContaining
({
variables
:
{
input
:
{
projectPath
,
...
mockPolicy
,
},
},
}),
);
});
it
(
'
hides the modal on successful policy creation
'
,
async
()
=>
{
mutate
.
mockResolvedValueOnce
({
data
:
{
escalationPolicyCreate
:
{
errors
:
[]
}
}
});
findModal
().
vm
.
$emit
(
'
primary
'
,
{
preventDefault
:
jest
.
fn
()
});
await
waitForPromises
();
expect
(
mockHideModal
).
toHaveBeenCalled
();
});
it
(
"
doesn't hide a modal and shows error alert on creation failure
"
,
async
()
=>
{
const
error
=
'
some error
'
;
mutate
.
mockResolvedValueOnce
({
data
:
{
escalationPolicyCreate
:
{
errors
:
[
error
]
}
}
});
findModal
().
vm
.
$emit
(
'
primary
'
,
{
preventDefault
:
jest
.
fn
()
});
await
waitForPromises
();
const
alert
=
findAlert
();
expect
(
mockHideModal
).
not
.
toHaveBeenCalled
();
expect
(
alert
.
exists
()).
toBe
(
true
);
expect
(
alert
.
text
()).
toContain
(
error
);
});
it
(
'
clears the form on modal close
'
,
()
=>
{
expect
(
wrapper
.
vm
.
form
).
toEqual
(
mockPolicy
);
findModal
().
vm
.
$emit
(
'
cancel
'
,
{
preventDefault
:
jest
.
fn
()
});
expect
(
wrapper
.
vm
.
form
).
toEqual
({
name
:
''
,
description
:
''
,
rules
:
[],
});
});
});
describe
(
'
modal buttons
'
,
()
=>
{
...
...
ee/spec/frontend/escalation_policies/escalation_rule_spec.js
View file @
68e7f44b
import
{
GlDropdownItem
,
GlSprintf
}
from
'
@gitlab/ui
'
;
import
{
GlDropdownItem
,
Gl
FormGroup
,
Gl
Sprintf
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
cloneDeep
}
from
'
lodash
'
;
import
EscalationRule
from
'
ee/escalation_policies/components/escalation_rule.vue
'
;
...
...
@@ -19,6 +19,8 @@ describe('EscalationRule', () => {
propsData
:
{
rule
:
cloneDeep
(
defaultEscalationRule
),
schedules
:
mockSchedules
,
index
:
0
,
isValid
:
false
,
...
props
,
},
stubs
:
{
...
...
@@ -45,6 +47,8 @@ describe('EscalationRule', () => {
const
findSchedulesDropdown
=
()
=>
wrapper
.
findByTestId
(
'
schedules-dropdown
'
);
const
findSchedulesDropdownOptions
=
()
=>
findSchedulesDropdown
().
findAll
(
GlDropdownItem
);
const
findFormGroup
=
()
=>
wrapper
.
findComponent
(
GlFormGroup
);
describe
(
'
Status dropdown
'
,
()
=>
{
it
(
'
should have correct alert status options
'
,
()
=>
{
expect
(
findStatusDropdownOptions
().
wrappers
.
map
((
w
)
=>
w
.
text
())).
toStrictEqual
(
...
...
@@ -76,4 +80,19 @@ describe('EscalationRule', () => {
);
});
});
describe
(
'
Validation
'
,
()
=>
{
it
.
each
`
isValid | state
${
true
}
|
${
'
true
'
}
${
false
}
|
${
undefined
}
`
(
'
when $isValid sets from group state to $state
'
,
({
isValid
,
state
})
=>
{
createComponent
({
props
:
{
isValid
,
},
});
expect
(
findFormGroup
().
attributes
(
'
state
'
)).
toBe
(
state
);
});
});
});
ee/spec/helpers/incident_management/escalation_policy_helper_spec.rb
View file @
68e7f44b
...
...
@@ -3,11 +3,14 @@
require
'spec_helper'
RSpec
.
describe
IncidentManagement
::
EscalationPolicyHelper
do
let_it_be
(
:project
)
{
create
(
:project
)
}
describe
'#escalation_policy_data'
do
subject
(
:data
)
{
helper
.
escalation_policy_data
}
subject
(
:data
)
{
helper
.
escalation_policy_data
(
project
)
}
it
'returns scalation policies data'
do
is_expected
.
to
eq
(
'project-path'
=>
project
.
full_path
,
'empty_escalation_policies_svg_path'
=>
helper
.
image_path
(
'illustrations/empty-state/empty-escalation.svg'
)
)
end
...
...
ee/spec/lib/ee/sidebars/projects/menus/monitor_menu_spec.rb
View file @
68e7f44b
...
...
@@ -26,7 +26,7 @@ RSpec.describe Sidebars::Projects::Menus::MonitorMenu do
end
end
describe
'Escalation
p
olicies'
do
describe
'Escalation
P
olicies'
do
let
(
:item_id
)
{
:escalation_policies
}
before
do
...
...
ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
View file @
68e7f44b
...
...
@@ -275,7 +275,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
end
end
describe
'Escalation
p
olicies'
do
describe
'Escalation
P
olicies'
do
before
do
allow
(
view
).
to
receive
(
:current_user
).
and_return
(
user
)
stub_licensed_features
(
oncall_schedules:
true
,
escalation_policies:
true
)
...
...
@@ -284,7 +284,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
it
'has a link to the escalation policies page'
do
render
expect
(
rendered
).
to
have_link
(
'Escalation
p
olicies'
,
href:
project_incident_management_escalation_policies_path
(
project
))
expect
(
rendered
).
to
have_link
(
'Escalation
P
olicies'
,
href:
project_incident_management_escalation_policies_path
(
project
))
end
describe
'when the user does not have access'
do
...
...
@@ -293,7 +293,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
it
'does not have a link to the escalation policies page'
do
render
expect
(
rendered
).
not_to
have_link
(
'Escalation
p
olicies'
)
expect
(
rendered
).
not_to
have_link
(
'Escalation
P
olicies'
)
end
end
end
...
...
locale/gitlab.pot
View file @
68e7f44b
...
...
@@ -13072,6 +13072,9 @@ msgstr ""
msgid "Errors:"
msgstr ""
msgid "Escalation Policies"
msgstr ""
msgid "Escalation policies"
msgstr ""
...
...
@@ -13081,6 +13084,9 @@ msgstr ""
msgid "EscalationPolicies|+ Add an additional rule"
msgstr ""
msgid "EscalationPolicies|A schedule is required for adding an escalation policy."
msgstr ""
msgid "EscalationPolicies|Add an escalation policy"
msgstr ""
...
...
@@ -13099,6 +13105,9 @@ msgstr ""
msgid "EscalationPolicies|Escalation rules"
msgstr ""
msgid "EscalationPolicies|Failed to load oncall-schedules"
msgstr ""
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes"
msgstr ""
...
...
spec/features/merge_request/user_merges_immediately_spec.rb
View file @
68e7f44b
...
...
@@ -36,7 +36,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do
Sidekiq
::
Testing
.
fake!
do
click_button
'Merge immediately'
expect
(
find
(
'.accept-merge-request.btn-
info
'
)).
to
have_content
(
'Merge in progress'
)
expect
(
find
(
'.accept-merge-request.btn-
confirm
'
)).
to
have_content
(
'Merge in progress'
)
wait_for_requests
end
...
...
spec/features/merge_request/user_sees_merge_widget_spec.rb
View file @
68e7f44b
...
...
@@ -274,10 +274,10 @@ RSpec.describe 'Merge request > User sees merge widget', :js do
visit
project_merge_request_path
(
project
,
merge_request
)
end
it
'has
info
button when MWBS button'
do
it
'has
confirm
button when MWBS button'
do
# Wait for the `ci_status` and `merge_check` requests
wait_for_requests
expect
(
page
).
to
have_selector
(
'.accept-merge-request.btn-
info
'
)
expect
(
page
).
to
have_selector
(
'.accept-merge-request.btn-
confirm
'
)
end
end
...
...
spec/frontend/jobs/components/table/job_table_app_spec.js
View file @
68e7f44b
import
{
GlSkeletonLoader
,
GlAlert
,
GlEmptyState
}
from
'
@gitlab/ui
'
;
import
{
GlSkeletonLoader
,
GlAlert
,
GlEmptyState
,
GlPagination
}
from
'
@gitlab/ui
'
;
import
{
createLocalVue
,
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
...
...
@@ -25,6 +25,10 @@ describe('Job table app', () => {
const
findTabs
=
()
=>
wrapper
.
findComponent
(
JobsTableTabs
);
const
findAlert
=
()
=>
wrapper
.
findComponent
(
GlAlert
);
const
findEmptyState
=
()
=>
wrapper
.
findComponent
(
GlEmptyState
);
const
findPagination
=
()
=>
wrapper
.
findComponent
(
GlPagination
);
const
findPrevious
=
()
=>
findPagination
().
findAll
(
'
.page-item
'
).
at
(
0
);
const
findNext
=
()
=>
findPagination
().
findAll
(
'
.page-item
'
).
at
(
1
);
const
createMockApolloProvider
=
(
handler
)
=>
{
const
requestHandlers
=
[[
getJobsQuery
,
handler
]];
...
...
@@ -32,8 +36,17 @@ describe('Job table app', () => {
return
createMockApollo
(
requestHandlers
);
};
const
createComponent
=
(
handler
=
successHandler
,
mountFn
=
shallowMount
)
=>
{
const
createComponent
=
({
handler
=
successHandler
,
mountFn
=
shallowMount
,
data
=
{},
}
=
{})
=>
{
wrapper
=
mountFn
(
JobsTableApp
,
{
data
()
{
return
{
...
data
,
};
},
provide
:
{
projectPath
,
},
...
...
@@ -52,6 +65,7 @@ describe('Job table app', () => {
expect
(
findSkeletonLoader
().
exists
()).
toBe
(
true
);
expect
(
findTable
().
exists
()).
toBe
(
false
);
expect
(
findPagination
().
exists
()).
toBe
(
false
);
});
});
...
...
@@ -65,9 +79,10 @@ describe('Job table app', () => {
it
(
'
should display the jobs table with data
'
,
()
=>
{
expect
(
findTable
().
exists
()).
toBe
(
true
);
expect
(
findSkeletonLoader
().
exists
()).
toBe
(
false
);
expect
(
findPagination
().
exists
()).
toBe
(
true
);
});
it
(
'
should re
tfe
ch jobs query on fetchJobsByStatus event
'
,
async
()
=>
{
it
(
'
should re
fet
ch jobs query on fetchJobsByStatus event
'
,
async
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
.
$apollo
.
queries
.
jobs
,
'
refetch
'
).
mockImplementation
(
jest
.
fn
());
expect
(
wrapper
.
vm
.
$apollo
.
queries
.
jobs
.
refetch
).
toHaveBeenCalledTimes
(
0
);
...
...
@@ -78,9 +93,72 @@ describe('Job table app', () => {
});
});
describe
(
'
pagination
'
,
()
=>
{
it
(
'
should disable the next page button on the last page
'
,
async
()
=>
{
createComponent
({
handler
:
successHandler
,
mountFn
:
mount
,
data
:
{
pagination
:
{
currentPage
:
3
,
},
jobs
:
{
pageInfo
:
{
hasPreviousPage
:
true
,
startCursor
:
'
abc
'
,
endCursor
:
'
bcd
'
,
},
},
},
});
await
wrapper
.
vm
.
$nextTick
();
wrapper
.
setData
({
jobs
:
{
pageInfo
:
{
hasNextPage
:
false
,
},
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
findPrevious
().
exists
()).
toBe
(
true
);
expect
(
findNext
().
exists
()).
toBe
(
true
);
expect
(
findNext
().
classes
(
'
disabled
'
)).
toBe
(
true
);
});
it
(
'
should disable the previous page button on the first page
'
,
async
()
=>
{
createComponent
({
handler
:
successHandler
,
mountFn
:
mount
,
data
:
{
pagination
:
{
currentPage
:
1
,
},
jobs
:
{
pageInfo
:
{
hasNextPage
:
true
,
hasPreviousPage
:
false
,
startCursor
:
'
abc
'
,
endCursor
:
'
bcd
'
,
},
},
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
findPrevious
().
exists
()).
toBe
(
true
);
expect
(
findPrevious
().
classes
(
'
disabled
'
)).
toBe
(
true
);
expect
(
findNext
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
error state
'
,
()
=>
{
it
(
'
should show an alert if there is an error fetching the data
'
,
async
()
=>
{
createComponent
(
failedHandler
);
createComponent
(
{
handler
:
failedHandler
}
);
await
waitForPromises
();
...
...
@@ -90,7 +168,7 @@ describe('Job table app', () => {
describe
(
'
empty state
'
,
()
=>
{
it
(
'
should display empty state if there are no jobs and tab scope is null
'
,
async
()
=>
{
createComponent
(
emptyHandler
,
mount
);
createComponent
(
{
handler
:
emptyHandler
,
mountFn
:
mount
}
);
await
waitForPromises
();
...
...
@@ -99,7 +177,7 @@ describe('Job table app', () => {
});
it
(
'
should not display empty state if there are jobs and tab scope is not null
'
,
async
()
=>
{
createComponent
(
successHandler
,
mount
);
createComponent
(
{
handler
:
successHandler
,
mountFn
:
mount
}
);
await
waitForPromises
();
...
...
spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
View file @
68e7f44b
...
...
@@ -123,26 +123,26 @@ describe('ReadyToMerge', () => {
});
describe
(
'
mergeButtonVariant
'
,
()
=>
{
it
(
'
defaults to
success
class
'
,
()
=>
{
it
(
'
defaults to
confirm
class
'
,
()
=>
{
createComponent
({
mr
:
{
availableAutoMergeStrategies
:
[]
},
});
expect
(
wrapper
.
vm
.
mergeButtonVariant
).
toEqual
(
'
success
'
);
expect
(
wrapper
.
vm
.
mergeButtonVariant
).
toEqual
(
'
confirm
'
);
});
it
(
'
returns
success
class for success status
'
,
()
=>
{
it
(
'
returns
confirm
class for success status
'
,
()
=>
{
createComponent
({
mr
:
{
availableAutoMergeStrategies
:
[],
pipeline
:
true
},
});
expect
(
wrapper
.
vm
.
mergeButtonVariant
).
toEqual
(
'
success
'
);
expect
(
wrapper
.
vm
.
mergeButtonVariant
).
toEqual
(
'
confirm
'
);
});
it
(
'
returns
info
class for pending status
'
,
()
=>
{
it
(
'
returns
confirm
class for pending status
'
,
()
=>
{
createComponent
();
expect
(
wrapper
.
vm
.
mergeButtonVariant
).
toEqual
(
'
info
'
);
expect
(
wrapper
.
vm
.
mergeButtonVariant
).
toEqual
(
'
confirm
'
);
});
it
(
'
returns danger class for failed status
'
,
()
=>
{
...
...
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