From 48aff82709769b098321c738f3444b9bdaa694c6 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 21 Oct 2020 07:08:36 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-5-stable-ee --- app/assets/javascripts/boards/boards_util.js | 41 ++++- .../boards/components/board_blank_state.vue | 104 ------------- .../javascripts/boards/components/board_column.vue | 18 +-- .../components/board_configuration_options.vue | 65 ++++++++ .../boards/components/board_content.vue | 7 +- .../javascripts/boards/components/board_delete.js | 30 ---- .../boards/components/board_extra_actions.vue | 57 +++++++ .../javascripts/boards/components/board_form.vue | 25 +++- .../javascripts/boards/components/board_list.vue | 14 +- .../boards/components/board_list_header.vue | 49 ++---- .../boards/components/board_list_new.vue | 166 +++++++++++++++++++++ .../boards/components/board_new_issue.vue | 6 +- .../boards/components/board_settings_sidebar.vue | 33 +++- .../boards/components/issue_card_inner.vue | 9 +- .../javascripts/boards/components/modal/tabs.vue | 34 +++-- .../boards/components/new_list_dropdown.js | 32 ++-- .../boards/components/project_select.vue | 6 +- .../components/sidebar/board_editable_item.vue | 8 +- .../sidebar/board_sidebar_labels_select.vue | 120 +++++++++++++++ app/assets/javascripts/boards/ee_functions.js | 2 +- .../javascripts/boards/filtered_search_boards.js | 3 +- .../boards/icons/fullscreen_collapse.svg | 1 - .../javascripts/boards/icons/fullscreen_expand.svg | 1 - app/assets/javascripts/boards/index.js | 108 ++++++-------- .../javascripts/boards/mixins/is_wip_limits.js | 7 - app/assets/javascripts/boards/models/list.js | 3 +- .../boards/queries/board.mutation.graphql | 11 ++ .../queries/board_list_create.mutation.graphql | 20 ++- .../boards/queries/board_lists.query.graphql | 28 ++++ .../boards/queries/group_board.query.graphql | 13 -- .../queries/issue_set_labels.mutation.graphql | 15 ++ .../boards/queries/lists_issues.query.graphql | 28 +++- .../boards/queries/project_board.query.graphql | 13 -- app/assets/javascripts/boards/stores/actions.js | 159 ++++++++++---------- .../javascripts/boards/stores/boards_store.js | 63 +++++--- app/assets/javascripts/boards/stores/getters.js | 15 +- .../javascripts/boards/stores/mutation_types.js | 6 +- app/assets/javascripts/boards/stores/mutations.js | 89 ++++++----- app/assets/javascripts/boards/stores/state.js | 5 +- app/assets/javascripts/boards/toggle_focus.js | 13 +- 40 files changed, 926 insertions(+), 501 deletions(-) delete mode 100644 app/assets/javascripts/boards/components/board_blank_state.vue create mode 100644 app/assets/javascripts/boards/components/board_configuration_options.vue delete mode 100644 app/assets/javascripts/boards/components/board_delete.js create mode 100644 app/assets/javascripts/boards/components/board_extra_actions.vue create mode 100644 app/assets/javascripts/boards/components/board_list_new.vue create mode 100644 app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue delete mode 100644 app/assets/javascripts/boards/icons/fullscreen_collapse.svg delete mode 100644 app/assets/javascripts/boards/icons/fullscreen_expand.svg delete mode 100644 app/assets/javascripts/boards/mixins/is_wip_limits.js create mode 100644 app/assets/javascripts/boards/queries/board.mutation.graphql create mode 100644 app/assets/javascripts/boards/queries/board_lists.query.graphql delete mode 100644 app/assets/javascripts/boards/queries/group_board.query.graphql create mode 100644 app/assets/javascripts/boards/queries/issue_set_labels.mutation.graphql delete mode 100644 app/assets/javascripts/boards/queries/project_board.query.graphql (limited to 'app/assets/javascripts/boards') diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 5c8df94ca90..6b7b0c2e28d 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -2,11 +2,24 @@ import { sortBy } from 'lodash'; import ListIssue from 'ee_else_ce/boards/models/issue'; import { ListType } from './constants'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import boardsStore from '~/boards/stores/boards_store'; export function getMilestone() { return null; } +export function formatBoardLists(lists) { + const formattedLists = lists.nodes.map(list => + boardsStore.updateListPosition({ ...list, doNotFetchIssues: true }), + ); + return formattedLists.reduce((map, list) => { + return { + ...map, + [list.id]: list, + }; + }, {}); +} + export function formatIssue(issue) { return new ListIssue({ ...issue, @@ -17,9 +30,15 @@ export function formatIssue(issue) { export function formatListIssues(listIssues) { const issues = {}; + let listIssuesCount; const listData = listIssues.nodes.reduce((map, list) => { - const sortedIssues = sortBy(list.issues.nodes, 'relativePosition'); + listIssuesCount = list.issues.count; + let sortedIssues = list.issues.edges.map(issueNode => ({ + ...issueNode.node, + })); + sortedIssues = sortBy(sortedIssues, 'relativePosition'); + return { ...map, [list.id]: sortedIssues.map(i => { @@ -39,13 +58,30 @@ export function formatListIssues(listIssues) { }; }, {}); - return { listData, issues }; + return { listData, issues, listIssuesCount }; +} + +export function formatListsPageInfo(lists) { + const listData = lists.nodes.reduce((map, list) => { + return { + ...map, + [list.id]: list.issues.pageInfo, + }; + }, {}); + return listData; } export function fullBoardId(boardId) { return `gid://gitlab/Board/${boardId}`; } +export function fullLabelId(label) { + if (label.project_id !== null) { + return `gid://gitlab/ProjectLabel/${label.id}`; + } + return `gid://gitlab/GroupLabel/${label.id}`; +} + export function moveIssueListHelper(issue, fromList, toList) { if (toList.type === ListType.label) { issue.addLabel(toList.label); @@ -69,4 +105,5 @@ export default { formatIssue, formatListIssues, fullBoardId, + fullLabelId, }; diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue deleted file mode 100644 index 55e3e4a6329..00000000000 --- a/app/assets/javascripts/boards/components/board_blank_state.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index 6d216911798..9295065b7b7 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -1,13 +1,12 @@ + + diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index c7b3da0e672..2515f471379 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -1,5 +1,6 @@ + + diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index 385dd5fdc71..793c594cf16 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -5,6 +5,8 @@ import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { visitUrl } from '~/lib/utils/url_utility'; import boardsStore from '~/boards/stores/boards_store'; +import BoardConfigurationOptions from './board_configuration_options.vue'; + const boardDefaults = { id: false, name: '', @@ -13,12 +15,15 @@ const boardDefaults = { assignee: {}, assignee_id: undefined, weight: null, + hide_backlog_list: false, + hide_closed_list: false, }; export default { components: { BoardScope: () => import('ee_component/boards/components/board_scope.vue'), DeprecatedModal, + BoardConfigurationOptions, }, props: { canAdminBoard: { @@ -140,7 +145,17 @@ export default { } else { boardsStore .createBoard(this.board) - .then(resp => resp.data) + .then(resp => { + // This handles 2 use cases + // - In create call we only get one parameter, the new board + // - In update call, due to Promise.all, we get REST response in + // array index 0 + + if (Array.isArray(resp)) { + return resp[0].data; + } + return resp.data ? resp.data : resp; + }) .then(data => { visitUrl(data.board_path); }) @@ -182,7 +197,7 @@ export default {
+ + 0 && this.list.issuesSize > this.list.maxIssueCount; }, + loading() { + return this.list.loading; + }, }, watch: { filters: { @@ -72,7 +73,6 @@ export default { deep: true, }, issues() { - if (this.glFeatures.graphqlBoardLists) return; this.$nextTick(() => { if ( this.scrollHeight() <= this.listHeight() && @@ -98,6 +98,8 @@ export default { eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop); }, mounted() { + // TODO: Use Draggable in ./board_list_new.vue to drag & drop issue + // https://gitlab.com/gitlab-org/gitlab/-/issues/218164 const multiSelectOpts = {}; if (gon.features && gon.features.multiSelectBoard) { multiSelectOpts.multiDrag = true; @@ -403,8 +405,6 @@ export default { this.showIssueForm = !this.showIssueForm; }, onScroll() { - if (this.glFeatures.graphqlBoardLists) return; - if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) { this.loadNextPage(); } diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index 361fe252afb..bb9a1b79d91 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -1,5 +1,5 @@ + + diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 348d485ff37..0a665b82880 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -22,11 +22,7 @@ export default { required: true, }, }, - inject: { - groupId: { - type: Number, - }, - }, + inject: ['groupId'], data() { return { title: '', diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue index e2600883e89..392e056dcbf 100644 --- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -1,5 +1,5 @@ @@ -91,6 +106,16 @@ export default { :board-list-type="boardListType" /> +
+ {{ __('Remove list') }} + +
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue index 8658f51e5cf..a181ea51c4a 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.vue +++ b/app/assets/javascripts/boards/components/issue_card_inner.vue @@ -41,14 +41,7 @@ export default { default: false, }, }, - inject: { - groupId: { - type: Number, - }, - rootPath: { - type: String, - }, - }, + inject: ['groupId', 'rootPath'], data() { return { limitBeforeCounter: 2, diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue index a71fda9d7c5..b066fb25360 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.vue +++ b/app/assets/javascripts/boards/components/modal/tabs.vue @@ -1,9 +1,15 @@ diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index 2e356f1353a..c8926c5ef2a 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -6,8 +6,14 @@ import axios from '~/lib/utils/axios_utils'; import { deprecatedCreateFlash as flash } from '~/flash'; import CreateLabelDropdown from '../../create_label'; import boardsStore from '../stores/boards_store'; +import { fullLabelId } from '../boards_util'; +import store from '~/boards/stores'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; +function shouldCreateListGraphQL(label) { + return store.getters.shouldUseGraphQL && !store.getters.getListByLabelId(fullLabelId(label)); +} + $(document) .off('created.label') .on('created.label', (e, label, addNewList) => { @@ -15,16 +21,20 @@ $(document) return; } - boardsStore.new({ - title: label.title, - position: boardsStore.state.lists.length - 2, - list_type: 'label', - label: { - id: label.id, + if (shouldCreateListGraphQL(label)) { + store.dispatch('createList', { labelId: fullLabelId(label) }); + } else { + boardsStore.new({ title: label.title, - color: label.color, - }, - }); + position: boardsStore.state.lists.length - 2, + list_type: 'label', + label: { + id: label.id, + title: label.title, + color: label.color, + }, + }); + } }); export default function initNewListDropdown() { @@ -74,7 +84,9 @@ export default function initNewListDropdown() { const label = options.selectedObj; e.preventDefault(); - if (!boardsStore.findListByLabelId(label.id)) { + if (shouldCreateListGraphQL(label)) { + store.dispatch('createList', { labelId: fullLabelId(label) }); + } else if (!boardsStore.findListByLabelId(label.id)) { boardsStore.new({ title: label.title, position: boardsStore.state.lists.length - 2, diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 59e7620962a..566c0081b9d 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -20,11 +20,7 @@ export default { required: true, }, }, - inject: { - groupId: { - type: Number, - }, - }, + inject: ['groupId'], data() { return { loading: true, diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue index 8df03ea581f..5fb7a9b210c 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue @@ -36,16 +36,18 @@ export default { } this.edit = true; - this.$emit('changed', this.edit); + this.$emit('open'); window.addEventListener('click', this.collapseWhenOffClick); }, - collapse() { + collapse({ emitEvent = true } = {}) { if (!this.edit) { return; } this.edit = false; - this.$emit('changed', this.edit); + if (emitEvent) { + this.$emit('close'); + } window.removeEventListener('click', this.collapseWhenOffClick); }, }, 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 new file mode 100644 index 00000000000..0f063c7582e --- /dev/null +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue @@ -0,0 +1,120 @@ + + + diff --git a/app/assets/javascripts/boards/ee_functions.js b/app/assets/javascripts/boards/ee_functions.js index 583270fcae5..419a640d5c5 100644 --- a/app/assets/javascripts/boards/ee_functions.js +++ b/app/assets/javascripts/boards/ee_functions.js @@ -1,6 +1,6 @@ export const setPromotionState = () => {}; -export const setWeigthFetchingState = () => {}; +export const setWeightFetchingState = () => {}; export const setEpicFetchingState = () => {}; export const getMilestoneTitle = () => ({}); diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index fff89832bf0..4fa78ecd5a4 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -25,7 +25,8 @@ export default class FilteredSearchBoards extends FilteredSearchManager { } updateObject(path) { - this.store.path = path.substr(1); + const groupByParam = new URLSearchParams(window.location.search).get('group_by'); + this.store.path = `${path.substr(1)}${groupByParam ? `&group_by=${groupByParam}` : ''}`; if (gon.features.boardsWithSwimlanes || gon.features.graphqlBoardLists) { boardsStore.updateFiltersUrl(); diff --git a/app/assets/javascripts/boards/icons/fullscreen_collapse.svg b/app/assets/javascripts/boards/icons/fullscreen_collapse.svg deleted file mode 100644 index 6bd773dc4c5..00000000000 --- a/app/assets/javascripts/boards/icons/fullscreen_collapse.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/assets/javascripts/boards/icons/fullscreen_expand.svg b/app/assets/javascripts/boards/icons/fullscreen_expand.svg deleted file mode 100644 index 306073b8af2..00000000000 --- a/app/assets/javascripts/boards/icons/fullscreen_expand.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 1173c6d0578..887abe79059 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -1,4 +1,3 @@ -import $ from 'jquery'; import Vue from 'vue'; import { mapActions, mapState } from 'vuex'; @@ -11,7 +10,7 @@ import toggleLabels from 'ee_else_ce/boards/toggle_labels'; import toggleEpicsSwimlanes from 'ee_else_ce/boards/toggle_epics_swimlanes'; import { setPromotionState, - setWeigthFetchingState, + setWeightFetchingState, setEpicFetchingState, getMilestoneTitle, getBoardsModalData, @@ -19,6 +18,7 @@ import { import VueApollo from 'vue-apollo'; import BoardContent from '~/boards/components/board_content.vue'; +import BoardExtraActions from '~/boards/components/board_extra_actions.vue'; import createDefaultClient from '~/lib/graphql'; import { deprecatedCreateFlash as Flash } from '~/flash'; import { __ } from '~/locale'; @@ -84,8 +84,12 @@ export default () => { }, provide: { boardId: $boardApp.dataset.boardId, - groupId: Number($boardApp.dataset.groupId) || null, + groupId: Number($boardApp.dataset.groupId), rootPath: $boardApp.dataset.rootPath, + canUpdate: $boardApp.dataset.canUpdate, + labelsFetchPath: $boardApp.dataset.labelsFetchPath, + labelsManagePath: $boardApp.dataset.labelsManagePath, + labelsFilterBasePath: $boardApp.dataset.labelsFilterBasePath, }, store, apolloProvider, @@ -131,6 +135,7 @@ export default () => { eventHub.$on('clearDetailIssue', this.clearDetailIssue); sidebarEventHub.$on('toggleSubscription', this.toggleSubscription); eventHub.$on('performSearch', this.performSearch); + eventHub.$on('initialBoardLoad', this.initialBoardLoad); }, beforeDestroy() { eventHub.$off('updateTokens', this.updateTokens); @@ -138,6 +143,7 @@ export default () => { eventHub.$off('clearDetailIssue', this.clearDetailIssue); sidebarEventHub.$off('toggleSubscription', this.toggleSubscription); eventHub.$off('performSearch', this.performSearch); + eventHub.$off('initialBoardLoad', this.initialBoardLoad); }, mounted() { this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit); @@ -148,6 +154,19 @@ export default () => { boardsStore.disabled = this.disabled; if (!gon.features.graphqlBoardLists) { + this.initialBoardLoad(); + } + }, + methods: { + ...mapActions([ + 'setInitialBoardData', + 'setFilters', + 'fetchEpicsSwimlanes', + 'resetIssues', + 'resetEpics', + 'fetchLists', + ]), + initialBoardLoad() { boardsStore .all() .then(res => res.data) @@ -160,30 +179,26 @@ export default () => { .catch(() => { Flash(__('An error occurred while fetching the board lists. Please try again.')); }); - } - }, - methods: { - ...mapActions([ - 'setInitialBoardData', - 'setFilters', - 'fetchEpicsSwimlanes', - 'fetchIssuesForAllLists', - ]), + }, updateTokens() { this.filterManager.updateTokens(); }, performSearch() { this.setFilters(convertObjectPropsToCamelCase(urlParamsToObject(window.location.search))); if (gon.features.boardsWithSwimlanes && this.isShowingEpicsSwimlanes) { - this.fetchEpicsSwimlanes(false); - this.fetchIssuesForAllLists(); + this.resetEpics(); + this.resetIssues(); + this.fetchEpicsSwimlanes({}); + } else if (gon.features.graphqlBoardLists && !this.isShowingEpicsSwimlanes) { + this.fetchLists(); + this.resetIssues(); } }, updateDetailIssue(newIssue, multiSelect = false) { const { sidebarInfoEndpoint } = newIssue; if (sidebarInfoEndpoint && newIssue.subscribed === undefined) { newIssue.setFetchingState('subscriptions', true); - setWeigthFetchingState(newIssue, true); + setWeightFetchingState(newIssue, true); setEpicFetchingState(newIssue, true); boardsStore .getIssueInfo(sidebarInfoEndpoint) @@ -201,7 +216,7 @@ export default () => { } = convertObjectPropsToCamelCase(data); newIssue.setFetchingState('subscriptions', false); - setWeigthFetchingState(newIssue, false); + setWeightFetchingState(newIssue, false); setEpicFetchingState(newIssue, false); newIssue.updateData({ humanTimeSpent: humanTotalTimeSpent, @@ -216,7 +231,7 @@ export default () => { }) .catch(() => { newIssue.setFetchingState('subscriptions', false); - setWeigthFetchingState(newIssue, false); + setWeightFetchingState(newIssue, false); Flash(__('An error occurred while fetching sidebar data')); }); } @@ -300,63 +315,32 @@ export default () => { } return !this.store.lists.filter(list => !list.preset).length; }, - tooltipTitle() { - if (this.disabled) { - return __('Please add a list to your board first'); - } - - return ''; - }, - }, - watch: { - disabled() { - this.updateTooltip(); - }, - }, - mounted() { - this.updateTooltip(); }, methods: { - updateTooltip() { - const $tooltip = $(this.$refs.addIssuesButton); - - this.$nextTick(() => { - if (this.disabled) { - $tooltip.tooltip(); - } else { - $tooltip.tooltip('dispose'); - } - }); - }, openModal() { if (!this.disabled) { this.toggleModal(true); } }, }, - template: ` -
- -
- `, + render(createElement) { + return createElement(BoardExtraActions, { + props: { + canAdminList: this.$options.el.hasAttribute('data-can-admin-list'), + openModal: this.openModal, + disabled: this.disabled, + }, + }); + }, }); } toggleFocusMode(ModalStore, boardsStore); toggleLabels(); - toggleEpicsSwimlanes(); + + if (gon.features?.swimlanes) { + toggleEpicsSwimlanes(); + } + mountMultipleBoardsSwitcher(); }; diff --git a/app/assets/javascripts/boards/mixins/is_wip_limits.js b/app/assets/javascripts/boards/mixins/is_wip_limits.js deleted file mode 100644 index f172179d3c7..00000000000 --- a/app/assets/javascripts/boards/mixins/is_wip_limits.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - computed: { - isWipLimitsOn() { - return false; - }, - }, -}; diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 2f6caffbf84..09f5d5b4dd8 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,4 +1,4 @@ -/* eslint-disable no-underscore-dangle, class-methods-use-this */ +/* eslint-disable class-methods-use-this */ import { __ } from '~/locale'; import ListLabel from './label'; import ListAssignee from './assignee'; @@ -34,7 +34,6 @@ const TYPES = { class List { constructor(obj) { this.id = obj.id; - this._uid = this.guid(); this.position = obj.position; this.title = (obj.list_type || obj.listType) === 'backlog' ? __('Open') : obj.title; this.type = obj.list_type || obj.listType; diff --git a/app/assets/javascripts/boards/queries/board.mutation.graphql b/app/assets/javascripts/boards/queries/board.mutation.graphql new file mode 100644 index 00000000000..ef2b81a7939 --- /dev/null +++ b/app/assets/javascripts/boards/queries/board.mutation.graphql @@ -0,0 +1,11 @@ +mutation UpdateBoard($id: ID!, $hideClosedList: Boolean, $hideBacklogList: Boolean) { + updateBoard( + input: { id: $id, hideClosedList: $hideClosedList, hideBacklogList: $hideBacklogList } + ) { + board { + id + hideClosedList + hideBacklogList + } + } +} diff --git a/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql b/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql index dcfe69222a0..48420b349ae 100644 --- a/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql +++ b/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql @@ -1,7 +1,21 @@ -#import "./board_list.fragment.graphql" +#import "ee_else_ce/boards/queries/board_list.fragment.graphql" -mutation CreateBoardList($boardId: BoardID!, $backlog: Boolean) { - boardListCreate(input: { boardId: $boardId, backlog: $backlog }) { +mutation CreateBoardList( + $boardId: BoardID! + $backlog: Boolean + $labelId: LabelID + $milestoneId: MilestoneID + $assigneeId: UserID +) { + boardListCreate( + input: { + boardId: $boardId + backlog: $backlog + labelId: $labelId + milestoneId: $milestoneId + assigneeId: $assigneeId + } + ) { list { ...BoardListFragment } diff --git a/app/assets/javascripts/boards/queries/board_lists.query.graphql b/app/assets/javascripts/boards/queries/board_lists.query.graphql new file mode 100644 index 00000000000..88425e9a9c1 --- /dev/null +++ b/app/assets/javascripts/boards/queries/board_lists.query.graphql @@ -0,0 +1,28 @@ +#import "ee_else_ce/boards/queries/board_list.fragment.graphql" + +query ListIssues( + $fullPath: ID! + $boardId: ID! + $filters: BoardIssueInput + $isGroup: Boolean = false + $isProject: Boolean = false +) { + group(fullPath: $fullPath) @include(if: $isGroup) { + board(id: $boardId) { + lists(issueFilters: $filters) { + nodes { + ...BoardListFragment + } + } + } + } + project(fullPath: $fullPath) @include(if: $isProject) { + board(id: $boardId) { + lists(issueFilters: $filters) { + nodes { + ...BoardListFragment + } + } + } + } +} diff --git a/app/assets/javascripts/boards/queries/group_board.query.graphql b/app/assets/javascripts/boards/queries/group_board.query.graphql deleted file mode 100644 index cb42cb3f73d..00000000000 --- a/app/assets/javascripts/boards/queries/group_board.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "ee_else_ce/boards/queries/board_list.fragment.graphql" - -query GroupBoard($fullPath: ID!, $boardId: ID!) { - group(fullPath: $fullPath) { - board(id: $boardId) { - lists { - nodes { - ...BoardListFragment - } - } - } - } -} diff --git a/app/assets/javascripts/boards/queries/issue_set_labels.mutation.graphql b/app/assets/javascripts/boards/queries/issue_set_labels.mutation.graphql new file mode 100644 index 00000000000..3c5f4b3e3bd --- /dev/null +++ b/app/assets/javascripts/boards/queries/issue_set_labels.mutation.graphql @@ -0,0 +1,15 @@ +mutation issueSetLabels($input: UpdateIssueInput!) { + updateIssue(input: $input) { + issue { + labels { + nodes { + id + title + color + description + } + } + } + errors + } +} diff --git a/app/assets/javascripts/boards/queries/lists_issues.query.graphql b/app/assets/javascripts/boards/queries/lists_issues.query.graphql index c66cdf68cf4..5dbfe4675c6 100644 --- a/app/assets/javascripts/boards/queries/lists_issues.query.graphql +++ b/app/assets/javascripts/boards/queries/lists_issues.query.graphql @@ -7,15 +7,24 @@ query ListIssues( $filters: BoardIssueInput $isGroup: Boolean = false $isProject: Boolean = false + $after: String + $first: Int ) { group(fullPath: $fullPath) @include(if: $isGroup) { board(id: $boardId) { lists(id: $id) { nodes { id - issues(filters: $filters) { - nodes { - ...IssueNode + issues(first: $first, filters: $filters, after: $after) { + count + edges { + node { + ...IssueNode + } + } + pageInfo { + endCursor + hasNextPage } } } @@ -27,9 +36,16 @@ query ListIssues( lists(id: $id) { nodes { id - issues(filters: $filters) { - nodes { - ...IssueNode + issues(first: $first, filters: $filters, after: $after) { + count + edges { + node { + ...IssueNode + } + } + pageInfo { + endCursor + hasNextPage } } } diff --git a/app/assets/javascripts/boards/queries/project_board.query.graphql b/app/assets/javascripts/boards/queries/project_board.query.graphql deleted file mode 100644 index 4620a7e0fd5..00000000000 --- a/app/assets/javascripts/boards/queries/project_board.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "ee_else_ce/boards/queries/board_list.fragment.graphql" - -query ProjectBoard($fullPath: ID!, $boardId: ID!) { - project(fullPath: $fullPath) { - board(id: $boardId) { - lists { - nodes { - ...BoardListFragment - } - } - } - } -} diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 4b81d9c73ef..1fed1228106 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -1,28 +1,38 @@ import Cookies from 'js-cookie'; -import { sortBy, pick } from 'lodash'; -import createFlash from '~/flash'; +import { pick } from 'lodash'; + +import boardListsQuery from 'ee_else_ce/boards/queries/board_lists.query.graphql'; import { __ } from '~/locale'; import { parseBoolean } from '~/lib/utils/common_utils'; -import createDefaultClient from '~/lib/graphql'; +import createGqClient, { fetchPolicies } from '~/lib/graphql'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { BoardType, ListType, inactiveId } from '~/boards/constants'; import * as types from './mutation_types'; -import { formatListIssues, fullBoardId } from '../boards_util'; +import { + formatBoardLists, + formatListIssues, + fullBoardId, + formatListsPageInfo, +} from '../boards_util'; import boardStore from '~/boards/stores/boards_store'; import listsIssuesQuery from '../queries/lists_issues.query.graphql'; -import projectBoardQuery from '../queries/project_board.query.graphql'; -import groupBoardQuery from '../queries/group_board.query.graphql'; import createBoardListMutation from '../queries/board_list_create.mutation.graphql'; import updateBoardListMutation from '../queries/board_list_update.mutation.graphql'; import issueMoveListMutation from '../queries/issue_move_list.mutation.graphql'; +import issueSetLabels from '../queries/issue_set_labels.mutation.graphql'; const notImplemented = () => { /* eslint-disable-next-line @gitlab/require-i18n-strings */ throw new Error('Not implemented!'); }; -export const gqlClient = createDefaultClient(); +export const gqlClient = createGqClient( + {}, + { + fetchPolicy: fetchPolicies.NO_CACHE, + }, +); export default { setInitialBoardData: ({ commit }, data) => { @@ -50,62 +60,46 @@ export default { }, fetchLists: ({ commit, state, dispatch }) => { - const { endpoints, boardType } = state; + const { endpoints, boardType, filterParams } = state; const { fullPath, boardId } = endpoints; - let query; - if (boardType === BoardType.group) { - query = groupBoardQuery; - } else if (boardType === BoardType.project) { - query = projectBoardQuery; - } else { - createFlash(__('Invalid board')); - return Promise.reject(); - } - const variables = { fullPath, boardId: fullBoardId(boardId), + filters: filterParams, + isGroup: boardType === BoardType.group, + isProject: boardType === BoardType.project, }; return gqlClient .query({ - query, + query: boardListsQuery, variables, }) .then(({ data }) => { - let { lists } = data[boardType]?.board; - // Temporarily using positioning logic from boardStore - lists = lists.nodes.map(list => - boardStore.updateListPosition({ - ...list, - doNotFetchIssues: true, - }), - ); - commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position')); - // Backlog list needs to be created if it doesn't exist - if (!lists.find(l => l.type === ListType.backlog)) { + const { lists, hideBacklogList } = data[boardType]?.board; + commit(types.RECEIVE_BOARD_LISTS_SUCCESS, formatBoardLists(lists)); + // Backlog list needs to be created if it doesn't exist and it's not hidden + if (!lists.nodes.find(l => l.listType === ListType.backlog) && !hideBacklogList) { dispatch('createList', { backlog: true }); } dispatch('showWelcomeList'); }) - .catch(() => { - createFlash( - __('An error occurred while fetching the board lists. Please reload the page.'), - ); - }); + .catch(() => commit(types.RECEIVE_BOARD_LISTS_FAILURE)); }, - // This action only supports backlog list creation at this stage - // Future iterations will add the ability to create other list types - createList: ({ state, commit, dispatch }, { backlog = false }) => { + createList: ({ state, commit, dispatch }, { backlog, labelId, milestoneId, assigneeId }) => { const { boardId } = state.endpoints; + gqlClient .mutate({ mutation: createBoardListMutation, variables: { boardId: fullBoardId(boardId), backlog, + labelId, + milestoneId, + assigneeId, }, }) .then(({ data }) => { @@ -116,16 +110,15 @@ export default { dispatch('addList', list); } }) - .catch(() => { - commit(types.CREATE_LIST_FAILURE); - }); + .catch(() => commit(types.CREATE_LIST_FAILURE)); }, - addList: ({ state, commit }, list) => { - const lists = state.boardLists; + addList: ({ commit }, list) => { // Temporarily using positioning logic from boardStore - lists.push(boardStore.updateListPosition({ ...list, doNotFetchIssues: true })); - commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position')); + commit( + types.RECEIVE_ADD_LIST_SUCCESS, + boardStore.updateListPosition({ ...list, doNotFetchIssues: true }), + ); }, showWelcomeList: ({ state, dispatch }) => { @@ -133,7 +126,9 @@ export default { return; } if ( - state.boardLists.find(list => list.type !== ListType.backlog && list.type !== ListType.closed) + Object.entries(state.boardLists).find( + ([, list]) => list.type !== ListType.backlog && list.type !== ListType.closed, + ) ) { return; } @@ -155,13 +150,16 @@ export default { notImplemented(); }, - moveList: ({ state, commit, dispatch }, { listId, newIndex, adjustmentValue }) => { + moveList: ( + { state, commit, dispatch }, + { listId, replacedListId, newIndex, adjustmentValue }, + ) => { const { boardLists } = state; - const backupList = [...boardLists]; - const movedList = boardLists.find(({ id }) => id === listId); + const backupList = { ...boardLists }; + const movedList = boardLists[listId]; const newPosition = newIndex - 1; - const listAtNewIndex = boardLists[newIndex]; + const listAtNewIndex = boardLists[replacedListId]; movedList.position = newPosition; listAtNewIndex.position += adjustmentValue; @@ -197,7 +195,9 @@ export default { notImplemented(); }, - fetchIssuesForList: ({ state, commit }, listId) => { + fetchIssuesForList: ({ state, commit }, { listId, fetchNext = false }) => { + commit(types.REQUEST_ISSUES_FOR_LIST, { listId, fetchNext }); + const { endpoints, boardType, filterParams } = state; const { fullPath, boardId } = endpoints; @@ -208,6 +208,8 @@ export default { filters: filterParams, isGroup: boardType === BoardType.group, isProject: boardType === BoardType.project, + first: 20, + after: fetchNext ? state.pageInfoByListId[listId].endCursor : undefined, }; return gqlClient @@ -221,36 +223,14 @@ export default { .then(({ data }) => { const { lists } = data[boardType]?.board; const listIssues = formatListIssues(lists); - commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listId }); + const listPageInfo = formatListsPageInfo(lists); + commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listPageInfo, listId }); }) .catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId)); }, - fetchIssuesForAllLists: ({ state, commit }) => { - commit(types.REQUEST_ISSUES_FOR_ALL_LISTS); - - const { endpoints, boardType, filterParams } = state; - const { fullPath, boardId } = endpoints; - - const variables = { - fullPath, - boardId: fullBoardId(boardId), - filters: filterParams, - isGroup: boardType === BoardType.group, - isProject: boardType === BoardType.project, - }; - - return gqlClient - .query({ - query: listsIssuesQuery, - variables, - }) - .then(({ data }) => { - const { lists } = data[boardType]?.board; - const listIssues = formatListIssues(lists); - commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS, listIssues); - }) - .catch(() => commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE)); + resetIssues: ({ commit }) => { + commit(types.RESET_ISSUES); }, moveIssue: ( @@ -303,6 +283,31 @@ export default { commit(types.ADD_ISSUE_TO_LIST_FAILURE, { list, issue }); }, + setActiveIssueLabels: async ({ commit, getters }, input) => { + const activeIssue = getters.getActiveIssue; + const { data } = await gqlClient.mutate({ + mutation: issueSetLabels, + variables: { + input: { + iid: String(activeIssue.iid), + addLabelIds: input.addLabelIds ?? [], + removeLabelIds: input.removeLabelIds ?? [], + projectPath: input.projectPath, + }, + }, + }); + + if (data.updateIssue?.errors?.length > 0) { + throw new Error(data.updateIssue.errors); + } + + commit(types.UPDATE_ISSUE_BY_ID, { + issueId: activeIssue.id, + prop: 'labels', + value: data.updateIssue.issue.labels.nodes, + }); + }, + fetchBacklog: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index faf4f9ebfd3..d1a5db1bcc5 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -2,7 +2,7 @@ /* global List */ /* global ListIssue */ import $ from 'jquery'; -import { sortBy } from 'lodash'; +import { sortBy, pick } from 'lodash'; import Vue from 'vue'; import Cookies from 'js-cookie'; import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee'; @@ -12,7 +12,7 @@ import { parseBoolean, convertObjectPropsToCamelCase, } from '~/lib/utils/common_utils'; -import { __ } from '~/locale'; +import createDefaultClient from '~/lib/graphql'; import axios from '~/lib/utils/axios_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; @@ -23,7 +23,11 @@ import ListLabel from '../models/label'; import ListAssignee from '../models/assignee'; import ListMilestone from '../models/milestone'; +import createBoardMutation from '../queries/board.mutation.graphql'; + const PER_PAGE = 20; +export const gqlClient = createDefaultClient(); + const boardsStore = { disabled: false, timeTracking: { @@ -114,7 +118,6 @@ const boardsStore = { .catch(() => { // https://gitlab.com/gitlab-org/gitlab-foss/issues/30821 }); - this.removeBlankState(); }, updateNewListDropdown(listId) { $(`.js-board-list-${listId}`).removeClass('is-active'); @@ -124,22 +127,14 @@ const boardsStore = { return !this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]; }, addBlankState() { - if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; - - this.addList({ - id: 'blank', - list_type: 'blank', - title: __('Welcome to your Issue Board!'), - position: 0, - }); - }, - removeBlankState() { - this.removeList('blank'); + if (!this.shouldAddBlankState() || this.welcomeIsHidden()) return; - Cookies.set('issue_board_welcome_hidden', 'true', { - expires: 365 * 10, - path: '', - }); + this.generateDefaultLists() + .then(res => res.data) + .then(data => Promise.all(data.map(list => this.addList(list)))) + .catch(() => { + this.removeList(undefined, 'label'); + }); }, findIssueLabel(issue, findLabel) { @@ -542,6 +537,10 @@ const boardsStore = { this.timeTracking.limitToHours = parseBoolean(limitToHours); }, + generateBoardGid(boardId) { + return `gid://gitlab/Board/${boardId}`; + }, + generateBoardsPath(id) { return `${this.state.endpoints.boardsEndpoint}${id ? `/${id}` : ''}.json`; }, @@ -800,9 +799,33 @@ const boardsStore = { } if (boardPayload.id) { - return axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload }); + const input = { + ...pick(boardPayload, ['hideClosedList', 'hideBacklogList']), + id: this.generateBoardGid(boardPayload.id), + }; + + return Promise.all([ + axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload }), + gqlClient.mutate({ + mutation: createBoardMutation, + variables: input, + }), + ]); } - return axios.post(this.generateBoardsPath(), { board: boardPayload }); + + return axios + .post(this.generateBoardsPath(), { board: boardPayload }) + .then(resp => resp.data) + .then(data => { + gqlClient.mutate({ + mutation: createBoardMutation, + variables: { + ...pick(boardPayload, ['hideClosedList', 'hideBacklogList']), + id: this.generateBoardGid(data.id), + }, + }); + return data; + }); }, deleteBoard({ id }) { diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js index 3688476dc5f..89a3b14b262 100644 --- a/app/assets/javascripts/boards/stores/getters.js +++ b/app/assets/javascripts/boards/stores/getters.js @@ -1,10 +1,11 @@ +import { find } from 'lodash'; import { inactiveId } from '../constants'; export default { getLabelToggleState: state => (state.isShowingLabels ? 'on' : 'off'), isSidebarOpen: state => state.activeId !== inactiveId, isSwimlanesOn: state => { - if (!gon?.features?.boardsWithSwimlanes) { + if (!gon?.features?.boardsWithSwimlanes && !gon?.features?.swimlanes) { return false; } @@ -22,4 +23,16 @@ export default { getActiveIssue: state => { return state.issues[state.activeId] || {}; }, + + getListByLabelId: state => labelId => { + return find(state.boardLists, l => l.label?.id === labelId); + }, + + getListByTitle: state => title => { + return find(state.boardLists, l => l.title === title); + }, + + shouldUseGraphQL: () => { + return gon?.features?.graphqlBoardLists; + }, }; diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index f0a283f6161..09ab08062df 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -3,6 +3,7 @@ export const SET_FILTERS = 'SET_FILTERS'; export const CREATE_LIST_SUCCESS = 'CREATE_LIST_SUCCESS'; export const CREATE_LIST_FAILURE = 'CREATE_LIST_FAILURE'; export const RECEIVE_BOARD_LISTS_SUCCESS = 'RECEIVE_BOARD_LISTS_SUCCESS'; +export const RECEIVE_BOARD_LISTS_FAILURE = 'RECEIVE_BOARD_LISTS_FAILURE'; export const SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST'; export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST'; export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS'; @@ -12,11 +13,9 @@ export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE'; export const REQUEST_REMOVE_LIST = 'REQUEST_REMOVE_LIST'; export const RECEIVE_REMOVE_LIST_SUCCESS = 'RECEIVE_REMOVE_LIST_SUCCESS'; export const RECEIVE_REMOVE_LIST_ERROR = 'RECEIVE_REMOVE_LIST_ERROR'; -export const REQUEST_ISSUES_FOR_ALL_LISTS = 'REQUEST_ISSUES_FOR_ALL_LISTS'; +export const REQUEST_ISSUES_FOR_LIST = 'REQUEST_ISSUES_FOR_LIST'; export const RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE'; export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS'; -export const RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS = 'RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS'; -export const RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE = 'RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE'; export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE'; export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS'; export const RECEIVE_ADD_ISSUE_ERROR = 'RECEIVE_ADD_ISSUE_ERROR'; @@ -32,3 +31,4 @@ export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE'; export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE'; export const SET_ACTIVE_ID = 'SET_ACTIVE_ID'; export const UPDATE_ISSUE_BY_ID = 'UPDATE_ISSUE_BY_ID'; +export const RESET_ISSUES = 'RESET_ISSUES'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index faeb3e25a71..0c7dbc0d2ef 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -1,8 +1,8 @@ import Vue from 'vue'; -import { sortBy, pull } from 'lodash'; +import { pull, union } from 'lodash'; import { formatIssue, moveIssueListHelper } from '../boards_util'; import * as mutationTypes from './mutation_types'; -import { __ } from '~/locale'; +import { s__ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; const notImplemented = () => { @@ -10,11 +10,13 @@ const notImplemented = () => { throw new Error('Not implemented!'); }; -const removeIssueFromList = (state, listId, issueId) => { +export const removeIssueFromList = ({ state, listId, issueId }) => { Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId)); + const list = state.boardLists[listId]; + Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize - 1 }); }; -const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => { +export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => { const listIssues = state.issuesByListId[listId]; let newIndex = atIndex || 0; if (moveBeforeId) { @@ -24,6 +26,8 @@ const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atI } listIssues.splice(newIndex, 0, issueId); Vue.set(state.issuesByListId, listId, listIssues); + const list = state.boardLists[listId]; + Vue.set(state.boardLists, listId, { ...list, issuesSize: list.issuesSize + 1 }); }; export default { @@ -39,6 +43,12 @@ export default { state.boardLists = lists; }, + [mutationTypes.RECEIVE_BOARD_LISTS_FAILURE]: state => { + state.error = s__( + 'Boards|An error occurred while fetching the board lists. Please reload the page.', + ); + }, + [mutationTypes.SET_ACTIVE_ID](state, { id, sidebarType }) { state.activeId = id; state.sidebarType = sidebarType; @@ -49,15 +59,15 @@ export default { }, [mutationTypes.CREATE_LIST_FAILURE]: state => { - state.error = __('An error occurred while creating the list. Please try again.'); + state.error = s__('Boards|An error occurred while creating the list. Please try again.'); }, [mutationTypes.REQUEST_ADD_LIST]: () => { notImplemented(); }, - [mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: () => { - notImplemented(); + [mutationTypes.RECEIVE_ADD_LIST_SUCCESS]: (state, list) => { + Vue.set(state.boardLists, list.id, list); }, [mutationTypes.RECEIVE_ADD_LIST_ERROR]: () => { @@ -66,14 +76,12 @@ export default { [mutationTypes.MOVE_LIST]: (state, { movedList, listAtNewIndex }) => { const { boardLists } = state; - const movedListIndex = state.boardLists.findIndex(l => l.id === movedList.id); - Vue.set(boardLists, movedListIndex, movedList); - Vue.set(boardLists, movedListIndex.position + 1, listAtNewIndex); - Vue.set(state, 'boardLists', sortBy(boardLists, 'position')); + Vue.set(boardLists, movedList.id, movedList); + Vue.set(boardLists, listAtNewIndex.id, listAtNewIndex); }, [mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => { - state.error = __('An error occurred while updating the list. Please try again.'); + state.error = s__('Boards|An error occurred while updating the list. Please try again.'); Vue.set(state, 'boardLists', backupList); }, @@ -89,28 +97,36 @@ export default { notImplemented(); }, - [mutationTypes.RECEIVE_ISSUES_FOR_LIST_SUCCESS]: (state, { listIssues, listId }) => { + [mutationTypes.REQUEST_ISSUES_FOR_LIST]: (state, { listId, fetchNext }) => { + Vue.set(state.listsFlags, listId, { [fetchNext ? 'isLoadingMore' : 'isLoading']: true }); + }, + + [mutationTypes.RECEIVE_ISSUES_FOR_LIST_SUCCESS]: ( + state, + { listIssues, listPageInfo, listId }, + ) => { const { listData, issues } = listIssues; Vue.set(state, 'issues', { ...state.issues, ...issues }); - Vue.set(state.issuesByListId, listId, listData[listId]); - const listIndex = state.boardLists.findIndex(l => l.id === listId); - Vue.set(state.boardLists[listIndex], 'loading', false); + Vue.set( + state.issuesByListId, + listId, + union(state.issuesByListId[listId] || [], listData[listId]), + ); + Vue.set(state.pageInfoByListId, listId, listPageInfo[listId]); + Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false }); }, [mutationTypes.RECEIVE_ISSUES_FOR_LIST_FAILURE]: (state, listId) => { - state.error = __('An error occurred while fetching the board issues. Please reload the page.'); - const listIndex = state.boardLists.findIndex(l => l.id === listId); - Vue.set(state.boardLists[listIndex], 'loading', false); - }, - - [mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => { - state.isLoadingIssues = true; + state.error = s__( + 'Boards|An error occurred while fetching the board issues. Please reload the page.', + ); + Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false }); }, - [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, { listData, issues }) => { - state.issuesByListId = listData; - state.issues = issues; - state.isLoadingIssues = false; + [mutationTypes.RESET_ISSUES]: state => { + Object.keys(state.issuesByListId).forEach(listId => { + Vue.set(state.issuesByListId, listId, []); + }); }, [mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => { @@ -122,11 +138,6 @@ export default { Vue.set(state.issues[issueId], prop, value); }, - [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => { - state.error = __('An error occurred while fetching the board issues. Please reload the page.'); - state.isLoadingIssues = false; - }, - [mutationTypes.REQUEST_ADD_ISSUE]: () => { notImplemented(); }, @@ -143,13 +154,13 @@ export default { state, { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId }, ) => { - const fromList = state.boardLists.find(l => l.id === fromListId); - const toList = state.boardLists.find(l => l.id === toListId); + const fromList = state.boardLists[fromListId]; + const toList = state.boardLists[toListId]; const issue = moveIssueListHelper(originalIssue, fromList, toList); Vue.set(state.issues, issue.id, issue); - removeIssueFromList(state, fromListId, issue.id); + removeIssueFromList({ state, listId: fromListId, issueId: issue.id }); addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId }); }, @@ -162,9 +173,9 @@ export default { state, { originalIssue, fromListId, toListId, originalIndex }, ) => { - state.error = __('An error occurred while moving the issue. Please try again.'); + state.error = s__('Boards|An error occurred while moving the issue. Please try again.'); Vue.set(state.issues, originalIssue.id, originalIssue); - removeIssueFromList(state, toListId, originalIssue.id); + removeIssueFromList({ state, listId: toListId, issueId: originalIssue.id }); addIssueToList({ state, listId: fromListId, @@ -193,8 +204,8 @@ export default { }, [mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issue }) => { - state.error = __('An error occurred while creating the issue. Please try again.'); - removeIssueFromList(state, list.id, issue.id); + state.error = s__('Boards|An error occurred while creating the issue. Please try again.'); + removeIssueFromList({ state, listId: list.id, issueId: issue.id }); }, [mutationTypes.SET_CURRENT_PAGE]: () => { diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index be937d68c6c..b91c09f8051 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -8,10 +8,11 @@ export default () => ({ isShowingLabels: true, activeId: inactiveId, sidebarType: '', - boardLists: [], + boardLists: {}, + listsFlags: {}, issuesByListId: {}, + pageInfoByListId: {}, issues: {}, - isLoadingIssues: false, filterParams: {}, error: undefined, // TODO: remove after ce/ee split of board_content.vue diff --git a/app/assets/javascripts/boards/toggle_focus.js b/app/assets/javascripts/boards/toggle_focus.js index e60e7059192..fa13d3a9e3c 100644 --- a/app/assets/javascripts/boards/toggle_focus.js +++ b/app/assets/javascripts/boards/toggle_focus.js @@ -1,13 +1,15 @@ import $ from 'jquery'; import Vue from 'vue'; -import collapseIcon from './icons/fullscreen_collapse.svg'; -import expandIcon from './icons/fullscreen_expand.svg'; +import { GlIcon } from '@gitlab/ui'; export default (ModalStore, boardsStore) => { const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board'); return new Vue({ el: document.getElementById('js-toggle-focus-btn'), + components: { + GlIcon, + }, data: { modal: ModalStore.store, store: boardsStore.state, @@ -32,12 +34,7 @@ export default (ModalStore, boardsStore) => { title="Toggle focus mode" ref="toggleFocusModeButton" @click="toggleFocusMode"> - - ${collapseIcon} - - - ${expandIcon} - + `, -- cgit v1.2.3