Commit 0128b699 authored by Eulyeon Ko's avatar Eulyeon Ko Committed by Patrick Bajao

Keep labels sorted by title after mutation

Labels assigned to an issuable should be kept sorted by title
after mutation.

This commit adds '.reset' to issue and merge request labels
in their mutation resolvers so they get reloaded with
the sort order 'title asc'.

For epic, only the spec coverage is added as
the mutation for epic already utilized '.reset'.

Changelog: changed
parent 65144ab3
...@@ -539,6 +539,9 @@ class IssuableBaseService < ::BaseProjectService ...@@ -539,6 +539,9 @@ class IssuableBaseService < ::BaseProjectService
def handle_label_changes(issuable, old_labels) def handle_label_changes(issuable, old_labels)
return unless has_label_changes?(issuable, old_labels) return unless has_label_changes?(issuable, old_labels)
# reset to preserve the label sort order (title ASC)
issuable.labels.reset
GraphqlTriggers.issuable_labels_updated(issuable) GraphqlTriggers.issuable_labels_updated(issuable)
end end
......
...@@ -8,9 +8,9 @@ RSpec.describe Mutations::Epics::Update do ...@@ -8,9 +8,9 @@ RSpec.describe Mutations::Epics::Update do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:label_1) { create(:group_label, group: group) } let(:label_1) { create(:group_label, title: "a", group: group) }
let(:label_2) { create(:group_label, group: group) } let(:label_2) { create(:group_label, title: "b", group: group) }
let(:label_3) { create(:group_label, group: group) } let(:label_3) { create(:group_label, title: "c", group: group) }
let(:epic) { create(:epic, group: group, title: 'original title', labels: [label_2]) } let(:epic) { create(:epic, group: group, title: 'original title', labels: [label_2]) }
let(:attributes) do let(:attributes) do
...@@ -25,9 +25,8 @@ RSpec.describe Mutations::Epics::Update do ...@@ -25,9 +25,8 @@ RSpec.describe Mutations::Epics::Update do
} }
end end
let(:params) { { group_path: group.full_path, iid: epic.iid.to_s }.merge(attributes) }
let(:mutation) do let(:mutation) do
params = { group_path: group.full_path, iid: epic.iid.to_s }.merge(attributes)
graphql_mutation(:update_epic, params) graphql_mutation(:update_epic, params)
end end
...@@ -106,12 +105,39 @@ RSpec.describe Mutations::Epics::Update do ...@@ -106,12 +105,39 @@ RSpec.describe Mutations::Epics::Update do
context 'when changing labels of the epic' do context 'when changing labels of the epic' do
let(:attributes) { { add_label_ids: [label_1.id, label_3.id], remove_label_ids: label_2.id } } let(:attributes) { { add_label_ids: [label_1.id, label_3.id], remove_label_ids: label_2.id } }
let(:mutation) do
graphql_mutation(:update_epic, params) do
<<~QL
epic {
labels {
nodes {
id
}
}
}
errors
QL
end
end
it 'adds and removes labels correctly' do it 'adds and removes labels correctly' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(epic.reload.labels).to match_array([label_1, label_3]) expect(epic.reload.labels).to match_array([label_1, label_3])
end end
context 'when labels are added' do
let(:attributes) { { add_label_ids: [label_1.id, label_3.id] } }
it 'adds labels correctly and keeps the title ordering' do
post_graphql_mutation(mutation, current_user: current_user)
labels_ids = mutation_response['epic']['labels']['nodes'].map { |l| l['id'] }
expected_label_ids = [label_1, label_2, label_3].map { |l| l.to_global_id.to_s }
expect(labels_ids).to eq(expected_label_ids)
end
end
end end
context 'when there are ActiveRecord validation errors' do context 'when there are ActiveRecord validation errors' do
......
...@@ -537,11 +537,14 @@ RSpec.describe Epics::UpdateService do ...@@ -537,11 +537,14 @@ RSpec.describe Epics::UpdateService do
let(:parent) { group } let(:parent) { group }
end end
it_behaves_like 'broadcasting issuable labels updates' do context 'labels are updated' do
let(:label_a) { create(:group_label, group: group) } let(:label_a) { create(:group_label, title: 'a', group: group) }
let(:label_b) { create(:group_label, group: group) } let(:label_b) { create(:group_label, title: 'b', group: group) }
let(:issuable) { epic } let(:issuable) { epic }
it_behaves_like 'keeps issuable labels sorted after update'
it_behaves_like 'broadcasting issuable labels updates'
def update_issuable(update_params) def update_issuable(update_params)
update_epic(update_params) update_epic(update_params)
end end
......
...@@ -8,8 +8,8 @@ RSpec.describe 'Update of an existing issue' do ...@@ -8,8 +8,8 @@ RSpec.describe 'Update of an existing issue' do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:label1) { create(:label, project: project) } let_it_be(:label1) { create(:label, title: "a", project: project) }
let_it_be(:label2) { create(:label, project: project) } let_it_be(:label2) { create(:label, title: "b", project: project) }
let(:input) do let(:input) do
{ {
...@@ -124,7 +124,7 @@ RSpec.describe 'Update of an existing issue' do ...@@ -124,7 +124,7 @@ RSpec.describe 'Update of an existing issue' do
context 'add and remove labels' do context 'add and remove labels' do
let(:input_params) { input.merge(extra_params).merge({ addLabelIds: [label1.id], removeLabelIds: [label2.id] }) } let(:input_params) { input.merge(extra_params).merge({ addLabelIds: [label1.id], removeLabelIds: [label2.id] }) }
it 'returns error for mutually exclusive arguments' do it 'returns correct labels' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
...@@ -132,6 +132,22 @@ RSpec.describe 'Update of an existing issue' do ...@@ -132,6 +132,22 @@ RSpec.describe 'Update of an existing issue' do
expect(mutation_response['issue']['labels']).to include({ "nodes" => [{ "id" => label1.to_global_id.to_s }] }) expect(mutation_response['issue']['labels']).to include({ "nodes" => [{ "id" => label1.to_global_id.to_s }] })
end end
end end
context 'add labels' do
let(:input_params) { input.merge(extra_params).merge({ addLabelIds: [label1.id] }) }
before do
issue.update!({ labels: [label2] })
end
it 'adds labels and keeps the title ordering' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(json_response['errors']).to be_nil
expect(mutation_response['issue']['labels']['nodes']).to eq([{ "id" => label1.to_global_id.to_s }, { "id" => label2.to_global_id.to_s }])
end
end
end end
end end
end end
...@@ -8,8 +8,8 @@ RSpec.describe 'Setting labels of a merge request' do ...@@ -8,8 +8,8 @@ RSpec.describe 'Setting labels of a merge request' do
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
let(:merge_request) { create(:merge_request) } let(:merge_request) { create(:merge_request) }
let(:project) { merge_request.project } let(:project) { merge_request.project }
let(:label) { create(:label, project: project) } let(:label) { create(:label, title: "a", project: project) }
let(:label2) { create(:label, project: project) } let(:label2) { create(:label, title: "b", project: project) }
let(:input) { { label_ids: [GitlabSchema.id_from_object(label).to_s] } } let(:input) { { label_ids: [GitlabSchema.id_from_object(label).to_s] } }
let(:mutation) do let(:mutation) do
...@@ -81,12 +81,12 @@ RSpec.describe 'Setting labels of a merge request' do ...@@ -81,12 +81,12 @@ RSpec.describe 'Setting labels of a merge request' do
merge_request.update!(labels: [label2]) merge_request.update!(labels: [label2])
end end
it 'sets the labels, without removing others' do it 'sets the labels and resets labels to keep the title ordering, without removing others' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
expect(mutation_label_nodes.count).to eq(2) expect(mutation_label_nodes.count).to eq(2)
expect(mutation_label_nodes).to contain_exactly({ 'id' => label.to_global_id.to_s }, { 'id' => label2.to_global_id.to_s }) expect(mutation_label_nodes).to eq([{ 'id' => label.to_global_id.to_s }, { 'id' => label2.to_global_id.to_s }])
end end
end end
......
...@@ -9,8 +9,8 @@ RSpec.describe Issues::UpdateService, :mailer do ...@@ -9,8 +9,8 @@ RSpec.describe Issues::UpdateService, :mailer do
let_it_be(:guest) { create(:user) } let_it_be(:guest) { create(:user) }
let_it_be(:group) { create(:group, :public, :crm_enabled) } let_it_be(:group) { create(:group, :public, :crm_enabled) }
let_it_be(:project, reload: true) { create(:project, :repository, group: group) } let_it_be(:project, reload: true) { create(:project, :repository, group: group) }
let_it_be(:label) { create(:label, project: project) } let_it_be(:label) { create(:label, title: 'a', project: project) }
let_it_be(:label2) { create(:label, project: project) } let_it_be(:label2) { create(:label, title: 'b', project: project) }
let_it_be(:milestone) { create(:milestone, project: project) } let_it_be(:milestone) { create(:milestone, project: project) }
let(:issue) do let(:issue) do
...@@ -1361,11 +1361,14 @@ RSpec.describe Issues::UpdateService, :mailer do ...@@ -1361,11 +1361,14 @@ RSpec.describe Issues::UpdateService, :mailer do
end end
end end
it_behaves_like 'broadcasting issuable labels updates' do context 'labels are updated' do
let(:label_a) { label } let(:label_a) { label }
let(:label_b) { label2 } let(:label_b) { label2 }
let(:issuable) { issue } let(:issuable) { issue }
it_behaves_like 'keeps issuable labels sorted after update'
it_behaves_like 'broadcasting issuable labels updates'
def update_issuable(update_params) def update_issuable(update_params)
update_issue(update_params) update_issue(update_params)
end end
......
...@@ -10,7 +10,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do ...@@ -10,7 +10,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
let(:user3) { create(:user) } let(:user3) { create(:user) }
let(:label) { create(:label, project: project) } let(:label) { create(:label, title: 'a', project: project) }
let(:label2) { create(:label) } let(:label2) { create(:label) }
let(:milestone) { create(:milestone, project: project) } let(:milestone) { create(:milestone, project: project) }
...@@ -1193,11 +1193,14 @@ RSpec.describe MergeRequests::UpdateService, :mailer do ...@@ -1193,11 +1193,14 @@ RSpec.describe MergeRequests::UpdateService, :mailer do
let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute(existing_merge_request) } let(:issuable) { described_class.new(project: project, current_user: user, params: params).execute(existing_merge_request) }
end end
it_behaves_like 'broadcasting issuable labels updates' do context 'labels are updated' do
let(:label_a) { label } let(:label_a) { label }
let(:label_b) { create(:label, project: project) } let(:label_b) { create(:label, title: 'b', project: project) }
let(:issuable) { merge_request } let(:issuable) { merge_request }
it_behaves_like 'keeps issuable labels sorted after update'
it_behaves_like 'broadcasting issuable labels updates'
def update_issuable(update_params) def update_issuable(update_params)
update_merge_request(update_params) update_merge_request(update_params)
end end
......
...@@ -24,6 +24,20 @@ RSpec.shared_examples 'issuable update service' do ...@@ -24,6 +24,20 @@ RSpec.shared_examples 'issuable update service' do
end end
end end
RSpec.shared_examples 'keeps issuable labels sorted after update' do
before do
update_issuable(label_ids: [label_b.id])
end
context 'when label is changed' do
it 'keeps the labels sorted by title ASC' do
update_issuable({ add_label_ids: [label_a.id] })
expect(issuable.labels).to eq([label_a, label_b])
end
end
end
RSpec.shared_examples 'broadcasting issuable labels updates' do RSpec.shared_examples 'broadcasting issuable labels updates' do
before do before do
update_issuable(label_ids: [label_a.id]) update_issuable(label_ids: [label_a.id])
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment