From 617e6f1caec0abcd1795f33bed2a83b9f6ec9d92 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Thu, 13 Feb 2025 17:08:22 -0600 Subject: [PATCH 1/3] Add create MR to work item branch When work items have related branches, show an action to create an MR for that branch. Changelog: added --- .../work_item_development.vue | 3 ++ .../work_item_development_branch_item.vue | 34 ++++++++++++ ...ork_item_development_relationship_list.vue | 22 +++++++- app/assets/javascripts/work_items/utils.js | 8 ++- .../work_item_development_branch_item_spec.js | 52 ++++++++++++++++++- ...item_development_relationship_list_spec.js | 3 ++ 6 files changed, 118 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue b/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue index d025a772ddc89c..b4bde56539da1c 100644 --- a/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue +++ b/app/assets/javascripts/work_items/components/work_item_development/work_item_development.vue @@ -287,6 +287,9 @@ export default { v-if="!isRelatedDevelopmentListEmpty" :is-modal="isModal" :work-item-dev-widget="workItemDevelopment" + :work-item-full-path="workItemFullPath" + :work-item-iid="workItemIid" + :can-create-merge-request="showAddButton" /> diff --git a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue index 3f1ce3a1703d47..2d85914d9016ce 100644 --- a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue +++ b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue @@ -5,10 +5,13 @@ import { GlDisclosureDropdown, GlDisclosureDropdownItem, GlTooltipDirective, + GlDropdownDivider, } from '@gitlab/ui'; import { __ } from '~/locale'; import TooltipOnTruncate from '~/vue_shared/directives/tooltip_on_truncate'; import toast from '~/vue_shared/plugins/global_toast'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { createBranchMRApiPathHelper } from '~/work_items/utils'; export default { components: { @@ -16,6 +19,7 @@ export default { GlLink, GlDisclosureDropdown, GlDisclosureDropdownItem, + GlDropdownDivider, }, directives: { GlTooltip: GlTooltipDirective, @@ -31,6 +35,19 @@ export default { required: false, default: false, }, + canCreateMergeRequest: { + type: Boolean, + required: false, + default: true, + }, + workItemFullPath: { + type: String, + required: true, + }, + workItemIid: { + type: String, + required: true, + }, }, computed: { branchName() { @@ -54,6 +71,15 @@ export default { closeDropdown() { this.$refs.branchMoreActions.close(); }, + createMergeRequest() { + const path = createBranchMRApiPathHelper.createMR({ + fullPath: this.workItemFullPath, + workItemIid: this.workItemIid, + sourceBranch: this.branchName, + }); + + visitUrl(path); + }, }, }; @@ -89,6 +115,14 @@ export default { no-caret placement="bottom-end" > + + + + - + diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index 5ff9cd5840df57..85b2f48c855cf1 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -388,10 +388,14 @@ export const createBranchMRApiPathHelper = { ); }, createMR({ fullPath, workItemIid, sourceBranch, targetBranch }) { - return joinPaths( + let url = joinPaths( gon.relative_url_root || '', - `/${fullPath}/-/merge_requests/new?merge_request%5Bissue_iid%5D=${workItemIid}&merge_request%5Bsource_branch%5D=${sourceBranch}&merge_request%5Btarget_branch%5D=${targetBranch}`, + `/${fullPath}/-/merge_requests/new?merge_request%5Bissue_iid%5D=${workItemIid}&merge_request%5Bsource_branch%5D=${sourceBranch}`, ); + if (targetBranch) { + url += `&merge_request%5Btarget_branch%5D=${targetBranch}`; + } + return url; }, getRefs({ fullPath }) { return joinPaths(gon.relative_url_root || '', `/${fullPath}/refs?search=`); diff --git a/spec/frontend/work_items/components/work_item_development/work_item_development_branch_item_spec.js b/spec/frontend/work_items/components/work_item_development/work_item_development_branch_item_spec.js index ec61b86b7fd1b3..c217b2a9c88eda 100644 --- a/spec/frontend/work_items/components/work_item_development/work_item_development_branch_item_spec.js +++ b/spec/frontend/work_items/components/work_item_development/work_item_development_branch_item_spec.js @@ -2,22 +2,40 @@ import { GlLink, GlIcon } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { workItemRelatedBranchNodes } from 'jest/work_items/mock_data'; import WorkItemDevelopmentBranchItem from '~/work_items/components/work_item_development/work_item_development_branch_item.vue'; +import { visitUrl } from '~/lib/utils/url_utility'; +import { createBranchMRApiPathHelper } from '~/work_items/utils'; + +jest.mock('~/lib/utils/url_utility', () => ({ + ...jest.requireActual('~/lib/utils/url_utility'), + visitUrl: jest.fn(), +})); +jest.mock('~/work_items/utils', () => ({ + createBranchMRApiPathHelper: { + createMR: jest.fn(), + }, +})); describe('WorkItemDevelopmentBranchItem', () => { let wrapper; const branchNode = workItemRelatedBranchNodes[0]; + const workItemFullPath = 'flightjs/Flight'; + const workItemIid = '1'; - const createComponent = ({ branch = branchNode } = {}) => { + const createComponent = ({ branch = branchNode, canCreateMergeRequest = true } = {}) => { wrapper = shallowMount(WorkItemDevelopmentBranchItem, { propsData: { itemContent: branch, + canCreateMergeRequest, + workItemFullPath, + workItemIid, }, }); }; const findIcon = () => wrapper.findComponent(GlIcon); const findLink = () => wrapper.findComponent(GlLink); + const findCreateMRAction = () => wrapper.find('[data-testid="branch-create-merge-request"]'); it('should render the comparePath and name with icon', () => { createComponent(); @@ -27,4 +45,36 @@ describe('WorkItemDevelopmentBranchItem', () => { expect(findLink().attributes('href')).toBe(branchNode.comparePath); expect(findLink().text()).toBe(branchNode.name); }); + + describe('dropdown actions', () => { + describe('when user can create a merge request', () => { + beforeEach(() => { + createComponent({ canCreateMergeRequest: true }); + createBranchMRApiPathHelper.createMR.mockReturnValue( + '/fullPath/-/merge_requests/new?merge_request%5Bissue_iid%5D=1&merge_request%5Bsource_branch%5D=branch_name', + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('shows the create merge request action', () => { + expect(findCreateMRAction().exists()).toBe(true); + }); + + it('navigates to the correct URL when create merge request is clicked', async () => { + await findCreateMRAction().vm.$emit('action'); + + expect(createBranchMRApiPathHelper.createMR).toHaveBeenCalledWith({ + fullPath: workItemFullPath, + workItemIid, + sourceBranch: branchNode.name, + }); + expect(visitUrl).toHaveBeenCalledWith( + '/fullPath/-/merge_requests/new?merge_request%5Bissue_iid%5D=1&merge_request%5Bsource_branch%5D=branch_name', + ); + }); + }); + }); }); diff --git a/spec/frontend/work_items/components/work_item_development/work_item_development_relationship_list_spec.js b/spec/frontend/work_items/components/work_item_development/work_item_development_relationship_list_spec.js index 0e5d42adabc5dc..39b97149b92519 100644 --- a/spec/frontend/work_items/components/work_item_development/work_item_development_relationship_list_spec.js +++ b/spec/frontend/work_items/components/work_item_development/work_item_development_relationship_list_spec.js @@ -14,6 +14,9 @@ describe('WorkItemDevelopmentRelationshipList', () => { wrapper = shallowMountExtended(WorkItemDevelopmentRelationshipList, { propsData: { workItemDevWidget, + canCreateMergeRequest: true, + workItemFullPath: 'gitlab-org/gitlab-test', + workItemIid: '1', }, }); }; -- GitLab From aafcfd2631e13a7c0547b19c0d05c2f197c6f267 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Tue, 11 Mar 2025 23:54:50 -0500 Subject: [PATCH 2/3] Add unit tests --- spec/frontend/work_items/utils_spec.js | 43 ++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js index c42a981eb6be80..d379823171092d 100644 --- a/spec/frontend/work_items/utils_spec.js +++ b/spec/frontend/work_items/utils_spec.js @@ -38,6 +38,7 @@ import { formatSelectOptionForCustomField, preserveDetailsState, getParentGroupName, + createBranchMRApiPathHelper, } from '~/work_items/utils'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { TYPE_EPIC } from '~/issues/constants'; @@ -476,3 +477,45 @@ describe('preserveDetailsState', () => { `); }); }); +describe('createMR', () => { + const fullPath = 'gitlab-org/gitlab'; + const workItemIID = '12'; + const sourceBranch = '12-fix'; + const targetBranch = 'main'; + + it('returns MR url with target branch', () => { + const path = createBranchMRApiPathHelper.createMR({ + fullPath, + workItemIid: workItemIID, + sourceBranch, + targetBranch, + }); + expect(path).toBe( + '/gitlab-org/gitlab/-/merge_requests/new?merge_request%5Bissue_iid%5D=12&merge_request%5Bsource_branch%5D=12-fix&merge_request%5Btarget_branch%5D=main', + ); + }); + + it('returns MR url without target branch', () => { + const path = createBranchMRApiPathHelper.createMR({ + fullPath, + workItemIid: workItemIID, + sourceBranch, + }); + expect(path).toBe( + '/gitlab-org/gitlab/-/merge_requests/new?merge_request%5Bissue_iid%5D=12&merge_request%5Bsource_branch%5D=12-fix', + ); + }); + + it('returns MR url with relative url', () => { + gon.relative_url_root = '/foobar'; + + const path = createBranchMRApiPathHelper.createMR({ + fullPath, + workItemIid: workItemIID, + sourceBranch, + }); + expect(path).toBe( + '/foobar/gitlab-org/gitlab/-/merge_requests/new?merge_request%5Bissue_iid%5D=12&merge_request%5Bsource_branch%5D=12-fix', + ); + }); +}); -- GitLab From 832ae7c8b293cbcf38e8adabfb7e1287494986f6 Mon Sep 17 00:00:00 2001 From: Nick Leonard Date: Mon, 24 Mar 2025 15:13:11 -0500 Subject: [PATCH 3/3] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Deepika Guliani --- .../work_item_development/work_item_development_branch_item.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue index 2d85914d9016ce..d55d0feece8270 100644 --- a/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue +++ b/app/assets/javascripts/work_items/components/work_item_development/work_item_development_branch_item.vue @@ -118,7 +118,7 @@ export default { -- GitLab