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
54332bb7
Commit
54332bb7
authored
Oct 13, 2020
by
charlie ablett
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use existing group label when promoting
parent
922c9f44
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
136 additions
and
129 deletions
+136
-129
app/services/labels/promote_service.rb
app/services/labels/promote_service.rb
+27
-35
changelogs/unreleased/cablett-231472-group-labels.yml
changelogs/unreleased/cablett-231472-group-labels.yml
+5
-0
spec/services/labels/promote_service_spec.rb
spec/services/labels/promote_service_spec.rb
+104
-94
No files found.
app/services/labels/promote_service.rb
View file @
54332bb7
...
@@ -10,81 +10,79 @@ module Labels
...
@@ -10,81 +10,79 @@ module Labels
label
.
is_a?
(
ProjectLabel
)
label
.
is_a?
(
ProjectLabel
)
Label
.
transaction
do
Label
.
transaction
do
new_label
=
clone_label_to_group_label
(
label
)
# use the existing group label if it exists
group_label
=
find_or_create_group_label
(
label
)
label_ids_for_merge
(
new
_label
).
find_in_batches
(
batch_size:
BATCH_SIZE
)
do
|
batched_ids
|
label_ids_for_merge
(
group
_label
).
find_in_batches
(
batch_size:
BATCH_SIZE
)
do
|
batched_ids
|
update_old_label_relations
(
new
_label
,
batched_ids
)
update_old_label_relations
(
group
_label
,
batched_ids
)
destroy_project_labels
(
batched_ids
)
destroy_project_labels
(
batched_ids
)
end
end
# We skipped validations during creation. Let's run them now, after deleting conflicting labels
group_label
raise
ActiveRecord
::
RecordInvalid
.
new
(
new_label
)
unless
new_label
.
valid?
new_label
end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
private
private
def
update_old_label_relations
(
new
_label
,
old_label_ids
)
def
update_old_label_relations
(
group
_label
,
old_label_ids
)
update_issuables
(
new
_label
,
old_label_ids
)
update_issuables
(
group
_label
,
old_label_ids
)
update_resource_label_events
(
new
_label
,
old_label_ids
)
update_resource_label_events
(
group
_label
,
old_label_ids
)
update_issue_board_lists
(
new
_label
,
old_label_ids
)
update_issue_board_lists
(
group
_label
,
old_label_ids
)
update_priorities
(
new
_label
,
old_label_ids
)
update_priorities
(
group
_label
,
old_label_ids
)
subscribe_users
(
new
_label
,
old_label_ids
)
subscribe_users
(
group
_label
,
old_label_ids
)
end
end
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def
subscribe_users
(
new
_label
,
label_ids
)
def
subscribe_users
(
group
_label
,
label_ids
)
# users can be subscribed to multiple labels that will be merged into the group one
# users can be subscribed to multiple labels that will be merged into the group one
# we want to keep only one subscription / user
# we want to keep only one subscription / user
ids_to_update
=
Subscription
.
where
(
subscribable_id:
label_ids
,
subscribable_type:
'Label'
)
ids_to_update
=
Subscription
.
where
(
subscribable_id:
label_ids
,
subscribable_type:
'Label'
)
.
group
(
:user_id
)
.
group
(
:user_id
)
.
pluck
(
'MAX(id)'
)
.
pluck
(
'MAX(id)'
)
Subscription
.
where
(
id:
ids_to_update
).
update_all
(
subscribable_id:
new
_label
.
id
)
Subscription
.
where
(
id:
ids_to_update
).
update_all
(
subscribable_id:
group
_label
.
id
)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def
label_ids_for_merge
(
new
_label
)
def
label_ids_for_merge
(
group
_label
)
LabelsFinder
LabelsFinder
.
new
(
current_user
,
title:
new
_label
.
title
,
group_id:
project
.
group
.
id
)
.
new
(
current_user
,
title:
group
_label
.
title
,
group_id:
project
.
group
.
id
)
.
execute
(
skip_authorization:
true
)
.
execute
(
skip_authorization:
true
)
.
where
.
not
(
id:
new
_label
)
.
where
.
not
(
id:
group
_label
)
.
select
(
:id
)
# Can't use pluck() to avoid object-creation because of the batching
.
select
(
:id
)
# Can't use pluck() to avoid object-creation because of the batching
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def
update_issuables
(
new
_label
,
label_ids
)
def
update_issuables
(
group
_label
,
label_ids
)
LabelLink
LabelLink
.
where
(
label:
label_ids
)
.
where
(
label:
label_ids
)
.
update_all
(
label_id:
new
_label
.
id
)
.
update_all
(
label_id:
group
_label
.
id
)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def
update_resource_label_events
(
new
_label
,
label_ids
)
def
update_resource_label_events
(
group
_label
,
label_ids
)
ResourceLabelEvent
ResourceLabelEvent
.
where
(
label:
label_ids
)
.
where
(
label:
label_ids
)
.
update_all
(
label_id:
new
_label
.
id
)
.
update_all
(
label_id:
group
_label
.
id
)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def
update_issue_board_lists
(
new
_label
,
label_ids
)
def
update_issue_board_lists
(
group
_label
,
label_ids
)
List
List
.
where
(
label:
label_ids
)
.
where
(
label:
label_ids
)
.
update_all
(
label_id:
new
_label
.
id
)
.
update_all
(
label_id:
group
_label
.
id
)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def
update_priorities
(
new
_label
,
label_ids
)
def
update_priorities
(
group
_label
,
label_ids
)
LabelPriority
LabelPriority
.
where
(
label:
label_ids
)
.
where
(
label:
label_ids
)
.
update_all
(
label_id:
new
_label
.
id
)
.
update_all
(
label_id:
group
_label
.
id
)
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: enable CodeReuse/ActiveRecord
...
@@ -92,18 +90,12 @@ module Labels
...
@@ -92,18 +90,12 @@ module Labels
def
destroy_project_labels
(
label_ids
)
def
destroy_project_labels
(
label_ids
)
Label
.
where
(
id:
label_ids
).
destroy_all
# rubocop: disable Cop/DestroyAll
Label
.
where
(
id:
label_ids
).
destroy_all
# rubocop: disable Cop/DestroyAll
end
end
# rubocop: enable CodeReuse/ActiveRecord
def
clone_label_to
_group_label
(
label
)
def
find_or_create
_group_label
(
label
)
params
=
label
.
attributes
.
slice
(
'title'
,
'description'
,
'color'
)
params
=
label
.
attributes
.
slice
(
'title'
,
'description'
,
'color'
)
# Since the title of the new label has to be the same as the previous labels
new_label
=
GroupLabel
.
create_with
(
params
).
find_or_initialize_by
(
group_id:
project
.
group
.
id
,
title:
label
.
title
)
# and we're merging old labels in batches we'll skip validation to omit 2-step
# merge process and do it in one batch
# We'll be forcing validation at the end of the transaction to ensure everything
# was merged correctly
new_label
=
GroupLabel
.
new
(
params
.
merge
(
group:
project
.
group
))
new_label
.
save
(
validate:
false
)
new_label
.
save!
unless
new_label
.
persisted?
new_label
new_label
end
end
end
end
...
...
changelogs/unreleased/cablett-231472-group-labels.yml
0 → 100644
View file @
54332bb7
---
title
:
Use existing group label when promoting project label
merge_request
:
45122
author
:
type
:
changed
spec/services/labels/promote_service_spec.rb
View file @
54332bb7
...
@@ -63,29 +63,15 @@ RSpec.describe Labels::PromoteService do
...
@@ -63,29 +63,15 @@ RSpec.describe Labels::PromoteService do
expect
(
service
.
execute
(
group_label
)).
to
be_falsey
expect
(
service
.
execute
(
group_label
)).
to
be_falsey
end
end
it
'is truthy on success'
do
shared_examples
'promoting a project label to a group label'
do
expect
(
service
.
execute
(
project_label_1_1
)).
to
be_truthy
it
'is truthy on success'
do
end
expect
(
service
.
execute
(
project_label_1_1
)).
to
be_truthy
end
it
'recreates the label as a group label'
do
expect
{
service
.
execute
(
project_label_1_1
)
}
.
to
change
(
project_1
.
labels
,
:count
).
by
(
-
1
)
.
and
change
(
group_1
.
labels
,
:count
).
by
(
1
)
expect
(
new_label
).
not_to
be_nil
end
it
'copies title, description and color'
do
service
.
execute
(
project_label_1_1
)
expect
(
new_label
.
title
).
to
eq
(
promoted_label_name
)
expect
(
new_label
.
description
).
to
eq
(
promoted_description
)
expect
(
new_label
.
color
).
to
eq
(
promoted_color
)
end
it
'merges labels with the same name in
group'
do
it
'removes all project labels with that title within the
group'
do
expect
{
service
.
execute
(
project_label_1_1
)
}.
to
change
(
project_2
.
labels
,
:count
).
by
(
-
1
).
and
\
expect
{
service
.
execute
(
project_label_1_1
)
}.
to
change
(
project_2
.
labels
,
:count
).
by
(
-
1
).
and
\
change
(
project_3
.
labels
,
:count
).
by
(
-
1
)
change
(
project_3
.
labels
,
:count
).
by
(
-
1
)
end
end
it
'keeps users\' subscriptions'
do
it
'keeps users\' subscriptions'
do
user2
=
create
(
:user
)
user2
=
create
(
:user
)
...
@@ -94,108 +80,132 @@ RSpec.describe Labels::PromoteService do
...
@@ -94,108 +80,132 @@ RSpec.describe Labels::PromoteService do
project_label_3_2
.
subscriptions
.
create!
(
user:
user
,
subscribed:
true
)
project_label_3_2
.
subscriptions
.
create!
(
user:
user
,
subscribed:
true
)
project_label_2_1
.
subscriptions
.
create!
(
user:
user2
,
subscribed:
true
)
project_label_2_1
.
subscriptions
.
create!
(
user:
user2
,
subscribed:
true
)
expect
{
service
.
execute
(
project_label_1_1
)
}.
to
change
{
Subscription
.
count
}.
from
(
4
).
to
(
3
)
expect
{
service
.
execute
(
project_label_1_1
)
}.
to
change
{
Subscription
.
count
}.
from
(
4
).
to
(
3
)
expect
(
new_label
.
subscribed?
(
user
)).
to
be_truthy
expect
(
new_label
.
subscribed?
(
user
)).
to
be_truthy
expect
(
new_label
.
subscribed?
(
user2
)).
to
be_truthy
expect
(
new_label
.
subscribed?
(
user2
)).
to
be_truthy
end
end
it
'recreates priorities'
do
it
'recreates priorities'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
expect
(
new_label
.
priority
(
project_1
)).
to
be_nil
expect
(
new_label
.
priority
(
project_1
)).
to
be_nil
expect
(
new_label
.
priority
(
project_2
)).
to
eq
(
label_2_1_priority
)
expect
(
new_label
.
priority
(
project_2
)).
to
eq
(
label_2_1_priority
)
expect
(
new_label
.
priority
(
project_3
)).
to
eq
(
label_3_1_priority
)
expect
(
new_label
.
priority
(
project_3
)).
to
eq
(
label_3_1_priority
)
end
end
it
'does not touch project out of promoted group'
do
it
'does not touch project out of promoted group'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
project_4_new_label
=
project_4
.
labels
.
find_by
(
title:
promoted_label_name
)
project_4_new_label
=
project_4
.
labels
.
find_by
(
title:
promoted_label_name
)
expect
(
project_4_new_label
).
not_to
be_nil
expect
(
project_4_new_label
).
not_to
be_nil
expect
(
project_4_new_label
.
id
).
to
eq
(
project_label_4_1
.
id
)
expect
(
project_4_new_label
.
id
).
to
eq
(
project_label_4_1
.
id
)
end
end
it
'does not touch out of group priority'
do
it
'does not touch out of group priority'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
expect
(
new_label
.
priority
(
project_4
)).
to
be_nil
expect
(
new_label
.
priority
(
project_4
)).
to
be_nil
end
end
it
'relinks issue with the promoted label'
do
it
'relinks issue with the promoted label'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
issue_label
=
issue_1_1
.
labels
.
find_by
(
title:
promoted_label_name
)
issue_label
=
issue_1_1
.
labels
.
find_by
(
title:
promoted_label_name
)
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
.
id
).
to
eq
(
new_label
.
id
)
expect
(
issue_label
.
id
).
to
eq
(
new_label
.
id
)
end
end
it
'does not remove untouched labels from issue'
do
it
'does not remove untouched labels from issue'
do
expect
{
service
.
execute
(
project_label_1_1
)
}.
not_to
change
(
issue_1_1
.
labels
,
:count
)
expect
{
service
.
execute
(
project_label_1_1
)
}.
not_to
change
(
issue_1_1
.
labels
,
:count
)
end
end
it
'does not relink untouched label in issue'
do
it
'does not relink untouched label in issue'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
issue_label
=
issue_1_2
.
labels
.
find_by
(
title:
untouched_label_name
)
issue_label
=
issue_1_2
.
labels
.
find_by
(
title:
untouched_label_name
)
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
.
id
).
to
eq
(
project_label_1_2
.
id
)
expect
(
issue_label
.
id
).
to
eq
(
project_label_1_2
.
id
)
end
end
it
'relinks issues with merged labels'
do
it
'relinks issues with merged labels'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
issue_label
=
issue_2_1
.
labels
.
find_by
(
title:
promoted_label_name
)
issue_label
=
issue_2_1
.
labels
.
find_by
(
title:
promoted_label_name
)
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
.
id
).
to
eq
(
new_label
.
id
)
expect
(
issue_label
.
id
).
to
eq
(
new_label
.
id
)
end
end
it
'does not relink issues from other group'
do
it
'does not relink issues from other group'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
issue_label
=
issue_4_1
.
labels
.
find_by
(
title:
promoted_label_name
)
issue_label
=
issue_4_1
.
labels
.
find_by
(
title:
promoted_label_name
)
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
).
not_to
be_nil
expect
(
issue_label
.
id
).
to
eq
(
project_label_4_1
.
id
)
expect
(
issue_label
.
id
).
to
eq
(
project_label_4_1
.
id
)
end
end
it
'updates merge request'
do
it
'updates merge request'
do
service
.
execute
(
project_label_1_1
)
service
.
execute
(
project_label_1_1
)
merge_label
=
merge_3_1
.
labels
.
find_by
(
title:
promoted_label_name
)
merge_label
=
merge_3_1
.
labels
.
find_by
(
title:
promoted_label_name
)
expect
(
merge_label
).
not_to
be_nil
expect
(
merge_label
).
not_to
be_nil
expect
(
merge_label
.
id
).
to
eq
(
new_label
.
id
)
expect
(
merge_label
.
id
).
to
eq
(
new_label
.
id
)
end
end
it
'updates board lists'
do
service
.
execute
(
project_label_1_1
)
list
=
issue_board_2_1
.
lists
.
find_by
(
label:
new_label
)
it
'updates board lists'
do
expect
(
list
).
not_to
be_nil
service
.
execute
(
project_label_1_1
)
end
list
=
issue_board_2_1
.
lists
.
find_by
(
label:
new_label
)
# In case someone adds a new relation to Label.rb and forgets to relink it
# and the database doesn't have foreign key constraints
it
'relinks all relations'
do
service
.
execute
(
project_label_1_1
)
expect
(
list
).
not_to
be_nil
Label
.
reflect_on_all_associations
.
each
do
|
association
|
expect
(
project_label_1_1
.
send
(
association
.
name
).
any?
).
to
be_falsey
end
end
end
end
# In case someone adds a new relation to Label.rb and forgets to relink it
context
'if there is an existing identical group label'
do
# and the database doesn't have foreign key constraints
let!
(
:existing_group_label
)
{
create
(
:group_label
,
group:
group_1
,
title:
project_label_1_1
.
title
)
}
it
'relinks all relations'
do
service
.
execute
(
project_label_1_1
)
it
'uses the existing group label'
do
expect
{
service
.
execute
(
project_label_1_1
)
}
.
to
change
(
project_1
.
labels
,
:count
).
by
(
-
1
)
.
and
not_change
(
group_1
.
labels
,
:count
)
expect
(
new_label
).
not_to
be_nil
end
Label
.
reflect_on_all_associations
.
each
do
|
association
|
it
'does not create a new group label clone'
do
expect
(
project_label_1_1
.
send
(
association
.
name
).
any?
).
to
be_falsey
expect
{
service
.
execute
(
project_label_1_1
)
}.
not_to
change
{
GroupLabel
.
maximum
(
:id
)
}
end
end
it_behaves_like
'promoting a project label to a group label'
end
end
context
'with invalid group label'
do
context
'if there is no existing identical group label'
do
before
do
let
(
:existing_group_label
)
{
nil
}
allow
(
service
).
to
receive
(
:clone_label_to_group_label
).
and_wrap_original
do
|
m
,
*
args
|
label
=
m
.
call
(
*
args
)
allow
(
label
).
to
receive
(
:valid?
).
and_return
(
false
)
label
it
'recreates the label as a group label'
do
end
expect
{
service
.
execute
(
project_label_1_1
)
}
.
to
change
(
project_1
.
labels
,
:count
).
by
(
-
1
)
.
and
change
(
group_1
.
labels
,
:count
).
by
(
1
)
expect
(
new_label
).
not_to
be_nil
end
end
it
'raises an exception'
do
it
'copies title, description and color to cloned group label'
do
expect
{
service
.
execute
(
project_label_1_1
)
}.
to
raise_error
(
ActiveRecord
::
RecordInvalid
)
service
.
execute
(
project_label_1_1
)
expect
(
new_label
.
title
).
to
eq
(
promoted_label_name
)
expect
(
new_label
.
description
).
to
eq
(
promoted_description
)
expect
(
new_label
.
color
).
to
eq
(
promoted_color
)
end
end
it_behaves_like
'promoting a project label to a group label'
end
end
end
end
end
end
...
...
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