diff options
Diffstat (limited to 'app/assets/javascripts/boards')
32 files changed, 350 insertions, 94 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 3178bda93b8..384a386d69c 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -1,7 +1,28 @@ +import ListIssue from 'ee_else_ce/boards/models/issue'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; + export function getMilestone() { return null; } +export function formatListIssues(listIssues) { + return listIssues.nodes.reduce((map, list) => { + return { + ...map, + [list.id]: list.issues.nodes.map( + i => + new ListIssue({ + ...i, + id: getIdFromGraphQLId(i.id), + labels: i.labels?.nodes || [], + assignees: i.assignees?.nodes || [], + }), + ), + }; + }, {}); +} + export default { getMilestone, + formatListIssues, }; diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index 0ed7579e8e1..dae24338e45 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -1,10 +1,10 @@ <script> import Sortable from 'sortablejs'; import isWipLimitsOn from 'ee_else_ce/boards/mixins/is_wip_limits'; +import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import Tooltip from '~/vue_shared/directives/tooltip'; import EmptyComponent from '~/vue_shared/components/empty_component'; import BoardBlankState from './board_blank_state.vue'; -import BoardListHeader from 'ee_else_ce/boards/components/board_list_header.vue'; import BoardList from './board_list.vue'; import boardsStore from '../stores/boards_store'; import eventHub from '../eventhub'; diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 6ac7fdce6a7..c42295792f1 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -1,8 +1,8 @@ <script> import { mapState } from 'vuex'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import BoardColumn from 'ee_else_ce/boards/components/board_column.vue'; import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; export default { components: { @@ -42,7 +42,7 @@ export default { }, }, computed: { - ...mapState(['isShowingEpicsSwimlanes']), + ...mapState(['isShowingEpicsSwimlanes', 'boardLists']), isSwimlanesOn() { return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes; }, @@ -73,11 +73,12 @@ export default { <epics-swimlanes v-else ref="swimlanes" - :lists="lists" + :lists="boardLists" :can-admin-list="canAdminList" :disabled="disabled" :board-id="boardId" :group-id="groupId" + :root-path="rootPath" /> </div> </template> diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index fbe221041c1..231059b895e 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -1,6 +1,6 @@ <script> import { __ } from '~/locale'; -import Flash from '~/flash'; +import { deprecatedCreateFlash as Flash } from '~/flash'; import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { visitUrl } from '~/lib/utils/url_utility'; import boardsStore from '~/boards/stores/boards_store'; diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 4270ad5783d..1a26782f6f0 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -6,7 +6,7 @@ import boardCard from './board_card.vue'; import eventHub from '../eventhub'; import boardsStore from '../stores/boards_store'; import { sprintf, __ } from '~/locale'; -import createFlash from '~/flash'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; import { getBoardSortableDefaultOptions, sortableStart, diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue index 02a04cb4e46..bafe07afb48 100644 --- a/app/assets/javascripts/boards/components/board_list_header.vue +++ b/app/assets/javascripts/boards/components/board_list_header.vue @@ -241,7 +241,7 @@ export default { v-if="isSwimlanesHeader && !list.isExpanded" ref="collapsedInfo" aria-hidden="true" - class="board-header-collapsed-info-icon gl-mt-2 gl-cursor-pointer gl-text-gray-700" + class="board-header-collapsed-info-icon gl-mt-2 gl-cursor-pointer gl-text-gray-500" > <gl-icon name="information" /> </span> @@ -282,7 +282,7 @@ export default { <div v-if="showBoardListAndBoardInfo" class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag text-secondary" - :class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }" + :class="{ 'gl-display-none!': !list.isExpanded && isSwimlanesHeader }" > <span class="gl-display-inline-flex"> <gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" /> diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 02ac45f8ef9..34e8438ba4c 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -119,7 +119,7 @@ export default { autocomplete="off" /> <project-select v-if="groupId" :group-id="groupId" :list="list" /> - <div class="clearfix prepend-top-10"> + <div class="clearfix gl-mt-3"> <gl-button ref="submit-button" :disabled="disabled" diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue new file mode 100644 index 00000000000..3149762ecdf --- /dev/null +++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue @@ -0,0 +1,92 @@ +<script> +import { GlDrawer, GlLabel } from '@gitlab/ui'; +import { mapActions, mapState } from 'vuex'; +import { __ } from '~/locale'; +import boardsStore from '~/boards/stores/boards_store'; +import eventHub from '~/sidebar/event_hub'; +import { isScopedLabel } from '~/lib/utils/common_utils'; +import { inactiveId } from '~/boards/constants'; + +// NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options. +export default { + headerHeight: process.env.NODE_ENV === 'development' ? '75px' : '40px', + listSettingsText: __('List settings'), + assignee: 'assignee', + milestone: 'milestone', + label: 'label', + labelListText: __('Label'), + components: { + GlDrawer, + GlLabel, + BoardSettingsSidebarWipLimit: () => + import('ee_component/boards/components/board_settings_wip_limit.vue'), + BoardSettingsListTypes: () => + import('ee_component/boards/components/board_settings_list_types.vue'), + }, + computed: { + ...mapState(['activeId']), + activeList() { + /* + Warning: Though a computed property it is not reactive because we are + referencing a List Model class. Reactivity only applies to plain JS objects + */ + return boardsStore.state.lists.find(({ id }) => id === this.activeId); + }, + isSidebarOpen() { + return this.activeId !== inactiveId; + }, + activeListLabel() { + return this.activeList.label; + }, + boardListType() { + return this.activeList.type || null; + }, + listTypeTitle() { + return this.$options.labelListText; + }, + }, + created() { + eventHub.$on('sidebar.closeAll', this.closeSidebar); + }, + beforeDestroy() { + eventHub.$off('sidebar.closeAll', this.closeSidebar); + }, + methods: { + ...mapActions(['setActiveId']), + closeSidebar() { + this.setActiveId(inactiveId); + }, + showScopedLabels(label) { + return boardsStore.scopedLabels.enabled && isScopedLabel(label); + }, + }, +}; +</script> + +<template> + <gl-drawer + class="js-board-settings-sidebar" + :open="isSidebarOpen" + :header-height="$options.headerHeight" + @close="closeSidebar" + > + <template #header>{{ $options.listSettingsText }}</template> + <template v-if="isSidebarOpen"> + <div v-if="boardListType === $options.label"> + <label class="js-list-label gl-display-block">{{ listTypeTitle }}</label> + <gl-label + :title="activeListLabel.title" + :background-color="activeListLabel.color" + :scoped="showScopedLabels(activeListLabel)" + /> + </div> + + <board-settings-list-types + v-else + :active-list="activeList" + :board-list-type="boardListType" + /> + <board-settings-sidebar-wip-limit :max-issue-count="activeList.maxIssueCount" /> + </template> + </gl-drawer> +</template> diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 056a7b48212..3790c494085 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -3,7 +3,7 @@ import $ from 'jquery'; import Vue from 'vue'; import { GlLabel } from '@gitlab/ui'; -import Flash from '~/flash'; +import { deprecatedCreateFlash as Flash } from '~/flash'; import { sprintf, __ } from '~/locale'; import Sidebar from '~/right_sidebar'; import eventHub from '~/sidebar/event_hub'; diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index dbe3e0790f6..48f6ba6cfc7 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -3,10 +3,10 @@ import { throttle } from 'lodash'; import { GlLoadingIcon, GlSearchBoxByType, - GlDropdown, - GlDropdownDivider, - GlDropdownHeader, - GlDropdownItem, + GlDeprecatedDropdown, + GlDeprecatedDropdownDivider, + GlDeprecatedDropdownHeader, + GlDeprecatedDropdownItem, } from '@gitlab/ui'; import httpStatusCodes from '~/lib/utils/http_status'; @@ -26,10 +26,10 @@ export default { BoardForm, GlLoadingIcon, GlSearchBoxByType, - GlDropdown, - GlDropdownDivider, - GlDropdownHeader, - GlDropdownItem, + GlDeprecatedDropdown, + GlDeprecatedDropdownDivider, + GlDeprecatedDropdownHeader, + GlDeprecatedDropdownItem, }, props: { currentBoard: { @@ -235,7 +235,7 @@ export default { <template> <div class="boards-switcher js-boards-selector gl-mr-3"> <span class="boards-selector-wrapper js-boards-selector-wrapper"> - <gl-dropdown + <gl-deprecated-dropdown data-qa-selector="boards_dropdown" toggle-class="dropdown-menu-toggle js-dropdown-toggle" menu-class="flex-column dropdown-extended-height" @@ -248,9 +248,9 @@ export default { </div> </div> - <gl-dropdown-header class="mt-0"> + <gl-deprecated-dropdown-header class="mt-0"> <gl-search-box-by-type ref="searchBox" v-model="filterTerm" /> - </gl-dropdown-header> + </gl-deprecated-dropdown-header> <div v-if="!loading" @@ -259,26 +259,26 @@ export default { class="dropdown-content flex-fill" @scroll.passive="throttledSetScrollFade" > - <gl-dropdown-item + <gl-deprecated-dropdown-item v-show="filteredBoards.length === 0" class="no-pointer-events text-secondary" > {{ s__('IssueBoards|No matching boards found') }} - </gl-dropdown-item> + </gl-deprecated-dropdown-item> <h6 v-if="showRecentSection" class="dropdown-bold-header my-0"> {{ __('Recent') }} </h6> <template v-if="showRecentSection"> - <gl-dropdown-item + <gl-deprecated-dropdown-item v-for="recentBoard in recentBoards" :key="`recent-${recentBoard.id}`" class="js-dropdown-item" :href="`${boardBaseUrl}/${recentBoard.id}`" > {{ recentBoard.name }} - </gl-dropdown-item> + </gl-deprecated-dropdown-item> </template> <hr v-if="showRecentSection" class="my-1" /> @@ -287,21 +287,21 @@ export default { {{ __('All') }} </h6> - <gl-dropdown-item + <gl-deprecated-dropdown-item v-for="otherBoard in filteredBoards" :key="otherBoard.id" class="js-dropdown-item" :href="`${boardBaseUrl}/${otherBoard.id}`" > {{ otherBoard.name }} - </gl-dropdown-item> - <gl-dropdown-item v-if="hasMissingBoards" class="small unclickable"> + </gl-deprecated-dropdown-item> + <gl-deprecated-dropdown-item v-if="hasMissingBoards" class="small unclickable"> {{ s__( 'IssueBoards|Some of your boards are hidden, activate a license to see them again.', ) }} - </gl-dropdown-item> + </gl-deprecated-dropdown-item> </div> <div @@ -313,25 +313,25 @@ export default { <gl-loading-icon v-if="loading" /> <div v-if="canAdminBoard"> - <gl-dropdown-divider /> + <gl-deprecated-dropdown-divider /> - <gl-dropdown-item + <gl-deprecated-dropdown-item v-if="multipleIssueBoardsAvailable" data-qa-selector="create_new_board_button" @click.prevent="showPage('new')" > {{ s__('IssueBoards|Create new board') }} - </gl-dropdown-item> + </gl-deprecated-dropdown-item> - <gl-dropdown-item + <gl-deprecated-dropdown-item v-if="showDelete" class="text-danger js-delete-board" @click.prevent="showPage('delete')" > {{ s__('IssueBoards|Delete board') }} - </gl-dropdown-item> + </gl-deprecated-dropdown-item> </div> - </gl-dropdown> + </gl-deprecated-dropdown> <board-form v-if="currentPage" diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue index 1d70c635c18..4add5ee646a 100644 --- a/app/assets/javascripts/boards/components/issue_due_date.vue +++ b/app/assets/javascripts/boards/components/issue_due_date.vue @@ -87,11 +87,7 @@ export default { <template> <span> <span ref="issueDueDate" :class="cssClass" class="board-card-info card-number"> - <icon - :class="{ 'text-danger': isPastDue }" - class="board-card-info-icon align-top" - name="calendar" - /> + <icon :class="{ 'text-danger': isPastDue }" class="board-card-info-icon" name="calendar" /> <time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{ body }}</time> diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue index 5c33ba9461c..e8b7689da13 100644 --- a/app/assets/javascripts/boards/components/issue_time_estimate.vue +++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue @@ -34,10 +34,9 @@ export default { <template> <span> <span ref="issueTimeEstimate" class="board-card-info card-number"> - <icon name="hourglass" class="board-card-info-icon align-top" /><time - class="board-card-info-text" - >{{ timeEstimate }}</time - > + <icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{ + timeEstimate + }}</time> </span> <gl-tooltip :target="() => $refs.issueTimeEstimate" diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue index 5f100c617a0..c4953dda793 100644 --- a/app/assets/javascripts/boards/components/modal/footer.vue +++ b/app/assets/javascripts/boards/components/modal/footer.vue @@ -1,6 +1,6 @@ <script> import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer'; -import Flash from '../../../flash'; +import { deprecatedCreateFlash as Flash } from '../../../flash'; import { __, n__ } from '../../../locale'; import ListsDropdown from './lists_dropdown.vue'; import ModalStore from '../../stores/modal_store'; diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue index 8eae8e4726f..573284d2b44 100644 --- a/app/assets/javascripts/boards/components/modal/header.vue +++ b/app/assets/javascripts/boards/components/modal/header.vue @@ -67,7 +67,7 @@ export default { </h2> </header> <modal-tabs v-if="!loading && issuesCount > 0" /> - <div v-if="showSearch" class="d-flex append-bottom-10"> + <div v-if="showSearch" class="d-flex gl-mb-3"> <modal-filters :store="filter" /> <button ref="selectAllBtn" diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue index ed67206218e..a71fda9d7c5 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.vue +++ b/app/assets/javascripts/boards/components/modal/tabs.vue @@ -19,7 +19,7 @@ export default { }; </script> <template> - <div class="top-area prepend-top-10 append-bottom-10"> + <div class="top-area gl-mt-3 gl-mb-3"> <ul class="nav-links issues-state-filters"> <li :class="{ active: activeTab == 'all' }"> <a href="#" role="button" @click.prevent="changeTab('all')"> diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js index 229bb82152b..2b9fdf11b37 100644 --- a/app/assets/javascripts/boards/components/new_list_dropdown.js +++ b/app/assets/javascripts/boards/components/new_list_dropdown.js @@ -3,7 +3,7 @@ import $ from 'jquery'; import { __ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; -import flash from '~/flash'; +import { deprecatedCreateFlash as flash } from '~/flash'; import CreateLabelDropdown from '../../create_label'; import boardsStore from '../stores/boards_store'; diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index 8fd377938b4..598e92726c1 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -64,10 +64,10 @@ export default { this.groupId, term, { - search_namespaces: true, with_issues_enabled: true, with_shared: false, include_subgroups: true, + order_by: 'similarity', ...additionalAttrs, }, projects => { @@ -97,7 +97,7 @@ export default { <template> <div> - <label class="label-bold prepend-top-10">{{ __('Project') }}</label> + <label class="label-bold gl-mt-3">{{ __('Project') }}</label> <div ref="projectsDropdown" class="dropdown dropdown-projects"> <button class="dropdown-menu-toggle wide" diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue index 71e5d8058da..4e5a6609042 100644 --- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue +++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue @@ -1,10 +1,14 @@ <script> +import { GlButton } from '@gitlab/ui'; import axios from '~/lib/utils/axios_utils'; -import Flash from '../../../flash'; +import { deprecatedCreateFlash as Flash } from '../../../flash'; import { __ } from '../../../locale'; import boardsStore from '../../stores/boards_store'; export default { + components: { + GlButton, + }, props: { issue: { type: Object, @@ -75,8 +79,8 @@ export default { </script> <template> <div class="block list"> - <button class="btn btn-default btn-block" type="button" @click="removeIssue"> + <gl-button variant="default" category="secondary" block="block" @click="removeIssue"> {{ __('Remove from board') }} - </button> + </gl-button> </div> </template> diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js index f577a168e75..35c52558cac 100644 --- a/app/assets/javascripts/boards/constants.js +++ b/app/assets/javascripts/boards/constants.js @@ -13,7 +13,7 @@ export const ListType = { blank: 'blank', }; -export const inactiveListId = 0; +export const inactiveId = 0; export default { BoardType, diff --git a/app/assets/javascripts/boards/eventhub.js b/app/assets/javascripts/boards/eventhub.js index 0948c2e5352..e31806ad199 100644 --- a/app/assets/javascripts/boards/eventhub.js +++ b/app/assets/javascripts/boards/eventhub.js @@ -1,3 +1,3 @@ -import Vue from 'vue'; +import createEventHub from '~/helpers/event_hub_factory'; -export default new Vue(); +export default createEventHub(); diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js index ca85e54eb89..b7966dd869d 100644 --- a/app/assets/javascripts/boards/filtered_search_boards.js +++ b/app/assets/javascripts/boards/filtered_search_boards.js @@ -1,6 +1,6 @@ import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; -import FilteredSearchContainer from '../filtered_search/container'; import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager'; +import FilteredSearchContainer from '../filtered_search/container'; import boardsStore from './stores/boards_store'; export default class FilteredSearchBoards extends FilteredSearchManager { @@ -10,6 +10,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager { isGroupDecendent: true, stateFiltersSelector: '.issues-state-filters', isGroup: IS_EE, + useDefaultState: false, filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 5b4a1d262dd..971edd71eec 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -4,7 +4,6 @@ import { mapActions } from 'vuex'; import 'ee_else_ce/boards/models/issue'; import 'ee_else_ce/boards/models/list'; -import BoardContent from '~/boards/components/board_content.vue'; import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar'; import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown'; import boardConfigToggle from 'ee_else_ce/boards/config_toggle'; @@ -19,8 +18,9 @@ import { } from 'ee_else_ce/boards/ee_functions'; import VueApollo from 'vue-apollo'; +import BoardContent from '~/boards/components/board_content.vue'; import createDefaultClient from '~/lib/graphql'; -import Flash from '~/flash'; +import { deprecatedCreateFlash as Flash } from '~/flash'; import { __ } from '~/locale'; import './models/label'; import './models/assignee'; @@ -83,8 +83,7 @@ export default () => { Board: () => import('ee_else_ce/boards/components/board_column.vue'), BoardSidebar, BoardAddIssuesModal, - BoardSettingsSidebar: () => - import('ee_component/boards/components/board_settings_sidebar.vue'), + BoardSettingsSidebar: () => import('~/boards/components/board_settings_sidebar.vue'), }, store, apolloProvider, @@ -118,7 +117,7 @@ export default () => { boardId: this.boardId, fullPath: $boardApp.dataset.fullPath, }; - this.setEndpoints(endpoints); + this.setInitialBoardData({ ...endpoints, boardType: this.parent }); boardsStore.setEndpoints(endpoints); boardsStore.rootPath = this.boardsEndpoint; @@ -190,7 +189,7 @@ export default () => { } }, methods: { - ...mapActions(['setEndpoints']), + ...mapActions(['setInitialBoardData']), updateTokens() { this.filterManager.updateTokens(); }, diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index 2aa92f86125..b8b30c958a9 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,12 +1,11 @@ /* eslint-disable no-underscore-dangle, class-methods-use-this */ - -import ListIssue from 'ee_else_ce/boards/models/issue'; import { __ } from '~/locale'; import ListLabel from './label'; import ListAssignee from './assignee'; -import flash from '~/flash'; +import { deprecatedCreateFlash as flash } from '~/flash'; import boardsStore from '../stores/boards_store'; import ListMilestone from './milestone'; +import 'ee_else_ce/boards/models/issue'; const TYPES = { backlog: { @@ -61,7 +60,9 @@ class List { this.title = this.milestone.title; } - if (!typeInfo.isBlank && this.id) { + // doNotFetchIssues is a temporary workaround until issues are fetched using GraphQL on issue boards + // Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/229416 + if (!typeInfo.isBlank && this.id && !obj.doNotFetchIssues) { this.getIssues().catch(() => { // TODO: handle request error }); @@ -100,12 +101,6 @@ class List { return boardsStore.newListIssue(this, issue); } - createIssues(data) { - data.forEach(issueObj => { - this.addIssue(new ListIssue(issueObj)); - }); - } - addMultipleIssues(issues, listFrom, newIndex) { boardsStore.addMultipleListIssues(this, issues, listFrom, newIndex); } diff --git a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql index 5b532906f6a..8abd79332fb 100644 --- a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql +++ b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql @@ -4,6 +4,7 @@ fragment BoardListShared on BoardList { position listType collapsed + maxIssueCount label { id title diff --git a/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql b/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql new file mode 100644 index 00000000000..724c7884c58 --- /dev/null +++ b/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql @@ -0,0 +1,18 @@ +#import "./issue.fragment.graphql" + +query GroupListIssues($fullPath: ID!, $boardId: ID!) { + group(fullPath: $fullPath) { + board(id: $boardId) { + lists { + nodes { + id + issues { + nodes { + ...IssueNode + } + } + } + } + } + } +} diff --git a/app/assets/javascripts/boards/queries/issue.fragment.graphql b/app/assets/javascripts/boards/queries/issue.fragment.graphql new file mode 100644 index 00000000000..89d56b895a4 --- /dev/null +++ b/app/assets/javascripts/boards/queries/issue.fragment.graphql @@ -0,0 +1,31 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + +fragment IssueNode on Issue { + id + iid + title + referencePath: reference(full: true) + dueDate + timeEstimate + weight + confidential + webUrl + subscribed + blocked + epic { + id + } + assignees { + nodes { + ...User + } + } + labels { + nodes { + id + title + color + description + } + } +} diff --git a/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql b/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql new file mode 100644 index 00000000000..149b76848ef --- /dev/null +++ b/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql @@ -0,0 +1,18 @@ +#import "./issue.fragment.graphql" + +query ProjectListIssues($fullPath: ID!, $boardId: ID!) { + project(fullPath: $fullPath) { + board(id: $boardId) { + lists { + nodes { + id + issues { + nodes { + ...IssueNode + } + } + } + } + } + } +} diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 08fedb14dff..b4be7546252 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -1,4 +1,11 @@ import * as types from './mutation_types'; +import createDefaultClient from '~/lib/graphql'; +import { BoardType } from '~/boards/constants'; +import { formatListIssues } from '../boards_util'; +import groupListsIssuesQuery from '../queries/group_lists_issues.query.graphql'; +import projectListsIssuesQuery from '../queries/project_lists_issues.query.graphql'; + +const gqlClient = createDefaultClient(); const notImplemented = () => { /* eslint-disable-next-line @gitlab/require-i18n-strings */ @@ -6,8 +13,12 @@ const notImplemented = () => { }; export default { - setEndpoints: ({ commit }, endpoints) => { - commit(types.SET_ENDPOINTS, endpoints); + setInitialBoardData: ({ commit }, data) => { + commit(types.SET_INITIAL_BOARD_DATA, data); + }, + + setActiveId({ commit }, id) { + commit(types.SET_ACTIVE_ID, id); }, fetchLists: () => { @@ -34,6 +45,32 @@ export default { notImplemented(); }, + fetchIssuesForAllLists: ({ state, commit }) => { + commit(types.REQUEST_ISSUES_FOR_ALL_LISTS); + + const { endpoints, boardType } = state; + const { fullPath, boardId } = endpoints; + + const query = boardType === BoardType.group ? groupListsIssuesQuery : projectListsIssuesQuery; + + const variables = { + fullPath, + boardId: `gid://gitlab/Board/${boardId}`, + }; + + return gqlClient + .query({ + query, + 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)); + }, + moveIssue: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index da7d2e19ec1..30c71d64085 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -1,6 +1,6 @@ /* eslint-disable no-shadow, no-param-reassign,consistent-return */ /* global List */ - +/* global ListIssue */ import $ from 'jquery'; import { sortBy } from 'lodash'; import Vue from 'vue'; @@ -81,7 +81,7 @@ const boardsStore = { showPage(page) { this.state.currentPage = page; }, - addList(listObj) { + updateListPosition(listObj) { const listType = listObj.listType || listObj.list_type; let { position } = listObj; if (listType === ListType.closed) { @@ -91,6 +91,10 @@ const boardsStore = { } const list = new List({ ...listObj, position }); + return list; + }, + addList(listObj) { + const list = this.updateListPosition(listObj); this.state.lists = sortBy([...this.state.lists, list], 'position'); return list; }, @@ -641,7 +645,9 @@ const boardsStore = { list.issues = []; } - list.createIssues(data.issues); + data.issues.forEach(issueObj => { + list.addIssue(new ListIssue(issueObj)); + }); return data; }); @@ -848,19 +854,28 @@ const boardsStore = { }, refreshIssueData(issue, obj) { - issue.id = obj.id; - issue.iid = obj.iid; - issue.title = obj.title; - issue.confidential = obj.confidential; - issue.dueDate = obj.due_date; - issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; - issue.referencePath = obj.reference_path; - issue.path = obj.real_path; - issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; + // issue.id = obj.id; + // issue.iid = obj.iid; + // issue.title = obj.title; + // issue.confidential = obj.confidential; + // issue.dueDate = obj.due_date || obj.dueDate; + // issue.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; + // issue.referencePath = obj.reference_path || obj.referencePath; + // issue.path = obj.real_path || obj.webUrl; + // issue.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint; + // issue.project_id = obj.project_id; + // issue.timeEstimate = obj.time_estimate || obj.timeEstimate; + // issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint; + // issue.blocked = obj.blocked; + // issue.epic = obj.epic; + + const convertedObj = convertObjectPropsToCamelCase(obj, { + dropKeys: ['issue_sidebar_endpoint', 'real_path', 'webUrl'], + }); + convertedObj.sidebarInfoEndpoint = obj.issue_sidebar_endpoint; + issue.path = obj.real_path || obj.webUrl; issue.project_id = obj.project_id; - issue.timeEstimate = obj.time_estimate; - issue.assignableLabelsEndpoint = obj.assignable_labels_endpoint; - issue.blocked = obj.blocked; + Object.assign(issue, convertedObj); if (obj.project) { issue.project = new IssueProject(obj.project); diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index fcdfa6799b6..0f96dc2e287 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -1,4 +1,4 @@ -export const SET_ENDPOINTS = 'SET_ENDPOINTS'; +export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA'; export const REQUEST_ADD_LIST = 'REQUEST_ADD_LIST'; export const RECEIVE_ADD_LIST_SUCCESS = 'RECEIVE_ADD_LIST_SUCCESS'; export const RECEIVE_ADD_LIST_ERROR = 'RECEIVE_ADD_LIST_ERROR'; @@ -8,6 +8,9 @@ export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR'; 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 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'; @@ -19,3 +22,4 @@ export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS'; export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR'; export const SET_CURRENT_PAGE = 'SET_CURRENT_PAGE'; export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE'; +export const SET_ACTIVE_ID = 'SET_ACTIVE_ID'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index e4459cdcc07..ca9b911ce5b 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -6,8 +6,14 @@ const notImplemented = () => { }; export default { - [mutationTypes.SET_ENDPOINTS]: (state, endpoints) => { + [mutationTypes.SET_INITIAL_BOARD_DATA]: (state, data) => { + const { boardType, ...endpoints } = data; state.endpoints = endpoints; + state.boardType = boardType; + }, + + [mutationTypes.SET_ACTIVE_ID](state, id) { + state.activeId = id; }, [mutationTypes.REQUEST_ADD_LIST]: () => { @@ -46,6 +52,20 @@ export default { notImplemented(); }, + [mutationTypes.REQUEST_ISSUES_FOR_ALL_LISTS]: state => { + state.isLoadingIssues = true; + }, + + [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, listIssues) => { + state.issuesByListId = listIssues; + state.isLoadingIssues = false; + }, + + [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => { + state.listIssueFetchFailure = true; + state.isLoadingIssues = false; + }, + [mutationTypes.REQUEST_ADD_ISSUE]: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index aca93c4d7c6..cb6930774ed 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -1,7 +1,11 @@ -import { inactiveListId } from '~/boards/constants'; +import { inactiveId } from '~/boards/constants'; export default () => ({ endpoints: {}, + boardType: null, isShowingLabels: true, - activeListId: inactiveListId, + activeId: inactiveId, + issuesByListId: {}, + isLoadingIssues: false, + listIssueFetchFailure: false, }); |