diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-26 12:11:08 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-10-26 12:11:08 +0300 |
commit | 79cd3f3a38777b1436107bd1e3205f593e1a3bd1 (patch) | |
tree | 4bc714410905fed21c92057b2a83149150dbe18d /app | |
parent | a8f6578cb24cb3a688b7a5be674867fa311b0b38 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
3 files changed, 84 insertions, 151 deletions
diff --git a/app/assets/javascripts/milestones/components/milestone_combobox.vue b/app/assets/javascripts/milestones/components/milestone_combobox.vue index 2f7fb542d0e..a5e306b5372 100644 --- a/app/assets/javascripts/milestones/components/milestone_combobox.vue +++ b/app/assets/javascripts/milestones/components/milestone_combobox.vue @@ -1,19 +1,10 @@ <script> -import { - GlDropdown, - GlDropdownDivider, - GlDropdownSectionHeader, - GlDropdownItem, - GlLoadingIcon, - GlSearchBoxByType, - GlIcon, -} from '@gitlab/ui'; +import { GlBadge, GlButton, GlCollapsibleListbox } from '@gitlab/ui'; import { debounce, isEqual } from 'lodash'; // eslint-disable-next-line no-restricted-imports import { mapActions, mapGetters, mapState } from 'vuex'; import { s__, __, sprintf } from '~/locale'; import createStore from '../stores'; -import MilestoneResultsSection from './milestone_results_section.vue'; const SEARCH_DEBOUNCE_MS = 250; @@ -21,14 +12,9 @@ export default { name: 'MilestoneCombobox', store: createStore(), components: { - GlDropdown, - GlDropdownDivider, - GlDropdownSectionHeader, - GlDropdownItem, - GlLoadingIcon, - GlSearchBoxByType, - GlIcon, - MilestoneResultsSection, + GlCollapsibleListbox, + GlBadge, + GlButton, }, props: { value: { @@ -56,27 +42,43 @@ export default { required: false, }, }, - data() { - return { - searchQuery: '', - }; - }, translations: { - milestone: s__('MilestoneCombobox|Milestone'), selectMilestone: s__('MilestoneCombobox|Select milestone'), noMilestone: s__('MilestoneCombobox|No milestone'), - noResultsLabel: s__('MilestoneCombobox|No matching results'), - searchMilestones: s__('MilestoneCombobox|Search Milestones'), - searchErrorMessage: s__('MilestoneCombobox|An error occurred while searching for milestones'), projectMilestones: s__('MilestoneCombobox|Project milestones'), groupMilestones: s__('MilestoneCombobox|Group milestones'), + unselect: __('Unselect'), }, computed: { ...mapState(['matches', 'selectedMilestones']), - ...mapGetters(['isLoading', 'groupMilestonesEnabled']), + ...mapGetters(['isLoading']), + allMilestones() { + const { groupMilestones, projectMilestones } = this.matches || {}; + const milestones = []; + + if (projectMilestones?.totalCount) { + milestones.push({ + id: 'project-milestones', + text: this.$options.translations.projectMilestones, + options: projectMilestones.list, + totalCount: projectMilestones.totalCount, + }); + } + + if (groupMilestones?.totalCount) { + milestones.push({ + id: 'group-milestones', + text: this.$options.translations.groupMilestones, + options: groupMilestones.list, + totalCount: groupMilestones.totalCount, + }); + } + + return milestones; + }, selectedMilestonesLabel() { const { selectedMilestones } = this; - const firstMilestoneName = selectedMilestones[0]; + const [firstMilestoneName] = selectedMilestones; if (selectedMilestones.length === 0) { return this.$options.translations.noMilestone; @@ -92,20 +94,6 @@ export default { numberOfOtherMilestones, }); }, - showProjectMilestoneSection() { - return Boolean( - this.matches.projectMilestones.totalCount > 0 || this.matches.projectMilestones.error, - ); - }, - showGroupMilestoneSection() { - return ( - this.groupMilestonesEnabled && - Boolean(this.matches.groupMilestones.totalCount > 0 || this.matches.groupMilestones.error) - ); - }, - showNoResults() { - return !this.showProjectMilestoneSection && !this.showGroupMilestoneSection; - }, }, watch: { // Keep the Vuex store synchronized if the parent @@ -127,8 +115,8 @@ export default { // because we need to access the .cancel() method // lodash attaches to the function, which is // made inaccessible by Vue. - this.debouncedSearch = debounce(function search() { - this.search(this.searchQuery); + this.debouncedSearch = debounce(function search(q) { + this.search(q); }, SEARCH_DEBOUNCE_MS); this.setProjectId(this.projectId); @@ -143,22 +131,14 @@ export default { 'setGroupMilestonesAvailable', 'setSelectedMilestones', 'clearSelectedMilestones', - 'toggleMilestones', 'search', 'fetchMilestones', ]), - focusSearchBox() { - this.$refs.searchBox.$el.querySelector('input').focus(); - }, - onSearchBoxEnter() { - this.debouncedSearch.cancel(); - this.search(this.searchQuery); + onSearchBoxInput(q) { + this.debouncedSearch(q); }, - onSearchBoxInput() { - this.debouncedSearch(); - }, - selectMilestone(milestone) { - this.toggleMilestones(milestone); + selectMilestone(milestones) { + this.setSelectedMilestones(milestones); this.$emit('input', this.selectedMilestones); }, selectNoMilestone() { @@ -170,84 +150,42 @@ export default { </script> <template> - <gl-dropdown v-bind="$attrs" class="milestone-combobox" @shown="focusSearchBox"> - <template #button-content> - <span data-testid="milestone-combobox-button-content" class="gl-flex-grow-1 text-muted">{{ - selectedMilestonesLabel - }}</span> - <gl-icon name="chevron-down" /> - </template> - - <gl-dropdown-section-header> - <span class="text-center d-block">{{ $options.translations.selectMilestone }}</span> - </gl-dropdown-section-header> - - <gl-dropdown-divider /> - - <gl-search-box-by-type - ref="searchBox" - v-model.trim="searchQuery" - class="gl-m-3" - :placeholder="$options.translations.searchMilestones" - @input="onSearchBoxInput" - @keydown.enter.prevent="onSearchBoxEnter" - /> - - <gl-dropdown-item - :is-checked="selectedMilestones.length === 0" - is-check-item - @click="selectNoMilestone()" - > - {{ $options.translations.noMilestone }} - </gl-dropdown-item> - - <gl-dropdown-divider /> - - <template v-if="isLoading"> - <gl-loading-icon size="sm" /> - <gl-dropdown-divider /> + <gl-collapsible-listbox + :header-text="$options.translations.selectMilestone" + :items="allMilestones" + :reset-button-label="$options.translations.unselect" + :searching="isLoading" + :selected="selectedMilestones" + :toggle-text="selectedMilestonesLabel" + block + multiple + searchable + @reset="selectNoMilestone" + @search="onSearchBoxInput" + @select="selectMilestone" + > + <template #group-label="{ group }"> + <span :data-testid="`${group.id}-section`" + >{{ group.text }}<gl-badge size="sm" class="gl-ml-2">{{ group.totalCount }}</gl-badge></span + > </template> - <template v-else-if="showNoResults"> - <div class="dropdown-item-space"> - <span data-testid="milestone-combobox-no-results" class="gl-pl-6">{{ - $options.translations.noResultsLabel - }}</span> + <template #footer> + <div + class="gl-border-t-solid gl-border-t-1 gl-border-t-gray-200 gl-display-flex gl-flex-direction-column gl-p-2! gl-pt-0!" + > + <gl-button + v-for="(item, idx) in extraLinks" + :key="idx" + :href="item.url" + is-check-item + data-testid="milestone-combobox-extra-links" + category="tertiary" + block + class="gl-justify-content-start! gl-mt-2!" + > + {{ item.text }} + </gl-button> </div> - <gl-dropdown-divider /> - </template> - <template v-else> - <milestone-results-section - v-if="showProjectMilestoneSection" - :section-title="$options.translations.projectMilestones" - :total-count="matches.projectMilestones.totalCount" - :items="matches.projectMilestones.list" - :selected-milestones="selectedMilestones" - :error="matches.projectMilestones.error" - :error-message="$options.translations.searchErrorMessage" - data-testid="project-milestones-section" - @selected="selectMilestone($event)" - /> - - <milestone-results-section - v-if="showGroupMilestoneSection" - :section-title="$options.translations.groupMilestones" - :total-count="matches.groupMilestones.totalCount" - :items="matches.groupMilestones.list" - :selected-milestones="selectedMilestones" - :error="matches.groupMilestones.error" - :error-message="$options.translations.searchErrorMessage" - data-testid="group-milestones-section" - @selected="selectMilestone($event)" - /> </template> - <gl-dropdown-item - v-for="(item, idx) in extraLinks" - :key="idx" - :href="item.url" - is-check-item - data-testid="milestone-combobox-extra-links" - > - {{ item.text }} - </gl-dropdown-item> - </gl-dropdown> + </gl-collapsible-listbox> </template> diff --git a/app/assets/javascripts/milestones/stores/mutations.js b/app/assets/javascripts/milestones/stores/mutations.js index 1f88c0a1ea6..c0cd58cc5d2 100644 --- a/app/assets/javascripts/milestones/stores/mutations.js +++ b/app/assets/javascripts/milestones/stores/mutations.js @@ -37,7 +37,7 @@ export default { }, [types.RECEIVE_PROJECT_MILESTONES_SUCCESS](state, response) { state.matches.projectMilestones = { - list: response.data.map(({ title }) => ({ title })), + list: response.data.map(({ title }) => ({ text: title, value: title })), totalCount: parseInt(response.headers['x-total'], 10) || response.data.length, error: null, }; @@ -51,7 +51,7 @@ export default { }, [types.RECEIVE_GROUP_MILESTONES_SUCCESS](state, response) { state.matches.groupMilestones = { - list: response.data.map(({ title }) => ({ title })), + list: response.data.map(({ title }) => ({ text: title, value: title })), totalCount: parseInt(response.headers['x-total'], 10) || response.data.length, error: null, }; diff --git a/app/assets/javascripts/releases/components/app_edit_new.vue b/app/assets/javascripts/releases/components/app_edit_new.vue index c68fbceb4f6..df9f333afe5 100644 --- a/app/assets/javascripts/releases/components/app_edit_new.vue +++ b/app/assets/javascripts/releases/components/app_edit_new.vue @@ -176,8 +176,7 @@ export default { </p> <form v-if="showForm" class="js-quick-submit" @submit.prevent="submitForm"> <tag-field /> - <gl-form-group> - <label for="release-title">{{ __('Release title') }}</label> + <gl-form-group :label="__('Release title')"> <gl-form-input id="release-title" ref="releaseTitleInput" @@ -186,17 +185,14 @@ export default { class="form-control" /> </gl-form-group> - <gl-form-group class="w-50" data-testid="milestones-field"> - <label>{{ __('Milestones') }}</label> - <div class="d-flex flex-column col-md-6 col-sm-10 pl-0"> - <milestone-combobox - v-model="releaseMilestones" - :project-id="projectId" - :group-id="groupId" - :group-milestones-available="groupMilestonesAvailable" - :extra-links="milestoneComboboxExtraLinks" - /> - </div> + <gl-form-group :label="__('Milestones')" class="gl-w-30" data-testid="milestones-field"> + <milestone-combobox + v-model="releaseMilestones" + :project-id="projectId" + :group-id="groupId" + :group-milestones-available="groupMilestonesAvailable" + :extra-links="milestoneComboboxExtraLinks" + /> </gl-form-group> <gl-form-group :label="__('Release date')" label-for="release-released-at"> <template #label-description> @@ -214,8 +210,7 @@ export default { </template> <gl-datepicker id="release-released-at" v-model="releasedAt" :default-date="releasedAt" /> </gl-form-group> - <gl-form-group data-testid="release-notes"> - <label for="release-notes">{{ __('Release notes') }}</label> + <gl-form-group :label="__('Release notes')" data-testid="release-notes"> <div class="common-note-form"> <markdown-field :can-attach-file="true" |