diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-09 12:11:03 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-09 12:11:03 +0300 |
commit | 3d233a67cf4ab39773e3ee10a09df9eb7c3b4167 (patch) | |
tree | 66a33a656fb9d69c5e11e56cc5c7d20f2f5d064d /app/assets/javascripts/boards | |
parent | 130e0444c6e5498ea95d38dd96bf758a724e2345 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/boards')
8 files changed, 136 insertions, 264 deletions
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 47dffc985aa..91bee8f3474 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -8,6 +8,7 @@ import defaultSortableConfig from '~/sortable/sortable_config'; import Tracking from '~/tracking'; import { toggleFormEventPrefix, DraggableItemTypes } from '../constants'; import eventHub from '../eventhub'; +import listQuery from '../graphql/board_lists_deferred.query.graphql'; import BoardCard from './board_card.vue'; import BoardNewIssue from './board_new_issue.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..c9cf044aaa0 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -20,6 +20,7 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import AccessorUtilities from '../../lib/utils/accessor'; import { inactiveId, LIST, ListType, toggleFormEventPrefix } from '../constants'; import eventHub from '../eventhub'; +import listQuery from '../graphql/board_lists_deferred.query.graphql'; import ItemCount from './item_count.vue'; export default { @@ -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/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/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_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/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql index 4e47b1123ed..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 diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 901461ae603..1ebfcfc331b 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -30,6 +30,7 @@ import { } 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'; @@ -501,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 }, }, @@ -522,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) { @@ -565,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); @@ -581,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) { |