Commit 92f8df4a authored by Paul Slaughter's avatar Paul Slaughter Committed by Phil Hughes

Extract EE specific files/lines for app/assets/javascripts/boards

parent de46bf61
......@@ -28,29 +28,29 @@ export default {
},
},
methods: {
buildUpdateRequest(list) {
return {
add_label_ids: [list.label.id],
};
},
addIssues() {
const firstListIndex = 1;
const list = this.modal.selectedList || this.state.lists[firstListIndex];
const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.id);
const { currentBoard } = this.state;
const boardLabelIds = currentBoard.labels.map(label => label.id);
const assigneeIds = currentBoard.assignee && [currentBoard.assignee.id];
const req = this.buildUpdateRequest(list);
// Post the data to the backend
gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id, ...boardLabelIds],
milestone_id: currentBoard.milestone_id,
assignee_ids: assigneeIds,
weight: currentBoard.weight,
}).catch(() => {
Flash(__('Failed to update issues, please try again.'));
gl.boardService
.bulkUpdate(issueIds, req)
.catch(() => {
Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
});
// Add the issues on the frontend
selectedIssues.forEach((issue) => {
......
......@@ -121,8 +121,7 @@
<div
v-if="issuesCount > 0 && issues.length === 0"
class="empty-state add-issues-empty-state-filter text-center">
<div
class="svg-content">
<div class="svg-content">
<img :src="emptyStateSvg" />
</div>
<div class="text-content">
......
......@@ -5,7 +5,7 @@
const Store = gl.issueBoards.BoardsStore;
export default {
export default Vue.extend({
props: {
issue: {
type: Object,
......@@ -23,42 +23,16 @@
},
methods: {
removeIssue() {
const board = Store.state.currentBoard;
const { issue } = this;
const lists = issue.getLists();
const boardLabelIds = board.labels.map(label => label.id);
const listLabelIds = lists.map(list => list.label.id);
let labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id))
.filter(id => !boardLabelIds.includes(id));
if (labelIds.length === 0) {
labelIds = [''];
}
let assigneeIds = issue.assignees
.map(assignee => assignee.id)
.filter(id => id !== board.assignee.id);
if (assigneeIds.length === 0) {
// for backend to explicitly set No Assignee
assigneeIds = ['0'];
}
const req = this.buildPatchRequest(issue, lists);
const data = {
issue: {
label_ids: labelIds,
assignee_ids: assigneeIds,
},
issue: this.seedPatchRequest(issue, req),
};
if (board.milestone_id) {
data.issue.milestone_id = -1;
}
if (board.weight) {
data.issue.weight = null;
if (data.issue.label_ids.length === 0) {
data.issue.label_ids = [''];
}
// Post the remove data
......@@ -77,8 +51,30 @@
Store.detail.issue = {};
},
/**
* Build the default patch request.
*/
buildPatchRequest(issue, lists) {
const listLabelIds = lists.map(list => list.label.id);
const labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id));
return {
label_ids: labelIds,
};
},
/**
* Seed the given patch request.
*
* (This is overridden in EE)
*/
seedPatchRequest(issue, req) {
return req;
},
},
};
});
</script>
<template>
<div
......
......@@ -12,13 +12,10 @@ import '~/vue_shared/models/assignee';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first
import './models/issue';
import './models/list';
import './models/milestone';
import './models/project';
import './stores/boards_store';
import ModalStore from './stores/modal_store';
import BoardService from './services/board_service';
import modalMixin from './mixins/modal_mixins';
import './mixins/sortable_default_options';
import './filters/due_date_filters';
......@@ -28,7 +25,12 @@ import './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first
import 'ee/boards/models/list'; // eslint-disable-line import/first
import 'ee/boards/models/issue'; // eslint-disable-line import/first
import 'ee/boards/models/project'; // eslint-disable-line import/first
import BoardService from 'ee/boards/services/board_service'; // eslint-disable-line import/first
import 'ee/boards/components/board_sidebar'; // eslint-disable-line import/first
import 'ee/boards/components/modal/index'; // eslint-disable-line import/first
import 'ee/boards/components/boards_selector'; // eslint-disable-line import/first
import collapseIcon from 'ee/boards/icons/fullscreen_collapse.svg'; // eslint-disable-line import/first
import expandIcon from 'ee/boards/icons/fullscreen_expand.svg'; // eslint-disable-line import/first
......
......@@ -4,7 +4,8 @@
/* global ListAssignee */
import Vue from 'vue';
import IssueProject from 'ee/boards/models/project';
import '~/vue_shared/models/label';
import IssueProject from './project';
class ListIssue {
constructor (obj, defaultAvatar) {
......@@ -20,18 +21,14 @@ class ListIssue {
this.position = obj.relative_position || Infinity;
this.isFetching = {
subscriptions: true,
weight: true,
};
this.isLoading = {
weight: false,
};
this.isLoading = {};
this.sidebarInfoEndpoint = obj.issue_sidebar_endpoint;
this.referencePath = obj.reference_path;
this.path = obj.real_path;
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
this.weight = obj.weight;
if (obj.project) {
this.project = new IssueProject(obj.project);
......
......@@ -7,6 +7,24 @@ import queryData from '../utils/query_data';
const PER_PAGE = 20;
const TYPES = {
backlog: {
isPreset: true,
isExpandable: true,
isBlank: false,
},
closed: {
isPreset: true,
isExpandable: true,
isBlank: false,
},
blank: {
isPreset: true,
isExpandable: false,
isBlank: true,
},
};
class List {
constructor(obj, defaultAvatar) {
this.id = obj.id;
......@@ -14,8 +32,10 @@ class List {
this.position = obj.position;
this.title = obj.title;
this.type = obj.list_type;
this.preset = ['backlog', 'closed', 'blank', 'promotion'].indexOf(this.type) > -1;
this.isExpandable = ['backlog', 'closed'].indexOf(this.type) > -1;
const typeInfo = this.getTypeInfo(this.type);
this.preset = !!typeInfo.isPreset;
this.isExpandable = !!typeInfo.isExpandable;
this.isExpanded = true;
this.page = 1;
this.loading = true;
......@@ -31,7 +51,7 @@ class List {
this.title = this.assignee.name;
}
if (this.type !== 'blank' && this.type !== 'promotion' && this.id) {
if (!typeInfo.isBlank && this.id) {
this.getIssues().catch(() => {
// TODO: handle request error
});
......@@ -126,23 +146,7 @@ class List {
return gl.boardService
.newIssue(this.id, issue)
.then(res => res.data)
.then(data => {
issue.id = data.id;
issue.iid = data.iid;
issue.milestone = data.milestone;
issue.project = data.project;
issue.assignees = Array.isArray(data.assignees)
? data.assignees.map(assignee => new ListAssignee(assignee))
: data.assignees;
issue.labels = data.labels;
issue.path = data.real_path;
issue.referencePath = data.reference_path;
if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
}
});
.then(data => this.onNewIssueResponse(issue, data));
}
createIssues(data) {
......@@ -222,6 +226,25 @@ class List {
return !matchesRemove;
});
}
getTypeInfo (type) {
return TYPES[type] || {};
}
onNewIssueResponse (issue, data) {
issue.id = data.id;
issue.iid = data.iid;
issue.project = data.project;
issue.path = data.real_path;
issue.referencePath = data.reference_path;
if (this.issuesSize > 1) {
const moveBeforeId = this.issues[1].id;
gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
}
}
}
window.List = List;
export default List;
......@@ -22,36 +22,6 @@ export default class BoardService {
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
}
allBoards() {
return axios.get(this.generateBoardsPath());
}
createBoard(board) {
const boardPayload = { ...board };
boardPayload.label_ids = (board.labels || []).map(b => b.id);
if (boardPayload.label_ids.length === 0) {
boardPayload.label_ids = [''];
}
if (boardPayload.assignee) {
boardPayload.assignee_id = boardPayload.assignee.id;
}
if (boardPayload.milestone) {
boardPayload.milestone_id = boardPayload.milestone.id;
}
if (boardPayload.id) {
return axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload });
}
return axios.post(this.generateBoardsPath(), { board: boardPayload });
}
deleteBoard({ id }) {
return axios.delete(this.generateBoardsPath(id));
}
all() {
return axios.get(this.listsEndpoint);
}
......@@ -122,12 +92,6 @@ export default class BoardService {
return axios.get(endpoint);
}
static updateWeight(endpoint, weight = null) {
return axios.put(endpoint, {
weight,
});
}
static toggleIssueSubscription(endpoint) {
return axios.post(endpoint);
}
......
......@@ -5,6 +5,7 @@
import $ from 'jquery';
import _ from 'underscore';
import { __ } from '~/locale';
import '~/gl_dropdown';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store';
......
......@@ -135,8 +135,7 @@ js-project-select form-control prepend-top-10 append-bottom-10"
<option
v-for="project in projects"
:key="project.id"
:value="project.id
">
:value="project.id">
{{ project.name }}
</option>
</select>
......
import '~/boards/components/board_sidebar';
import RemoveBtn from './sidebar/remove_issue';
const base = gl.issueBoards.BoardSidebar;
gl.issueBoards.BoardSidebar = base.extend({
components: {
RemoveBtn,
},
});
import Vue from 'vue';
import $ from 'jquery';
import { throttle } from 'underscore';
import '~/boards/stores/boards_store';
import BoardForm from './board_form.vue';
import AssigneesList from './assignees_list';
......
import Vue from 'vue';
import base from '~/boards/components/modal/footer.vue';
export default Vue.extend(base, {
methods: {
buildUpdateRequest(list) {
const { currentBoard } = this.state;
const boardLabelIds = currentBoard.labels.map(label => label.id);
const assigneeIds = currentBoard.assignee && [currentBoard.assignee.id];
return {
add_label_ids: [list.label.id, ...boardLabelIds],
milestone_id: currentBoard.milestone_id,
assignee_ids: assigneeIds,
weight: currentBoard.weight,
};
},
},
});
import Vue from 'vue';
import base from '~/boards/components/modal/index.vue';
import ModalFooter from './footer';
gl.issueBoards.IssuesModal = Vue.extend(base, {
components: {
ModalFooter,
},
});
import base from '~/boards/components/sidebar/remove_issue.vue';
const Store = gl.issueBoards.BoardsStore;
export default base.extend({
methods: {
seedPatchRequest(issue, req) {
const board = Store.state.currentBoard;
const boardLabelIds = board.labels.map(label => label.id);
req.label_ids = req.label_ids.filter(id => !boardLabelIds.includes(id));
if (board.milestone_id) {
req.milestone_id = -1;
}
if (board.weight) {
req.weight = null;
}
const boardAssignee = board.assignee ? board.assignee.id : null;
const assigneeIds = issue.assignees
.map(assignee => assignee.id)
.filter(id => id !== boardAssignee);
return {
...req,
assignee_ids: assigneeIds.length ? assigneeIds : ['0'],
};
},
},
});
import ListIssue from '~/boards/models/issue';
import IssueProjectEE from './project';
class ListIssueEE extends ListIssue {
constructor(obj, defaultAvatar) {
super(obj, defaultAvatar, {
IssueProject: IssueProjectEE,
});
this.isFetching.weight = true;
this.isLoading.weight = false;
this.weight = obj.weight;
if (obj.project) {
this.project = new IssueProjectEE(obj.project);
}
}
}
window.ListIssue = ListIssueEE;
export default ListIssueEE;
/* eslint-disable no-param-reassign */
import List from '~/boards/models/list';
import ListAssignee from '~/vue_shared/models/assignee';
const EE_TYPES = {
promotion: {
isPreset: true,
isExpandable: false,
isBlank: true,
},
};
class ListEE extends List {
getTypeInfo(type) {
return EE_TYPES[type] || super.getTypeInfo(type);
}
onNewIssueResponse(issue, data) {
issue.milestone = data.milestone;
issue.assignees = Array.isArray(data.assignees)
? data.assignees.map(assignee => new ListAssignee(assignee))
: data.assignees;
issue.labels = data.labels;
super.onNewIssueResponse(issue, data);
}
}
window.List = ListEE;
export default ListEE;
import axios from '~/lib/utils/axios_utils';
import BoardService from '~/boards/services/board_service';
export default class BoardServiceEE extends BoardService {
allBoards() {
return axios.get(this.generateBoardsPath());
}
createBoard(board) {
const boardPayload = { ...board };
boardPayload.label_ids = (board.labels || []).map(b => b.id);
if (boardPayload.label_ids.length === 0) {
boardPayload.label_ids = [''];
}
if (boardPayload.assignee) {
boardPayload.assignee_id = boardPayload.assignee.id;
}
if (boardPayload.milestone) {
boardPayload.milestone_id = boardPayload.milestone.id;
}
if (boardPayload.id) {
return axios.put(this.generateBoardsPath(boardPayload.id), { board: boardPayload });
}
return axios.post(this.generateBoardsPath(), { board: boardPayload });
}
deleteBoard({ id }) {
return axios.delete(this.generateBoardsPath(id));
}
static updateWeight(endpoint, weight = null) {
return axios.put(endpoint, {
weight,
});
}
}
import Vue from 'vue';
import BoardService from '~/boards/services/board_service';
import BoardService from 'ee/boards/services/board_service';
import 'ee/boards/components/boards_selector';
import setTimeoutPromiseHelper from 'spec/helpers/set_timeout_promise_helper';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
......
import Vue from 'vue';
import '~/boards/models/issue';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import ListIssue from '~/boards/models/issue';
import ListIssue from 'ee/boards/models/issue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { listObj } from './mock_data';
import { listObj } from 'spec/boards/mock_data';
describe('Issue card component', () => {
let vm;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment