diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index fa7a5b82549d5944312e17bea4092649813919aa..1e74e240f752aaee803a826c082419a009df1878 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -881,9 +881,24 @@ export default { this.viewType = ISSUES_LIST_VIEW_KEY; }, handleSelectIssuable(issuable) { - this.activeIssuable = { - ...issuable, - }; + if ( + this.issuesDrawerEnabled && + this.activeIssuable && + this.activeIssuable.iid === issuable.iid + ) { + this.activeIssuable = null; + + const queryParam = getParameterByName(DETAIL_VIEW_QUERY_PARAM_NAME); + if (queryParam) { + updateHistory({ + url: removeParams([DETAIL_VIEW_QUERY_PARAM_NAME]), + }); + } + } else { + this.activeIssuable = { + ...issuable, + }; + } }, updateIssuablesCache(workItem) { const client = this.$apollo.provider.clients.defaultClient; diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index cc1dcaa9b82b808c742d95ac4989e032859df7ed..5267b10620d6a51758d04672c59988e427252602 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -619,7 +619,11 @@ export default { return; } - this.activeChildItem = modalWorkItem; + if (this.activeChildItem && this.activeChildItem.iid === modalWorkItem.iid) { + this.activeChildItem = null; + } else { + this.activeChildItem = modalWorkItem; + } }, openReportAbuseModal(reply) { if (this.isModal) { diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue index ffc06b2459d4d8cbd6154c277115e1739202507a..7a8fb112696881bf3341df770c540d4092c8fd7b 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_children_wrapper.vue @@ -570,7 +570,7 @@ export default { @mouseout="clearPrefetching" @removeChild="removeChild" @error="$emit('error', $event)" - @click="onClick($event, child)" + @click.stop="onClick($event, child)" @click.native="onClick($event, child)" /> </component> diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue index 550bf053bc37d27da86bca87e97d0a8cf9770f5d..a32b5da47f1160ddf1f0188a204e417cd9ad33cb 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue @@ -173,7 +173,6 @@ export default { if (this.hasNextPage && this.children.length === 0) { this.fetchNextPage(); } - this.checkDrawerParams(); }, }, workItemTypes: { @@ -302,6 +301,16 @@ export default { }; }, }, + watch: { + 'workItem.id': { + // we only want to open the drawer on initial widget load and not from drawer widget + handler(newValue, oldValue) { + if (!oldValue && newValue && !this.isDrawer) { + this.checkDrawerParams(); + } + }, + }, + }, mounted() { this.showLabels = getToggleFromLocalStorage(this.showLabelsLocalStorageKey); this.showClosed = getToggleFromLocalStorage(this.showClosedLocalStorageKey); diff --git a/app/assets/javascripts/work_items/pages/work_items_list_app.vue b/app/assets/javascripts/work_items/pages/work_items_list_app.vue index d77cc42a9fc1566031cba2e4eedc87b347f9724b..bdb161d732227659f6ca95334b4e17bb32866ec7 100644 --- a/app/assets/javascripts/work_items/pages/work_items_list_app.vue +++ b/app/assets/javascripts/work_items/pages/work_items_list_app.vue @@ -534,8 +534,18 @@ export default { this.autocompleteCache = new AutocompleteCache(); }, methods: { - handleSelect(item) { - this.activeItem = item; + handleToggle(item) { + if (item && this.activeItem?.iid === item.iid) { + this.activeItem = null; + const queryParam = getParameterByName(DETAIL_VIEW_QUERY_PARAM_NAME); + if (queryParam) { + updateHistory({ + url: removeParams([DETAIL_VIEW_QUERY_PARAM_NAME]), + }); + } + } else { + this.activeItem = item; + } }, calculateDocumentTitle(data) { const middleCrumb = this.isGroup ? data.group.name : data.project.name; @@ -802,7 +812,7 @@ export default { @page-size-change="handlePageSizeChange" @previous-page="handlePreviousPage" @sort="handleSort" - @select-issuable="handleSelect" + @select-issuable="handleToggle" > <template #nav-actions> <gl-button diff --git a/spec/frontend/work_items/components/work_item_detail_spec.js b/spec/frontend/work_items/components/work_item_detail_spec.js index 819800bb1cba03bcfc0e67291b8c73e189a0260f..b393932ebfd571e685a2f8ed61a9b7a1491f5760 100644 --- a/spec/frontend/work_items/components/work_item_detail_spec.js +++ b/spec/frontend/work_items/components/work_item_detail_spec.js @@ -672,6 +672,30 @@ describe('WorkItemDetail component', () => { expect(findDrawer().props('activeItem')).toEqual(modalWorkItem); }); + it('closes the drawer when `close-drawer` is emitted from the selected work item', async () => { + createComponent({ handler: objectiveHandler, workItemsAlphaEnabled: true }); + await waitForPromises(); + + const event = { + preventDefault: jest.fn(), + }; + const modalWorkItem = { id: 'childWorkItemId' }; + + findHierarchyTree().vm.$emit('show-modal', { + event, + modalWorkItem, + }); + await waitForPromises(); + + findHierarchyTree().vm.$emit('show-modal', { + event, + modalWorkItem, + }); + await waitForPromises(); + + expect(findDrawer().props('activeItem')).toEqual(null); + }); + describe('work item is rendered in a modal and has children', () => { beforeEach(async () => { createComponent({ diff --git a/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js index 61e89f7b314d6618d3eb2ad4cb74a8abf2cd3640..83abace12581954296b0f3d6ba51baa33d13579a 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_children_wrapper_spec.js @@ -128,6 +128,7 @@ describe('WorkItemChildrenWrapper', () => { createComponent(); const event = { childItem: 'gid://gitlab/WorkItem/2', + stopPropagation: jest.fn(), }; findFirstWorkItemLinkChildItem().vm.$emit('click', event); @@ -137,11 +138,14 @@ describe('WorkItemChildrenWrapper', () => { it('emits `click` event when clicking on nested child', () => { createComponent({ isTopLevel: false }); - const event = expect.anything(); + const event = { + childItem: 'gid://gitlab/WorkItem/2', + stopPropagation: jest.fn(), + }; findFirstWorkItemLinkChildItem().vm.$emit('click', event); - expect(wrapper.emitted('click')).toEqual([[{ event, childItem: 'gid://gitlab/WorkItem/2' }]]); + expect(wrapper.emitted('click')).toEqual([[event]]); }); it.each` diff --git a/spec/frontend/work_items/list/components/work_items_list_app_spec.js b/spec/frontend/work_items/list/components/work_items_list_app_spec.js index 9fe0e90908d46d2eec60c71866c8f344689c1625..0f2328cdc86fa41f8cf3b8bfb5840fa506486a81 100644 --- a/spec/frontend/work_items/list/components/work_items_list_app_spec.js +++ b/spec/frontend/work_items/list/components/work_items_list_app_spec.js @@ -580,6 +580,14 @@ describeSkipVue3(skipReason, () => { expect(findDrawer().props('activeItem')).toEqual(payload); }); + it('closes drawer when work item is clicked again', async () => { + findIssuableList().vm.$emit('select-issuable', payload); + await nextTick(); + + expect(findDrawer().props('open')).toBe(false); + expect(findDrawer().props('activeItem')).toBeNull(); + }); + const checkThatDrawerPropsAreEmpty = () => { expect(findDrawer().props('activeItem')).toBeNull(); expect(findDrawer().props('open')).toBe(false);