From 4294daddf9dc77d9ff7ee4ef178b0b0fc7f59601 Mon Sep 17 00:00:00 2001 From: GitLab Duo Date: Tue, 23 Sep 2025 19:54:41 +0000 Subject: [PATCH 1/2] Duo Workflow: Resolve issue #569660 --- .../ai_catalog_item_consumer_modal.vue | 14 ++++++- .../components/form_project_dropdown.vue | 6 +++ .../ai_catalog_item_consumer_modal_spec.js | 42 ++++++++++++++++++- .../components/form_project_dropdown_spec.js | 22 ++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_item_consumer_modal.vue b/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_item_consumer_modal.vue index bab678ccff1125..22c1d43f68acf1 100644 --- a/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_item_consumer_modal.vue +++ b/ee/app/assets/javascripts/ai/catalog/components/ai_catalog_item_consumer_modal.vue @@ -26,7 +26,7 @@ export default { data() { return { isOpen: true, - targetId: this.item.project?.id || null, + targetId: null, error: null, }; }, @@ -47,6 +47,9 @@ export default { itemType: this.itemTypeLabel, }); }, + isProjectValid() { + return Boolean(this.targetId); + }, isPrivateItem() { return !this.item.public; }, @@ -132,8 +135,15 @@ export default { :label="s__('AICatalog|Project')" :label-description="projectLabelDescription" label-for="target-id" + :state="isProjectValid" + :invalid-feedback="__('Project is required.')" > - +
diff --git a/ee/app/assets/javascripts/ai/catalog/components/form_project_dropdown.vue b/ee/app/assets/javascripts/ai/catalog/components/form_project_dropdown.vue index 3a4bdb7836bbe9..2db47205e187d7 100644 --- a/ee/app/assets/javascripts/ai/catalog/components/form_project_dropdown.vue +++ b/ee/app/assets/javascripts/ai/catalog/components/form_project_dropdown.vue @@ -23,6 +23,11 @@ export default { required: false, default: null, }, + isValid: { + type: Boolean, + required: false, + default: true, + }, value: { type: String, required: false, @@ -147,6 +152,7 @@ export default { :items="projectList" :toggle-id="id" :toggle-text="projectDropdownText" + :toggle-class="{ 'gl-shadow-inner-1-red-500': !isValid }" :header-text="__('Select a project')" :loading="isLoadingInitial" searchable diff --git a/ee/spec/frontend/ai/catalog/components/ai_catalog_item_consumer_modal_spec.js b/ee/spec/frontend/ai/catalog/components/ai_catalog_item_consumer_modal_spec.js index f84116cf345787..a7fa9f6f3b0b89 100644 --- a/ee/spec/frontend/ai/catalog/components/ai_catalog_item_consumer_modal_spec.js +++ b/ee/spec/frontend/ai/catalog/components/ai_catalog_item_consumer_modal_spec.js @@ -64,8 +64,8 @@ describe('AiCatalogItemConsumerModal', () => { expect(findProjectDropdown().exists()).toBe(true); }); - it('renders project dropdown with preselected project', () => { - expect(findProjectDropdown().props('value')).toBe(agent.project.id); + it('renders project dropdown with no preselected project', () => { + expect(findProjectDropdown().props('value')).toBe(null); }); it('renders alert when there was a problem fetching the projects', async () => { @@ -82,6 +82,44 @@ describe('AiCatalogItemConsumerModal', () => { }); }); + describe('isProjectValid computed property', () => { + it('returns false when targetId is null', () => { + expect(wrapper.vm.isProjectValid).toBe(false); + }); + + it('returns true when targetId has a value', async () => { + const projectId = 'gid://gitlab/Project/1000000'; + await findProjectDropdown().vm.$emit('input', projectId); + + expect(wrapper.vm.isProjectValid).toBe(true); + }); + }); + + describe('GlFormGroup validation state', () => { + it('passes invalid state to form group when no project is selected', () => { + expect(findFormGroup().props('state')).toBe(false); + expect(findFormGroup().props('invalidFeedback')).toBe('Project is required.'); + }); + + it('passes valid state to form group when project is selected', async () => { + const projectId = 'gid://gitlab/Project/1000000'; + await findProjectDropdown().vm.$emit('input', projectId); + + expect(findFormGroup().props('state')).toBe(true); + }); + + it('passes isValid prop to project dropdown', () => { + expect(findProjectDropdown().props('isValid')).toBe(false); + }); + + it('passes isValid prop as true to project dropdown when project is selected', async () => { + const projectId = 'gid://gitlab/Project/1000000'; + await findProjectDropdown().vm.$emit('input', projectId); + + expect(findProjectDropdown().props('isValid')).toBe(true); + }); + }); + describe('when submitting the form', () => { it('emits the submit event', async () => { const projectId = 'gid://gitlab/Project/1000000'; diff --git a/ee/spec/frontend/ai/catalog/components/form_project_dropdown_spec.js b/ee/spec/frontend/ai/catalog/components/form_project_dropdown_spec.js index 9cf81a71c35c51..e7ecbdd0b56553 100644 --- a/ee/spec/frontend/ai/catalog/components/form_project_dropdown_spec.js +++ b/ee/spec/frontend/ai/catalog/components/form_project_dropdown_spec.js @@ -201,4 +201,26 @@ describe('FormProjectDropdown', () => { expect(wrapper.emitted('input')).toEqual([['gid://gitlab/Project/1']]); }); }); + + describe('isValid prop', () => { + it('defaults to true when not provided', () => { + expect(findListbox().props('toggleClass')).toEqual({}); + }); + + it('applies error styling when isValid is false', () => { + createComponent({ props: { isValid: false } }); + + expect(findListbox().props('toggleClass')).toEqual({ + 'gl-shadow-inner-1-red-500': true, + }); + }); + + it('does not apply error styling when isValid is true', () => { + createComponent({ props: { isValid: true } }); + + expect(findListbox().props('toggleClass')).toEqual({ + 'gl-shadow-inner-1-red-500': false, + }); + }); + }); }); -- GitLab From 5def05def60304813446bf03aad4645e5b760f6f Mon Sep 17 00:00:00 2001 From: Angus Ryer Date: Tue, 23 Sep 2025 17:04:28 -0400 Subject: [PATCH 2/2] Adds translation file entry for validation error state --- locale/gitlab.pot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index caff951fe6ea12..2a501dabf4615a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -50421,6 +50421,9 @@ msgstr "" msgid "Project information" msgstr "" +msgid "Project is required." +msgstr "" + msgid "Project members" msgstr "" -- GitLab