From 6b0ea951cac3905437abb2bbacaf422371f097e0 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Fri, 19 Oct 2018 09:16:58 +0200 Subject: [PATCH] Creates /create_merge_request quickaction With this quick action the user can create a new MR starting from the current issue using as `source_branch` the given `branch name` and as `target_branch` the project default branch. If the `branch name` is omitted a name is automatically created starting from the issue title. --- app/services/issues/update_service.rb | 13 +++++ .../create_from_issue_service.rb | 6 +- .../quick_actions/interpret_service.rb | 16 +++++ app/services/system_note_service.rb | 8 ++- ...dd-a-create_merge_request-quick-action.yml | 5 ++ doc/user/project/quick_actions.md | 1 + ...r_creates_branch_and_merge_request_spec.rb | 4 +- .../issues/user_uses_quick_actions_spec.rb | 58 +++++++++++++++++++ .../create_from_issue_service_spec.rb | 10 +++- .../quick_actions/interpret_service_spec.rb | 53 +++++++++++++++++ spec/services/system_note_service_spec.rb | 14 +++++ 11 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index fba252b0bae..a1d0cc0e568 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -11,6 +11,12 @@ module Issues move_issue_to_new_project(issue) || update(issue) end + def update(issue) + create_merge_request_from_quick_action + + super + end + def before_update(issue) spam_check(issue, current_user) end @@ -93,6 +99,13 @@ module Issues private + def create_merge_request_from_quick_action + create_merge_request_params = params.delete(:create_merge_request) + return unless create_merge_request_params + + MergeRequests::CreateFromIssueService.new(project, current_user, create_merge_request_params).execute + end + def handle_milestone_change(issue) return if skip_milestone_email diff --git a/app/services/merge_requests/create_from_issue_service.rb b/app/services/merge_requests/create_from_issue_service.rb index 020af0bb950..e69791872cc 100644 --- a/app/services/merge_requests/create_from_issue_service.rb +++ b/app/services/merge_requests/create_from_issue_service.rb @@ -19,13 +19,15 @@ module MergeRequests result = CreateBranchService.new(project, current_user).execute(branch_name, ref) return result if result[:status] == :error - SystemNoteService.new_issue_branch(issue, project, current_user, branch_name) - new_merge_request = create(merge_request) if new_merge_request.valid? + SystemNoteService.new_merge_request(issue, project, current_user, new_merge_request) + success(new_merge_request) else + SystemNoteService.new_issue_branch(issue, project, current_user, branch_name) + error(new_merge_request.errors) end end diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 9c81de7e90e..d248b10f41e 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -635,6 +635,22 @@ module QuickActions @updates[:tag_message] = message end + desc 'Create a merge request.' + explanation do |branch_name = nil| + branch_text = branch_name ? "branch '#{branch_name}'" : 'a branch' + "Creates #{branch_text} and a merge request to resolve this issue" + end + params "" + condition do + issuable.is_a?(Issue) && current_user.can?(:create_merge_request_in, project) && current_user.can?(:push_code, project) + end + command :create_merge_request do |branch_name = nil| + @updates[:create_merge_request] = { + branch_name: branch_name, + issue_iid: issuable.iid + } + end + # rubocop: disable CodeReuse/ActiveRecord def extract_users(params) return [] if params.nil? diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 729bc991294..ec6c306227b 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -407,11 +407,17 @@ module SystemNoteService def new_issue_branch(issue, project, author, branch) link = url_helpers.project_compare_url(project, from: project.default_branch, to: branch) - body = "created branch [`#{branch}`](#{link})" + body = "created branch [`#{branch}`](#{link}) to address this issue" create_note(NoteSummary.new(issue, project, author, body, action: 'branch')) end + def new_merge_request(issue, project, author, merge_request) + body = "created merge request #{merge_request.to_reference} to address this issue" + + create_note(NoteSummary.new(issue, project, author, body, action: 'merge')) + end + # Called when a Mentionable references a Noteable # # noteable - Noteable object being referenced diff --git a/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml b/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml new file mode 100644 index 00000000000..e1614ac7669 --- /dev/null +++ b/changelogs/unreleased/40085-add-a-create_merge_request-quick-action.yml @@ -0,0 +1,5 @@ +--- +title: Creates /create_merge_request quickaction +merge_request: 22485 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 0a4542b71ed..85a03d125dd 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -53,6 +53,7 @@ discussions, and descriptions: | `/target_branch ` | Set target branch | | ✓ | | `/wip` | Toggle the Work In Progress status | | ✓ | | `/merge` | Merge (when pipeline succeeds) | | ✓ | +| `/create_merge_request ` | Create a new merge request starting from the current issue | ✓ | | ## Quick actions for commit messages diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 3dfcbc2fcb8..a912c327728 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -76,7 +76,7 @@ describe 'User creates branch and merge request on issue page', :js do visit project_issue_path(project, issue) - expect(page).to have_content('created branch 1-cherry-coloured-funk') + expect(page).to have_content("created merge request !1 to address this issue") expect(page).to have_content('mentioned in merge request !1') end @@ -106,7 +106,7 @@ describe 'User creates branch and merge request on issue page', :js do visit project_issue_path(project, issue) - expect(page).to have_content('created branch custom-branch-name') + expect(page).to have_content("created merge request !1 to address this issue") expect(page).to have_content('mentioned in merge request !1') end diff --git a/spec/features/issues/user_uses_quick_actions_spec.rb b/spec/features/issues/user_uses_quick_actions_spec.rb index 5926e442f24..27cffdc5f8b 100644 --- a/spec/features/issues/user_uses_quick_actions_spec.rb +++ b/spec/features/issues/user_uses_quick_actions_spec.rb @@ -303,5 +303,63 @@ describe 'Issues > User uses quick actions', :js do end end end + + describe 'create a merge request starting from an issue' do + let(:project) { create(:project, :public, :repository) } + let(:issue) { create(:issue, project: project) } + + def expect_mr_quickaction(success) + expect(page).to have_content 'Commands applied' + + if success + expect(page).to have_content 'created merge request' + else + expect(page).not_to have_content 'created merge request' + end + end + + it "doesn't create a merge request when the branch name is invalid" do + add_note("/create_merge_request invalid branch name") + + wait_for_requests + + expect_mr_quickaction(false) + end + + it "doesn't create a merge request when a branch with that name already exists" do + add_note("/create_merge_request feature") + + wait_for_requests + + expect_mr_quickaction(false) + end + + it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do + add_note("/create_merge_request") + + wait_for_requests + + expect_mr_quickaction(true) + + created_mr = project.merge_requests.last + expect(created_mr.source_branch).to eq(issue.to_branch_name) + + visit project_merge_request_path(project, created_mr) + expect(page).to have_content %{WIP: Resolve "#{issue.title}"} + end + + it 'creates a merge request using the given branch name' do + branch_name = '1-feature' + add_note("/create_merge_request #{branch_name}") + + expect_mr_quickaction(true) + + created_mr = project.merge_requests.last + expect(created_mr.source_branch).to eq(branch_name) + + visit project_merge_request_path(project, created_mr) + expect(page).to have_content %{WIP: Resolve "#{issue.title}"} + end + end end end diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb index b1882df732d..393299cce00 100644 --- a/spec/services/merge_requests/create_from_issue_service_spec.rb +++ b/spec/services/merge_requests/create_from_issue_service_spec.rb @@ -61,7 +61,15 @@ describe MergeRequests::CreateFromIssueService do expect(project.repository.branch_exists?(custom_source_branch)).to be_truthy end - it 'creates a system note' do + it 'creates the new_merge_request system note' do + expect(SystemNoteService).to receive(:new_merge_request).with(issue, project, user, instance_of(MergeRequest)) + + service.execute + end + + it 'creates the new_issue_branch system note when the branch could be created but the merge_request cannot be created' do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED) + expect(SystemNoteService).to receive(:new_issue_branch).with(issue, project, user, issue.to_branch_name) service.execute diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index 5a7cafcb60f..938764f40b0 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1222,6 +1222,37 @@ describe QuickActions::InterpretService do expect(commands).to be_empty expect(text).to eq("#{described_class::SHRUG}\n/close") end + + context '/create_merge_request command' do + let(:branch_name) { '1-feature' } + let(:content) { "/create_merge_request #{branch_name}" } + let(:issuable) { issue } + + context 'if issuable is not an Issue' do + let(:issuable) { merge_request } + + it_behaves_like 'empty command' + end + + context "when logged user cannot create_merge_requests in the project" do + let(:project) { create(:project, :archived) } + + it_behaves_like 'empty command' + end + + context 'when logged user cannot push code to the project' do + let(:project) { create(:project, :private) } + let(:service) { described_class.new(project, create(:user)) } + + it_behaves_like 'empty command' + end + + it 'populates create_merge_request with branch_name and issue iid' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(create_merge_request: { branch_name: branch_name, issue_iid: issuable.iid }) + end + end end describe '#explain' do @@ -1473,5 +1504,27 @@ describe QuickActions::InterpretService do end end end + + describe 'create a merge request' do + context 'with no branch name' do + let(:content) { '/create_merge_request' } + + it 'uses the default branch name' do + _, explanations = service.explain(content, issue) + + expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue']) + end + end + + context 'with a branch name' do + let(:content) { '/create_merge_request foo' } + + it 'uses the given branch name' do + _, explanations = service.explain(content, issue) + + expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue"]) + end + end + end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index a18126ee339..0fbfcb34e50 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -432,6 +432,20 @@ describe SystemNoteService do end end + describe '.new_merge_request' do + subject { described_class.new_merge_request(noteable, project, author, merge_request) } + + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + + it_behaves_like 'a system note' do + let(:action) { 'merge' } + end + + it 'sets the new merge request note text' do + expect(subject.note).to eq("created merge request #{merge_request.to_reference} to address this issue") + end + end + describe '.cross_reference' do subject { described_class.cross_reference(noteable, mentioner, author) }