diff options
Diffstat (limited to 'app/assets/javascripts/boards/components')
19 files changed, 317 insertions, 178 deletions
diff --git a/app/assets/javascripts/boards/components/board_add_new_column.vue b/app/assets/javascripts/boards/components/board_add_new_column.vue index 985b9798b36..f103feecab2 100644 --- a/app/assets/javascripts/boards/components/board_add_new_column.vue +++ b/app/assets/javascripts/boards/components/board_add_new_column.vue @@ -7,12 +7,14 @@ import { GlCollapsibleListbox, GlIcon, } from '@gitlab/ui'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapGetters, mapState } from 'vuex'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; import BoardAddNewColumnForm from '~/boards/components/board_add_new_column_form.vue'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import { createListMutations, listsQuery, BoardType, ListType } from 'ee_else_ce/boards/constants'; import boardLabelsQuery from '../graphql/board_labels.query.graphql'; +import { setError } from '../graphql/cache_updates'; import { getListByTypeId } from '../boards_util'; export default { @@ -70,6 +72,12 @@ export default { skip() { return !this.isApolloBoard; }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while fetching labels. Please try again.'), + }); + }, }, }, computed: { @@ -102,36 +110,43 @@ export default { }, methods: { ...mapActions(['createList', 'fetchLabels', 'highlightList']), - createListApollo({ labelId }) { - return this.$apollo.mutate({ - mutation: createListMutations[this.issuableType].mutation, - variables: { - labelId, - boardId: this.boardId, - }, - update: ( - store, - { - data: { - boardListCreate: { list }, + async createListApollo({ labelId }) { + try { + await this.$apollo.mutate({ + mutation: createListMutations[this.issuableType].mutation, + variables: { + labelId, + boardId: this.boardId, + }, + update: ( + store, + { + data: { + boardListCreate: { list }, + }, }, + ) => { + const sourceData = store.readQuery({ + query: listsQuery[this.issuableType].query, + variables: this.listQueryVariables, + }); + const data = produce(sourceData, (draftData) => { + draftData[this.boardType].board.lists.nodes.push(list); + }); + store.writeQuery({ + query: listsQuery[this.issuableType].query, + variables: this.listQueryVariables, + data, + }); + this.$emit('highlight-list', list.id); }, - ) => { - const sourceData = store.readQuery({ - query: listsQuery[this.issuableType].query, - variables: this.listQueryVariables, - }); - const data = produce(sourceData, (draftData) => { - draftData[this.boardType].board.lists.nodes.push(list); - }); - store.writeQuery({ - query: listsQuery[this.issuableType].query, - variables: this.listQueryVariables, - data, - }); - this.$emit('highlight-list', list.id); - }, - }); + }); + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while creating the list. Please try again.'), + }); + } }, addList() { if (!this.selectedLabel) { diff --git a/app/assets/javascripts/boards/components/board_app.vue b/app/assets/javascripts/boards/components/board_app.vue index ca8299ddf80..1cfa35ffd91 100644 --- a/app/assets/javascripts/boards/components/board_app.vue +++ b/app/assets/javascripts/boards/components/board_app.vue @@ -1,4 +1,5 @@ <script> +// eslint-disable-next-line no-restricted-imports import { mapGetters } from 'vuex'; import { refreshCurrentPage, queryToObject } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; @@ -6,10 +7,11 @@ import BoardContent from '~/boards/components/board_content.vue'; import BoardSettingsSidebar from '~/boards/components/board_settings_sidebar.vue'; import BoardTopBar from '~/boards/components/board_top_bar.vue'; import eventHub from '~/boards/eventhub'; -import { listsQuery } from 'ee_else_ce/boards/constants'; -import { formatBoardLists } from 'ee_else_ce/boards/boards_util'; +import { listsQuery, FilterFields } from 'ee_else_ce/boards/constants'; +import { formatBoardLists, filterVariables, FiltersInfo } from 'ee_else_ce/boards/boards_util'; import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql'; import errorQuery from '../graphql/client/error.query.graphql'; +import { setError } from '../graphql/cache_updates'; export default { i18n: { @@ -34,12 +36,12 @@ export default { ], data() { return { + boardListsApollo: {}, activeListId: '', boardId: this.initialBoardId, filterParams: { ...this.initialFilterParams }, addColumnFormVisible: false, isShowingEpicsSwimlanes: Boolean(queryToObject(window.location.search).group_by), - apolloError: null, error: null, }; }, @@ -74,8 +76,11 @@ export default { const { lists } = data[this.boardType].board; return formatBoardLists(lists); }, - error() { - this.apolloError = this.$options.i18n.fetchError; + error(error) { + setError({ + error, + message: this.$options.i18n.fetchError, + }); }, }, error: { @@ -87,7 +92,6 @@ export default { computed: { ...mapGetters(['isSidebarOpen']), listQueryVariables() { - if (this.filterParams.groupBy) delete this.filterParams.groupBy; return { ...(this.isIssueBoard && { isGroup: this.isGroupBoard, @@ -95,7 +99,7 @@ export default { }), fullPath: this.fullPath, boardId: this.boardId, - filters: this.filterParams, + filters: this.formattedFilterParams, }; }, isSwimlanesOn() { @@ -110,6 +114,15 @@ export default { activeList() { return this.activeListId ? this.boardListsApollo[this.activeListId] : undefined; }, + formattedFilterParams() { + if (this.filterParams.groupBy) delete this.filterParams.groupBy; + return filterVariables({ + filters: this.filterParams, + issuableType: this.issuableType, + filterInfo: FiltersInfo, + filterFields: FilterFields, + }); + }, }, created() { window.addEventListener('popstate', refreshCurrentPage); @@ -132,7 +145,6 @@ export default { }, setFilters(filters) { const filterParams = { ...filters }; - if (filterParams.groupBy) delete filterParams.groupBy; this.filterParams = filterParams; }, }, @@ -151,13 +163,12 @@ export default { @toggleSwimlanes="isShowingEpicsSwimlanes = $event" /> <board-content - v-if="!isApolloBoard || boardListsApollo" :board-id="boardId" :add-column-form-visible="addColumnFormVisible" :is-swimlanes-on="isSwimlanesOn" - :filter-params="filterParams" + :filter-params="formattedFilterParams" :board-lists-apollo="boardListsApollo" - :apollo-error="apolloError || error" + :apollo-error="error" :list-query-variables="listQueryVariables" @setActiveList="setActiveId" @setAddColumnFormVisibility="addColumnFormVisible = $event" diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 18495f285da..05865dc7305 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -1,4 +1,5 @@ <script> +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import Tracking from '~/tracking'; import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; @@ -113,8 +114,8 @@ export default { this.$apollo.mutate({ mutation: setActiveBoardItemMutation, variables: { - boardItem: this.item, - isIssue: this.isIssueBoard, + boardItem: this.isActive ? null : this.item, + isIssue: this.isActive ? undefined : this.isIssueBoard, }, }); }, diff --git a/app/assets/javascripts/boards/components/board_card_inner.vue b/app/assets/javascripts/boards/components/board_card_inner.vue index 6036f0c359c..692ca6bf59b 100644 --- a/app/assets/javascripts/boards/components/board_card_inner.vue +++ b/app/assets/javascripts/boards/components/board_card_inner.vue @@ -8,6 +8,7 @@ import { GlSprintf, } from '@gitlab/ui'; import { sortBy } from 'lodash'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner'; import { isScopedLabel } from '~/lib/utils/common_utils'; @@ -306,7 +307,7 @@ export default { </span> <span class="board-info-items gl-mt-3 gl-display-inline-block"> <span v-if="shouldRenderEpicCountables" data-testid="epic-countables"> - <gl-tooltip :target="() => $refs.countBadge" data-testid="epic-countables-tooltip"> + <gl-tooltip :target="() => $refs.countBadge"> <p v-if="allowSubEpics" class="gl-font-weight-bold gl-m-0"> {{ __('Epics') }} • <span class="gl-font-weight-normal"> diff --git a/app/assets/javascripts/boards/components/board_card_move_to_position.vue b/app/assets/javascripts/boards/components/board_card_move_to_position.vue index 19eddbfdd68..8034819732a 100644 --- a/app/assets/javascripts/boards/components/board_card_move_to_position.vue +++ b/app/assets/javascripts/boards/components/board_card_move_to_position.vue @@ -1,5 +1,6 @@ <script> import { GlDisclosureDropdown } from '@gitlab/ui'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import Tracking from '~/tracking'; import { diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index 2ee0b4593d6..bcd7db8dcb4 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -1,4 +1,5 @@ <script> +// eslint-disable-next-line no-restricted-imports import { mapGetters, mapActions, mapState } from 'vuex'; import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import { isListDraggable } from '../boards_util'; diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 14c781f588f..3c2659b00c9 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -3,8 +3,10 @@ import { GlAlert } from '@gitlab/ui'; import { sortBy } from 'lodash'; import produce from 'immer'; import Draggable from 'vuedraggable'; +// eslint-disable-next-line no-restricted-imports import { mapState, mapActions } from 'vuex'; import BoardAddNewColumn from 'ee_else_ce/boards/components/board_add_new_column.vue'; +import { s__ } from '~/locale'; import { defaultSortableOptions } from '~/sortable/constants'; import { DraggableItemTypes, @@ -13,6 +15,7 @@ import { updateListQueries, } from 'ee_else_ce/boards/constants'; import { calculateNewPosition } from 'ee_else_ce/boards/boards_util'; +import { setError } from '../graphql/cache_updates'; import BoardColumn from './board_column.vue'; export default { @@ -122,7 +125,14 @@ export default { this.highlightedLists = this.highlightedLists.filter((id) => id !== listId); }, flashAnimationDuration); }, - updateListPosition({ + dismissError() { + if (this.isApolloBoard) { + setError({ message: null, captureError: false }); + } else { + this.unsetError(); + } + }, + async updateListPosition({ item: { dataset: { listId: movedListId, draggableItemType }, }, @@ -153,7 +163,7 @@ export default { const targetPosition = this.boardListsById[displacedListId].position; try { - this.$apollo.mutate({ + await this.$apollo.mutate({ mutation: updateListQueries[this.issuableType].mutation, variables: { listId: movedListId, @@ -195,8 +205,11 @@ export default { }, }, }); - } catch { - // handle error + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while moving the list. Please try again.'), + }); } }, }, @@ -209,7 +222,7 @@ export default { data-qa-selector="boards_list" class="gl-flex-grow-1 gl-display-flex gl-flex-direction-column gl-min-h-0" > - <gl-alert v-if="errorToDisplay" variant="danger" :dismissible="true" @dismiss="unsetError"> + <gl-alert v-if="errorToDisplay" variant="danger" :dismissible="true" @dismiss="dismissError"> {{ errorToDisplay }} </gl-alert> <component diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue index 1b97214ff8b..5e1e46dd198 100644 --- a/app/assets/javascripts/boards/components/board_content_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue @@ -1,12 +1,13 @@ <script> import { GlDrawer } from '@gitlab/ui'; import { MountingPortal } from 'portal-vue'; +// eslint-disable-next-line no-restricted-imports import { mapState, mapActions, mapGetters } from 'vuex'; import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdown_widget.vue'; import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql'; import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; -import { __, sprintf } from '~/locale'; -import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue'; +import { __, s__, sprintf } from '~/locale'; +import SidebarTimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue'; import { INCIDENT } from '~/boards/constants'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; @@ -18,6 +19,7 @@ import SidebarSeverityWidget from '~/sidebar/components/severity/sidebar_severit import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue'; import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue'; import SidebarLabelsWidget from '~/sidebar/components/labels/labels_select_widget/labels_select_root.vue'; +import { setError } from '../graphql/cache_updates'; export default { components: { @@ -26,7 +28,7 @@ export default { SidebarAssigneesWidget, SidebarDateWidget, SidebarConfidentialityWidget, - BoardSidebarTimeTracker, + SidebarTimeTracker, SidebarLabelsWidget, SidebarSubscriptionsWidget, SidebarDropdownWidget, @@ -74,6 +76,9 @@ export default { isApolloBoard: { default: false, }, + timeTrackingLimitToHours: { + default: false, + }, }, inheritAttrs: false, apollo: { @@ -94,6 +99,12 @@ export default { skip() { return !this.isApolloBoard; }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while selecting the card. Please try again.'), + }); + }, }, }, computed: { @@ -250,7 +261,15 @@ export default { data-testid="iteration-edit" /> </div> - <board-sidebar-time-tracker /> + <sidebar-time-tracker + :can-add-time-entries="canUpdate" + :can-set-time-estimate="canUpdate" + :full-path="projectPathForActiveIssue" + :issuable-id="activeBoardIssuable.id" + :issuable-iid="activeBoardIssuable.iid" + :limit-to-hours="timeTrackingLimitToHours" + :show-collapsed="false" + /> <sidebar-date-widget :iid="activeBoardIssuable.iid" :full-path="projectPathForActiveIssue" diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue index b5d3613ca27..91dd5c81f77 100644 --- a/app/assets/javascripts/boards/components/board_filtered_search.vue +++ b/app/assets/javascripts/boards/components/board_filtered_search.vue @@ -1,5 +1,6 @@ <script> import { pickBy, isEmpty, mapValues } from 'lodash'; +// eslint-disable-next-line no-restricted-imports import { mapActions } from 'vuex'; import { getIdFromGraphQLId, isGid, convertToGraphQLId } from '~/graphql_shared/utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -342,7 +343,8 @@ export default { ); }, formattedFilterParams() { - const filtersCopy = { ...this.filterParams }; + const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true }); + const filtersCopy = convertObjectPropsToCamelCase(rawFilterParams, {}); if (this.filterParams?.iterationId) { filtersCopy.iterationId = convertToGraphQLId( TYPENAME_ITERATION, diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index 9ea801dc9a2..4986c3780e5 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -1,5 +1,6 @@ <script> import { GlModal, GlAlert } from '@gitlab/ui'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { visitUrl, updateHistory, getParameterByName } from '~/lib/utils/url_utility'; diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index b4249c63b4d..67bfcfb9d97 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -1,13 +1,15 @@ <script> import { GlLoadingIcon, GlIntersectionObserver } from '@gitlab/ui'; import Draggable from 'vuedraggable'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import { STATUS_CLOSED } from '~/issues/constants'; -import { sprintf, __ } from '~/locale'; +import { sprintf, __, s__ } from '~/locale'; import { defaultSortableOptions } from '~/sortable/constants'; import { sortableStart, sortableEnd } from '~/sortable/utils'; import Tracking from '~/tracking'; import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql'; +import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; import BoardCardMoveToPosition from '~/boards/components/board_card_move_to_position.vue'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { @@ -49,6 +51,7 @@ export default { mixins: [Tracking.mixin(), glFeatureFlagMixin()], inject: [ 'isEpicBoard', + 'isIssueBoard', 'isGroupBoard', 'disabled', 'fullPath', @@ -122,6 +125,12 @@ export default { context: { isSingleRequest: true, }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while fetching a list. Please try again.'), + }); + }, }, toList: { query() { @@ -142,8 +151,16 @@ export default { context: { isSingleRequest: true, }, - error() { - // handle error + error(error) { + setError({ + error, + message: sprintf( + s__('Boards|An error occurred while moving the %{issuableType}. Please try again.'), + { + issuableType: this.isEpicBoard ? 'epic' : 'issue', + }, + ), + }); }, }, }, @@ -442,8 +459,16 @@ export default { }, }, }); - } catch { - // handle error + } catch (error) { + setError({ + error, + message: sprintf( + s__('Boards|An error occurred while moving the %{issuableType}. Please try again.'), + { + issuableType: this.isEpicBoard ? 'epic' : 'issue', + }, + ), + }); } }, updateCacheAfterMovingItem({ issuableMoveList, fromListId, toListId, newIndex, cache }) { @@ -494,56 +519,69 @@ export default { }); } }, - moveToPosition(positionInList, oldIndex, item) { - this.$apollo.mutate({ - mutation: listIssuablesQueries[this.issuableType].moveMutation, - variables: { - ...moveItemVariables({ - iid: item.iid, - epicId: item.id, - fromListId: this.currentList.id, - toListId: this.currentList.id, - isIssue: !this.isEpicBoard, - boardId: this.boardId, - itemToMove: item, - }), - positionInList, - withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, - }, - optimisticResponse: { - issuableMoveList: { - issuable: item, - errors: [], + async moveToPosition(positionInList, oldIndex, item) { + try { + await this.$apollo.mutate({ + mutation: listIssuablesQueries[this.issuableType].moveMutation, + variables: { + ...moveItemVariables({ + iid: item.iid, + epicId: item.id, + fromListId: this.currentList.id, + toListId: this.currentList.id, + isIssue: !this.isEpicBoard, + boardId: this.boardId, + itemToMove: item, + }), + positionInList, + withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, }, - }, - update: (cache, { data: { issuableMoveList } }) => { - const { issuable } = issuableMoveList; - removeItemFromList({ - query: listIssuablesQueries[this.issuableType].query, - variables: { ...this.listQueryVariables, id: this.currentList.id }, - boardType: this.boardType, - id: issuable.id, - issuableType: this.issuableType, - cache, - }); - if (positionInList === 0 || this.listItemsCount <= this.boardListItems.length) { - const newIndex = positionInList === 0 ? 0 : this.boardListItems.length - 1; - addItemToList({ + optimisticResponse: { + issuableMoveList: { + issuable: item, + errors: [], + }, + }, + update: (cache, { data: { issuableMoveList } }) => { + const { issuable } = issuableMoveList; + removeItemFromList({ query: listIssuablesQueries[this.issuableType].query, variables: { ...this.listQueryVariables, id: this.currentList.id }, - issuable, - newIndex, boardType: this.boardType, + id: issuable.id, issuableType: this.issuableType, cache, }); - } - }, - }); + if (positionInList === 0 || this.listItemsCount <= this.boardListItems.length) { + const newIndex = positionInList === 0 ? 0 : this.boardListItems.length - 1; + addItemToList({ + query: listIssuablesQueries[this.issuableType].query, + variables: { ...this.listQueryVariables, id: this.currentList.id }, + issuable, + newIndex, + boardType: this.boardType, + issuableType: this.issuableType, + cache, + }); + } + }, + }); + } catch (error) { + setError({ + error, + message: sprintf( + s__('Boards|An error occurred while moving the %{issuableType}. Please try again.'), + { + issuableType: this.isEpicBoard ? 'epic' : 'issue', + }, + ), + }); + } }, async addListItem(input) { this.toggleForm(); this.addItemToListInProgress = true; + let issuable; try { await this.$apollo.mutate({ mutation: listIssuablesQueries[this.issuableType].createMutation, @@ -552,7 +590,7 @@ export default { withColor: this.isEpicBoard && this.glFeatures.epicColorHighlight, }, update: (cache, { data: { createIssuable } }) => { - const { issuable } = createIssuable; + issuable = createIssuable.issuable; addItemToList({ query: listIssuablesQueries[this.issuableType].query, variables: { ...this.listQueryVariables, id: this.currentList.id }, @@ -583,7 +621,7 @@ export default { } catch (error) { setError({ message: sprintf( - __('An error occurred while creating the %{issuableType}. Please try again.'), + s__('Boards|An error occurred while creating the %{issuableType}. Please try again.'), { issuableType: this.isEpicBoard ? 'epic' : 'issue', }, @@ -592,6 +630,13 @@ export default { }); } finally { this.addItemToListInProgress = false; + this.$apollo.mutate({ + mutation: setActiveBoardItemMutation, + variables: { + boardItem: issuable, + isIssue: this.isIssueBoard, + }, + }); } }, }, diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index 8db86d0e894..068db98a750 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -8,9 +8,11 @@ import { GlSprintf, GlTooltipDirective, } from '@gitlab/ui'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import { isListDraggable } from '~/boards/boards_util'; import { isScopedLabel, parseBoolean } from '~/lib/utils/common_utils'; +import { fetchPolicies } from '~/lib/graphql'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { n__, s__ } from '~/locale'; import sidebarEventHub from '~/sidebar/event_hub'; @@ -18,7 +20,6 @@ import Tracking from '~/tracking'; import { TYPE_ISSUE } from '~/issues/constants'; 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 setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql'; import AccessorUtilities from '~/lib/utils/accessor'; import { @@ -28,8 +29,10 @@ import { toggleFormEventPrefix, updateListQueries, toggleCollapsedMutations, + listsDeferredQuery, } from 'ee_else_ce/boards/constants'; import eventHub from '../eventhub'; +import { setError } from '../graphql/cache_updates'; import ItemCount from './item_count.vue'; export default { @@ -39,6 +42,9 @@ export default { listSettings: s__('Boards|Edit list settings'), expand: s__('Boards|Expand'), collapse: s__('Boards|Collapse'), + fetchError: s__( + "Boards|An error occurred while fetching list's information. Please try again.", + ), }, components: { GlButton, @@ -184,8 +190,16 @@ export default { userCanDrag() { return !this.disabled && isListDraggable(this.list); }, + // due to the issues with cache-and-network, we need this hack to check if there is any data for the query in the cache. + // if we have cached data, we disregard the loading state isLoading() { - return this.$apollo.queries.boardList.loading; + return ( + this.$apollo.queries.boardList.loading && + !this.$apollo.provider.clients.defaultClient.readQuery({ + query: listsDeferredQuery[this.issuableType].query, + variables: this.countQueryVariables, + }) + ); }, totalWeight() { return this.boardList?.totalWeight; @@ -193,19 +207,31 @@ export default { canShowTotalWeight() { return this.weightFeatureAvailable && !this.isLoading; }, + countQueryVariables() { + return { + id: this.list.id, + filters: this.filterParams, + }; + }, }, apollo: { boardList: { - query: listQuery, + fetchPolicy: fetchPolicies.CACHE_AND_NETWORK, + query() { + return listsDeferredQuery[this.issuableType].query; + }, variables() { - return { - id: this.list.id, - filters: this.filterParams, - }; + return this.countQueryVariables; }, context: { isSingleRequest: true, }, + error(error) { + setError({ + error, + message: this.$options.i18n.fetchError, + }); + }, }, }, created() { @@ -293,8 +319,11 @@ export default { }, }, }); - } catch { - this.$emit('error'); + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while updating the list. Please try again.'), + }); } } else { this.updateList({ listId: this.list.id, collapsed }); diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index b68444fb011..d78b60e91a8 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -1,4 +1,5 @@ <script> +// eslint-disable-next-line no-restricted-imports import { mapActions, mapGetters } from 'vuex'; import { s__ } from '~/locale'; import { getMilestone, formatIssueInput, getBoardQuery } from 'ee_else_ce/boards/boards_util'; diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue index 0f43aae3936..58db2c9ac2a 100644 --- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -2,6 +2,7 @@ import produce from 'immer'; import { GlButton, GlDrawer, GlLabel, GlModal, GlModalDirective } from '@gitlab/ui'; import { MountingPortal } from 'portal-vue'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState, mapGetters } from 'vuex'; import { LIST, @@ -11,10 +12,11 @@ import { deleteListQueries, } from 'ee_else_ce/boards/constants'; import { isScopedLabel } from '~/lib/utils/common_utils'; -import { __ } from '~/locale'; +import { __, s__ } from '~/locale'; import eventHub from '~/sidebar/event_hub'; import Tracking from '~/tracking'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { setError } from '../graphql/cache_updates'; export default { listSettingsText: __('List settings'), @@ -131,23 +133,30 @@ export default { } }, async deleteList(listId) { - await this.$apollo.mutate({ - mutation: deleteListQueries[this.issuableType].mutation, - variables: { - listId, - }, - update: (store) => { - store.updateQuery( - { query: listsQuery[this.issuableType].query, variables: this.queryVariables }, - (sourceData) => - produce(sourceData, (draftData) => { - draftData[this.boardType].board.lists.nodes = draftData[ - this.boardType - ].board.lists.nodes.filter((list) => list.id !== listId); - }), - ); - }, - }); + try { + await this.$apollo.mutate({ + mutation: deleteListQueries[this.issuableType].mutation, + variables: { + listId, + }, + update: (store) => { + store.updateQuery( + { query: listsQuery[this.issuableType].query, variables: this.queryVariables }, + (sourceData) => + produce(sourceData, (draftData) => { + draftData[this.boardType].board.lists.nodes = draftData[ + this.boardType + ].board.lists.nodes.filter((list) => list.id !== listId); + }), + ); + }, + }); + } catch (error) { + setError({ + error, + message: s__('Boards|An error occurred while deleting the list. Please try again.'), + }); + } }, }, }; diff --git a/app/assets/javascripts/boards/components/board_top_bar.vue b/app/assets/javascripts/boards/components/board_top_bar.vue index fd9043a561f..2b8418333a8 100644 --- a/app/assets/javascripts/boards/components/board_top_bar.vue +++ b/app/assets/javascripts/boards/components/board_top_bar.vue @@ -1,8 +1,10 @@ <script> import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue'; +import { s__ } from '~/locale'; import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue'; import IssueBoardFilteredSearch from 'ee_else_ce/boards/components/issue_board_filtered_search.vue'; import { getBoardQuery } from 'ee_else_ce/boards/boards_util'; +import { setError } from '../graphql/cache_updates'; import ConfigToggle from './config_toggle.vue'; import NewBoardButton from './new_board_button.vue'; import ToggleFocus from './toggle_focus.vue'; @@ -70,6 +72,12 @@ export default { labels: board.labels?.nodes, }; }, + error(error) { + setError({ + error, + message: s__('Boards|An error occurred while fetching board details. Please try again.'), + }); + }, }, }, computed: { diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index fddb58c45fe..b3fe52944dc 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -10,6 +10,7 @@ import { } from '@gitlab/ui'; import { produce } from 'immer'; import { throttle } from 'lodash'; +// eslint-disable-next-line no-restricted-imports import { mapActions, mapState } from 'vuex'; import BoardForm from 'ee_else_ce/boards/components/board_form.vue'; @@ -24,12 +25,16 @@ import groupBoardsQuery from '../graphql/group_boards.query.graphql'; import projectBoardsQuery from '../graphql/project_boards.query.graphql'; import groupRecentBoardsQuery from '../graphql/group_recent_boards.query.graphql'; import projectRecentBoardsQuery from '../graphql/project_recent_boards.query.graphql'; +import { setError } from '../graphql/cache_updates'; import { fullBoardId } from '../boards_util'; const MIN_BOARDS_TO_VIEW_RECENT = 10; export default { name: 'BoardsSelector', + i18n: { + fetchBoardsError: s__('Boards|An error occurred while fetching boards. Please try again.'), + }, components: { BoardForm, GlLoadingIcon, @@ -90,9 +95,12 @@ export default { parentType() { return this.boardType; }, - boardQuery() { + issueBoardsQuery() { return this.isGroupBoard ? groupBoardsQuery : projectBoardsQuery; }, + boardsQuery() { + return this.issueBoardsQuery; + }, loading() { return this.loadingRecentBoards || this.loadingBoards; }, @@ -143,7 +151,7 @@ export default { eventHub.$off('showBoardModal', this.showPage); }, methods: { - ...mapActions(['setError', 'fetchBoard', 'unsetActiveId']), + ...mapActions(['fetchBoard', 'unsetActiveId']), fullBoardId(boardId) { return fullBoardId(boardId); }, @@ -157,7 +165,7 @@ export default { if (!data?.[this.parentType]) { return []; } - return data[this.parentType][boardType].edges.map(({ node }) => ({ + return data[this.parentType][boardType].nodes.map((node) => ({ id: getIdFromGraphQLId(node.id), name: node.name, })); @@ -174,11 +182,17 @@ export default { variables() { return { fullPath: this.fullPath }; }, - query: this.boardQuery, + query: this.boardsQuery, update: (data) => this.boardUpdate(data, 'boards'), watchLoading: (isLoading) => { this.loadingBoards = isLoading; }, + error(error) { + setError({ + error, + message: this.$options.i18n.fetchBoardsError, + }); + }, }); this.loadRecentBoards(); @@ -193,25 +207,33 @@ export default { watchLoading: (isLoading) => { this.loadingRecentBoards = isLoading; }, + error(error) { + setError({ + error, + message: s__( + 'Boards|An error occurred while fetching recent boards. Please try again.', + ), + }); + }, }); }, addBoard(board) { const { defaultClient: store } = this.$apollo.provider.clients; const sourceData = store.readQuery({ - query: this.boardQuery, + query: this.boardsQuery, variables: { fullPath: this.fullPath }, }); const newData = produce(sourceData, (draftState) => { - draftState[this.parentType].boards.edges = [ - ...draftState[this.parentType].boards.edges, - { node: board }, + draftState[this.parentType].boards.nodes = [ + ...draftState[this.parentType].boards.nodes, + { ...board }, ]; }); store.writeQuery({ - query: this.boardQuery, + query: this.boardsQuery, variables: { fullPath: this.fullPath }, data: newData, }); @@ -267,9 +289,6 @@ export default { } }, }, - i18n: { - errorFetchingBoard: s__('Board|An error occurred while fetching the board, please try again.'), - }, }; </script> diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue index dd3b9472879..bc896932ffc 100644 --- a/app/assets/javascripts/boards/components/config_toggle.vue +++ b/app/assets/javascripts/boards/components/config_toggle.vue @@ -1,5 +1,6 @@ <script> import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui'; +// eslint-disable-next-line no-restricted-imports import { mapGetters } from 'vuex'; import { formType } from '~/boards/constants'; import eventHub from '~/boards/eventhub'; @@ -29,7 +30,7 @@ export default { return this.canAdminList ? s__('Boards|Edit board') : s__('Boards|View scope'); }, tooltipTitle() { - return this.hasScope ? __("This board's scope is reduced") : ''; + return this.hasScope || this.boardHasScope ? __("This board's scope is reduced") : ''; }, }, methods: { diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue deleted file mode 100644 index b70294c9db3..00000000000 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue +++ /dev/null @@ -1,39 +0,0 @@ -<script> -import { mapGetters } from 'vuex'; -import IssuableTimeTracker from '~/sidebar/components/time_tracking/time_tracker.vue'; - -export default { - components: { - IssuableTimeTracker, - }, - inject: ['timeTrackingLimitToHours', 'canUpdate'], - computed: { - ...mapGetters(['activeBoardItem']), - initialTimeTracking() { - const { - timeEstimate, - totalTimeSpent, - humanTimeEstimate, - humanTotalTimeSpent, - } = this.activeBoardItem; - return { - timeEstimate, - totalTimeSpent, - humanTimeEstimate, - humanTotalTimeSpent, - }; - }, - }, -}; -</script> - -<template> - <issuable-time-tracker - :issuable-id="activeBoardItem.id.toString()" - :issuable-iid="activeBoardItem.iid.toString()" - :limit-to-hours="timeTrackingLimitToHours" - :initial-time-tracking="initialTimeTracking" - :show-collapsed="false" - :can-add-time-entries="canUpdate" - /> -</template> diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue index 020edcb01b8..1c2c0022ddf 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue @@ -1,5 +1,6 @@ <script> import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormInput, GlLink } from '@gitlab/ui'; +// eslint-disable-next-line no-restricted-imports import { mapGetters, mapActions } from 'vuex'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; import { joinPaths } from '~/lib/utils/url_utility'; |