diff --git a/ee/app/assets/javascripts/security_orchestration/components/group_projects_dropdown.vue b/ee/app/assets/javascripts/security_orchestration/components/group_projects_dropdown.vue index 8d0ac420619581809d38fa8096c0a0faeada52b1..c9704afed19599caf0e0fcdcb2011deb8f0c028c 100644 --- a/ee/app/assets/javascripts/security_orchestration/components/group_projects_dropdown.vue +++ b/ee/app/assets/javascripts/security_orchestration/components/group_projects_dropdown.vue @@ -10,6 +10,8 @@ import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; export default { i18n: { projectDropdownHeader: __('Select projects'), + selectAllLabel: __('Select all'), + clearAllLabel: __('Clear all'), }, name: 'GroupProjectsDropdown', components: { @@ -84,11 +86,15 @@ export default { selected: this.formattedSelectedProjectsIds, items: this.projectItems, itemTypeName: __('projects'), + useAllSelected: !this.hasNextPage, }); }, loading() { return this.$apollo.queries.projects.loading; }, + hasNextPage() { + return this.projectsPageInfo.hasNextPage; + }, projectItems() { return this.projects?.reduce((acc, { id, name }) => { acc[id] = name; @@ -101,6 +107,15 @@ export default { projectsIds() { return this.projects.map(({ id }) => id); }, + resetButtonLabel() { + return this.multiple ? this.$options.i18n.clearAllLabel : ''; + }, + category() { + return this.state ? 'primary' : 'secondary'; + }, + variant() { + return this.state ? 'default' : 'danger'; + }, }, created() { this.debouncedSearch = debounce(this.setSearchTerm, DEFAULT_DEBOUNCE_AND_THROTTLE_MS); @@ -148,12 +163,15 @@ export default { is-check-centered searchable fluid-width - :toggle-class="{ 'gl-inset-border-1-red-500!': !state }" + :category="category" + :variant="variant" :multiple="multiple" :loading="loading" :header-text="$options.i18n.projectDropdownHeader" - :infinite-scroll="projectsPageInfo.hasNextPage" + :infinite-scroll="hasNextPage" :infinite-scroll-loading="loading" + :reset-button-label="resetButtonLabel" + :show-select-all-button-label="$options.i18n.selectAllLabel" :searching="loading" :selected="existingFormattedSelectedProjectsIds" :placement="placement" @@ -161,6 +179,8 @@ export default { :toggle-text="dropdownPlaceholder" @bottom-reached="fetchMoreGroupProjects" @search="debouncedSearch" + @reset="selectProjects([])" @select="selectProjects" + @select-all="selectProjects(projectsIds)" /> </template> diff --git a/ee/spec/frontend/security_orchestration/components/group_projects_dropdown_spec.js b/ee/spec/frontend/security_orchestration/components/group_projects_dropdown_spec.js index 41960de2846fa34821ce4bf6dc911bc65e6be77e..a279de29fb65390f6582f1850f85e8dfd0992049 100644 --- a/ee/spec/frontend/security_orchestration/components/group_projects_dropdown_spec.js +++ b/ee/spec/frontend/security_orchestration/components/group_projects_dropdown_spec.js @@ -191,6 +191,26 @@ describe('GroupProjectsDropdown', () => { expect(requestHandlers.getGroupProjects).toHaveBeenCalledTimes(2); }); + it.each` + hasNextPage | expectedText + ${true} | ${'1 +1 more'} + ${false} | ${'All projects'} + `( + 'selects all projects only when all projects loaded', + async ({ hasNextPage, expectedText }) => { + createComponent({ + propsData: { + selected: defaultNodesIds, + }, + handlers: mockApolloHandlers(defaultNodes, hasNextPage), + }); + + await waitForPromises(); + + expect(findDropdown().props('toggleText')).toBe(expectedText); + }, + ); + describe('when the fetch query throws an error', () => { it('emits an error event', async () => { createComponent({ @@ -231,4 +251,44 @@ describe('GroupProjectsDropdown', () => { expect(findDropdown().props('selected')).toEqual(defaultNodesIds); }); }); + + describe('validation', () => { + it('renders default dropdown when validation passes', () => { + createComponent({ + propsData: { + state: true, + }, + }); + + expect(findDropdown().props('variant')).toEqual('default'); + expect(findDropdown().props('category')).toEqual('primary'); + }); + + it('renders danger dropdown when validation passes', () => { + createComponent(); + + expect(findDropdown().props('variant')).toEqual('danger'); + expect(findDropdown().props('category')).toEqual('secondary'); + }); + }); + + describe('select all', () => { + it('selects all projects', async () => { + createComponent(); + await waitForPromises(); + + findDropdown().vm.$emit('select-all'); + + expect(wrapper.emitted('select')).toEqual([[defaultNodes]]); + }); + + it('resets all projects', async () => { + createComponent(); + await waitForPromises(); + + findDropdown().vm.$emit('reset'); + + expect(wrapper.emitted('select')).toEqual([[[]]]); + }); + }); });