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
ce5c8c67
Commit
ce5c8c67
authored
Jul 06, 2020
by
Jan Provaznik
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'bulk-edit-health-status' into 'master'
Bulk edit health status See merge request gitlab-org/gitlab!33065
parents
e99e3f2d
6143f05f
Changes
16
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
469 additions
and
68 deletions
+469
-68
app/assets/javascripts/issuable_bulk_update_actions.js
app/assets/javascripts/issuable_bulk_update_actions.js
+1
-0
app/assets/javascripts/issuable_bulk_update_sidebar.js
app/assets/javascripts/issuable_bulk_update_sidebar.js
+6
-0
app/controllers/concerns/issuable_actions.rb
app/controllers/concerns/issuable_actions.rb
+6
-2
app/services/issuable/bulk_update_service.rb
app/services/issuable/bulk_update_service.rb
+32
-21
app/views/shared/issuable/_bulk_update_sidebar.html.haml
app/views/shared/issuable/_bulk_update_sidebar.html.haml
+8
-0
ee/app/assets/javascripts/sidebar/components/status/health_status_dropdown.vue
...ipts/sidebar/components/status/health_status_dropdown.vue
+131
-0
ee/app/assets/javascripts/sidebar/constants.js
ee/app/assets/javascripts/sidebar/constants.js
+7
-0
ee/app/assets/javascripts/vue_shared/components/sidebar/health_status_select/health_status_bundle.js
...ents/sidebar/health_status_select/health_status_bundle.js
+48
-0
ee/app/controllers/concerns/ee/issuable_actions.rb
ee/app/controllers/concerns/ee/issuable_actions.rb
+1
-0
ee/app/services/ee/issuable/bulk_update_service.rb
ee/app/services/ee/issuable/bulk_update_service.rb
+16
-3
ee/app/views/shared/issuable/_group_bulk_update_sidebar.html.haml
...iews/shared/issuable/_group_bulk_update_sidebar.html.haml
+8
-0
ee/changelogs/unreleased/bulk-edit-health-status.yml
ee/changelogs/unreleased/bulk-edit-health-status.yml
+5
-0
ee/spec/features/issues/bulk_assignment_health_status_spec.rb
...pec/features/issues/bulk_assignment_health_status_spec.rb
+153
-0
ee/spec/services/ee/issuable/bulk_update_service_spec.rb
ee/spec/services/ee/issuable/bulk_update_service_spec.rb
+27
-28
locale/gitlab.pot
locale/gitlab.pot
+6
-0
spec/services/issuable/bulk_update_service_spec.rb
spec/services/issuable/bulk_update_service_spec.rb
+14
-14
No files found.
app/assets/javascripts/issuable_bulk_update_actions.js
View file @
ce5c8c67
...
@@ -86,6 +86,7 @@ export default {
...
@@ -86,6 +86,7 @@ export default {
milestone_id
:
this
.
form
.
find
(
'
input[name="update[milestone_id]"]
'
).
val
(),
milestone_id
:
this
.
form
.
find
(
'
input[name="update[milestone_id]"]
'
).
val
(),
issuable_ids
:
this
.
form
.
find
(
'
input[name="update[issuable_ids]"]
'
).
val
(),
issuable_ids
:
this
.
form
.
find
(
'
input[name="update[issuable_ids]"]
'
).
val
(),
subscription_event
:
this
.
form
.
find
(
'
input[name="update[subscription_event]"]
'
).
val
(),
subscription_event
:
this
.
form
.
find
(
'
input[name="update[subscription_event]"]
'
).
val
(),
health_status
:
this
.
form
.
find
(
'
input[name="update[health_status]"]
'
).
val
(),
add_label_ids
:
[],
add_label_ids
:
[],
remove_label_ids
:
[],
remove_label_ids
:
[],
},
},
...
...
app/assets/javascripts/issuable_bulk_update_sidebar.js
View file @
ce5c8c67
...
@@ -7,6 +7,8 @@ import MilestoneSelect from './milestone_select';
...
@@ -7,6 +7,8 @@ import MilestoneSelect from './milestone_select';
import
issueStatusSelect
from
'
./issue_status_select
'
;
import
issueStatusSelect
from
'
./issue_status_select
'
;
import
subscriptionSelect
from
'
./subscription_select
'
;
import
subscriptionSelect
from
'
./subscription_select
'
;
import
LabelsSelect
from
'
./labels_select
'
;
import
LabelsSelect
from
'
./labels_select
'
;
import
HealthStatusSelect
from
'
ee_else_ce/vue_shared/components/sidebar/health_status_select/health_status_bundle
'
;
import
issueableEventHub
from
'
./issuables_list/eventhub
'
;
import
issueableEventHub
from
'
./issuables_list/eventhub
'
;
const
HIDDEN_CLASS
=
'
hidden
'
;
const
HIDDEN_CLASS
=
'
hidden
'
;
...
@@ -63,6 +65,10 @@ export default class IssuableBulkUpdateSidebar {
...
@@ -63,6 +65,10 @@ export default class IssuableBulkUpdateSidebar {
new
MilestoneSelect
();
new
MilestoneSelect
();
issueStatusSelect
();
issueStatusSelect
();
subscriptionSelect
();
subscriptionSelect
();
if
(
HealthStatusSelect
)
{
HealthStatusSelect
();
}
}
}
setupBulkUpdateActions
()
{
setupBulkUpdateActions
()
{
...
...
app/controllers/concerns/issuable_actions.rb
View file @
ce5c8c67
...
@@ -110,9 +110,13 @@ module IssuableActions
...
@@ -110,9 +110,13 @@ module IssuableActions
def
bulk_update
def
bulk_update
result
=
Issuable
::
BulkUpdateService
.
new
(
parent
,
current_user
,
bulk_update_params
).
execute
(
resource_name
)
result
=
Issuable
::
BulkUpdateService
.
new
(
parent
,
current_user
,
bulk_update_params
).
execute
(
resource_name
)
quantity
=
result
[
:count
]
if
result
.
success?
quantity
=
result
.
payload
[
:count
]
render
json:
{
notice:
"
#{
quantity
}
#{
resource_name
.
pluralize
(
quantity
)
}
updated"
}
render
json:
{
notice:
"
#{
quantity
}
#{
resource_name
.
pluralize
(
quantity
)
}
updated"
}
elsif
result
.
error?
render
json:
{
errors:
result
.
message
},
status:
result
.
http_status
end
end
end
# rubocop:disable CodeReuse/ActiveRecord
# rubocop:disable CodeReuse/ActiveRecord
...
...
app/services/issuable/bulk_update_service.rb
View file @
ce5c8c67
...
@@ -11,40 +11,29 @@ module Issuable
...
@@ -11,40 +11,29 @@ module Issuable
end
end
def
execute
(
type
)
def
execute
(
type
)
model_class
=
type
.
classify
.
constantize
update_class
=
type
.
classify
.
pluralize
.
constantize
::
UpdateService
ids
=
params
.
delete
(
:issuable_ids
).
split
(
","
)
ids
=
params
.
delete
(
:issuable_ids
).
split
(
","
)
items
=
find_issuables
(
parent
,
model_class
,
ids
)
set_update_params
(
type
)
items
=
update_issuables
(
type
,
ids
)
response_success
(
payload:
{
count:
items
.
count
})
rescue
ArgumentError
=>
e
response_error
(
e
.
message
,
422
)
end
private
def
set_update_params
(
type
)
params
.
slice!
(
*
permitted_attrs
(
type
))
params
.
slice!
(
*
permitted_attrs
(
type
))
params
.
delete_if
{
|
k
,
v
|
v
.
blank?
}
params
.
delete_if
{
|
k
,
v
|
v
.
blank?
}
if
params
[
:assignee_ids
]
==
[
IssuableFinder
::
Params
::
NONE
.
to_s
]
if
params
[
:assignee_ids
]
==
[
IssuableFinder
::
Params
::
NONE
.
to_s
]
params
[
:assignee_ids
]
=
[]
params
[
:assignee_ids
]
=
[]
end
end
items
.
each
do
|
issuable
|
next
unless
can?
(
current_user
,
:"update_
#{
type
}
"
,
issuable
)
update_class
.
new
(
issuable
.
issuing_parent
,
current_user
,
params
).
execute
(
issuable
)
end
{
count:
items
.
count
,
success:
!
items
.
count
.
zero?
}
end
end
private
def
permitted_attrs
(
type
)
def
permitted_attrs
(
type
)
attrs
=
%i(state_event milestone_id add_label_ids remove_label_ids subscription_event)
attrs
=
%i(state_event milestone_id add_label_ids remove_label_ids subscription_event)
issuable_specific_attrs
(
type
,
attrs
)
end
def
issuable_specific_attrs
(
type
,
attrs
)
if
type
==
'issue'
||
type
==
'merge_request'
if
type
==
'issue'
||
type
==
'merge_request'
attrs
.
push
(
:assignee_ids
)
attrs
.
push
(
:assignee_ids
)
else
else
...
@@ -52,6 +41,20 @@ module Issuable
...
@@ -52,6 +41,20 @@ module Issuable
end
end
end
end
def
update_issuables
(
type
,
ids
)
model_class
=
type
.
classify
.
constantize
update_class
=
type
.
classify
.
pluralize
.
constantize
::
UpdateService
items
=
find_issuables
(
parent
,
model_class
,
ids
)
items
.
each
do
|
issuable
|
next
unless
can?
(
current_user
,
:"update_
#{
type
}
"
,
issuable
)
update_class
.
new
(
issuable
.
issuing_parent
,
current_user
,
params
).
execute
(
issuable
)
end
items
end
def
find_issuables
(
parent
,
model_class
,
ids
)
def
find_issuables
(
parent
,
model_class
,
ids
)
if
parent
.
is_a?
(
Project
)
if
parent
.
is_a?
(
Project
)
model_class
.
id_in
(
ids
).
of_projects
(
parent
)
model_class
.
id_in
(
ids
).
of_projects
(
parent
)
...
@@ -59,6 +62,14 @@ module Issuable
...
@@ -59,6 +62,14 @@ module Issuable
model_class
.
id_in
(
ids
).
of_projects
(
parent
.
all_projects
)
model_class
.
id_in
(
ids
).
of_projects
(
parent
.
all_projects
)
end
end
end
end
def
response_success
(
message:
nil
,
payload:
nil
)
ServiceResponse
.
success
(
message:
message
,
payload:
payload
)
end
def
response_error
(
message
,
http_status
)
ServiceResponse
.
error
(
message:
message
,
http_status:
http_status
)
end
end
end
end
end
...
...
app/views/shared/issuable/_bulk_update_sidebar.html.haml
View file @
ce5c8c67
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
bulk_issue_health_status_flag
=
Feature
.
enabled?
(
:bulk_update_health_status
,
@project
&
.
group
)
&&
type
==
:issues
&&
@project
&
.
group
&
.
feature_available?
(
:issuable_health_status
)
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
"aria-live"
=>
"polite"
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
"aria-live"
=>
"polite"
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
.issuable-sidebar.hidden
.issuable-sidebar.hidden
...
@@ -36,6 +37,13 @@
...
@@ -36,6 +37,13 @@
=
_
(
'Labels'
)
=
_
(
'Labels'
)
.filter-item.labels-filter
.filter-item.labels-filter
=
render
"shared/issuable/label_dropdown"
,
classes:
[
"js-filter-bulk-update"
,
"js-multiselect"
],
dropdown_title:
_
(
"Apply a label"
),
show_create:
false
,
show_footer:
false
,
extra_options:
false
,
filter_submit:
false
,
data_options:
{
persist_when_hide:
"true"
,
field_name:
"update[label_ids][]"
,
show_no:
false
,
show_any:
false
,
use_id:
true
,
default_label:
_
(
"Labels"
)
},
label_name:
_
(
"Select labels"
),
no_default_styles:
true
=
render
"shared/issuable/label_dropdown"
,
classes:
[
"js-filter-bulk-update"
,
"js-multiselect"
],
dropdown_title:
_
(
"Apply a label"
),
show_create:
false
,
show_footer:
false
,
extra_options:
false
,
filter_submit:
false
,
data_options:
{
persist_when_hide:
"true"
,
field_name:
"update[label_ids][]"
,
show_no:
false
,
show_any:
false
,
use_id:
true
,
default_label:
_
(
"Labels"
)
},
label_name:
_
(
"Select labels"
),
no_default_styles:
true
-
if
bulk_issue_health_status_flag
.block
.title
=
_
(
'Health status'
)
.filter-item.health-status.health-status-filter
#js-bulk-update-health-status-root
%input
{
id:
'issue_health_status_value'
,
type:
'hidden'
,
name:
'update[health_status]'
}
.block
.block
.title
.title
=
_
(
'Subscriptions'
)
=
_
(
'Subscriptions'
)
...
...
ee/app/assets/javascripts/sidebar/components/status/health_status_dropdown.vue
0 → 100644
View file @
ce5c8c67
<
script
>
import
{
GlButton
,
GlDropdownItem
,
GlDropdown
,
GlDropdownDivider
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
healthStatusTextMap
}
from
'
../../constants
'
;
export
default
{
components
:
{
GlButton
,
GlDropdown
,
GlDropdownItem
,
GlDropdownDivider
,
},
props
:
{
isEditable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isFetching
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
status
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
},
data
()
{
return
{
isDropdownShowing
:
false
,
selectedStatus
:
this
.
status
,
statusOptions
:
Object
.
keys
(
healthStatusTextMap
).
map
(
key
=>
({
key
,
value
:
healthStatusTextMap
[
key
],
})),
};
},
computed
:
{
statusText
()
{
return
this
.
status
?
healthStatusTextMap
[
this
.
status
]
:
s__
(
'
Sidebar|None
'
);
},
dropdownText
()
{
if
(
this
.
status
===
null
)
{
return
s__
(
'
No status
'
);
}
return
this
.
status
?
healthStatusTextMap
[
this
.
status
]
:
s__
(
'
Select health status
'
);
},
tooltipText
()
{
let
tooltipText
=
s__
(
'
Sidebar|Health status
'
);
if
(
this
.
status
)
{
tooltipText
+=
`:
${
this
.
statusText
}
`
;
}
return
tooltipText
;
},
},
watch
:
{
status
(
status
)
{
this
.
selectedStatus
=
status
;
},
},
methods
:
{
handleDropdownClick
(
status
)
{
this
.
selectedStatus
=
status
;
this
.
$emit
(
'
onDropdownClick
'
,
status
);
this
.
hideDropdown
();
},
hideDropdown
()
{
this
.
isDropdownShowing
=
false
;
},
isSelected
(
status
)
{
return
this
.
status
===
status
;
},
},
};
</
script
>
<
template
>
<div
class=
"dropdown dropdown-menu-selectable"
>
<gl-dropdown
ref=
"dropdown"
class=
"w-100"
:text=
"dropdownText"
@
keydown.esc.native=
"hideDropdown"
@
hide=
"hideDropdown"
>
<div
class=
"dropdown-title"
>
<span
class=
"health-title"
>
{{
s__
(
'
Sidebar|Assign health status
'
)
}}
</span>
<gl-button
:aria-label=
"__('Close')"
variant=
"link"
class=
"dropdown-title-button dropdown-menu-close"
icon=
"close"
@
click=
"hideDropdown"
/>
</div>
<div
class=
"dropdown-content dropdown-body"
>
<gl-dropdown-item
@
click=
"handleDropdownClick(null)"
>
<gl-button
variant=
"link"
class=
"dropdown-item health-dropdown-item"
:class=
"
{ 'is-active': isSelected(null) }"
>
{{
s__
(
'
Sidebar|No status
'
)
}}
</gl-button>
</gl-dropdown-item>
<gl-dropdown-divider
class=
"divider health-divider"
/>
<gl-dropdown-item
v-for=
"option in statusOptions"
:key=
"option.key"
@
click=
"handleDropdownClick(option.key)"
>
<gl-button
variant=
"link"
class=
"dropdown-item health-dropdown-item"
:class=
"
{ 'is-active': isSelected(option.key) }"
>
{{
option
.
value
}}
</gl-button>
</gl-dropdown-item>
</div>
</gl-dropdown>
</div>
</
template
>
ee/app/assets/javascripts/sidebar/constants.js
View file @
ce5c8c67
...
@@ -18,3 +18,10 @@ export const iterationSelectTextMap = {
...
@@ -18,3 +18,10 @@ export const iterationSelectTextMap = {
noIterationItem
:
[{
title
:
__
(
'
No iteration
'
),
id
:
null
}],
noIterationItem
:
[{
title
:
__
(
'
No iteration
'
),
id
:
null
}],
iterationSelectFail
:
__
(
'
Failed to set iteration on this issue. Please try again.
'
),
iterationSelectFail
:
__
(
'
Failed to set iteration on this issue. Please try again.
'
),
};
};
export
const
healthStatusForRestApi
=
{
NO_STATUS
:
'
0
'
,
[
healthStatus
.
ON_TRACK
]:
'
on_track
'
,
[
healthStatus
.
NEEDS_ATTENTION
]:
'
needs_attention
'
,
[
healthStatus
.
AT_RISK
]:
'
at_risk
'
,
};
ee/app/assets/javascripts/vue_shared/components/sidebar/health_status_select/health_status_bundle.js
0 → 100644
View file @
ce5c8c67
import
Vue
from
'
vue
'
;
import
HealthStatusSelect
from
'
ee/sidebar/components/status/health_status_dropdown.vue
'
;
import
{
healthStatusForRestApi
}
from
'
ee/sidebar/constants
'
;
export
default
()
=>
{
const
el
=
document
.
getElementById
(
'
js-bulk-update-health-status-root
'
);
const
healthStatusFormFieldEl
=
document
.
getElementById
(
'
issue_health_status_value
'
);
if
(
!
el
&&
!
healthStatusFormFieldEl
)
{
return
false
;
}
return
new
Vue
({
el
,
components
:
{
HealthStatusSelect
,
},
data
()
{
return
{
selectedStatus
:
undefined
,
};
},
methods
:
{
handleHealthStatusSelect
(
selectedStatus
)
{
this
.
selectedStatus
=
selectedStatus
;
healthStatusFormFieldEl
.
setAttribute
(
'
value
'
,
healthStatusForRestApi
[
selectedStatus
||
'
NO_STATUS
'
],
);
},
},
render
(
createElement
)
{
return
createElement
(
'
health-status-select
'
,
{
props
:
{
isFetching
:
false
,
isEditable
:
true
,
showDropdown
:
true
,
status
:
this
.
selectedStatus
,
},
on
:
{
onDropdownClick
:
this
.
handleHealthStatusSelect
.
bind
(
this
),
},
});
},
});
};
ee/app/controllers/concerns/ee/issuable_actions.rb
View file @
ce5c8c67
...
@@ -7,6 +7,7 @@ module EE
...
@@ -7,6 +7,7 @@ module EE
EE_PERMITTED_KEYS
=
%w[
EE_PERMITTED_KEYS
=
%w[
weight
weight
health_status
]
.
freeze
]
.
freeze
private
private
...
...
ee/app/services/ee/issuable/bulk_update_service.rb
View file @
ce5c8c67
...
@@ -14,11 +14,24 @@ module EE
...
@@ -14,11 +14,24 @@ module EE
super
super
end
end
override
:
issuable_specific
_attrs
override
:
permitted
_attrs
def
issuable_specific_attrs
(
type
,
attrs
)
def
permitted_attrs
(
type
)
return
super
unless
type
==
'issue'
return
super
unless
type
==
'issue'
super
.
push
(
:health_status
,
:epic
)
super
.
push
(
:health_status
,
:epic_id
)
end
override
:set_update_params
def
set_update_params
(
type
)
super
set_health_status
end
def
set_health_status
return
unless
params
[
:health_status
].
present?
params
[
:health_status
]
=
nil
if
params
[
:health_status
]
==
IssuableFinder
::
Params
::
NONE
.
to_s
end
end
end
end
end
end
...
...
ee/app/views/shared/issuable/_group_bulk_update_sidebar.html.haml
View file @
ce5c8c67
-
group
=
local_assigns
.
fetch
(
:group
)
-
group
=
local_assigns
.
fetch
(
:group
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
type
=
local_assigns
.
fetch
(
:type
)
-
bulk_issue_health_status_flag
=
type
==
:issues
&&
Feature
.
enabled?
(
:bulk_update_health_status
,
group
)
&&
group
&
.
feature_available?
(
:issuable_health_status
)
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
'aria-live'
=>
'polite'
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
%aside
.issues-bulk-update.js-right-sidebar.right-sidebar
{
'aria-live'
=>
'polite'
,
data:
{
'signed-in'
:
current_user
.
present?
}
}
.issuable-sidebar.hidden
.issuable-sidebar.hidden
...
@@ -19,6 +20,13 @@
...
@@ -19,6 +20,13 @@
=
_
(
'Labels'
)
=
_
(
'Labels'
)
.filter-item.labels-filter
.filter-item.labels-filter
=
render
"shared/issuable/label_dropdown"
,
classes:
[
"js-filter-bulk-update"
,
"js-multiselect"
],
dropdown_title:
_
(
'Apply a label'
),
show_create:
false
,
show_footer:
false
,
extra_options:
false
,
filter_submit:
false
,
data_options:
{
persist_when_hide:
"true"
,
field_name:
"update[label_ids][]"
,
show_no:
false
,
show_any:
false
,
use_id:
true
,
default_label:
"Labels"
},
label_name:
_
(
'Select labels'
),
no_default_styles:
true
,
edit_context:
group
=
render
"shared/issuable/label_dropdown"
,
classes:
[
"js-filter-bulk-update"
,
"js-multiselect"
],
dropdown_title:
_
(
'Apply a label'
),
show_create:
false
,
show_footer:
false
,
extra_options:
false
,
filter_submit:
false
,
data_options:
{
persist_when_hide:
"true"
,
field_name:
"update[label_ids][]"
,
show_no:
false
,
show_any:
false
,
use_id:
true
,
default_label:
"Labels"
},
label_name:
_
(
'Select labels'
),
no_default_styles:
true
,
edit_context:
group
-
if
bulk_issue_health_status_flag
.block
.title
=
_
(
'Health status'
)
.filter-item.health-status.health-status-filter
#js-bulk-update-health-status-root
%input
{
id:
'issue_health_status_value'
,
type:
'hidden'
,
name:
'update[health_status]'
}
=
hidden_field_tag
'update[issuable_ids]'
,
[]
=
hidden_field_tag
'update[issuable_ids]'
,
[]
=
hidden_field_tag
:state_event
,
params
[
:state_event
]
=
hidden_field_tag
:state_event
,
params
[
:state_event
]
ee/changelogs/unreleased/bulk-edit-health-status.yml
0 → 100644
View file @
ce5c8c67
---
title
:
Bulk edit health status
merge_request
:
33065
author
:
type
:
added
ee/spec/features/issues/bulk_assignment_health_status_spec.rb
0 → 100644
View file @
ce5c8c67
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
'Issues > Health status bulk assignment'
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:group
)
{
create
(
:group
,
:public
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:public
,
group:
group
)
}
let_it_be
(
:issue1
)
{
create
(
:issue
,
project:
project
,
title:
"Issue 1"
)
}
let_it_be
(
:issue2
)
{
create
(
:issue
,
project:
project
,
title:
"Issue 2"
)
}
shared_examples
'bulk edit option in sidebar'
do
|
context
|
it
'is present when bulk edit is enabled'
do
enable_bulk_update
(
context
)
expect
(
page
).
to
have_css
(
'.issuable-sidebar'
)
end
it
'is not present when bulk edit is disabled'
do
expect
(
page
).
not_to
have_css
(
'.issuable-sidebar'
)
end
end
shared_examples
'bulk edit health status'
do
|
context
|
before
do
enable_bulk_update
(
context
)
end
context
'health_status'
,
:js
do
context
'to all issues'
do
before
do
check
'check-all-issues'
open_health_status_dropdown
[
'On track'
]
update_issues
end
it
'updates the health statuses'
do
expect
(
issue1
.
reload
.
health_status
).
to
eq
'on_track'
expect
(
issue2
.
reload
.
health_status
).
to
eq
'on_track'
end
end
context
'to an issue'
do
before
do
check
"selected_issue_
#{
issue1
.
id
}
"
open_health_status_dropdown
[
'At risk'
]
update_issues
end
it
'updates the checked issue\'s status'
do
expect
(
issue1
.
reload
.
health_status
).
to
eq
'at_risk'
expect
(
issue2
.
reload
.
health_status
).
to
eq
nil
end
end
end
end
shared_examples
'bulk edit health_status with insufficient permissions'
do
it
'cannot bulk assign health_status'
do
expect
(
page
).
not_to
have_button
'Edit issues'
expect
(
page
).
not_to
have_css
'.check-all-issues'
expect
(
page
).
not_to
have_css
'.issue-check'
end
end
before
do
stub_feature_flags
(
vue_issuables_list:
false
)
end
context
'as an allowed user'
,
:js
do
before
do
allow
(
group
).
to
receive
(
:feature_enabled?
).
and_return
(
true
)
stub_licensed_features
(
group_bulk_edit:
true
,
issuable_health_status:
true
)
group
.
add_maintainer
(
user
)
sign_in
user
end
context
'at group level'
do
it_behaves_like
'bulk edit option in sidebar'
,
:group
it_behaves_like
'bulk edit health status'
,
:group
end
context
'at project level'
do
it_behaves_like
'bulk edit option in sidebar'
,
:project
it_behaves_like
'bulk edit health status'
,
:project
end
end
context
'as a guest'
,
:js
do
before
do
sign_in
user
allow
(
group
).
to
receive
(
:feature_enabled?
).
and_return
(
true
)
stub_licensed_features
(
issuable_health_status:
true
)
end
context
'at group level'
do
before
do
visit
issues_group_path
(
group
)
end
it_behaves_like
'bulk edit health_status with insufficient permissions'
end
context
'at project level'
do
before
do
visit
project_issues_path
(
project
)
end
it_behaves_like
'bulk edit health_status with insufficient permissions'
end
end
def
open_health_status_dropdown
(
items
=
[])
page
.
within
(
'.issues-bulk-update'
)
do
click_button
'Select health status'
items
.
map
do
|
item
|
find
(
'.gl-button-text'
,
{
text:
item
}).
click
end
end
end
def
toggle_issue
(
issue
,
uncheck
=
false
)
page
.
within
(
'.issues-list'
)
do
if
uncheck
uncheck
"selected_issue_
#{
issue
.
id
}
"
else
check
"selected_issue_
#{
issue
.
id
}
"
end
end
end
def
uncheck_issue
(
issue
)
toggle_issue
(
issue
,
uncheck:
true
)
end
def
update_issues
find
(
'.update-selected-issues'
).
click
wait_for_requests
end
def
enable_bulk_update
(
context
)
if
context
==
:project
visit
project_issues_path
(
project
)
else
visit
issues_group_path
(
group
)
end
click_button
'Edit issues'
end
end
ee/spec/services/ee/issuable/bulk_update_service_spec.rb
View file @
ce5c8c67
...
@@ -12,8 +12,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -12,8 +12,8 @@ RSpec.describe Issuable::BulkUpdateService do
shared_examples
'updates issuables attribute'
do
|
attribute
|
shared_examples
'updates issuables attribute'
do
|
attribute
|
it
'succeeds and returns the correct number of issuables updated'
do
it
'succeeds and returns the correct number of issuables updated'
do
expect
(
subject
[
:success
]
).
to
be_truthy
expect
(
subject
.
success?
).
to
be_truthy
expect
(
subject
[
:count
]).
to
eq
(
issuables
.
count
)
expect
(
subject
.
payload
[
:count
]).
to
eq
(
issuables
.
count
)
issuables
.
each
do
|
issuable
|
issuables
.
each
do
|
issuable
|
expect
(
issuable
.
reload
.
send
(
attribute
)).
to
eq
(
new_value
)
expect
(
issuable
.
reload
.
send
(
attribute
)).
to
eq
(
new_value
)
end
end
...
@@ -31,48 +31,55 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -31,48 +31,55 @@ RSpec.describe Issuable::BulkUpdateService do
context
'with issues'
do
context
'with issues'
do
let_it_be
(
:type
)
{
'issue'
}
let_it_be
(
:type
)
{
'issue'
}
let_it_be
(
:parent
)
{
group
}
let_it_be
(
:parent
)
{
group
}
let
(
:issue1
)
{
create
(
:issue
,
project:
project1
,
health_status: :at_risk
,
epic:
epic
)
}
let
(
:issue1
)
{
create
(
:issue
,
project:
project1
,
health_status: :at_risk
)
}
let
(
:issue2
)
{
create
(
:issue
,
project:
project2
,
health_status: :at_risk
,
epic:
epic
)
}
let
(
:issue2
)
{
create
(
:issue
,
project:
project2
,
health_status: :at_risk
)
}
let
(
:epic
)
{
create
(
:epic
,
group:
group
)
}
let
(
:epic2
)
{
create
(
:epic
,
group:
group
)
}
let
(
:issuables
)
{
[
issue1
,
issue2
]
}
let
(
:issuables
)
{
[
issue1
,
issue2
]
}
before
do
before
do
group
.
add_reporter
(
user
)
group
.
add_reporter
(
user
)
end
end
context
'updating health status
and epic
'
do
context
'updating health status'
do
let
(
:params
)
do
let
(
:params
)
do
{
{
issuable_ids:
issuables
.
map
(
&
:id
),
issuable_ids:
issuables
.
map
(
&
:id
),
health_status: :on_track
,
health_status: :on_track
epic:
epic2
}
}
end
end
context
'when features are enabled'
do
context
'when features are enabled'
do
before
do
before
do
stub_licensed_features
(
epics:
true
,
issuable_health_status:
true
)
stub_licensed_features
(
issuable_health_status:
true
)
end
end
it
'succeeds and returns the correct number of issuables updated'
do
it
'succeeds and returns the correct number of issuables updated'
do
expect
(
subject
[
:success
]).
to
be_truthy
expect
(
subject
.
success?
).
to
be_truthy
expect
(
subject
[
:count
]).
to
eq
(
issuables
.
count
)
expect
(
subject
.
payload
[
:count
]).
to
eq
(
issuables
.
count
)
issuables
.
each
do
|
issuable
|
expect
(
issuable
.
reload
.
health_status
).
to
eq
(
'on_track'
)
end
end
context
"when params value is '0'"
do
let
(
:params
)
{
{
issuable_ids:
issuables
.
map
(
&
:id
),
health_status:
'0'
}
}
it
'succeeds and remove values'
do
expect
(
subject
.
success?
).
to
be_truthy
expect
(
subject
.
payload
[
:count
]).
to
eq
(
issuables
.
count
)
issuables
.
each
do
|
issuable
|
issuables
.
each
do
|
issuable
|
issuable
.
reload
issuable
.
reload
expect
(
issuable
.
epic
).
to
eq
(
epic2
)
expect
(
issuable
.
health_status
).
to
be_nil
e
xpect
(
issuable
.
health_status
).
to
eq
(
'on_track'
)
e
nd
end
end
end
end
end
end
context
'when feature
s are
disabled'
do
context
'when feature
issuable_health_status is
disabled'
do
before
do
before
do
stub_licensed_features
(
epics:
false
,
issuable_health_status:
false
)
stub_licensed_features
(
issuable_health_status:
false
)
end
end
it_behaves_like
'does not update issuables attribute'
,
:health_status
it_behaves_like
'does not update issuables attribute'
,
:health_status
it_behaves_like
'does not update issuables attribute'
,
:epic
end
end
context
'when user can not update issues'
do
context
'when user can not update issues'
do
...
@@ -81,14 +88,6 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -81,14 +88,6 @@ RSpec.describe Issuable::BulkUpdateService do
end
end
it_behaves_like
'does not update issuables attribute'
,
:health_status
it_behaves_like
'does not update issuables attribute'
,
:health_status
it_behaves_like
'does not update issuables attribute'
,
:epic
end
context
'when user can not admin epic'
do
let
(
:epic3
)
{
create
(
:epic
,
group:
create
(
:group
))
}
let
(
:params
)
{
{
issuable_ids:
issuables
.
map
(
&
:id
),
epic:
epic3
}
}
it_behaves_like
'does not update issuables attribute'
,
:epic
end
end
end
end
end
end
...
@@ -140,8 +139,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -140,8 +139,8 @@ RSpec.describe Issuable::BulkUpdateService do
let
(
:params
)
{
{
issuable_ids:
[
epic1
.
id
,
epic3
.
id
,
outer_epic
.
id
],
add_label_ids:
[
label3
.
id
]
}
}
let
(
:params
)
{
{
issuable_ids:
[
epic1
.
id
,
epic3
.
id
,
outer_epic
.
id
],
add_label_ids:
[
label3
.
id
]
}
}
it
'updates epics that belong to the parent group or descendants'
do
it
'updates epics that belong to the parent group or descendants'
do
expect
(
subject
[
:success
]
).
to
be_truthy
expect
(
subject
.
success?
).
to
be_truthy
expect
(
subject
[
:count
]).
to
eq
(
2
)
expect
(
subject
.
payload
[
:count
]).
to
eq
(
2
)
expect
(
epic1
.
reload
.
labels
).
to
eq
([
label1
,
label3
])
expect
(
epic1
.
reload
.
labels
).
to
eq
([
label1
,
label3
])
expect
(
epic3
.
reload
.
labels
).
to
eq
([
label1
,
label3
])
expect
(
epic3
.
reload
.
labels
).
to
eq
([
label1
,
label3
])
...
...
locale/gitlab.pot
View file @
ce5c8c67
...
@@ -11749,6 +11749,9 @@ msgstr ""
...
@@ -11749,6 +11749,9 @@ msgstr ""
msgid "Health information can be retrieved from the following endpoints. More information is available"
msgid "Health information can be retrieved from the following endpoints. More information is available"
msgstr ""
msgstr ""
msgid "Health status"
msgstr ""
msgid "HealthCheck|Access token is"
msgid "HealthCheck|Access token is"
msgstr ""
msgstr ""
...
@@ -15391,6 +15394,9 @@ msgstr ""
...
@@ -15391,6 +15394,9 @@ msgstr ""
msgid "No start date"
msgid "No start date"
msgstr ""
msgstr ""
msgid "No status"
msgstr ""
msgid "No template"
msgid "No template"
msgstr ""
msgstr ""
...
...
spec/services/issuable/bulk_update_service_spec.rb
View file @
ce5c8c67
...
@@ -18,8 +18,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -18,8 +18,8 @@ RSpec.describe Issuable::BulkUpdateService do
it
'succeeds'
do
it
'succeeds'
do
result
=
bulk_update
(
issuables
,
milestone_id:
milestone
.
id
)
result
=
bulk_update
(
issuables
,
milestone_id:
milestone
.
id
)
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
issuables
.
count
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
issuables
.
count
)
end
end
it
'updates the issuables milestone'
do
it
'updates the issuables milestone'
do
...
@@ -121,8 +121,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -121,8 +121,8 @@ RSpec.describe Issuable::BulkUpdateService do
it
'succeeds and returns the correct number of issues updated'
do
it
'succeeds and returns the correct number of issues updated'
do
result
=
bulk_update
(
issues
,
state_event:
'close'
)
result
=
bulk_update
(
issues
,
state_event:
'close'
)
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
issues
.
count
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
issues
.
count
)
end
end
it
'closes all the issues passed'
do
it
'closes all the issues passed'
do
...
@@ -139,8 +139,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -139,8 +139,8 @@ RSpec.describe Issuable::BulkUpdateService do
it
'succeeds and returns the correct number of issues updated'
do
it
'succeeds and returns the correct number of issues updated'
do
result
=
bulk_update
(
issues
,
state_event:
'reopen'
)
result
=
bulk_update
(
issues
,
state_event:
'reopen'
)
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
issues
.
count
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
issues
.
count
)
end
end
it
'reopens all the issues passed'
do
it
'reopens all the issues passed'
do
...
@@ -161,8 +161,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -161,8 +161,8 @@ RSpec.describe Issuable::BulkUpdateService do
result
=
bulk_update
(
merge_request
,
assignee_ids:
[
user
.
id
,
new_assignee
.
id
])
result
=
bulk_update
(
merge_request
,
assignee_ids:
[
user
.
id
,
new_assignee
.
id
])
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
1
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
1
)
end
end
it
'updates the assignee to the user ID passed'
do
it
'updates the assignee to the user ID passed'
do
...
@@ -199,8 +199,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -199,8 +199,8 @@ RSpec.describe Issuable::BulkUpdateService do
result
=
bulk_update
(
issue
,
assignee_ids:
[
new_assignee
.
id
])
result
=
bulk_update
(
issue
,
assignee_ids:
[
new_assignee
.
id
])
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
1
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
1
)
end
end
it
'updates the assignee to the user ID passed'
do
it
'updates the assignee to the user ID passed'
do
...
@@ -273,8 +273,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -273,8 +273,8 @@ RSpec.describe Issuable::BulkUpdateService do
issue2
=
create
(
:issue
,
project:
create
(
:project
))
issue2
=
create
(
:issue
,
project:
create
(
:project
))
result
=
bulk_update
([
issue1
,
issue2
],
assignee_ids:
[
user
.
id
])
result
=
bulk_update
([
issue1
,
issue2
],
assignee_ids:
[
user
.
id
])
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
1
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
1
)
expect
(
issue1
.
reload
.
assignees
).
to
eq
([
user
])
expect
(
issue1
.
reload
.
assignees
).
to
eq
([
user
])
expect
(
issue2
.
reload
.
assignees
).
to
be_empty
expect
(
issue2
.
reload
.
assignees
).
to
be_empty
...
@@ -332,8 +332,8 @@ RSpec.describe Issuable::BulkUpdateService do
...
@@ -332,8 +332,8 @@ RSpec.describe Issuable::BulkUpdateService do
milestone
=
create
(
:milestone
,
group:
group
)
milestone
=
create
(
:milestone
,
group:
group
)
result
=
bulk_update
([
issue1
,
issue2
,
issue3
],
milestone_id:
milestone
.
id
)
result
=
bulk_update
([
issue1
,
issue2
,
issue3
],
milestone_id:
milestone
.
id
)
expect
(
result
[
:success
]
).
to
be_truthy
expect
(
result
.
success?
).
to
be_truthy
expect
(
result
[
:count
]).
to
eq
(
2
)
expect
(
result
.
payload
[
:count
]).
to
eq
(
2
)
expect
(
issue1
.
reload
.
milestone
).
to
eq
(
milestone
)
expect
(
issue1
.
reload
.
milestone
).
to
eq
(
milestone
)
expect
(
issue2
.
reload
.
milestone
).
to
be_nil
expect
(
issue2
.
reload
.
milestone
).
to
be_nil
...
...
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