diff options
Diffstat (limited to 'app/assets/javascripts/boards/components/sidebar')
4 files changed, 175 insertions, 6 deletions
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue index 5fb7a9b210c..ce267be6d45 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue @@ -50,6 +50,13 @@ export default { } window.removeEventListener('click', this.collapseWhenOffClick); }, + toggle({ emitEvent = true } = {}) { + if (this.edit) { + this.collapse({ emitEvent }); + } else { + this.expand(); + } + }, }, }; </script> @@ -64,18 +71,18 @@ export default { <gl-button v-if="canUpdate" variant="link" - class="gl-text-gray-900!" + class="gl-text-gray-900! js-sidebar-dropdown-toggle" data-testid="edit-button" - @click="expand()" + @click="toggle" > {{ __('Edit') }} </gl-button> </div> - <div v-show="!edit" class="gl-text-gray-400" data-testid="collapsed-content"> + <div v-show="!edit" class="gl-text-gray-500" data-testid="collapsed-content"> <slot name="collapsed">{{ __('None') }}</slot> </div> <div v-show="edit" data-testid="expanded-content"> - <slot></slot> + <slot :edit="edit"></slot> </div> </div> </template> diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue index 6935ead2706..904ceaed1b3 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue @@ -79,7 +79,7 @@ export default { <span class="gl-mx-2">-</span> <gl-button variant="link" - class="gl-text-gray-400!" + class="gl-text-gray-500!" data-testid="reset-button" :disabled="loading" @click="setDueDate(null)" diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue index 9d537a4ef2c..6a407bd6ba6 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue @@ -92,7 +92,7 @@ export default { @close="removeLabel(label.id)" /> </template> - <template> + <template #default="{ edit }"> <labels-select ref="labelsSelect" :allow-label-edit="false" @@ -105,6 +105,7 @@ export default { :labels-filter-base-path="labelsFilterBasePath" :labels-list-title="__('Select label')" :dropdown-button-text="__('Choose labels')" + :is-editing="edit" variant="embedded" class="gl-display-block labels gl-w-full" @updateSelectedLabels="setLabels" diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue new file mode 100644 index 00000000000..78c3f8acc62 --- /dev/null +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue @@ -0,0 +1,161 @@ +<script> +import { mapGetters, mapActions } from 'vuex'; +import { + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, + GlDropdownDivider, + GlLoadingIcon, +} from '@gitlab/ui'; +import { fetchPolicies } from '~/lib/graphql'; +import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; +import groupMilestones from '../../graphql/group_milestones.query.graphql'; +import createFlash from '~/flash'; +import { __, s__ } from '~/locale'; + +export default { + components: { + BoardEditableItem, + GlDropdown, + GlLoadingIcon, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, + GlDropdownDivider, + }, + data() { + return { + milestones: [], + searchTitle: '', + loading: false, + edit: false, + }; + }, + apollo: { + milestones: { + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + query: groupMilestones, + debounce: 250, + skip() { + return !this.edit; + }, + variables() { + return { + fullPath: this.groupFullPath, + searchTitle: this.searchTitle, + state: 'active', + includeDescendants: true, + }; + }, + update(data) { + const edges = data?.group?.milestones?.edges ?? []; + return edges.map(item => item.node); + }, + error() { + createFlash({ message: this.$options.i18n.fetchMilestonesError }); + }, + }, + }, + computed: { + ...mapGetters({ issue: 'activeIssue' }), + hasMilestone() { + return this.issue.milestone !== null; + }, + groupFullPath() { + const { referencePath = '' } = this.issue; + return referencePath.slice(0, referencePath.indexOf('/')); + }, + projectPath() { + const { referencePath = '' } = this.issue; + return referencePath.slice(0, referencePath.indexOf('#')); + }, + dropdownText() { + return this.issue.milestone?.title ?? this.$options.i18n.noMilestone; + }, + }, + mounted() { + this.$root.$on('bv::dropdown::hide', () => { + this.$refs.sidebarItem.collapse(); + }); + }, + methods: { + ...mapActions(['setActiveIssueMilestone']), + handleOpen() { + this.edit = true; + this.$refs.dropdown.show(); + }, + async setMilestone(milestoneId) { + this.loading = true; + this.searchTitle = ''; + this.$refs.sidebarItem.collapse(); + + try { + const input = { milestoneId, projectPath: this.projectPath }; + await this.setActiveIssueMilestone(input); + } catch (e) { + createFlash({ message: this.$options.i18n.updateMilestoneError }); + } finally { + this.loading = false; + } + }, + }, + i18n: { + milestone: __('Milestone'), + noMilestone: __('No milestone'), + assignMilestone: __('Assign milestone'), + noMilestonesFound: s__('Milestones|No milestones found'), + fetchMilestonesError: __('There was a problem fetching milestones.'), + updateMilestoneError: __('An error occurred while updating the milestone.'), + }, +}; +</script> + +<template> + <board-editable-item + ref="sidebarItem" + :title="$options.i18n.milestone" + :loading="loading" + @open="handleOpen()" + @close="edit = false" + > + <template v-if="hasMilestone" #collapsed> + <strong class="gl-text-gray-900">{{ issue.milestone.title }}</strong> + </template> + <template> + <gl-dropdown + ref="dropdown" + :text="dropdownText" + :header-text="$options.i18n.assignMilestone" + block + > + <gl-search-box-by-type ref="search" v-model.trim="searchTitle" class="gl-m-3" /> + <gl-dropdown-item + data-testid="no-milestone-item" + :is-check-item="true" + :is-checked="!issue.milestone" + @click="setMilestone(null)" + > + {{ $options.i18n.noMilestone }} + </gl-dropdown-item> + <gl-dropdown-divider /> + <gl-loading-icon v-if="$apollo.loading" class="gl-py-4" /> + <template v-else-if="milestones.length > 0"> + <gl-dropdown-item + v-for="milestone in milestones" + :key="milestone.id" + :is-check-item="true" + :is-checked="issue.milestone && milestone.id === issue.milestone.id" + data-testid="milestone-item" + @click="setMilestone(milestone.id)" + > + {{ milestone.title }} + </gl-dropdown-item> + </template> + <gl-dropdown-text v-else data-testid="no-milestones-found"> + {{ $options.i18n.noMilestonesFound }} + </gl-dropdown-text> + </gl-dropdown> + </template> + </board-editable-item> +</template> |