diff options
Diffstat (limited to 'app/assets/javascripts/boards/components')
13 files changed, 344 insertions, 130 deletions
diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue index 05b64ddc773..5658a34e9a6 100644 --- a/app/assets/javascripts/boards/components/board_card_inner.vue +++ b/app/assets/javascripts/boards/components/board_card_inner.vue @@ -65,7 +65,7 @@ export default { }, computed: { ...mapState(['isShowingLabels', 'issuableType', 'allowSubEpics']), - ...mapGetters(['isEpicBoard']), + ...mapGetters(['isEpicBoard', 'isProjectBoard']), cappedAssignees() { // e.g. maxRender is 4, // Render up to all 4 assignees if there are only 4 assigness @@ -144,6 +144,9 @@ export default { totalProgress() { return Math.round((this.item.descendantWeightSum.closedIssues / this.totalWeight) * 100); }, + showReferencePath() { + return !this.isProjectBoard && this.itemReferencePath; + }, }, methods: { ...mapActions(['performSearch', 'setError']), @@ -247,7 +250,7 @@ export default { :class="{ 'gl-font-base': isEpicBoard }" > <tooltip-on-truncate - v-if="itemReferencePath" + v-if="showReferencePath" :title="itemReferencePath" placement="bottom" class="board-item-path gl-text-truncate gl-font-weight-bold" diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index 69abf886ad7..bcf5b12b209 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -79,7 +79,7 @@ export default { 'is-collapsed': list.collapsed, 'board-type-assignee': list.listType === 'assignee', }" - :data-id="list.id" + :data-list-id="list.id" class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal is-expandable" data-qa-selector="board_list" > diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 53b071aaed1..4df6ff75249 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -6,10 +6,12 @@ import { mapState, mapGetters, mapActions } from 'vuex'; import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue'; import defaultSortableConfig from '~/sortable/sortable_config'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { DraggableItemTypes } from '../constants'; import BoardColumn from './board_column.vue'; import BoardColumnDeprecated from './board_column_deprecated.vue'; export default { + draggableItemTypes: DraggableItemTypes, components: { BoardAddNewColumn, BoardColumn, @@ -76,19 +78,6 @@ export default { const el = this.canDragColumns ? this.$refs.list.$el : this.$refs.list; el.scrollTo({ left: el.scrollWidth, behavior: 'smooth' }); }, - handleDragOnEnd(params) { - const { item, newIndex, oldIndex, to } = params; - - const listId = item.dataset.id; - const replacedListId = to.children[newIndex].dataset.id; - - this.moveList({ - listId, - replacedListId, - newIndex, - adjustmentValue: newIndex < oldIndex ? 1 : -1, - }); - }, }, }; </script> @@ -104,7 +93,7 @@ export default { ref="list" v-bind="draggableOptions" class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap" - @end="handleDragOnEnd" + @end="moveList" > <component :is="boardColumnComponent" @@ -112,6 +101,7 @@ export default { :key="index" ref="board" :list="list" + :data-draggable-item-type="$options.draggableItemTypes.list" :disabled="disabled" /> diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue index e014b82d362..7a936e75676 100644 --- a/app/assets/javascripts/boards/components/board_content_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue @@ -87,6 +87,7 @@ export default { v-bind="$attrs" :open="isSidebarOpen" class="boards-sidebar gl-absolute" + variant="sidebar" @close="handleClose" > <template #title> @@ -159,7 +160,7 @@ export default { :issuable-type="issuableType" data-testid="sidebar-due-date" /> - <board-sidebar-labels-select class="labels" /> + <board-sidebar-labels-select class="block labels" /> <sidebar-weight-widget v-if="weightFeatureAvailable" :iid="activeBoardItem.iid" diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue index cfd6b21fa66..7f242dea644 100644 --- a/app/assets/javascripts/boards/components/board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/board_filtered_search.vue @@ -27,7 +27,15 @@ export default { }, computed: { urlParams() { - const { authorUsername, labelName, assigneeUsername, search } = this.filterParams; + const { + authorUsername, + labelName, + assigneeUsername, + search, + milestoneTitle, + types, + weight, + } = this.filterParams; let notParams = {}; if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) { @@ -36,6 +44,9 @@ export default { 'not[label_name][]': this.filterParams.not.labelName, 'not[author_username]': this.filterParams.not.authorUsername, 'not[assignee_username]': this.filterParams.not.assigneeUsername, + 'not[types]': this.filterParams.not.types, + 'not[milestone_title]': this.filterParams.not.milestoneTitle, + 'not[weight]': this.filterParams.not.weight, }, undefined, ); @@ -46,7 +57,10 @@ export default { author_username: authorUsername, 'label_name[]': labelName, assignee_username: assigneeUsername, + milestone_title: milestoneTitle, search, + types, + weight, }; }, }, @@ -64,7 +78,15 @@ export default { this.performSearch(); }, getFilteredSearchValue() { - const { authorUsername, labelName, assigneeUsername, search } = this.filterParams; + const { + authorUsername, + labelName, + assigneeUsername, + search, + milestoneTitle, + types, + weight, + } = this.filterParams; const filteredSearchValue = []; if (authorUsername) { @@ -81,6 +103,13 @@ export default { }); } + if (types) { + filteredSearchValue.push({ + type: 'types', + value: { data: types, operator: '=' }, + }); + } + if (labelName?.length) { filteredSearchValue.push( ...labelName.map((label) => ({ @@ -90,6 +119,20 @@ export default { ); } + if (milestoneTitle) { + filteredSearchValue.push({ + type: 'milestone_title', + value: { data: milestoneTitle, operator: '=' }, + }); + } + + if (weight) { + filteredSearchValue.push({ + type: 'weight', + value: { data: weight, operator: '=' }, + }); + } + if (this.filterParams['not[authorUsername]']) { filteredSearchValue.push({ type: 'author_username', @@ -97,6 +140,20 @@ export default { }); } + if (this.filterParams['not[milestoneTitle]']) { + filteredSearchValue.push({ + type: 'milestone_title', + value: { data: this.filterParams['not[milestoneTitle]'], operator: '!=' }, + }); + } + + if (this.filterParams['not[weight]']) { + filteredSearchValue.push({ + type: 'weight', + value: { data: this.filterParams['not[weight]'], operator: '!=' }, + }); + } + if (this.filterParams['not[assigneeUsername]']) { filteredSearchValue.push({ type: 'assignee_username', @@ -113,6 +170,13 @@ export default { ); } + if (this.filterParams['not[types]']) { + filteredSearchValue.push({ + type: 'types', + value: { data: this.filterParams['not[types]'], operator: '!=' }, + }); + } + if (search) { filteredSearchValue.push(search); } @@ -140,9 +204,18 @@ export default { case 'assignee_username': filterParams.assigneeUsername = filter.value.data; break; + case 'types': + filterParams.types = filter.value.data; + break; case 'label_name': labels.push(filter.value.data); break; + case 'milestone_title': + filterParams.milestoneTitle = filter.value.data; + break; + case 'weight': + filterParams.weight = filter.value.data; + break; case 'filtered-search-term': if (filter.value.data) plainText.push(filter.value.data); break; diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index 386ed6bd0a1..a89f71504a9 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -2,7 +2,7 @@ import { GlModal, GlAlert } from '@gitlab/ui'; import { mapGetters, mapActions, mapState } from 'vuex'; import ListLabel from '~/boards/models/label'; -import { TYPE_ITERATION, TYPE_MILESTONE, TYPE_USER } from '~/graphql_shared/constants'; +import { TYPE_ITERATION, TYPE_MILESTONE } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { getParameterByName, visitUrl } from '~/lib/utils/url_utility'; import { __, s__ } from '~/locale'; @@ -18,10 +18,9 @@ const boardDefaults = { id: false, name: '', labels: [], - milestone_id: undefined, + milestone: {}, iteration_id: undefined, assignee: {}, - assignee_id: undefined, weight: null, hide_backlog_list: false, hide_closed_list: false, @@ -190,13 +189,10 @@ export default { issueBoardScopeMutationVariables() { return { weight: this.board.weight, - assigneeId: this.board.assignee?.id - ? convertToGraphQLId(TYPE_USER, this.board.assignee.id) + assigneeId: this.board.assignee?.id || null, + milestoneId: this.board.milestone?.id + ? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id) : null, - milestoneId: - this.board.milestone?.id || this.board.milestone?.id === 0 - ? convertToGraphQLId(TYPE_MILESTONE, this.board.milestone.id) - : null, iterationId: this.board.iteration_id ? convertToGraphQLId(TYPE_ITERATION, this.board.iteration_id) : null, @@ -306,6 +302,19 @@ export default { } }); }, + setAssignee(assigneeId) { + this.$set(this.board, 'assignee', { + id: assigneeId, + }); + }, + setMilestone(milestoneId) { + this.$set(this.board, 'milestone', { + id: milestoneId, + }); + }, + setWeight(weight) { + this.$set(this.board, 'weight', weight); + }, }, }; </script> @@ -373,6 +382,9 @@ export default { :weights="weights" @set-iteration="setIteration" @set-board-labels="setBoardLabels" + @set-assignee="setAssignee" + @set-milestone="setMilestone" + @set-weight="setWeight" /> </form> </gl-modal> diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 8dca6be853f..849492effab 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -6,12 +6,13 @@ import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_opt import { sprintf, __ } from '~/locale'; import defaultSortableConfig from '~/sortable/sortable_config'; import Tracking from '~/tracking'; -import { toggleFormEventPrefix } from '../constants'; +import { toggleFormEventPrefix, DraggableItemTypes } from '../constants'; import eventHub from '../eventhub'; import BoardCard from './board_card.vue'; import BoardNewIssue from './board_new_issue.vue'; export default { + draggableItemTypes: DraggableItemTypes, name: 'BoardList', i18n: { loading: __('Loading'), @@ -27,11 +28,6 @@ export default { GlIntersectionObserver, }, mixins: [Tracking.mixin()], - inject: { - canAdminList: { - default: false, - }, - }, props: { disabled: { type: Boolean, @@ -89,8 +85,8 @@ export default { return !this.isEpicBoard && this.list.listType !== 'closed' && this.showIssueForm; }, listRef() { - // When list is draggable, the reference to the list needs to be accessed differently - return this.canAdminList ? this.$refs.list.$el : this.$refs.list; + // When list is draggable, the reference to the list needs to be accessed differently + return this.canMoveIssue ? this.$refs.list.$el : this.$refs.list; }, showingAllItems() { return this.boardItems.length === this.listItemsCount; @@ -100,8 +96,11 @@ export default { ? this.$options.i18n.showingAllEpics : this.$options.i18n.showingAllIssues; }, + canMoveIssue() { + return !this.disabled; + }, treeRootWrapper() { - return this.canAdminList && !this.listsFlags[this.list.id]?.addItemToListInProgress + return this.canMoveIssue && !this.listsFlags[this.list.id]?.addItemToListInProgress ? Draggable : 'ul'; }, @@ -116,7 +115,7 @@ export default { value: this.boardItems, }; - return this.canAdminList ? options : {}; + return this.canMoveIssue ? options : {}; }, }, watch: { @@ -172,15 +171,33 @@ export default { this.loadNextPage(); } }, - handleDragOnStart() { + handleDragOnStart({ + item: { + dataset: { draggableItemType }, + }, + }) { + if (draggableItemType !== DraggableItemTypes.card) { + return; + } + sortableStart(); this.track('drag_card', { label: 'board' }); }, - handleDragOnEnd(params) { + handleDragOnEnd({ + newIndex: originalNewIndex, + oldIndex, + from, + to, + item: { + dataset: { draggableItemType, itemId, itemIid, itemPath }, + }, + }) { + if (draggableItemType !== DraggableItemTypes.card) { + return; + } + sortableEnd(); - const { oldIndex, from, to, item } = params; - let { newIndex } = params; - const { itemId, itemIid, itemPath } = item.dataset; + let newIndex = originalNewIndex; let { children } = to; let moveBeforeId; let moveAfterId; @@ -267,6 +284,7 @@ export default { :index="index" :list="list" :item="item" + :data-draggable-item-type="$options.draggableItemTypes.card" :disabled="disabled" /> <gl-intersection-observer @appear="onReachingListBottom"> diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index caeecb25227..84c9191975e 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -1,21 +1,19 @@ <script> -import { GlButton } from '@gitlab/ui'; import { mapActions, mapGetters, mapState } from 'vuex'; import { getMilestone } from 'ee_else_ce/boards/boards_util'; import BoardNewIssueMixin from 'ee_else_ce/boards/mixins/board_new_issue'; -import { __ } from '~/locale'; + import { toggleFormEventPrefix } from '../constants'; import eventHub from '../eventhub'; + +import BoardNewItem from './board_new_item.vue'; import ProjectSelect from './project_select.vue'; export default { name: 'BoardNewIssue', - i18n: { - cancel: __('Cancel'), - }, components: { + BoardNewItem, ProjectSelect, - GlButton, }, mixins: [BoardNewIssueMixin], inject: ['groupId'], @@ -25,106 +23,55 @@ export default { required: true, }, }, - data() { - return { - title: '', - }; - }, computed: { - ...mapState(['selectedProject']), - ...mapGetters(['isGroupBoard', 'isEpicBoard']), - /** - * We've extended this component in EE where - * submitButtonTitle returns a different string - * hence this is kept as a computed prop. - */ - submitButtonTitle() { - return __('Create issue'); + ...mapState(['selectedProject', 'fullPath']), + ...mapGetters(['isGroupBoard']), + formEventPrefix() { + return toggleFormEventPrefix.issue; }, - disabled() { - if (this.isGroupBoard) { - return this.title === '' || !this.selectedProject.name; - } - return this.title === ''; + disableSubmit() { + return this.isGroupBoard ? !this.selectedProject.name : false; }, - inputFieldId() { - // eslint-disable-next-line @gitlab/require-i18n-strings - return `${this.list.id}-title`; + projectPath() { + return this.isGroupBoard ? this.selectedProject.fullPath : this.fullPath; }, }, - mounted() { - this.$refs.input.focus(); - eventHub.$on('setSelectedProject', this.setSelectedProject); - }, methods: { ...mapActions(['addListNewIssue']), - submit() { - const { title } = this; + submit({ title }) { const labels = this.list.label ? [this.list.label] : []; const assignees = this.list.assignee ? [this.list.assignee] : []; const milestone = getMilestone(this.list); - eventHub.$emit(`scroll-board-list-${this.list.id}`); - return this.addListNewIssue({ + list: this.list, issueInput: { title, labelIds: labels?.map((l) => l.id), assigneeIds: assignees?.map((a) => a?.id), milestoneId: milestone?.id, - projectPath: this.selectedProject.fullPath, - ...this.extraIssueInput(), + projectPath: this.projectPath, }, - list: this.list, }).then(() => { - this.reset(); + this.cancel(); }); }, - reset() { - this.title = ''; - eventHub.$emit(`${toggleFormEventPrefix.issue}${this.list.id}`); + cancel() { + eventHub.$emit(`${this.formEventPrefix}${this.list.id}`); }, }, }; </script> <template> - <div class="board-new-issue-form"> - <div class="board-card position-relative p-3 rounded"> - <form ref="submitForm" @submit.prevent="submit"> - <label :for="inputFieldId" class="label-bold">{{ __('Title') }}</label> - <input - :id="inputFieldId" - ref="input" - v-model="title" - class="form-control" - type="text" - name="issue_title" - autocomplete="off" - /> - <project-select v-if="isGroupBoard && !isEpicBoard" :group-id="groupId" :list="list" /> - <div class="clearfix gl-mt-3"> - <gl-button - ref="submitButton" - :disabled="disabled" - class="float-left js-no-auto-disable" - variant="confirm" - category="primary" - type="submit" - > - {{ submitButtonTitle }} - </gl-button> - <gl-button - ref="cancelButton" - class="float-right" - type="button" - variant="default" - @click="reset" - > - {{ $options.i18n.cancel }} - </gl-button> - </div> - </form> - </div> - </div> + <board-new-item + :list="list" + :form-event-prefix="formEventPrefix" + :submit-button-title="__('Create issue')" + :disable-submit="disableSubmit" + @form-submit="submit" + @form-cancel="cancel" + > + <project-select v-if="isGroupBoard" :group-id="groupId" :list="list" /> + </board-new-item> </template> diff --git a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue index 1218941065f..a25b436b8de 100644 --- a/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue +++ b/app/assets/javascripts/boards/components/board_new_issue_deprecated.vue @@ -11,7 +11,7 @@ import ProjectSelect from './project_select_deprecated.vue'; // This component is being replaced in favor of './board_new_issue.vue' for GraphQL boards export default { - name: 'BoardNewIssue', + name: 'BoardNewIssueDeprecated', components: { ProjectSelect, GlButton, diff --git a/app/assets/javascripts/boards/components/board_new_item.vue b/app/assets/javascripts/boards/components/board_new_item.vue new file mode 100644 index 00000000000..44574de17d7 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_new_item.vue @@ -0,0 +1,95 @@ +<script> +import { GlForm, GlFormInput, GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; + +import eventHub from '../eventhub'; + +export default { + i18n: { + cancel: __('Cancel'), + }, + components: { + GlForm, + GlFormInput, + GlButton, + }, + props: { + list: { + type: Object, + required: true, + }, + formEventPrefix: { + type: String, + required: true, + }, + disableSubmit: { + type: Boolean, + required: false, + default: false, + }, + submitButtonTitle: { + type: String, + required: false, + default: __('Create issue'), + }, + }, + data() { + return { + title: '', + }; + }, + computed: { + inputFieldId() { + // eslint-disable-next-line @gitlab/require-i18n-strings + return `${this.list.id}-title`; + }, + }, + methods: { + handleFormCancel() { + this.title = ''; + this.$emit('form-cancel'); + }, + handleFormSubmit() { + const { title, list } = this; + + eventHub.$emit(`scroll-board-list-${this.list.id}`); + this.$emit('form-submit', { + title, + list, + }); + }, + }, +}; +</script> + +<template> + <div class="board-new-issue-form"> + <div class="board-card position-relative gl-p-5 rounded"> + <gl-form @submit.prevent="handleFormSubmit" @reset="handleFormCancel"> + <label :for="inputFieldId" class="gl-font-weight-bold">{{ __('Title') }}</label> + <gl-form-input + :id="inputFieldId" + v-model.trim="title" + :autofocus="true" + autocomplete="off" + type="text" + name="issue_title" + /> + <slot></slot> + <div class="gl-clearfix gl-mt-4"> + <gl-button + :disabled="!title || disableSubmit" + class="gl-float-left js-no-auto-disable" + variant="confirm" + type="submit" + > + {{ submitButtonTitle }} + </gl-button> + <gl-button class="gl-float-right js-no-auto-disable" type="reset"> + {{ $options.i18n.cancel }} + </gl-button> + </div> + </gl-form> + </div> + </div> +</template> diff --git a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue index d8dac17d326..5206db05410 100644 --- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue @@ -1,4 +1,6 @@ <script> +import { GlFilteredSearchToken } from '@gitlab/ui'; +import { mapActions } from 'vuex'; import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue'; import issueBoardFilters from '~/boards/issue_board_filters'; import { TYPE_USER } from '~/graphql_shared/constants'; @@ -6,13 +8,24 @@ import { convertToGraphQLId } from '~/graphql_shared/utils'; import { __ } from '~/locale'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; +import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; +import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue'; export default { + types: { + ISSUE: 'ISSUE', + INCIDENT: 'INCIDENT', + }, i18n: { search: __('Search'), label: __('Label'), author: __('Author'), assignee: __('Assignee'), + type: __('Type'), + incident: __('Incident'), + issue: __('Issue'), + milestone: __('Milestone'), + weight: __('Weight'), is: __('is'), isNot: __('is not'), }, @@ -29,7 +42,19 @@ export default { }, computed: { tokens() { - const { label, is, isNot, author, assignee } = this.$options.i18n; + const { + label, + is, + isNot, + author, + assignee, + issue, + incident, + type, + milestone, + weight, + } = this.$options.i18n; + const { types } = this.$options; const { fetchAuthors, fetchLabels } = issueBoardFilters( this.$apollo, this.fullPath, @@ -77,10 +102,40 @@ export default { fetchAuthors, preloadedAuthors: this.preloadedAuthors(), }, + { + icon: 'issues', + title: type, + type: 'types', + operators: [{ value: '=', description: is }], + token: GlFilteredSearchToken, + unique: true, + options: [ + { icon: 'issue-type-issue', value: types.ISSUE, title: issue }, + { icon: 'issue-type-incident', value: types.INCIDENT, title: incident }, + ], + }, + { + type: 'milestone_title', + title: milestone, + icon: 'clock', + symbol: '%', + token: MilestoneToken, + unique: true, + defaultMilestones: [], // todo: https://gitlab.com/gitlab-org/gitlab/-/issues/337044#note_640010094 + fetchMilestones: this.fetchMilestones, + }, + { + type: 'weight', + title: weight, + icon: 'weight', + token: WeightToken, + unique: true, + }, ]; }, }, methods: { + ...mapActions(['fetchMilestones']), preloadedAuthors() { return gon?.current_user_id ? [ 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 84802650dad..e7696b8d31b 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue @@ -87,7 +87,7 @@ export default { <div> <header v-show="showHeader" - class="gl-display-flex gl-justify-content-space-between gl-align-items-flex-start gl-mb-3" + class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-mb-2" > <span class="gl-vertical-align-middle"> <slot name="title"> @@ -97,7 +97,8 @@ export default { </span> <gl-button v-if="canUpdate" - variant="link" + category="tertiary" + size="small" class="gl-text-gray-900! gl-ml-5 js-sidebar-dropdown-toggle edit-link" data-testid="edit-button" @click="toggle" 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 29febd0fa51..e74463825c5 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 @@ -25,6 +25,8 @@ export default { data() { return { loading: false, + oldIid: null, + isEditing: false, }; }, computed: { @@ -72,6 +74,15 @@ export default { return this.labelsFetchPath || projectLabelsFetchPath; }, }, + watch: { + activeBoardItem(_, oldVal) { + if (this.isEditing) { + this.oldIid = oldVal.iid; + } else { + this.oldIid = null; + } + }, + }, methods: { ...mapActions(['setActiveBoardItemLabels', 'setError']), async setLabels(payload) { @@ -84,8 +95,14 @@ export default { .filter((label) => !payload.find((selected) => selected.id === label.id)) .map((label) => label.id); - const input = { addLabelIds, removeLabelIds, projectPath: this.projectPathForActiveIssue }; + const input = { + addLabelIds, + removeLabelIds, + projectPath: this.projectPathForActiveIssue, + iid: this.oldIid, + }; await this.setActiveBoardItemLabels(input); + this.oldIid = null; } catch (e) { this.setError({ error: e, message: __('An error occurred while updating labels.') }); } finally { @@ -115,6 +132,8 @@ export default { :title="__('Labels')" :loading="loading" data-testid="sidebar-labels" + @open="isEditing = true" + @close="isEditing = false" > <template #collapsed> <gl-label |