Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-08-18 13:50:51 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-08-18 13:50:51 +0300
commitdb384e6b19af03b4c3c82a5760d83a3fd79f7982 (patch)
tree34beaef37df5f47ccbcf5729d7583aae093cffa0 /app/assets/javascripts/boards
parent54fd7b1bad233e3944434da91d257fa7f63c3996 (diff)
Add latest changes from gitlab-org/gitlab@16-3-stable-eev16.3.0-rc42
Diffstat (limited to 'app/assets/javascripts/boards')
-rw-r--r--app/assets/javascripts/boards/components/board_add_new_column.vue73
-rw-r--r--app/assets/javascripts/boards/components/board_app.vue33
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue5
-rw-r--r--app/assets/javascripts/boards/components/board_card_inner.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_card_move_to_position.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue23
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue27
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue137
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue47
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue45
-rw-r--r--app/assets/javascripts/boards/components/board_top_bar.vue8
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue43
-rw-r--r--app/assets/javascripts/boards/components/config_toggle.vue3
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_time_tracker.vue39
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue1
-rw-r--r--app/assets/javascripts/boards/graphql/group_boards.query.graphql8
-rw-r--r--app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql8
-rw-r--r--app/assets/javascripts/boards/graphql/project_boards.query.graphql8
-rw-r--r--app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql8
-rw-r--r--app/assets/javascripts/boards/index.js2
-rw-r--r--app/assets/javascripts/boards/stores/index.js1
25 files changed, 331 insertions, 199 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') }} &#8226;
<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';
diff --git a/app/assets/javascripts/boards/graphql/group_boards.query.graphql b/app/assets/javascripts/boards/graphql/group_boards.query.graphql
index ce9f7bbfd2a..0fb5748abfc 100644
--- a/app/assets/javascripts/boards/graphql/group_boards.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_boards.query.graphql
@@ -2,11 +2,9 @@ query group_boards($fullPath: ID!) {
group(fullPath: $fullPath) {
id
boards {
- edges {
- node {
- id
- name
- }
+ nodes {
+ id
+ name
}
}
}
diff --git a/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql b/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql
index b9fe778d4d4..9dbf4528cec 100644
--- a/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_recent_boards.query.graphql
@@ -2,11 +2,9 @@ query group_recent_boards($fullPath: ID!) {
group(fullPath: $fullPath) {
id
recentIssueBoards {
- edges {
- node {
- id
- name
- }
+ nodes {
+ id
+ name
}
}
}
diff --git a/app/assets/javascripts/boards/graphql/project_boards.query.graphql b/app/assets/javascripts/boards/graphql/project_boards.query.graphql
index 770c246a95b..97a298db246 100644
--- a/app/assets/javascripts/boards/graphql/project_boards.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_boards.query.graphql
@@ -2,11 +2,9 @@ query project_boards($fullPath: ID!) {
project(fullPath: $fullPath) {
id
boards {
- edges {
- node {
- id
- name
- }
+ nodes {
+ id
+ name
}
}
}
diff --git a/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql b/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql
index c633107a409..0d3a8616603 100644
--- a/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_recent_boards.query.graphql
@@ -2,11 +2,9 @@ query project_recent_boards($fullPath: ID!) {
project(fullPath: $fullPath) {
id
recentIssueBoards {
- edges {
- node {
- id
- name
- }
+ nodes {
+ id
+ name
}
}
}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 67388284d31..a03ec9193ea 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -29,7 +29,7 @@ function mountBoardApp(el) {
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
const initialFilterParams = {
- ...convertObjectPropsToCamelCase(rawFilterParams),
+ ...convertObjectPropsToCamelCase(rawFilterParams, {}),
};
const boardType = el.dataset.parent;
diff --git a/app/assets/javascripts/boards/stores/index.js b/app/assets/javascripts/boards/stores/index.js
index 0a87c6ab821..ee0a5e27d9a 100644
--- a/app/assets/javascripts/boards/stores/index.js
+++ b/app/assets/javascripts/boards/stores/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import actions from 'ee_else_ce/boards/stores/actions';
import getters from 'ee_else_ce/boards/stores/getters';