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 d025a772ddc89cecb3feceb0f633c89899a6e059..b4bde56539da1c706d8980e4bf637e8d9952f608 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 3f1ce3a1703d479a08baaa406101f18eb56f9a53..d55d0feece82707518d22c5102b6698093267758 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"
>
+
+ {{ __('Create merge request') }}
+
+
-
+
diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js
index 5ff9cd5840df570178fdfae9c5159478288cc001..85b2f48c855cf14e789ce9068c9178c45c11af76 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 ec61b86b7fd1b3013aabf91ad7f8eed0273e0f50..c217b2a9c88edac3c7bd99b7a1e485154a9db0e6 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 0e5d42adabc5dcf3bfeea50aed0bf2c6ca4ba7ee..39b97149b92519145c9df4bfff6179cd9c3d6646 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',
},
});
};
diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js
index c42a981eb6be80ef6ce54d868012f9ab30b10d19..d379823171092d0b01b338deff5e0c362a0461d8 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',
+ );
+ });
+});