diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/boards | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/boards')
34 files changed, 342 insertions, 373 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index e6c91c7ac1f..7e4d3ebb686 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -1,6 +1,6 @@ import { sortBy, cloneDeep } from 'lodash'; import { isGid } from '~/graphql_shared/utils'; -import { ListType, MilestoneIDs } from './constants'; +import { ListType, MilestoneIDs, AssigneeFilterType, MilestoneFilterType } from './constants'; export function getMilestone() { return null; @@ -186,6 +186,7 @@ export function isListDraggable(list) { export const FiltersInfo = { assigneeUsername: { negatedSupport: true, + remap: (k, v) => (v === AssigneeFilterType.any ? 'assigneeWildcardId' : k), }, assigneeId: { // assigneeId should be renamed to assigneeWildcardId. @@ -204,6 +205,11 @@ export const FiltersInfo = { }, milestoneTitle: { negatedSupport: true, + remap: (k, v) => (Object.values(MilestoneFilterType).includes(v) ? 'milestoneWildcardId' : k), + }, + milestoneWildcardId: { + negatedSupport: true, + transform: (val) => val.toUpperCase(), }, myReactionEmoji: { negatedSupport: true, @@ -214,6 +220,10 @@ export const FiltersInfo = { types: { negatedSupport: true, }, + confidential: { + negatedSupport: false, + transform: (val) => val === 'yes', + }, search: { negatedSupport: false, }, diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue index b6ccc6a00fe..ea80496c3f5 100644 --- a/app/assets/javascripts/boards/components/board_card_inner.vue +++ b/app/assets/javascripts/boards/components/board_card_inner.vue @@ -13,7 +13,7 @@ import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner'; import { isScopedLabel } from '~/lib/utils/common_utils'; import { updateHistory } from '~/lib/utils/url_utility'; import { sprintf, __, n__ } from '~/locale'; -import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; import { ListType } from '../constants'; import eventHub from '../eventhub'; diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue index 54668c9e88e..f89f8e5feb8 100644 --- a/app/assets/javascripts/boards/components/board_content_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue @@ -4,7 +4,6 @@ import { MountingPortal } from 'portal-vue'; import { mapState, mapActions, mapGetters } from 'vuex'; import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue'; import { __, sprintf } from '~/locale'; -import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue'; import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue'; import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import { ISSUABLE } from '~/boards/constants'; @@ -26,7 +25,6 @@ export default { SidebarDateWidget, SidebarConfidentialityWidget, BoardSidebarTimeTracker, - BoardSidebarLabelsSelect, SidebarLabelsWidget, SidebarSubscriptionsWidget, SidebarDropdownWidget, @@ -210,7 +208,6 @@ export default { data-testid="sidebar-due-date" /> <sidebar-labels-widget - v-if="glFeatures.labelsWidget" class="block labels" data-testid="sidebar-labels" :iid="activeBoardItem.iid" @@ -230,7 +227,6 @@ export default { > {{ __('None') }} </sidebar-labels-widget> - <board-sidebar-labels-select v-else 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 6e6ada2d109..09ec385bbba 100644 --- a/app/assets/javascripts/boards/components/board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/board_filtered_search.vue @@ -1,7 +1,7 @@ <script> import { pickBy, isEmpty } from 'lodash'; import { mapActions } from 'vuex'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils'; import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants'; @@ -39,30 +39,33 @@ export default { assigneeUsername, search, milestoneTitle, + iterationId, types, weight, epicId, myReactionEmoji, + releaseTag, + confidential, } = this.filterParams; const filteredSearchValue = []; if (authorUsername) { filteredSearchValue.push({ - type: 'author_username', + type: 'author', value: { data: authorUsername, operator: '=' }, }); } if (assigneeUsername) { filteredSearchValue.push({ - type: 'assignee_username', + type: 'assignee', value: { data: assigneeUsername, operator: '=' }, }); } if (types) { filteredSearchValue.push({ - type: 'types', + type: 'type', value: { data: types, operator: '=' }, }); } @@ -70,7 +73,7 @@ export default { if (labelName?.length) { filteredSearchValue.push( ...labelName.map((label) => ({ - type: 'label_name', + type: 'label', value: { data: label, operator: '=' }, })), ); @@ -78,11 +81,18 @@ export default { if (milestoneTitle) { filteredSearchValue.push({ - type: 'milestone_title', + type: 'milestone', value: { data: milestoneTitle, operator: '=' }, }); } + if (iterationId) { + filteredSearchValue.push({ + type: 'iteration', + value: { data: iterationId, operator: '=' }, + }); + } + if (weight) { filteredSearchValue.push({ type: 'weight', @@ -92,32 +102,53 @@ export default { if (myReactionEmoji) { filteredSearchValue.push({ - type: 'my_reaction_emoji', + type: 'my-reaction', value: { data: myReactionEmoji, operator: '=' }, }); } + if (releaseTag) { + filteredSearchValue.push({ + type: 'release', + value: { data: releaseTag, operator: '=' }, + }); + } + + if (confidential !== undefined) { + filteredSearchValue.push({ + type: 'confidential', + value: { data: confidential }, + }); + } + if (epicId) { filteredSearchValue.push({ - type: 'epic_id', + type: 'epic', value: { data: epicId, operator: '=' }, }); } if (this.filterParams['not[authorUsername]']) { filteredSearchValue.push({ - type: 'author_username', + type: 'author', value: { data: this.filterParams['not[authorUsername]'], operator: '!=' }, }); } if (this.filterParams['not[milestoneTitle]']) { filteredSearchValue.push({ - type: 'milestone_title', + type: 'milestone', value: { data: this.filterParams['not[milestoneTitle]'], operator: '!=' }, }); } + if (this.filterParams['not[iteration_id]']) { + filteredSearchValue.push({ + type: 'iteration_id', + value: { data: this.filterParams['not[iteration_id]'], operator: '!=' }, + }); + } + if (this.filterParams['not[weight]']) { filteredSearchValue.push({ type: 'weight', @@ -127,7 +158,7 @@ export default { if (this.filterParams['not[assigneeUsername]']) { filteredSearchValue.push({ - type: 'assignee_username', + type: 'assignee', value: { data: this.filterParams['not[assigneeUsername]'], operator: '!=' }, }); } @@ -135,7 +166,7 @@ export default { if (this.filterParams['not[labelName]']) { filteredSearchValue.push( ...this.filterParams['not[labelName]'].map((label) => ({ - type: 'label_name', + type: 'label', value: { data: label, operator: '!=' }, })), ); @@ -143,25 +174,32 @@ export default { if (this.filterParams['not[types]']) { filteredSearchValue.push({ - type: 'types', + type: 'type', value: { data: this.filterParams['not[types]'], operator: '!=' }, }); } if (this.filterParams['not[epicId]']) { filteredSearchValue.push({ - type: 'epic_id', + type: 'epic', value: { data: this.filterParams['not[epicId]'], operator: '!=' }, }); } if (this.filterParams['not[myReactionEmoji]']) { filteredSearchValue.push({ - type: 'my_reaction_emoji', + type: 'my-reaction', value: { data: this.filterParams['not[myReactionEmoji]'], operator: '!=' }, }); } + if (this.filterParams['not[releaseTag]']) { + filteredSearchValue.push({ + type: 'release', + value: { data: this.filterParams['not[releaseTag]'], operator: '!=' }, + }); + } + if (search) { filteredSearchValue.push(search); } @@ -179,8 +217,10 @@ export default { weight, epicId, myReactionEmoji, + iterationId, + releaseTag, + confidential, } = this.filterParams; - let notParams = {}; if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) { @@ -194,6 +234,8 @@ export default { 'not[weight]': this.filterParams.not.weight, 'not[epic_id]': this.filterParams.not.epicId, 'not[my_reaction_emoji]': this.filterParams.not.myReactionEmoji, + 'not[iteration_id]': this.filterParams.not.iterationId, + 'not[release_tag]': this.filterParams.not.releaseTag, }, undefined, ); @@ -205,11 +247,14 @@ export default { 'label_name[]': labelName, assignee_username: assigneeUsername, milestone_title: milestoneTitle, + iteration_id: iterationId, search, types, weight, - epic_id: getIdFromGraphQLId(epicId), + epic_id: isGid(epicId) ? getIdFromGraphQLId(epicId) : epicId, my_reaction_emoji: myReactionEmoji, + release_tag: releaseTag, + confidential, }; }, }, @@ -246,30 +291,39 @@ export default { filters.forEach((filter) => { switch (filter.type) { - case 'author_username': + case 'author': filterParams.authorUsername = filter.value.data; break; - case 'assignee_username': + case 'assignee': filterParams.assigneeUsername = filter.value.data; break; - case 'types': + case 'type': filterParams.types = filter.value.data; break; - case 'label_name': + case 'label': labels.push(filter.value.data); break; - case 'milestone_title': + case 'milestone': filterParams.milestoneTitle = filter.value.data; break; + case 'iteration': + filterParams.iterationId = filter.value.data; + break; case 'weight': filterParams.weight = filter.value.data; break; - case 'epic_id': + case 'epic': filterParams.epicId = filter.value.data; break; - case 'my_reaction_emoji': + case 'my-reaction': filterParams.myReactionEmoji = filter.value.data; break; + case 'release': + filterParams.releaseTag = filter.value.data; + break; + case 'confidential': + filterParams.confidential = filter.value.data; + break; case 'filtered-search-term': if (filter.value.data) plainText.push(filter.value.data); break; @@ -285,6 +339,7 @@ export default { if (plainText.length) { filterParams.search = plainText.join(' '); } + return filterParams; }, }, diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 47dffc985aa..e4c3c3206a8 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -6,6 +6,7 @@ import { sortableStart, sortableEnd } from '~/boards/mixins/sortable_default_opt import { sprintf, __ } from '~/locale'; import defaultSortableConfig from '~/sortable/sortable_config'; import Tracking from '~/tracking'; +import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import { toggleFormEventPrefix, DraggableItemTypes } from '../constants'; import eventHub from '../eventhub'; import BoardCard from './board_card.vue'; @@ -50,11 +51,22 @@ export default { showEpicForm: false, }; }, + apollo: { + boardList: { + query: listQuery, + variables() { + return { + id: this.list.id, + filters: this.filterParams, + }; + }, + }, + }, computed: { - ...mapState(['pageInfoByListId', 'listsFlags']), + ...mapState(['pageInfoByListId', 'listsFlags', 'filterParams']), ...mapGetters(['isEpicBoard']), listItemsCount() { - return this.isEpicBoard ? this.list.epicsCount : this.list.issuesCount; + return this.isEpicBoard ? this.list.epicsCount : this.boardList?.issuesCount; }, paginatedIssueText() { return sprintf(__('Showing %{pageSize} of %{total} %{issuableType}'), { diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index e985a368e64..19004518edf 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -17,6 +17,7 @@ import sidebarEventHub from '~/sidebar/event_hub'; import Tracking from '~/tracking'; import { formatDate } from '~/lib/utils/datetime_utility'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import AccessorUtilities from '../../lib/utils/accessor'; import { inactiveId, LIST, ListType, toggleFormEventPrefix } from '../constants'; import eventHub from '../eventhub'; @@ -74,7 +75,7 @@ export default { }, }, computed: { - ...mapState(['activeId']), + ...mapState(['activeId', 'filterParams']), ...mapGetters(['isEpicBoard', 'isSwimlanesOn']), isLoggedIn() { return Boolean(this.currentUserId); @@ -119,14 +120,11 @@ export default { } return false; }, - itemsCount() { - return this.list.issuesCount; - }, countIcon() { return 'issues'; }, itemsTooltipLabel() { - return n__(`%d issue`, `%d issues`, this.itemsCount); + return n__(`%d issue`, `%d issues`, this.boardLists?.issuesCount); }, chevronTooltip() { return this.list.collapsed ? this.$options.i18n.expand : this.$options.i18n.collapse; @@ -158,6 +156,23 @@ export default { userCanDrag() { return !this.disabled && isListDraggable(this.list); }, + isLoading() { + return this.$apollo.queries.boardList.loading; + }, + }, + apollo: { + boardList: { + query: listQuery, + variables() { + return { + id: this.list.id, + filters: this.filterParams, + }; + }, + skip() { + return this.isEpicBoard; + }, + }, }, created() { const localCollapsed = parseBoolean(localStorage.getItem(`${this.uniqueKey}.collapsed`)); @@ -375,10 +390,10 @@ export default { </gl-sprintf> </div> <div v-else>• {{ itemsTooltipLabel }}</div> - <div v-if="weightFeatureAvailable"> + <div v-if="weightFeatureAvailable && !isLoading"> • <gl-sprintf :message="__('%{totalWeight} total weight')"> - <template #totalWeight>{{ list.totalWeight }}</template> + <template #totalWeight>{{ boardList.totalWeight }}</template> </gl-sprintf> </div> </gl-tooltip> @@ -396,14 +411,18 @@ export default { <gl-tooltip :target="() => $refs.itemCount" :title="itemsTooltipLabel" /> <span ref="itemCount" class="gl-display-inline-flex gl-align-items-center"> <gl-icon class="gl-mr-2" :name="countIcon" /> - <item-count :items-size="itemsCount" :max-issue-count="list.maxIssueCount" /> + <item-count + v-if="!isLoading" + :items-size="isEpicBoard ? list.epicsCount : boardList.issuesCount" + :max-issue-count="list.maxIssueCount" + /> </span> <!-- EE start --> - <template v-if="weightFeatureAvailable && !isEpicBoard"> + <template v-if="weightFeatureAvailable && !isEpicBoard && !isLoading"> <gl-tooltip :target="() => $refs.weightTooltip" :title="weightCountToolTip" /> <span ref="weightTooltip" class="gl-display-inline-flex gl-ml-3"> <gl-icon class="gl-mr-2" name="weight" /> - {{ list.totalWeight }} + {{ boardList.totalWeight }} </span> </template> <!-- EE end --> diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 71facba1378..69343cd78d8 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -349,6 +349,9 @@ export default { v-if="showCreate" v-gl-modal-directive="'board-config-modal'" data-qa-selector="create_new_board_button" + data-track-action="click_button" + data-track-label="create_new_board" + data-track-property="dropdown" @click.prevent="showPage('new')" > {{ s__('IssueBoards|Create new board') }} 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 bdb9c2be836..7fc87f9f672 100644 --- a/app/assets/javascripts/boards/components/issue_board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/issue_board_filtered_search.vue @@ -2,22 +2,25 @@ import { GlFilteredSearchToken } from '@gitlab/ui'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; import { mapActions } from 'vuex'; +import { orderBy } from 'lodash'; import BoardFilteredSearch from 'ee_else_ce/boards/components/board_filtered_search.vue'; import { BoardType } from '~/boards/constants'; import axios from '~/lib/utils/axios_utils'; +import { joinPaths } from '~/lib/utils/url_utility'; import issueBoardFilters from '~/boards/issue_board_filters'; import { TYPE_USER } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { __ } from '~/locale'; import { - DEFAULT_MILESTONES_GRAPHQL, TOKEN_TITLE_MY_REACTION, + OPERATOR_IS_AND_IS_NOT, + OPERATOR_IS_ONLY, } from '~/vue_shared/components/filtered_search_bar/constants'; import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue'; import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_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'; +import ReleaseToken from '~/vue_shared/components/filtered_search_bar/tokens/release_token.vue'; export default { types: { @@ -34,12 +37,11 @@ export default { incident: __('Incident'), issue: __('Issue'), milestone: __('Milestone'), - weight: __('Weight'), - is: __('is'), - isNot: __('is not'), + release: __('Release'), + confidential: __('Confidential'), }, components: { BoardFilteredSearch }, - inject: ['isSignedIn'], + inject: ['isSignedIn', 'releasesFetchPath'], props: { fullPath: { type: String, @@ -62,15 +64,14 @@ export default { tokensCE() { const { label, - is, - isNot, author, assignee, issue, incident, type, milestone, - weight, + release, + confidential, } = this.$options.i18n; const { types } = this.$options; const { fetchAuthors, fetchLabels } = issueBoardFilters( @@ -79,15 +80,12 @@ export default { this.boardType, ); - return [ + const tokens = [ { icon: 'user', title: assignee, - type: 'assignee_username', - operators: [ - { value: '=', description: is }, - { value: '!=', description: isNot }, - ], + type: 'assignee', + operators: OPERATOR_IS_AND_IS_NOT, token: AuthorToken, unique: true, fetchAuthors, @@ -96,11 +94,8 @@ export default { { icon: 'pencil', title: author, - type: 'author_username', - operators: [ - { value: '=', description: is }, - { value: '!=', description: isNot }, - ], + type: 'author', + operators: OPERATOR_IS_AND_IS_NOT, symbol: '@', token: AuthorToken, unique: true, @@ -110,11 +105,8 @@ export default { { icon: 'labels', title: label, - type: 'label_name', - operators: [ - { value: '=', description: is }, - { value: '!=', description: isNot }, - ], + type: 'label', + operators: OPERATOR_IS_AND_IS_NOT, token: LabelToken, unique: false, symbol: '~', @@ -123,7 +115,7 @@ export default { ...(this.isSignedIn ? [ { - type: 'my_reaction_emoji', + type: 'my-reaction', title: TOKEN_TITLE_MY_REACTION, icon: 'thumb-up', token: EmojiToken, @@ -144,22 +136,33 @@ export default { }); }, }, + { + type: 'confidential', + icon: 'eye-slash', + title: confidential, + unique: true, + token: GlFilteredSearchToken, + operators: OPERATOR_IS_ONLY, + options: [ + { icon: 'eye-slash', value: 'yes', title: __('Yes') }, + { icon: 'eye', value: 'no', title: __('No') }, + ], + }, ] : []), { - type: 'milestone_title', + type: 'milestone', title: milestone, icon: 'clock', symbol: '%', token: MilestoneToken, unique: true, - defaultMilestones: DEFAULT_MILESTONES_GRAPHQL, fetchMilestones: this.fetchMilestones, }, { icon: 'issues', title: type, - type: 'types', + type: 'type', token: GlFilteredSearchToken, unique: true, options: [ @@ -168,13 +171,27 @@ export default { ], }, { - type: 'weight', - title: weight, - icon: 'weight', - token: WeightToken, - unique: true, + type: 'release', + title: release, + icon: 'rocket', + token: ReleaseToken, + fetchReleases: (search) => { + // TODO: Switch to GraphQL query when backend is ready: https://gitlab.com/gitlab-org/gitlab/-/issues/337686 + return axios + .get(joinPaths(gon.relative_url_root, this.releasesFetchPath)) + .then(({ data }) => { + if (search) { + return fuzzaldrinPlus.filter(data, search, { + key: ['tag'], + }); + } + return data; + }); + }, }, ]; + + return orderBy(tokens, ['title']); }, tokens() { return this.tokensCE; 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 deleted file mode 100644 index ec53947fd5f..00000000000 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue +++ /dev/null @@ -1,173 +0,0 @@ -<script> -import { GlLabel } from '@gitlab/ui'; -import { mapGetters, mapActions } from 'vuex'; -import Api from '~/api'; -import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { isScopedLabel } from '~/lib/utils/common_utils'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import { __ } from '~/locale'; -import LabelsSelect from '~/vue_shared/components/sidebar/labels_select_vue/labels_select_root.vue'; - -export default { - components: { - BoardEditableItem, - LabelsSelect, - GlLabel, - }, - inject: { - labelsFetchPath: { - default: null, - }, - labelsManagePath: {}, - labelsFilterBasePath: {}, - }, - data() { - return { - loading: false, - oldIid: null, - isEditing: false, - }; - }, - computed: { - ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue']), - selectedLabels() { - const { labels = [] } = this.activeBoardItem; - - return labels.map((label) => ({ - ...label, - id: getIdFromGraphQLId(label.id), - })); - }, - issueLabels() { - const { labels = [] } = this.activeBoardItem; - - return labels.map((label) => ({ - ...label, - scoped: isScopedLabel(label), - })); - }, - fetchPath() { - /* - Labels fetched in epic boards are always group-level labels - and the correct path are passed from the backend (injected through labelsFetchPath) - - For issue boards, we should always include project-level labels and use a different endpoint. - (it requires knowing the project path of a selected issue.) - - Note 1. that we will be using GraphQL to fetch labels when we create a labels select widget. - And this component will be removed _wholesale_ https://gitlab.com/gitlab-org/gitlab/-/issues/300653. - - Note 2. Moreover, 'fetchPath' needs to be used as a key for 'labels-select' component to force updates. - 'labels-select' has its own vuex store and initializes the passed props as states - and these states aren't reactively bound to the passed props. - */ - - const projectLabelsFetchPath = mergeUrlParams( - { include_ancestor_groups: true }, - Api.buildUrl(Api.projectLabelsPath).replace( - ':namespace_path/:project_path', - this.projectPathForActiveIssue, - ), - ); - - 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) { - this.loading = true; - this.$refs.sidebarItem.collapse(); - - try { - const addLabelIds = payload.filter((label) => label.set).map((label) => label.id); - const removeLabelIds = payload.filter((label) => !label.set).map((label) => label.id); - - 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 { - this.loading = false; - } - }, - async removeLabel(id) { - this.loading = true; - - try { - const removeLabelIds = [getIdFromGraphQLId(id)]; - const input = { removeLabelIds, projectPath: this.projectPathForActiveIssue }; - await this.setActiveBoardItemLabels(input); - } catch (e) { - this.setError({ error: e, message: __('An error occurred when removing the label.') }); - } finally { - this.loading = false; - } - }, - }, -}; -</script> - -<template> - <board-editable-item - ref="sidebarItem" - :title="__('Labels')" - :loading="loading" - data-testid="sidebar-labels" - @open="isEditing = true" - @close="isEditing = false" - > - <template #collapsed> - <gl-label - v-for="label in issueLabels" - :key="label.id" - :background-color="label.color" - :title="label.title" - :description="label.description" - :scoped="label.scoped" - :show-close-button="true" - :disabled="loading" - class="gl-mr-2 gl-mb-2" - @close="removeLabel(label.id)" - /> - </template> - <template #default="{ edit }"> - <labels-select - ref="labelsSelect" - :key="fetchPath" - :allow-label-edit="false" - :allow-label-create="false" - :allow-multiselect="true" - :allow-scoped-labels="true" - :selected-labels="selectedLabels" - :labels-fetch-path="fetchPath" - :labels-manage-path="labelsManagePath" - :labels-filter-base-path="labelsFilterBasePath" - :labels-list-title="__('Select label')" - :dropdown-button-text="__('Choose labels')" - :is-editing="edit" - variant="sidebar" - class="gl-display-block labels gl-w-full" - @updateSelectedLabels="setLabels" - > - {{ __('None') }} - </labels-select> - </template> - </board-editable-item> -</template> diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue deleted file mode 100644 index 4f5c55d0c5d..00000000000 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue +++ /dev/null @@ -1,75 +0,0 @@ -<script> -import { GlToggle } from '@gitlab/ui'; -import { mapGetters, mapActions } from 'vuex'; -import { __, s__ } from '~/locale'; - -export default { - i18n: { - header: { - title: __('Notifications'), - /* Any change to subscribeDisabledDescription - must be reflected in app/helpers/notifications_helper.rb */ - subscribeDisabledDescription: __( - 'Notifications have been disabled by the project or group owner', - ), - }, - updateSubscribedErrorMessage: s__( - 'IssueBoards|An error occurred while setting notifications status. Please try again.', - ), - }, - components: { - GlToggle, - }, - inject: ['emailsDisabled'], - data() { - return { - loading: false, - }; - }, - computed: { - ...mapGetters(['activeBoardItem', 'projectPathForActiveIssue', 'isEpicBoard']), - isEmailsDisabled() { - return this.isEpicBoard ? this.emailsDisabled : this.activeBoardItem.emailsDisabled; - }, - notificationText() { - return this.isEmailsDisabled - ? this.$options.i18n.header.subscribeDisabledDescription - : this.$options.i18n.header.title; - }, - }, - methods: { - ...mapActions(['setActiveItemSubscribed', 'setError']), - async handleToggleSubscription() { - this.loading = true; - try { - await this.setActiveItemSubscribed({ - subscribed: !this.activeBoardItem.subscribed, - projectPath: this.projectPathForActiveIssue, - }); - } catch (error) { - this.setError({ error, message: this.$options.i18n.updateSubscribedErrorMessage }); - } finally { - this.loading = false; - } - }, - }, -}; -</script> - -<template> - <div - class="gl-display-flex gl-align-items-center gl-justify-content-space-between" - data-testid="sidebar-notifications" - > - <span data-testid="notification-header-text"> {{ notificationText }} </span> - <gl-toggle - v-if="!isEmailsDisabled" - :value="activeBoardItem.subscribed" - :is-loading="loading" - :label="$options.i18n.header.title" - label-position="hidden" - data-testid="notification-subscribe-toggle" - @change="handleToggleSubscription" - /> - </div> -</template> diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js index 391e0d1fb0a..851b5eca40d 100644 --- a/app/assets/javascripts/boards/constants.js +++ b/app/assets/javascripts/boards/constants.js @@ -104,8 +104,10 @@ export const FilterFields = { 'assigneeUsername', 'assigneeWildcardId', 'authorUsername', + 'confidential', 'labelName', 'milestoneTitle', + 'milestoneWildcardId', 'myReactionEmoji', 'releaseTag', 'search', @@ -114,6 +116,18 @@ export const FilterFields = { ], }; +/* eslint-disable @gitlab/require-i18n-strings */ +export const AssigneeFilterType = { + any: 'Any', +}; + +export const MilestoneFilterType = { + any: 'Any', + none: 'None', + started: 'Started', + upcoming: 'Upcoming', +}; + export const DraggableItemTypes = { card: 'card', list: 'list', diff --git a/app/assets/javascripts/boards/graphql/board_labels.query.graphql b/app/assets/javascripts/boards/graphql/board_labels.query.graphql index b19a24e8808..525a4863379 100644 --- a/app/assets/javascripts/boards/graphql/board_labels.query.graphql +++ b/app/assets/javascripts/boards/graphql/board_labels.query.graphql @@ -7,6 +7,7 @@ query BoardLabels( $isProject: Boolean = false ) { group(fullPath: $fullPath) @include(if: $isGroup) { + id labels(searchTerm: $searchTerm, onlyGroupLabels: true, includeAncestorGroups: true) { nodes { ...Label @@ -14,6 +15,7 @@ query BoardLabels( } } project(fullPath: $fullPath) @include(if: $isProject) { + id labels(searchTerm: $searchTerm, includeAncestorGroups: true) { nodes { ...Label diff --git a/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql index 0e1d11727cf..81cc7b4d246 100644 --- a/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql +++ b/app/assets/javascripts/boards/graphql/board_list_create.mutation.graphql @@ -2,6 +2,8 @@ mutation createBoardList($boardId: BoardID!, $backlog: Boolean, $labelId: LabelID) { boardListCreate(input: { boardId: $boardId, backlog: $backlog, labelId: $labelId }) { + # We have ID in a deeply nested fragment + # eslint-disable-next-line @graphql-eslint/require-id-when-available list { ...BoardListFragment } diff --git a/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql index d85b736720b..5b532906f6a 100644 --- a/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql +++ b/app/assets/javascripts/boards/graphql/board_list_shared.fragment.graphql @@ -4,7 +4,6 @@ fragment BoardListShared on BoardList { position listType collapsed - issuesCount label { id title diff --git a/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql b/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql index b474c9acb93..7ea0e2f915a 100644 --- a/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql +++ b/app/assets/javascripts/boards/graphql/board_list_update.mutation.graphql @@ -2,6 +2,8 @@ mutation UpdateBoardList($listId: ID!, $position: Int, $collapsed: Boolean) { updateBoardList(input: { listId: $listId, position: $position, collapsed: $collapsed }) { + # We have ID in a deeply nested fragment + # eslint-disable-next-line @graphql-eslint/require-id-when-available list { ...BoardListFragment } diff --git a/app/assets/javascripts/boards/graphql/board_lists.query.graphql b/app/assets/javascripts/boards/graphql/board_lists.query.graphql index 47e87907d76..e6e98864aad 100644 --- a/app/assets/javascripts/boards/graphql/board_lists.query.graphql +++ b/app/assets/javascripts/boards/graphql/board_lists.query.graphql @@ -8,9 +8,13 @@ query BoardLists( $isProject: Boolean = false ) { group(fullPath: $fullPath) @include(if: $isGroup) { + id board(id: $boardId) { + id hideBacklogList lists(issueFilters: $filters) { + # We have ID in a deeply nested fragment + # eslint-disable-next-line @graphql-eslint/require-id-when-available nodes { ...BoardListFragment } @@ -18,9 +22,13 @@ query BoardLists( } } project(fullPath: $fullPath) @include(if: $isProject) { + id board(id: $boardId) { + id hideBacklogList lists(issueFilters: $filters) { + # We have ID in a deeply nested fragment + # eslint-disable-next-line @graphql-eslint/require-id-when-available nodes { ...BoardListFragment } diff --git a/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql new file mode 100644 index 00000000000..bae3220dfad --- /dev/null +++ b/app/assets/javascripts/boards/graphql/board_lists_deferred.query.graphql @@ -0,0 +1,6 @@ +query BoardList($id: ID!, $filters: BoardIssueInput) { + boardList(id: $id, issueFilters: $filters) { + id + issuesCount + } +} diff --git a/app/assets/javascripts/boards/graphql/group_board.query.graphql b/app/assets/javascripts/boards/graphql/group_board.query.graphql index 77c8e0378f0..8d87b83da96 100644 --- a/app/assets/javascripts/boards/graphql/group_board.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_board.query.graphql @@ -2,6 +2,7 @@ query GroupBoard($fullPath: ID!, $boardId: ID!) { workspace: group(fullPath: $fullPath) { + id board(id: $boardId) { ...BoardScopeFragment } diff --git a/app/assets/javascripts/boards/graphql/group_board_members.query.graphql b/app/assets/javascripts/boards/graphql/group_board_members.query.graphql index d3251c2aa12..aec674eb006 100644 --- a/app/assets/javascripts/boards/graphql/group_board_members.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_board_members.query.graphql @@ -3,6 +3,7 @@ query GroupBoardMembers($fullPath: ID!, $search: String) { workspace: group(fullPath: $fullPath) { __typename + id assignees: groupMembers(search: $search, relations: [DIRECT, DESCENDANTS, INHERITED]) { __typename nodes { diff --git a/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql b/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql index 73aa9137dec..0963b3fbfaa 100644 --- a/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql @@ -1,5 +1,6 @@ query GroupBoardMilestones($fullPath: ID!, $searchTerm: String) { group(fullPath: $fullPath) { + id milestones(includeAncestors: true, searchTitle: $searchTerm) { nodes { id diff --git a/app/assets/javascripts/boards/graphql/group_boards.query.graphql b/app/assets/javascripts/boards/graphql/group_boards.query.graphql index feafd6ae10d..0823c4f5a83 100644 --- a/app/assets/javascripts/boards/graphql/group_boards.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_boards.query.graphql @@ -2,6 +2,7 @@ query group_boards($fullPath: ID!) { group(fullPath: $fullPath) { + id boards { edges { node { diff --git a/app/assets/javascripts/boards/graphql/group_projects.query.graphql b/app/assets/javascripts/boards/graphql/group_projects.query.graphql index c5732bbaff3..0da14d0b872 100644 --- a/app/assets/javascripts/boards/graphql/group_projects.query.graphql +++ b/app/assets/javascripts/boards/graphql/group_projects.query.graphql @@ -2,6 +2,7 @@ query boardsGetGroupProjects($fullPath: ID!, $search: String, $after: String) { group(fullPath: $fullPath) { + id projects(search: $search, after: $after, first: 100, includeSubgroups: true) { nodes { id diff --git a/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql index 70eb1dfbf7e..c9c5d744371 100644 --- a/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql +++ b/app/assets/javascripts/boards/graphql/issue_set_labels.mutation.graphql @@ -1,13 +1,12 @@ +#import "~/graphql_shared/fragments/label.fragment.graphql" + mutation issueSetLabels($input: UpdateIssueInput!) { - updateIssue(input: $input) { - issue { + updateIssuableLabels: updateIssue(input: $input) { + issuable: issue { id labels { nodes { - id - title - color - description + ...Label } } } diff --git a/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql index bfb87758e17..c130a64cac4 100644 --- a/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql +++ b/app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql @@ -1,6 +1,7 @@ mutation issueSetSubscription($input: IssueSetSubscriptionInput!) { updateIssuableSubscription: issueSetSubscription(input: $input) { issue { + id subscribed } errors diff --git a/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql index 6ad12d982e0..147cf040a85 100644 --- a/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql +++ b/app/assets/javascripts/boards/graphql/issue_set_title.mutation.graphql @@ -1,6 +1,7 @@ mutation issueSetTitle($input: UpdateIssueInput!) { updateIssuableTitle: updateIssue(input: $input) { issue { + id title } errors diff --git a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql index 9f93bc6d5bf..105f2931caa 100644 --- a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql +++ b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql @@ -1,6 +1,6 @@ #import "ee_else_ce/boards/graphql/issue.fragment.graphql" -query BoardListEE( +query BoardListsEE( $fullPath: ID! $boardId: ID! $id: ID @@ -11,7 +11,9 @@ query BoardListEE( $first: Int ) { group(fullPath: $fullPath) @include(if: $isGroup) { + id board(id: $boardId) { + id lists(id: $id, issueFilters: $filters) { nodes { id @@ -33,7 +35,9 @@ query BoardListEE( } } project(fullPath: $fullPath) @include(if: $isProject) { + id board(id: $boardId) { + id lists(id: $id, issueFilters: $filters) { nodes { id diff --git a/app/assets/javascripts/boards/graphql/project_board.query.graphql b/app/assets/javascripts/boards/graphql/project_board.query.graphql index 6e4cd6bed57..8246d615a6a 100644 --- a/app/assets/javascripts/boards/graphql/project_board.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_board.query.graphql @@ -2,6 +2,7 @@ query ProjectBoard($fullPath: ID!, $boardId: ID!) { workspace: project(fullPath: $fullPath) { + id board(id: $boardId) { ...BoardScopeFragment } diff --git a/app/assets/javascripts/boards/graphql/project_board_members.query.graphql b/app/assets/javascripts/boards/graphql/project_board_members.query.graphql index fc6cc6b832c..45bec5e574b 100644 --- a/app/assets/javascripts/boards/graphql/project_board_members.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_board_members.query.graphql @@ -3,6 +3,7 @@ query ProjectBoardMembers($fullPath: ID!, $search: String) { workspace: project(fullPath: $fullPath) { __typename + id assignees: projectMembers(search: $search) { __typename nodes { diff --git a/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql b/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql index 8dd4d256caa..e456823d78a 100644 --- a/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql @@ -1,5 +1,6 @@ query ProjectBoardMilestones($fullPath: ID!, $searchTerm: String) { project(fullPath: $fullPath) { + id milestones(searchTitle: $searchTerm, includeAncestors: true) { nodes { id diff --git a/app/assets/javascripts/boards/graphql/project_boards.query.graphql b/app/assets/javascripts/boards/graphql/project_boards.query.graphql index f98d25ba671..b8879bc260c 100644 --- a/app/assets/javascripts/boards/graphql/project_boards.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_boards.query.graphql @@ -2,6 +2,7 @@ query project_boards($fullPath: ID!) { project(fullPath: $fullPath) { + id boards { edges { node { diff --git a/app/assets/javascripts/boards/graphql/project_milestones.query.graphql b/app/assets/javascripts/boards/graphql/project_milestones.query.graphql index 61c9ddded9b..4c952096d76 100644 --- a/app/assets/javascripts/boards/graphql/project_milestones.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_milestones.query.graphql @@ -5,6 +5,7 @@ query boardProjectMilestones( $searchTitle: String ) { project(fullPath: $fullPath) { + id milestones(state: $state, includeAncestors: $includeAncestors, searchTitle: $searchTitle) { edges { node { diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 6fa8dd63245..ded3bfded86 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -110,7 +110,8 @@ export default () => { }); if (gon?.features?.issueBoardsFilteredSearch) { - initBoardsFilteredSearch(apolloProvider, isLoggedIn()); + const { releasesFetchPath } = $boardApp.dataset; + initBoardsFilteredSearch(apolloProvider, isLoggedIn(), releasesFetchPath); } mountBoardApp($boardApp); diff --git a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js index 1ea74d5685c..a8ade58e316 100644 --- a/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js +++ b/app/assets/javascripts/boards/mount_filtered_search_issue_boards.js @@ -4,7 +4,7 @@ import store from '~/boards/stores'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { queryToObject } from '~/lib/utils/url_utility'; -export default (apolloProvider, isSignedIn) => { +export default (apolloProvider, isSignedIn, releasesFetchPath) => { const el = document.getElementById('js-issue-board-filtered-search'); const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true }); @@ -21,6 +21,7 @@ export default (apolloProvider, isSignedIn) => { provide: { initialFilterParams, isSignedIn, + releasesFetchPath, }, store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094 apolloProvider, diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 3a96e535cf7..1ebfcfc331b 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -16,30 +16,30 @@ import { ListTypeTitles, DraggableItemTypes, } from 'ee_else_ce/boards/constants'; -import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql'; -import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { queryToObject } from '~/lib/utils/url_utility'; -import { s__ } from '~/locale'; import { + formatIssueInput, formatBoardLists, formatListIssues, formatListsPageInfo, formatIssue, - formatIssueInput, updateListPosition, moveItemListHelper, getMoveData, FiltersInfo, filterVariables, -} from '../boards_util'; +} from 'ee_else_ce/boards/boards_util'; +import createBoardListMutation from 'ee_else_ce/boards/graphql/board_list_create.mutation.graphql'; +import issueMoveListMutation from 'ee_else_ce/boards/graphql/issue_move_list.mutation.graphql'; +import totalCountAndWeightQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { queryToObject } from '~/lib/utils/url_utility'; +import { s__ } from '~/locale'; import { gqlClient } from '../graphql'; import boardLabelsQuery from '../graphql/board_labels.query.graphql'; import groupBoardMilestonesQuery from '../graphql/group_board_milestones.query.graphql'; import groupProjectsQuery from '../graphql/group_projects.query.graphql'; import issueCreateMutation from '../graphql/issue_create.mutation.graphql'; -import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql'; import listsIssuesQuery from '../graphql/lists_issues.query.graphql'; import projectBoardMilestonesQuery from '../graphql/project_board_milestones.query.graphql'; @@ -373,7 +373,6 @@ export default { commit(types.REQUEST_ITEMS_FOR_LIST, { listId, fetchNext }); const { fullPath, fullBoardId, boardType, filterParams } = state; - const variables = { fullPath, boardId: fullBoardId, @@ -503,9 +502,10 @@ export default { updateIssueOrder: async ({ commit, dispatch, state }, { moveData, mutationVariables = {} }) => { try { - const { itemId, fromListId, toListId, moveBeforeId, moveAfterId } = moveData; + const { itemId, fromListId, toListId, moveBeforeId, moveAfterId, itemNotInToList } = moveData; const { fullBoardId, + filterParams, boardItems: { [itemId]: { iid, referencePath }, }, @@ -524,6 +524,67 @@ export default { // 'mutationVariables' allows EE code to pass in extra parameters. ...mutationVariables, }, + update( + cache, + { + data: { + issueMoveList: { + issue: { weight }, + }, + }, + }, + ) { + if (fromListId === toListId) return; + + const updateFromList = () => { + const fromList = cache.readQuery({ + query: totalCountAndWeightQuery, + variables: { id: fromListId, filters: filterParams }, + }); + + const updatedFromList = { + boardList: { + __typename: 'BoardList', + id: fromList.boardList.id, + issuesCount: fromList.boardList.issuesCount - 1, + totalWeight: fromList.boardList.totalWeight - Number(weight), + }, + }; + + cache.writeQuery({ + query: totalCountAndWeightQuery, + variables: { id: fromListId, filters: filterParams }, + data: updatedFromList, + }); + }; + + const updateToList = () => { + if (!itemNotInToList) return; + + const toList = cache.readQuery({ + query: totalCountAndWeightQuery, + variables: { id: toListId, filters: filterParams }, + }); + + const updatedToList = { + boardList: { + __typename: 'BoardList', + id: toList.boardList.id, + issuesCount: toList.boardList.issuesCount + 1, + totalWeight: toList.boardList.totalWeight + Number(weight), + }, + }; + + cache.writeQuery({ + query: totalCountAndWeightQuery, + variables: { id: toListId, filters: filterParams }, + data: updatedToList, + }); + }; + + updateFromList(); + updateToList(); + }, }); if (data?.issueMoveList?.errors.length || !data.issueMoveList) { @@ -567,7 +628,7 @@ export default { }, addListNewIssue: ( - { state: { boardConfig, boardType, fullPath }, dispatch, commit }, + { state: { boardConfig, boardType, fullPath, filterParams }, dispatch, commit }, { issueInput, list, placeholderId = `tmp-${new Date().getTime()}` }, ) => { const input = formatIssueInput(issueInput, boardConfig); @@ -583,6 +644,27 @@ export default { .mutate({ mutation: issueCreateMutation, variables: { input }, + update(cache) { + const fromList = cache.readQuery({ + query: totalCountAndWeightQuery, + variables: { id: list.id, filters: filterParams }, + }); + + const updatedList = { + boardList: { + __typename: 'BoardList', + id: fromList.boardList.id, + issuesCount: fromList.boardList.issuesCount + 1, + totalWeight: fromList.boardList.totalWeight, + }, + }; + + cache.writeQuery({ + query: totalCountAndWeightQuery, + variables: { id: list.id, filters: filterParams }, + data: updatedList, + }); + }, }) .then(({ data }) => { if (data.createIssue.errors.length) { @@ -610,33 +692,6 @@ export default { setActiveIssueLabels: async ({ commit, getters }, input) => { const { activeBoardItem } = getters; - if (!gon.features?.labelsWidget) { - const { data } = await gqlClient.mutate({ - mutation: issueSetLabelsMutation, - variables: { - input: { - iid: input.iid || String(activeBoardItem.iid), - labelIds: input.labelsId ?? undefined, - addLabelIds: input.addLabelIds ?? [], - removeLabelIds: input.removeLabelIds ?? [], - projectPath: input.projectPath, - }, - }, - }); - - if (data.updateIssue?.errors?.length > 0) { - throw new Error(data.updateIssue.errors); - } - - commit(types.UPDATE_BOARD_ITEM_BY_ID, { - itemId: data.updateIssue?.issue?.id || activeBoardItem.id, - prop: 'labels', - value: data.updateIssue?.issue?.labels.nodes, - }); - - return; - } - let labels = input?.labels || []; if (input.removeLabelIds) { labels = activeBoardItem.labels.filter( |