summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean McGivern <sean@mcgivern.me.uk>2017-09-07 13:42:23 +0000
committerSean McGivern <sean@mcgivern.me.uk>2017-09-07 13:42:23 +0000
commitb4778a586665fe522ae3f4f7a567766b696b847d (patch)
tree133d25f06731111382632e8f42b81532b9b19b81
parentcb555da178573ceac01f0b76060572545857364a (diff)
parenta1a839c99f7d9ce69ce0712d93951dba216ecb11 (diff)
downloadgitlab-ce-b4778a586665fe522ae3f4f7a567766b696b847d.tar.gz
Merge branch 'ee_issue_928_backport' into 'master'
Group boards CE backport See merge request !13883
-rw-r--r--app/assets/javascripts/api.js16
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js51
-rw-r--r--app/assets/javascripts/boards/components/board_list.js10
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.js5
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js9
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js2
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js28
-rw-r--r--app/assets/javascripts/boards/models/issue.js4
-rw-r--r--app/assets/javascripts/boards/models/label.js1
-rw-r--r--app/assets/javascripts/boards/models/list.js28
-rw-r--r--app/assets/javascripts/boards/services/board_service.js20
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss63
-rw-r--r--app/controllers/boards/application_controller.rb21
-rw-r--r--app/controllers/boards/issues_controller.rb90
-rw-r--r--app/controllers/boards/lists_controller.rb75
-rw-r--r--app/controllers/concerns/boards_responses.rb42
-rw-r--r--app/controllers/projects/boards/application_controller.rb15
-rw-r--r--app/controllers/projects/boards/issues_controller.rb94
-rw-r--r--app/controllers/projects/boards/lists_controller.rb86
-rw-r--r--app/controllers/projects/boards_controller.rb27
-rw-r--r--app/helpers/boards_helper.rb77
-rw-r--r--app/helpers/issuables_helper.rb8
-rw-r--r--app/helpers/labels_helper.rb7
-rw-r--r--app/helpers/search_helper.rb14
-rw-r--r--app/models/board.rb14
-rw-r--r--app/models/concerns/relative_positioning.rb14
-rw-r--r--app/models/label.rb4
-rw-r--r--app/models/project.rb8
-rw-r--r--app/services/boards/base_service.rb10
-rw-r--r--app/services/boards/create_service.rb6
-rw-r--r--app/services/boards/issues/create_service.rb12
-rw-r--r--app/services/boards/issues/list_service.rb10
-rw-r--r--app/services/boards/issues/move_service.rb20
-rw-r--r--app/services/boards/list_service.rb8
-rw-r--r--app/services/boards/lists/create_service.rb9
-rw-r--r--app/services/boards/lists/destroy_service.rb2
-rw-r--r--app/services/boards/lists/generate_service.rb6
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/boards/lists/move_service.rb2
-rw-r--r--app/services/issues/update_service.rb16
-rw-r--r--app/views/projects/boards/index.html.haml2
-rw-r--r--app/views/projects/boards/show.html.haml2
-rw-r--r--app/views/shared/boards/_show.html.haml (renamed from app/views/projects/boards/_show.html.haml)4
-rw-r--r--app/views/shared/boards/components/_board.html.haml (renamed from app/views/projects/boards/components/_board.html.haml)23
-rw-r--r--app/views/shared/boards/components/_sidebar.html.haml (renamed from app/views/projects/boards/components/_sidebar.html.haml)13
-rw-r--r--app/views/shared/boards/components/sidebar/_assignee.html.haml (renamed from app/views/projects/boards/components/sidebar/_assignee.html.haml)12
-rw-r--r--app/views/shared/boards/components/sidebar/_due_date.html.haml (renamed from app/views/projects/boards/components/sidebar/_due_date.html.haml)8
-rw-r--r--app/views/shared/boards/components/sidebar/_labels.html.haml (renamed from app/views/projects/boards/components/sidebar/_labels.html.haml)17
-rw-r--r--app/views/shared/boards/components/sidebar/_milestone.html.haml (renamed from app/views/projects/boards/components/sidebar/_milestone.html.haml)10
-rw-r--r--app/views/shared/boards/components/sidebar/_notifications.html.haml (renamed from app/views/projects/boards/components/sidebar/_notifications.html.haml)2
-rw-r--r--app/views/shared/boards/index.html.haml1
-rw-r--r--app/views/shared/boards/show.html.haml1
-rw-r--r--app/views/shared/issuable/_label_page_default.html.haml11
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml6
-rw-r--r--config/routes.rb13
-rw-r--r--config/routes/project.rb14
-rw-r--r--doc/README.md2
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb (renamed from spec/controllers/projects/boards/issues_controller_spec.rb)32
-rw-r--r--spec/controllers/boards/lists_controller_spec.rb (renamed from spec/controllers/projects/boards/lists_controller_spec.rb)2
-rw-r--r--spec/factories/milestones.rb4
-rw-r--r--spec/fixtures/api/schemas/issue.json6
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js3
-rw-r--r--spec/javascripts/boards/board_card_spec.js5
-rw-r--r--spec/javascripts/boards/board_list_spec.js4
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js3
-rw-r--r--spec/javascripts/boards/boards_store_spec.js6
-rw-r--r--spec/javascripts/boards/components/board_spec.js10
-rw-r--r--spec/javascripts/boards/issue_card_spec.js95
-rw-r--r--spec/javascripts/boards/issue_spec.js4
-rw-r--r--spec/javascripts/boards/list_spec.js15
-rw-r--r--spec/javascripts/boards/mock_data.js22
-rw-r--r--spec/javascripts/boards/modal_store_spec.js2
-rw-r--r--spec/services/boards/issues/create_service_spec.rb2
-rw-r--r--spec/services/boards/issues/move_service_spec.rb2
-rw-r--r--spec/services/issues/update_service_spec.rb2
78 files changed, 774 insertions, 527 deletions
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 8acddd6194c..38d1effc77c 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -6,7 +6,8 @@ const Api = {
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
- labelsPath: '/:namespace_path/:project_path/labels',
+ projectLabelsPath: '/:namespace_path/:project_path/labels',
+ groupLabelsPath: '/groups/:namespace_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
@@ -74,9 +75,16 @@ const Api = {
},
newLabel(namespacePath, projectPath, data, callback) {
- const url = Api.buildUrl(Api.labelsPath)
- .replace(':namespace_path', namespacePath)
- .replace(':project_path', projectPath);
+ let url;
+
+ if (projectPath) {
+ url = Api.buildUrl(Api.projectLabelsPath)
+ .replace(':namespace_path', namespacePath)
+ .replace(':project_path', projectPath);
+ } else {
+ url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
+ }
+
return $.ajax({
url,
type: 'POST',
diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js
index 89c14180149..ea00efe4b46 100644
--- a/app/assets/javascripts/boards/boards_bundle.js
+++ b/app/assets/javascripts/boards/boards_bundle.js
@@ -53,7 +53,8 @@ $(() => {
data: {
state: Store.state,
loading: true,
- endpoint: $boardApp.dataset.endpoint,
+ boardsEndpoint: $boardApp.dataset.boardsEndpoint,
+ listsEndpoint: $boardApp.dataset.listsEndpoint,
boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase,
@@ -68,7 +69,13 @@ $(() => {
},
},
created () {
- gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
+ gl.boardService = new BoardService({
+ boardsEndpoint: this.boardsEndpoint,
+ listsEndpoint: this.listsEndpoint,
+ bulkUpdatePath: this.bulkUpdatePath,
+ boardId: this.boardId,
+ });
+ Store.rootPath = this.boardsEndpoint;
this.filterManager = new FilteredSearchBoards(Store.filter, true);
this.filterManager.setup();
@@ -112,19 +119,21 @@ $(() => {
gl.IssueBoardsSearch = new Vue({
el: document.getElementById('js-add-list'),
data: {
- filters: Store.state.filters
+ filters: Store.state.filters,
},
mounted () {
gl.issueBoards.newListDropdownInit();
- }
+ },
});
gl.IssueBoardsModalAddBtn = new Vue({
mixins: [gl.issueBoards.ModalMixins],
el: document.getElementById('js-add-issues-btn'),
- data: {
- modal: ModalStore.store,
- store: Store.state,
+ data() {
+ return {
+ modal: ModalStore.store,
+ store: Store.state,
+ };
},
watch: {
disabled() {
@@ -133,6 +142,9 @@ $(() => {
},
computed: {
disabled() {
+ if (!this.store) {
+ return true;
+ }
return !this.store.lists.filter(list => !list.preset).length;
},
tooltipTitle() {
@@ -145,7 +157,7 @@ $(() => {
},
methods: {
updateTooltip() {
- const $tooltip = $(this.$el);
+ const $tooltip = $(this.$refs.addIssuesButton);
this.$nextTick(() => {
if (this.disabled) {
@@ -165,16 +177,19 @@ $(() => {
this.updateTooltip();
},
template: `
- <button
- class="btn btn-create pull-right prepend-left-10"
- type="button"
- data-placement="bottom"
- :class="{ 'disabled': disabled }"
- :title="tooltipTitle"
- :aria-disabled="disabled"
- @click="openModal">
- Add issues
- </button>
+ <div class="board-extra-actions">
+ <button
+ class="btn btn-create prepend-left-10"
+ type="button"
+ data-placement="bottom"
+ ref="addIssuesButton"
+ :class="{ 'disabled': disabled }"
+ :title="tooltipTitle"
+ :aria-disabled="disabled"
+ @click="openModal">
+ Add issues
+ </button>
+ </div>
`,
});
});
diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js
index bebca17fb1e..6159680f1e6 100644
--- a/app/assets/javascripts/boards/components/board_list.js
+++ b/app/assets/javascripts/boards/components/board_list.js
@@ -77,7 +77,7 @@ export default {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
- if ((this.scrollTop() > this.scrollHeight() - this.scrollOffset) && !this.list.loadingMore) {
+ if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
this.loadNextPage();
}
},
@@ -165,11 +165,9 @@ export default {
v-if="loading">
<loading-icon />
</div>
- <transition name="slide-down">
- <board-new-issue
- :list="list"
- v-if="list.type !== 'closed' && showIssueForm"/>
- </transition>
+ <board-new-issue
+ :list="list"
+ v-if="list.type !== 'closed' && showIssueForm"/>
<ul
class="board-list"
v-show="!loading"
diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js
index 4af8b0c7713..541b8049855 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.js
+++ b/app/assets/javascripts/boards/components/board_new_issue.js
@@ -6,7 +6,10 @@ const Store = gl.issueBoards.BoardsStore;
export default {
name: 'BoardNewIssue',
props: {
- list: Object,
+ list: {
+ type: Object,
+ required: true,
+ },
},
data() {
return {
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js
index 9a5d87ede7e..bf474879024 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
@@ -64,10 +64,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({
return this.issue.assignees.length > this.numberOverLimit;
},
cardUrl() {
- return `${this.issueLinkBase}/${this.issue.id}`;
+ return `${this.issueLinkBase}/${this.issue.iid}`;
},
issueId() {
- return `#${this.issue.id}`;
+ if (this.issue.iid) {
+ return `#${this.issue.iid}`;
+ }
+ return false;
},
showLabelFooter() {
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
@@ -143,7 +146,7 @@ gl.issueBoards.IssueCardInner = Vue.extend({
:title="issue.title">{{ issue.title }}</a>
<span
class="card-number"
- v-if="issue.id"
+ v-if="issueId"
>
{{ issueId }}
</span>
diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js
index 478a1335b2b..a656f0546c0 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js
+++ b/app/assets/javascripts/boards/components/modal/footer.js
@@ -29,7 +29,7 @@ gl.issueBoards.ModalFooter = Vue.extend({
const firstListIndex = 1;
const list = this.modal.selectedList || this.state.lists[firstListIndex];
const selectedIssues = ModalStore.getSelectedIssues();
- const issueIds = selectedIssues.map(issue => issue.globalId);
+ const issueIds = selectedIssues.map(issue => issue.id);
// Post the data to the backend
gl.boardService.bulkUpdate(issueIds, {
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 72bb9e10fbc..d7f203b3f96 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -27,7 +27,7 @@ gl.issueBoards.newListDropdownInit = () => {
$this.glDropdown({
data(term, callback) {
- $.get($this.attr('data-labels'))
+ $.get($this.attr('data-list-labels-path'))
.then((resp) => {
callback(resp);
});
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index 6a900d4abd0..1e623cf58b7 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
@@ -18,17 +18,33 @@ gl.issueBoards.RemoveIssueBtn = Vue.extend({
type: Object,
required: true,
},
+ issueUpdate: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ updateUrl() {
+ return this.issueUpdate;
+ },
},
methods: {
removeIssue() {
const issue = this.issue;
const lists = issue.getLists();
- const labelIds = lists.map(list => list.label.id);
-
- // Post the remove data
- gl.boardService.bulkUpdate([issue.globalId], {
- remove_label_ids: labelIds,
- }).catch(() => {
+ const listLabelIds = lists.map(list => list.label.id);
+ let labelIds = this.issue.labels
+ .map(label => label.id)
+ .filter(id => !listLabelIds.includes(id));
+ if (labelIds.length === 0) {
+ labelIds = [''];
+ }
+ const data = {
+ issue: {
+ label_ids: labelIds,
+ },
+ };
+ Vue.http.patch(this.updateUrl, data).catch(() => {
new Flash('Failed to remove issue from board, please try again.', 'alert');
lists.forEach((list) => {
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 6c2d8a3781b..407db176446 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -7,8 +7,8 @@ import Vue from 'vue';
class ListIssue {
constructor (obj, defaultAvatar) {
- this.globalId = obj.id;
- this.id = obj.iid;
+ this.id = obj.id;
+ this.iid = obj.iid;
this.title = obj.title;
this.confidential = obj.confidential;
this.dueDate = obj.due_date;
diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/boards/models/label.js
index 9af88d167d6..98c1ec014c4 100644
--- a/app/assets/javascripts/boards/models/label.js
+++ b/app/assets/javascripts/boards/models/label.js
@@ -4,6 +4,7 @@ class ListLabel {
constructor (obj) {
this.id = obj.id;
this.title = obj.title;
+ this.type = obj.type;
this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description;
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 08f7c5ddcd2..df2809e1805 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -110,11 +110,13 @@ class List {
return gl.boardService.newIssue(this.id, issue)
.then(resp => resp.json())
.then((data) => {
- issue.id = data.iid;
+ issue.id = data.id;
+ issue.iid = data.iid;
+ issue.project = data.project;
if (this.issuesSize > 1) {
- const moveBeforeIid = this.issues[1].id;
- gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeIid);
+ const moveBeforeId = this.issues[1].id;
+ gl.boardService.moveIssue(issue.id, null, null, null, moveBeforeId);
}
});
}
@@ -126,19 +128,19 @@ class List {
}
addIssue (issue, listFrom, newIndex) {
- let moveBeforeIid = null;
- let moveAfterIid = null;
+ 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]) {
- moveBeforeIid = this.issues[newIndex - 1].id;
+ moveBeforeId = this.issues[newIndex - 1].id;
}
if (this.issues[newIndex + 1]) {
- moveAfterIid = this.issues[newIndex + 1].id;
+ moveAfterId = this.issues[newIndex + 1].id;
}
} else {
this.issues.push(issue);
@@ -151,30 +153,30 @@ class List {
if (listFrom) {
this.issuesSize += 1;
- this.updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid);
+ this.updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId);
}
}
}
- moveIssue (issue, oldIndex, newIndex, moveBeforeIid, moveAfterIid) {
+ moveIssue (issue, oldIndex, newIndex, moveBeforeId, moveAfterId) {
this.issues.splice(oldIndex, 1);
this.issues.splice(newIndex, 0, issue);
- gl.boardService.moveIssue(issue.id, null, null, moveBeforeIid, moveAfterIid)
+ gl.boardService.moveIssue(issue.id, null, null, moveBeforeId, moveAfterId)
.catch(() => {
// TODO: handle request error
});
}
- updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) {
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid)
+ updateIssueLabel(issue, listFrom, moveBeforeId, moveAfterId) {
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeId, moveAfterId)
.catch(() => {
// TODO: handle request error
});
}
findIssue (id) {
- return this.issues.filter(issue => issue.id === id)[0];
+ return this.issues.find(issue => issue.id === id);
}
removeIssue (removeIssue) {
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index 3742507b236..38eea38f949 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -3,21 +3,21 @@
import Vue from 'vue';
class BoardService {
- constructor (root, bulkUpdatePath, boardId) {
- this.boards = Vue.resource(`${root}{/id}.json`, {}, {
+ constructor ({ boardsEndpoint, listsEndpoint, bulkUpdatePath, boardId }) {
+ this.boards = Vue.resource(`${boardsEndpoint}{/id}.json`, {}, {
issues: {
method: 'GET',
- url: `${root}/${boardId}/issues.json`
+ url: `${gon.relative_url_root}/boards/${boardId}/issues.json`,
}
});
- this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
+ this.lists = Vue.resource(`${listsEndpoint}{/id}`, {}, {
generate: {
method: 'POST',
- url: `${root}/${boardId}/lists/generate.json`
+ url: `${listsEndpoint}/generate.json`
}
});
- this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
- this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}, {
+ this.issue = Vue.resource(`${gon.relative_url_root}/boards/${boardId}/issues{/id}`, {});
+ this.issues = Vue.resource(`${listsEndpoint}{/id}/issues`, {}, {
bulkUpdate: {
method: 'POST',
url: bulkUpdatePath,
@@ -60,12 +60,12 @@ class BoardService {
return this.issues.get(data);
}
- moveIssue (id, from_list_id = null, to_list_id = null, move_before_iid = null, move_after_iid = null) {
+ moveIssue (id, from_list_id = null, to_list_id = null, move_before_id = null, move_after_id = null) {
return this.issue.update({ id }, {
from_list_id,
to_list_id,
- move_before_iid,
- move_after_iid,
+ move_before_id,
+ move_after_id,
});
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index d4a1bb8402c..cf557d94bdd 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -183,7 +183,7 @@
width: auto;
top: 100%;
left: 0;
- z-index: 200;
+ z-index: 300;
min-width: 240px;
max-width: 500px;
margin-top: 2px;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 314dd2d1a21..700be173039 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -117,13 +117,12 @@
}
.board-title {
- position: initial;
padding: 0;
border-bottom: 0;
> span {
display: block;
- transform: rotate(90deg) translate(25px, 0);
+ transform: rotate(90deg) translate(35px, 10px);
}
}
@@ -151,11 +150,18 @@
}
.board-header {
- border-top-left-radius: $border-radius-default;
- border-top-right-radius: $border-radius-default;
+ position: relative;
- &.has-border {
+ &.has-border::before {
border-top: 3px solid;
+ border-color: inherit;
+ border-top-left-radius: $border-radius-default;
+ border-top-right-radius: $border-radius-default;
+ content: '';
+ position: absolute;
+ width: calc(100% + 2px);
+ top: 0;
+ left: 0;
margin-top: -1px;
margin-right: -1px;
margin-left: -1px;
@@ -176,12 +182,16 @@
}
.board-title {
- position: relative;
margin: 0;
- padding: $gl-padding;
- padding-bottom: ($gl-padding + 3px);
+ padding: 12px $gl-padding;
font-size: 1em;
border-bottom: 1px solid $border-color;
+ display: flex;
+ align-items: center;
+}
+
+.board-title-text {
+ margin-right: auto;
}
.board-delete {
@@ -221,43 +231,10 @@
}
}
-.slide-down-enter {
- transform: translateY(-100%);
-}
-
-.slide-down-enter-active {
- transition: transform $fade-in-duration;
-
- + .board-list {
- transform: translateY(-136px);
- transition: none;
- }
-}
-
-.slide-down-enter-to {
- + .board-list {
- transform: translateY(0);
- transition: transform $fade-in-duration ease;
- }
-}
-
-.slide-down-leave {
- transform: translateY(0);
-}
-
-.slide-down-leave-active {
- transition: all $fade-in-duration;
- transform: translateY(-136px);
-
- + .board-list {
- transition: transform $fade-in-duration ease;
- transform: translateY(-136px);
- }
-}
-
.board-list-component {
height: calc(100% - 49px);
overflow: hidden;
+ position: relative;
}
.board-list {
@@ -429,7 +406,7 @@
}
.board-new-issue-form {
- z-index: 1;
+ z-index: 4;
margin: 5px;
}
diff --git a/app/controllers/boards/application_controller.rb b/app/controllers/boards/application_controller.rb
new file mode 100644
index 00000000000..b2675025fc0
--- /dev/null
+++ b/app/controllers/boards/application_controller.rb
@@ -0,0 +1,21 @@
+module Boards
+ class ApplicationController < ::ApplicationController
+ respond_to :json
+
+ rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
+
+ private
+
+ def board
+ @board ||= Board.find(params[:board_id])
+ end
+
+ def board_parent
+ @board_parent ||= board.parent
+ end
+
+ def record_not_found(exception)
+ render json: { error: exception.message }, status: :not_found
+ end
+ end
+end
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
new file mode 100644
index 00000000000..8d4ec2d6d9d
--- /dev/null
+++ b/app/controllers/boards/issues_controller.rb
@@ -0,0 +1,90 @@
+module Boards
+ class IssuesController < Boards::ApplicationController
+ include BoardsResponses
+
+ before_action :authorize_read_issue, only: [:index]
+ before_action :authorize_create_issue, only: [:create]
+ before_action :authorize_update_issue, only: [:update]
+ skip_before_action :authenticate_user!, only: [:index]
+
+ def index
+ issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute
+ issues = issues.page(params[:page]).per(params[:per] || 20)
+ make_sure_position_is_set(issues)
+
+ render json: {
+ issues: serialize_as_json(issues.preload(:project)),
+ size: issues.total_count
+ }
+ end
+
+ def create
+ service = Boards::Issues::CreateService.new(board_parent, project, current_user, issue_params)
+ issue = service.execute
+
+ if issue.valid?
+ render json: serialize_as_json(issue)
+ else
+ render json: issue.errors, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ service = Boards::Issues::MoveService.new(board_parent, current_user, move_params)
+
+ if service.execute(issue)
+ head :ok
+ else
+ head :unprocessable_entity
+ end
+ end
+
+ private
+
+ def make_sure_position_is_set(issues)
+ issues.each do |issue|
+ issue.move_to_end && issue.save unless issue.relative_position
+ end
+ end
+
+ def issue
+ @issue ||= issues_finder.execute.find(params[:id])
+ end
+
+ def filter_params
+ params.merge(board_id: params[:board_id], id: params[:list_id])
+ .reject { |_, value| value.nil? }
+ end
+
+ def issues_finder
+ IssuesFinder.new(current_user, project_id: board_parent.id)
+ end
+
+ def project
+ board_parent
+ end
+
+ def move_params
+ params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_id, :move_after_id)
+ end
+
+ def issue_params
+ params.require(:issue)
+ .permit(:title, :milestone_id, :project_id)
+ .merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
+ end
+
+ def serialize_as_json(resource)
+ resource.as_json(
+ labels: true,
+ only: [:id, :iid, :project_id, :title, :confidential, :due_date, :relative_position],
+ include: {
+ project: { only: [:id, :path] },
+ assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
+ milestone: { only: [:id, :title] }
+ },
+ user: current_user
+ )
+ end
+ end
+end
diff --git a/app/controllers/boards/lists_controller.rb b/app/controllers/boards/lists_controller.rb
new file mode 100644
index 00000000000..381fd4d7508
--- /dev/null
+++ b/app/controllers/boards/lists_controller.rb
@@ -0,0 +1,75 @@
+module Boards
+ class ListsController < Boards::ApplicationController
+ include BoardsResponses
+
+ before_action :authorize_admin_list, only: [:create, :update, :destroy, :generate]
+ before_action :authorize_read_list, only: [:index]
+ skip_before_action :authenticate_user!, only: [:index]
+
+ def index
+ lists = Boards::Lists::ListService.new(board.parent, current_user).execute(board)
+
+ render json: serialize_as_json(lists)
+ end
+
+ def create
+ list = Boards::Lists::CreateService.new(board.parent, current_user, list_params).execute(board)
+
+ if list.valid?
+ render json: serialize_as_json(list)
+ else
+ render json: list.errors, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ list = board.lists.movable.find(params[:id])
+ service = Boards::Lists::MoveService.new(board_parent, current_user, move_params)
+
+ if service.execute(list)
+ head :ok
+ else
+ head :unprocessable_entity
+ end
+ end
+
+ def destroy
+ list = board.lists.destroyable.find(params[:id])
+ service = Boards::Lists::DestroyService.new(board_parent, current_user)
+
+ if service.execute(list)
+ head :ok
+ else
+ head :unprocessable_entity
+ end
+ end
+
+ def generate
+ service = Boards::Lists::GenerateService.new(board_parent, current_user)
+
+ if service.execute(board)
+ render json: serialize_as_json(board.lists.movable)
+ else
+ head :unprocessable_entity
+ end
+ end
+
+ private
+
+ def list_params
+ params.require(:list).permit(:label_id)
+ end
+
+ def move_params
+ params.require(:list).permit(:position)
+ end
+
+ def serialize_as_json(resource)
+ resource.as_json(
+ only: [:id, :list_type, :position],
+ methods: [:title],
+ label: true
+ )
+ end
+ end
+end
diff --git a/app/controllers/concerns/boards_responses.rb b/app/controllers/concerns/boards_responses.rb
new file mode 100644
index 00000000000..2c9c095a5d7
--- /dev/null
+++ b/app/controllers/concerns/boards_responses.rb
@@ -0,0 +1,42 @@
+module BoardsResponses
+ def authorize_read_list
+ authorize_action_for!(board.parent, :read_list)
+ end
+
+ def authorize_read_issue
+ authorize_action_for!(board.parent, :read_issue)
+ end
+
+ def authorize_update_issue
+ authorize_action_for!(issue, :admin_issue)
+ end
+
+ def authorize_create_issue
+ authorize_action_for!(project, :admin_issue)
+ end
+
+ def authorize_admin_list
+ authorize_action_for!(board.parent, :admin_list)
+ end
+
+ def authorize_action_for!(resource, ability)
+ return render_403 unless can?(current_user, ability, resource)
+ end
+
+ def respond_with_boards
+ respond_with(@boards)
+ end
+
+ def respond_with_board
+ respond_with(@board)
+ end
+
+ def respond_with(resource)
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: serialize_as_json(resource)
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/boards/application_controller.rb b/app/controllers/projects/boards/application_controller.rb
deleted file mode 100644
index dad38fff6b9..00000000000
--- a/app/controllers/projects/boards/application_controller.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module Projects
- module Boards
- class ApplicationController < Projects::ApplicationController
- respond_to :json
-
- rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
-
- private
-
- def record_not_found(exception)
- render json: { error: exception.message }, status: :not_found
- end
- end
- end
-end
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
deleted file mode 100644
index 653e7bc7e40..00000000000
--- a/app/controllers/projects/boards/issues_controller.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-module Projects
- module Boards
- class IssuesController < Boards::ApplicationController
- before_action :authorize_read_issue!, only: [:index]
- before_action :authorize_create_issue!, only: [:create]
- before_action :authorize_update_issue!, only: [:update]
-
- def index
- issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
- issues = issues.page(params[:page]).per(params[:per] || 20)
- make_sure_position_is_set(issues)
-
- render json: {
- issues: serialize_as_json(issues),
- size: issues.total_count
- }
- end
-
- def create
- service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
- issue = service.execute
-
- if issue.valid?
- render json: serialize_as_json(issue)
- else
- render json: issue.errors, status: :unprocessable_entity
- end
- end
-
- def update
- service = ::Boards::Issues::MoveService.new(project, current_user, move_params)
-
- if service.execute(issue)
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- private
-
- def make_sure_position_is_set(issues)
- issues.each do |issue|
- issue.move_to_end && issue.save unless issue.relative_position
- end
- end
-
- def issue
- @issue ||=
- IssuesFinder.new(current_user, project_id: project.id)
- .execute
- .where(iid: params[:id])
- .first!
- end
-
- def authorize_read_issue!
- return render_403 unless can?(current_user, :read_issue, project)
- end
-
- def authorize_create_issue!
- return render_403 unless can?(current_user, :admin_issue, project)
- end
-
- def authorize_update_issue!
- return render_403 unless can?(current_user, :update_issue, issue)
- end
-
- def filter_params
- params.merge(board_id: params[:board_id], id: params[:list_id])
- .reject { |_, value| value.nil? }
- end
-
- def move_params
- params.permit(:board_id, :id, :from_list_id, :to_list_id, :move_before_iid, :move_after_iid)
- end
-
- def issue_params
- params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request)
- end
-
- def serialize_as_json(resource)
- resource.as_json(
- labels: true,
- only: [:id, :iid, :title, :confidential, :due_date, :relative_position],
- include: {
- assignees: { only: [:id, :name, :username], methods: [:avatar_url] },
- milestone: { only: [:id, :title] }
- },
- user: current_user
- )
- end
- end
- end
-end
diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb
deleted file mode 100644
index ad53bb749a0..00000000000
--- a/app/controllers/projects/boards/lists_controller.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-module Projects
- module Boards
- class ListsController < Boards::ApplicationController
- before_action :authorize_admin_list!, only: [:create, :update, :destroy, :generate]
- before_action :authorize_read_list!, only: [:index]
-
- def index
- lists = ::Boards::Lists::ListService.new(project, current_user).execute(board)
-
- render json: serialize_as_json(lists)
- end
-
- def create
- list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board)
-
- if list.valid?
- render json: serialize_as_json(list)
- else
- render json: list.errors, status: :unprocessable_entity
- end
- end
-
- def update
- list = board.lists.movable.find(params[:id])
- service = ::Boards::Lists::MoveService.new(project, current_user, move_params)
-
- if service.execute(list)
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- def destroy
- list = board.lists.destroyable.find(params[:id])
- service = ::Boards::Lists::DestroyService.new(project, current_user)
-
- if service.execute(list)
- head :ok
- else
- head :unprocessable_entity
- end
- end
-
- def generate
- service = ::Boards::Lists::GenerateService.new(project, current_user)
-
- if service.execute(board)
- render json: serialize_as_json(board.lists.movable)
- else
- head :unprocessable_entity
- end
- end
-
- private
-
- def authorize_admin_list!
- return render_403 unless can?(current_user, :admin_list, project)
- end
-
- def authorize_read_list!
- return render_403 unless can?(current_user, :read_list, project)
- end
-
- def board
- @board ||= project.boards.find(params[:board_id])
- end
-
- def list_params
- params.require(:list).permit(:label_id)
- end
-
- def move_params
- params.require(:list).permit(:position)
- end
-
- def serialize_as_json(resource)
- resource.as_json(
- only: [:id, :list_type, :position],
- methods: [:title],
- label: true
- )
- end
- end
- end
-end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 808affa4f98..d1b99ecce4a 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -1,32 +1,31 @@
class Projects::BoardsController < Projects::ApplicationController
+ include BoardsResponses
include IssuableCollections
before_action :authorize_read_board!, only: [:index, :show]
+ before_action :assign_endpoint_vars
def index
- @boards = ::Boards::ListService.new(project, current_user).execute
-
- respond_to do |format|
- format.html
- format.json do
- render json: serialize_as_json(@boards)
- end
- end
+ @boards = Boards::ListService.new(project, current_user).execute
+
+ respond_with_boards
end
def show
@board = project.boards.find(params[:id])
- respond_to do |format|
- format.html
- format.json do
- render json: serialize_as_json(@board)
- end
- end
+ respond_with_board
end
private
+ def assign_endpoint_vars
+ @boards_endpoint = project_boards_url(project)
+ @bulk_issues_path = bulk_update_project_issues_path(project)
+ @namespace_path = project.namespace.full_path
+ @labels_endpoint = project_labels_path(project)
+ end
+
def authorize_read_board!
return access_denied! unless can?(current_user, :read_board, project)
end
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 8b33c362a9c..4bd61aa8f86 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -1,15 +1,80 @@
module BoardsHelper
- def board_data
- board = @board || @boards.first
+ def board
+ @board ||= @board || @boards.first
+ end
+ def board_data
{
- endpoint: project_boards_path(@project),
+ boards_endpoint: @boards_endpoint,
+ lists_endpoint: board_lists_url(board),
board_id: board.id,
- disabled: "#{!can?(current_user, :admin_list, @project)}",
- issue_link_base: project_issues_path(@project),
+ disabled: "#{!can?(current_user, :admin_list, current_board_parent)}",
+ issue_link_base: build_issue_link_base,
root_path: root_path,
- bulk_update_path: bulk_update_project_issues_path(@project),
+ bulk_update_path: @bulk_issues_path,
default_avatar: image_path(default_avatar)
}
end
+
+ def build_issue_link_base
+ project_issues_path(@project)
+ end
+
+ def current_board_json
+ board = @board || @boards.first
+
+ board.to_json(
+ only: [:id, :name, :milestone_id],
+ include: {
+ milestone: { only: [:title] }
+ }
+ )
+ end
+
+ def board_base_url
+ project_boards_path(@project)
+ end
+
+ def multiple_boards_available?
+ current_board_parent.multiple_issue_boards_available?(current_user)
+ end
+
+ def current_board_path(board)
+ @current_board_path ||= project_board_path(current_board_parent, board)
+ end
+
+ def current_board_parent
+ @current_board_parent ||= @project
+ end
+
+ def can_admin_issue?
+ can?(current_user, :admin_issue, current_board_parent)
+ end
+
+ def board_list_data
+ {
+ toggle: "dropdown",
+ list_labels_path: labels_filter_path(true),
+ labels: labels_filter_path(true),
+ labels_endpoint: @labels_endpoint,
+ namespace_path: @namespace_path,
+ project_path: @project&.try(:path)
+ }
+ end
+
+ def board_sidebar_user_data
+ dropdown_options = issue_assignees_dropdown_options
+
+ {
+ toggle: 'dropdown',
+ field_name: 'issue[assignee_ids][]',
+ first_user: current_user&.username,
+ current_user: 'true',
+ project_id: @project&.try(:id),
+ null_user: 'true',
+ multi_select: 'true',
+ 'dropdown-header': dropdown_options[:data][:'dropdown-header'],
+ 'max-select': dropdown_options[:data][:'max-select']
+ }
+ end
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index ce2999e6696..66e1e607e01 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -347,6 +347,14 @@ module IssuablesHelper
end
end
+ def labels_path
+ if @project
+ project_labels_path(@project)
+ elsif @group
+ group_labels_path(@group)
+ end
+ end
+
def issuable_sidebar_options(issuable, can_edit_issuable)
{
endpoint: "#{issuable_json_path(issuable)}?basic=true",
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index e60513b35c7..e1ba7898ee6 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -121,13 +121,14 @@ module LabelsHelper
end
end
- def labels_filter_path
- return group_labels_path(@group, :json) if @group
-
+ def labels_filter_path(only_group_labels = false)
project = @target_project || @project
if project
project_labels_path(project, :json)
+ elsif @group
+ options = { only_group_labels: only_group_labels } if only_group_labels
+ group_labels_path(@group, :json, options)
else
dashboard_labels_path(:json)
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 98e824a8c65..af6683a548b 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -134,19 +134,21 @@ module SearchHelper
end
def search_filter_input_options(type)
- opts = {
- id: "filtered-search-#{type}",
- placeholder: 'Search or filter results...',
- data: {
- 'username-params' => @users.to_json(only: [:id, :username])
+ opts =
+ {
+ id: "filtered-search-#{type}",
+ placeholder: 'Search or filter results...',
+ data: {
+ 'username-params' => @users.to_json(only: [:id, :username])
+ }
}
- }
if @project.present?
opts[:data]['project-id'] = @project.id
opts[:data]['base-endpoint'] = project_path(@project)
else
# Group context
+ opts[:data]['group-id'] = @group.id
opts[:data]['base-endpoint'] = group_canonical_path(@group)
end
diff --git a/app/models/board.rb b/app/models/board.rb
index 97d0f550925..5bb7d3d3722 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -3,7 +3,19 @@ class Board < ActiveRecord::Base
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- validates :project, presence: true
+ validates :project, presence: true, if: :project_needed?
+
+ def project_needed?
+ true
+ end
+
+ def parent
+ project
+ end
+
+ def group_board?
+ false
+ end
def backlog_list
lists.merge(List.backlog).take
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 7cb9a28a284..e961c97e337 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -10,8 +10,12 @@ module RelativePositioning
after_save :save_positionable_neighbours
end
+ def project_ids
+ [project.id]
+ end
+
def max_relative_position
- self.class.in_projects(project.id).maximum(:relative_position)
+ self.class.in_projects(project_ids).maximum(:relative_position)
end
def prev_relative_position
@@ -19,7 +23,7 @@ module RelativePositioning
if self.relative_position
prev_pos = self.class
- .in_projects(project.id)
+ .in_projects(project_ids)
.where('relative_position < ?', self.relative_position)
.maximum(:relative_position)
end
@@ -32,7 +36,7 @@ module RelativePositioning
if self.relative_position
next_pos = self.class
- .in_projects(project.id)
+ .in_projects(project_ids)
.where('relative_position > ?', self.relative_position)
.minimum(:relative_position)
end
@@ -59,7 +63,7 @@ module RelativePositioning
pos_after = before.next_relative_position
if before.shift_after?
- issue_to_move = self.class.in_projects(project.id).find_by!(relative_position: pos_after)
+ issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_after)
issue_to_move.move_after
@positionable_neighbours = [issue_to_move]
@@ -74,7 +78,7 @@ module RelativePositioning
pos_before = after.prev_relative_position
if after.shift_before?
- issue_to_move = self.class.in_projects(project.id).find_by!(relative_position: pos_before)
+ issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_before)
issue_to_move.move_before
@positionable_neighbours = [issue_to_move]
diff --git a/app/models/label.rb b/app/models/label.rb
index 674bb3f2720..958141a7358 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -34,7 +34,8 @@ class Label < ActiveRecord::Base
scope :templates, -> { where(template: true) }
scope :with_title, ->(title) { where(title: title) }
- scope :on_project_boards, ->(project_id) { joins(lists: :board).merge(List.movable).where(boards: { project_id: project_id }) }
+ scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
+ scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
def self.prioritized(project)
joins(:priorities)
@@ -172,6 +173,7 @@ class Label < ActiveRecord::Base
def as_json(options = {})
super(options).tap do |json|
+ json[:type] = self.try(:type)
json[:priority] = priority(options[:project]) if options.key?(:project)
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index fdd516ec2ae..18800921c6c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1486,6 +1486,14 @@ class Project < ActiveRecord::Base
end
end
+ def multiple_issue_boards_available?(user)
+ feature_available?(:multiple_issue_boards, user)
+ end
+
+ def issue_board_milestone_available?(user = nil)
+ feature_available?(:issue_board_milestone, user)
+ end
+
def full_path_was
File.join(namespace.full_path, previous_changes['path'].first)
end
diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb
new file mode 100644
index 00000000000..72822ffffa1
--- /dev/null
+++ b/app/services/boards/base_service.rb
@@ -0,0 +1,10 @@
+module Boards
+ class BaseService < ::BaseService
+ # Parent can either a group or a project
+ attr_accessor :parent, :current_user, :params
+
+ def initialize(parent, user, params = {})
+ @parent, @current_user, @params = parent, user, params.dup
+ end
+ end
+end
diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb
index 9eedb9e65a2..bd0bb387662 100644
--- a/app/services/boards/create_service.rb
+++ b/app/services/boards/create_service.rb
@@ -1,5 +1,5 @@
module Boards
- class CreateService < BaseService
+ class CreateService < Boards::BaseService
def execute
create_board! if can_create_board?
end
@@ -7,11 +7,11 @@ module Boards
private
def can_create_board?
- project.boards.size == 0
+ parent.boards.size == 0
end
def create_board!
- board = project.boards.create(params)
+ board = parent.boards.create(params)
if board.persisted?
board.lists.create(list_type: :backlog)
diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb
index c0d7ff5b585..7c4a79f555e 100644
--- a/app/services/boards/issues/create_service.rb
+++ b/app/services/boards/issues/create_service.rb
@@ -1,6 +1,14 @@
module Boards
module Issues
- class CreateService < BaseService
+ class CreateService < Boards::BaseService
+ attr_accessor :project
+
+ def initialize(parent, project, user, params = {})
+ @project = project
+
+ super(parent, user, params)
+ end
+
def execute
create_issue(params.merge(label_ids: [list.label_id]))
end
@@ -8,7 +16,7 @@ module Boards
private
def board
- @board ||= project.boards.find(params.delete(:board_id))
+ @board ||= parent.boards.find(params.delete(:board_id))
end
def list
diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb
index eb345fead2d..d85d93e251b 100644
--- a/app/services/boards/issues/list_service.rb
+++ b/app/services/boards/issues/list_service.rb
@@ -1,6 +1,6 @@
module Boards
module Issues
- class ListService < BaseService
+ class ListService < Boards::BaseService
def execute
issues = IssuesFinder.new(current_user, filter_params).execute
issues = without_board_labels(issues) unless movable_list? || closed_list?
@@ -11,7 +11,7 @@ module Boards
private
def board
- @board ||= project.boards.find(params[:board_id])
+ @board ||= parent.boards.find(params[:board_id])
end
def list
@@ -33,14 +33,14 @@ module Boards
end
def filter_params
- set_project
+ set_parent
set_state
params
end
- def set_project
- params[:project_id] = project.id
+ def set_parent
+ params[:project_id] = parent.id
end
def set_state
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index ecabb2a48e4..797d6df7c1a 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -1,17 +1,17 @@
module Boards
module Issues
- class MoveService < BaseService
+ class MoveService < Boards::BaseService
def execute(issue)
return false unless can?(current_user, :update_issue, issue)
return false if issue_params.empty?
- update_service.execute(issue)
+ update(issue)
end
private
def board
- @board ||= project.boards.find(params[:board_id])
+ @board ||= parent.boards.find(params[:board_id])
end
def move_between_lists?
@@ -27,8 +27,8 @@ module Boards
@moving_to_list ||= board.lists.find_by(id: params[:to_list_id])
end
- def update_service
- ::Issues::UpdateService.new(project, current_user, issue_params)
+ def update(issue)
+ ::Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
end
def issue_params
@@ -42,7 +42,7 @@ module Boards
)
end
- attrs[:move_between_iids] = move_between_iids if move_between_iids
+ attrs[:move_between_ids] = move_between_ids if move_between_ids
attrs
end
@@ -61,16 +61,16 @@ module Boards
if moving_to_list.movable?
moving_from_list.label_id
else
- Label.on_project_boards(project.id).pluck(:label_id)
+ Label.on_project_boards(parent.id).pluck(:label_id)
end
Array(label_ids).compact
end
- def move_between_iids
- return unless params[:move_after_iid] || params[:move_before_iid]
+ def move_between_ids
+ return unless params[:move_after_id] || params[:move_before_id]
- [params[:move_after_iid], params[:move_before_iid]]
+ [params[:move_after_id], params[:move_before_id]]
end
end
end
diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb
index 84f1fc3a4e2..6d0dd0a9f99 100644
--- a/app/services/boards/list_service.rb
+++ b/app/services/boards/list_service.rb
@@ -1,14 +1,14 @@
module Boards
- class ListService < BaseService
+ class ListService < Boards::BaseService
def execute
- create_board! if project.boards.empty?
- project.boards
+ create_board! if parent.boards.empty?
+ parent.boards
end
private
def create_board!
- Boards::CreateService.new(project, current_user).execute
+ Boards::CreateService.new(parent, current_user).execute
end
end
end
diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb
index fe0d762ccd2..183556a1d6b 100644
--- a/app/services/boards/lists/create_service.rb
+++ b/app/services/boards/lists/create_service.rb
@@ -1,19 +1,18 @@
module Boards
module Lists
- class CreateService < BaseService
+ class CreateService < Boards::BaseService
def execute(board)
List.transaction do
- label = available_labels.find(params[:label_id])
+ label = available_labels_for(board).find(params[:label_id])
position = next_position(board)
-
create_list(board, label, position)
end
end
private
- def available_labels
- LabelsFinder.new(current_user, project_id: project.id).execute
+ def available_labels_for(board)
+ LabelsFinder.new(current_user, project_id: parent.id).execute
end
def next_position(board)
diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb
index f986e05944c..d75c5fd3dc6 100644
--- a/app/services/boards/lists/destroy_service.rb
+++ b/app/services/boards/lists/destroy_service.rb
@@ -1,6 +1,6 @@
module Boards
module Lists
- class DestroyService < BaseService
+ class DestroyService < Boards::BaseService
def execute(list)
return false unless list.destroyable?
diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb
index 939f9bfd068..05d4ab5dbcc 100644
--- a/app/services/boards/lists/generate_service.rb
+++ b/app/services/boards/lists/generate_service.rb
@@ -1,6 +1,6 @@
module Boards
module Lists
- class GenerateService < BaseService
+ class GenerateService < Boards::BaseService
def execute(board)
return false unless board.lists.movable.empty?
@@ -15,11 +15,11 @@ module Boards
def create_list(board, params)
label = find_or_create_label(params)
- Lists::CreateService.new(project, current_user, label_id: label.id).execute(board)
+ Lists::CreateService.new(parent, current_user, label_id: label.id).execute(board)
end
def find_or_create_label(params)
- ::Labels::FindOrCreateService.new(current_user, project, params).execute
+ ::Labels::FindOrCreateService.new(current_user, parent, params).execute
end
def label_params
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index df2a01a69e5..e57c95294af 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -1,6 +1,6 @@
module Boards
module Lists
- class ListService < BaseService
+ class ListService < Boards::BaseService
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb
index f2a68865f7b..7d0730e8332 100644
--- a/app/services/boards/lists/move_service.rb
+++ b/app/services/boards/lists/move_service.rb
@@ -1,6 +1,6 @@
module Boards
module Lists
- class MoveService < BaseService
+ class MoveService < Boards::BaseService
def execute(list)
@board = list.board
@old_position = list.position
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index deb4990eb4f..b4ca3966505 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -3,7 +3,7 @@ module Issues
include SpamCheckService
def execute(issue)
- handle_move_between_iids(issue)
+ handle_move_between_ids(issue)
filter_spam_check_params
change_issue_duplicate(issue)
move_issue_to_new_project(issue) || update(issue)
@@ -54,13 +54,13 @@ module Issues
end
end
- def handle_move_between_iids(issue)
- return unless params[:move_between_iids]
+ def handle_move_between_ids(issue)
+ return unless params[:move_between_ids]
- after_iid, before_iid = params.delete(:move_between_iids)
+ after_id, before_id = params.delete(:move_between_ids)
- issue_before = get_issue_if_allowed(issue.project, before_iid) if before_iid
- issue_after = get_issue_if_allowed(issue.project, after_iid) if after_iid
+ issue_before = get_issue_if_allowed(issue.project, before_id) if before_id
+ issue_after = get_issue_if_allowed(issue.project, after_id) if after_id
issue.move_between(issue_before, issue_after)
end
@@ -87,8 +87,8 @@ module Issues
private
- def get_issue_if_allowed(project, iid)
- issue = project.issues.find_by(iid: iid)
+ def get_issue_if_allowed(project, id)
+ issue = project.issues.find(id)
issue if can?(current_user, :update_issue, issue)
end
diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml
index 2a5b8b1441e..bb56769bd3f 100644
--- a/app/views/projects/boards/index.html.haml
+++ b/app/views/projects/boards/index.html.haml
@@ -1 +1 @@
-= render "show"
+= render "shared/boards/show", board: @boards.first
diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml
index 2a5b8b1441e..e5b5f6404bb 100644
--- a/app/views/projects/boards/show.html.haml
+++ b/app/views/projects/boards/show.html.haml
@@ -1 +1 @@
-= render "show"
+= render "shared/boards/show", board: @board
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 303e20e8780..1a50b7d4b69 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -9,7 +9,7 @@
= webpack_bundle_tag 'filtered_search'
= webpack_bundle_tag 'boards'
- %script#js-board-template{ type: "text/x-template" }= render "projects/boards/components/board"
+ %script#js-board-template{ type: "text/x-template" }= render "shared/boards/components/board"
%script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal
= render "projects/issues/head"
@@ -30,7 +30,7 @@
":root-path" => "rootPath",
":board-id" => "boardId",
":key" => "_uid" }
- = render "projects/boards/components/sidebar"
+ = render "shared/boards/components/sidebar"
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
"new-issue-path" => new_project_issue_path(@project),
"milestone-path" => milestones_filter_dropdown_path,
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index 64f5f6d7ba0..ce0aa72ab00 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -7,20 +7,26 @@
":class": "{ \"fa-caret-down\": list.isExpanded, \"fa-caret-right\": !list.isExpanded && list.position === -1, \"fa-caret-left\": !list.isExpanded && list.position !== -1 }",
"aria-hidden": "true" }
- %span.has-tooltip{ "v-if": "list.type !== \"label\"",
+ %span.board-title-text.has-tooltip{ "v-if": "list.type !== \"label\"",
":title" => '(list.label ? list.label.description : "")' }
{{ list.title }}
%span.has-tooltip{ "v-if": "list.type === \"label\"",
":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" },
- class: "label color-label title",
+ class: "label color-label title board-title-text",
":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.color ? list.label.text_color : \"#2e2e2e\") }" }
{{ list.title }}
- .issue-count-badge.pull-right.clearfix{ "v-if" => 'list.type !== "blank"' }
+ - if can?(current_user, :admin_list, current_board_parent)
+ %board-delete{ "inline-template" => true,
+ ":list" => "list",
+ "v-if" => "!list.preset && list.id" }
+ %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
+ = icon("trash")
+ .issue-count-badge.clearfix{ "v-if" => 'list.type !== "blank"' }
%span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' }
{{ list.issuesSize }}
- - if can?(current_user, :admin_issue, @project)
+ - if can?(current_user, :admin_list, current_board_parent)
%button.issue-count-badge-add-button.btn.btn-small.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button",
"@click" => "showNewIssueForm",
"v-if" => 'list.type !== "closed"',
@@ -28,12 +34,7 @@
"title" => "New issue",
data: { placement: "top", container: "body" } }
= icon("plus", class: "js-no-trigger-collapse")
- - if can?(current_user, :admin_list, @project)
- %board-delete{ "inline-template" => true,
- ":list" => "list",
- "v-if" => "!list.preset && list.id" }
- %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
- = icon("trash")
+
%board-list{ "v-if" => 'list.type !== "blank"',
":list" => "list",
":issues" => "list.issues",
@@ -42,5 +43,5 @@
":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath",
"ref" => "board-list" }
- - if can?(current_user, :admin_list, @project)
+ - if can?(current_user, :admin_list, current_board_parent)
%board-blank-state{ "v-if" => 'list.id == "blank"' }
diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml
index 09d70f658a3..b3f73e96b81 100644
--- a/app/views/projects/boards/components/_sidebar.html.haml
+++ b/app/views/shared/boards/components/_sidebar.html.haml
@@ -10,18 +10,19 @@
%br/
%span
= precede "#" do
- {{ issue.id }}
+ {{ issue.iid }}
%a.gutter-toggle.pull-right{ role: "button",
href: "#",
"@click.prevent" => "closeSidebar",
"aria-label" => "Toggle sidebar" }
= custom_icon("icon_close", size: 15)
.js-issuable-update
- = render "projects/boards/components/sidebar/assignee"
- = render "projects/boards/components/sidebar/milestone"
- = render "projects/boards/components/sidebar/due_date"
- = render "projects/boards/components/sidebar/labels"
- = render "projects/boards/components/sidebar/notifications"
+ = render "shared/boards/components/sidebar/assignee"
+ = render "shared/boards/components/sidebar/milestone"
+ = render "shared/boards/components/sidebar/due_date"
+ = render "shared/boards/components/sidebar/labels"
+ = render "shared/boards/components/sidebar/notifications"
%remove-btn{ ":issue" => "issue",
+ ":issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'",
":list" => "list",
"v-if" => "canRemove" }
diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/shared/boards/components/sidebar/_assignee.html.haml
index 8d957613be1..3d2e8471a60 100644
--- a/app/views/projects/boards/components/sidebar/_assignee.html.haml
+++ b/app/views/shared/boards/components/sidebar/_assignee.html.haml
@@ -2,13 +2,13 @@
%template{ "v-if" => "issue.assignees" }
%assignee-title{ ":number-of-assignees" => "issue.assignees.length",
":loading" => "loadingAssignees",
- ":editable" => can?(current_user, :admin_issue, @project) }
+ ":editable" => can_admin_issue? }
%assignees.value{ "root-path" => "#{root_url}",
":users" => "issue.assignees",
- ":editable" => can?(current_user, :admin_issue, @project),
+ ":editable" => can_admin_issue?,
"@assign-self" => "assignSelf" }
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
.selectbox.hide-collapsed
%input.js-vue{ type: "hidden",
name: "issue[assignee_ids][]",
@@ -20,9 +20,9 @@
":data-username" => "assignee.username" }
.dropdown
- dropdown_options = issue_assignees_dropdown_options
- %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: { toggle: 'dropdown', field_name: 'issue[assignee_ids][]', first_user: current_user&.username, current_user: 'true', project_id: @project.id, null_user: 'true', multi_select: 'true', 'dropdown-header': dropdown_options[:data][:'dropdown-header'], 'max-select': dropdown_options[:data][:'max-select'] },
- ":data-issuable-id" => "issue.id",
- ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
+ %button.dropdown-menu-toggle.js-user-search.js-author-search.js-multiselect.js-save-user-data.js-issue-board-sidebar{ type: 'button', ref: 'assigneeDropdown', data: board_sidebar_user_data,
+ ":data-issuable-id" => "issue.iid",
+ ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
= dropdown_options[:title]
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-user.dropdown-menu-selectable.dropdown-menu-author
diff --git a/app/views/projects/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml
index e8394eab213..db794d6f855 100644
--- a/app/views/projects/boards/components/sidebar/_due_date.html.haml
+++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml
@@ -1,7 +1,7 @@
.block.due_date
.title
Due date
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
.value
@@ -10,12 +10,12 @@
No due date
%span.bold{ "v-if" => "issue.dueDate" }
{{ issue.dueDate | due-date }}
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
%span.no-value.js-remove-due-date-holder{ "v-if" => "issue.dueDate" }
\-
%a.js-remove-due-date{ href: "#", role: "button" }
remove due date
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
.selectbox
%input{ type: "hidden",
name: "issue[due_date]",
@@ -23,7 +23,7 @@
.dropdown
%button.dropdown-menu-toggle.js-due-date-select.js-issue-boards-due-date{ type: 'button',
data: { toggle: 'dropdown', field_name: "issue[due_date]", ability_name: "issue" },
- ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
+ ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
%span.dropdown-toggle-text Due date
= icon('chevron-down')
.dropdown-menu.dropdown-menu-due-date
diff --git a/app/views/projects/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml
index 6b389736e8b..1f540bdaf93 100644
--- a/app/views/projects/boards/components/sidebar/_labels.html.haml
+++ b/app/views/shared/boards/components/sidebar/_labels.html.haml
@@ -1,7 +1,7 @@
.block.labels
.title
Labels
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
.value.issuable-show-labels
@@ -11,7 +11,7 @@
"v-for" => "label in issue.labels" }
%span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" }
{{ label.title }}
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
.selectbox
%input{ type: "hidden",
name: "issue[label_names][]",
@@ -19,12 +19,19 @@
":value" => "label.id" }
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect.js-issue-board-sidebar{ type: "button",
- data: { toggle: "dropdown", field_name: "issue[label_names][]", show_no: "true", show_any: "true", project_id: @project.id, labels: project_labels_path(@project, :json), namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) },
- ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
+ data: { toggle: "dropdown",
+ field_name: "issue[label_names][]",
+ show_no: "true",
+ show_any: "true",
+ project_id: @project&.try(:id),
+ labels: labels_filter_path(false),
+ namespace_path: @project.try(:namespace).try(:full_path),
+ project_path: @project.try(:path) },
+ ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
%span.dropdown-toggle-text
Label
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
- - if can? current_user, :admin_label, @project and @project
+ - if can?(current_user, :admin_label, current_board_parent)
= render partial: "shared/issuable/label_page_create"
diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml
index a1ddb261ea3..d09c7c218e0 100644
--- a/app/views/projects/boards/components/sidebar/_milestone.html.haml
+++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml
@@ -1,7 +1,7 @@
.block.milestone
.title
Milestone
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
= icon("spinner spin", class: "block-loading")
= link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right"
.value
@@ -9,17 +9,17 @@
None
%span.bold.has-tooltip{ "v-if" => "issue.milestone" }
{{ issue.milestone.title }}
- - if can?(current_user, :admin_issue, @project)
+ - if can_admin_issue?
.selectbox
%input{ type: "hidden",
":value" => "issue.milestone.id",
name: "issue[milestone_id]",
"v-if" => "issue.milestone" }
.dropdown
- %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: project_milestones_path(@project, :json), ability_name: "issue", use_id: "true", default_no: "true" },
+ %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", milestones: milestones_filter_path(format: :json), ability_name: "issue", use_id: "true", default_no: "true" },
":data-selected" => "milestoneTitle",
- ":data-issuable-id" => "issue.id",
- ":data-issue-update" => "'#{project_issues_path(@project)}/' + issue.id + '.json'" }
+ ":data-issuable-id" => "issue.iid",
+ ":data-issue-update" => "'#{build_issue_link_base}/' + issue.iid + '.json'" }
Milestone
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable
diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/shared/boards/components/sidebar/_notifications.html.haml
index aaddd7e249f..9b989c23cab 100644
--- a/app/views/projects/boards/components/sidebar/_notifications.html.haml
+++ b/app/views/shared/boards/components/sidebar/_notifications.html.haml
@@ -1,5 +1,5 @@
- if current_user
- .block.light.subscription{ ":data-url" => "'#{project_issues_path(@project)}/' + issue.id + '/toggle_subscription'" }
+ .block.light.subscription{ ":data-url" => "'#{build_issue_link_base}/' + issue.iid + '/toggle_subscription'" }
%span.issuable-header-text.hide-collapsed.pull-left
Notifications
%button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" }
diff --git a/app/views/shared/boards/index.html.haml b/app/views/shared/boards/index.html.haml
new file mode 100644
index 00000000000..2a5b8b1441e
--- /dev/null
+++ b/app/views/shared/boards/index.html.haml
@@ -0,0 +1 @@
+= render "show"
diff --git a/app/views/shared/boards/show.html.haml b/app/views/shared/boards/show.html.haml
new file mode 100644
index 00000000000..2a5b8b1441e
--- /dev/null
+++ b/app/views/shared/boards/show.html.haml
@@ -0,0 +1 @@
+= render "show"
diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml
index e8feff32d26..ad031e6af80 100644
--- a/app/views/shared/issuable/_label_page_default.html.haml
+++ b/app/views/shared/issuable/_label_page_default.html.haml
@@ -8,20 +8,19 @@
- if show_boards_content
.issue-board-dropdown-content
%p
- Create lists from the labels you use in your project. Issues with that
- label will automatically be added to the list.
+ Create lists from labels. Issues with that label appear in that list.
= dropdown_filter(filter_placeholder)
= dropdown_content
- - if @project && show_footer
+ - if current_board_parent && show_footer
= dropdown_footer do
%ul.dropdown-footer-list
- - if can?(current_user, :admin_label, @project)
+ - if can?(current_user, :admin_label, current_board_parent)
%li
%a.dropdown-toggle-page{ href: "#" }
Create new label
%li
- = link_to project_labels_path(@project), :"data-is-link" => true do
- - if show_create && @project && can?(current_user, :admin_label, @project)
+ = link_to labels_path, :"data-is-link" => true do
+ - if show_create && can?(current_user, :admin_label, current_board_parent)
Manage labels
- else
View labels
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index e81789ea7a2..161b1c9fd72 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -104,13 +104,13 @@
= icon('times')
.filter-dropdown-container
- if type == :boards
- - if can?(current_user, :admin_list, @project)
+ - if can?(current_user, :admin_list, board.parent)
.dropdown.prepend-left-10#js-add-list
- %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path) } }
+ %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
- - if can?(current_user, :admin_label, @project)
+ - if can?(current_user, :admin_label, board.parent)
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
#js-add-issues-btn.prepend-left-10
diff --git a/config/routes.rb b/config/routes.rb
index ce7ab1d20f6..5683725c8a2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -74,6 +74,19 @@ Rails.application.routes.draw do
# Notification settings
resources :notification_settings, only: [:create, :update]
+ # Boards resources shared between group and projects
+ resources :boards do
+ resources :lists, module: :boards, only: [:index, :create, :update, :destroy] do
+ collection do
+ post :generate
+ end
+
+ resources :issues, only: [:index, :create, :update]
+ end
+
+ resources :issues, module: :boards, only: [:index, :update]
+ end
+
draw :import
draw :uploads
draw :explore
diff --git a/config/routes/project.rb b/config/routes/project.rb
index a15e7f8a344..b36d13888cd 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -343,19 +343,7 @@ constraints(ProjectUrlConstrainer.new) do
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
- resources :boards, only: [:index, :show] do
- scope module: :boards do
- resources :issues, only: [:index, :update]
-
- resources :lists, only: [:index, :create, :update, :destroy] do
- collection do
- post :generate
- end
-
- resources :issues, only: [:index, :create]
- end
- end
- end
+ resources :boards, only: [:index, :show, :create, :update, :destroy]
resources :todos, only: [:create]
diff --git a/doc/README.md b/doc/README.md
index b250fa08382..a59f71e83a5 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -84,7 +84,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
- [Discussions](user/discussions/index.md) Threads, comments, and resolvable discussions in issues, commits, and merge requests.
- [Issues](user/project/issues/index.md)
-- [Issue Board](user/project/issue_board.md)
+- [Project issue Board](user/project/issue_board.md)
- [Issues and merge requests templates](user/project/description_templates.md): Create templates for submitting new issues and merge requests.
- [Labels](user/project/labels.md): Categorize your issues or merge requests based on descriptive titles.
- [Merge Requests](user/project/merge_requests/index.md)
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 894bd5efae5..7c02c9c5c48 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -26,6 +26,7 @@ module Gitlab
apple-touch-icon.png
assets
autocomplete
+ boards
ci
dashboard
deploy.html
diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 3f6c1092163..dfa06c78d46 100644
--- a/spec/controllers/projects/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::Boards::IssuesController do
+describe Boards::IssuesController do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
@@ -133,6 +133,22 @@ describe Projects::Boards::IssuesController do
expect(response).to have_http_status(404)
end
end
+
+ context 'with invalid board id' do
+ it 'returns a not found 404 response' do
+ create_issue user: user, board: 999, list: list1, title: 'New issue'
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'with invalid list id' do
+ it 'returns a not found 404 response' do
+ create_issue user: user, board: board, list: 999, title: 'New issue'
+
+ expect(response).to have_http_status(404)
+ end
+ end
end
context 'with unauthorized user' do
@@ -146,17 +162,15 @@ describe Projects::Boards::IssuesController do
def create_issue(user:, board:, list:, title:)
sign_in(user)
- post :create, namespace_id: project.namespace.to_param,
- project_id: project,
- board_id: board.to_param,
+ post :create, board_id: board.to_param,
list_id: list.to_param,
- issue: { title: title },
+ issue: { title: title, project_id: project.id },
format: :json
end
end
describe 'PATCH update' do
- let(:issue) { create(:labeled_issue, project: project, labels: [planning]) }
+ let!(:issue) { create(:labeled_issue, project: project, labels: [planning]) }
context 'with valid params' do
it 'returns a successful 200 response' do
@@ -186,7 +200,7 @@ describe Projects::Boards::IssuesController do
end
it 'returns a not found 404 response for invalid issue id' do
- move user: user, board: board, issue: 999, from_list_id: list1.id, to_list_id: list2.id
+ move user: user, board: board, issue: double(id: 999), from_list_id: list1.id, to_list_id: list2.id
expect(response).to have_http_status(404)
end
@@ -210,9 +224,9 @@ describe Projects::Boards::IssuesController do
sign_in(user)
patch :update, namespace_id: project.namespace.to_param,
- project_id: project,
+ project_id: project.id,
board_id: board.to_param,
- id: issue.to_param,
+ id: issue.id,
from_list_id: from_list_id,
to_list_id: to_list_id,
format: :json
diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/boards/lists_controller_spec.rb
index 65beec16307..b11fce0fa58 100644
--- a/spec/controllers/projects/boards/lists_controller_spec.rb
+++ b/spec/controllers/boards/lists_controller_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Projects::Boards::ListsController do
+describe Boards::ListsController do
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let(:user) { create(:user) }
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 2f75bf12cd7..b5298b2f969 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -7,6 +7,7 @@ FactoryGirl.define do
group nil
project_id nil
group_id nil
+ parent nil
end
trait :active do
@@ -26,6 +27,9 @@ FactoryGirl.define do
milestone.project = evaluator.project
elsif evaluator.project_id
milestone.project_id = evaluator.project_id
+ elsif evaluator.parent
+ id = evaluator.parent.id
+ evaluator.parent.is_a?(Group) ? board.group_id = id : evaluator.project_id = id
else
milestone.project = create(:project)
end
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index ff86437fdd5..e1f62508933 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -8,10 +8,15 @@
"properties" : {
"id": { "type": "integer" },
"iid": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
"title": { "type": "string" },
"confidential": { "type": "boolean" },
"due_date": { "type": ["date", "null"] },
"relative_position": { "type": "integer" },
+ "project": {
+ "id": { "type": "integer" },
+ "path": { "type": "string" }
+ },
"labels": {
"type": "array",
"items": {
@@ -34,6 +39,7 @@
"type": "string",
"pattern": "^#[0-9A-Fa-f]{3}{1,2}+$"
},
+ "type": { "type": "string" },
"title": { "type": "string" },
"priority": { "type": ["integer", "null"] }
},
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index 47baf83512f..2ee3792dd65 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,4 +1,5 @@
/* global BoardService */
+/* global mockBoardService */
import Vue from 'vue';
import '~/boards/stores/boards_store';
import boardBlankState from '~/boards/components/board_blank_state';
@@ -12,7 +13,7 @@ describe('Boards blank state', () => {
const Comp = Vue.extend(boardBlankState);
gl.issueBoards.BoardsStore.create();
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService();
spyOn(gl.boardService, 'generateDefaultLists').and.callFake(() => new Promise((resolve, reject) => {
if (fail) {
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 447b244c71f..83b13b06dc1 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -4,6 +4,7 @@
/* global listObj */
/* global boardsMockInterceptor */
/* global BoardService */
+/* global mockBoardService */
import Vue from 'vue';
import '~/boards/models/assignee';
@@ -14,13 +15,13 @@ import '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card';
import './mock_data';
-describe('Issue card', () => {
+describe('Board card', () => {
let vm;
beforeEach((done) => {
Vue.http.interceptors.push(boardsMockInterceptor);
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService();
gl.issueBoards.BoardsStore.create();
gl.issueBoards.BoardsStore.detail.issue = {};
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index a89be911667..6bd00943a8f 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -3,6 +3,7 @@
/* global List */
/* global listObj */
/* global ListIssue */
+/* global mockBoardService */
import Vue from 'vue';
import _ from 'underscore';
import Sortable from 'vendor/Sortable';
@@ -24,7 +25,7 @@ describe('Board list component', () => {
document.body.appendChild(el);
Vue.http.interceptors.push(boardsMockInterceptor);
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService();
gl.issueBoards.BoardsStore.create();
gl.IssueBoardsApp = new Vue();
@@ -32,6 +33,7 @@ describe('Board list component', () => {
const list = new List(listObj);
const issue = new ListIssue({
title: 'Testing',
+ id: 1,
iid: 1,
confidential: false,
labels: [],
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index eac2eecb6bc..02e6692dda8 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -2,6 +2,7 @@
/* global BoardService */
/* global List */
/* global listObj */
+/* global mockBoardService */
import Vue from 'vue';
import boardNewIssue from '~/boards/components/board_new_issue';
@@ -35,7 +36,7 @@ describe('Issue boards new issue form', () => {
const BoardNewIssueComp = Vue.extend(boardNewIssue);
Vue.http.interceptors.push(boardsMockInterceptor);
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService();
gl.issueBoards.BoardsStore.create();
gl.IssueBoardsApp = new Vue();
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 5ea160b7790..9e5b0bd3efe 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -4,6 +4,7 @@
/* global listObj */
/* global listObjDuplicate */
/* global ListIssue */
+/* global mockBoardService */
import Vue from 'vue';
import Cookies from 'js-cookie';
@@ -20,7 +21,7 @@ import './mock_data';
describe('Store', () => {
beforeEach(() => {
Vue.http.interceptors.push(boardsMockInterceptor);
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService();
gl.issueBoards.BoardsStore.create();
spyOn(gl.boardService, 'moveIssue').and.callFake(() => new Promise((resolve) => {
@@ -78,7 +79,7 @@ describe('Store', () => {
it('persists new list', (done) => {
gl.issueBoards.BoardsStore.new({
title: 'Test',
- type: 'label',
+ list_type: 'label',
label: {
id: 1,
title: 'Testing',
@@ -210,6 +211,7 @@ describe('Store', () => {
it('moves issue in list', (done) => {
const issue = new ListIssue({
title: 'Testing',
+ id: 2,
iid: 2,
confidential: false,
labels: [],
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index c4e8966ad6c..8dacac20cad 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -1,7 +1,9 @@
+/* global mockBoardService */
import Vue from 'vue';
import '~/boards/services/board_service';
import '~/boards/components/board';
import '~/boards/models/list';
+import '../mock_data';
describe('Board component', () => {
let vm;
@@ -13,8 +15,12 @@ describe('Board component', () => {
el = document.createElement('div');
document.body.appendChild(el);
- // eslint-disable-next-line no-undef
- gl.boardService = new BoardService('/', '/', 1);
+ gl.boardService = mockBoardService({
+ boardsEndpoint: '/',
+ listsEndpoint: '/',
+ bulkUpdatePath: '/',
+ boardId: 1,
+ });
vm = new gl.issueBoards.Board({
propsData: {
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 47aaa57e6b9..7d430ec35e2 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -37,6 +37,7 @@ describe('Issue card component', () => {
list = listObj;
issue = new ListIssue({
title: 'Testing',
+ id: 1,
iid: 1,
confidential: false,
labels: [list.label],
@@ -238,65 +239,63 @@ describe('Issue card component', () => {
});
describe('labels', () => {
- describe('exists', () => {
- beforeEach((done) => {
- component.issue.addLabel(label1);
+ beforeEach((done) => {
+ component.issue.addLabel(label1);
- Vue.nextTick(() => done());
- });
+ Vue.nextTick(() => done());
+ });
- it('renders list label', () => {
- expect(
- component.$el.querySelectorAll('.label').length,
- ).toBe(2);
+ it('renders list label', () => {
+ expect(
+ component.$el.querySelectorAll('.label').length,
+ ).toBe(2);
+ });
+
+ it('renders label', () => {
+ const nodes = [];
+ component.$el.querySelectorAll('.label').forEach((label) => {
+ nodes.push(label.title);
});
- it('renders label', () => {
- const nodes = [];
- component.$el.querySelectorAll('.label').forEach((label) => {
- nodes.push(label.title);
- });
+ expect(
+ nodes.includes(label1.description),
+ ).toBe(true);
+ });
- expect(
- nodes.includes(label1.description),
- ).toBe(true);
- });
+ it('sets label description as title', () => {
+ expect(
+ component.$el.querySelector('.label').getAttribute('title'),
+ ).toContain(label1.description);
+ });
- it('sets label description as title', () => {
- expect(
- component.$el.querySelector('.label').getAttribute('title'),
- ).toContain(label1.description);
+ it('sets background color of button', () => {
+ const nodes = [];
+ component.$el.querySelectorAll('.label').forEach((label) => {
+ nodes.push(label.style.backgroundColor);
});
- it('sets background color of button', () => {
- const nodes = [];
- component.$el.querySelectorAll('.label').forEach((label) => {
- nodes.push(label.style.backgroundColor);
- });
+ expect(
+ nodes.includes(label1.color),
+ ).toBe(true);
+ });
- expect(
- nodes.includes(label1.color),
- ).toBe(true);
- });
+ it('does not render label if label does not have an ID', (done) => {
+ component.issue.addLabel(new ListLabel({
+ title: 'closed',
+ }));
- it('does not render label if label does not have an ID', (done) => {
- component.issue.addLabel(new ListLabel({
- title: 'closed',
- }));
+ Vue.nextTick()
+ .then(() => {
+ expect(
+ component.$el.querySelectorAll('.label').length,
+ ).toBe(2);
+ expect(
+ component.$el.textContent,
+ ).not.toContain('closed');
- Vue.nextTick()
- .then(() => {
- expect(
- component.$el.querySelectorAll('.label').length,
- ).toBe(2);
- expect(
- component.$el.textContent,
- ).not.toContain('closed');
-
- done();
- })
- .catch(done.fail);
- });
+ done();
+ })
+ .catch(done.fail);
});
});
});
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index cd1497bc5e6..022d286d5df 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -1,6 +1,7 @@
/* eslint-disable comma-dangle */
/* global BoardService */
/* global ListIssue */
+/* global mockBoardService */
import Vue from 'vue';
import '~/lib/utils/url_utility';
@@ -16,11 +17,12 @@ describe('Issue model', () => {
let issue;
beforeEach(() => {
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService();
gl.issueBoards.BoardsStore.create();
issue = new ListIssue({
title: 'Testing',
+ id: 1,
iid: 1,
confidential: false,
labels: [{
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index db50829a276..d4627223a12 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -1,6 +1,7 @@
/* eslint-disable comma-dangle */
/* global boardsMockInterceptor */
/* global BoardService */
+/* global mockBoardService */
/* global List */
/* global ListIssue */
/* global listObj */
@@ -22,7 +23,9 @@ describe('List model', () => {
beforeEach(() => {
Vue.http.interceptors.push(boardsMockInterceptor);
- gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
+ gl.boardService = mockBoardService({
+ bulkUpdatePath: '/test/issue-boards/board/1/lists',
+ });
gl.issueBoards.BoardsStore.create();
list = new List(listObj);
@@ -92,6 +95,7 @@ describe('List model', () => {
const listDup = new List(listObjDuplicate);
const issue = new ListIssue({
title: 'Testing',
+ id: _.random(10000),
iid: _.random(10000),
confidential: false,
labels: [list.label, listDup.label],
@@ -118,6 +122,7 @@ describe('List model', () => {
for (let i = 0; i < 30; i += 1) {
list.issues.push(new ListIssue({
title: 'Testing',
+ id: _.random(10000) + i,
iid: _.random(10000) + i,
confidential: false,
labels: [list.label],
@@ -137,7 +142,7 @@ describe('List model', () => {
it('does not increase page number if issue count is less than the page size', () => {
list.issues.push(new ListIssue({
title: 'Testing',
- iid: _.random(10000),
+ id: _.random(10000),
confidential: false,
labels: [list.label],
assignees: [],
@@ -156,7 +161,7 @@ describe('List model', () => {
spyOn(gl.boardService, 'newIssue').and.returnValue(Promise.resolve({
json() {
return {
- iid: 42,
+ id: 42,
};
},
}));
@@ -165,14 +170,14 @@ describe('List model', () => {
it('adds new issue to top of list', (done) => {
list.issues.push(new ListIssue({
title: 'Testing',
- iid: _.random(10000),
+ id: _.random(10000),
confidential: false,
labels: [list.label],
assignees: [],
}));
const dummyIssue = new ListIssue({
title: 'new issue',
- iid: _.random(10000),
+ id: _.random(10000),
confidential: false,
labels: [list.label],
assignees: [],
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index a64c3964ee3..0a93086985e 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,3 +1,4 @@
+/* global BoardService */
/* eslint-disable comma-dangle, no-unused-vars, quote-props */
const listObj = {
@@ -28,19 +29,19 @@ const listObjDuplicate = {
const BoardsMockData = {
'GET': {
- '/test/issue-boards/board/1/lists{/id}/issues': {
+ '/test/boards/1{/id}/issues': {
issues: [{
title: 'Testing',
+ id: 1,
iid: 1,
confidential: false,
labels: [],
assignees: [],
}],
- size: 1
}
},
'POST': {
- '/test/issue-boards/board/1/lists{/id}': listObj
+ '/test/boards/1{/id}': listObj
},
'PUT': {
'/test/issue-boards/board/1/lists{/id}': {}
@@ -58,7 +59,22 @@ const boardsMockInterceptor = (request, next) => {
}));
};
+const mockBoardService = (opts = {}) => {
+ const boardsEndpoint = opts.boardsEndpoint || '/test/issue-boards/board';
+ const listsEndpoint = opts.listsEndpoint || '/test/boards/1';
+ const bulkUpdatePath = opts.bulkUpdatePath || '';
+ const boardId = opts.boardId || '1';
+
+ return new BoardService({
+ boardsEndpoint,
+ listsEndpoint,
+ bulkUpdatePath,
+ boardId,
+ });
+};
+
window.listObj = listObj;
window.listObjDuplicate = listObjDuplicate;
window.BoardsMockData = BoardsMockData;
window.boardsMockInterceptor = boardsMockInterceptor;
+window.mockBoardService = mockBoardService;
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js
index 32e6d04df9f..7eecb58a4c3 100644
--- a/spec/javascripts/boards/modal_store_spec.js
+++ b/spec/javascripts/boards/modal_store_spec.js
@@ -18,6 +18,7 @@ describe('Modal store', () => {
issue = new ListIssue({
title: 'Testing',
+ id: 1,
iid: 1,
confidential: false,
labels: [],
@@ -25,6 +26,7 @@ describe('Modal store', () => {
});
issue2 = new ListIssue({
title: 'Testing',
+ id: 1,
iid: 2,
confidential: false,
labels: [],
diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb
index f2ddaa903da..1a56164dba4 100644
--- a/spec/services/boards/issues/create_service_spec.rb
+++ b/spec/services/boards/issues/create_service_spec.rb
@@ -8,7 +8,7 @@ describe Boards::Issues::CreateService do
let(:label) { create(:label, project: project, name: 'in-progress') }
let!(:list) { create(:list, board: board, label: label, position: 0) }
- subject(:service) { described_class.new(project, user, board_id: board.id, list_id: list.id, title: 'New issue') }
+ subject(:service) { described_class.new(board.parent, project, user, board_id: board.id, list_id: list.id, title: 'New issue') }
before do
project.team << [user, :developer]
diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb
index 63dfe80d672..464ff9f94b3 100644
--- a/spec/services/boards/issues/move_service_spec.rb
+++ b/spec/services/boards/issues/move_service_spec.rb
@@ -98,7 +98,7 @@ describe Boards::Issues::MoveService do
issue.move_to_end && issue.save!
end
- params.merge!(move_after_iid: issue1.iid, move_before_iid: issue2.iid)
+ params.merge!(move_after_id: issue1.id, move_before_id: issue2.id)
described_class.new(project, user, params).execute(issue)
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 85f46838351..15a50b85f19 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -80,7 +80,7 @@ describe Issues::UpdateService, :mailer do
issue.save
end
- opts[:move_between_iids] = [issue1.iid, issue2.iid]
+ opts[:move_between_ids] = [issue1.id, issue2.id]
update_issue(opts)