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>2020-09-19 04:45:44 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 04:45:44 +0300
commit85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch)
tree9160f299afd8c80c038f08e1545be119f5e3f1e1 /app/assets/javascripts/boards
parent15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff)
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'app/assets/javascripts/boards')
-rw-r--r--app/assets/javascripts/boards/boards_util.js64
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue18
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue112
-rw-r--r--app/assets/javascripts/boards/components/board_card_layout.vue93
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue63
-rw-r--r--app/assets/javascripts/boards/components/board_content.vue70
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue27
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue60
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue47
-rw-r--r--app/assets/javascripts/boards/components/board_settings_sidebar.vue30
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js4
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue10
-rw-r--r--app/assets/javascripts/boards/components/issuable_title.vue21
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue24
-rw-r--r--app/assets/javascripts/boards/components/issue_due_date.vue7
-rw-r--r--app/assets/javascripts/boards/components/issue_time_estimate.vue7
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue24
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue10
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue26
-rw-r--r--app/assets/javascripts/boards/components/modal/index.vue25
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue16
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue7
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js3
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue22
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_editable_item.vue79
-rw-r--r--app/assets/javascripts/boards/constants.js3
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js5
-rw-r--r--app/assets/javascripts/boards/index.js82
-rw-r--r--app/assets/javascripts/boards/models/issue.js2
-rw-r--r--app/assets/javascripts/boards/models/list.js2
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js2
-rw-r--r--app/assets/javascripts/boards/queries/board_list_create.mutation.graphql10
-rw-r--r--app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/boards/queries/board_list_update.mutation.graphql10
-rw-r--r--app/assets/javascripts/boards/queries/group_lists_issues.query.graphql18
-rw-r--r--app/assets/javascripts/boards/queries/issue.fragment.graphql6
-rw-r--r--app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql28
-rw-r--r--app/assets/javascripts/boards/queries/lists_issues.query.graphql39
-rw-r--r--app/assets/javascripts/boards/queries/project_lists_issues.query.graphql18
-rw-r--r--app/assets/javascripts/boards/stores/actions.js270
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js34
-rw-r--r--app/assets/javascripts/boards/stores/getters.js22
-rw-r--r--app/assets/javascripts/boards/stores/mutation_types.js21
-rw-r--r--app/assets/javascripts/boards/stores/mutations.js135
-rw-r--r--app/assets/javascripts/boards/stores/state.js10
46 files changed, 1084 insertions, 510 deletions
diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js
index 384a386d69c..5c8df94ca90 100644
--- a/app/assets/javascripts/boards/boards_util.js
+++ b/app/assets/javascripts/boards/boards_util.js
@@ -1,28 +1,72 @@
+import { sortBy } from 'lodash';
import ListIssue from 'ee_else_ce/boards/models/issue';
+import { ListType } from './constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
export function getMilestone() {
return null;
}
+export function formatIssue(issue) {
+ return new ListIssue({
+ ...issue,
+ labels: issue.labels?.nodes || [],
+ assignees: issue.assignees?.nodes || [],
+ });
+}
+
export function formatListIssues(listIssues) {
- return listIssues.nodes.reduce((map, list) => {
+ const issues = {};
+
+ const listData = listIssues.nodes.reduce((map, list) => {
+ const sortedIssues = sortBy(list.issues.nodes, 'relativePosition');
return {
...map,
- [list.id]: list.issues.nodes.map(
- i =>
- new ListIssue({
- ...i,
- id: getIdFromGraphQLId(i.id),
- labels: i.labels?.nodes || [],
- assignees: i.assignees?.nodes || [],
- }),
- ),
+ [list.id]: sortedIssues.map(i => {
+ const id = getIdFromGraphQLId(i.id);
+
+ const listIssue = new ListIssue({
+ ...i,
+ id,
+ labels: i.labels?.nodes || [],
+ assignees: i.assignees?.nodes || [],
+ });
+
+ issues[id] = listIssue;
+
+ return id;
+ }),
};
}, {});
+
+ return { listData, issues };
+}
+
+export function fullBoardId(boardId) {
+ return `gid://gitlab/Board/${boardId}`;
+}
+
+export function moveIssueListHelper(issue, fromList, toList) {
+ if (toList.type === ListType.label) {
+ issue.addLabel(toList.label);
+ }
+ if (fromList && fromList.type === ListType.label) {
+ issue.removeLabel(fromList.label);
+ }
+
+ if (toList.type === ListType.assignee) {
+ issue.addAssignee(toList.assignee);
+ }
+ if (fromList && fromList.type === ListType.assignee) {
+ issue.removeAssignee(fromList.assignee);
+ }
+
+ return issue;
}
export default {
getMilestone,
+ formatIssue,
formatListIssues,
+ fullBoardId,
};
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index afdf0290e8e..55e3e4a6329 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,10 +1,14 @@
<script>
+import { GlButton } from '@gitlab/ui';
import Cookies from 'js-cookie';
import { __ } from '~/locale';
import ListLabel from '~/boards/models/label';
import boardsStore from '../stores/boards_store';
export default {
+ components: {
+ GlButton,
+ },
data() {
return {
predefinedLabels: [
@@ -84,15 +88,17 @@ export default {
)
}}
</p>
- <button
- class="btn btn-success btn-inverted btn-block"
- type="button"
+ <gl-button
+ category="secondary"
+ variant="success"
+ block="block"
+ class="gl-mb-0"
@click.stop="addDefaultLists"
>
{{ s__('BoardBlankState|Add default lists') }}
- </button>
- <button class="btn btn-default btn-block" type="button" @click.stop="clearBlankState">
+ </gl-button>
+ <gl-button category="secondary" variant="default" block="block" @click.stop="clearBlankState">
{{ s__("BoardBlankState|Nevermind, I'll use my own") }}
- </button>
+ </gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 246d3b9dcd1..31050eef83d 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -1,6 +1,5 @@
<script>
-/* eslint-disable vue/require-default-prop */
-import IssueCardInner from './issue_card_inner.vue';
+import BoardCardLayout from './board_card_layout.vue';
import eventHub from '../eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
import boardsStore from '../stores/boards_store';
@@ -8,7 +7,7 @@ import boardsStore from '../stores/boards_store';
export default {
name: 'BoardsIssueCard',
components: {
- IssueCardInner,
+ BoardCardLayout,
},
props: {
list: {
@@ -21,80 +20,29 @@ export default {
default: () => ({}),
required: false,
},
- issueLinkBase: {
- type: String,
- default: '',
- required: false,
- },
- disabled: {
- type: Boolean,
- default: false,
- required: false,
- },
- index: {
- type: Number,
- default: 0,
- required: false,
- },
- rootPath: {
- type: String,
- default: '',
- required: false,
- },
- groupId: {
- type: Number,
- required: false,
- },
- },
- data() {
- return {
- showDetail: false,
- detailIssue: boardsStore.detail,
- multiSelect: boardsStore.multiSelect,
- };
- },
- computed: {
- issueDetailVisible() {
- return this.detailIssue.issue && this.detailIssue.issue.id === this.issue.id;
- },
- multiSelectVisible() {
- return this.multiSelect.list.findIndex(issue => issue.id === this.issue.id) > -1;
- },
- canMultiSelect() {
- return gon.features && gon.features.multiSelectBoard;
- },
},
methods: {
- mouseDown() {
- this.showDetail = true;
+ // These are methods instead of computed's, because boardsStore is not reactive.
+ isActive() {
+ return this.getActiveId() === this.issue.id;
},
- mouseMove() {
- this.showDetail = false;
+ getActiveId() {
+ return boardsStore.detail?.issue?.id;
},
- showIssue(e) {
- if (e.target.classList.contains('js-no-trigger')) return;
-
+ showIssue({ isMultiSelect }) {
// If no issues are opened, close all sidebars first
- if (!boardsStore.detail?.issue?.id) {
+ if (!this.getActiveId()) {
sidebarEventHub.$emit('sidebar.closeAll');
}
+ if (this.isActive()) {
+ eventHub.$emit('clearDetailIssue', isMultiSelect);
- // If CMD or CTRL is clicked
- const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
-
- if (this.showDetail || isMultiSelect) {
- this.showDetail = false;
-
- if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
- eventHub.$emit('clearDetailIssue', isMultiSelect);
-
- if (isMultiSelect) {
- eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
- }
- } else {
+ if (isMultiSelect) {
eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
- boardsStore.setListDetail(this.list);
}
+ } else {
+ eventHub.$emit('newDetailIssue', this.issue, isMultiSelect);
+ boardsStore.setListDetail(this.list);
}
},
},
@@ -102,28 +50,12 @@ export default {
</script>
<template>
- <li
- :class="{
- 'multi-select': multiSelectVisible,
- 'user-can-drag': !disabled && issue.id,
- 'is-disabled': disabled || !issue.id,
- 'is-active': issueDetailVisible,
- }"
- :index="index"
- :data-issue-id="issue.id"
+ <board-card-layout
data-qa-selector="board_card"
- class="board-card p-3 rounded"
- @mousedown="mouseDown"
- @mousemove="mouseMove"
- @mouseup="showIssue($event)"
- >
- <issue-card-inner
- :list="list"
- :issue="issue"
- :issue-link-base="issueLinkBase"
- :group-id="groupId"
- :root-path="rootPath"
- :update-filters="true"
- />
- </li>
+ :issue="issue"
+ :list="list"
+ :is-active="isActive()"
+ v-bind="$attrs"
+ @show="showIssue"
+ />
</template>
diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue
new file mode 100644
index 00000000000..072dd87861a
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_card_layout.vue
@@ -0,0 +1,93 @@
+<script>
+import IssueCardInner from './issue_card_inner.vue';
+import boardsStore from '../stores/boards_store';
+
+export default {
+ name: 'BoardsIssueCard',
+ components: {
+ IssueCardInner,
+ },
+ props: {
+ list: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
+ issue: {
+ type: Object,
+ default: () => ({}),
+ required: false,
+ },
+ disabled: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ index: {
+ type: Number,
+ default: 0,
+ required: false,
+ },
+ isActive: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ showDetail: false,
+ multiSelect: boardsStore.multiSelect,
+ };
+ },
+ computed: {
+ multiSelectVisible() {
+ return this.multiSelect.list.findIndex(issue => issue.id === this.issue.id) > -1;
+ },
+ canMultiSelect() {
+ return gon.features && gon.features.multiSelectBoard;
+ },
+ },
+ methods: {
+ mouseDown() {
+ this.showDetail = true;
+ },
+ mouseMove() {
+ this.showDetail = false;
+ },
+ showIssue(e) {
+ // Don't do anything if this happened on a no trigger element
+ if (e.target.classList.contains('js-no-trigger')) return;
+
+ const isMultiSelect = this.canMultiSelect && (e.ctrlKey || e.metaKey);
+
+ if (this.showDetail || isMultiSelect) {
+ this.showDetail = false;
+ this.$emit('show', { event: e, isMultiSelect });
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <li
+ :class="{
+ 'multi-select': multiSelectVisible,
+ 'user-can-drag': !disabled && issue.id,
+ 'is-disabled': disabled || !issue.id,
+ 'is-active': isActive,
+ }"
+ :index="index"
+ :data-issue-id="issue.id"
+ :data-issue-iid="issue.iid"
+ :data-issue-path="issue.referencePath"
+ data-testid="board_card"
+ class="board-card p-3 rounded"
+ @mousedown="mouseDown"
+ @mousemove="mouseMove"
+ @mouseup="showIssue($event)"
+ >
+ <issue-card-inner :list="list" :issue="issue" :update-filters="true" />
+ </li>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index dae24338e45..6d216911798 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -1,9 +1,11 @@
<script>
+import { mapGetters, mapActions } from 'vuex';
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 glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import BoardBlankState from './board_blank_state.vue';
import BoardList from './board_list.vue';
import boardsStore from '../stores/boards_store';
@@ -21,7 +23,7 @@ export default {
directives: {
Tooltip,
},
- mixins: [isWipLimitsOn],
+ mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: {
list: {
type: Object,
@@ -32,27 +34,15 @@ export default {
type: Boolean,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- boardId: {
- type: String,
- required: true,
- },
canAdminList: {
type: Boolean,
required: false,
default: false,
},
- groupId: {
- type: Number,
- required: false,
- default: null,
+ },
+ inject: {
+ boardId: {
+ type: String,
},
},
data() {
@@ -62,6 +52,7 @@ export default {
};
},
computed: {
+ ...mapGetters(['getIssues']),
showBoardListAndBoardInfo() {
return this.list.type !== ListType.blank && this.list.type !== ListType.promotion;
},
@@ -69,19 +60,36 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
+ listIssues() {
+ if (!this.glFeatures.graphqlBoardLists) {
+ return this.list.issues;
+ }
+ return this.getIssues(this.list.id);
+ },
+ shouldFetchIssues() {
+ return this.glFeatures.graphqlBoardLists && this.list.type !== ListType.blank;
+ },
},
watch: {
filter: {
handler() {
- this.list.page = 1;
- this.list.getIssues(true).catch(() => {
- // TODO: handle request error
- });
+ if (this.shouldFetchIssues) {
+ this.fetchIssuesForList(this.list.id);
+ } else {
+ this.list.page = 1;
+ this.list.getIssues(true).catch(() => {
+ // TODO: handle request error
+ });
+ }
},
deep: true,
},
},
mounted() {
+ if (this.shouldFetchIssues) {
+ this.fetchIssuesForList(this.list.id);
+ }
+
const instance = this;
const sortableOptions = getBoardSortableDefaultOptions({
@@ -108,6 +116,7 @@ export default {
Sortable.create(this.$el.parentNode, sortableOptions);
},
methods: {
+ ...mapActions(['fetchIssuesForList']),
showListNewIssueForm(listId) {
eventHub.$emit('showForm', listId);
},
@@ -130,22 +139,14 @@ export default {
<div
class="board-inner gl-display-flex gl-flex-direction-column gl-relative gl-h-full gl-rounded-base"
>
- <board-list-header
- :can-admin-list="canAdminList"
- :list="list"
- :disabled="disabled"
- :board-id="boardId"
- />
+ <board-list-header :can-admin-list="canAdminList" :list="list" :disabled="disabled" />
<board-list
v-if="showBoardListAndBoardInfo"
ref="board-list"
:disabled="disabled"
- :group-id="groupId || null"
- :issue-link-base="issueLinkBase"
- :issues="list.issues"
+ :issues="listIssues"
:list="list"
:loading="list.loading"
- :root-path="rootPath"
/>
<board-blank-state v-if="canAdminList && list.id === 'blank'" />
diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue
index c42295792f1..c7b3da0e672 100644
--- a/app/assets/javascripts/boards/components/board_content.vue
+++ b/app/assets/javascripts/boards/components/board_content.vue
@@ -1,13 +1,15 @@
<script>
-import { mapState } from 'vuex';
+import { mapState, mapGetters, mapActions } from 'vuex';
import BoardColumn from 'ee_else_ce/boards/components/board_column.vue';
-import EpicsSwimlanes from 'ee_component/boards/components/epics_swimlanes.vue';
+import { GlAlert } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
BoardColumn,
- EpicsSwimlanes,
+ BoardContentSidebar: () => import('ee_component/boards/components/board_content_sidebar.vue'),
+ EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'),
+ GlAlert,
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -19,66 +21,58 @@ export default {
type: Boolean,
required: true,
},
- groupId: {
- type: Number,
- required: false,
- default: null,
- },
disabled: {
type: Boolean,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
- boardId: {
- type: String,
- required: true,
- },
},
computed: {
- ...mapState(['isShowingEpicsSwimlanes', 'boardLists']),
- isSwimlanesOn() {
- return this.glFeatures.boardsWithSwimlanes && this.isShowingEpicsSwimlanes;
+ ...mapState(['boardLists', 'error']),
+ ...mapGetters(['isSwimlanesOn']),
+ boardListsToUse() {
+ return this.glFeatures.graphqlBoardLists ? this.boardLists : this.lists;
},
},
+ mounted() {
+ if (this.glFeatures.graphqlBoardLists) {
+ this.fetchLists();
+ this.showPromotionList();
+ }
+ },
+ methods: {
+ ...mapActions(['fetchLists', 'showPromotionList']),
+ },
};
</script>
<template>
<div>
+ <gl-alert v-if="error" variant="danger" :dismissible="false">
+ {{ error }}
+ </gl-alert>
<div
v-if="!isSwimlanesOn"
class="boards-list gl-w-full gl-py-5 gl-px-3 gl-white-space-nowrap"
data-qa-selector="boards_list"
>
<board-column
- v-for="list in lists"
+ v-for="list in boardListsToUse"
:key="list.id"
ref="board"
:can-admin-list="canAdminList"
- :group-id="groupId"
:list="list"
:disabled="disabled"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath"
- :board-id="boardId"
/>
</div>
- <epics-swimlanes
- v-else
- ref="swimlanes"
- :lists="boardLists"
- :can-admin-list="canAdminList"
- :disabled="disabled"
- :board-id="boardId"
- :group-id="groupId"
- :root-path="rootPath"
- />
+
+ <template v-else>
+ <epics-swimlanes
+ ref="swimlanes"
+ :lists="boardLists"
+ :can-admin-list="canAdminList"
+ :disabled="disabled"
+ />
+ <board-content-sidebar />
+ </template>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 231059b895e..385dd5fdc71 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -25,11 +25,11 @@ export default {
type: Boolean,
required: true,
},
- milestonePath: {
+ labelsPath: {
type: String,
required: true,
},
- labelsPath: {
+ labelsWebUrl: {
type: String,
required: true,
},
@@ -201,8 +201,8 @@ export default {
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
- :milestone-path="milestonePath"
:labels-path="labelsPath"
+ :labels-web-url="labelsWebUrl"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
:group-id="groupId"
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 1a26782f6f0..25f8ffca633 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -6,6 +6,7 @@ import boardCard from './board_card.vue';
import eventHub from '../eventhub';
import boardsStore from '../stores/boards_store';
import { sprintf, __ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import {
getBoardSortableDefaultOptions,
@@ -24,12 +25,8 @@ export default {
boardNewIssue,
GlLoadingIcon,
},
+ mixins: [glFeatureFlagMixin()],
props: {
- groupId: {
- type: Number,
- required: false,
- default: 0,
- },
disabled: {
type: Boolean,
required: true,
@@ -46,14 +43,6 @@ export default {
type: Boolean,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
},
data() {
return {
@@ -83,6 +72,7 @@ export default {
deep: true,
},
issues() {
+ if (this.glFeatures.graphqlBoardLists) return;
this.$nextTick(() => {
if (
this.scrollHeight() <= this.listHeight() &&
@@ -413,6 +403,8 @@ export default {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
+ if (this.glFeatures.graphqlBoardLists) return;
+
if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) {
this.loadNextPage();
}
@@ -430,11 +422,7 @@ export default {
<div v-if="loading" class="board-list-loading text-center" :aria-label="__('Loading issues')">
<gl-loading-icon />
</div>
- <board-new-issue
- v-if="list.type !== 'closed' && showIssueForm"
- :group-id="groupId"
- :list="list"
- />
+ <board-new-issue v-if="list.type !== 'closed' && showIssueForm" :list="list" />
<ul
v-show="!loading"
ref="list"
@@ -450,9 +438,6 @@ export default {
:index="index"
:list="list"
:issue="issue"
- :issue-link-base="issueLinkBase"
- :group-id="groupId"
- :root-path="rootPath"
:disabled="disabled"
/>
<li v-if="showCount" class="board-list-count text-center" data-issue-id="-1">
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index bafe07afb48..361fe252afb 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions } from 'vuex';
import {
GlButton,
GlButtonGroup,
@@ -17,6 +18,7 @@ import boardsStore from '../stores/boards_store';
import eventHub from '../eventhub';
import { ListType } from '../constants';
import { isScopedLabel } from '~/lib/utils/common_utils';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -32,7 +34,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [isWipLimitsOn],
+ mixins: [isWipLimitsOn, glFeatureFlagMixin()],
props: {
list: {
type: Object,
@@ -43,10 +45,6 @@ export default {
type: Boolean,
required: true,
},
- boardId: {
- type: String,
- required: true,
- },
canAdminList: {
type: Boolean,
required: false,
@@ -58,6 +56,11 @@ export default {
default: false,
},
},
+ inject: {
+ boardId: {
+ type: String,
+ },
+ },
data() {
return {
weightFeatureAvailable: false,
@@ -94,10 +97,11 @@ export default {
showAssigneeListDetails() {
return this.list.type === 'assignee' && (this.list.isExpanded || !this.isSwimlanesHeader);
},
+ issuesCount() {
+ return this.list.issuesSize;
+ },
issuesTooltipLabel() {
- const { issuesSize } = this.list;
-
- return n__(`%d issue`, `%d issues`, issuesSize);
+ return n__(`%d issue`, `%d issues`, this.issuesCount);
},
chevronTooltip() {
return this.list.isExpanded ? s__('Boards|Collapse') : s__('Boards|Expand');
@@ -126,8 +130,12 @@ export default {
collapsedTooltipTitle() {
return this.listTitle || this.listAssignee;
},
+ shouldDisplaySwimlanes() {
+ return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn;
+ },
},
methods: {
+ ...mapActions(['updateList']),
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
@@ -136,20 +144,28 @@ export default {
eventHub.$emit(`toggle-issue-form-${this.list.id}`);
},
toggleExpanded() {
- if (this.list.isExpandable) {
- this.list.isExpanded = !this.list.isExpanded;
-
- if (AccessorUtilities.isLocalStorageAccessSafe() && !this.isLoggedIn) {
- localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
- }
+ this.list.isExpanded = !this.list.isExpanded;
- if (this.isLoggedIn) {
- this.list.update();
- }
+ if (!this.isLoggedIn) {
+ this.addToLocalStorage();
+ } else {
+ this.updateListFunction();
+ }
- // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
- // Close all tooltips manually to prevent dangling tooltips.
- this.$root.$emit('bv::hide::tooltip');
+ // When expanding/collapsing, the tooltip on the caret button sometimes stays open.
+ // Close all tooltips manually to prevent dangling tooltips.
+ this.$root.$emit('bv::hide::tooltip');
+ },
+ addToLocalStorage() {
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ localStorage.setItem(`${this.uniqueKey}.expanded`, this.list.isExpanded);
+ }
+ },
+ updateListFunction() {
+ if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
+ this.updateList({ listId: this.list.id, collapsed: !this.list.isExpanded });
+ } else {
+ this.list.update();
}
},
},
@@ -172,7 +188,7 @@ export default {
<h3
:class="{
'user-can-drag': !disabled && !list.preset,
- 'gl-py-3': !list.isExpanded && !isSwimlanesHeader,
+ 'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
}"
@@ -288,7 +304,7 @@ export default {
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
<span ref="issueCount" class="issue-count-badge-count">
<gl-icon class="gl-mr-2" name="issues" />
- <issue-count :issues-size="list.issuesSize" :max-issue-count="list.maxIssueCount" />
+ <issue-count :issues-size="issuesCount" :max-issue-count="list.maxIssueCount" />
</span>
<!-- The following is only true in EE. -->
<template v-if="weightFeatureAvailable">
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 34e8438ba4c..348d485ff37 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -1,11 +1,13 @@
<script>
import $ from 'jquery';
+import { mapActions, mapGetters } from 'vuex';
import { GlButton } from '@gitlab/ui';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import boardsStore from '../stores/boards_store';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'BoardNewIssue',
@@ -13,17 +15,18 @@ export default {
ProjectSelect,
GlButton,
},
+ mixins: [glFeatureFlagMixin()],
props: {
- groupId: {
- type: Number,
- required: false,
- default: 0,
- },
list: {
type: Object,
required: true,
},
},
+ inject: {
+ groupId: {
+ type: Number,
+ },
+ },
data() {
return {
title: '',
@@ -32,18 +35,23 @@ export default {
};
},
computed: {
+ ...mapGetters(['isSwimlanesOn']),
disabled() {
if (this.groupId) {
return this.title === '' || !this.selectedProject.name;
}
return this.title === '';
},
+ shouldDisplaySwimlanes() {
+ return this.glFeatures.boardsWithSwimlanes && this.isSwimlanesOn;
+ },
},
mounted() {
this.$refs.input.focus();
eventHub.$on('setSelectedProject', this.setSelectedProject);
},
methods: {
+ ...mapActions(['addListIssue', 'addListIssueFailure']),
submit(e) {
e.preventDefault();
if (this.title.trim() === '') return Promise.resolve();
@@ -70,21 +78,31 @@ export default {
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
+ if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
+ this.addListIssue({ list: this.list, issue, position: 0 });
+ }
+
return this.list
.newIssue(issue)
.then(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
- boardsStore.setIssueDetail(issue);
- boardsStore.setListDetail(this.list);
+ if (!this.shouldDisplaySwimlanes && !this.glFeatures.graphqlBoardLists) {
+ boardsStore.setIssueDetail(issue);
+ boardsStore.setListDetail(this.list);
+ }
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
// Remove the issue
- this.list.removeIssue(issue);
+ if (this.shouldDisplaySwimlanes || this.glFeatures.graphqlBoardLists) {
+ this.addListIssueFailure({ list: this.list, issue });
+ } else {
+ this.list.removeIssue(issue);
+ }
// Show error message
this.error = true;
@@ -121,7 +139,7 @@ export default {
<project-select v-if="groupId" :group-id="groupId" :list="list" />
<div class="clearfix gl-mt-3">
<gl-button
- ref="submit-button"
+ ref="submitButton"
:disabled="disabled"
class="float-left"
variant="success"
@@ -129,9 +147,14 @@ export default {
type="submit"
>{{ __('Submit issue') }}</gl-button
>
- <gl-button class="float-right" type="button" variant="default" @click="cancel">{{
- __('Cancel')
- }}</gl-button>
+ <gl-button
+ ref="cancelButton"
+ class="float-right"
+ type="button"
+ variant="default"
+ @click="cancel"
+ >{{ __('Cancel') }}</gl-button
+ >
</div>
</form>
</div>
diff --git a/app/assets/javascripts/boards/components/board_settings_sidebar.vue b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
index 3149762ecdf..e2600883e89 100644
--- a/app/assets/javascripts/boards/components/board_settings_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_settings_sidebar.vue
@@ -1,11 +1,12 @@
<script>
import { GlDrawer, GlLabel } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
+import { mapActions, mapState, mapGetters } 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';
+import { LIST } from '~/boards/constants';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
// NOTE: need to revisit how we handle headerHeight, because we have so many different header and footer options.
export default {
@@ -23,18 +24,20 @@ export default {
BoardSettingsListTypes: () =>
import('ee_component/boards/components/board_settings_list_types.vue'),
},
+ mixins: [glFeatureFlagMixin()],
computed: {
- ...mapState(['activeId']),
+ ...mapGetters(['isSidebarOpen']),
+ ...mapState(['activeId', 'sidebarType', 'boardLists']),
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
*/
+ if (this.glFeatures.graphqlBoardLists) {
+ return this.boardLists.find(({ id }) => id === this.activeId);
+ }
return boardsStore.state.lists.find(({ id }) => id === this.activeId);
},
- isSidebarOpen() {
- return this.activeId !== inactiveId;
- },
activeListLabel() {
return this.activeList.label;
},
@@ -44,18 +47,18 @@ export default {
listTypeTitle() {
return this.$options.labelListText;
},
+ showSidebar() {
+ return this.sidebarType === LIST;
+ },
},
created() {
- eventHub.$on('sidebar.closeAll', this.closeSidebar);
+ eventHub.$on('sidebar.closeAll', this.unsetActiveId);
},
beforeDestroy() {
- eventHub.$off('sidebar.closeAll', this.closeSidebar);
+ eventHub.$off('sidebar.closeAll', this.unsetActiveId);
},
methods: {
- ...mapActions(['setActiveId']),
- closeSidebar() {
- this.setActiveId(inactiveId);
- },
+ ...mapActions(['unsetActiveId']),
showScopedLabels(label) {
return boardsStore.scopedLabels.enabled && isScopedLabel(label);
},
@@ -65,10 +68,11 @@ export default {
<template>
<gl-drawer
+ v-if="showSidebar"
class="js-board-settings-sidebar"
:open="isSidebarOpen"
:header-height="$options.headerHeight"
- @close="closeSidebar"
+ @close="unsetActiveId"
>
<template #header>{{ $options.listSettingsText }}</template>
<template v-if="isSidebarOpen">
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 3790c494085..d26f15c1723 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -83,7 +83,7 @@ export default Vue.extend({
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el)
- .data('glDropdown')
+ .data('deprecatedJQueryDropdown')
.clearMenu();
});
}
@@ -95,7 +95,7 @@ export default Vue.extend({
},
},
created() {
- // Get events from glDropdown
+ // Get events from deprecatedJQueryDropdown
eventHub.$on('sidebar.removeAssignee', this.removeAssignee);
eventHub.$on('sidebar.addAssignee', this.addAssignee);
eventHub.$on('sidebar.removeAllAssignees', this.removeAllAssignees);
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index 48f6ba6cfc7..271e1fc4b5f 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -36,10 +36,6 @@ export default {
type: Object,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
throttleDuration: {
type: Number,
default: 200,
@@ -65,6 +61,10 @@ export default {
type: String,
required: true,
},
+ labelsWebUrl: {
+ type: String,
+ required: true,
+ },
projectId: {
type: Number,
required: true,
@@ -335,8 +335,8 @@ export default {
<board-form
v-if="currentPage"
- :milestone-path="milestonePath"
:labels-path="labelsPath"
+ :labels-web-url="labelsWebUrl"
:project-id="projectId"
:group-id="groupId"
:can-admin-board="canAdminBoard"
diff --git a/app/assets/javascripts/boards/components/issuable_title.vue b/app/assets/javascripts/boards/components/issuable_title.vue
new file mode 100644
index 00000000000..40627a9fab8
--- /dev/null
+++ b/app/assets/javascripts/boards/components/issuable_title.vue
@@ -0,0 +1,21 @@
+<script>
+export default {
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ refPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div data-testid="issue-title">
+ <p class="gl-font-weight-bold">{{ title }}</p>
+ <p class="gl-mb-0">{{ refPath }}</p>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index d90928f35b6..8658f51e5cf 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,10 +1,9 @@
<script>
import { sortBy } from 'lodash';
import { mapState } from 'vuex';
-import { GlLabel, GlTooltipDirective } from '@gitlab/ui';
+import { GlLabel, GlTooltipDirective, GlIcon } from '@gitlab/ui';
import issueCardInner from 'ee_else_ce/boards/mixins/issue_card_inner';
import { sprintf, __ } from '~/locale';
-import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import IssueDueDate from './issue_due_date.vue';
@@ -15,7 +14,7 @@ import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
components: {
GlLabel,
- Icon,
+ GlIcon,
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
@@ -31,28 +30,23 @@ export default {
type: Object,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
list: {
type: Object,
required: false,
default: () => ({}),
},
- rootPath: {
- type: String,
- required: true,
- },
updateFilters: {
type: Boolean,
required: false,
default: false,
},
+ },
+ inject: {
groupId: {
type: Number,
- required: false,
- default: null,
+ },
+ rootPath: {
+ type: String,
},
},
data() {
@@ -148,7 +142,7 @@ export default {
<div>
<div class="d-flex board-card-header" dir="auto">
<h4 class="board-card-title gl-mb-0 gl-mt-0">
- <icon
+ <gl-icon
v-if="issue.blocked"
v-gl-tooltip
name="issue-block"
@@ -156,7 +150,7 @@ export default {
class="issue-blocked-icon gl-mr-2"
:aria-label="__('Blocked issue')"
/>
- <icon
+ <gl-icon
v-if="issue.confidential"
v-gl-tooltip
name="eye-slash"
diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue
index 4add5ee646a..fb45de6e14d 100644
--- a/app/assets/javascripts/boards/components/issue_due_date.vue
+++ b/app/assets/javascripts/boards/components/issue_due_date.vue
@@ -1,7 +1,6 @@
<script>
import dateFormat from 'dateformat';
-import { GlTooltip } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlTooltip, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import {
getDayDifference,
@@ -12,7 +11,7 @@ import {
export default {
components: {
- Icon,
+ GlIcon,
GlTooltip,
},
props: {
@@ -87,7 +86,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" name="calendar" />
+ <gl-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 e8b7689da13..fe56833016e 100644
--- a/app/assets/javascripts/boards/components/issue_time_estimate.vue
+++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue
@@ -1,12 +1,11 @@
<script>
-import { GlTooltip } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlTooltip, GlIcon } from '@gitlab/ui';
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import boardsStore from '../stores/boards_store';
export default {
components: {
- Icon,
+ GlIcon,
GlTooltip,
},
props: {
@@ -34,7 +33,7 @@ export default {
<template>
<span>
<span ref="issueTimeEstimate" class="board-card-info card-number">
- <icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{
+ <gl-icon name="hourglass" class="board-card-info-icon" /><time class="board-card-info-text">{{
timeEstimate
}}</time>
</span>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index 66f59009714..cd4512f320f 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -1,9 +1,14 @@
<script>
+/* eslint-disable vue/no-v-html */
+import { GlButton } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
export default {
+ components: {
+ GlButton,
+ },
mixins: [modalMixin],
props: {
newIssuePath: {
@@ -53,17 +58,22 @@ export default {
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
- <a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">{{
- __('New issue')
- }}</a>
- <button
+ <gl-button
+ v-if="activeTab === 'all'"
+ :href="newIssuePath"
+ category="secondary"
+ variant="success"
+ >
+ {{ __('New issue') }}
+ </gl-button>
+ <gl-button
v-if="activeTab === 'selected'"
- class="btn btn-default"
- type="button"
+ category="primary"
+ variant="default"
@click="changeTab('all')"
>
{{ __('Open issues') }}
- </button>
+ </gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index c4953dda793..d28a03da97f 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -1,4 +1,5 @@
<script>
+import { GlButton } from '@gitlab/ui';
import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer';
import { deprecatedCreateFlash as Flash } from '../../../flash';
import { __, n__ } from '../../../locale';
@@ -10,6 +11,7 @@ import boardsStore from '../../stores/boards_store';
export default {
components: {
ListsDropdown,
+ GlButton,
},
mixins: [modalMixin, footerEEMixin],
data() {
@@ -65,14 +67,14 @@ export default {
<template>
<footer class="form-actions add-issues-footer">
<div class="float-left">
- <button :disabled="submitDisabled" class="btn btn-success" type="button" @click="addIssues">
+ <gl-button :disabled="submitDisabled" category="primary" variant="success" @click="addIssues">
{{ submitText }}
- </button>
+ </gl-button>
<span class="inline add-issues-footer-to-list">{{ __('to list') }}</span>
<lists-dropdown />
</div>
- <button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
+ <gl-button class="float-right" @click="toggleModal(false)">
{{ __('Cancel') }}
- </button>
+ </gl-button>
</footer>
</template>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index 573284d2b44..3e96ecca24c 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -1,5 +1,6 @@
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
+import { GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
import ModalFilters from './filters';
import ModalTabs from './tabs.vue';
@@ -10,6 +11,7 @@ export default {
components: {
ModalTabs,
ModalFilters,
+ GlButton,
},
mixins: [modalMixin],
props: {
@@ -17,10 +19,6 @@ export default {
type: Number,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelPath: {
type: String,
required: true,
@@ -43,7 +41,7 @@ export default {
},
methods: {
toggleAll() {
- this.$refs.selectAllBtn.blur();
+ this.$refs.selectAllBtn.$el.blur();
ModalStore.toggleAll();
},
@@ -55,28 +53,28 @@ export default {
<header class="add-issues-header border-top-0 form-actions">
<h2 class="m-0">
Add issues
- <button
- type="button"
+ <gl-button
+ category="tertiary"
+ icon="close"
class="close"
data-dismiss="modal"
:aria-label="__('Close')"
@click="toggleModal(false)"
- >
- <span aria-hidden="true">×</span>
- </button>
+ />
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0" />
<div v-if="showSearch" class="d-flex gl-mb-3">
<modal-filters :store="filter" />
- <button
+ <gl-button
ref="selectAllBtn"
- type="button"
- class="btn btn-success btn-inverted gl-ml-3"
+ category="secondary"
+ variant="success"
+ class="gl-ml-3"
@click="toggleAll"
>
{{ selectAllText }}
- </button>
+ </gl-button>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue
index 20344b66140..817b3bdddb0 100644
--- a/app/assets/javascripts/boards/components/modal/index.vue
+++ b/app/assets/javascripts/boards/components/modal/index.vue
@@ -26,22 +26,10 @@ export default {
type: String,
required: true,
},
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
projectId: {
type: Number,
required: true,
},
- milestonePath: {
- type: String,
- required: true,
- },
labelPath: {
type: String,
required: true,
@@ -149,17 +137,8 @@ export default {
class="add-issues-modal d-flex position-fixed position-top-0 position-bottom-0 position-left-0 position-right-0 h-100"
>
<div class="add-issues-container d-flex flex-column m-auto rounded">
- <modal-header
- :project-id="projectId"
- :milestone-path="milestonePath"
- :label-path="labelPath"
- />
- <modal-list
- v-if="!loading && showList && !filterLoading"
- :issue-link-base="issueLinkBase"
- :root-path="rootPath"
- :empty-state-svg="emptyStateSvg"
- />
+ <modal-header :project-id="projectId" :label-path="labelPath" />
+ <modal-list v-if="!loading && showList && !filterLoading" :empty-state-svg="emptyStateSvg" />
<empty-state
v-if="showEmptyState"
:new-issue-path="newIssuePath"
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index 78e3351a79e..219263bd9b9 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -1,23 +1,15 @@
<script>
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlIcon } from '@gitlab/ui';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
export default {
components: {
IssueCardInner,
- Icon,
+ GlIcon,
},
props: {
- issueLinkBase: {
- type: String,
- required: true,
- },
- rootPath: {
- type: String,
- required: true,
- },
emptyStateSvg: {
type: String,
required: true,
@@ -134,8 +126,8 @@ export default {
class="board-card position-relative p-3 rounded"
@click="toggleIssue($event, issue)"
>
- <issue-card-inner :issue="issue" :issue-link-base="issueLinkBase" :root-path="rootPath" />
- <icon
+ <issue-card-inner :issue="issue" />
+ <gl-icon
v-if="issue.selected"
:aria-label="'Issue #' + issue.id + ' selected'"
name="mobile-issue-close"
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 3fbe8fe1be7..fe10e7fb856 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -1,13 +1,12 @@
<script>
-import { GlLink } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLink, GlIcon } from '@gitlab/ui';
import ModalStore from '../../stores/modal_store';
import boardsStore from '../../stores/boards_store';
export default {
components: {
GlLink,
- Icon,
+ GlIcon,
},
data() {
return {
@@ -29,7 +28,7 @@ export default {
<div class="dropdown inline">
<button class="dropdown-menu-toggle" type="button" data-toggle="dropdown" aria-expanded="false">
<span :style="{ backgroundColor: selected.label.color }" class="dropdown-label-box"> </span>
- {{ selected.title }} <icon name="chevron-down" />
+ {{ selected.title }} <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon" />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 2b9fdf11b37..2e356f1353a 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -6,6 +6,7 @@ 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 initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
$(document)
.off('created.label')
@@ -36,7 +37,7 @@ export default function initNewListDropdown() {
$dropdownToggle.data('projectPath'),
);
- $dropdownToggle.glDropdown({
+ initDeprecatedJQueryDropdown($dropdownToggle, {
data(term, callback) {
axios
.get($dropdownToggle.attr('data-list-labels-path'))
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index 598e92726c1..59e7620962a 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,30 +1,30 @@
<script>
import $ from 'jquery';
import { escape } from 'lodash';
-import { GlLoadingIcon } from '@gitlab/ui';
-import Icon from '~/vue_shared/components/icon.vue';
+import { GlLoadingIcon, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import eventHub from '../eventhub';
import Api from '../../api';
import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants';
+import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default {
name: 'BoardProjectSelect',
components: {
- Icon,
+ GlIcon,
GlLoadingIcon,
},
props: {
- groupId: {
- type: Number,
- required: true,
- default: 0,
- },
list: {
type: Object,
required: true,
},
},
+ inject: {
+ groupId: {
+ type: Number,
+ },
+ },
data() {
return {
loading: true,
@@ -37,7 +37,7 @@ export default {
},
},
mounted() {
- $(this.$refs.projectsDropdown).glDropdown({
+ initDeprecatedJQueryDropdown($(this.$refs.projectsDropdown), {
filterable: true,
filterRemote: true,
search: {
@@ -105,13 +105,13 @@ export default {
data-toggle="dropdown"
aria-expanded="false"
>
- {{ selectedProjectName }} <icon name="chevron-down" />
+ {{ selectedProjectName }} <gl-icon name="chevron-down" class="dropdown-menu-toggle-icon" />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
<div class="dropdown-title">{{ __('Projects') }}</div>
<div class="dropdown-input">
<input class="dropdown-input-field" type="search" :placeholder="__('Search projects')" />
- <icon name="search" class="dropdown-input-search" data-hidden="true" />
+ <gl-icon name="search" class="dropdown-input-search" data-hidden="true" />
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><gl-loading-icon /></div>
diff --git a/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
new file mode 100644
index 00000000000..8df03ea581f
--- /dev/null
+++ b/app/assets/javascripts/boards/components/sidebar/board_editable_item.vue
@@ -0,0 +1,79 @@
+<script>
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+
+export default {
+ components: { GlButton, GlLoadingIcon },
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ inject: ['canUpdate'],
+ data() {
+ return {
+ edit: false,
+ };
+ },
+ destroyed() {
+ window.removeEventListener('click', this.collapseWhenOffClick);
+ },
+ methods: {
+ collapseWhenOffClick({ target }) {
+ if (!this.$el.contains(target)) {
+ this.collapse();
+ }
+ },
+ expand() {
+ if (this.edit) {
+ return;
+ }
+
+ this.edit = true;
+ this.$emit('changed', this.edit);
+ window.addEventListener('click', this.collapseWhenOffClick);
+ },
+ collapse() {
+ if (!this.edit) {
+ return;
+ }
+
+ this.edit = false;
+ this.$emit('changed', this.edit);
+ window.removeEventListener('click', this.collapseWhenOffClick);
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-display-flex gl-justify-content-space-between gl-mb-3">
+ <span class="gl-vertical-align-middle">
+ <span data-testid="title">{{ title }}</span>
+ <gl-loading-icon v-if="loading" inline class="gl-ml-2" />
+ </span>
+ <gl-button
+ v-if="canUpdate"
+ variant="link"
+ class="gl-text-gray-900!"
+ data-testid="edit-button"
+ @click="expand()"
+ >
+ {{ __('Edit') }}
+ </gl-button>
+ </div>
+ <div v-show="!edit" class="gl-text-gray-400" data-testid="collapsed-content">
+ <slot name="collapsed">{{ __('None') }}</slot>
+ </div>
+ <div v-show="edit" data-testid="expanded-content">
+ <slot></slot>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index 35c52558cac..2f64014a949 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -15,6 +15,9 @@ export const ListType = {
export const inactiveId = 0;
+export const ISSUABLE = 'issuable';
+export const LIST = 'list';
+
export default {
BoardType,
ListType,
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index b7966dd869d..fff89832bf0 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -27,6 +27,11 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
updateObject(path) {
this.store.path = path.substr(1);
+ if (gon.features.boardsWithSwimlanes || gon.features.graphqlBoardLists) {
+ boardsStore.updateFiltersUrl();
+ boardsStore.performSearch();
+ }
+
if (this.updateUrl) {
boardsStore.updateFiltersUrl();
}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 971edd71eec..1173c6d0578 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
-import { mapActions } from 'vuex';
+import { mapActions, mapState } from 'vuex';
import 'ee_else_ce/boards/models/issue';
import 'ee_else_ce/boards/models/list';
@@ -24,7 +24,6 @@ import { deprecatedCreateFlash as Flash } from '~/flash';
import { __ } from '~/locale';
import './models/label';
import './models/assignee';
-import { BoardType } from './constants';
import toggleFocusMode from '~/boards/toggle_focus';
import FilteredSearchBoards from '~/boards/filtered_search_boards';
@@ -42,11 +41,9 @@ import {
NavigationType,
convertObjectPropsToCamelCase,
parseBoolean,
+ urlParamsToObject,
} from '~/lib/utils/common_utils';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
-import projectBoardQuery from './queries/project_board.query.graphql';
-import groupQuery from './queries/group_board.query.graphql';
Vue.use(VueApollo);
@@ -85,6 +82,11 @@ export default () => {
BoardAddIssuesModal,
BoardSettingsSidebar: () => import('~/boards/components/board_settings_sidebar.vue'),
},
+ provide: {
+ boardId: $boardApp.dataset.boardId,
+ groupId: Number($boardApp.dataset.groupId) || null,
+ rootPath: $boardApp.dataset.rootPath,
+ },
store,
apolloProvider,
data() {
@@ -94,16 +96,14 @@ export default () => {
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
recentBoardsEndpoint: $boardApp.dataset.recentBoardsEndpoint,
listsEndpoint: $boardApp.dataset.listsEndpoint,
- boardId: $boardApp.dataset.boardId,
disabled: parseBoolean($boardApp.dataset.disabled),
- issueLinkBase: $boardApp.dataset.issueLinkBase,
- rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
detailIssue: boardsStore.detail,
parent: $boardApp.dataset.parent,
};
},
computed: {
+ ...mapState(['isShowingEpicsSwimlanes']),
detailIssueVisible() {
return Object.keys(this.detailIssue.issue).length;
},
@@ -114,10 +114,15 @@ export default () => {
recentBoardsEndpoint: this.recentBoardsEndpoint,
listsEndpoint: this.listsEndpoint,
bulkUpdatePath: this.bulkUpdatePath,
- boardId: this.boardId,
+ boardId: $boardApp.dataset.boardId,
fullPath: $boardApp.dataset.fullPath,
};
- this.setInitialBoardData({ ...endpoints, boardType: this.parent });
+ this.setInitialBoardData({
+ ...endpoints,
+ boardType: this.parent,
+ disabled: this.disabled,
+ showPromotion: parseBoolean($boardApp.getAttribute('data-show-promotion')),
+ });
boardsStore.setEndpoints(endpoints);
boardsStore.rootPath = this.boardsEndpoint;
@@ -125,55 +130,24 @@ export default () => {
eventHub.$on('newDetailIssue', this.updateDetailIssue);
eventHub.$on('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$on('toggleSubscription', this.toggleSubscription);
+ eventHub.$on('performSearch', this.performSearch);
},
beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens);
eventHub.$off('newDetailIssue', this.updateDetailIssue);
eventHub.$off('clearDetailIssue', this.clearDetailIssue);
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
+ eventHub.$off('performSearch', this.performSearch);
},
mounted() {
this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
this.filterManager.setup();
- boardsStore.disabled = this.disabled;
-
- if (gon.features.graphqlBoardLists) {
- this.$apollo.addSmartQuery('lists', {
- query() {
- return this.parent === BoardType.group ? groupQuery : projectBoardQuery;
- },
- variables() {
- return {
- fullPath: this.state.endpoints.fullPath,
- boardId: `gid://gitlab/Board/${this.boardId}`,
- };
- },
- update(data) {
- return this.getNodes(data);
- },
- result({ data, error }) {
- if (error) {
- throw error;
- }
-
- const lists = this.getNodes(data);
+ this.performSearch();
- lists.forEach(list =>
- boardsStore.addList({
- ...list,
- id: getIdFromGraphQLId(list.id),
- }),
- );
+ boardsStore.disabled = this.disabled;
- boardsStore.addBlankState();
- setPromotionState(boardsStore);
- },
- error() {
- Flash(__('An error occurred while fetching the board lists. Please try again.'));
- },
- });
- } else {
+ if (!gon.features.graphqlBoardLists) {
boardsStore
.all()
.then(res => res.data)
@@ -189,10 +163,22 @@ export default () => {
}
},
methods: {
- ...mapActions(['setInitialBoardData']),
+ ...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();
+ }
+ },
updateDetailIssue(newIssue, multiSelect = false) {
const { sidebarInfoEndpoint } = newIssue;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
@@ -354,6 +340,8 @@ export default () => {
class="btn btn-success gl-ml-3"
type="button"
data-placement="bottom"
+ data-track-event="click_button"
+ data-track-label="board_add_issues"
ref="addIssuesButton"
:class="{ 'disabled': disabled }"
:title="tooltipTitle"
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 98eac35b2ed..822e6d62ab3 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -15,7 +15,7 @@ class ListIssue {
this.labels = [];
this.assignees = [];
this.selected = false;
- this.position = obj.position || obj.relative_position || Infinity;
+ this.position = obj.position || obj.relative_position || obj.relativePosition || Infinity;
this.isFetching = {
subscriptions: true,
};
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index b8b30c958a9..2f6caffbf84 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -47,7 +47,7 @@ class List {
this.loading = true;
this.loadingMore = false;
this.issues = obj.issues || [];
- this.issuesSize = obj.issuesSize ? obj.issuesSize : 0;
+ this.issuesSize = obj.issuesSize || obj.issuesCount || 0;
this.maxIssueCount = obj.maxIssueCount || obj.max_issue_count || 0;
if (obj.label) {
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
index 73d37459bfe..51bb72b7657 100644
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -27,7 +27,7 @@ export default () => {
hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
canAdminBoard: parseBoolean(dataset.canAdminBoard),
multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
- projectId: Number(dataset.projectId),
+ projectId: dataset.projectId ? Number(dataset.projectId) : 0,
groupId: Number(dataset.groupId),
scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
weights: JSON.parse(dataset.weights),
diff --git a/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql b/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql
new file mode 100644
index 00000000000..dcfe69222a0
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/board_list_create.mutation.graphql
@@ -0,0 +1,10 @@
+#import "./board_list.fragment.graphql"
+
+mutation CreateBoardList($boardId: BoardID!, $backlog: Boolean) {
+ boardListCreate(input: { boardId: $boardId, backlog: $backlog }) {
+ list {
+ ...BoardListFragment
+ }
+ errors
+ }
+}
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 8abd79332fb..d85b736720b 100644
--- a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
+++ b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
@@ -4,7 +4,7 @@ fragment BoardListShared on BoardList {
position
listType
collapsed
- maxIssueCount
+ issuesCount
label {
id
title
diff --git a/app/assets/javascripts/boards/queries/board_list_update.mutation.graphql b/app/assets/javascripts/boards/queries/board_list_update.mutation.graphql
new file mode 100644
index 00000000000..b474c9acb93
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/board_list_update.mutation.graphql
@@ -0,0 +1,10 @@
+#import "./board_list.fragment.graphql"
+
+mutation UpdateBoardList($listId: ID!, $position: Int, $collapsed: Boolean) {
+ updateBoardList(input: { listId: $listId, position: $position, collapsed: $collapsed }) {
+ list {
+ ...BoardListFragment
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql b/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql
deleted file mode 100644
index 724c7884c58..00000000000
--- a/app/assets/javascripts/boards/queries/group_lists_issues.query.graphql
+++ /dev/null
@@ -1,18 +0,0 @@
-#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
index 89d56b895a4..4b429f875a6 100644
--- a/app/assets/javascripts/boards/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/boards/queries/issue.fragment.graphql
@@ -7,14 +7,10 @@ fragment IssueNode on Issue {
referencePath: reference(full: true)
dueDate
timeEstimate
- weight
confidential
webUrl
subscribed
- blocked
- epic {
- id
- }
+ relativePosition
assignees {
nodes {
...User
diff --git a/app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql b/app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql
new file mode 100644
index 00000000000..ff6aa597f48
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/issue_move_list.mutation.graphql
@@ -0,0 +1,28 @@
+#import "ee_else_ce/boards/queries/issue.fragment.graphql"
+
+mutation IssueMoveList(
+ $projectPath: ID!
+ $iid: String!
+ $boardId: ID!
+ $fromListId: ID
+ $toListId: ID
+ $moveBeforeId: ID
+ $moveAfterId: ID
+) {
+ issueMoveList(
+ input: {
+ projectPath: $projectPath
+ iid: $iid
+ boardId: $boardId
+ fromListId: $fromListId
+ toListId: $toListId
+ moveBeforeId: $moveBeforeId
+ moveAfterId: $moveAfterId
+ }
+ ) {
+ issue {
+ ...IssueNode
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/lists_issues.query.graphql b/app/assets/javascripts/boards/queries/lists_issues.query.graphql
new file mode 100644
index 00000000000..c66cdf68cf4
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/lists_issues.query.graphql
@@ -0,0 +1,39 @@
+#import "ee_else_ce/boards/queries/issue.fragment.graphql"
+
+query ListIssues(
+ $fullPath: ID!
+ $boardId: ID!
+ $id: ID
+ $filters: BoardIssueInput
+ $isGroup: Boolean = false
+ $isProject: Boolean = false
+) {
+ group(fullPath: $fullPath) @include(if: $isGroup) {
+ board(id: $boardId) {
+ lists(id: $id) {
+ nodes {
+ id
+ issues(filters: $filters) {
+ nodes {
+ ...IssueNode
+ }
+ }
+ }
+ }
+ }
+ }
+ project(fullPath: $fullPath) @include(if: $isProject) {
+ board(id: $boardId) {
+ lists(id: $id) {
+ nodes {
+ id
+ issues(filters: $filters) {
+ nodes {
+ ...IssueNode
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql b/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql
deleted file mode 100644
index 149b76848ef..00000000000
--- a/app/assets/javascripts/boards/queries/project_lists_issues.query.graphql
+++ /dev/null
@@ -1,18 +0,0 @@
-#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 b4be7546252..4b81d9c73ef 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -1,66 +1,248 @@
-import * as types from './mutation_types';
+import Cookies from 'js-cookie';
+import { sortBy, pick } from 'lodash';
+import createFlash from '~/flash';
+import { __ } from '~/locale';
+import { parseBoolean } from '~/lib/utils/common_utils';
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';
+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 boardStore from '~/boards/stores/boards_store';
-const gqlClient = createDefaultClient();
+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';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('Not implemented!');
};
+export const gqlClient = createDefaultClient();
+
export default {
setInitialBoardData: ({ commit }, data) => {
commit(types.SET_INITIAL_BOARD_DATA, data);
},
- setActiveId({ commit }, id) {
- commit(types.SET_ACTIVE_ID, id);
+ setActiveId({ commit }, { id, sidebarType }) {
+ commit(types.SET_ACTIVE_ID, { id, sidebarType });
},
- fetchLists: () => {
- notImplemented();
+ unsetActiveId({ dispatch }) {
+ dispatch('setActiveId', { id: inactiveId, sidebarType: '' });
},
+ setFilters: ({ commit }, filters) => {
+ const filterParams = pick(filters, [
+ 'assigneeUsername',
+ 'authorUsername',
+ 'labelName',
+ 'milestoneTitle',
+ 'releaseTag',
+ 'search',
+ ]);
+ commit(types.SET_FILTERS, filterParams);
+ },
+
+ fetchLists: ({ commit, state, dispatch }) => {
+ const { endpoints, boardType } = 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),
+ };
+
+ return gqlClient
+ .query({
+ query,
+ 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)) {
+ dispatch('createList', { backlog: true });
+ }
+ dispatch('showWelcomeList');
+ })
+ .catch(() => {
+ createFlash(
+ __('An error occurred while fetching the board lists. Please reload the page.'),
+ );
+ });
+ },
+
+ // 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 }) => {
+ const { boardId } = state.endpoints;
+ gqlClient
+ .mutate({
+ mutation: createBoardListMutation,
+ variables: {
+ boardId: fullBoardId(boardId),
+ backlog,
+ },
+ })
+ .then(({ data }) => {
+ if (data?.boardListCreate?.errors.length) {
+ commit(types.CREATE_LIST_FAILURE);
+ } else {
+ const list = data.boardListCreate?.list;
+ dispatch('addList', list);
+ }
+ })
+ .catch(() => {
+ commit(types.CREATE_LIST_FAILURE);
+ });
+ },
+
+ addList: ({ state, commit }, list) => {
+ const lists = state.boardLists;
+ // Temporarily using positioning logic from boardStore
+ lists.push(boardStore.updateListPosition({ ...list, doNotFetchIssues: true }));
+ commit(types.RECEIVE_BOARD_LISTS_SUCCESS, sortBy(lists, 'position'));
+ },
+
+ showWelcomeList: ({ state, dispatch }) => {
+ if (state.disabled) {
+ return;
+ }
+ if (
+ state.boardLists.find(list => list.type !== ListType.backlog && list.type !== ListType.closed)
+ ) {
+ return;
+ }
+ if (parseBoolean(Cookies.get('issue_board_welcome_hidden'))) {
+ return;
+ }
+
+ dispatch('addList', {
+ id: 'blank',
+ listType: ListType.blank,
+ title: __('Welcome to your issue board!'),
+ position: 0,
+ });
+ },
+
+ showPromotionList: () => {},
+
generateDefaultLists: () => {
notImplemented();
},
- createList: () => {
- notImplemented();
+ moveList: ({ state, commit, dispatch }, { listId, newIndex, adjustmentValue }) => {
+ const { boardLists } = state;
+ const backupList = [...boardLists];
+ const movedList = boardLists.find(({ id }) => id === listId);
+
+ const newPosition = newIndex - 1;
+ const listAtNewIndex = boardLists[newIndex];
+
+ movedList.position = newPosition;
+ listAtNewIndex.position += adjustmentValue;
+ commit(types.MOVE_LIST, {
+ movedList,
+ listAtNewIndex,
+ });
+
+ dispatch('updateList', { listId, position: newPosition, backupList });
},
- updateList: () => {
- notImplemented();
+ updateList: ({ commit }, { listId, position, collapsed, backupList }) => {
+ gqlClient
+ .mutate({
+ mutation: updateBoardListMutation,
+ variables: {
+ listId,
+ position,
+ collapsed,
+ },
+ })
+ .then(({ data }) => {
+ if (data?.updateBoardList?.errors.length) {
+ commit(types.UPDATE_LIST_FAILURE, backupList);
+ }
+ })
+ .catch(() => {
+ commit(types.UPDATE_LIST_FAILURE, backupList);
+ });
},
deleteList: () => {
notImplemented();
},
- fetchIssuesForList: () => {
- notImplemented();
+ fetchIssuesForList: ({ state, commit }, listId) => {
+ const { endpoints, boardType, filterParams } = state;
+ const { fullPath, boardId } = endpoints;
+
+ const variables = {
+ fullPath,
+ boardId: fullBoardId(boardId),
+ id: listId,
+ filters: filterParams,
+ isGroup: boardType === BoardType.group,
+ isProject: boardType === BoardType.project,
+ };
+
+ return gqlClient
+ .query({
+ query: listsIssuesQuery,
+ context: {
+ isSingleRequest: true,
+ },
+ variables,
+ })
+ .then(({ data }) => {
+ const { lists } = data[boardType]?.board;
+ const listIssues = formatListIssues(lists);
+ commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listId });
+ })
+ .catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId));
},
fetchIssuesForAllLists: ({ state, commit }) => {
commit(types.REQUEST_ISSUES_FOR_ALL_LISTS);
- const { endpoints, boardType } = state;
+ const { endpoints, boardType, filterParams } = state;
const { fullPath, boardId } = endpoints;
- const query = boardType === BoardType.group ? groupListsIssuesQuery : projectListsIssuesQuery;
-
const variables = {
fullPath,
- boardId: `gid://gitlab/Board/${boardId}`,
+ boardId: fullBoardId(boardId),
+ filters: filterParams,
+ isGroup: boardType === BoardType.group,
+ isProject: boardType === BoardType.project,
};
return gqlClient
.query({
- query,
+ query: listsIssuesQuery,
variables,
})
.then(({ data }) => {
@@ -71,14 +253,56 @@ export default {
.catch(() => commit(types.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE));
},
- moveIssue: () => {
- notImplemented();
+ moveIssue: (
+ { state, commit },
+ { issueId, issueIid, issuePath, fromListId, toListId, moveBeforeId, moveAfterId },
+ ) => {
+ const originalIssue = state.issues[issueId];
+ const fromList = state.issuesByListId[fromListId];
+ const originalIndex = fromList.indexOf(Number(issueId));
+ commit(types.MOVE_ISSUE, { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId });
+
+ const { boardId } = state.endpoints;
+ const [fullProjectPath] = issuePath.split(/[#]/);
+
+ gqlClient
+ .mutate({
+ mutation: issueMoveListMutation,
+ variables: {
+ projectPath: fullProjectPath,
+ boardId: fullBoardId(boardId),
+ iid: issueIid,
+ fromListId: getIdFromGraphQLId(fromListId),
+ toListId: getIdFromGraphQLId(toListId),
+ moveBeforeId,
+ moveAfterId,
+ },
+ })
+ .then(({ data }) => {
+ if (data?.issueMoveList?.errors.length) {
+ commit(types.MOVE_ISSUE_FAILURE, { originalIssue, fromListId, toListId, originalIndex });
+ } else {
+ const issue = data.issueMoveList?.issue;
+ commit(types.MOVE_ISSUE_SUCCESS, { issue });
+ }
+ })
+ .catch(() =>
+ commit(types.MOVE_ISSUE_FAILURE, { originalIssue, fromListId, toListId, originalIndex }),
+ );
},
createNewIssue: () => {
notImplemented();
},
+ addListIssue: ({ commit }, { list, issue, position }) => {
+ commit(types.ADD_ISSUE_TO_LIST, { list, issue, position });
+ },
+
+ addListIssueFailure: ({ commit }, { list, issue }) => {
+ commit(types.ADD_ISSUE_TO_LIST_FAILURE, { list, issue });
+ },
+
fetchBacklog: () => {
notImplemented();
},
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index 30c71d64085..faf4f9ebfd3 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -15,6 +15,7 @@ import {
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import eventHub from '../eventhub';
import { ListType } from '../constants';
import IssueProject from '../models/project';
@@ -303,7 +304,11 @@ const boardsStore = {
onNewListIssueResponse(list, issue, data) {
issue.refreshData(data);
- if (list.issuesSize > 1) {
+ if (
+ !gon.features.boardsWithSwimlanes &&
+ !gon.features.graphqlBoardLists &&
+ list.issues.length > 1
+ ) {
const moveBeforeId = list.issues[1].id;
this.moveIssue(issue.id, null, null, null, moveBeforeId);
}
@@ -513,6 +518,10 @@ const boardsStore = {
eventHub.$emit('updateTokens');
},
+ performSearch() {
+ eventHub.$emit('performSearch');
+ },
+
setListDetail(newList) {
this.detail.list = newList;
},
@@ -706,6 +715,10 @@ const boardsStore = {
},
newIssue(id, issue) {
+ if (typeof id === 'string') {
+ id = getIdFromGraphQLId(id);
+ }
+
return axios.post(this.generateIssuesPath(id), {
issue,
});
@@ -714,6 +727,10 @@ const boardsStore = {
newListIssue(list, issue) {
list.addIssue(issue, null, 0);
list.issuesSize += 1;
+ let listId = list.id;
+ if (typeof listId === 'string') {
+ listId = getIdFromGraphQLId(listId);
+ }
return this.newIssue(list.id, issue)
.then(res => res.data)
@@ -854,21 +871,6 @@ 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 || 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'],
});
diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js
index 4de1576099d..3688476dc5f 100644
--- a/app/assets/javascripts/boards/stores/getters.js
+++ b/app/assets/javascripts/boards/stores/getters.js
@@ -1,3 +1,25 @@
+import { inactiveId } from '../constants';
+
export default {
getLabelToggleState: state => (state.isShowingLabels ? 'on' : 'off'),
+ isSidebarOpen: state => state.activeId !== inactiveId,
+ isSwimlanesOn: state => {
+ if (!gon?.features?.boardsWithSwimlanes) {
+ return false;
+ }
+
+ return state.isShowingEpicsSwimlanes;
+ },
+ getIssueById: state => id => {
+ return state.issues[id] || {};
+ },
+
+ getIssues: (state, getters) => listId => {
+ const listIssueIds = state.issuesByListId[listId] || [];
+ return listIssueIds.map(id => getters.getIssueById(id));
+ },
+
+ getActiveIssue: state => {
+ return state.issues[state.activeId] || {};
+ },
};
diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js
index 0f96dc2e287..f0a283f6161 100644
--- a/app/assets/javascripts/boards/stores/mutation_types.js
+++ b/app/assets/javascripts/boards/stores/mutation_types.js
@@ -1,25 +1,34 @@
export const SET_INITIAL_BOARD_DATA = 'SET_INITIAL_BOARD_DATA';
+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 SHOW_PROMOTION_LIST = 'SHOW_PROMOTION_LIST';
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';
-export const REQUEST_UPDATE_LIST = 'REQUEST_UPDATE_LIST';
-export const RECEIVE_UPDATE_LIST_SUCCESS = 'RECEIVE_UPDATE_LIST_SUCCESS';
-export const RECEIVE_UPDATE_LIST_ERROR = 'RECEIVE_UPDATE_LIST_ERROR';
+export const MOVE_LIST = 'MOVE_LIST';
+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 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';
-export const REQUEST_MOVE_ISSUE = 'REQUEST_MOVE_ISSUE';
-export const RECEIVE_MOVE_ISSUE_SUCCESS = 'RECEIVE_MOVE_ISSUE_SUCCESS';
-export const RECEIVE_MOVE_ISSUE_ERROR = 'RECEIVE_MOVE_ISSUE_ERROR';
+export const MOVE_ISSUE = 'MOVE_ISSUE';
+export const MOVE_ISSUE_SUCCESS = 'MOVE_ISSUE_SUCCESS';
+export const MOVE_ISSUE_FAILURE = 'MOVE_ISSUE_FAILURE';
export const REQUEST_UPDATE_ISSUE = 'REQUEST_UPDATE_ISSUE';
export const RECEIVE_UPDATE_ISSUE_SUCCESS = 'RECEIVE_UPDATE_ISSUE_SUCCESS';
export const RECEIVE_UPDATE_ISSUE_ERROR = 'RECEIVE_UPDATE_ISSUE_ERROR';
+export const ADD_ISSUE_TO_LIST = 'ADD_ISSUE_TO_LIST';
+export const ADD_ISSUE_TO_LIST_FAILURE = 'ADD_ISSUE_TO_LIST_FAILURE';
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';
diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js
index ca9b911ce5b..faeb3e25a71 100644
--- a/app/assets/javascripts/boards/stores/mutations.js
+++ b/app/assets/javascripts/boards/stores/mutations.js
@@ -1,19 +1,55 @@
+import Vue from 'vue';
+import { sortBy, pull } from 'lodash';
+import { formatIssue, moveIssueListHelper } from '../boards_util';
import * as mutationTypes from './mutation_types';
+import { __ } from '~/locale';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
const notImplemented = () => {
/* eslint-disable-next-line @gitlab/require-i18n-strings */
throw new Error('Not implemented!');
};
+const removeIssueFromList = (state, listId, issueId) => {
+ Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId));
+};
+
+const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => {
+ const listIssues = state.issuesByListId[listId];
+ let newIndex = atIndex || 0;
+ if (moveBeforeId) {
+ newIndex = listIssues.indexOf(moveBeforeId) + 1;
+ } else if (moveAfterId) {
+ newIndex = listIssues.indexOf(moveAfterId);
+ }
+ listIssues.splice(newIndex, 0, issueId);
+ Vue.set(state.issuesByListId, listId, listIssues);
+};
+
export default {
- [mutationTypes.SET_INITIAL_BOARD_DATA]: (state, data) => {
- const { boardType, ...endpoints } = data;
+ [mutationTypes.SET_INITIAL_BOARD_DATA](state, data) {
+ const { boardType, disabled, showPromotion, ...endpoints } = data;
state.endpoints = endpoints;
state.boardType = boardType;
+ state.disabled = disabled;
+ state.showPromotion = showPromotion;
},
- [mutationTypes.SET_ACTIVE_ID](state, id) {
+ [mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, lists) => {
+ state.boardLists = lists;
+ },
+
+ [mutationTypes.SET_ACTIVE_ID](state, { id, sidebarType }) {
state.activeId = id;
+ state.sidebarType = sidebarType;
+ },
+
+ [mutationTypes.SET_FILTERS](state, filterParams) {
+ state.filterParams = filterParams;
+ },
+
+ [mutationTypes.CREATE_LIST_FAILURE]: state => {
+ state.error = __('An error occurred while creating the list. Please try again.');
},
[mutationTypes.REQUEST_ADD_LIST]: () => {
@@ -28,16 +64,17 @@ export default {
notImplemented();
},
- [mutationTypes.REQUEST_UPDATE_LIST]: () => {
- notImplemented();
- },
-
- [mutationTypes.RECEIVE_UPDATE_LIST_SUCCESS]: () => {
- notImplemented();
+ [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'));
},
- [mutationTypes.RECEIVE_UPDATE_LIST_ERROR]: () => {
- notImplemented();
+ [mutationTypes.UPDATE_LIST_FAILURE]: (state, backupList) => {
+ state.error = __('An error occurred while updating the list. Please try again.');
+ Vue.set(state, 'boardLists', backupList);
},
[mutationTypes.REQUEST_REMOVE_LIST]: () => {
@@ -52,17 +89,41 @@ export default {
notImplemented();
},
+ [mutationTypes.RECEIVE_ISSUES_FOR_LIST_SUCCESS]: (state, { listIssues, 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);
+ },
+
+ [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;
},
- [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, listIssues) => {
- state.issuesByListId = listIssues;
+ [mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_SUCCESS]: (state, { listData, issues }) => {
+ state.issuesByListId = listData;
+ state.issues = issues;
state.isLoadingIssues = false;
},
+ [mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => {
+ if (!state.issues[issueId]) {
+ /* eslint-disable-next-line @gitlab/require-i18n-strings */
+ throw new Error('No issue found.');
+ }
+
+ Vue.set(state.issues[issueId], prop, value);
+ },
+
[mutationTypes.RECEIVE_ISSUES_FOR_ALL_LISTS_FAILURE]: state => {
- state.listIssueFetchFailure = true;
+ state.error = __('An error occurred while fetching the board issues. Please reload the page.');
state.isLoadingIssues = false;
},
@@ -78,16 +139,38 @@ export default {
notImplemented();
},
- [mutationTypes.REQUEST_MOVE_ISSUE]: () => {
- notImplemented();
+ [mutationTypes.MOVE_ISSUE]: (
+ 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 issue = moveIssueListHelper(originalIssue, fromList, toList);
+ Vue.set(state.issues, issue.id, issue);
+
+ removeIssueFromList(state, fromListId, issue.id);
+ addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId });
},
- [mutationTypes.RECEIVE_MOVE_ISSUE_SUCCESS]: () => {
- notImplemented();
+ [mutationTypes.MOVE_ISSUE_SUCCESS]: (state, { issue }) => {
+ const issueId = getIdFromGraphQLId(issue.id);
+ Vue.set(state.issues, issueId, formatIssue({ ...issue, id: issueId }));
},
- [mutationTypes.RECEIVE_MOVE_ISSUE_ERROR]: () => {
- notImplemented();
+ [mutationTypes.MOVE_ISSUE_FAILURE]: (
+ state,
+ { originalIssue, fromListId, toListId, originalIndex },
+ ) => {
+ state.error = __('An error occurred while moving the issue. Please try again.');
+ Vue.set(state.issues, originalIssue.id, originalIssue);
+ removeIssueFromList(state, toListId, originalIssue.id);
+ addIssueToList({
+ state,
+ listId: fromListId,
+ issueId: originalIssue.id,
+ atIndex: originalIndex,
+ });
},
[mutationTypes.REQUEST_UPDATE_ISSUE]: () => {
@@ -102,6 +185,18 @@ export default {
notImplemented();
},
+ [mutationTypes.ADD_ISSUE_TO_LIST]: (state, { list, issue, position }) => {
+ const listIssues = state.issuesByListId[list.id];
+ listIssues.splice(position, 0, issue.id);
+ Vue.set(state.issuesByListId, list.id, listIssues);
+ Vue.set(state.issues, issue.id, issue);
+ },
+
+ [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);
+ },
+
[mutationTypes.SET_CURRENT_PAGE]: () => {
notImplemented();
},
diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js
index cb6930774ed..be937d68c6c 100644
--- a/app/assets/javascripts/boards/stores/state.js
+++ b/app/assets/javascripts/boards/stores/state.js
@@ -3,9 +3,17 @@ import { inactiveId } from '~/boards/constants';
export default () => ({
endpoints: {},
boardType: null,
+ disabled: false,
+ showPromotion: false,
isShowingLabels: true,
activeId: inactiveId,
+ sidebarType: '',
+ boardLists: [],
issuesByListId: {},
+ issues: {},
isLoadingIssues: false,
- listIssueFetchFailure: false,
+ filterParams: {},
+ error: undefined,
+ // TODO: remove after ce/ee split of board_content.vue
+ isShowingEpicsSwimlanes: false,
});