Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Jérome Perrin
gitlab-ce
Commits
3213dfd7
Commit
3213dfd7
authored
Feb 03, 2017
by
Fatih Acet
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'add-issues-to-boards' into 'master'
Add issues to boards list Closes #26205 See merge request !8737
parents
0dc36591
a810e2e2
Changes
63
Hide whitespace changes
Inline
Side-by-side
Showing
63 changed files
with
1917 additions
and
426 deletions
+1917
-426
app/assets/javascripts/boards/boards_bundle.js.es6
app/assets/javascripts/boards/boards_bundle.js.es6
+30
-4
app/assets/javascripts/boards/components/board.js.es6
app/assets/javascripts/boards/components/board.js.es6
+2
-1
app/assets/javascripts/boards/components/board_card.js.es6
app/assets/javascripts/boards/components/board_card.js.es6
+7
-26
app/assets/javascripts/boards/components/board_list.js.es6
app/assets/javascripts/boards/components/board_list.js.es6
+1
-0
app/assets/javascripts/boards/components/board_new_issue.js.es6
...sets/javascripts/boards/components/board_new_issue.js.es6
+1
-0
app/assets/javascripts/boards/components/board_sidebar.js.es6
...assets/javascripts/boards/components/board_sidebar.js.es6
+8
-2
app/assets/javascripts/boards/components/issue_card_inner.js.es6
...ets/javascripts/boards/components/issue_card_inner.js.es6
+111
-0
app/assets/javascripts/boards/components/modal/empty_state.js.es6
...ts/javascripts/boards/components/modal/empty_state.js.es6
+70
-0
app/assets/javascripts/boards/components/modal/footer.js.es6
app/assets/javascripts/boards/components/modal/footer.js.es6
+81
-0
app/assets/javascripts/boards/components/modal/header.js.es6
app/assets/javascripts/boards/components/modal/header.js.es6
+68
-0
app/assets/javascripts/boards/components/modal/index.js.es6
app/assets/javascripts/boards/components/modal/index.js.es6
+134
-0
app/assets/javascripts/boards/components/modal/list.js.es6
app/assets/javascripts/boards/components/modal/list.js.es6
+142
-0
app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6
...javascripts/boards/components/modal/lists_dropdown.js.es6
+56
-0
app/assets/javascripts/boards/components/modal/tabs.js.es6
app/assets/javascripts/boards/components/modal/tabs.js.es6
+47
-0
app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6
...javascripts/boards/components/sidebar/remove_issue.js.es6
+59
-0
app/assets/javascripts/boards/mixins/modal_mixins.js.es6
app/assets/javascripts/boards/mixins/modal_mixins.js.es6
+14
-0
app/assets/javascripts/boards/models/issue.js.es6
app/assets/javascripts/boards/models/issue.js.es6
+3
-0
app/assets/javascripts/boards/models/list.js.es6
app/assets/javascripts/boards/models/list.js.es6
+1
-1
app/assets/javascripts/boards/services/board_service.js.es6
app/assets/javascripts/boards/services/board_service.js.es6
+27
-2
app/assets/javascripts/boards/stores/boards_store.js.es6
app/assets/javascripts/boards/stores/boards_store.js.es6
+2
-7
app/assets/javascripts/boards/stores/modal_store.js.es6
app/assets/javascripts/boards/stores/modal_store.js.es6
+96
-0
app/assets/javascripts/lib/utils/text_utility.js
app/assets/javascripts/lib/utils/text_utility.js
+3
-0
app/assets/stylesheets/framework/dropdowns.scss
app/assets/stylesheets/framework/dropdowns.scss
+5
-0
app/assets/stylesheets/pages/boards.scss
app/assets/stylesheets/pages/boards.scss
+133
-1
app/controllers/projects/boards/issues_controller.rb
app/controllers/projects/boards/issues_controller.rb
+3
-3
app/helpers/boards_helper.rb
app/helpers/boards_helper.rb
+3
-1
app/models/board.rb
app/models/board.rb
+0
-4
app/models/list.rb
app/models/list.rb
+1
-1
app/services/boards/create_service.rb
app/services/boards/create_service.rb
+0
-1
app/services/boards/issues/list_service.rb
app/services/boards/issues/list_service.rb
+10
-4
app/views/projects/boards/_show.html.haml
app/views/projects/boards/_show.html.haml
+5
-0
app/views/projects/boards/components/_board.html.haml
app/views/projects/boards/components/_board.html.haml
+1
-0
app/views/projects/boards/components/_board_list.html.haml
app/views/projects/boards/components/_board_list.html.haml
+1
-0
app/views/projects/boards/components/_card.html.haml
app/views/projects/boards/components/_card.html.haml
+4
-22
app/views/projects/boards/components/_sidebar.html.haml
app/views/projects/boards/components/_sidebar.html.haml
+2
-0
app/views/shared/issuable/_filter.html.haml
app/views/shared/issuable/_filter.html.haml
+2
-1
config/routes/project.rb
config/routes/project.rb
+1
-1
db/migrate/20170127032550_remove_backlog_lists_from_boards.rb
...igrate/20170127032550_remove_backlog_lists_from_boards.rb
+17
-0
lib/api/boards.rb
lib/api/boards.rb
+1
-1
spec/controllers/projects/boards/issues_controller_spec.rb
spec/controllers/projects/boards/issues_controller_spec.rb
+50
-26
spec/controllers/projects/boards/lists_controller_spec.rb
spec/controllers/projects/boards/lists_controller_spec.rb
+1
-1
spec/factories/boards.rb
spec/factories/boards.rb
+0
-1
spec/factories/lists.rb
spec/factories/lists.rb
+0
-6
spec/features/boards/add_issues_modal_spec.rb
spec/features/boards/add_issues_modal_spec.rb
+233
-0
spec/features/boards/boards_spec.rb
spec/features/boards/boards_spec.rb
+72
-145
spec/features/boards/new_issue_spec.rb
spec/features/boards/new_issue_spec.rb
+3
-2
spec/features/boards/sidebar_spec.rb
spec/features/boards/sidebar_spec.rb
+47
-30
spec/fixtures/api/schemas/issue.json
spec/fixtures/api/schemas/issue.json
+1
-0
spec/fixtures/api/schemas/list.json
spec/fixtures/api/schemas/list.json
+1
-1
spec/javascripts/boards/boards_store_spec.js.es6
spec/javascripts/boards/boards_store_spec.js.es6
+2
-17
spec/javascripts/boards/issue_card_spec.js.es6
spec/javascripts/boards/issue_card_spec.js.es6
+193
-0
spec/javascripts/boards/issue_spec.js.es6
spec/javascripts/boards/issue_spec.js.es6
+1
-1
spec/javascripts/boards/list_spec.js.es6
spec/javascripts/boards/list_spec.js.es6
+1
-1
spec/javascripts/boards/modal_store_spec.js.es6
spec/javascripts/boards/modal_store_spec.js.es6
+134
-0
spec/javascripts/lib/utils/text_utility_spec.js.es6
spec/javascripts/lib/utils/text_utility_spec.js.es6
+14
-0
spec/models/list_spec.rb
spec/models/list_spec.rb
+4
-35
spec/services/boards/create_service_spec.rb
spec/services/boards/create_service_spec.rb
+3
-4
spec/services/boards/issues/list_service_spec.rb
spec/services/boards/issues/list_service_spec.rb
+2
-3
spec/services/boards/issues/move_service_spec.rb
spec/services/boards/issues/move_service_spec.rb
+0
-49
spec/services/boards/lists/create_service_spec.rb
spec/services/boards/lists/create_service_spec.rb
+2
-2
spec/services/boards/lists/destroy_service_spec.rb
spec/services/boards/lists/destroy_service_spec.rb
+0
-9
spec/services/boards/lists/list_service_spec.rb
spec/services/boards/lists/list_service_spec.rb
+1
-1
spec/services/boards/lists/move_service_spec.rb
spec/services/boards/lists/move_service_spec.rb
+0
-9
No files found.
app/assets/javascripts/boards/boards_bundle.js.es6
View file @
3213dfd7
...
...
@@ -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.b
ulkUpdatePath, this.b
oardId);
},
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>
`,
});
});
app/assets/javascripts/boards/components/board.js.es6
View file @
3213dfd7
...
...
@@ -22,7 +22,8 @@
props: {
list: Object,
disabled: Boolean,
issueLinkBase: String
issueLinkBase: String,
rootPath: String,
},
data () {
return {
...
...
app/assets/javascripts/boards/components/board_card.js.es6
View file @
3213dfd7
/* 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;
}
}
}
...
...
app/assets/javascripts/boards/components/board_list.js.es6
View file @
3213dfd7
...
...
@@ -23,6 +23,7 @@
issues: Array,
loading: Boolean,
issueLinkBase: String,
rootPath: String,
},
data () {
return {
...
...
app/assets/javascripts/boards/components/board_new_issue.js.es6
View file @
3213dfd7
...
...
@@ -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
...
...
app/assets/javascripts/boards/components/board_sidebar.js.es6
View file @
3213dfd7
...
...
@@ -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,
},
});
})();
app/assets/javascripts/boards/components/issue_card_inner.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/empty_state.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/footer.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/header.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/index.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/list.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/modal/tabs.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6
0 → 100644
View file @
3213dfd7
/* 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>
`,
});
})();
app/assets/javascripts/boards/mixins/modal_mixins.js.es6
0 → 100644
View file @
3213dfd7
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalMixins = {
methods: {
toggleModal(toggle) {
ModalStore.store.showAddIssuesModal = toggle;
},
changeTab(tab) {
ModalStore.store.activeTab = tab;
},
},
};
})();
app/assets/javascripts/boards/models/issue.js.es6
View file @
3213dfd7
...
...
@@ -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);
...
...
app/assets/javascripts/boards/models/list.js.es6
View file @
3213dfd7
...
...
@@ -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;
...
...
app/assets/javascripts/boards/services/board_service.js.es6
View file @
3213dfd7
...
...
@@ -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;
app/assets/javascripts/boards/stores/boards_store.js.es6
View file @
3213dfd7
...
...
@@ -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);
});
...
...
app/assets/javascripts/boards/stores/modal_store.js.es6
0 → 100644
View file @
3213dfd7
(() => {
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();
})();
app/assets/javascripts/lib/utils/text_utility.js
View file @
3213dfd7
...
...
@@ -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
))
+
'
...
'
;
};
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
3213dfd7
...
...
@@ -227,6 +227,11 @@
}
}
.dropdown-menu-drop-up
{
top
:
auto
;
bottom
:
100%
;
}
.dropdown-menu-large
{
width
:
340px
;
}
...
...
app/assets/stylesheets/pages/boards.scss
View file @
3213dfd7
...
...
@@ -250,7 +250,7 @@
}
.issue-boards-search
{
width
:
290
px
;
width
:
395
px
;
.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%
;
}
app/controllers/projects/boards/issues_controller.rb
View file @
3213dfd7
...
...
@@ -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:
[
:i
d
,
:i
id
,
:title
,
:confidential
,
:due_date
],
include:
{
assignee:
{
only:
[
:id
,
:name
,
:username
],
methods:
[
:avatar_url
]
},
milestone:
{
only:
[
:id
,
:title
]
}
...
...
app/helpers/boards_helper.rb
View file @
3213dfd7
...
...
@@ -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
app/models/board.rb
View file @
3213dfd7
...
...
@@ -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
...
...
app/models/list.rb
View file @
3213dfd7
...
...
@@ -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?
...
...
app/services/boards/create_service.rb
View file @
3213dfd7
...
...
@@ -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
...
...
app/services/boards/issues/list_service.rb
View file @
3213dfd7
...
...
@@ -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
...
...
app/views/projects/boards/_show.html.haml
View file @
3213dfd7
...
...
@@ -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"
}
app/views/projects/boards/components/_board.html.haml
View file @
3213dfd7
...
...
@@ -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"
app/views/projects/boards/components/_board_list.html.haml
View file @
3213dfd7
...
...
@@ -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"
}
...
...
app/views/projects/boards/components/_card.html.haml
View file @
3213dfd7
...
...
@@ -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"
}
app/views/projects/boards/components/_sidebar.html.haml
View file @
3213dfd7
...
...
@@ -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"
}
app/views/shared/issuable/_filter.html.haml
View file @
3213dfd7
...
...
@@ -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"
}
...
...
config/routes/project.rb
View file @
3213dfd7
...
...
@@ -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
...
...
db/migrate/20170127032550_remove_backlog_lists_from_boards.rb
0 → 100644
View file @
3213dfd7
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
lib/api/boards.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/controllers/projects/boards/issues_controller_spec.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/controllers/projects/boards/lists_controller_spec.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/factories/boards.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/factories/lists.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/features/boards/add_issues_modal_spec.rb
0 → 100644
View file @
3213dfd7
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
spec/features/boards/boards_spec.rb
View file @
3213dfd7
...
...
@@ -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
(
'5
6
'
)
expect
(
page
.
find
(
'.board-header'
)).
to
have_content
(
'5
8
'
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
20
)
expect
(
page
).
to
have_content
(
'Showing 20 of 5
6
issues'
)
expect
(
page
).
to
have_content
(
'Showing 20 of 5
8
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 5
6
issues'
)
expect
(
page
).
to
have_content
(
'Showing 40 of 5
8
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:
5
6
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
5
8
)
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
)
wait_for_board_cards
(
1
,
2
)
wait_for_board_cards
(
2
,
8
)
wait_for_board_cards
(
3
,
1
)
expect
(
find
(
'.board:nth-child(
2
)'
)).
to
have_content
(
development
.
title
)
expect
(
find
(
'.board:nth-child(
2
)'
)).
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:
1
,
card_index:
1
,
list_to_index:
2
)
drag_to
(
list_from_index:
0
,
card_index:
1
,
list_to_index:
1
)
wait_for_board_cards
(
1
,
6
)
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
3
,
3
)
wait_for_board_cards
(
4
,
1
)
wait_for_board_cards
(
1
,
7
)
wait_for_board_cards
(
2
,
2
)
wait_for_board_cards
(
3
,
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
)
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:
2
,
list_to_index:
1
)
drag_to
(
list_from_index:
1
,
list_to_index:
0
)
wait_for_board_cards
(
1
,
6
)
wait_for_board_cards
(
2
,
3
)
wait_for_board_cards
(
1
,
9
)
wait_for_board_cards
(
2
,
1
)
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
(
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
)
...
...
spec/features/boards/new_issue_spec.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/features/boards/sidebar_spec.rb
View file @
3213dfd7
...
...
@@ -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
fi
nd
(
'.card:nth-child(2)
'
).
click
fi
rst
(
'.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
(
fi
nd
(
'.card:nth-child(2)
'
))
do
expect
(
page
).
not_to
have_selector
(
'.label'
,
count:
1
)
expect
(
page
).
not_to
have_content
(
label
.
title
)
page
.
within
(
fi
rst
(
'.card
'
))
do
expect
(
page
).
not_to
have_selector
(
'.label'
)
expect
(
page
).
not_to
have_content
(
stretch
.
title
)
end
end
end
...
...
spec/fixtures/api/schemas/issue.json
View file @
3213dfd7
...
...
@@ -6,6 +6,7 @@
"confidential"
],
"properties"
:
{
"id"
:
{
"type"
:
"integer"
},
"iid"
:
{
"type"
:
"integer"
},
"title"
:
{
"type"
:
"string"
},
"confidential"
:
{
"type"
:
"boolean"
},
...
...
spec/fixtures/api/schemas/list.json
View file @
3213dfd7
...
...
@@ -10,7 +10,7 @@
"id"
:
{
"type"
:
"integer"
},
"list_type"
:
{
"type"
:
"string"
,
"enum"
:
[
"
backlog"
,
"
label"
,
"done"
]
"enum"
:
[
"label"
,
"done"
]
},
"label"
:
{
"type"
:
[
"object"
,
"null"
],
...
...
spec/javascripts/boards/boards_store_spec.js.es6
View file @
3213dfd7
...
...
@@ -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'
});
...
...
spec/javascripts/boards/issue_card_spec.js.es6
0 → 100644
View file @
3213dfd7
/* 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);
});
});
});
});
spec/javascripts/boards/issue_spec.js.es6
View file @
3213dfd7
...
...
@@ -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({
...
...
spec/javascripts/boards/list_spec.js.es6
View file @
3213dfd7
...
...
@@ -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);
...
...
spec/javascripts/boards/modal_store_spec.js.es6
0 → 100644
View file @
3213dfd7
/* 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);
});
});
spec/javascripts/lib/utils/text_utility_spec.js.es6
View file @
3213dfd7
...
...
@@ -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');
});
});
});
})();
spec/models/list_spec.rb
View file @
3213dfd7
...
...
@@ -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
'ret
ru
ns true when list_type is set to label'
do
it
'ret
ur
ns 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
'ret
ru
ns true when list_type is set to label'
do
it
'ret
ur
ns 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
...
...
spec/services/boards/create_service_spec.rb
View file @
3213dfd7
...
...
@@ -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
...
...
spec/services/boards/issues/list_service_spec.rb
View file @
3213dfd7
...
...
@@ -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 list
ing issues from Backlo
g'
do
params
=
{
board_id:
board
.
id
,
id:
backlog
.
id
}
it
'returns opened issues when list
id is missin
g'
do
params
=
{
board_id:
board
.
id
}
issues
=
described_class
.
new
(
project
,
user
,
params
).
execute
...
...
spec/services/boards/issues/move_service_spec.rb
View file @
3213dfd7
...
...
@@ -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
}
}
...
...
spec/services/boards/lists/create_service_spec.rb
View file @
3213dfd7
...
...
@@ -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
)
...
...
spec/services/boards/lists/destroy_service_spec.rb
View file @
3213dfd7
...
...
@@ -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
)
...
...
spec/services/boards/lists/list_service_spec.rb
View file @
3213dfd7
...
...
@@ -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
spec/services/boards/lists/move_service_spec.rb
View file @
3213dfd7
...
...
@@ -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
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment