diff --git a/app/assets/javascripts/groups/components/group_name_and_path.vue b/app/assets/javascripts/groups/components/group_name_and_path.vue index 983535d3e9c..9a1ea2f1812 100644 --- a/app/assets/javascripts/groups/components/group_name_and_path.vue +++ b/app/assets/javascripts/groups/components/group_name_and_path.vue @@ -6,6 +6,13 @@ import { GlInputGroupText, GlLink, GlAlert, + GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlTruncate, + GlSearchBoxByType, } from '@gitlab/ui'; import { debounce } from 'lodash'; @@ -15,6 +22,11 @@ import { createAlert } from '~/flash'; import { slugify } from '~/lib/utils/text_utility'; import axios from '~/lib/utils/axios_utils'; import { helpPagePath } from '~/helpers/help_page_helper'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; +import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants'; + +import searchGroupsWhereUserCanCreateSubgroups from '../queries/search_groups_where_user_can_create_subgroups.query.graphql'; const DEBOUNCE_DURATION = 1000; @@ -22,7 +34,6 @@ export default { i18n: { inputs: { name: { - label: s__('Groups|Group name'), placeholder: __('My awesome group'), description: s__( 'Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.', @@ -30,7 +41,6 @@ export default { invalidFeedback: s__('Groups|Enter a descriptive name for your group.'), }, path: { - label: s__('Groups|Group URL'), placeholder: __('my-awesome-group'), invalidFeedbackInvalidPattern: s__( 'GroupSettings|Choose a group path that does not start with a dash or end with a period. It can also contain alphanumeric characters and underscores.', @@ -40,9 +50,6 @@ export default { ), validFeedback: s__('Groups|Group path is available.'), }, - groupId: { - label: s__('Groups|Group ID'), - }, }, apiLoadingMessage: s__('Groups|Checking group URL availability...'), apiErrorMessage: __( @@ -51,7 +58,7 @@ export default { changingUrlWarningMessage: s__('Groups|Changing group URL can have unintended side effects.'), learnMore: s__('Groups|Learn more'), }, - nameInputSize: { md: 'lg' }, + inputSize: { md: 'lg' }, changingGroupPathHelpPagePath: helpPagePath('user/group/index', { anchor: 'change-a-groups-path', }), @@ -63,8 +70,35 @@ export default { GlInputGroupText, GlLink, GlAlert, + GlButton, + GlButtonGroup, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlTruncate, + GlSearchBoxByType, }, - inject: ['fields', 'basePath', 'mattermostEnabled'], + apollo: { + currentUserGroups: { + query: searchGroupsWhereUserCanCreateSubgroups, + variables() { + return { + search: this.search, + }; + }, + update(data) { + return data.currentUser?.groups?.nodes || []; + }, + skip() { + const hasNotEnoughSearchCharacters = + this.search.length > 0 && this.search.length < MINIMUM_SEARCH_LENGTH; + + return this.shouldSkipQuery || hasNotEnoughSearchCharacters; + }, + debounce: DEBOUNCE_DELAY, + }, + }, + inject: ['fields', 'basePath', 'newSubgroup', 'mattermostEnabled'], data() { return { name: this.fields.name.value, @@ -76,9 +110,27 @@ export default { pathFeedbackState: null, pathInvalidFeedback: null, activeApiRequestAbortController: null, + search: '', + currentUserGroups: {}, + shouldSkipQuery: true, + selectedGroup: { + id: this.fields.parentId.value, + fullPath: this.fields.parentFullPath.value, + }, }; }, computed: { + inputLabels() { + return { + name: this.newSubgroup ? s__('Groups|Subgroup name') : s__('Groups|Group name'), + path: this.newSubgroup ? s__('Groups|Subgroup slug') : s__('Groups|Group URL'), + subgroupPath: s__('Groups|Subgroup URL'), + groupId: s__('Groups|Group ID'), + }; + }, + pathInputSize() { + return this.newSubgroup ? {} : this.$options.inputSize; + }, computedPath() { return this.apiSuggestedPath || this.path; }, @@ -129,9 +181,11 @@ export default { try { const { data: { exists, suggests }, - } = await getGroupPathAvailability(this.path, this.fields.parentId?.value, { - signal: this.activeApiRequestAbortController.signal, - }); + } = await getGroupPathAvailability( + this.path, + this.selectedGroup.id || this.fields.parentId.value, + { signal: this.activeApiRequestAbortController.signal }, + ); this.apiLoading = false; @@ -198,6 +252,21 @@ export default { this.pathInvalidFeedback = this.$options.i18n.inputs.path.invalidFeedbackInvalidPattern; this.pathFeedbackState = false; }, + handleDropdownShown() { + if (this.shouldSkipQuery) { + this.shouldSkipQuery = false; + } + + this.$refs.search.focusInput(); + }, + handleDropdownItemClick({ id, fullPath }) { + this.selectedGroup = { + id: getIdFromGraphQLId(id), + fullPath, + }; + + this.debouncedValidatePath(); + }, }, }; @@ -208,10 +277,10 @@ export default { :id="fields.parentId.id" type="hidden" :name="fields.parentId.name" - :value="fields.parentId.value" + :value="selectedGroup.id" /> - - - - - - + +
+ +
+ + + {{ basePath }} + + + + + + + + + + + +
+ / +
+
+
+ + + + + + + +
+