From da12bf16887b5770a980f6879ab35de48e401b7a Mon Sep 17 00:00:00 2001 From: GitLab Duo Date: Thu, 11 Sep 2025 07:45:32 +0000 Subject: [PATCH] Duo Workflow: Resolve issue #569599 --- .../work_items/pages/work_items_list_app.vue | 5 +- .../components/work_items_list_app_spec.js | 115 +++++++++++++++++- 2 files changed, 118 insertions(+), 2 deletions(-) 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 ead29041250d7f..56a25d51a898f5 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 @@ -39,6 +39,7 @@ import EmptyStateWithoutAnyIssues from '~/issues/list/components/empty_state_wit import NewResourceDropdown from '~/vue_shared/components/new_resource_dropdown/new_resource_dropdown.vue'; import { CREATED_DESC, + ISSUE_REFERENCE, PARAM_FIRST_PAGE_SIZE, PARAM_LAST_PAGE_SIZE, PARAM_PAGE_AFTER, @@ -455,13 +456,15 @@ export default { queryVariables() { const hasGroupFilter = Boolean(this.urlFilterParams.group_path); const singleWorkItemType = this.workItemType ? NAME_TO_ENUM_MAP[this.workItemType] : null; + const isIidSearch = ISSUE_REFERENCE.test(this.searchQuery); return { fullPath: this.rootPageFullPath, sort: this.sortKey, state: this.state, ...this.apiFilterParams, ...this.pageParams, - search: this.searchQuery, + iid: isIidSearch ? this.searchQuery.slice(1) : undefined, + search: isIidSearch ? undefined : this.searchQuery, excludeProjects: hasGroupFilter || this.isEpicsList, includeDescendants: !hasGroupFilter, types: this.apiFilterParams.types || singleWorkItemType || this.defaultWorkItemTypes, 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 ff3652439da4a7..8ddacce56386fd 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 @@ -850,7 +850,120 @@ describeSkipVue3(skipReason, () => { }); }); - describe('events', () => { + describe('IID search functionality', () => { + describe('when search query matches IID pattern', () => { + it.each([ + { searchQuery: '#123', expectedIid: '123' }, + { searchQuery: '#1', expectedIid: '1' }, + { searchQuery: '#999999', expectedIid: '999999' }, + ])('detects IID search for $searchQuery and passes iid parameter', async ({ searchQuery, expectedIid }) => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { type: FILTERED_SEARCH_TERM, value: { data: searchQuery, operator: 'undefined' } }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + iid: expectedIid, + search: undefined, + }), + ); + }); + + it('sets search parameter to undefined when IID search is detected', async () => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { type: FILTERED_SEARCH_TERM, value: { data: '#42', operator: 'undefined' } }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + iid: '42', + search: undefined, + }), + ); + }); + }); + + describe('when search query does not match IID pattern', () => { + it.each([ + 'regular search text', + 'bug fix', + '#', + '#abc', + '123', + 'issue #123 description', + '#123abc', + ])('does not detect IID search for "%s" and uses search parameter', async (searchQuery) => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { type: FILTERED_SEARCH_TERM, value: { data: searchQuery, operator: 'undefined' } }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + iid: undefined, + search: searchQuery, + }), + ); + }); + }); + + describe('when combining IID search with other filters', () => { + it('maintains other filter parameters when IID search is detected', async () => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { type: FILTERED_SEARCH_TERM, value: { data: '#456', operator: 'undefined' } }, + { type: TOKEN_TYPE_AUTHOR, value: { data: 'john', operator: OPERATOR_IS } }, + { type: TOKEN_TYPE_LABEL, value: { data: 'bug', operator: OPERATOR_IS } }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + iid: '456', + search: undefined, + authorUsername: 'john', + labelName: ['bug'], + }), + ); + }); + }); + + describe('backward compatibility', () => { + it('maintains existing search functionality when no IID pattern is detected', async () => { + mountComponent(); + await waitForPromises(); + + findIssuableList().vm.$emit('filter', [ + { type: FILTERED_SEARCH_TERM, value: { data: 'find issues', operator: 'undefined' } }, + { type: TOKEN_TYPE_AUTHOR, value: { data: 'homer', operator: OPERATOR_IS } }, + { type: TOKEN_TYPE_SEARCH_WITHIN, value: { data: 'TITLE', operator: OPERATOR_IS } }, + ]); + await nextTick(); + + expect(defaultQueryHandler).toHaveBeenCalledWith( + expect.objectContaining({ + search: 'find issues', + authorUsername: 'homer', + in: 'TITLE', + iid: undefined, + }), + ); + }); + }); + }); describe('when "click-tab" event is emitted by IssuableList', () => { beforeEach(async () => { getParameterByName.mockImplementation((args) => -- GitLab