Add issues to boards list
This removes the backlog list & instead creates a modal window that will list all issues that are not part of a list for easy adding onto the board Closes #26205
This commit is contained in:
parent
52ea505126
commit
a132b7d8ce
18 changed files with 438 additions and 52 deletions
|
@ -13,6 +13,7 @@
|
||||||
//= require ./components/board
|
//= require ./components/board
|
||||||
//= require ./components/board_sidebar
|
//= require ./components/board_sidebar
|
||||||
//= require ./components/new_list_dropdown
|
//= require ./components/new_list_dropdown
|
||||||
|
//= require ./components/modal
|
||||||
//= require ./vue_resource_interceptor
|
//= require ./vue_resource_interceptor
|
||||||
|
|
||||||
$(() => {
|
$(() => {
|
||||||
|
@ -31,7 +32,8 @@ $(() => {
|
||||||
el: $boardApp,
|
el: $boardApp,
|
||||||
components: {
|
components: {
|
||||||
'board': gl.issueBoards.Board,
|
'board': gl.issueBoards.Board,
|
||||||
'board-sidebar': gl.issueBoards.BoardSidebar
|
'board-sidebar': gl.issueBoards.BoardSidebar,
|
||||||
|
'board-add-issues-modal': gl.issueBoards.IssuesModal,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
state: Store.state,
|
state: Store.state,
|
||||||
|
@ -55,12 +57,12 @@ $(() => {
|
||||||
gl.boardService.all()
|
gl.boardService.all()
|
||||||
.then((resp) => {
|
.then((resp) => {
|
||||||
resp.json().forEach((board) => {
|
resp.json().forEach((board) => {
|
||||||
|
if (board.list_type === 'backlog') return;
|
||||||
|
|
||||||
const list = Store.addList(board);
|
const list = Store.addList(board);
|
||||||
|
|
||||||
if (list.type === 'done') {
|
if (list.type === 'done') {
|
||||||
list.position = Infinity;
|
list.position = Infinity;
|
||||||
} else if (list.type === 'backlog') {
|
|
||||||
list.position = -1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,4 +83,13 @@ $(() => {
|
||||||
gl.issueBoards.newListDropdownInit();
|
gl.issueBoards.newListDropdownInit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This element is outside the Vue app
|
||||||
|
$(document)
|
||||||
|
.off('click', '.js-show-add-issues')
|
||||||
|
.on('click', '.js-show-add-issues', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
Store.modal.showAddIssuesModal = true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
|
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
|
||||||
|
//= require ./issue_card_inner
|
||||||
/* global Vue */
|
/* global Vue */
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
@ -9,6 +10,9 @@
|
||||||
|
|
||||||
gl.issueBoards.BoardCard = Vue.extend({
|
gl.issueBoards.BoardCard = Vue.extend({
|
||||||
template: '#js-board-list-card',
|
template: '#js-board-list-card',
|
||||||
|
components: {
|
||||||
|
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
list: Object,
|
list: Object,
|
||||||
issue: Object,
|
issue: Object,
|
||||||
|
@ -28,31 +32,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
filterByLabel (label, e) {
|
|
||||||
let labelToggleText = label.title;
|
|
||||||
const labelIndex = Store.state.filters['label_name'].indexOf(label.title);
|
|
||||||
$(e.target).tooltip('hide');
|
|
||||||
|
|
||||||
if (labelIndex === -1) {
|
|
||||||
Store.state.filters['label_name'].push(label.title);
|
|
||||||
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
|
|
||||||
} else {
|
|
||||||
Store.state.filters['label_name'].splice(labelIndex, 1);
|
|
||||||
labelToggleText = Store.state.filters['label_name'][0];
|
|
||||||
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedLabels = Store.state.filters['label_name'];
|
|
||||||
if (selectedLabels.length === 0) {
|
|
||||||
labelToggleText = 'Label';
|
|
||||||
} else if (selectedLabels.length > 1) {
|
|
||||||
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
|
|
||||||
}
|
|
||||||
|
|
||||||
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
|
|
||||||
|
|
||||||
Store.updateFiltersUrl();
|
|
||||||
},
|
|
||||||
mouseDown () {
|
mouseDown () {
|
||||||
this.showDetail = true;
|
this.showDetail = true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.IssueCardInner = Vue.extend({
|
||||||
|
props: [
|
||||||
|
'issue', 'issueLinkBase', 'list',
|
||||||
|
],
|
||||||
|
methods: {
|
||||||
|
showLabel(label) {
|
||||||
|
if (!this.list) return true;
|
||||||
|
|
||||||
|
return !this.list.label || label.id !== this.list.label.id;
|
||||||
|
},
|
||||||
|
filterByLabel(label, e) {
|
||||||
|
let labelToggleText = label.title;
|
||||||
|
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
|
||||||
|
$(e.target).tooltip('hide');
|
||||||
|
|
||||||
|
if (labelIndex === -1) {
|
||||||
|
Store.state.filters.label_name.push(label.title);
|
||||||
|
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
|
||||||
|
} else {
|
||||||
|
Store.state.filters.label_name.splice(labelIndex, 1);
|
||||||
|
labelToggleText = Store.state.filters.label_name[0];
|
||||||
|
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedLabels = Store.state.filters.label_name;
|
||||||
|
if (selectedLabels.length === 0) {
|
||||||
|
labelToggleText = 'Label';
|
||||||
|
} else if (selectedLabels.length > 1) {
|
||||||
|
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
|
||||||
|
|
||||||
|
Store.updateFiltersUrl();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<h4 class="card-title">
|
||||||
|
<i
|
||||||
|
class="fa fa-eye-flash"
|
||||||
|
v-if="issue.confidential"></i>
|
||||||
|
<a
|
||||||
|
:href="issueLinkBase + '/' + issue.id"
|
||||||
|
:title="issue.title">
|
||||||
|
{{ issue.title }}
|
||||||
|
</a>
|
||||||
|
</h4>
|
||||||
|
<div class="card-footer">
|
||||||
|
<span
|
||||||
|
class="card-number"
|
||||||
|
v-if="issue.id">
|
||||||
|
#{{issue.id}}
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
class="has-tooltip"
|
||||||
|
:href="issue.assignee.username"
|
||||||
|
:title="'Assigned to ' + issue.assignee.name"
|
||||||
|
v-if="issue.assignee"
|
||||||
|
data-container="body">
|
||||||
|
<img
|
||||||
|
class="avatar avatar-inline s20"
|
||||||
|
:src="issue.assignee.avatar"
|
||||||
|
width="20"
|
||||||
|
height="20" />
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
class="label color-label has-tooltip"
|
||||||
|
v-for="label in issue.labels"
|
||||||
|
type="button"
|
||||||
|
v-if="showLabel(label)"
|
||||||
|
@click="filterByLabel(label, $event)"
|
||||||
|
:style="{ backgroundColor: label.color, color: label.textColor }"
|
||||||
|
:title="label.description"
|
||||||
|
data-container="body">
|
||||||
|
{{ label.title }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.DismissModal = Vue.extend({
|
||||||
|
data() {
|
||||||
|
return Store.modal;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleModal(toggle) {
|
||||||
|
this.showAddIssuesModal = toggle;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="close"
|
||||||
|
data-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
@click="toggleModal(false)">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
33
app/assets/javascripts/boards/components/modal/footer.js.es6
Normal file
33
app/assets/javascripts/boards/components/modal/footer.js.es6
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.ModalFooter = Vue.extend({
|
||||||
|
data() {
|
||||||
|
return Store.modal;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
hideModal() {
|
||||||
|
this.showAddIssuesModal = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<footer class="form-actions add-issues-footer">
|
||||||
|
<button
|
||||||
|
class="btn btn-success pull-left"
|
||||||
|
type="button">
|
||||||
|
Add issues
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-default pull-right"
|
||||||
|
type="button"
|
||||||
|
@click="hideModal">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
31
app/assets/javascripts/boards/components/modal/header.js.es6
Normal file
31
app/assets/javascripts/boards/components/modal/header.js.es6
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
//= require ./dismiss
|
||||||
|
//= require ./tabs
|
||||||
|
//= require ./search
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.IssuesModalHeader = Vue.extend({
|
||||||
|
data() {
|
||||||
|
return Store.modal;
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'modal-dismiss': gl.issueBoards.DismissModal,
|
||||||
|
'modal-tabs': gl.issueBoards.ModalTabs,
|
||||||
|
'modal-search': gl.issueBoards.ModalSearch,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<header class="add-issues-header">
|
||||||
|
<h2>
|
||||||
|
Add issues to board
|
||||||
|
<modal-dismiss></modal-dismiss>
|
||||||
|
</h2>
|
||||||
|
<modal-tabs></modal-tabs>
|
||||||
|
<modal-search></modal-search>
|
||||||
|
</header>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
32
app/assets/javascripts/boards/components/modal/index.es6
Normal file
32
app/assets/javascripts/boards/components/modal/index.es6
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
//= require ./header
|
||||||
|
//= require ./list
|
||||||
|
//= require ./footer
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.IssuesModal = Vue.extend({
|
||||||
|
data() {
|
||||||
|
return Store.modal;
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'modal-header': gl.issueBoards.IssuesModalHeader,
|
||||||
|
'modal-list': gl.issueBoards.ModalList,
|
||||||
|
'modal-footer': gl.issueBoards.ModalFooter,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
class="add-issues-modal"
|
||||||
|
v-if="showAddIssuesModal">
|
||||||
|
<div class="add-issues-container">
|
||||||
|
<modal-header></modal-header>
|
||||||
|
<modal-list></modal-list>
|
||||||
|
<modal-footer></modal-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
51
app/assets/javascripts/boards/components/modal/list.js.es6
Normal file
51
app/assets/javascripts/boards/components/modal/list.js.es6
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/* global Vue */
|
||||||
|
/* global ListIssue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.ModalList = Vue.extend({
|
||||||
|
data() {
|
||||||
|
return Store.modal;
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
loading() {
|
||||||
|
return this.issues.length === 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
gl.boardService.getBacklog()
|
||||||
|
.then((res) => {
|
||||||
|
const data = res.json();
|
||||||
|
|
||||||
|
data.forEach((issueObj) => {
|
||||||
|
this.issues.push(new ListIssue(issueObj));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<section class="add-issues-list">
|
||||||
|
<i
|
||||||
|
class="fa fa-spinner fa-spin"
|
||||||
|
v-if="loading"></i>
|
||||||
|
<ul
|
||||||
|
class="list-unstyled"
|
||||||
|
v-if="!loading">
|
||||||
|
<li
|
||||||
|
class="card"
|
||||||
|
v-for="issue in issues">
|
||||||
|
<issue-card-inner
|
||||||
|
:issue="issue"
|
||||||
|
:issue-link-base="'/'">
|
||||||
|
</issue-card-inner>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
14
app/assets/javascripts/boards/components/modal/search.js.es6
Normal file
14
app/assets/javascripts/boards/components/modal/search.js.es6
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.ModalSearch = Vue.extend({
|
||||||
|
template: `
|
||||||
|
<input
|
||||||
|
placeholder="Search issues..."
|
||||||
|
class="form-control"
|
||||||
|
type="search" />
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
46
app/assets/javascripts/boards/components/modal/tabs.js.es6
Normal file
46
app/assets/javascripts/boards/components/modal/tabs.js.es6
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* global Vue */
|
||||||
|
(() => {
|
||||||
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||||
|
|
||||||
|
gl.issueBoards.ModalTabs = Vue.extend({
|
||||||
|
data() {
|
||||||
|
return Store.modal;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeTab(tab) {
|
||||||
|
this.activeTab = tab;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div class="top-area">
|
||||||
|
<ul class="nav-links issues-state-filters">
|
||||||
|
<li :class="{ 'active': activeTab == 'all' }">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
@click.prevent="changeTab('all')">
|
||||||
|
<span>All issues</span>
|
||||||
|
<span class="badge">
|
||||||
|
{{ issues.length }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li :class="{ 'active': activeTab == 'selected' }">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
@click.prevent="changeTab('selected')">
|
||||||
|
<span>Selected issues</span>
|
||||||
|
<span class="badge">
|
||||||
|
0
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
})();
|
|
@ -3,6 +3,12 @@
|
||||||
|
|
||||||
class BoardService {
|
class BoardService {
|
||||||
constructor (root, boardId) {
|
constructor (root, boardId) {
|
||||||
|
this.boards = Vue.resource(`${root}{/id}.json`, {}, {
|
||||||
|
backlog: {
|
||||||
|
method: 'GET',
|
||||||
|
url: `${root}/${boardId}/backlog.json`
|
||||||
|
}
|
||||||
|
});
|
||||||
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
|
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
|
||||||
generate: {
|
generate: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -65,6 +71,10 @@ class BoardService {
|
||||||
issue
|
issue
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBacklog() {
|
||||||
|
return this.boards.backlog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.BoardService = BoardService;
|
window.BoardService = BoardService;
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
detail: {
|
detail: {
|
||||||
issue: {}
|
issue: {}
|
||||||
},
|
},
|
||||||
|
modal: {
|
||||||
|
issues: [],
|
||||||
|
showAddIssuesModal: false,
|
||||||
|
activeTab: 'all',
|
||||||
|
},
|
||||||
moving: {
|
moving: {
|
||||||
issue: {},
|
issue: {},
|
||||||
list: {}
|
list: {}
|
||||||
|
|
|
@ -250,11 +250,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.issue-boards-search {
|
.issue-boards-search {
|
||||||
width: 290px;
|
width: 395px;
|
||||||
|
|
||||||
.form-control {
|
.form-control {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 210px;
|
width: 210px;
|
||||||
|
margin-right: 10px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,3 +355,52 @@
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-issues-modal {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: rgba($black, .3);
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-issues-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 90vw;
|
||||||
|
height: 85vh;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 25px 15px 0;
|
||||||
|
background-color: $white-light;
|
||||||
|
border-radius: $border-radius-default;
|
||||||
|
box-shadow: 0 2px 12px rgba($black, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-issues-header {
|
||||||
|
> h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-area {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-issues-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-issues-footer {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-left: -15px;
|
||||||
|
margin-right: -15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
class Projects::BoardsController < Projects::ApplicationController
|
class Projects::BoardsController < Projects::ApplicationController
|
||||||
include IssuableCollections
|
include IssuableCollections
|
||||||
|
|
||||||
before_action :authorize_read_board!, only: [:index, :show]
|
# before_action :authorize_read_board!, only: [:index, :show, :backlog]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@boards = ::Boards::ListService.new(project, current_user).execute
|
@boards = ::Boards::ListService.new(project, current_user).execute
|
||||||
|
@ -25,6 +25,27 @@ class Projects::BoardsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def backlog
|
||||||
|
board = project.boards.find(params[:id])
|
||||||
|
|
||||||
|
@issues = issues_collection
|
||||||
|
@issues = @issues.where.not(
|
||||||
|
LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id")
|
||||||
|
.where(label_id: board.lists.movable.pluck(:label_id)).limit(1).arel.exists
|
||||||
|
)
|
||||||
|
@issues = @issues.page(params[:page])
|
||||||
|
|
||||||
|
render json: @issues.as_json(
|
||||||
|
labels: true,
|
||||||
|
only: [:iid, :title, :confidential, :due_date],
|
||||||
|
include: {
|
||||||
|
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||||
|
milestone: { only: [:id, :title] }
|
||||||
|
},
|
||||||
|
user: current_user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authorize_read_board!
|
def authorize_read_board!
|
||||||
|
|
|
@ -26,3 +26,4 @@
|
||||||
":issue-link-base" => "issueLinkBase",
|
":issue-link-base" => "issueLinkBase",
|
||||||
":key" => "_uid" }
|
":key" => "_uid" }
|
||||||
= render "projects/boards/components/sidebar"
|
= render "projects/boards/components/sidebar"
|
||||||
|
%board-add-issues-modal
|
||||||
|
|
|
@ -4,25 +4,6 @@
|
||||||
"@mousedown" => "mouseDown",
|
"@mousedown" => "mouseDown",
|
||||||
"@mousemove" => "mouseMove",
|
"@mousemove" => "mouseMove",
|
||||||
"@mouseup" => "showIssue($event)" }
|
"@mouseup" => "showIssue($event)" }
|
||||||
%h4.card-title
|
%issue-card-inner{ ":list" => "list",
|
||||||
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
|
":issue" => "issue",
|
||||||
%a{ ":href" => 'issueLinkBase + "/" + issue.id',
|
":issue-link-base" => "issueLinkBase" }
|
||||||
":title" => "issue.title" }
|
|
||||||
{{ issue.title }}
|
|
||||||
.card-footer
|
|
||||||
%span.card-number{ "v-if" => "issue.id" }
|
|
||||||
= precede '#' do
|
|
||||||
{{ issue.id }}
|
|
||||||
%a.has-tooltip{ ":href" => "\"#{root_path}\" + issue.assignee.username",
|
|
||||||
":title" => '"Assigned to " + issue.assignee.name',
|
|
||||||
"v-if" => "issue.assignee",
|
|
||||||
data: { container: 'body' } }
|
|
||||||
%img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20, alt: "Avatar" }
|
|
||||||
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
|
|
||||||
type: "button",
|
|
||||||
"v-if" => "(!list.label || label.id !== list.label.id)",
|
|
||||||
"@click" => "filterByLabel(label, $event)",
|
|
||||||
":style" => "{ backgroundColor: label.color, color: label.textColor }",
|
|
||||||
":title" => "label.description",
|
|
||||||
data: { container: 'body' } }
|
|
||||||
{{ label.title }}
|
|
||||||
|
|
|
@ -38,6 +38,8 @@
|
||||||
#js-boards-search.issue-boards-search
|
#js-boards-search.issue-boards-search
|
||||||
%input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
|
%input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
|
||||||
- if can?(current_user, :admin_list, @project)
|
- if can?(current_user, :admin_list, @project)
|
||||||
|
%button.btn.btn-create.btn-inverted.js-show-add-issues{ type: "button" }
|
||||||
|
Add issues
|
||||||
.dropdown.pull-right
|
.dropdown.pull-right
|
||||||
%button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } }
|
%button.btn.btn-create.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } }
|
||||||
Add list
|
Add list
|
||||||
|
|
|
@ -266,6 +266,8 @@ constraints(ProjectUrlConstrainer.new) do
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :boards, only: [:index, :show] do
|
resources :boards, only: [:index, :show] do
|
||||||
|
get :backlog, on: :member
|
||||||
|
|
||||||
scope module: :boards do
|
scope module: :boards do
|
||||||
resources :issues, only: [:update]
|
resources :issues, only: [:update]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue