diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-13 15:07:18 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-06-13 15:07:18 +0300 |
commit | c3eeb6a8d6a4b11f0bc5e5eb1ed43b0726f1ea26 (patch) | |
tree | 7283014bb12ce53b57d2703b229095ad58e6d820 /app/assets/javascripts/boards | |
parent | 10e15ac3c2798956ff6a43d7b36bdf86c68aa817 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/boards')
6 files changed, 319 insertions, 16 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 93bd97e691b..bf77aa4996c 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -5,7 +5,7 @@ import { TYPENAME_MILESTONE, TYPENAME_USER, } from '~/graphql_shared/constants'; -import { isGid, convertToGraphQLId } from '~/graphql_shared/utils'; +import { isGid, convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; import { ListType, MilestoneIDs, @@ -202,6 +202,38 @@ export function moveItemListHelper(item, fromList, toList) { return updatedItem; } +export function moveItemVariables({ + iid, + epicId, + fromListId, + toListId, + moveBeforeId, + moveAfterId, + isIssue, + boardId, + itemToMove, +}) { + if (isIssue) { + return { + iid, + boardId, + projectPath: itemToMove.referencePath.split(/[#]/)[0], + moveBeforeId: moveBeforeId ? getIdFromGraphQLId(moveBeforeId) : undefined, + moveAfterId: moveAfterId ? getIdFromGraphQLId(moveAfterId) : undefined, + fromListId: getIdFromGraphQLId(fromListId), + toListId: getIdFromGraphQLId(toListId), + }; + } + return { + epicId, + boardId, + moveBeforeId, + moveAfterId, + fromListId, + toListId, + }; +} + export function isListDraggable(list) { return list.listType !== ListType.backlog && list.listType !== ListType.closed; } diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 5f082066ad4..218711346b0 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -9,6 +9,7 @@ import { sortableStart, sortableEnd } from '~/sortable/utils'; import Tracking from '~/tracking'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { DEFAULT_BOARD_LIST_ITEMS_SIZE, toggleFormEventPrefix, @@ -16,6 +17,13 @@ import { listIssuablesQueries, ListType, } from 'ee_else_ce/boards/constants'; +import { + addItemToList, + removeItemFromList, + updateEpicsCount, + updateIssueCountAndWeight, +} from '../graphql/cache_updates'; +import { shouldCloneCard, moveItemVariables } from '../boards_util'; import eventHub from '../eventhub'; import BoardCard from './board_card.vue'; import BoardNewIssue from './board_new_issue.vue'; @@ -37,7 +45,7 @@ export default { GlIntersectionObserver, BoardCardMoveToPosition, }, - mixins: [Tracking.mixin()], + mixins: [Tracking.mixin(), glFeatureFlagMixin()], inject: [ 'isEpicBoard', 'isGroupBoard', @@ -73,6 +81,8 @@ export default { showEpicForm: false, currentList: null, isLoadingMore: false, + toListId: null, + toList: {}, }; }, apollo: { @@ -111,6 +121,29 @@ export default { isSingleRequest: true, }, }, + toList: { + query() { + return listIssuablesQueries[this.issuableType].query; + }, + variables() { + return { + id: this.toListId, + ...this.listQueryVariables, + }; + }, + skip() { + return !this.toListId; + }, + update(data) { + return data[this.boardType].board.lists.nodes[0]; + }, + context: { + isSingleRequest: true, + }, + error() { + // handle error + }, + }, }, computed: { ...mapState(['pageInfoByListId', 'listsFlags', 'isUpdateIssueOrderInProgress']), @@ -205,6 +238,9 @@ export default { showMoveToPosition() { return !this.disabled && this.list.listType !== ListType.closed; }, + shouldCloneCard() { + return shouldCloneCard(this.list.listType, this.toList.listType); + }, }, watch: { boardListItems() { @@ -337,15 +373,123 @@ export default { } } - this.moveItem({ - itemId, - itemIid, - itemPath, - fromListId: from.dataset.listId, - toListId: to.dataset.listId, - moveBeforeId, - moveAfterId, + if (this.isApolloBoard) { + this.moveBoardItem( + { + epicId: itemId, + iid: itemIid, + fromListId: from.dataset.listId, + toListId: to.dataset.listId, + moveBeforeId, + moveAfterId, + }, + newIndex, + ); + } else { + this.moveItem({ + itemId, + itemIid, + itemPath, + fromListId: from.dataset.listId, + toListId: to.dataset.listId, + moveBeforeId, + moveAfterId, + }); + } + }, + isItemInTheList(itemIid) { + const items = this.toList?.[`${this.issuableType}s`]?.nodes || []; + return items.some((item) => item.iid === itemIid); + }, + async moveBoardItem(variables, newIndex) { + const { fromListId, toListId, iid } = variables; + this.toListId = toListId; + await this.$nextTick(); // we need this next tick to retrieve `toList` from Apollo cache + + const itemToMove = this.boardListItems.find((item) => item.iid === iid); + + if (this.shouldCloneCard && this.isItemInTheList(iid)) { + return; + } + + try { + await this.$apollo.mutate({ + mutation: listIssuablesQueries[this.issuableType].moveMutation, + variables: { + ...moveItemVariables({ + ...variables, + isIssue: !this.isEpicBoard, + boardId: this.boardId, + itemToMove, + }), + withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, + }, + update: (cache, { data: { issuableMoveList } }) => + this.updateCacheAfterMovingItem({ + issuableMoveList, + fromListId, + toListId, + newIndex, + cache, + }), + optimisticResponse: { + issuableMoveList: { + issuable: itemToMove, + errors: [], + }, + }, + }); + } catch { + // handle error + } + }, + updateCacheAfterMovingItem({ issuableMoveList, fromListId, toListId, newIndex, cache }) { + const { issuable } = issuableMoveList; + if (!this.shouldCloneCard) { + removeItemFromList({ + query: listIssuablesQueries[this.issuableType].query, + variables: { ...this.listQueryVariables, id: fromListId }, + boardType: this.boardType, + id: issuable.id, + issuableType: this.issuableType, + cache, + }); + } + + addItemToList({ + query: listIssuablesQueries[this.issuableType].query, + variables: { ...this.listQueryVariables, id: toListId }, + issuable, + newIndex, + boardType: this.boardType, + issuableType: this.issuableType, + cache, }); + + this.updateCountAndWeight({ fromListId, toListId, issuable, cache }); + }, + updateCountAndWeight({ fromListId, toListId, issuable, isAddingIssue, cache }) { + if (!this.isEpicBoard) { + updateIssueCountAndWeight({ + fromListId, + toListId, + filterParams: this.filterParams, + issuable, + shouldClone: isAddingIssue || this.shouldCloneCard, + cache, + }); + } else { + const { issuableType, filterParams } = this; + updateEpicsCount({ + issuableType, + toListId, + fromListId, + filterParams, + issuable, + shouldClone: this.shouldCloneCard, + cache, + }); + } }, }, }; diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js index ca188c741a9..d4d1bc7804e 100644 --- a/app/assets/javascripts/boards/constants.js +++ b/app/assets/javascripts/boards/constants.js @@ -10,9 +10,11 @@ import updateBoardListMutation from './graphql/board_list_update.mutation.graphq import toggleListCollapsedMutation from './graphql/client/board_toggle_collapsed.mutation.graphql'; import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutation.graphql'; import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql'; +import issueMoveListMutation from './graphql/issue_move_list.mutation.graphql'; import groupBoardQuery from './graphql/group_board.query.graphql'; import projectBoardQuery from './graphql/project_board.query.graphql'; import listIssuesQuery from './graphql/lists_issues.query.graphql'; +import listDeferredQuery from './graphql/board_lists_deferred.query.graphql'; export const BoardType = { project: 'project', @@ -72,6 +74,12 @@ export const listsQuery = { }, }; +export const listsDeferredQuery = { + [TYPE_ISSUE]: { + query: listDeferredQuery, + }, +}; + export const createListMutations = { [TYPE_ISSUE]: { mutation: createBoardListMutation, @@ -117,6 +125,7 @@ export const subscriptionQueries = { export const listIssuablesQueries = { [TYPE_ISSUE]: { query: listIssuesQuery, + moveMutation: issueMoveListMutation, }, }; diff --git a/app/assets/javascripts/boards/graphql/cache_updates.js b/app/assets/javascripts/boards/graphql/cache_updates.js new file mode 100644 index 00000000000..084809e4e60 --- /dev/null +++ b/app/assets/javascripts/boards/graphql/cache_updates.js @@ -0,0 +1,118 @@ +import produce from 'immer'; +import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; +import { listsDeferredQuery } from 'ee_else_ce/boards/constants'; + +export function removeItemFromList({ query, variables, boardType, id, issuableType, cache }) { + cache.updateQuery({ query, variables }, (sourceData) => + produce(sourceData, (draftData) => { + const { nodes: items } = draftData[boardType].board.lists.nodes[0][`${issuableType}s`]; + items.splice( + items.findIndex((item) => item.id === id), + 1, + ); + }), + ); +} + +export function addItemToList({ + query, + variables, + boardType, + issuable, + newIndex, + issuableType, + cache, +}) { + cache.updateQuery({ query, variables }, (sourceData) => + produce(sourceData, (draftData) => { + const { nodes: items } = draftData[boardType].board.lists.nodes[0][`${issuableType}s`]; + items.splice(newIndex, 0, issuable); + }), + ); +} + +export function updateIssueCountAndWeight({ + fromListId, + toListId, + filterParams, + issuable: issue, + shouldClone, + cache, +}) { + if (!shouldClone) { + cache.updateQuery( + { + query: listQuery, + variables: { id: fromListId, filters: filterParams }, + }, + ({ boardList }) => ({ + boardList: { + ...boardList, + issuesCount: boardList.issuesCount - 1, + totalWeight: boardList.totalWeight - issue.weight, + }, + }), + ); + } + + cache.updateQuery( + { + query: listQuery, + variables: { id: toListId, filters: filterParams }, + }, + ({ boardList }) => ({ + boardList: { + ...boardList, + issuesCount: boardList.issuesCount + 1, + totalWeight: boardList.totalWeight + issue.weight, + }, + }), + ); +} + +export function updateEpicsCount({ + issuableType, + filterParams, + fromListId, + toListId, + issuable: epic, + shouldClone, + cache, +}) { + const epicWeight = epic.descendantWeightSum.openedIssues + epic.descendantWeightSum.closedIssues; + if (!shouldClone) { + cache.updateQuery( + { + query: listsDeferredQuery[issuableType].query, + variables: { id: fromListId, filters: filterParams }, + }, + ({ epicBoardList }) => ({ + epicBoardList: { + ...epicBoardList, + metadata: { + epicsCount: epicBoardList.metadata.epicsCount - 1, + totalWeight: epicBoardList.metadata.totalWeight - epicWeight, + ...epicBoardList.metadata, + }, + }, + }), + ); + } + + cache.updateQuery( + { + query: listsDeferredQuery[issuableType].query, + variables: { id: toListId, filters: filterParams }, + }, + ({ epicBoardList }) => ({ + epicBoardList: { + ...epicBoardList, + metadata: { + epicsCount: epicBoardList.metadata.epicsCount + 1, + totalWeight: epicBoardList.metadata.totalWeight + epicWeight, + ...epicBoardList.metadata, + }, + }, + }), + ); +} diff --git a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql index 89670760450..4a46d741a78 100644 --- a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql +++ b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql @@ -9,7 +9,7 @@ mutation issueMoveList( $moveBeforeId: ID $moveAfterId: ID ) { - issueMoveList( + issuableMoveList: issueMoveList( input: { projectPath: $projectPath iid: $iid @@ -20,7 +20,7 @@ mutation issueMoveList( moveAfterId: $moveAfterId } ) { - issue { + issuable: issue { ...Issue } errors diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index a144054d680..d96d92948be 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -602,8 +602,8 @@ export default { cache, { data: { - issueMoveList: { - issue: { weight }, + issuableMoveList: { + issuable: { weight }, }, }, }, @@ -661,11 +661,11 @@ export default { }, }); - if (data?.issueMoveList?.errors.length || !data.issueMoveList) { + if (data?.issuableMoveList?.errors.length || !data.issuableMoveList) { throw new Error('issueMoveList empty'); } - commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issueMoveList.issue }); + commit(types.MUTATE_ISSUE_SUCCESS, { issue: data.issuableMoveList.issuable }); commit(types.MUTATE_ISSUE_IN_PROGRESS, false); } catch { commit(types.MUTATE_ISSUE_IN_PROGRESS, false); |