Merge branch 'add-issues-to-boards' into 'master'
Add issues to boards list Closes #26205 See merge request !8737
This commit is contained in:
commit
3213dfd797
|
@ -13,11 +13,13 @@
|
|||
//= require ./components/board
|
||||
//= require ./components/board_sidebar
|
||||
//= require ./components/new_list_dropdown
|
||||
//= require ./components/modal/index
|
||||
//= require ./vue_resource_interceptor
|
||||
|
||||
$(() => {
|
||||
const $boardApp = document.getElementById('board-app');
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
||||
|
@ -31,7 +33,8 @@ $(() => {
|
|||
el: $boardApp,
|
||||
components: {
|
||||
'board': gl.issueBoards.Board,
|
||||
'board-sidebar': gl.issueBoards.BoardSidebar
|
||||
'board-sidebar': gl.issueBoards.BoardSidebar,
|
||||
'board-add-issues-modal': gl.issueBoards.IssuesModal,
|
||||
},
|
||||
data: {
|
||||
state: Store.state,
|
||||
|
@ -40,6 +43,8 @@ $(() => {
|
|||
boardId: $boardApp.dataset.boardId,
|
||||
disabled: $boardApp.dataset.disabled === 'true',
|
||||
issueLinkBase: $boardApp.dataset.issueLinkBase,
|
||||
rootPath: $boardApp.dataset.rootPath,
|
||||
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
|
||||
detailIssue: Store.detail
|
||||
},
|
||||
computed: {
|
||||
|
@ -48,7 +53,7 @@ $(() => {
|
|||
},
|
||||
},
|
||||
created () {
|
||||
gl.boardService = new BoardService(this.endpoint, this.boardId);
|
||||
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
||||
},
|
||||
mounted () {
|
||||
Store.disabled = this.disabled;
|
||||
|
@ -59,8 +64,6 @@ $(() => {
|
|||
|
||||
if (list.type === 'done') {
|
||||
list.position = Infinity;
|
||||
} else if (list.type === 'backlog') {
|
||||
list.position = -1;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -81,4 +84,27 @@ $(() => {
|
|||
gl.issueBoards.newListDropdownInit();
|
||||
}
|
||||
});
|
||||
|
||||
gl.IssueBoardsModalAddBtn = new Vue({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
el: '#js-add-issues-btn',
|
||||
data: {
|
||||
modal: ModalStore.store,
|
||||
store: Store.state,
|
||||
},
|
||||
computed: {
|
||||
disabled() {
|
||||
return Store.shouldAddBlankState();
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<button
|
||||
class="btn btn-create pull-right prepend-left-10 has-tooltip"
|
||||
type="button"
|
||||
:disabled="disabled"
|
||||
@click="toggleModal(true)">
|
||||
Add issues
|
||||
</button>
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
props: {
|
||||
list: Object,
|
||||
disabled: Boolean,
|
||||
issueLinkBase: String
|
||||
issueLinkBase: String,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
|
||||
//= require ./issue_card_inner
|
||||
/* global Vue */
|
||||
|
||||
(() => {
|
||||
|
@ -9,12 +10,16 @@
|
|||
|
||||
gl.issueBoards.BoardCard = Vue.extend({
|
||||
template: '#js-board-list-card',
|
||||
components: {
|
||||
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
props: {
|
||||
list: Object,
|
||||
issue: Object,
|
||||
issueLinkBase: String,
|
||||
disabled: Boolean,
|
||||
index: Number
|
||||
index: Number,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
@ -28,31 +33,6 @@
|
|||
}
|
||||
},
|
||||
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 () {
|
||||
this.showDetail = true;
|
||||
},
|
||||
|
@ -71,6 +51,7 @@
|
|||
Store.detail.issue = {};
|
||||
} else {
|
||||
Store.detail.issue = this.issue;
|
||||
Store.detail.list = this.list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
issues: Array,
|
||||
loading: Boolean,
|
||||
issueLinkBase: String,
|
||||
rootPath: String,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
$(this.$refs.submitButton).enable();
|
||||
|
||||
Store.detail.issue = issue;
|
||||
Store.detail.list = this.list;
|
||||
})
|
||||
.catch(() => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
/* global MilestoneSelect */
|
||||
/* global LabelsSelect */
|
||||
/* global Sidebar */
|
||||
//= require ./sidebar/remove_issue
|
||||
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
@ -18,7 +19,8 @@
|
|||
data() {
|
||||
return {
|
||||
detail: Store.detail,
|
||||
issue: {}
|
||||
issue: {},
|
||||
list: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -36,6 +38,7 @@
|
|||
}
|
||||
|
||||
this.issue = this.detail.issue;
|
||||
this.list = this.detail.list;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
@ -60,6 +63,9 @@
|
|||
new LabelsSelect();
|
||||
new Sidebar();
|
||||
gl.Subscription.bindAll('.subscription');
|
||||
}
|
||||
},
|
||||
components: {
|
||||
removeBtn: gl.issueBoards.RemoveIssueBtn,
|
||||
},
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/* global Vue */
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.IssueCardInner = Vue.extend({
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: false,
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
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.currentTarget).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();
|
||||
},
|
||||
labelStyle(label) {
|
||||
return {
|
||||
backgroundColor: label.color,
|
||||
color: label.textColor,
|
||||
};
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<h4 class="card-title">
|
||||
<i
|
||||
class="fa fa-eye-slash confidential-icon"
|
||||
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="card-assignee has-tooltip"
|
||||
:href="rootPath + 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"
|
||||
:alt="'Avatar for ' + issue.assignee.name" />
|
||||
</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="labelStyle(label)"
|
||||
:title="label.description"
|
||||
data-container="body">
|
||||
{{ label.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,70 @@
|
|||
/* global Vue */
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalEmptyState = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newIssuePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
contents() {
|
||||
const obj = {
|
||||
title: 'You haven\'t added any issues to your project yet',
|
||||
content: `
|
||||
An issue can be a bug, a todo or a feature request that needs to be
|
||||
discussed in a project. Besides, issues are searchable and filterable.
|
||||
`,
|
||||
};
|
||||
|
||||
if (this.activeTab === 'selected') {
|
||||
obj.title = 'You haven\'t selected any issues yet';
|
||||
obj.content = `
|
||||
Go back to <strong>All issues</strong> and select some issues
|
||||
to add to your board.
|
||||
`;
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<section class="empty-state">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6 col-sm-push-6">
|
||||
<aside class="svg-content" v-html="image"></aside>
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6 col-sm-pull-6">
|
||||
<div class="text-content">
|
||||
<h4>{{ contents.title }}</h4>
|
||||
<p v-html="contents.content"></p>
|
||||
<a
|
||||
:href="newIssuePath"
|
||||
class="btn btn-success btn-inverted"
|
||||
v-if="activeTab === 'all'">
|
||||
New issue
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-default"
|
||||
@click="changeTab('all')"
|
||||
v-if="activeTab === 'selected'">
|
||||
All issues
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,81 @@
|
|||
/* eslint-disable no-new */
|
||||
//= require ./lists_dropdown
|
||||
/* global Vue */
|
||||
/* global Flash */
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalFooter = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
submitDisabled() {
|
||||
return !ModalStore.selectedCount();
|
||||
},
|
||||
submitText() {
|
||||
const count = ModalStore.selectedCount();
|
||||
|
||||
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addIssues() {
|
||||
const list = this.modal.selectedList || this.state.lists[0];
|
||||
const selectedIssues = ModalStore.getSelectedIssues();
|
||||
const issueIds = selectedIssues.map(issue => issue.globalId);
|
||||
|
||||
// Post the data to the backend
|
||||
gl.boardService.bulkUpdate(issueIds, {
|
||||
add_label_ids: [list.label.id],
|
||||
}).catch(() => {
|
||||
new Flash('Failed to update issues, please try again.', 'alert');
|
||||
|
||||
selectedIssues.forEach((issue) => {
|
||||
list.removeIssue(issue);
|
||||
list.issuesSize -= 1;
|
||||
});
|
||||
});
|
||||
|
||||
// Add the issues on the frontend
|
||||
selectedIssues.forEach((issue) => {
|
||||
list.addIssue(issue);
|
||||
list.issuesSize += 1;
|
||||
});
|
||||
|
||||
this.toggleModal(false);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
|
||||
},
|
||||
template: `
|
||||
<footer
|
||||
class="form-actions add-issues-footer">
|
||||
<div class="pull-left">
|
||||
<button
|
||||
class="btn btn-success"
|
||||
type="button"
|
||||
:disabled="submitDisabled"
|
||||
@click="addIssues">
|
||||
{{ submitText }}
|
||||
</button>
|
||||
<span class="inline add-issues-footer-to-list">
|
||||
to list
|
||||
</span>
|
||||
<lists-dropdown></lists-dropdown>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-default pull-right"
|
||||
type="button"
|
||||
@click="toggleModal(false)">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,68 @@
|
|||
/* global Vue */
|
||||
//= require ./tabs
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalHeader = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
computed: {
|
||||
selectAllText() {
|
||||
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
|
||||
return 'Select all';
|
||||
}
|
||||
|
||||
return 'Deselect all';
|
||||
},
|
||||
showSearch() {
|
||||
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleAll() {
|
||||
this.$refs.selectAllBtn.blur();
|
||||
|
||||
ModalStore.toggleAll();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'modal-tabs': gl.issueBoards.ModalTabs,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<header class="add-issues-header form-actions">
|
||||
<h2>
|
||||
Add issues
|
||||
<button
|
||||
type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
@click="toggleModal(false)">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</h2>
|
||||
</header>
|
||||
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
|
||||
<div
|
||||
class="add-issues-search append-bottom-10"
|
||||
v-if="showSearch">
|
||||
<input
|
||||
placeholder="Search issues..."
|
||||
class="form-control"
|
||||
type="search"
|
||||
v-model="searchTerm" />
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-success btn-inverted prepend-left-10"
|
||||
ref="selectAllBtn"
|
||||
@click="toggleAll">
|
||||
{{ selectAllText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,134 @@
|
|||
/* global Vue */
|
||||
/* global ListIssue */
|
||||
//= require ./header
|
||||
//= require ./list
|
||||
//= require ./footer
|
||||
//= require ./empty_state
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.IssuesModal = Vue.extend({
|
||||
props: {
|
||||
blankStateImage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newIssuePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
watch: {
|
||||
page() {
|
||||
this.loadIssues();
|
||||
},
|
||||
searchTerm() {
|
||||
this.searchOperation();
|
||||
},
|
||||
showAddIssuesModal() {
|
||||
if (this.showAddIssuesModal && !this.issues.length) {
|
||||
this.loading = true;
|
||||
|
||||
this.loadIssues()
|
||||
.then(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
} else if (!this.showAddIssuesModal) {
|
||||
this.issues = [];
|
||||
this.selectedIssues = [];
|
||||
this.issuesCount = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
searchOperation: _.debounce(function searchOperationDebounce() {
|
||||
this.loadIssues(true);
|
||||
}, 500),
|
||||
loadIssues(clearIssues = false) {
|
||||
return gl.boardService.getBacklog({
|
||||
search: this.searchTerm,
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
}).then((res) => {
|
||||
const data = res.json();
|
||||
|
||||
if (clearIssues) {
|
||||
this.issues = [];
|
||||
}
|
||||
|
||||
data.issues.forEach((issueObj) => {
|
||||
const issue = new ListIssue(issueObj);
|
||||
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
|
||||
issue.selected = !!foundSelectedIssue;
|
||||
|
||||
this.issues.push(issue);
|
||||
});
|
||||
|
||||
this.loadingNewPage = false;
|
||||
|
||||
if (!this.issuesCount) {
|
||||
this.issuesCount = data.size;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showList() {
|
||||
if (this.activeTab === 'selected') {
|
||||
return this.selectedIssues.length > 0;
|
||||
}
|
||||
|
||||
return this.issuesCount > 0;
|
||||
},
|
||||
showEmptyState() {
|
||||
if (!this.loading && this.issuesCount === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
'modal-header': gl.issueBoards.ModalHeader,
|
||||
'modal-list': gl.issueBoards.ModalList,
|
||||
'modal-footer': gl.issueBoards.ModalFooter,
|
||||
'empty-state': gl.issueBoards.ModalEmptyState,
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="add-issues-modal"
|
||||
v-if="showAddIssuesModal">
|
||||
<div class="add-issues-container">
|
||||
<modal-header></modal-header>
|
||||
<modal-list
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"
|
||||
v-if="!loading && showList"></modal-list>
|
||||
<empty-state
|
||||
v-if="showEmptyState"
|
||||
:image="blankStateImage"
|
||||
:new-issue-path="newIssuePath"></empty-state>
|
||||
<section
|
||||
class="add-issues-list text-center"
|
||||
v-if="loading">
|
||||
<div class="add-issues-list-loading">
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</section>
|
||||
<modal-footer></modal-footer>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,142 @@
|
|||
/* global Vue */
|
||||
/* global ListIssue */
|
||||
/* global bp */
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalList = Vue.extend({
|
||||
props: {
|
||||
issueLinkBase: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
rootPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
watch: {
|
||||
activeTab() {
|
||||
if (this.activeTab === 'all') {
|
||||
ModalStore.purgeUnselectedIssues();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
loopIssues() {
|
||||
if (this.activeTab === 'all') {
|
||||
return this.issues;
|
||||
}
|
||||
|
||||
return this.selectedIssues;
|
||||
},
|
||||
groupedIssues() {
|
||||
const groups = [];
|
||||
this.loopIssues.forEach((issue, i) => {
|
||||
const index = i % this.columns;
|
||||
|
||||
if (!groups[index]) {
|
||||
groups.push([]);
|
||||
}
|
||||
|
||||
groups[index].push(issue);
|
||||
});
|
||||
|
||||
return groups;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
scrollHandler() {
|
||||
const currentPage = Math.floor(this.issues.length / this.perPage);
|
||||
|
||||
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
|
||||
&& currentPage === this.page) {
|
||||
this.loadingNewPage = true;
|
||||
this.page += 1;
|
||||
}
|
||||
},
|
||||
toggleIssue(e, issue) {
|
||||
if (e.target.tagName !== 'A') {
|
||||
ModalStore.toggleIssue(issue);
|
||||
}
|
||||
},
|
||||
listHeight() {
|
||||
return this.$refs.list.getBoundingClientRect().height;
|
||||
},
|
||||
scrollHeight() {
|
||||
return this.$refs.list.scrollHeight;
|
||||
},
|
||||
scrollTop() {
|
||||
return this.$refs.list.scrollTop + this.listHeight();
|
||||
},
|
||||
showIssue(issue) {
|
||||
if (this.activeTab === 'all') return true;
|
||||
|
||||
const index = ModalStore.selectedIssueIndex(issue);
|
||||
|
||||
return index !== -1;
|
||||
},
|
||||
setColumnCount() {
|
||||
const breakpoint = bp.getBreakpointSize();
|
||||
|
||||
if (breakpoint === 'lg' || breakpoint === 'md') {
|
||||
this.columns = 3;
|
||||
} else if (breakpoint === 'sm') {
|
||||
this.columns = 2;
|
||||
} else {
|
||||
this.columns = 1;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.scrollHandlerWrapper = this.scrollHandler.bind(this);
|
||||
this.setColumnCountWrapper = this.setColumnCount.bind(this);
|
||||
this.setColumnCount();
|
||||
|
||||
this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
|
||||
window.addEventListener('resize', this.setColumnCountWrapper);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
|
||||
window.removeEventListener('resize', this.setColumnCountWrapper);
|
||||
},
|
||||
components: {
|
||||
'issue-card-inner': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
template: `
|
||||
<section
|
||||
class="add-issues-list add-issues-list-columns"
|
||||
ref="list">
|
||||
<div
|
||||
v-for="group in groupedIssues"
|
||||
class="add-issues-list-column">
|
||||
<div
|
||||
v-for="issue in group"
|
||||
v-if="showIssue(issue)"
|
||||
class="card-parent">
|
||||
<div
|
||||
class="card"
|
||||
:class="{ 'is-active': issue.selected }"
|
||||
@click="toggleIssue($event, issue)">
|
||||
<issue-card-inner
|
||||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath">
|
||||
</issue-card-inner>
|
||||
<span
|
||||
:aria-label="'Issue #' + issue.id + ' selected'"
|
||||
aria-checked="true"
|
||||
v-if="issue.selected"
|
||||
class="issue-card-selected text-center">
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,56 @@
|
|||
/* global Vue */
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selected() {
|
||||
return this.modal.selectedList || this.state.lists[0];
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
this.modal.selectedList = null;
|
||||
},
|
||||
template: `
|
||||
<div class="dropdown inline">
|
||||
<button
|
||||
class="dropdown-menu-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<span
|
||||
class="dropdown-label-box"
|
||||
:style="{ backgroundColor: selected.label.color }">
|
||||
</span>
|
||||
{{ selected.title }}
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
|
||||
<ul>
|
||||
<li
|
||||
v-for="list in state.lists"
|
||||
v-if="list.type == 'label'">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
:class="{ 'is-active': list.id == selected.id }"
|
||||
@click.prevent="modal.selectedList = list">
|
||||
<span
|
||||
class="dropdown-label-box"
|
||||
:style="{ backgroundColor: list.label.color }">
|
||||
</span>
|
||||
{{ list.title }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,47 @@
|
|||
/* global Vue */
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalTabs = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
computed: {
|
||||
selectedCount() {
|
||||
return ModalStore.selectedCount();
|
||||
},
|
||||
},
|
||||
destroyed() {
|
||||
this.activeTab = 'all';
|
||||
},
|
||||
template: `
|
||||
<div class="top-area prepend-top-10 append-bottom-10">
|
||||
<ul class="nav-links issues-state-filters">
|
||||
<li :class="{ 'active': activeTab == 'all' }">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="changeTab('all')">
|
||||
All issues
|
||||
<span class="badge">
|
||||
{{ issuesCount }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li :class="{ 'active': activeTab == 'selected' }">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
@click.prevent="changeTab('selected')">
|
||||
Selected issues
|
||||
<span class="badge">
|
||||
{{ selectedCount }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,59 @@
|
|||
/* eslint-disable no-new */
|
||||
/* global Vue */
|
||||
/* global Flash */
|
||||
(() => {
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.RemoveIssueBtn = Vue.extend({
|
||||
props: {
|
||||
issue: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
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(() => {
|
||||
new Flash('Failed to remove issue from board, please try again.', 'alert');
|
||||
|
||||
lists.forEach((list) => {
|
||||
list.addIssue(issue);
|
||||
});
|
||||
});
|
||||
|
||||
// Remove from the frontend store
|
||||
lists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
|
||||
Store.detail.issue = {};
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
class="block list"
|
||||
v-if="list.type !== 'done'">
|
||||
<button
|
||||
class="btn btn-default btn-block"
|
||||
type="button"
|
||||
@click="removeIssue">
|
||||
Remove from board
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,14 @@
|
|||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalMixins = {
|
||||
methods: {
|
||||
toggleModal(toggle) {
|
||||
ModalStore.store.showAddIssuesModal = toggle;
|
||||
},
|
||||
changeTab(tab) {
|
||||
ModalStore.store.activeTab = tab;
|
||||
},
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -6,12 +6,15 @@
|
|||
|
||||
class ListIssue {
|
||||
constructor (obj) {
|
||||
this.globalId = obj.id;
|
||||
this.id = obj.iid;
|
||||
this.title = obj.title;
|
||||
this.confidential = obj.confidential;
|
||||
this.dueDate = obj.due_date;
|
||||
this.subscribed = obj.subscribed;
|
||||
this.labels = [];
|
||||
this.selected = false;
|
||||
this.assignee = false;
|
||||
|
||||
if (obj.assignee) {
|
||||
this.assignee = new ListUser(obj.assignee);
|
||||
|
|
|
@ -9,7 +9,7 @@ class List {
|
|||
this.position = obj.position;
|
||||
this.title = obj.title;
|
||||
this.type = obj.list_type;
|
||||
this.preset = ['backlog', 'done', 'blank'].indexOf(this.type) > -1;
|
||||
this.preset = ['done', 'blank'].indexOf(this.type) > -1;
|
||||
this.filters = gl.issueBoards.BoardsStore.state.filters;
|
||||
this.page = 1;
|
||||
this.loading = true;
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
/* global Vue */
|
||||
|
||||
class BoardService {
|
||||
constructor (root, boardId) {
|
||||
constructor (root, bulkUpdatePath, boardId) {
|
||||
this.boards = Vue.resource(`${root}{/id}.json`, {}, {
|
||||
issues: {
|
||||
method: 'GET',
|
||||
url: `${root}/${boardId}/issues.json`
|
||||
}
|
||||
});
|
||||
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
|
||||
generate: {
|
||||
method: 'POST',
|
||||
|
@ -10,7 +16,12 @@ class BoardService {
|
|||
}
|
||||
});
|
||||
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
|
||||
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {});
|
||||
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}, {
|
||||
bulkUpdate: {
|
||||
method: 'POST',
|
||||
url: bulkUpdatePath,
|
||||
},
|
||||
});
|
||||
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
|
||||
|
@ -65,6 +76,20 @@ class BoardService {
|
|||
issue
|
||||
});
|
||||
}
|
||||
|
||||
getBacklog(data) {
|
||||
return this.boards.issues(data);
|
||||
}
|
||||
|
||||
bulkUpdate(issueIds, extraData = {}) {
|
||||
const data = {
|
||||
update: Object.assign(extraData, {
|
||||
issuable_ids: issueIds.join(','),
|
||||
}),
|
||||
};
|
||||
|
||||
return this.issues.bulkUpdate(data);
|
||||
}
|
||||
}
|
||||
|
||||
window.BoardService = BoardService;
|
||||
|
|
|
@ -34,15 +34,10 @@
|
|||
},
|
||||
new (listObj) {
|
||||
const list = this.addList(listObj);
|
||||
const backlogList = this.findList('type', 'backlog', 'backlog');
|
||||
|
||||
list
|
||||
.save()
|
||||
.then(() => {
|
||||
// Remove any new issues from the backlog
|
||||
// as they will be visible in the new list
|
||||
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
|
||||
|
||||
this.state.lists = _.sortBy(this.state.lists, 'position');
|
||||
});
|
||||
this.removeBlankState();
|
||||
|
@ -52,7 +47,7 @@
|
|||
},
|
||||
shouldAddBlankState () {
|
||||
// Decide whether to add the blank state
|
||||
return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'done')[0]);
|
||||
return !(this.state.lists.filter(list => list.type !== 'done')[0]);
|
||||
},
|
||||
addBlankState () {
|
||||
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
|
||||
|
@ -102,7 +97,7 @@
|
|||
listTo.addIssue(issue, listFrom, newIndex);
|
||||
}
|
||||
|
||||
if (listTo.type === 'done' && listFrom.type !== 'backlog') {
|
||||
if (listTo.type === 'done') {
|
||||
issueLists.forEach((list) => {
|
||||
list.removeIssue(issue);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
(() => {
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
class ModalStore {
|
||||
constructor() {
|
||||
this.store = {
|
||||
columns: 3,
|
||||
issues: [],
|
||||
issuesCount: false,
|
||||
selectedIssues: [],
|
||||
showAddIssuesModal: false,
|
||||
activeTab: 'all',
|
||||
selectedList: null,
|
||||
searchTerm: '',
|
||||
loading: false,
|
||||
loadingNewPage: false,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
};
|
||||
}
|
||||
|
||||
selectedCount() {
|
||||
return this.getSelectedIssues().length;
|
||||
}
|
||||
|
||||
toggleIssue(issueObj) {
|
||||
const issue = issueObj;
|
||||
const selected = issue.selected;
|
||||
|
||||
issue.selected = !selected;
|
||||
|
||||
if (!selected) {
|
||||
this.addSelectedIssue(issue);
|
||||
} else {
|
||||
this.removeSelectedIssue(issue);
|
||||
}
|
||||
}
|
||||
|
||||
toggleAll() {
|
||||
const select = this.selectedCount() !== this.store.issues.length;
|
||||
|
||||
this.store.issues.forEach((issue) => {
|
||||
const issueUpdate = issue;
|
||||
|
||||
if (issueUpdate.selected !== select) {
|
||||
issueUpdate.selected = select;
|
||||
|
||||
if (select) {
|
||||
this.addSelectedIssue(issue);
|
||||
} else {
|
||||
this.removeSelectedIssue(issue);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedIssues() {
|
||||
return this.store.selectedIssues.filter(issue => issue.selected);
|
||||
}
|
||||
|
||||
addSelectedIssue(issue) {
|
||||
const index = this.selectedIssueIndex(issue);
|
||||
|
||||
if (index === -1) {
|
||||
this.store.selectedIssues.push(issue);
|
||||
}
|
||||
}
|
||||
|
||||
removeSelectedIssue(issue, forcePurge = false) {
|
||||
if (this.store.activeTab === 'all' || forcePurge) {
|
||||
this.store.selectedIssues = this.store.selectedIssues
|
||||
.filter(fIssue => fIssue.id !== issue.id);
|
||||
}
|
||||
}
|
||||
|
||||
purgeUnselectedIssues() {
|
||||
this.store.selectedIssues.forEach((issue) => {
|
||||
if (!issue.selected) {
|
||||
this.removeSelectedIssue(issue, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
selectedIssueIndex(issue) {
|
||||
return this.store.selectedIssues.indexOf(issue);
|
||||
}
|
||||
|
||||
findSelectedIssue(issue) {
|
||||
return this.store.selectedIssues
|
||||
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
|
||||
}
|
||||
}
|
||||
|
||||
gl.issueBoards.ModalStore = new ModalStore();
|
||||
})();
|
|
@ -161,6 +161,9 @@
|
|||
gl.text.humanize = function(string) {
|
||||
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
|
||||
};
|
||||
gl.text.pluralize = function(str, count) {
|
||||
return str + (count > 1 || count === 0 ? 's' : '');
|
||||
};
|
||||
return gl.text.truncate = function(string, maxLength) {
|
||||
return string.substr(0, (maxLength - 3)) + '...';
|
||||
};
|
||||
|
|
|
@ -227,6 +227,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-menu-drop-up {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.dropdown-menu-large {
|
||||
width: 340px;
|
||||
}
|
||||
|
|
|
@ -250,7 +250,7 @@
|
|||
}
|
||||
|
||||
.issue-boards-search {
|
||||
width: 290px;
|
||||
width: 395px;
|
||||
|
||||
.form-control {
|
||||
display: inline-block;
|
||||
|
@ -354,3 +354,135 @@
|
|||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.add-issues-modal {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba($black, .3);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.add-issues-container {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
width: 90vw;
|
||||
height: 85vh;
|
||||
max-width: 1100px;
|
||||
min-height: 500px;
|
||||
margin: auto;
|
||||
padding: 25px 15px 0;
|
||||
background-color: $white-light;
|
||||
border-radius: $border-radius-default;
|
||||
box-shadow: 0 2px 12px rgba($black, .5);
|
||||
|
||||
.empty-state {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
margin-top: 0;
|
||||
|
||||
> .row {
|
||||
width: 100%;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.svg-content {
|
||||
margin-top: -40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-issues-header {
|
||||
margin: -25px -15px -5px;
|
||||
border-top: 0;
|
||||
border-bottom: 1px solid $border-color;
|
||||
border-top-right-radius: $border-radius-default;
|
||||
border-top-left-radius: $border-radius-default;
|
||||
|
||||
> h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-issues-search {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.add-issues-list-column {
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
width: (100% / 3);
|
||||
}
|
||||
}
|
||||
|
||||
.add-issues-list {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
padding-top: 3px;
|
||||
margin-left: -$gl-vert-padding;
|
||||
margin-right: -$gl-vert-padding;
|
||||
overflow-y: scroll;
|
||||
|
||||
.card-parent {
|
||||
padding: 0 5px 5px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid $border-gray-dark;
|
||||
box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, .3);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.add-issues-list-loading {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
padding-left: $gl-vert-padding;
|
||||
padding-right: $gl-vert-padding;
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
.add-issues-footer {
|
||||
margin: auto -15px 0;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
border-bottom-right-radius: $border-radius-default;
|
||||
border-bottom-left-radius: $border-radius-default;
|
||||
}
|
||||
|
||||
.add-issues-footer-to-list {
|
||||
padding-left: $gl-vert-padding;
|
||||
padding-right: $gl-vert-padding;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
.issue-card-selected {
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: -3px;
|
||||
width: 17px;
|
||||
background-color: $blue-light;
|
||||
color: $white-light;
|
||||
border: 1px solid $border-blue-light;
|
||||
font-size: 9px;
|
||||
line-height: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ module Projects
|
|||
|
||||
def index
|
||||
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
|
||||
issues = issues.page(params[:page])
|
||||
issues = issues.page(params[:page]).per(params[:per] || 20)
|
||||
|
||||
render json: {
|
||||
issues: serialize_as_json(issues),
|
||||
|
@ -59,7 +59,7 @@ module Projects
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.merge(board_id: params[:board_id], id: params[:list_id])
|
||||
params.merge(board_id: params[:board_id], id: params[:list_id]).compact
|
||||
end
|
||||
|
||||
def move_params
|
||||
|
@ -73,7 +73,7 @@ module Projects
|
|||
def serialize_as_json(resource)
|
||||
resource.as_json(
|
||||
labels: true,
|
||||
only: [:iid, :title, :confidential, :due_date],
|
||||
only: [:id, :iid, :title, :confidential, :due_date],
|
||||
include: {
|
||||
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||
milestone: { only: [:id, :title] }
|
||||
|
|
|
@ -6,7 +6,9 @@ module BoardsHelper
|
|||
endpoint: namespace_project_boards_path(@project.namespace, @project),
|
||||
board_id: board.id,
|
||||
disabled: "#{!can?(current_user, :admin_list, @project)}",
|
||||
issue_link_base: namespace_project_issues_path(@project.namespace, @project)
|
||||
issue_link_base: namespace_project_issues_path(@project.namespace, @project),
|
||||
root_path: root_path,
|
||||
bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,10 +5,6 @@ class Board < ActiveRecord::Base
|
|||
|
||||
validates :project, presence: true
|
||||
|
||||
def backlog_list
|
||||
lists.merge(List.backlog).take
|
||||
end
|
||||
|
||||
def done_list
|
||||
lists.merge(List.done).take
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ class List < ActiveRecord::Base
|
|||
belongs_to :board
|
||||
belongs_to :label
|
||||
|
||||
enum list_type: { backlog: 0, label: 1, done: 2 }
|
||||
enum list_type: { label: 1, done: 2 }
|
||||
|
||||
validates :board, :list_type, presence: true
|
||||
validates :label, :position, presence: true, if: :label?
|
||||
|
|
|
@ -12,7 +12,6 @@ module Boards
|
|||
|
||||
def create_board!
|
||||
board = project.boards.create
|
||||
board.lists.create(list_type: :backlog)
|
||||
board.lists.create(list_type: :done)
|
||||
|
||||
board
|
||||
|
|
|
@ -3,8 +3,8 @@ module Boards
|
|||
class ListService < BaseService
|
||||
def execute
|
||||
issues = IssuesFinder.new(current_user, filter_params).execute
|
||||
issues = without_board_labels(issues) unless list.movable?
|
||||
issues = with_list_label(issues) if list.movable?
|
||||
issues = without_board_labels(issues) unless movable_list?
|
||||
issues = with_list_label(issues) if movable_list?
|
||||
issues
|
||||
end
|
||||
|
||||
|
@ -15,7 +15,13 @@ module Boards
|
|||
end
|
||||
|
||||
def list
|
||||
@list ||= board.lists.find(params[:id])
|
||||
return @list if defined?(@list)
|
||||
|
||||
@list = board.lists.find(params[:id]) if params.key?(:id)
|
||||
end
|
||||
|
||||
def movable_list?
|
||||
@movable_list ||= list.present? && list.movable?
|
||||
end
|
||||
|
||||
def filter_params
|
||||
|
@ -40,7 +46,7 @@ module Boards
|
|||
end
|
||||
|
||||
def set_state
|
||||
params[:state] = list.done? ? 'closed' : 'opened'
|
||||
params[:state] = list && list.done? ? 'closed' : 'opened'
|
||||
end
|
||||
|
||||
def board_label_ids
|
||||
|
|
|
@ -24,5 +24,10 @@
|
|||
":list" => "list",
|
||||
":disabled" => "disabled",
|
||||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath",
|
||||
":key" => "_uid" }
|
||||
= render "projects/boards/components/sidebar"
|
||||
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'),
|
||||
"new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project),
|
||||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath" }
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
":loading" => "list.loading",
|
||||
":disabled" => "disabled",
|
||||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath",
|
||||
"ref" => "board-list" }
|
||||
- if can?(current_user, :admin_list, @project)
|
||||
= render "projects/boards/components/blank_state"
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
":list" => "list",
|
||||
":issue" => "issue",
|
||||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath",
|
||||
":disabled" => "disabled",
|
||||
":key" => "issue.id" }
|
||||
%li.board-list-count.text-center{ "v-if" => "showCount" }
|
||||
|
|
|
@ -4,25 +4,7 @@
|
|||
"@mousedown" => "mouseDown",
|
||||
"@mousemove" => "mouseMove",
|
||||
"@mouseup" => "showIssue($event)" }
|
||||
%h4.card-title
|
||||
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
|
||||
%a{ ":href" => 'issueLinkBase + "/" + issue.id',
|
||||
":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 }}
|
||||
%issue-card-inner{ ":list" => "list",
|
||||
":issue" => "issue",
|
||||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath" }
|
||||
|
|
|
@ -22,3 +22,5 @@
|
|||
= render "projects/boards/components/sidebar/due_date"
|
||||
= render "projects/boards/components/sidebar/labels"
|
||||
= render "projects/boards/components/sidebar/notifications"
|
||||
%remove-btn{ ":issue" => "issue",
|
||||
":list" => "list" }
|
||||
|
|
|
@ -38,8 +38,9 @@
|
|||
#js-boards-search.issue-boards-search
|
||||
%input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
|
||||
- if can?(current_user, :admin_list, @project)
|
||||
#js-add-issues-btn.pull-right.prepend-left-10
|
||||
.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.btn-inverted.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
|
||||
.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" }
|
||||
|
|
|
@ -267,7 +267,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
|
||||
resources :boards, only: [:index, :show] do
|
||||
scope module: :boards do
|
||||
resources :issues, only: [:update]
|
||||
resources :issues, only: [:index, :update]
|
||||
|
||||
resources :lists, only: [:index, :create, :update, :destroy] do
|
||||
collection do
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
class RemoveBacklogListsFromBoards < ActiveRecord::Migration
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
execute <<-SQL
|
||||
DELETE FROM lists WHERE list_type = 0;
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<-SQL
|
||||
INSERT INTO lists (board_id, list_type, created_at, updated_at)
|
||||
SELECT boards.id, 0, NOW(), NOW()
|
||||
FROM boards;
|
||||
SQL
|
||||
end
|
||||
end
|
|
@ -37,7 +37,7 @@ module API
|
|||
end
|
||||
|
||||
desc 'Get the lists of a project board' do
|
||||
detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13'
|
||||
detail 'Does not include `done` list. This feature was introduced in 8.13'
|
||||
success Entities::List
|
||||
end
|
||||
get '/lists' do
|
||||
|
|
|
@ -18,23 +18,7 @@ describe Projects::Boards::IssuesController do
|
|||
end
|
||||
|
||||
describe 'GET index' do
|
||||
context 'with valid list id' do
|
||||
it 'returns issues that have the list label applied' do
|
||||
johndoe = create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png')))
|
||||
issue = create(:labeled_issue, project: project, labels: [planning])
|
||||
create(:labeled_issue, project: project, labels: [planning])
|
||||
create(:labeled_issue, project: project, labels: [development], due_date: Date.tomorrow)
|
||||
create(:labeled_issue, project: project, labels: [development], assignee: johndoe)
|
||||
issue.subscribe(johndoe, project)
|
||||
|
||||
list_issues user: user, board: board, list: list2
|
||||
|
||||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(response).to match_response_schema('issues')
|
||||
expect(parsed_response.length).to eq 2
|
||||
end
|
||||
end
|
||||
let(:johndoe) { create(:user, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
|
||||
|
||||
context 'with invalid board id' do
|
||||
it 'returns a not found 404 response' do
|
||||
|
@ -44,11 +28,47 @@ describe Projects::Boards::IssuesController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with invalid list id' do
|
||||
it 'returns a not found 404 response' do
|
||||
list_issues user: user, board: board, list: 999
|
||||
context 'when list id is present' do
|
||||
context 'with valid list id' do
|
||||
it 'returns issues that have the list label applied' do
|
||||
issue = create(:labeled_issue, project: project, labels: [planning])
|
||||
create(:labeled_issue, project: project, labels: [planning])
|
||||
create(:labeled_issue, project: project, labels: [development], due_date: Date.tomorrow)
|
||||
create(:labeled_issue, project: project, labels: [development], assignee: johndoe)
|
||||
issue.subscribe(johndoe, project)
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
list_issues user: user, board: board, list: list2
|
||||
|
||||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(response).to match_response_schema('issues')
|
||||
expect(parsed_response.length).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid list id' do
|
||||
it 'returns a not found 404 response' do
|
||||
list_issues user: user, board: board, list: 999
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when list id is missing' do
|
||||
it 'returns opened issues without board labels applied' do
|
||||
bug = create(:label, project: project, name: 'Bug')
|
||||
create(:issue, project: project)
|
||||
create(:labeled_issue, project: project, labels: [planning])
|
||||
create(:labeled_issue, project: project, labels: [development])
|
||||
create(:labeled_issue, project: project, labels: [bug])
|
||||
|
||||
list_issues user: user, board: board
|
||||
|
||||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(response).to match_response_schema('issues')
|
||||
expect(parsed_response.length).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -65,13 +85,17 @@ describe Projects::Boards::IssuesController do
|
|||
end
|
||||
end
|
||||
|
||||
def list_issues(user:, board:, list:)
|
||||
def list_issues(user:, board:, list: nil)
|
||||
sign_in(user)
|
||||
|
||||
get :index, namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param,
|
||||
board_id: board.to_param,
|
||||
list_id: list.to_param
|
||||
params = {
|
||||
namespace_id: project.namespace.to_param,
|
||||
project_id: project.to_param,
|
||||
board_id: board.to_param,
|
||||
list_id: list.try(:to_param)
|
||||
}
|
||||
|
||||
get :index, params.compact
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ describe Projects::Boards::ListsController do
|
|||
parsed_response = JSON.parse(response.body)
|
||||
|
||||
expect(response).to match_response_schema('lists')
|
||||
expect(parsed_response.length).to eq 3
|
||||
expect(parsed_response.length).to eq 2
|
||||
end
|
||||
|
||||
context 'with unauthorized user' do
|
||||
|
|
|
@ -3,7 +3,6 @@ FactoryGirl.define do
|
|||
project factory: :empty_project
|
||||
|
||||
after(:create) do |board|
|
||||
board.lists.create(list_type: :backlog)
|
||||
board.lists.create(list_type: :done)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,6 @@ FactoryGirl.define do
|
|||
sequence(:position)
|
||||
end
|
||||
|
||||
factory :backlog_list, parent: :list do
|
||||
list_type :backlog
|
||||
label nil
|
||||
position nil
|
||||
end
|
||||
|
||||
factory :done_list, parent: :list do
|
||||
list_type :done
|
||||
label nil
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe 'Issue Boards add issue modal', :feature, :js do
|
||||
include WaitForAjax
|
||||
include WaitForVueResource
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:board) { create(:board, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
let!(:planning) { create(:label, project: project, name: 'Planning') }
|
||||
let!(:label) { create(:label, project: project) }
|
||||
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
|
||||
let!(:list2) { create(:list, board: board, label: label, position: 1) }
|
||||
let!(:issue) { create(:issue, project: project) }
|
||||
let!(:issue2) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
|
||||
login_as(user)
|
||||
|
||||
visit namespace_project_board_path(project.namespace, project, board)
|
||||
wait_for_vue_resource
|
||||
end
|
||||
|
||||
context 'modal interaction' do
|
||||
it 'opens modal' do
|
||||
click_button('Add issues')
|
||||
|
||||
expect(page).to have_selector('.add-issues-modal')
|
||||
end
|
||||
|
||||
it 'closes modal' do
|
||||
click_button('Add issues')
|
||||
|
||||
page.within('.add-issues-modal') do
|
||||
find('.close').click
|
||||
end
|
||||
|
||||
expect(page).not_to have_selector('.add-issues-modal')
|
||||
end
|
||||
|
||||
it 'closes modal if cancel button clicked' do
|
||||
click_button('Add issues')
|
||||
|
||||
page.within('.add-issues-modal') do
|
||||
click_button 'Cancel'
|
||||
end
|
||||
|
||||
expect(page).not_to have_selector('.add-issues-modal')
|
||||
end
|
||||
end
|
||||
|
||||
context 'issues list' do
|
||||
before do
|
||||
click_button('Add issues')
|
||||
|
||||
wait_for_vue_resource
|
||||
end
|
||||
|
||||
it 'loads issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
page.within('.nav-links') do
|
||||
expect(page).to have_content('2')
|
||||
end
|
||||
|
||||
expect(page).to have_selector('.card', count: 2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows selected issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
click_link 'Selected issues'
|
||||
|
||||
expect(page).not_to have_selector('.card')
|
||||
end
|
||||
end
|
||||
|
||||
context 'list dropdown' do
|
||||
it 'resets after deleting list' do
|
||||
page.within('.add-issues-modal') do
|
||||
expect(find('.add-issues-footer')).to have_button(planning.title)
|
||||
|
||||
click_button 'Cancel'
|
||||
end
|
||||
|
||||
first('.board-delete').click
|
||||
|
||||
click_button('Add issues')
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
page.within('.add-issues-modal') do
|
||||
expect(find('.add-issues-footer')).not_to have_button(planning.title)
|
||||
expect(find('.add-issues-footer')).to have_button(label.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'search' do
|
||||
it 'returns issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
find('.form-control').native.send_keys(issue.title)
|
||||
|
||||
expect(page).to have_selector('.card', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns no issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
find('.form-control').native.send_keys('testing search')
|
||||
|
||||
expect(page).not_to have_selector('.card')
|
||||
expect(page).not_to have_content("You haven't added any issues to your project yet")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'selecing issues' do
|
||||
it 'selects single issue' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
page.within('.nav-links') do
|
||||
expect(page).to have_content('Selected issues 1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'changes button text' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
expect(first('.add-issues-footer .btn')).to have_content('Add 1 issue')
|
||||
end
|
||||
end
|
||||
|
||||
it 'changes button text with plural' do
|
||||
page.within('.add-issues-modal') do
|
||||
all('.card').each do |el|
|
||||
el.click
|
||||
end
|
||||
|
||||
expect(first('.add-issues-footer .btn')).to have_content('Add 2 issues')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows only selected issues on selected tab' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
click_link 'Selected issues'
|
||||
|
||||
expect(page).to have_selector('.card', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
it 'selects all issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
click_button 'Select all'
|
||||
|
||||
expect(page).to have_selector('.is-active', count: 2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'deselects all issues' do
|
||||
page.within('.add-issues-modal') do
|
||||
click_button 'Select all'
|
||||
|
||||
expect(page).to have_selector('.is-active', count: 2)
|
||||
|
||||
click_button 'Deselect all'
|
||||
|
||||
expect(page).not_to have_selector('.is-active')
|
||||
end
|
||||
end
|
||||
|
||||
it 'selects all that arent already selected' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
expect(page).to have_selector('.is-active', count: 1)
|
||||
|
||||
click_button 'Select all'
|
||||
|
||||
expect(page).to have_selector('.is-active', count: 2)
|
||||
end
|
||||
end
|
||||
|
||||
it 'unselects from selected tab' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
click_link 'Selected issues'
|
||||
|
||||
first('.card').click
|
||||
|
||||
expect(page).not_to have_selector('.is-active')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'adding issues' do
|
||||
it 'adds to board' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
click_button 'Add 1 issue'
|
||||
end
|
||||
|
||||
page.within(first('.board')) do
|
||||
expect(page).to have_selector('.card')
|
||||
end
|
||||
end
|
||||
|
||||
it 'adds to second list' do
|
||||
page.within('.add-issues-modal') do
|
||||
first('.card').click
|
||||
|
||||
click_button planning.title
|
||||
|
||||
click_link label.title
|
||||
|
||||
click_button 'Add 1 issue'
|
||||
end
|
||||
|
||||
page.within(find('.board:nth-child(2)')) do
|
||||
expect(page).to have_selector('.card')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,7 +20,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
before do
|
||||
visit namespace_project_board_path(project.namespace, project, board)
|
||||
wait_for_vue_resource
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
expect(page).to have_selector('.board', count: 2)
|
||||
end
|
||||
|
||||
it 'shows blank state' do
|
||||
|
@ -31,18 +31,18 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
page.within(find('.board-blank-state')) do
|
||||
click_button("Nevermind, I'll use my own")
|
||||
end
|
||||
expect(page).to have_selector('.board', count: 2)
|
||||
expect(page).to have_selector('.board', count: 1)
|
||||
end
|
||||
|
||||
it 'creates default lists' do
|
||||
lists = ['Backlog', 'To Do', 'Doing', 'Done']
|
||||
lists = ['To Do', 'Doing', 'Done']
|
||||
|
||||
page.within(find('.board-blank-state')) do
|
||||
click_button('Add default lists')
|
||||
end
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
|
||||
page.all('.board').each_with_index do |list, i|
|
||||
expect(list.find('.board-title')).to have_content(lists[i])
|
||||
|
@ -64,42 +64,41 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
|
||||
let!(:list2) { create(:list, board: board, label: development, position: 1) }
|
||||
|
||||
let!(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
|
||||
let!(:issue1) { create(:issue, project: project, assignee: user) }
|
||||
let!(:issue2) { create(:issue, project: project, author: user2) }
|
||||
let!(:issue3) { create(:issue, project: project) }
|
||||
let!(:issue4) { create(:issue, project: project) }
|
||||
let!(:confidential_issue) { create(:labeled_issue, :confidential, project: project, author: user, labels: [planning]) }
|
||||
let!(:issue1) { create(:labeled_issue, project: project, assignee: user, labels: [planning]) }
|
||||
let!(:issue2) { create(:labeled_issue, project: project, author: user2, labels: [planning]) }
|
||||
let!(:issue3) { create(:labeled_issue, project: project, labels: [planning]) }
|
||||
let!(:issue4) { create(:labeled_issue, project: project, labels: [planning]) }
|
||||
let!(:issue5) { create(:labeled_issue, project: project, labels: [planning], milestone: milestone) }
|
||||
let!(:issue6) { create(:labeled_issue, project: project, labels: [planning, development]) }
|
||||
let!(:issue7) { create(:labeled_issue, project: project, labels: [development]) }
|
||||
let!(:issue8) { create(:closed_issue, project: project) }
|
||||
let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug, accepting]) }
|
||||
let!(:issue9) { create(:labeled_issue, project: project, labels: [planning, testing, bug, accepting]) }
|
||||
|
||||
before do
|
||||
visit namespace_project_board_path(project.namespace, project, board)
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
expect(find('.board:nth-child(1)')).to have_selector('.card')
|
||||
expect(find('.board:nth-child(2)')).to have_selector('.card')
|
||||
expect(find('.board:nth-child(3)')).to have_selector('.card')
|
||||
expect(find('.board:nth-child(4)')).to have_selector('.card')
|
||||
end
|
||||
|
||||
it 'shows lists' do
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
end
|
||||
|
||||
it 'shows description tooltip on list title' do
|
||||
page.within('.board:nth-child(2)') do
|
||||
page.within('.board:nth-child(1)') do
|
||||
expect(find('.board-title span.has-tooltip')[:title]).to eq('Test')
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows issues in lists' do
|
||||
wait_for_board_cards(1, 8)
|
||||
wait_for_board_cards(2, 2)
|
||||
wait_for_board_cards(3, 2)
|
||||
end
|
||||
|
||||
it 'shows confidential issues with icon' do
|
||||
|
@ -108,19 +107,6 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
end
|
||||
end
|
||||
|
||||
it 'search backlog list' do
|
||||
page.within('#js-boards-search') do
|
||||
find('.form-control').set(issue1.title)
|
||||
end
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1)
|
||||
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
|
||||
end
|
||||
|
||||
it 'search done list' do
|
||||
page.within('#js-boards-search') do
|
||||
find('.form-control').set(issue8.title)
|
||||
|
@ -130,8 +116,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1)
|
||||
expect(find('.board:nth-child(3)')).to have_selector('.card', count: 1)
|
||||
end
|
||||
|
||||
it 'search list' do
|
||||
|
@ -141,157 +126,135 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(find('.board:nth-child(1)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1)
|
||||
expect(find('.board:nth-child(1)')).to have_selector('.card', count: 1)
|
||||
expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0)
|
||||
expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0)
|
||||
end
|
||||
|
||||
it 'allows user to delete board' do
|
||||
page.within(find('.board:nth-child(2)')) do
|
||||
page.within(find('.board:nth-child(1)')) do
|
||||
find('.board-delete').click
|
||||
end
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
expect(page).to have_selector('.board', count: 2)
|
||||
end
|
||||
|
||||
it 'removes checkmark in new list dropdown after deleting' do
|
||||
click_button 'Add list'
|
||||
wait_for_ajax
|
||||
|
||||
page.within(find('.board:nth-child(2)')) do
|
||||
page.within(find('.board:nth-child(1)')) do
|
||||
find('.board-delete').click
|
||||
end
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
expect(find(".js-board-list-#{planning.id}", visible: false)).not_to have_css('.is-active')
|
||||
expect(page).to have_selector('.board', count: 2)
|
||||
end
|
||||
|
||||
it 'infinite scrolls list' do
|
||||
50.times do
|
||||
create(:issue, project: project)
|
||||
create(:labeled_issue, project: project, labels: [planning])
|
||||
end
|
||||
|
||||
visit namespace_project_board_path(project.namespace, project, board)
|
||||
wait_for_vue_resource
|
||||
|
||||
page.within(find('.board', match: :first)) do
|
||||
expect(page.find('.board-header')).to have_content('56')
|
||||
expect(page.find('.board-header')).to have_content('58')
|
||||
expect(page).to have_selector('.card', count: 20)
|
||||
expect(page).to have_content('Showing 20 of 56 issues')
|
||||
expect(page).to have_content('Showing 20 of 58 issues')
|
||||
|
||||
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.card', count: 40)
|
||||
expect(page).to have_content('Showing 40 of 56 issues')
|
||||
expect(page).to have_content('Showing 40 of 58 issues')
|
||||
|
||||
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.card', count: 56)
|
||||
expect(page).to have_selector('.card', count: 58)
|
||||
expect(page).to have_content('Showing all issues')
|
||||
end
|
||||
end
|
||||
|
||||
context 'backlog' do
|
||||
it 'shows issues in backlog with no labels' do
|
||||
wait_for_board_cards(1, 6)
|
||||
end
|
||||
|
||||
it 'moves issue from backlog into list' do
|
||||
drag_to(list_to_index: 1)
|
||||
|
||||
wait_for_vue_resource
|
||||
wait_for_board_cards(1, 5)
|
||||
wait_for_board_cards(2, 3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'done' do
|
||||
it 'shows list of done issues' do
|
||||
wait_for_board_cards(4, 1)
|
||||
wait_for_board_cards(3, 1)
|
||||
wait_for_ajax
|
||||
end
|
||||
|
||||
it 'moves issue to done' do
|
||||
drag_to(list_from_index: 0, list_to_index: 3)
|
||||
drag_to(list_from_index: 0, list_to_index: 2)
|
||||
|
||||
wait_for_board_cards(1, 5)
|
||||
wait_for_board_cards(1, 7)
|
||||
wait_for_board_cards(2, 2)
|
||||
wait_for_board_cards(3, 2)
|
||||
wait_for_board_cards(4, 2)
|
||||
|
||||
expect(find('.board:nth-child(1)')).not_to have_content(issue9.title)
|
||||
expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2)
|
||||
expect(find('.board:nth-child(4)')).to have_content(issue9.title)
|
||||
expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
|
||||
expect(find('.board:nth-child(3)')).to have_selector('.card', count: 2)
|
||||
expect(find('.board:nth-child(3)')).to have_content(issue9.title)
|
||||
expect(find('.board:nth-child(3)')).not_to have_content(planning.title)
|
||||
end
|
||||
|
||||
it 'removes all of the same issue to done' do
|
||||
drag_to(list_from_index: 1, list_to_index: 3)
|
||||
drag_to(list_from_index: 0, list_to_index: 2)
|
||||
|
||||
wait_for_board_cards(1, 6)
|
||||
wait_for_board_cards(2, 1)
|
||||
wait_for_board_cards(3, 1)
|
||||
wait_for_board_cards(4, 2)
|
||||
wait_for_board_cards(1, 7)
|
||||
wait_for_board_cards(2, 2)
|
||||
wait_for_board_cards(3, 2)
|
||||
|
||||
expect(find('.board:nth-child(2)')).not_to have_content(issue6.title)
|
||||
expect(find('.board:nth-child(4)')).to have_content(issue6.title)
|
||||
expect(find('.board:nth-child(4)')).not_to have_content(planning.title)
|
||||
expect(find('.board:nth-child(1)')).not_to have_content(issue9.title)
|
||||
expect(find('.board:nth-child(3)')).to have_content(issue9.title)
|
||||
expect(find('.board:nth-child(3)')).not_to have_content(planning.title)
|
||||
end
|
||||
end
|
||||
|
||||
context 'lists' do
|
||||
it 'changes position of list' do
|
||||
drag_to(list_from_index: 1, list_to_index: 2, selector: '.board-header')
|
||||
drag_to(list_from_index: 1, list_to_index: 0, selector: '.board-header')
|
||||
|
||||
wait_for_board_cards(1, 6)
|
||||
wait_for_board_cards(2, 2)
|
||||
wait_for_board_cards(3, 2)
|
||||
wait_for_board_cards(4, 1)
|
||||
|
||||
expect(find('.board:nth-child(2)')).to have_content(development.title)
|
||||
expect(find('.board:nth-child(2)')).to have_content(planning.title)
|
||||
end
|
||||
|
||||
it 'issue moves between lists' do
|
||||
drag_to(list_from_index: 1, card_index: 1, list_to_index: 2)
|
||||
|
||||
wait_for_board_cards(1, 6)
|
||||
wait_for_board_cards(2, 1)
|
||||
wait_for_board_cards(3, 3)
|
||||
wait_for_board_cards(4, 1)
|
||||
|
||||
expect(find('.board:nth-child(3)')).to have_content(issue6.title)
|
||||
expect(find('.board:nth-child(3)').all('.card').last).not_to have_content(development.title)
|
||||
end
|
||||
|
||||
it 'issue moves between lists' do
|
||||
drag_to(list_from_index: 2, list_to_index: 1)
|
||||
|
||||
wait_for_board_cards(1, 6)
|
||||
wait_for_board_cards(2, 3)
|
||||
wait_for_board_cards(1, 2)
|
||||
wait_for_board_cards(2, 8)
|
||||
wait_for_board_cards(3, 1)
|
||||
wait_for_board_cards(4, 1)
|
||||
|
||||
expect(find('.board:nth-child(2)')).to have_content(issue7.title)
|
||||
expect(find('.board:nth-child(2)').all('.card').first).not_to have_content(planning.title)
|
||||
expect(find('.board:nth-child(1)')).to have_content(development.title)
|
||||
expect(find('.board:nth-child(1)')).to have_content(planning.title)
|
||||
end
|
||||
|
||||
it 'issue moves between lists' do
|
||||
drag_to(list_from_index: 0, card_index: 1, list_to_index: 1)
|
||||
|
||||
wait_for_board_cards(1, 7)
|
||||
wait_for_board_cards(2, 2)
|
||||
wait_for_board_cards(3, 1)
|
||||
|
||||
expect(find('.board:nth-child(2)')).to have_content(issue6.title)
|
||||
expect(find('.board:nth-child(2)').all('.card').last).not_to have_content(development.title)
|
||||
end
|
||||
|
||||
it 'issue moves between lists' do
|
||||
drag_to(list_from_index: 1, list_to_index: 0)
|
||||
|
||||
wait_for_board_cards(1, 9)
|
||||
wait_for_board_cards(2, 1)
|
||||
wait_for_board_cards(3, 1)
|
||||
|
||||
expect(find('.board:nth-child(1)')).to have_content(issue7.title)
|
||||
expect(find('.board:nth-child(1)').all('.card').first).not_to have_content(planning.title)
|
||||
end
|
||||
|
||||
it 'issue moves from done' do
|
||||
drag_to(list_from_index: 3, list_to_index: 1)
|
||||
drag_to(list_from_index: 2, list_to_index: 1)
|
||||
|
||||
expect(find('.board:nth-child(2)')).to have_content(issue8.title)
|
||||
|
||||
wait_for_board_cards(1, 6)
|
||||
wait_for_board_cards(1, 8)
|
||||
wait_for_board_cards(2, 3)
|
||||
wait_for_board_cards(3, 2)
|
||||
wait_for_board_cards(4, 0)
|
||||
wait_for_board_cards(3, 0)
|
||||
end
|
||||
|
||||
context 'issue card' do
|
||||
|
@ -324,7 +287,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 5)
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
end
|
||||
|
||||
it 'creates new list for Backlog label' do
|
||||
|
@ -337,7 +300,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 5)
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
end
|
||||
|
||||
it 'creates new list for Done label' do
|
||||
|
@ -350,7 +313,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 5)
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
end
|
||||
|
||||
it 'keeps dropdown open after adding new list' do
|
||||
|
@ -366,21 +329,6 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
expect(find('.issue-boards-search')).to have_selector('.open')
|
||||
end
|
||||
|
||||
it 'moves issues from backlog into new list' do
|
||||
wait_for_board_cards(1, 6)
|
||||
|
||||
click_button 'Add list'
|
||||
wait_for_ajax
|
||||
|
||||
page.within('.dropdown-menu-issues-board-new') do
|
||||
click_link testing.title
|
||||
end
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
wait_for_board_cards(1, 5)
|
||||
end
|
||||
|
||||
it 'creates new list from a new label' do
|
||||
click_button 'Add list'
|
||||
|
||||
|
@ -397,7 +345,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
wait_for_ajax
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 5)
|
||||
expect(page).to have_selector('.board', count: 4)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -418,7 +366,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_vue_resource
|
||||
wait_for_board_cards(1, 1)
|
||||
wait_for_empty_boards((2..4))
|
||||
wait_for_empty_boards((2..3))
|
||||
end
|
||||
|
||||
it 'filters by assignee' do
|
||||
|
@ -437,7 +385,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
wait_for_vue_resource
|
||||
|
||||
wait_for_board_cards(1, 1)
|
||||
wait_for_empty_boards((2..4))
|
||||
wait_for_empty_boards((2..3))
|
||||
end
|
||||
|
||||
it 'filters by milestone' do
|
||||
|
@ -454,10 +402,9 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
end
|
||||
|
||||
wait_for_vue_resource
|
||||
wait_for_board_cards(1, 0)
|
||||
wait_for_board_cards(2, 1)
|
||||
wait_for_board_cards(1, 1)
|
||||
wait_for_board_cards(2, 0)
|
||||
wait_for_board_cards(3, 0)
|
||||
wait_for_board_cards(4, 0)
|
||||
end
|
||||
|
||||
it 'filters by label' do
|
||||
|
@ -474,7 +421,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_vue_resource
|
||||
wait_for_board_cards(1, 1)
|
||||
wait_for_empty_boards((2..4))
|
||||
wait_for_empty_boards((2..3))
|
||||
end
|
||||
|
||||
it 'filters by label with space after reload' do
|
||||
|
@ -530,7 +477,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
it 'infinite scrolls list with label filter' do
|
||||
50.times do
|
||||
create(:labeled_issue, project: project, labels: [testing])
|
||||
create(:labeled_issue, project: project, labels: [planning, testing])
|
||||
end
|
||||
|
||||
page.within '.issues-filters' do
|
||||
|
@ -580,32 +527,12 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
wait_for_vue_resource
|
||||
|
||||
wait_for_board_cards(1, 1)
|
||||
wait_for_empty_boards((2..4))
|
||||
end
|
||||
|
||||
it 'filters by no label' do
|
||||
page.within '.issues-filters' do
|
||||
click_button('Label')
|
||||
wait_for_ajax
|
||||
|
||||
page.within '.dropdown-menu-labels' do
|
||||
click_link("No Label")
|
||||
wait_for_vue_resource
|
||||
find('.dropdown-menu-close').click
|
||||
end
|
||||
end
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
wait_for_board_cards(1, 5)
|
||||
wait_for_board_cards(2, 0)
|
||||
wait_for_board_cards(3, 0)
|
||||
wait_for_board_cards(4, 1)
|
||||
wait_for_empty_boards((2..3))
|
||||
end
|
||||
|
||||
it 'filters by clicking label button on issue' do
|
||||
page.within(find('.board', match: :first)) do
|
||||
expect(page).to have_selector('.card', count: 6)
|
||||
expect(page).to have_selector('.card', count: 8)
|
||||
expect(find('.card', match: :first)).to have_content(bug.title)
|
||||
click_button(bug.title)
|
||||
wait_for_vue_resource
|
||||
|
@ -614,7 +541,7 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
wait_for_vue_resource
|
||||
|
||||
wait_for_board_cards(1, 1)
|
||||
wait_for_empty_boards((2..4))
|
||||
wait_for_empty_boards((2..3))
|
||||
|
||||
page.within('.labels-filter') do
|
||||
expect(find('.dropdown-toggle-text')).to have_content(bug.title)
|
||||
|
|
|
@ -6,6 +6,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
|
|||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:board) { create(:board, project: project) }
|
||||
let!(:list) { create(:list, board: board, position: 0) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
context 'authorized user' do
|
||||
|
@ -17,7 +18,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
|
|||
visit namespace_project_board_path(project.namespace, project, board)
|
||||
wait_for_vue_resource
|
||||
|
||||
expect(page).to have_selector('.board', count: 3)
|
||||
expect(page).to have_selector('.board', count: 2)
|
||||
end
|
||||
|
||||
it 'displays new issue button' do
|
||||
|
@ -25,7 +26,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
|
|||
end
|
||||
|
||||
it 'does not display new issue button in done list' do
|
||||
page.within('.board:nth-child(3)') do
|
||||
page.within('.board:nth-child(2)') do
|
||||
expect(page).not_to have_selector('.board-issue-count-holder .btn')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,14 +4,17 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
include WaitForAjax
|
||||
include WaitForVueResource
|
||||
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let(:board) { create(:board, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
let!(:label) { create(:label, project: project) }
|
||||
let!(:label2) { create(:label, project: project) }
|
||||
let!(:milestone) { create(:milestone, project: project) }
|
||||
let!(:issue2) { create(:labeled_issue, project: project, assignee: user, milestone: milestone, labels: [label]) }
|
||||
let!(:issue) { create(:issue, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
let(:project) { create(:empty_project, :public) }
|
||||
let!(:milestone) { create(:milestone, project: project) }
|
||||
let!(:development) { create(:label, project: project, name: 'Development') }
|
||||
let!(:bug) { create(:label, project: project, name: 'Bug') }
|
||||
let!(:regression) { create(:label, project: project, name: 'Regression') }
|
||||
let!(:stretch) { create(:label, project: project, name: 'Stretch') }
|
||||
let!(:issue1) { create(:labeled_issue, project: project, assignee: user, milestone: milestone, labels: [development]) }
|
||||
let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch]) }
|
||||
let(:board) { create(:board, project: project) }
|
||||
let!(:list) { create(:list, board: board, label: development, position: 0) }
|
||||
|
||||
before do
|
||||
project.team << [user, :master]
|
||||
|
@ -62,8 +65,22 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
end
|
||||
|
||||
page.within('.issue-boards-sidebar') do
|
||||
expect(page).to have_content(issue.title)
|
||||
expect(page).to have_content(issue.to_reference)
|
||||
expect(page).to have_content(issue2.title)
|
||||
expect(page).to have_content(issue2.to_reference)
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes card from board when clicking remove button' do
|
||||
page.within(first('.board')) do
|
||||
first('.card').click
|
||||
end
|
||||
|
||||
page.within('.issue-boards-sidebar') do
|
||||
click_button 'Remove from board'
|
||||
end
|
||||
|
||||
page.within(first('.board')) do
|
||||
expect(page).to have_selector('.card', count: 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -244,22 +261,22 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_ajax
|
||||
|
||||
click_link label.title
|
||||
click_link bug.title
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
find('.dropdown-menu-close-icon').click
|
||||
|
||||
page.within('.value') do
|
||||
expect(page).to have_selector('.label', count: 1)
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_selector('.label', count: 3)
|
||||
expect(page).to have_content(bug.title)
|
||||
end
|
||||
end
|
||||
|
||||
page.within(first('.board')) do
|
||||
page.within(first('.card')) do
|
||||
expect(page).to have_selector('.label', count: 1)
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_selector('.label', count: 2)
|
||||
expect(page).to have_content(bug.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -274,32 +291,32 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_ajax
|
||||
|
||||
click_link label.title
|
||||
click_link label2.title
|
||||
click_link bug.title
|
||||
click_link regression.title
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
find('.dropdown-menu-close-icon').click
|
||||
|
||||
page.within('.value') do
|
||||
expect(page).to have_selector('.label', count: 2)
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_content(label2.title)
|
||||
expect(page).to have_selector('.label', count: 4)
|
||||
expect(page).to have_content(bug.title)
|
||||
expect(page).to have_content(regression.title)
|
||||
end
|
||||
end
|
||||
|
||||
page.within(first('.board')) do
|
||||
page.within(first('.card')) do
|
||||
expect(page).to have_selector('.label', count: 2)
|
||||
expect(page).to have_content(label.title)
|
||||
expect(page).to have_content(label2.title)
|
||||
expect(page).to have_selector('.label', count: 3)
|
||||
expect(page).to have_content(bug.title)
|
||||
expect(page).to have_content(regression.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes a label' do
|
||||
page.within(first('.board')) do
|
||||
find('.card:nth-child(2)').click
|
||||
first('.card').click
|
||||
end
|
||||
|
||||
page.within('.labels') do
|
||||
|
@ -307,22 +324,22 @@ describe 'Issue Boards', feature: true, js: true do
|
|||
|
||||
wait_for_ajax
|
||||
|
||||
click_link label.title
|
||||
click_link stretch.title
|
||||
|
||||
wait_for_vue_resource
|
||||
|
||||
find('.dropdown-menu-close-icon').click
|
||||
|
||||
page.within('.value') do
|
||||
expect(page).to have_selector('.label', count: 0)
|
||||
expect(page).not_to have_content(label.title)
|
||||
expect(page).to have_selector('.label', count: 1)
|
||||
expect(page).not_to have_content(stretch.title)
|
||||
end
|
||||
end
|
||||
|
||||
page.within(first('.board')) do
|
||||
page.within(find('.card:nth-child(2)')) do
|
||||
expect(page).not_to have_selector('.label', count: 1)
|
||||
expect(page).not_to have_content(label.title)
|
||||
page.within(first('.card')) do
|
||||
expect(page).not_to have_selector('.label')
|
||||
expect(page).not_to have_content(stretch.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"confidential"
|
||||
],
|
||||
"properties" : {
|
||||
"id": { "type": "integer" },
|
||||
"iid": { "type": "integer" },
|
||||
"title": { "type": "string" },
|
||||
"confidential": { "type": "boolean" },
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"id": { "type": "integer" },
|
||||
"list_type": {
|
||||
"type": "string",
|
||||
"enum": ["backlog", "label", "done"]
|
||||
"enum": ["label", "done"]
|
||||
},
|
||||
"label": {
|
||||
"type": ["object", "null"],
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
describe('Store', () => {
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(boardsMockInterceptor);
|
||||
gl.boardService = new BoardService('/test/issue-boards/board', '1');
|
||||
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
|
||||
gl.issueBoards.BoardsStore.create();
|
||||
|
||||
Cookies.set('issue_board_welcome_hidden', 'false', {
|
||||
|
@ -61,18 +61,6 @@ describe('Store', () => {
|
|||
expect(list).toBeDefined();
|
||||
});
|
||||
|
||||
it('finds list limited by type', () => {
|
||||
gl.issueBoards.BoardsStore.addList({
|
||||
id: 1,
|
||||
position: 0,
|
||||
title: 'Test',
|
||||
list_type: 'backlog'
|
||||
});
|
||||
const list = gl.issueBoards.BoardsStore.findList('id', 1, 'backlog');
|
||||
|
||||
expect(list).toBeDefined();
|
||||
});
|
||||
|
||||
it('gets issue when new list added', (done) => {
|
||||
gl.issueBoards.BoardsStore.addList(listObj);
|
||||
const list = gl.issueBoards.BoardsStore.findList('id', 1);
|
||||
|
@ -117,10 +105,7 @@ describe('Store', () => {
|
|||
expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false);
|
||||
});
|
||||
|
||||
it('check for blank state adding when backlog & done list exist', () => {
|
||||
gl.issueBoards.BoardsStore.addList({
|
||||
list_type: 'backlog'
|
||||
});
|
||||
it('check for blank state adding when done list exist', () => {
|
||||
gl.issueBoards.BoardsStore.addList({
|
||||
list_type: 'done'
|
||||
});
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
/* global Vue */
|
||||
/* global ListUser */
|
||||
/* global ListLabel */
|
||||
/* global listObj */
|
||||
/* global ListIssue */
|
||||
|
||||
//= require jquery
|
||||
//= require vue
|
||||
//= require boards/models/issue
|
||||
//= require boards/models/label
|
||||
//= require boards/models/list
|
||||
//= require boards/models/user
|
||||
//= require boards/stores/boards_store
|
||||
//= require boards/components/issue_card_inner
|
||||
//= require ./mock_data
|
||||
|
||||
describe('Issue card component', () => {
|
||||
const user = new ListUser({
|
||||
id: 1,
|
||||
name: 'testing 123',
|
||||
username: 'test',
|
||||
avatar: 'test_image',
|
||||
});
|
||||
const label1 = new ListLabel({
|
||||
id: 3,
|
||||
title: 'testing 123',
|
||||
color: 'blue',
|
||||
text_color: 'white',
|
||||
description: 'test',
|
||||
});
|
||||
let component;
|
||||
let issue;
|
||||
let list;
|
||||
|
||||
beforeEach(() => {
|
||||
setFixtures('<div class="test-container"></div>');
|
||||
|
||||
list = listObj;
|
||||
issue = new ListIssue({
|
||||
title: 'Testing',
|
||||
iid: 1,
|
||||
confidential: false,
|
||||
labels: [list.label],
|
||||
});
|
||||
|
||||
component = new Vue({
|
||||
el: document.querySelector('.test-container'),
|
||||
data() {
|
||||
return {
|
||||
list,
|
||||
issue,
|
||||
issueLinkBase: '/test',
|
||||
rootPath: '/',
|
||||
};
|
||||
},
|
||||
components: {
|
||||
'issue-card': gl.issueBoards.IssueCardInner,
|
||||
},
|
||||
template: `
|
||||
<issue-card
|
||||
:issue="issue"
|
||||
:list="list"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"></issue-card>
|
||||
`,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders issue title', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-title').textContent,
|
||||
).toContain(issue.title);
|
||||
});
|
||||
|
||||
it('includes issue base in link', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-title a').getAttribute('href'),
|
||||
).toContain('/test');
|
||||
});
|
||||
|
||||
it('includes issue title on link', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-title a').getAttribute('title'),
|
||||
).toBe(issue.title);
|
||||
});
|
||||
|
||||
it('does not render confidential icon', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.fa-eye-flash'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('renders confidential icon', (done) => {
|
||||
component.issue.confidential = true;
|
||||
|
||||
setTimeout(() => {
|
||||
expect(
|
||||
component.$el.querySelector('.confidential-icon'),
|
||||
).not.toBeNull();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('renders issue ID with #', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-number').textContent,
|
||||
).toContain(`#${issue.id}`);
|
||||
});
|
||||
|
||||
describe('assignee', () => {
|
||||
it('does not render assignee', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-assignee'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
beforeEach((done) => {
|
||||
component.issue.assignee = user;
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('renders assignee', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-assignee'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('sets title', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-assignee').getAttribute('title'),
|
||||
).toContain(`Assigned to ${user.name}`);
|
||||
});
|
||||
|
||||
it('sets users path', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-assignee').getAttribute('href'),
|
||||
).toBe('/test');
|
||||
});
|
||||
|
||||
it('renders avatar', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.card-assignee img'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('labels', () => {
|
||||
it('does not render any', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.label'),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
describe('exists', () => {
|
||||
beforeEach((done) => {
|
||||
component.issue.addLabel(label1);
|
||||
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('does not render list label', () => {
|
||||
expect(
|
||||
component.$el.querySelectorAll('.label').length,
|
||||
).toBe(1);
|
||||
});
|
||||
|
||||
it('renders label', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.label').textContent,
|
||||
).toContain(label1.title);
|
||||
});
|
||||
|
||||
it('sets label description as title', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.label').getAttribute('title'),
|
||||
).toContain(label1.description);
|
||||
});
|
||||
|
||||
it('sets background color of button', () => {
|
||||
expect(
|
||||
component.$el.querySelector('.label').style.backgroundColor,
|
||||
).toContain(label1.color);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -20,7 +20,7 @@ describe('Issue model', () => {
|
|||
let issue;
|
||||
|
||||
beforeEach(() => {
|
||||
gl.boardService = new BoardService('/test/issue-boards/board', '1');
|
||||
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
|
||||
gl.issueBoards.BoardsStore.create();
|
||||
|
||||
issue = new ListIssue({
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('List model', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
Vue.http.interceptors.push(boardsMockInterceptor);
|
||||
gl.boardService = new BoardService('/test/issue-boards/board', '1');
|
||||
gl.boardService = new BoardService('/test/issue-boards/board', '', '1');
|
||||
gl.issueBoards.BoardsStore.create();
|
||||
|
||||
list = new List(listObj);
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
/* global Vue */
|
||||
/* global ListIssue */
|
||||
|
||||
//= require jquery
|
||||
//= require vue
|
||||
//= require boards/models/issue
|
||||
//= require boards/models/label
|
||||
//= require boards/models/list
|
||||
//= require boards/models/user
|
||||
//= require boards/stores/modal_store
|
||||
|
||||
describe('Modal store', () => {
|
||||
let issue;
|
||||
let issue2;
|
||||
const Store = gl.issueBoards.ModalStore;
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup default state
|
||||
Store.store.issues = [];
|
||||
Store.store.selectedIssues = [];
|
||||
|
||||
issue = new ListIssue({
|
||||
title: 'Testing',
|
||||
iid: 1,
|
||||
confidential: false,
|
||||
labels: [],
|
||||
});
|
||||
issue2 = new ListIssue({
|
||||
title: 'Testing',
|
||||
iid: 2,
|
||||
confidential: false,
|
||||
labels: [],
|
||||
});
|
||||
Store.store.issues.push(issue);
|
||||
Store.store.issues.push(issue2);
|
||||
});
|
||||
|
||||
it('returns selected count', () => {
|
||||
expect(Store.selectedCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('toggles the issue as selected', () => {
|
||||
Store.toggleIssue(issue);
|
||||
|
||||
expect(issue.selected).toBe(true);
|
||||
expect(Store.selectedCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('toggles the issue as un-selected', () => {
|
||||
Store.toggleIssue(issue);
|
||||
Store.toggleIssue(issue);
|
||||
|
||||
expect(issue.selected).toBe(false);
|
||||
expect(Store.selectedCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('toggles all issues as selected', () => {
|
||||
Store.toggleAll();
|
||||
|
||||
expect(issue.selected).toBe(true);
|
||||
expect(issue2.selected).toBe(true);
|
||||
expect(Store.selectedCount()).toBe(2);
|
||||
});
|
||||
|
||||
it('toggles all issues as un-selected', () => {
|
||||
Store.toggleAll();
|
||||
Store.toggleAll();
|
||||
|
||||
expect(issue.selected).toBe(false);
|
||||
expect(issue2.selected).toBe(false);
|
||||
expect(Store.selectedCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('toggles all if a single issue is selected', () => {
|
||||
Store.toggleIssue(issue);
|
||||
Store.toggleAll();
|
||||
|
||||
expect(issue.selected).toBe(true);
|
||||
expect(issue2.selected).toBe(true);
|
||||
expect(Store.selectedCount()).toBe(2);
|
||||
});
|
||||
|
||||
it('adds issue to selected array', () => {
|
||||
issue.selected = true;
|
||||
Store.addSelectedIssue(issue);
|
||||
|
||||
expect(Store.selectedCount()).toBe(1);
|
||||
});
|
||||
|
||||
it('removes issue from selected array', () => {
|
||||
Store.addSelectedIssue(issue);
|
||||
Store.removeSelectedIssue(issue);
|
||||
|
||||
expect(Store.selectedCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('returns selected issue index if present', () => {
|
||||
Store.toggleIssue(issue);
|
||||
|
||||
expect(Store.selectedIssueIndex(issue)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns -1 if issue is not selected', () => {
|
||||
expect(Store.selectedIssueIndex(issue)).toBe(-1);
|
||||
});
|
||||
|
||||
it('finds the selected issue', () => {
|
||||
Store.toggleIssue(issue);
|
||||
|
||||
expect(Store.findSelectedIssue(issue)).toBe(issue);
|
||||
});
|
||||
|
||||
it('does not find a selected issue', () => {
|
||||
expect(Store.findSelectedIssue(issue)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('does not remove from selected issue if tab is not all', () => {
|
||||
Store.store.activeTab = 'selected';
|
||||
|
||||
Store.toggleIssue(issue);
|
||||
Store.toggleIssue(issue);
|
||||
|
||||
expect(Store.store.selectedIssues.length).toBe(1);
|
||||
expect(Store.selectedCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('gets selected issue array with only selected issues', () => {
|
||||
Store.toggleIssue(issue);
|
||||
Store.toggleIssue(issue2);
|
||||
Store.toggleIssue(issue2);
|
||||
|
||||
expect(Store.getSelectedIssues().length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -21,5 +21,19 @@
|
|||
expect(largeFont > regular).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gl.text.pluralize', () => {
|
||||
it('returns pluralized', () => {
|
||||
expect(gl.text.pluralize('test', 2)).toBe('tests');
|
||||
});
|
||||
|
||||
it('returns pluralized when count is 0', () => {
|
||||
expect(gl.text.pluralize('test', 0)).toBe('tests');
|
||||
});
|
||||
|
||||
it('does not return pluralized', () => {
|
||||
expect(gl.text.pluralize('test', 1)).toBe('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -19,13 +19,6 @@ describe List do
|
|||
expect(subject).to validate_uniqueness_of(:label_id).scoped_to(:board_id)
|
||||
end
|
||||
|
||||
context 'when list_type is set to backlog' do
|
||||
subject { described_class.new(list_type: :backlog) }
|
||||
|
||||
it { is_expected.not_to validate_presence_of(:label) }
|
||||
it { is_expected.not_to validate_presence_of(:position) }
|
||||
end
|
||||
|
||||
context 'when list_type is set to done' do
|
||||
subject { described_class.new(list_type: :done) }
|
||||
|
||||
|
@ -41,12 +34,6 @@ describe List do
|
|||
expect(subject.destroy).to be_truthy
|
||||
end
|
||||
|
||||
it 'can not be destroyed when list_type is set to backlog' do
|
||||
subject = create(:backlog_list)
|
||||
|
||||
expect(subject.destroy).to be_falsey
|
||||
end
|
||||
|
||||
it 'can not be destroyed when when list_type is set to done' do
|
||||
subject = create(:done_list)
|
||||
|
||||
|
@ -55,19 +42,13 @@ describe List do
|
|||
end
|
||||
|
||||
describe '#destroyable?' do
|
||||
it 'retruns true when list_type is set to label' do
|
||||
it 'returns true when list_type is set to label' do
|
||||
subject.list_type = :label
|
||||
|
||||
expect(subject).to be_destroyable
|
||||
end
|
||||
|
||||
it 'retruns false when list_type is set to backlog' do
|
||||
subject.list_type = :backlog
|
||||
|
||||
expect(subject).not_to be_destroyable
|
||||
end
|
||||
|
||||
it 'retruns false when list_type is set to done' do
|
||||
it 'returns false when list_type is set to done' do
|
||||
subject.list_type = :done
|
||||
|
||||
expect(subject).not_to be_destroyable
|
||||
|
@ -75,19 +56,13 @@ describe List do
|
|||
end
|
||||
|
||||
describe '#movable?' do
|
||||
it 'retruns true when list_type is set to label' do
|
||||
it 'returns true when list_type is set to label' do
|
||||
subject.list_type = :label
|
||||
|
||||
expect(subject).to be_movable
|
||||
end
|
||||
|
||||
it 'retruns false when list_type is set to backlog' do
|
||||
subject.list_type = :backlog
|
||||
|
||||
expect(subject).not_to be_movable
|
||||
end
|
||||
|
||||
it 'retruns false when list_type is set to done' do
|
||||
it 'returns false when list_type is set to done' do
|
||||
subject.list_type = :done
|
||||
|
||||
expect(subject).not_to be_movable
|
||||
|
@ -102,12 +77,6 @@ describe List do
|
|||
expect(subject.title).to eq 'Development'
|
||||
end
|
||||
|
||||
it 'returns Backlog when list_type is set to backlog' do
|
||||
subject.list_type = :backlog
|
||||
|
||||
expect(subject.title).to eq 'Backlog'
|
||||
end
|
||||
|
||||
it 'returns Done when list_type is set to done' do
|
||||
subject.list_type = :done
|
||||
|
||||
|
|
|
@ -11,12 +11,11 @@ describe Boards::CreateService, services: true do
|
|||
expect { service.execute }.to change(Board, :count).by(1)
|
||||
end
|
||||
|
||||
it 'creates default lists' do
|
||||
it 'creates the default lists' do
|
||||
board = service.execute
|
||||
|
||||
expect(board.lists.size).to eq 2
|
||||
expect(board.lists.first).to be_backlog
|
||||
expect(board.lists.last).to be_done
|
||||
expect(board.lists.size).to eq 1
|
||||
expect(board.lists.first).to be_done
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ describe Boards::Issues::ListService, services: true do
|
|||
let(:p2) { create(:label, title: 'P2', project: project, priority: 2) }
|
||||
let(:p3) { create(:label, title: 'P3', project: project, priority: 3) }
|
||||
|
||||
let!(:backlog) { create(:backlog_list, board: board) }
|
||||
let!(:list1) { create(:list, board: board, label: development, position: 0) }
|
||||
let!(:list2) { create(:list, board: board, label: testing, position: 1) }
|
||||
let!(:done) { create(:done_list, board: board) }
|
||||
|
@ -45,8 +44,8 @@ describe Boards::Issues::ListService, services: true do
|
|||
end
|
||||
|
||||
context 'sets default order to priority' do
|
||||
it 'returns opened issues when listing issues from Backlog' do
|
||||
params = { board_id: board.id, id: backlog.id }
|
||||
it 'returns opened issues when list id is missing' do
|
||||
params = { board_id: board.id }
|
||||
|
||||
issues = described_class.new(project, user, params).execute
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ describe Boards::Issues::MoveService, services: true do
|
|||
let(:development) { create(:label, project: project, name: 'Development') }
|
||||
let(:testing) { create(:label, project: project, name: 'Testing') }
|
||||
|
||||
let!(:backlog) { create(:backlog_list, board: board1) }
|
||||
let!(:list1) { create(:list, board: board1, label: development, position: 0) }
|
||||
let!(:list2) { create(:list, board: board1, label: testing, position: 1) }
|
||||
let!(:done) { create(:done_list, board: board1) }
|
||||
|
@ -19,41 +18,6 @@ describe Boards::Issues::MoveService, services: true do
|
|||
project.team << [user, :developer]
|
||||
end
|
||||
|
||||
context 'when moving from backlog' do
|
||||
it 'adds the label of the list it goes to' do
|
||||
issue = create(:labeled_issue, project: project, labels: [bug])
|
||||
params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: list1.id }
|
||||
|
||||
described_class.new(project, user, params).execute(issue)
|
||||
|
||||
expect(issue.reload.labels).to contain_exactly(bug, development)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when moving to backlog' do
|
||||
it 'removes all list-labels' do
|
||||
issue = create(:labeled_issue, project: project, labels: [bug, development, testing])
|
||||
params = { board_id: board1.id, from_list_id: list1.id, to_list_id: backlog.id }
|
||||
|
||||
described_class.new(project, user, params).execute(issue)
|
||||
|
||||
expect(issue.reload.labels).to contain_exactly(bug)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when moving from backlog to done' do
|
||||
it 'closes the issue' do
|
||||
issue = create(:labeled_issue, project: project, labels: [bug])
|
||||
params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: done.id }
|
||||
|
||||
described_class.new(project, user, params).execute(issue)
|
||||
issue.reload
|
||||
|
||||
expect(issue.labels).to contain_exactly(bug)
|
||||
expect(issue).to be_closed
|
||||
end
|
||||
end
|
||||
|
||||
context 'when moving an issue between lists' do
|
||||
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
|
||||
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } }
|
||||
|
@ -113,19 +77,6 @@ describe Boards::Issues::MoveService, services: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when moving from done to backlog' do
|
||||
it 'reopens the issue' do
|
||||
issue = create(:labeled_issue, :closed, project: project, labels: [bug])
|
||||
params = { board_id: board1.id, from_list_id: done.id, to_list_id: backlog.id }
|
||||
|
||||
described_class.new(project, user, params).execute(issue)
|
||||
issue.reload
|
||||
|
||||
expect(issue.labels).to contain_exactly(bug)
|
||||
expect(issue).to be_reopened
|
||||
end
|
||||
end
|
||||
|
||||
context 'when moving to same list' do
|
||||
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
|
||||
let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } }
|
||||
|
|
|
@ -21,7 +21,7 @@ describe Boards::Lists::CreateService, services: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when board lists has backlog, and done lists' do
|
||||
context 'when board lists has the done list' do
|
||||
it 'creates a new list at beginning of the list' do
|
||||
list = service.execute(board)
|
||||
|
||||
|
@ -40,7 +40,7 @@ describe Boards::Lists::CreateService, services: true do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when board lists has backlog, label and done lists' do
|
||||
context 'when board lists has label and done lists' do
|
||||
it 'creates a new list at end of the label lists' do
|
||||
list1 = create(:list, board: board, position: 0)
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ describe Boards::Lists::DestroyService, services: true do
|
|||
end
|
||||
|
||||
it 'decrements position of higher lists' do
|
||||
backlog = board.backlog_list
|
||||
development = create(:list, board: board, position: 0)
|
||||
review = create(:list, board: board, position: 1)
|
||||
staging = create(:list, board: board, position: 2)
|
||||
|
@ -23,20 +22,12 @@ describe Boards::Lists::DestroyService, services: true do
|
|||
|
||||
described_class.new(project, user).execute(development)
|
||||
|
||||
expect(backlog.reload.position).to be_nil
|
||||
expect(review.reload.position).to eq 0
|
||||
expect(staging.reload.position).to eq 1
|
||||
expect(done.reload.position).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not remove list from board when list type is backlog' do
|
||||
list = board.backlog_list
|
||||
service = described_class.new(project, user)
|
||||
|
||||
expect { service.execute(list) }.not_to change(board.lists, :count)
|
||||
end
|
||||
|
||||
it 'does not remove list from board when list type is done' do
|
||||
list = board.done_list
|
||||
service = described_class.new(project, user)
|
||||
|
|
|
@ -10,7 +10,7 @@ describe Boards::Lists::ListService, services: true do
|
|||
|
||||
service = described_class.new(project, double)
|
||||
|
||||
expect(service.execute(board)).to eq [board.backlog_list, list, board.done_list]
|
||||
expect(service.execute(board)).to eq [list, board.done_list]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,7 +6,6 @@ describe Boards::Lists::MoveService, services: true do
|
|||
let(:board) { create(:board, project: project) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
let!(:backlog) { create(:backlog_list, board: board) }
|
||||
let!(:planning) { create(:list, board: board, position: 0) }
|
||||
let!(:development) { create(:list, board: board, position: 1) }
|
||||
let!(:review) { create(:list, board: board, position: 2) }
|
||||
|
@ -87,14 +86,6 @@ describe Boards::Lists::MoveService, services: true do
|
|||
end
|
||||
end
|
||||
|
||||
it 'keeps position of lists when list type is backlog' do
|
||||
service = described_class.new(project, user, position: 2)
|
||||
|
||||
service.execute(backlog)
|
||||
|
||||
expect(current_list_positions).to eq [0, 1, 2, 3]
|
||||
end
|
||||
|
||||
it 'keeps position of lists when list type is done' do
|
||||
service = described_class.new(project, user, position: 2)
|
||||
|
||||
|
|
Loading…
Reference in New Issue