From 18a7f6365f709257d87d30b57243bc6f7fcc5054 Mon Sep 17 00:00:00 2001 From: Rajan Mistry Date: Mon, 23 Oct 2023 18:47:03 +0530 Subject: [PATCH 1/6] Add support to search by iid in work item input component Add support to search by iid in work item input component. Chnagelog: added --- .../shared/work_item_token_input.vue | 125 +++++++++++++----- .../components/work_item_parent.vue | 2 + .../javascripts/work_items/constants.js | 2 +- .../graphql/project_work_items.query.graphql | 11 +- locale/gitlab.pot | 2 +- .../shared/work_item_token_input_spec.js | 57 +++++--- .../components/work_item_parent_spec.js | 14 +- spec/frontend/work_items/mock_data.js | 64 +++++++-- 8 files changed, 206 insertions(+), 71 deletions(-) diff --git a/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue b/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue index ce3d4749f17e26..cb1a911bd7e4bf 100644 --- a/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue +++ b/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue @@ -1,8 +1,9 @@ diff --git a/app/assets/javascripts/work_items/components/work_item_parent.vue b/app/assets/javascripts/work_items/components/work_item_parent.vue index d0e83ba8c21d4a..ce30f7985cf504 100644 --- a/app/assets/javascripts/work_items/components/work_item_parent.vue +++ b/app/assets/javascripts/work_items/components/work_item_parent.vue @@ -107,6 +107,8 @@ export default { searchTerm: this.search, types: this.parentType, in: this.search ? 'TITLE' : undefined, + iid: null, + isNumber: false, }; }, skip() { diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index e2dbfeb55a5d1f..3f45ea562a58bc 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -95,7 +95,7 @@ export const I18N_WORK_ITEM_CREATE_BUTTON_LABEL = s__('WorkItem|Create %{workIte export const I18N_WORK_ITEM_ADD_BUTTON_LABEL = s__('WorkItem|Add %{workItemType}'); export const I18N_WORK_ITEM_ADD_MULTIPLE_BUTTON_LABEL = s__('WorkItem|Add %{workItemType}s'); export const I18N_WORK_ITEM_SEARCH_INPUT_PLACEHOLDER = s__( - 'WorkItem|Search existing %{workItemType}s', + 'WorkItem|Search existing %{workItemType}s or type the reference ids', ); export const I18N_WORK_ITEM_CONFIDENTIALITY_CHECKBOX_LABEL = s__( 'WorkItem|This %{workItemType} is confidential and should only be visible to team members with at least Reporter access', diff --git a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql index 2be436aa8c20d6..4d3a7bdf44f3fe 100644 --- a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql +++ b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql @@ -3,6 +3,8 @@ query projectWorkItems( $fullPath: ID! $types: [IssueType!] $in: [IssuableSearchableField!] + $iid: String = null + $isNumber: Boolean! ) { workspace: project(fullPath: $fullPath) { id @@ -11,8 +13,13 @@ query projectWorkItems( id iid title - state - confidential + } + } + workItemsByIid: workItems(iid: $iid) @include(if: $isNumber) { + nodes { + id + iid + title } } } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9e8e539ff109e5..11d1c4d67e1795 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -54519,7 +54519,7 @@ msgstr "" msgid "WorkItem|Save and overwrite" msgstr "" -msgid "WorkItem|Search existing %{workItemType}s" +msgid "WorkItem|Search existing %{workItemType}s or type the reference ids" msgstr "" msgid "WorkItem|Select type" diff --git a/spec/frontend/work_items/components/shared/work_item_token_input_spec.js b/spec/frontend/work_items/components/shared/work_item_token_input_spec.js index c70dbbd909d517..3044adbe772d00 100644 --- a/spec/frontend/work_items/components/shared/work_item_token_input_spec.js +++ b/spec/frontend/work_items/components/shared/work_item_token_input_spec.js @@ -8,7 +8,12 @@ import WorkItemTokenInput from '~/work_items/components/shared/work_item_token_i import { WORK_ITEM_TYPE_ENUM_TASK } from '~/work_items/constants'; import groupWorkItemsQuery from '~/work_items/graphql/group_work_items.query.graphql'; import projectWorkItemsQuery from '~/work_items/graphql/project_work_items.query.graphql'; -import { availableWorkItemsResponse, searchedWorkItemsResponse } from '../../mock_data'; +import { + availableWorkItemsResponse, + searchWorkItemsTextResponse, + searchWorkItemsIidResponse, + searchWorkItemsTextIidResponse, +} from '../../mock_data'; Vue.use(VueApollo); @@ -16,15 +21,17 @@ describe('WorkItemTokenInput', () => { let wrapper; const availableWorkItemsResolver = jest.fn().mockResolvedValue(availableWorkItemsResponse); - const groupSearchedWorkItemResolver = jest.fn().mockResolvedValue(searchedWorkItemsResponse); - const searchedWorkItemResolver = jest.fn().mockResolvedValue(searchedWorkItemsResponse); + const groupSearchedWorkItemResolver = jest.fn().mockResolvedValue(searchWorkItemsTextResponse); + const searchWorkItemTextResolver = jest.fn().mockResolvedValue(searchWorkItemsTextResponse); + const searchWorkItemIidResolver = jest.fn().mockResolvedValue(searchWorkItemsIidResponse); + const searchWorkItemTextIidResolver = jest.fn().mockResolvedValue(searchWorkItemsTextIidResponse); const createComponent = async ({ workItemsToAdd = [], parentConfidential = false, childrenType = WORK_ITEM_TYPE_ENUM_TASK, areWorkItemsToAddValid = true, - workItemsResolver = searchedWorkItemResolver, + workItemsResolver = searchWorkItemTextResolver, isGroup = false, } = {}) => { wrapper = shallowMountExtended(WorkItemTokenInput, { @@ -61,24 +68,34 @@ describe('WorkItemTokenInput', () => { searchTerm: '', types: [WORK_ITEM_TYPE_ENUM_TASK], in: undefined, + iid: null, + isNumber: false, }); expect(findTokenSelector().props('dropdownItems')).toHaveLength(3); }); - it('searches for available work items when typing in input', async () => { - createComponent({ workItemsResolver: searchedWorkItemResolver }); - findTokenSelector().vm.$emit('focus'); - findTokenSelector().vm.$emit('text-input', 'Task 2'); - await waitForPromises(); - - expect(searchedWorkItemResolver).toHaveBeenCalledWith({ - fullPath: 'test-project-path', - searchTerm: 'Task 2', - types: [WORK_ITEM_TYPE_ENUM_TASK], - in: 'TITLE', - }); - expect(findTokenSelector().props('dropdownItems')).toHaveLength(1); - }); + it.each` + inputType | input | resolver | searchTerm | iid | isNumber | length + ${'iid'} | ${'101'} | ${searchWorkItemIidResolver} | ${'101'} | ${'101'} | ${true} | ${1} + ${'text'} | ${'Task 2'} | ${searchWorkItemTextResolver} | ${'Task 2'} | ${null} | ${false} | ${1} + ${'iid and text'} | ${'123'} | ${searchWorkItemTextIidResolver} | ${'123'} | ${'123'} | ${true} | ${2} + `( + 'searches by $inputType for available work items when typing in input', + async ({ input, resolver, searchTerm, iid, isNumber, length }) => { + createComponent({ workItemsResolver: resolver }); + findTokenSelector().vm.$emit('focus'); + findTokenSelector().vm.$emit('text-input', input); + await waitForPromises(); + + expect(resolver).toHaveBeenCalledWith({ + searchTerm, + in: 'TITLE', + iid, + isNumber, + }); + expect(findTokenSelector().props('dropdownItems')).toHaveLength(length); + }, + ); it('renders red border around token selector input when work item is not valid', () => { createComponent({ @@ -95,7 +112,7 @@ describe('WorkItemTokenInput', () => { }); it('calls the project work items query', () => { - expect(searchedWorkItemResolver).toHaveBeenCalled(); + expect(searchWorkItemTextResolver).toHaveBeenCalled(); }); it('skips calling the group work items query', () => { @@ -110,7 +127,7 @@ describe('WorkItemTokenInput', () => { }); it('skips calling the project work items query', () => { - expect(searchedWorkItemResolver).not.toHaveBeenCalled(); + expect(searchWorkItemTextResolver).not.toHaveBeenCalled(); }); it('calls the group work items query', () => { diff --git a/spec/frontend/work_items/components/work_item_parent_spec.js b/spec/frontend/work_items/components/work_item_parent_spec.js index 0c02f0c63ec45b..11fe6dffbfa416 100644 --- a/spec/frontend/work_items/components/work_item_parent_spec.js +++ b/spec/frontend/work_items/components/work_item_parent_spec.js @@ -148,15 +148,27 @@ describe('WorkItemParent component', () => { }); await findCollapsibleListbox().vm.$emit('shown'); - await findCollapsibleListbox().vm.$emit('search', 'Objective 101'); await waitForPromises(); + expect(searchedItemQueryHandler).toHaveBeenCalledWith({ + fullPath: 'full-path', + searchTerm: '', + types: [WORK_ITEM_TYPE_ENUM_OBJECTIVE], + in: undefined, + iid: null, + isNumber: false, + }); + + await findCollapsibleListbox().vm.$emit('search', 'Objective 101'); + expect(searchedItemQueryHandler).toHaveBeenCalledWith({ fullPath: 'full-path', searchTerm: 'Objective 101', types: [WORK_ITEM_TYPE_ENUM_OBJECTIVE], in: 'TITLE', + iid: null, + isNumber: false, }); await nextTick(); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index 06d59a3436798e..3a2e36f2d5ef89 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -1640,24 +1640,18 @@ export const availableObjectivesResponse = { id: 'gid://gitlab/WorkItem/716', iid: '122', title: 'Objective 101', - state: 'OPEN', - confidential: false, __typename: 'WorkItem', }, { id: 'gid://gitlab/WorkItem/712', iid: '118', title: 'Objective 103', - state: 'OPEN', - confidential: false, __typename: 'WorkItem', }, { id: 'gid://gitlab/WorkItem/711', iid: '117', title: 'Objective 102', - state: 'OPEN', - confidential: false, __typename: 'WorkItem', }, ], @@ -1677,8 +1671,6 @@ export const searchedObjectiveResponse = { id: 'gid://gitlab/WorkItem/716', iid: '122', title: 'Objective 101', - state: 'OPEN', - confidential: false, __typename: 'WorkItem', }, ], @@ -1687,7 +1679,7 @@ export const searchedObjectiveResponse = { }, }; -export const searchedWorkItemsResponse = { +export const searchWorkItemsTextResponse = { data: { workspace: { __typename: 'Project', @@ -1698,9 +1690,57 @@ export const searchedWorkItemsResponse = { id: 'gid://gitlab/WorkItem/459', iid: '3', title: 'Task 2', - state: 'OPEN', - createdAt: '2022-08-03T12:41:54Z', - confidential: false, + __typename: 'WorkItem', + }, + ], + }, + }, + }, +}; + +export const searchWorkItemsIidResponse = { + data: { + workspace: { + __typename: 'Project', + id: 'gid://gitlab/Project/2', + workItems: { + nodes: [], + }, + workItemsByIid: { + nodes: [ + { + id: 'gid://gitlab/WorkItem/460', + iid: '101', + title: 'Task 3', + __typename: 'WorkItem', + }, + ], + }, + }, + }, +}; + +export const searchWorkItemsTextIidResponse = { + data: { + workspace: { + __typename: 'Project', + id: 'gid://gitlab/Project/2', + workItems: { + nodes: [ + { + id: 'gid://gitlab/WorkItem/459', + iid: '3', + title: 'Task 123', + __typename: 'WorkItem', + }, + ], + }, + workItemsByIid: { + nodes: [ + { + id: 'gid://gitlab/WorkItem/460', + iid: '123', + title: 'Task 2', __typename: 'WorkItem', }, ], -- GitLab From 8705d16b615f4eeb40eb2547065450f3c36acd7a Mon Sep 17 00:00:00 2001 From: Rajan Mistry Date: Thu, 2 Nov 2023 18:53:44 +0530 Subject: [PATCH 2/6] Apply review suggestions for number check and text --- .../components/shared/work_item_token_input.vue | 8 +++----- app/assets/javascripts/work_items/constants.js | 2 +- locale/gitlab.pot | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue b/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue index cb1a911bd7e4bf..63814a920963c5 100644 --- a/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue +++ b/app/assets/javascripts/work_items/components/shared/work_item_token_input.vue @@ -1,6 +1,6 @@