summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/boards
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/boards')
-rw-r--r--app/assets/javascripts/boards/components/board.js3
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue6
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue8
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js5
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue6
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue4
-rw-r--r--app/assets/javascripts/boards/constants.js8
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js2
-rw-r--r--app/assets/javascripts/boards/icons/fullscreen_collapse.svg1
-rw-r--r--app/assets/javascripts/boards/icons/fullscreen_expand.svg1
-rw-r--r--app/assets/javascripts/boards/index.js116
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js5
-rw-r--r--app/assets/javascripts/boards/models/assignee.js2
-rw-r--r--app/assets/javascripts/boards/models/issue.js28
-rw-r--r--app/assets/javascripts/boards/models/list.js87
-rw-r--r--app/assets/javascripts/boards/queries/board_list.fragment.graphql5
-rw-r--r--app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql15
-rw-r--r--app/assets/javascripts/boards/queries/group_board.query.graphql13
-rw-r--r--app/assets/javascripts/boards/queries/project_board.query.graphql13
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js114
-rw-r--r--app/assets/javascripts/boards/stores/state.js4
-rw-r--r--app/assets/javascripts/boards/toggle_focus.js46
22 files changed, 313 insertions, 179 deletions
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index ac02c229a07..517a13ceb27 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -113,9 +113,6 @@ export default Vue.extend({
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
- helpLink() {
- return boardsStore.scopedLabels.helpLink;
- },
},
watch: {
filter: {
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index 10c855675db..fb854616a04 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -113,9 +113,6 @@ export default {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `boards.${this.boardId}.${this.list.type}.${this.list.id}`;
},
- helpLink() {
- return boardsStore.scopedLabels.helpLink;
- },
},
watch: {
filter: {
@@ -286,7 +283,6 @@ export default {
:background-color="list.label.color"
:description="list.label.description"
:scoped="showScopedLabels(list.label)"
- :scoped-labels-documentation-link="helpLink"
:size="!list.isExpanded ? 'sm' : ''"
:title="list.label.title"
tooltip-placement="bottom"
@@ -353,7 +349,7 @@ export default {
v-if="isSettingsShown"
ref="settingsBtn"
:aria-label="__(`List settings`)"
- class="no-drag rounded-right"
+ class="no-drag rounded-right js-board-settings-button"
title="List settings"
type="button"
@click="openSidebarSettings"
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index c0df8b72095..fbe221041c1 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -58,11 +58,6 @@ export default {
required: false,
default: false,
},
- scopedLabelsDocumentationLink: {
- type: String,
- required: false,
- default: '#',
- },
},
data() {
return {
@@ -182,7 +177,7 @@ export default {
@cancel="cancel"
@submit="submit"
>
- <template slot="body">
+ <template #body>
<p v-if="isDeleteForm">{{ __('Are you sure you want to delete this board?') }}</p>
<form v-else class="js-board-config-modal" @submit.prevent>
<div v-if="!readonly" class="append-bottom-20">
@@ -208,7 +203,6 @@ export default {
:can-admin-board="canAdminBoard"
:milestone-path="milestonePath"
:labels-path="labelsPath"
- :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
:group-id="groupId"
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 66a5e134205..c8953158811 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -51,7 +51,7 @@ export default Vue.extend({
return Object.keys(this.issue).length;
},
milestoneTitle() {
- return this.issue.milestone ? this.issue.milestone.title : __('No Milestone');
+ return this.issue.milestone ? this.issue.milestone.title : __('No milestone');
},
canRemove() {
return !this.list.preset;
@@ -70,9 +70,6 @@ export default Vue.extend({
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
},
- helpLink() {
- return boardsStore.scopedLabels.helpLink;
- },
},
watch: {
detail: {
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index f2c976be7ae..80db9930259 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -86,11 +86,6 @@ export default {
required: false,
default: false,
},
- scopedLabelsDocumentationLink: {
- type: String,
- required: false,
- default: '#',
- },
},
data() {
return {
@@ -348,7 +343,6 @@ export default {
:scoped-issue-board-feature-enabled="scopedIssueBoardFeatureEnabled"
:weights="weights"
:enable-scoped-labels="enabledScopedLabels"
- :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
/>
</span>
</div>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index daaa12c096b..a589fb325b2 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -102,9 +102,6 @@ export default {
orderedLabels() {
return sortBy(this.issue.labels.filter(this.isNonListLabel), 'title');
},
- helpLink() {
- return boardsStore.scopedLabels.helpLink;
- },
},
methods: {
isIndexLessThanlimit(index) {
@@ -181,7 +178,6 @@ export default {
:description="label.description"
size="sm"
:scoped="showScopedLabel(label)"
- :scoped-labels-documentation-link="helpLink"
@click="filterByLabel(label)"
/>
</template>
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index dcecfe5e1bb..f577a168e75 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -1,3 +1,8 @@
+export const BoardType = {
+ project: 'project',
+ group: 'group',
+};
+
export const ListType = {
assignee: 'assignee',
milestone: 'milestone',
@@ -8,6 +13,9 @@ export const ListType = {
blank: 'blank',
};
+export const inactiveListId = 0;
+
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 b1b4b1c5508..ca85e54eb89 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 '../filtered_search/filtered_search_manager';
+import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_manager';
import boardsStore from './stores/boards_store';
export default class FilteredSearchBoards extends FilteredSearchManager {
diff --git a/app/assets/javascripts/boards/icons/fullscreen_collapse.svg b/app/assets/javascripts/boards/icons/fullscreen_collapse.svg
new file mode 100644
index 00000000000..6bd773dc4c5
--- /dev/null
+++ b/app/assets/javascripts/boards/icons/fullscreen_collapse.svg
@@ -0,0 +1 @@
+<svg width="17" height="17" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg"><path d="M.147 15.496l2.146-2.146-1.286-1.286a.55.55 0 0 1-.125-.616c.101-.238.277-.357.527-.357h4a.55.55 0 0 1 .402.17.55.55 0 0 1 .17.401v4c0 .25-.12.426-.358.527-.232.101-.437.06-.616-.125l-1.286-1.286-2.146 2.146-1.428-1.428zM14.996.646l1.428 1.43-2.146 2.145 1.286 1.286c.185.179.226.384.125.616-.101.238-.277.357-.527.357h-4a.55.55 0 0 1-.402-.17.55.55 0 0 1-.17-.401v-4c0-.25.12-.426.358-.527a.553.553 0 0 1 .616.125l1.286 1.286L14.996.647zm-13.42 0L3.72 2.794l1.286-1.286a.55.55 0 0 1 .616-.125c.238.101.357.277.357.527v4a.55.55 0 0 1-.17.402.55.55 0 0 1-.401.17h-4c-.25 0-.426-.12-.527-.358-.101-.232-.06-.437.125-.616l1.286-1.286L.147 2.075 1.575.647zm14.848 14.85l-1.428 1.428-2.146-2.146-1.286 1.286c-.179.185-.384.226-.616.125-.238-.101-.357-.277-.357-.527v-4a.55.55 0 0 1 .17-.402.55.55 0 0 1 .401-.17h4c.25 0 .426.12.527.358a.553.553 0 0 1-.125.616l-1.286 1.286 2.146 2.146z" fill-rule="evenodd"/></svg>
diff --git a/app/assets/javascripts/boards/icons/fullscreen_expand.svg b/app/assets/javascripts/boards/icons/fullscreen_expand.svg
new file mode 100644
index 00000000000..306073b8af2
--- /dev/null
+++ b/app/assets/javascripts/boards/icons/fullscreen_expand.svg
@@ -0,0 +1 @@
+<svg width="15" height="15" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"><path d="M8.591 5.056l2.147-2.146-1.286-1.286a.55.55 0 0 1-.125-.616c.101-.238.277-.357.527-.357h4a.55.55 0 0 1 .402.17.55.55 0 0 1 .17.401v4c0 .25-.12.426-.358.527-.232.101-.437.06-.616-.125l-1.286-1.286-2.146 2.147-1.429-1.43zM5.018 8.553l1.429 1.43L4.3 12.127l1.286 1.286c.185.179.226.384.125.616-.101.238-.277.357-.527.357h-4a.55.55 0 0 1-.402-.17.55.55 0 0 1-.17-.401v-4c0-.25.12-.426.358-.527a.553.553 0 0 1 .616.125L2.872 10.7l2.146-2.147zm4.964 0l2.146 2.147 1.286-1.286a.55.55 0 0 1 .616-.125c.238.101.357.277.357.527v4a.55.55 0 0 1-.17.402.55.55 0 0 1-.401.17h-4c-.25 0-.426-.12-.527-.358-.101-.232-.06-.437.125-.616l1.286-1.286-2.147-2.146 1.43-1.429zM6.447 5.018l-1.43 1.429L2.873 4.3 1.586 5.586c-.179.185-.384.226-.616.125-.238-.101-.357-.277-.357-.527v-4a.55.55 0 0 1 .17-.402.55.55 0 0 1 .401-.17h4c.25 0 .426.12.527.358a.553.553 0 0 1-.125.616L4.3 2.872l2.147 2.146z" fill-rule="evenodd"/></svg>
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index a12db7a5f1a..9ff7575ae09 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -6,7 +6,6 @@ import 'ee_else_ce/boards/models/list';
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';
-import toggleFocusMode from 'ee_else_ce/boards/toggle_focus';
import toggleLabels from 'ee_else_ce/boards/toggle_labels';
import {
setPromotionState,
@@ -16,11 +15,15 @@ import {
getBoardsModalData,
} from 'ee_else_ce/boards/ee_functions';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
import 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';
import eventHub from '~/boards/eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
@@ -37,7 +40,16 @@ import {
convertObjectPropsToCamelCase,
parseBoolean,
} 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);
+
+const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+});
let issueBoardsApp;
@@ -79,18 +91,22 @@ export default () => {
import('ee_component/boards/components/board_settings_sidebar.vue'),
},
store,
- data: {
- state: boardsStore.state,
- loading: true,
- 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,
+ apolloProvider,
+ data() {
+ return {
+ state: boardsStore.state,
+ loading: 0,
+ 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: {
detailIssueVisible() {
@@ -124,31 +140,56 @@ export default () => {
this.filterManager.setup();
boardsStore.disabled = this.disabled;
- boardsStore
- .all()
- .then(res => res.data)
- .then(lists => {
- lists.forEach(listObj => {
- let { position } = listObj;
- if (listObj.list_type === 'closed') {
- position = Infinity;
- } else if (listObj.list_type === 'backlog') {
- position = -1;
+
+ 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;
}
- boardsStore.addList({
- ...listObj,
- position,
- });
- });
+ const lists = this.getNodes(data);
+
+ lists.forEach(list =>
+ boardsStore.addList({
+ ...list,
+ id: getIdFromGraphQLId(list.id),
+ }),
+ );
- boardsStore.addBlankState();
- setPromotionState(boardsStore);
- this.loading = false;
- })
- .catch(() => {
- Flash(__('An error occurred while fetching the board lists. Please try again.'));
+ boardsStore.addBlankState();
+ setPromotionState(boardsStore);
+ },
+ error() {
+ Flash(__('An error occurred while fetching the board lists. Please try again.'));
+ },
});
+ } else {
+ boardsStore
+ .all()
+ .then(res => res.data)
+ .then(lists => {
+ lists.forEach(list => boardsStore.addList(list));
+ boardsStore.addBlankState();
+ setPromotionState(boardsStore);
+ this.loading = false;
+ })
+ .catch(() => {
+ Flash(__('An error occurred while fetching the board lists. Please try again.'));
+ });
+ }
},
methods: {
updateTokens() {
@@ -233,6 +274,9 @@ export default () => {
});
}
},
+ getNodes(data) {
+ return data[this.parent]?.board?.lists.nodes;
+ },
},
});
@@ -261,7 +305,7 @@ export default () => {
return {
modal: ModalStore.store,
store: boardsStore.state,
- ...getBoardsModalData($boardApp),
+ ...getBoardsModalData(),
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
};
},
@@ -325,7 +369,7 @@ export default () => {
});
}
- toggleFocusMode(ModalStore, boardsStore, $boardApp);
+ toggleFocusMode(ModalStore, boardsStore);
toggleLabels();
mountMultipleBoardsSwitcher();
};
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index 68ea28e68d9..fceb8c9d48e 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -19,14 +19,15 @@ export function getBoardSortableDefaultOptions(obj) {
const touchEnabled =
'ontouchstart' in window || (window.DocumentTouch && document instanceof DocumentTouch);
- const defaultSortOptions = Object.assign({}, sortableConfig, {
+ const defaultSortOptions = {
+ ...sortableConfig,
filter: '.no-drag',
delay: touchEnabled ? 100 : 0,
scrollSensitivity: touchEnabled ? 60 : 100,
scrollSpeed: 20,
onStart: sortableStart,
onEnd: sortableEnd,
- });
+ };
Object.keys(obj).forEach(key => {
defaultSortOptions[key] = obj[key];
diff --git a/app/assets/javascripts/boards/models/assignee.js b/app/assets/javascripts/boards/models/assignee.js
index 5f5758583bb..1e822d06bfd 100644
--- a/app/assets/javascripts/boards/models/assignee.js
+++ b/app/assets/javascripts/boards/models/assignee.js
@@ -3,7 +3,7 @@ export default class ListAssignee {
this.id = obj.id;
this.name = obj.name;
this.username = obj.username;
- this.avatar = obj.avatar_url || obj.avatar || gon.default_avatar_url;
+ this.avatar = obj.avatarUrl || obj.avatar_url || obj.avatar || gon.default_avatar_url;
this.path = obj.path;
this.state = obj.state;
this.webUrl = obj.web_url || obj.webUrl;
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index d099c4b930c..878f49cc6be 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.relative_position || Infinity;
+ this.position = obj.position || obj.relative_position || Infinity;
this.isFetching = {
subscriptions: true,
};
@@ -99,31 +99,7 @@ class ListIssue {
}
update() {
- const data = {
- issue: {
- milestone_id: this.milestone ? this.milestone.id : null,
- due_date: this.dueDate,
- assignee_ids: this.assignees.length > 0 ? this.assignees.map(u => u.id) : [0],
- label_ids: this.labels.map(label => label.id),
- },
- };
-
- if (!data.issue.label_ids.length) {
- data.issue.label_ids = [''];
- }
-
- const projectPath = this.project ? this.project.path : '';
- return axios.patch(`${this.path}.json`, data).then(({ data: body = {} } = {}) => {
- /**
- * Since post implementation of Scoped labels, server can reject
- * same key-ed labels. To keep the UI and server Model consistent,
- * we're just assigning labels that server echo's back to us when we
- * PATCH the said object.
- */
- if (body) {
- this.labels = convertObjectPropsToCamelCase(body.labels, { deep: true });
- }
- });
+ return boardsStore.updateIssue(this);
}
}
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 990b648190a..31c372b7a75 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -1,10 +1,9 @@
-/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow */
+/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return */
import ListIssue from 'ee_else_ce/boards/models/issue';
import { __ } from '~/locale';
import ListLabel from './label';
import ListAssignee from './assignee';
-import { urlParamsToObject } from '~/lib/utils/common_utils';
import flash from '~/flash';
import boardsStore from '../stores/boards_store';
import ListMilestone from './milestone';
@@ -40,8 +39,8 @@ class List {
this.id = obj.id;
this._uid = this.guid();
this.position = obj.position;
- this.title = obj.list_type === 'backlog' ? __('Open') : obj.title;
- this.type = obj.list_type;
+ this.title = (obj.list_type || obj.listType) === 'backlog' ? __('Open') : obj.title;
+ this.type = obj.list_type || obj.listType;
const typeInfo = this.getTypeInfo(this.type);
this.preset = Boolean(typeInfo.isPreset);
@@ -52,14 +51,12 @@ class List {
this.loadingMore = false;
this.issues = obj.issues || [];
this.issuesSize = obj.issuesSize ? obj.issuesSize : 0;
- this.maxIssueCount = Object.hasOwnProperty.call(obj, 'max_issue_count')
- ? obj.max_issue_count
- : 0;
+ this.maxIssueCount = obj.maxIssueCount || obj.max_issue_count || 0;
if (obj.label) {
this.label = new ListLabel(obj.label);
- } else if (obj.user) {
- this.assignee = new ListAssignee(obj.user);
+ } else if (obj.user || obj.assignee) {
+ this.assignee = new ListAssignee(obj.user || obj.assignee);
this.title = this.assignee.name;
} else if (IS_EE && obj.milestone) {
this.milestone = new ListMilestone(obj.milestone);
@@ -113,34 +110,7 @@ class List {
}
getIssues(emptyIssues = true) {
- const data = {
- ...urlParamsToObject(boardsStore.filter.path),
- page: this.page,
- };
-
- if (this.label && data.label_name) {
- data.label_name = data.label_name.filter(label => label !== this.label.title);
- }
-
- if (emptyIssues) {
- this.loading = true;
- }
-
- return boardsStore
- .getIssuesForList(this.id, data)
- .then(res => res.data)
- .then(data => {
- this.loading = false;
- this.issuesSize = data.size;
-
- if (emptyIssues) {
- this.issues = [];
- }
-
- this.createIssues(data.issues);
-
- return data;
- });
+ return boardsStore.getListIssues(this, emptyIssues);
}
newIssue(issue) {
@@ -164,48 +134,7 @@ class List {
}
addIssue(issue, listFrom, newIndex) {
- let moveBeforeId = null;
- let moveAfterId = null;
-
- if (!this.findIssue(issue.id)) {
- if (newIndex !== undefined) {
- this.issues.splice(newIndex, 0, issue);
-
- if (this.issues[newIndex - 1]) {
- moveBeforeId = this.issues[newIndex - 1].id;
- }
-
- if (this.issues[newIndex + 1]) {
- moveAfterId = this.issues[newIndex + 1].id;
- }
- } else {
- this.issues.push(issue);
- }
-
- if (this.label) {
- issue.addLabel(this.label);
- }
-
- if (this.assignee) {
- if (listFrom && listFrom.type === 'assignee') {
- issue.removeAssignee(listFrom.assignee);
- }
- issue.addAssignee(this.assignee);
- }
-
- if (IS_EE && this.milestone) {
- if (listFrom && listFrom.type === 'milestone') {
- issue.removeMilestone(listFrom.milestone);
- }
- issue.addMilestone(this.milestone);
- }
-
- if (listFrom) {
- this.issuesSize += 1;
-
- this.updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId);
- }
- }
+ boardsStore.addListIssue(this, issue, listFrom, newIndex);
}
moveIssue(issue, oldIndex, newIndex, moveBeforeId, moveAfterId) {
diff --git a/app/assets/javascripts/boards/queries/board_list.fragment.graphql b/app/assets/javascripts/boards/queries/board_list.fragment.graphql
new file mode 100644
index 00000000000..bbf3314377e
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/board_list.fragment.graphql
@@ -0,0 +1,5 @@
+#import "./board_list_shared.fragment.graphql"
+
+fragment BoardListFragment on BoardList {
+ ...BoardListShared
+}
diff --git a/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
new file mode 100644
index 00000000000..6ba6c05d6d9
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/board_list_shared.fragment.graphql
@@ -0,0 +1,15 @@
+fragment BoardListShared on BoardList {
+ id,
+ title,
+ position,
+ listType,
+ collapsed,
+ label {
+ id,
+ title,
+ color,
+ textColor,
+ description,
+ descriptionHtml
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/group_board.query.graphql b/app/assets/javascripts/boards/queries/group_board.query.graphql
new file mode 100644
index 00000000000..cb42cb3f73d
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/group_board.query.graphql
@@ -0,0 +1,13 @@
+#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
+
+query GroupBoard($fullPath: ID!, $boardId: ID!) {
+ group(fullPath: $fullPath) {
+ board(id: $boardId) {
+ lists {
+ nodes {
+ ...BoardListFragment
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/boards/queries/project_board.query.graphql b/app/assets/javascripts/boards/queries/project_board.query.graphql
new file mode 100644
index 00000000000..4620a7e0fd5
--- /dev/null
+++ b/app/assets/javascripts/boards/queries/project_board.query.graphql
@@ -0,0 +1,13 @@
+#import "ee_else_ce/boards/queries/board_list.fragment.graphql"
+
+query ProjectBoard($fullPath: ID!, $boardId: ID!) {
+ project(fullPath: $fullPath) {
+ board(id: $boardId) {
+ lists {
+ nodes {
+ ...BoardListFragment
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index e5447080e37..fdbd7e89bfb 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -6,7 +6,12 @@ import { sortBy } from 'lodash';
import Vue from 'vue';
import Cookies from 'js-cookie';
import BoardsStoreEE from 'ee_else_ce/boards/stores/boards_store_ee';
-import { getUrlParamsArray, parseBoolean } from '~/lib/utils/common_utils';
+import {
+ urlParamsToObject,
+ getUrlParamsArray,
+ parseBoolean,
+ convertObjectPropsToCamelCase,
+} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import { mergeUrlParams } from '~/lib/utils/url_utility';
@@ -23,7 +28,6 @@ const boardsStore = {
limitToHours: false,
},
scopedLabels: {
- helpLink: '',
enabled: false,
},
filter: {
@@ -75,7 +79,15 @@ const boardsStore = {
this.state.currentPage = page;
},
addList(listObj) {
- const list = new List(listObj);
+ const listType = listObj.listType || listObj.list_type;
+ let { position } = listObj;
+ if (listType === ListType.closed) {
+ position = Infinity;
+ } else if (listType === ListType.backlog) {
+ position = -1;
+ }
+
+ const list = new List({ ...listObj, position });
this.state.lists = sortBy([...this.state.lists, list], 'position');
return list;
},
@@ -121,6 +133,50 @@ const boardsStore = {
path: '',
});
},
+ addListIssue(list, issue, listFrom, newIndex) {
+ let moveBeforeId = null;
+ let moveAfterId = null;
+
+ if (!list.findIssue(issue.id)) {
+ if (newIndex !== undefined) {
+ list.issues.splice(newIndex, 0, issue);
+
+ if (list.issues[newIndex - 1]) {
+ moveBeforeId = list.issues[newIndex - 1].id;
+ }
+
+ if (list.issues[newIndex + 1]) {
+ moveAfterId = list.issues[newIndex + 1].id;
+ }
+ } else {
+ list.issues.push(issue);
+ }
+
+ if (list.label) {
+ issue.addLabel(list.label);
+ }
+
+ if (list.assignee) {
+ if (listFrom && listFrom.type === 'assignee') {
+ issue.removeAssignee(listFrom.assignee);
+ }
+ issue.addAssignee(list.assignee);
+ }
+
+ if (IS_EE && list.milestone) {
+ if (listFrom && listFrom.type === 'milestone') {
+ issue.removeMilestone(listFrom.milestone);
+ }
+ issue.addMilestone(list.milestone);
+ }
+
+ if (listFrom) {
+ list.issuesSize += 1;
+
+ list.updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId);
+ }
+ }
+ },
welcomeIsHidden() {
return parseBoolean(Cookies.get('issue_board_welcome_hidden'));
},
@@ -487,6 +543,36 @@ const boardsStore = {
});
},
+ getListIssues(list, emptyIssues = true) {
+ const data = {
+ ...urlParamsToObject(this.filter.path),
+ page: list.page,
+ };
+
+ if (list.label && data.label_name) {
+ data.label_name = data.label_name.filter(label => label !== list.label.title);
+ }
+
+ if (emptyIssues) {
+ list.loading = true;
+ }
+
+ return this.getIssuesForList(list.id, data)
+ .then(res => res.data)
+ .then(data => {
+ list.loading = false;
+ list.issuesSize = data.size;
+
+ if (emptyIssues) {
+ list.issues = [];
+ }
+
+ list.createIssues(data.issues);
+
+ return data;
+ });
+ },
+
getIssuesForList(id, filter = {}) {
const data = { id };
Object.keys(filter).forEach(key => {
@@ -632,6 +718,28 @@ const boardsStore = {
issue.assignees = obj.assignees.map(a => new ListAssignee(a));
}
},
+ updateIssue(issue) {
+ const data = {
+ issue: {
+ milestone_id: issue.milestone ? issue.milestone.id : null,
+ due_date: issue.dueDate,
+ assignee_ids: issue.assignees.length > 0 ? issue.assignees.map(({ id }) => id) : [0],
+ label_ids: issue.labels.length > 0 ? issue.labels.map(({ id }) => id) : [''],
+ },
+ };
+
+ return axios.patch(`${issue.path}.json`, data).then(({ data: body = {} } = {}) => {
+ /**
+ * Since post implementation of Scoped labels, server can reject
+ * same key-ed labels. To keep the UI and server Model consistent,
+ * we're just assigning labels that server echo's back to us when we
+ * PATCH the said object.
+ */
+ if (body) {
+ issue.labels = convertObjectPropsToCamelCase(body.labels, { deep: true });
+ }
+ });
+ },
};
BoardsStoreEE.initEESpecific(boardsStore);
diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js
index 731aea996fb..10aac2f649e 100644
--- a/app/assets/javascripts/boards/stores/state.js
+++ b/app/assets/javascripts/boards/stores/state.js
@@ -1,4 +1,6 @@
+import { inactiveListId } from '~/boards/constants';
+
export default () => ({
isShowingLabels: true,
- activeListId: 0,
+ activeListId: inactiveListId,
});
diff --git a/app/assets/javascripts/boards/toggle_focus.js b/app/assets/javascripts/boards/toggle_focus.js
index 2d1ec238274..a437a34c948 100644
--- a/app/assets/javascripts/boards/toggle_focus.js
+++ b/app/assets/javascripts/boards/toggle_focus.js
@@ -1 +1,45 @@
-export default () => {};
+import $ from 'jquery';
+import Vue from 'vue';
+import collapseIcon from './icons/fullscreen_collapse.svg';
+import expandIcon from './icons/fullscreen_expand.svg';
+
+export default (ModalStore, boardsStore) => {
+ const issueBoardsContent = document.querySelector('.content-wrapper > .js-focus-mode-board');
+
+ return new Vue({
+ el: document.getElementById('js-toggle-focus-btn'),
+ data: {
+ modal: ModalStore.store,
+ store: boardsStore.state,
+ isFullscreen: false,
+ },
+ methods: {
+ toggleFocusMode() {
+ $(this.$refs.toggleFocusModeButton).tooltip('hide');
+ issueBoardsContent.classList.toggle('is-focused');
+
+ this.isFullscreen = !this.isFullscreen;
+ },
+ },
+ template: `
+ <div class="board-extra-actions">
+ <a
+ href="#"
+ class="btn btn-default has-tooltip prepend-left-10 js-focus-mode-btn"
+ data-qa-selector="focus_mode_button"
+ role="button"
+ aria-label="Toggle focus mode"
+ title="Toggle focus mode"
+ ref="toggleFocusModeButton"
+ @click="toggleFocusMode">
+ <span v-show="isFullscreen">
+ ${collapseIcon}
+ </span>
+ <span v-show="!isFullscreen">
+ ${expandIcon}
+ </span>
+ </a>
+ </div>
+ `,
+ });
+};