From d9b2bb3d0647fa9576569a634faec799d9c1ac98 Mon Sep 17 00:00:00 2001 From: Rajan Mistry Date: Wed, 1 Nov 2023 22:43:29 +0530 Subject: [PATCH 1/6] Add support to search by work item links in token input Add support to search by work item links in token input Changelog: added --- .../shared/work_item_token_input.vue | 80 ++++-- .../components/work_item_parent_inline.vue | 2 +- .../javascripts/work_items/constants.js | 8 +- .../graphql/project_work_items.query.graphql | 7 +- .../work_items_by_references.query.graphql | 10 + locale/gitlab.pot | 5 +- .../work_items/linked_work_items_spec.rb | 52 ++-- .../shared/work_item_token_input_spec.js | 232 +++++++++++++++--- .../work_item_parent_inline_spec.js | 6 +- .../work_item_parent_with_edit_spec.js | 4 + spec/frontend/work_items/mock_data.js | 39 +++ 11 files changed, 365 insertions(+), 80 deletions(-) create mode 100644 app/assets/javascripts/work_items/graphql/work_items_by_references.query.graphql 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 c122db6c9024ee..ecda94395fb37a 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,19 +1,22 @@ diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index c3c292c3dd94e8..816acbef33c598 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -55,3 +55,13 @@ export const markdownPreviewPath = (fullPath, iid) => `${ gon.relative_url_root || '' }/${fullPath}/preview_markdown?target_type=WorkItem&target_id=${iid}`; + +export const isReference = (input) => { + /** + * The regular expression checks if the `value` is + * a project work item or group work item. + * e.g., gitlab-org/project-path#101 or gitlab-org&101 + */ + const referenceRegExp = /^([\w-]+(?:\/[\w-]+)*)[#&](\d+)$/; + return new RegExp(referenceRegExp).test(input); +}; diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js index aa24b80cf08037..c8435f78696312 100644 --- a/spec/frontend/work_items/utils_spec.js +++ b/spec/frontend/work_items/utils_spec.js @@ -1,4 +1,4 @@ -import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils'; +import { autocompleteDataSources, markdownPreviewPath, isReference } from '~/work_items/utils'; describe('autocompleteDataSources', () => { beforeEach(() => { @@ -25,3 +25,19 @@ describe('markdownPreviewPath', () => { ); }); }); + +describe('isReference', () => { + it.each` + referenceId | result + ${'gitlab-org&101'} | ${true} + ${'gitlab-org/project-path#101'} | ${true} + ${'gitlab-org/sub-group/project-path#101'} | ${true} + ${'gitlab-org'} | ${false} + ${'gitlab-org101#'} | ${false} + ${'gitlab-org101&'} | ${false} + ${'#gitlab-org101'} | ${false} + ${'&gitlab-org101'} | ${false} + `('returns corrrect result for $referenceId', ({ referenceId, result }) => { + expect(isReference(referenceId)).toEqual(result); + }); +}); -- GitLab From 84eca826554a02062df80d896a6d345ed17b13db Mon Sep 17 00:00:00 2001 From: Rajan Mistry Date: Mon, 18 Dec 2023 16:25:35 +0530 Subject: [PATCH 4/6] Remove the constructor of regex and use test directly --- app/assets/javascripts/work_items/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index 816acbef33c598..1e694b767810b4 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -63,5 +63,5 @@ export const isReference = (input) => { * e.g., gitlab-org/project-path#101 or gitlab-org&101 */ const referenceRegExp = /^([\w-]+(?:\/[\w-]+)*)[#&](\d+)$/; - return new RegExp(referenceRegExp).test(input); + return referenceRegExp.test(input); }; -- GitLab From ecbb7d173db84a282f9e84cc962f86461731ac7a Mon Sep 17 00:00:00 2001 From: Rajan Mistry Date: Tue, 19 Dec 2023 14:03:47 +0530 Subject: [PATCH 5/6] Support the wildcard symbol to search project work items --- .../components/shared/work_item_token_input.vue | 2 +- .../projects/work_items/linked_work_items_spec.rb | 4 ++++ .../components/shared/work_item_token_input_spec.js | 10 ++++++---- 3 files changed, 11 insertions(+), 5 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 f7f9ca4315a71f..ee50cbc677b75b 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 @@ -120,7 +120,7 @@ export default { methods: { getIdFromGraphQLId, async setSearchKey(value) { - this.searchTerm = value; + this.searchTerm = value.startsWith('#') ? value.substring(1) : value; // Check if it is a URL or reference if (isReference(value) || isSafeURL(value)) { diff --git a/spec/features/projects/work_items/linked_work_items_spec.rb b/spec/features/projects/work_items/linked_work_items_spec.rb index 2ed972e610e59f..963be23e5a8d4b 100644 --- a/spec/features/projects/work_items/linked_work_items_spec.rb +++ b/spec/features/projects/work_items/linked_work_items_spec.rb @@ -72,6 +72,10 @@ verify_linked_item_added(task.iid) end + it 'links a new item with work item wildcard iid', :aggregate_failures do + verify_linked_item_added("##{task.iid}") + end + it 'links a new item with work item reference', :aggregate_failures do verify_linked_item_added(task.to_reference(full: true)) end 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 000ccf38ce9188..c2c76d028037f6 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 @@ -183,6 +183,7 @@ describe('WorkItemTokenInput', () => { const emptyWorkItemResolver = jest.fn().mockResolvedValue(searchWorkItemsResponse()); const validIid = mockWorkItemResponseItem1.iid; + const validWildCardIid = `#${mockWorkItemResponseItem1.iid}`; const searchedText = mockWorkItem.title; const searchedIidText = mockWorkItemResponseItem3.iid; const nonExistentIid = '111'; @@ -192,10 +193,11 @@ describe('WorkItemTokenInput', () => { const invalidWorkItemUrl = 'invalid-url/gitlab-org/test-project-path/-/work_items/101'; it.each` - inputType | input | resolver | searchTerm | iid | searchByText | searchByIid | length - ${'iid'} | ${validIid} | ${searchWorkItemIidResolver} | ${validIid} | ${validIid} | ${true} | ${true} | ${1} - ${'text'} | ${searchedText} | ${searchWorkItemTextResolver} | ${searchedText} | ${null} | ${true} | ${false} | ${1} - ${'iid and text'} | ${searchedIidText} | ${searchWorkItemTextIidResolver} | ${searchedIidText} | ${searchedIidText} | ${true} | ${true} | ${2} + inputType | input | resolver | searchTerm | iid | searchByText | searchByIid | length + ${'iid'} | ${validIid} | ${searchWorkItemIidResolver} | ${validIid} | ${validIid} | ${true} | ${true} | ${1} + ${'wildcard iid'} | ${validWildCardIid} | ${searchWorkItemIidResolver} | ${validIid} | ${validIid} | ${true} | ${true} | ${1} + ${'text'} | ${searchedText} | ${searchWorkItemTextResolver} | ${searchedText} | ${null} | ${true} | ${false} | ${1} + ${'iid and text'} | ${searchedIidText} | ${searchWorkItemTextIidResolver} | ${searchedIidText} | ${searchedIidText} | ${true} | ${true} | ${2} `( 'lists work items when $inputType is valid', async ({ input, resolver, searchTerm, iid, searchByIid, searchByText, length }) => { -- GitLab From 5d8acb7e61c54870dbc2d97e861a6eef9b550bf1 Mon Sep 17 00:00:00 2001 From: Rajan Mistry Date: Tue, 19 Dec 2023 17:14:31 +0530 Subject: [PATCH 6/6] Search only id if wildcard is entered --- .../components/shared/work_item_token_input.vue | 2 +- app/assets/javascripts/work_items/utils.js | 5 +++-- .../shared/work_item_token_input_spec.js | 16 ++++++++-------- spec/frontend/work_items/utils_spec.js | 8 +++++++- 4 files changed, 19 insertions(+), 12 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 ee50cbc677b75b..f7f9ca4315a71f 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 @@ -120,7 +120,7 @@ export default { methods: { getIdFromGraphQLId, async setSearchKey(value) { - this.searchTerm = value.startsWith('#') ? value.substring(1) : value; + this.searchTerm = value; // Check if it is a URL or reference if (isReference(value) || isSafeURL(value)) { diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index 1e694b767810b4..6d304e7ebf01ab 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -61,7 +61,8 @@ export const isReference = (input) => { * The regular expression checks if the `value` is * a project work item or group work item. * e.g., gitlab-org/project-path#101 or gitlab-org&101 + * or #1234 */ - const referenceRegExp = /^([\w-]+(?:\/[\w-]+)*)[#&](\d+)$/; - return referenceRegExp.test(input); + + return /^([\w-]+(?:\/[\w-]+)*)?[#&](\d+)$/.test(input); }; 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 c2c76d028037f6..8ad2a34055e38c 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 @@ -193,11 +193,10 @@ describe('WorkItemTokenInput', () => { const invalidWorkItemUrl = 'invalid-url/gitlab-org/test-project-path/-/work_items/101'; it.each` - inputType | input | resolver | searchTerm | iid | searchByText | searchByIid | length - ${'iid'} | ${validIid} | ${searchWorkItemIidResolver} | ${validIid} | ${validIid} | ${true} | ${true} | ${1} - ${'wildcard iid'} | ${validWildCardIid} | ${searchWorkItemIidResolver} | ${validIid} | ${validIid} | ${true} | ${true} | ${1} - ${'text'} | ${searchedText} | ${searchWorkItemTextResolver} | ${searchedText} | ${null} | ${true} | ${false} | ${1} - ${'iid and text'} | ${searchedIidText} | ${searchWorkItemTextIidResolver} | ${searchedIidText} | ${searchedIidText} | ${true} | ${true} | ${2} + inputType | input | resolver | searchTerm | iid | searchByText | searchByIid | length + ${'iid'} | ${validIid} | ${searchWorkItemIidResolver} | ${validIid} | ${validIid} | ${true} | ${true} | ${1} + ${'text'} | ${searchedText} | ${searchWorkItemTextResolver} | ${searchedText} | ${null} | ${true} | ${false} | ${1} + ${'iid and text'} | ${searchedIidText} | ${searchWorkItemTextIidResolver} | ${searchedIidText} | ${searchedIidText} | ${true} | ${true} | ${2} `( 'lists work items when $inputType is valid', async ({ input, resolver, searchTerm, iid, searchByIid, searchByText, length }) => { @@ -244,9 +243,10 @@ describe('WorkItemTokenInput', () => { ); it.each` - inputType | input | refs | length - ${'url'} | ${validWorkItemUrl} | ${[validWorkItemUrl]} | ${1} - ${'reference'} | ${validWorkItemReference} | ${[validWorkItemReference]} | ${1} + inputType | input | refs | length + ${'iid with wildcard'} | ${validWildCardIid} | ${[validWildCardIid]} | ${1} + ${'url'} | ${validWorkItemUrl} | ${[validWorkItemUrl]} | ${1} + ${'reference'} | ${validWorkItemReference} | ${[validWorkItemReference]} | ${1} `('lists work items when valid $inputType is pasted', async ({ input, refs, length }) => { createComponent({ workItemsResolver: workItemReferencesQueryResolver }); diff --git a/spec/frontend/work_items/utils_spec.js b/spec/frontend/work_items/utils_spec.js index c8435f78696312..166712de20b8c3 100644 --- a/spec/frontend/work_items/utils_spec.js +++ b/spec/frontend/work_items/utils_spec.js @@ -29,6 +29,12 @@ describe('markdownPreviewPath', () => { describe('isReference', () => { it.each` referenceId | result + ${'#101'} | ${true} + ${'&101'} | ${true} + ${'101'} | ${false} + ${'#'} | ${false} + ${'&'} | ${false} + ${' &101'} | ${false} ${'gitlab-org&101'} | ${true} ${'gitlab-org/project-path#101'} | ${true} ${'gitlab-org/sub-group/project-path#101'} | ${true} @@ -37,7 +43,7 @@ describe('isReference', () => { ${'gitlab-org101&'} | ${false} ${'#gitlab-org101'} | ${false} ${'&gitlab-org101'} | ${false} - `('returns corrrect result for $referenceId', ({ referenceId, result }) => { + `('returns $result for $referenceId', ({ referenceId, result }) => { expect(isReference(referenceId)).toEqual(result); }); }); -- GitLab