diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index a4c4c9e4812215ebca6ccdc819e34cf6d410c042..be9d1e48435754d9d0eed5ba90250129ae3717a9 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -489,6 +489,30 @@ module QuickActions "#{comment} #{TABLEFLIP}" end + desc "Lock the discussion" + explanation "Locks the discussion" + condition do + issuable.is_a?(Issuable) && + issuable.persisted? && + !issuable.discussion_locked? && + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + end + command :lock do + @updates[:discussion_locked] = true + end + + desc "Unlock the discussion" + explanation "Unlocks the discussion" + condition do + issuable.is_a?(Issuable) && + issuable.persisted? && + issuable.discussion_locked? && + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + end + command :unlock do + @updates[:discussion_locked] = false + end + # This is a dummy command, so that it appears in the autocomplete commands desc 'CC' params '@user' diff --git a/changelogs/unreleased/lock-unlock-quick-actions.yml b/changelogs/unreleased/lock-unlock-quick-actions.yml new file mode 100644 index 0000000000000000000000000000000000000000..9322d60ba528d76d8ddc287916add402275e467f --- /dev/null +++ b/changelogs/unreleased/lock-unlock-quick-actions.yml @@ -0,0 +1,5 @@ +--- +title: Add /lock and /unlock quick actions +merge_request: 15197 +author: Mehdi Lahmam (@mehlah) +type: added diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 8fdfd2a6f4dacb0f4dacd3197ae625b67d08ebd0..9ad9155258f0aabf2a87f1bd7be030b80cc67d6c 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -44,3 +44,5 @@ do. | `/shrug` | Append the comment with `¯\_(ツ)_/¯` | | /copy_metadata #issue | !merge_request | Copy labels and milestone from other issue or merge request | | `/confidential` | Makes the issue confidential | +| `/lock` | Lock the discussion | +| `/unlock` | Unlock the discussion | diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index bf1c157c4a23eadcca33fad1a606370e2fc2f06d..06cad9c00d27c58d5403f81779852421bf64f8ba 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -272,6 +272,28 @@ describe QuickActions::InterpretService do end end + shared_examples 'lock command' do + let(:issue) { create(:issue, project: project, discussion_locked: false) } + let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: false) } + + it 'returns discussion_locked: true if content contains /lock' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(discussion_locked: true) + end + end + + shared_examples 'unlock command' do + let(:issue) { create(:issue, project: project, discussion_locked: true) } + let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: true) } + + it 'returns discussion_locked: true if content contains /unlock' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(discussion_locked: false) + end + end + shared_examples 'empty command' do it 'populates {} if content contains an unsupported command' do _, updates = service.execute(content, issuable) @@ -786,6 +808,26 @@ describe QuickActions::InterpretService do let(:issuable) { issue } end + it_behaves_like 'lock command' do + let(:content) { '/lock' } + let(:issuable) { issue } + end + + it_behaves_like 'lock command' do + let(:content) { '/lock' } + let(:issuable) { merge_request } + end + + it_behaves_like 'unlock command' do + let(:content) { '/unlock' } + let(:issuable) { issue } + end + + it_behaves_like 'unlock command' do + let(:content) { '/unlock' } + let(:issuable) { merge_request } + end + context '/todo' do let(:content) { '/todo' } @@ -961,6 +1003,16 @@ describe QuickActions::InterpretService do let(:content) { '/duplicate #{issue.to_reference}' } let(:issuable) { issue } end + + it_behaves_like 'empty command' do + let(:content) { '/lock' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/unlock' } + let(:issuable) { issue } + end end context '/award command' do diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 9b44c532ff60b388db5d3412755f05b9a0d6ecb7..f19ba3c5cf950f2837ab1fc821b971485483b059 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -285,6 +285,80 @@ shared_examples 'issuable record that supports quick actions in its description expect(issuable.reload.assignees).to eq [maintainer] end end + + context "with a note locking the #{issuable_type} discussion" do + before do + issuable.update(discussion_locked: false) + expect(issuable).not_to be_discussion_locked + end + + context "when current user can lock #{issuable_type} discussion" do + it "locks the #{issuable_type} discussion" do + add_note("/lock") + + expect(page).not_to have_content '/lock' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload).to be_discussion_locked + end + end + + context "when current user cannot lock #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) + end + + it "does not lock the #{issuable_type} discussion" do + add_note("/lock") + + expect(page).not_to have_content 'Commands applied' + + expect(issuable).not_to be_discussion_locked + end + end + end + + context "with a note unlocking the #{issuable_type} discussion" do + before do + issuable.update(discussion_locked: true) + expect(issuable).to be_discussion_locked + end + + context "when current user can unlock #{issuable_type} discussion" do + it "unlocks the #{issuable_type} discussion" do + add_note("/unlock") + + expect(page).not_to have_content '/unlock' + expect(page).to have_content 'Commands applied' + + expect(issuable.reload).not_to be_discussion_locked + end + end + + context "when current user cannot unlock #{issuable_type}" do + before do + guest = create(:user) + project.add_guest(guest) + + gitlab_sign_out + gitlab_sign_in(guest) + visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) + end + + it "does not unlock the #{issuable_type} discussion" do + add_note("/unlock") + + expect(page).not_to have_content 'Commands applied' + + expect(issuable).to be_discussion_locked + end + end + end end describe "preview of note on #{issuable_type}", :js do