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
Léo-Paul Géneau
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
Show 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 @@
...
@@ -13,11 +13,13 @@
//= require ./components/board
//= require ./components/board
//= require ./components/board_sidebar
//= require ./components/board_sidebar
//= require ./components/new_list_dropdown
//= require ./components/new_list_dropdown
//= require ./components/modal/index
//= require ./vue_resource_interceptor
//= require ./vue_resource_interceptor
$(() => {
$(() => {
const $boardApp = document.getElementById('board-app');
const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore;
const Store = gl.issueBoards.BoardsStore;
const ModalStore = gl.issueBoards.ModalStore;
window.gl = window.gl || {};
window.gl = window.gl || {};
...
@@ -31,7 +33,8 @@ $(() => {
...
@@ -31,7 +33,8 @@ $(() => {
el: $boardApp,
el: $boardApp,
components: {
components: {
'board': gl.issueBoards.Board,
'board': gl.issueBoards.Board,
'board-sidebar': gl.issueBoards.BoardSidebar
'board-sidebar': gl.issueBoards.BoardSidebar,
'board-add-issues-modal': gl.issueBoards.IssuesModal,
},
},
data: {
data: {
state: Store.state,
state: Store.state,
...
@@ -40,6 +43,8 @@ $(() => {
...
@@ -40,6 +43,8 @@ $(() => {
boardId: $boardApp.dataset.boardId,
boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true',
disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase,
issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
detailIssue: Store.detail
detailIssue: Store.detail
},
},
computed: {
computed: {
...
@@ -48,7 +53,7 @@ $(() => {
...
@@ -48,7 +53,7 @@ $(() => {
},
},
},
},
created () {
created () {
gl.boardService = new BoardService(this.endpoint, this.boardId);
gl.boardService = new BoardService(this.endpoint, this.b
ulkUpdatePath, this.b
oardId);
},
},
mounted () {
mounted () {
Store.disabled = this.disabled;
Store.disabled = this.disabled;
...
@@ -59,8 +64,6 @@ $(() => {
...
@@ -59,8 +64,6 @@ $(() => {
if (list.type === 'done') {
if (list.type === 'done') {
list.position = Infinity;
list.position = Infinity;
} else if (list.type === 'backlog') {
list.position = -1;
}
}
});
});
...
@@ -81,4 +84,27 @@ $(() => {
...
@@ -81,4 +84,27 @@ $(() => {
gl.issueBoards.newListDropdownInit();
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 @@
...
@@ -22,7 +22,8 @@
props: {
props: {
list: Object,
list: Object,
disabled: Boolean,
disabled: Boolean,
issueLinkBase: String
issueLinkBase: String,
rootPath: String,
},
},
data () {
data () {
return {
return {
...
...
app/assets/javascripts/boards/components/board_card.js.es6
View file @
3213dfd7
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
//= require ./issue_card_inner
/* global Vue */
/* global Vue */
(() => {
(() => {
...
@@ -9,12 +10,16 @@
...
@@ -9,12 +10,16 @@
gl.issueBoards.BoardCard = Vue.extend({
gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card',
template: '#js-board-list-card',
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: {
props: {
list: Object,
list: Object,
issue: Object,
issue: Object,
issueLinkBase: String,
issueLinkBase: String,
disabled: Boolean,
disabled: Boolean,
index: Number
index: Number,
rootPath: String,
},
},
data () {
data () {
return {
return {
...
@@ -28,31 +33,6 @@
...
@@ -28,31 +33,6 @@
}
}
},
},
methods: {
methods: {
filterByLabel (label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters['label_name'].indexOf(label.title);
$(e.target).tooltip('hide');
if (labelIndex === -1) {
Store.state.filters['label_name'].push(label.title);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
} else {
Store.state.filters['label_name'].splice(labelIndex, 1);
labelToggleText = Store.state.filters['label_name'][0];
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
}
const selectedLabels = Store.state.filters['label_name'];
if (selectedLabels.length === 0) {
labelToggleText = 'Label';
} else if (selectedLabels.length > 1) {
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
}
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
Store.updateFiltersUrl();
},
mouseDown () {
mouseDown () {
this.showDetail = true;
this.showDetail = true;
},
},
...
@@ -71,6 +51,7 @@
...
@@ -71,6 +51,7 @@
Store.detail.issue = {};
Store.detail.issue = {};
} else {
} else {
Store.detail.issue = this.issue;
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 @@
...
@@ -23,6 +23,7 @@
issues: Array,
issues: Array,
loading: Boolean,
loading: Boolean,
issueLinkBase: String,
issueLinkBase: String,
rootPath: String,
},
},
data () {
data () {
return {
return {
...
...
app/assets/javascripts/boards/components/board_new_issue.js.es6
View file @
3213dfd7
...
@@ -37,6 +37,7 @@
...
@@ -37,6 +37,7 @@
$(this.$refs.submitButton).enable();
$(this.$refs.submitButton).enable();
Store.detail.issue = issue;
Store.detail.issue = issue;
Store.detail.list = this.list;
})
})
.catch(() => {
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
// 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 @@
...
@@ -4,6 +4,7 @@
/* global MilestoneSelect */
/* global MilestoneSelect */
/* global LabelsSelect */
/* global LabelsSelect */
/* global Sidebar */
/* global Sidebar */
//= require ./sidebar/remove_issue
(() => {
(() => {
const Store = gl.issueBoards.BoardsStore;
const Store = gl.issueBoards.BoardsStore;
...
@@ -18,7 +19,8 @@
...
@@ -18,7 +19,8 @@
data() {
data() {
return {
return {
detail: Store.detail,
detail: Store.detail,
issue: {}
issue: {},
list: {},
};
};
},
},
computed: {
computed: {
...
@@ -36,6 +38,7 @@
...
@@ -36,6 +38,7 @@
}
}
this.issue = this.detail.issue;
this.issue = this.detail.issue;
this.list = this.detail.list;
},
},
deep: true
deep: true
},
},
...
@@ -60,6 +63,9 @@
...
@@ -60,6 +63,9 @@
new LabelsSelect();
new LabelsSelect();
new Sidebar();
new Sidebar();
gl.Subscription.bindAll('.subscription');
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 @@
...
@@ -6,12 +6,15 @@
class ListIssue {
class ListIssue {
constructor (obj) {
constructor (obj) {
this.globalId = obj.id;
this.id = obj.iid;
this.id = obj.iid;
this.title = obj.title;
this.title = obj.title;
this.confidential = obj.confidential;
this.confidential = obj.confidential;
this.dueDate = obj.due_date;
this.dueDate = obj.due_date;
this.subscribed = obj.subscribed;
this.subscribed = obj.subscribed;
this.labels = [];
this.labels = [];
this.selected = false;
this.assignee = false;
if (obj.assignee) {
if (obj.assignee) {
this.assignee = new ListUser(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 {
...
@@ -9,7 +9,7 @@ class List {
this.position = obj.position;
this.position = obj.position;
this.title = obj.title;
this.title = obj.title;
this.type = obj.list_type;
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.filters = gl.issueBoards.BoardsStore.state.filters;
this.page = 1;
this.page = 1;
this.loading = true;
this.loading = true;
...
...
app/assets/javascripts/boards/services/board_service.js.es6
View file @
3213dfd7
...
@@ -2,7 +2,13 @@
...
@@ -2,7 +2,13 @@
/* global Vue */
/* global Vue */
class BoardService {
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}`, {}, {
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: {
generate: {
method: 'POST',
method: 'POST',
...
@@ -10,7 +16,12 @@ class BoardService {
...
@@ -10,7 +16,12 @@ class BoardService {
}
}
});
});
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
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) => {
Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
...
@@ -65,6 +76,20 @@ class BoardService {
...
@@ -65,6 +76,20 @@ class BoardService {
issue
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;
window.BoardService = BoardService;
app/assets/javascripts/boards/stores/boards_store.js.es6
View file @
3213dfd7
...
@@ -34,15 +34,10 @@
...
@@ -34,15 +34,10 @@
},
},
new (listObj) {
new (listObj) {
const list = this.addList(listObj);
const list = this.addList(listObj);
const backlogList = this.findList('type', 'backlog', 'backlog');
list
list
.save()
.save()
.then(() => {
.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.state.lists = _.sortBy(this.state.lists, 'position');
});
});
this.removeBlankState();
this.removeBlankState();
...
@@ -52,7 +47,7 @@
...
@@ -52,7 +47,7 @@
},
},
shouldAddBlankState () {
shouldAddBlankState () {
// Decide whether to add the blank state
// 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 () {
addBlankState () {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
...
@@ -102,7 +97,7 @@
...
@@ -102,7 +97,7 @@
listTo.addIssue(issue, listFrom, newIndex);
listTo.addIssue(issue, listFrom, newIndex);
}
}
if (listTo.type === 'done'
&& listFrom.type !== 'backlog'
) {
if (listTo.type === 'done') {
issueLists.forEach((list) => {
issueLists.forEach((list) => {
list.removeIssue(issue);
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 @@
...
@@ -161,6 +161,9 @@
gl
.
text
.
humanize
=
function
(
string
)
{
gl
.
text
.
humanize
=
function
(
string
)
{
return
string
.
charAt
(
0
).
toUpperCase
()
+
string
.
replace
(
/_/g
,
'
'
).
slice
(
1
);
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
gl
.
text
.
truncate
=
function
(
string
,
maxLength
)
{
return
string
.
substr
(
0
,
(
maxLength
-
3
))
+
'
...
'
;
return
string
.
substr
(
0
,
(
maxLength
-
3
))
+
'
...
'
;
};
};
...
...
app/assets/stylesheets/framework/dropdowns.scss
View file @
3213dfd7
...
@@ -227,6 +227,11 @@
...
@@ -227,6 +227,11 @@
}
}
}
}
.dropdown-menu-drop-up
{
top
:
auto
;
bottom
:
100%
;
}
.dropdown-menu-large
{
.dropdown-menu-large
{
width
:
340px
;
width
:
340px
;
}
}
...
...
app/assets/stylesheets/pages/boards.scss
View file @
3213dfd7
...
@@ -250,7 +250,7 @@
...
@@ -250,7 +250,7 @@
}
}
.issue-boards-search
{
.issue-boards-search
{
width
:
290
px
;
width
:
395
px
;
.form-control
{
.form-control
{
display
:
inline-block
;
display
:
inline-block
;
...
@@ -354,3 +354,135 @@
...
@@ -354,3 +354,135 @@
padding-right
:
0
;
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
...
@@ -7,7 +7,7 @@ module Projects
def
index
def
index
issues
=
::
Boards
::
Issues
::
ListService
.
new
(
project
,
current_user
,
filter_params
).
execute
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:
{
render
json:
{
issues:
serialize_as_json
(
issues
),
issues:
serialize_as_json
(
issues
),
...
@@ -59,7 +59,7 @@ module Projects
...
@@ -59,7 +59,7 @@ module Projects
end
end
def
filter_params
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
end
def
move_params
def
move_params
...
@@ -73,7 +73,7 @@ module Projects
...
@@ -73,7 +73,7 @@ module Projects
def
serialize_as_json
(
resource
)
def
serialize_as_json
(
resource
)
resource
.
as_json
(
resource
.
as_json
(
labels:
true
,
labels:
true
,
only:
[
:iid
,
:title
,
:confidential
,
:due_date
],
only:
[
:i
d
,
:i
id
,
:title
,
:confidential
,
:due_date
],
include:
{
include:
{
assignee:
{
only:
[
:id
,
:name
,
:username
],
methods:
[
:avatar_url
]
},
assignee:
{
only:
[
:id
,
:name
,
:username
],
methods:
[
:avatar_url
]
},
milestone:
{
only:
[
:id
,
:title
]
}
milestone:
{
only:
[
:id
,
:title
]
}
...
...
app/helpers/boards_helper.rb
View file @
3213dfd7
...
@@ -6,7 +6,9 @@ module BoardsHelper
...
@@ -6,7 +6,9 @@ module BoardsHelper
endpoint:
namespace_project_boards_path
(
@project
.
namespace
,
@project
),
endpoint:
namespace_project_boards_path
(
@project
.
namespace
,
@project
),
board_id:
board
.
id
,
board_id:
board
.
id
,
disabled:
"
#{
!
can?
(
current_user
,
:admin_list
,
@project
)
}
"
,
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
end
end
app/models/board.rb
View file @
3213dfd7
...
@@ -5,10 +5,6 @@ class Board < ActiveRecord::Base
...
@@ -5,10 +5,6 @@ class Board < ActiveRecord::Base
validates
:project
,
presence:
true
validates
:project
,
presence:
true
def
backlog_list
lists
.
merge
(
List
.
backlog
).
take
end
def
done_list
def
done_list
lists
.
merge
(
List
.
done
).
take
lists
.
merge
(
List
.
done
).
take
end
end
...
...
app/models/list.rb
View file @
3213dfd7
...
@@ -2,7 +2,7 @@ class List < ActiveRecord::Base
...
@@ -2,7 +2,7 @@ class List < ActiveRecord::Base
belongs_to
:board
belongs_to
:board
belongs_to
:label
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
:board
,
:list_type
,
presence:
true
validates
:label
,
:position
,
presence:
true
,
if: :label?
validates
:label
,
:position
,
presence:
true
,
if: :label?
...
...
app/services/boards/create_service.rb
View file @
3213dfd7
...
@@ -12,7 +12,6 @@ module Boards
...
@@ -12,7 +12,6 @@ module Boards
def
create_board!
def
create_board!
board
=
project
.
boards
.
create
board
=
project
.
boards
.
create
board
.
lists
.
create
(
list_type: :backlog
)
board
.
lists
.
create
(
list_type: :done
)
board
.
lists
.
create
(
list_type: :done
)
board
board
...
...
app/services/boards/issues/list_service.rb
View file @
3213dfd7
...
@@ -3,8 +3,8 @@ module Boards
...
@@ -3,8 +3,8 @@ module Boards
class
ListService
<
BaseService
class
ListService
<
BaseService
def
execute
def
execute
issues
=
IssuesFinder
.
new
(
current_user
,
filter_params
).
execute
issues
=
IssuesFinder
.
new
(
current_user
,
filter_params
).
execute
issues
=
without_board_labels
(
issues
)
unless
list
.
movable
?
issues
=
without_board_labels
(
issues
)
unless
movable_list
?
issues
=
with_list_label
(
issues
)
if
list
.
movable
?
issues
=
with_list_label
(
issues
)
if
movable_list
?
issues
issues
end
end
...
@@ -15,7 +15,13 @@ module Boards
...
@@ -15,7 +15,13 @@ module Boards
end
end
def
list
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
end
def
filter_params
def
filter_params
...
@@ -40,7 +46,7 @@ module Boards
...
@@ -40,7 +46,7 @@ module Boards
end
end
def
set_state
def
set_state
params
[
:state
]
=
list
.
done?
?
'closed'
:
'opened'
params
[
:state
]
=
list
&&
list
.
done?
?
'closed'
:
'opened'
end
end
def
board_label_ids
def
board_label_ids
...
...
app/views/projects/boards/_show.html.haml
View file @
3213dfd7
...
@@ -24,5 +24,10 @@
...
@@ -24,5 +24,10 @@
":list"
=>
"list"
,
":list"
=>
"list"
,
":disabled"
=>
"disabled"
,
":disabled"
=>
"disabled"
,
":issue-link-base"
=>
"issueLinkBase"
,
":issue-link-base"
=>
"issueLinkBase"
,
":root-path"
=>
"rootPath"
,
":key"
=>
"_uid"
}
":key"
=>
"_uid"
}
=
render
"projects/boards/components/sidebar"
=
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 @@
...
@@ -29,6 +29,7 @@
":loading"
=>
"list.loading"
,
":loading"
=>
"list.loading"
,
":disabled"
=>
"disabled"
,
":disabled"
=>
"disabled"
,
":issue-link-base"
=>
"issueLinkBase"
,
":issue-link-base"
=>
"issueLinkBase"
,
":root-path"
=>
"rootPath"
,
"ref"
=>
"board-list"
}
"ref"
=>
"board-list"
}
-
if
can?
(
current_user
,
:admin_list
,
@project
)
-
if
can?
(
current_user
,
:admin_list
,
@project
)
=
render
"projects/boards/components/blank_state"
=
render
"projects/boards/components/blank_state"
app/views/projects/boards/components/_board_list.html.haml
View file @
3213dfd7
...
@@ -34,6 +34,7 @@
...
@@ -34,6 +34,7 @@
":list"
=>
"list"
,
":list"
=>
"list"
,
":issue"
=>
"issue"
,
":issue"
=>
"issue"
,
":issue-link-base"
=>
"issueLinkBase"
,
":issue-link-base"
=>
"issueLinkBase"
,
":root-path"
=>
"rootPath"
,
":disabled"
=>
"disabled"
,
":disabled"
=>
"disabled"
,
":key"
=>
"issue.id"
}
":key"
=>
"issue.id"
}
%li
.board-list-count.text-center
{
"v-if"
=>
"showCount"
}
%li
.board-list-count.text-center
{
"v-if"
=>
"showCount"
}
...
...
app/views/projects/boards/components/_card.html.haml
View file @
3213dfd7
...
@@ -4,25 +4,7 @@
...
@@ -4,25 +4,7 @@
"@mousedown"
=>
"mouseDown"
,
"@mousedown"
=>
"mouseDown"
,
"@mousemove"
=>
"mouseMove"
,
"@mousemove"
=>
"mouseMove"
,
"@mouseup"
=>
"showIssue($event)"
}
"@mouseup"
=>
"showIssue($event)"
}
%h4
.card-title
%issue-card-inner
{
":list"
=>
"list"
,
=
icon
(
"eye-slash"
,
class:
"confidential-icon"
,
"v-if"
=>
"issue.confidential"
)
":issue"
=>
"issue"
,
%a
{
":href"
=>
'issueLinkBase + "/" + issue.id'
,
":issue-link-base"
=>
"issueLinkBase"
,
":title"
=>
"issue.title"
}
":root-path"
=>
"rootPath"
}
{{ 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 }}
app/views/projects/boards/components/_sidebar.html.haml
View file @
3213dfd7
...
@@ -22,3 +22,5 @@
...
@@ -22,3 +22,5 @@
=
render
"projects/boards/components/sidebar/due_date"
=
render
"projects/boards/components/sidebar/due_date"
=
render
"projects/boards/components/sidebar/labels"
=
render
"projects/boards/components/sidebar/labels"
=
render
"projects/boards/components/sidebar/notifications"
=
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 @@
...
@@ -38,8 +38,9 @@
#js-boards-search
.issue-boards-search
#js-boards-search
.issue-boards-search
%input
.pull-left.form-control
{
type:
"search"
,
placeholder:
"Filter by name..."
,
"v-model"
=>
"filters.search"
,
"debounce"
=>
"250"
}
%input
.pull-left.form-control
{
type:
"search"
,
placeholder:
"Filter by name..."
,
"v-model"
=>
"filters.search"
,
"debounce"
=>
"250"
}
-
if
can?
(
current_user
,
:admin_list
,
@project
)
-
if
can?
(
current_user
,
:admin_list
,
@project
)
#js-add-issues-btn
.pull-right.prepend-left-10
.dropdown.pull-right
.dropdown.pull-right
%button
.btn.btn-create.js-new-board-list
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
labels:
labels_filter_path
,
namespace_path:
@project
.
try
(
:namespace
).
try
(
:path
),
project_path:
@project
.
try
(
:path
)
}
}
%button
.btn.btn-create.
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
Add list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
.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"
}
=
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
...
@@ -267,7 +267,7 @@ constraints(ProjectUrlConstrainer.new) do
resources
:boards
,
only:
[
:index
,
:show
]
do
resources
:boards
,
only:
[
:index
,
:show
]
do
scope
module: :boards
do
scope
module: :boards
do
resources
:issues
,
only:
[
:update
]
resources
:issues
,
only:
[
:
index
,
:
update
]
resources
:lists
,
only:
[
:index
,
:create
,
:update
,
:destroy
]
do
resources
:lists
,
only:
[
:index
,
:create
,
:update
,
:destroy
]
do
collection
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
...
@@ -37,7 +37,7 @@ module API
end
end
desc
'Get the lists of a project board'
do
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
success
Entities
::
List
end
end
get
'/lists'
do
get
'/lists'
do
...
...
spec/controllers/projects/boards/issues_controller_spec.rb
View file @
3213dfd7
...
@@ -18,9 +18,19 @@ describe Projects::Boards::IssuesController do
...
@@ -18,9 +18,19 @@ describe Projects::Boards::IssuesController do
end
end
describe
'GET index'
do
describe
'GET index'
do
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
list_issues
user:
user
,
board:
999
,
list:
list2
expect
(
response
).
to
have_http_status
(
404
)
end
end
context
'when list id is present'
do
context
'with valid list id'
do
context
'with valid list id'
do
it
'returns issues that have the list label applied'
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
])
issue
=
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
])
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
],
due_date:
Date
.
tomorrow
)
...
@@ -36,19 +46,29 @@ describe Projects::Boards::IssuesController do
...
@@ -36,19 +46,29 @@ describe Projects::Boards::IssuesController do
end
end
end
end
context
'with invalid board
id'
do
context
'with invalid list
id'
do
it
'returns a not found 404 response'
do
it
'returns a not found 404 response'
do
list_issues
user:
user
,
board:
999
,
list:
list2
list_issues
user:
user
,
board:
board
,
list:
999
expect
(
response
).
to
have_http_status
(
404
)
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
end
end
context
'with invalid list id'
do
context
'when list id is missing'
do
it
'returns a not found 404 response'
do
it
'returns opened issues without board labels applied'
do
list_issues
user:
user
,
board:
board
,
list:
999
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
])
expect
(
response
).
to
have_http_status
(
404
)
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
end
end
...
@@ -65,13 +85,17 @@ describe Projects::Boards::IssuesController do
...
@@ -65,13 +85,17 @@ describe Projects::Boards::IssuesController do
end
end
end
end
def
list_issues
(
user
:,
board
:,
list
:)
def
list_issues
(
user
:,
board
:,
list:
nil
)
sign_in
(
user
)
sign_in
(
user
)
get
:index
,
namespace_id:
project
.
namespace
.
to_param
,
params
=
{
namespace_id:
project
.
namespace
.
to_param
,
project_id:
project
.
to_param
,
project_id:
project
.
to_param
,
board_id:
board
.
to_param
,
board_id:
board
.
to_param
,
list_id:
list
.
to_param
list_id:
list
.
try
(
:to_param
)
}
get
:index
,
params
.
compact
end
end
end
end
...
...
spec/controllers/projects/boards/lists_controller_spec.rb
View file @
3213dfd7
...
@@ -27,7 +27,7 @@ describe Projects::Boards::ListsController do
...
@@ -27,7 +27,7 @@ describe Projects::Boards::ListsController do
parsed_response
=
JSON
.
parse
(
response
.
body
)
parsed_response
=
JSON
.
parse
(
response
.
body
)
expect
(
response
).
to
match_response_schema
(
'lists'
)
expect
(
response
).
to
match_response_schema
(
'lists'
)
expect
(
parsed_response
.
length
).
to
eq
3
expect
(
parsed_response
.
length
).
to
eq
2
end
end
context
'with unauthorized user'
do
context
'with unauthorized user'
do
...
...
spec/factories/boards.rb
View file @
3213dfd7
...
@@ -3,7 +3,6 @@ FactoryGirl.define do
...
@@ -3,7 +3,6 @@ FactoryGirl.define do
project
factory: :empty_project
project
factory: :empty_project
after
(
:create
)
do
|
board
|
after
(
:create
)
do
|
board
|
board
.
lists
.
create
(
list_type: :backlog
)
board
.
lists
.
create
(
list_type: :done
)
board
.
lists
.
create
(
list_type: :done
)
end
end
end
end
...
...
spec/factories/lists.rb
View file @
3213dfd7
...
@@ -6,12 +6,6 @@ FactoryGirl.define do
...
@@ -6,12 +6,6 @@ FactoryGirl.define do
sequence
(
:position
)
sequence
(
:position
)
end
end
factory
:backlog_list
,
parent: :list
do
list_type
:backlog
label
nil
position
nil
end
factory
:done_list
,
parent: :list
do
factory
:done_list
,
parent: :list
do
list_type
:done
list_type
:done
label
nil
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
...
@@ -20,7 +20,7 @@ describe 'Issue Boards', feature: true, js: true do
before
do
before
do
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
3
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
2
)
end
end
it
'shows blank state'
do
it
'shows blank state'
do
...
@@ -31,18 +31,18 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -31,18 +31,18 @@ describe 'Issue Boards', feature: true, js: true do
page
.
within
(
find
(
'.board-blank-state'
))
do
page
.
within
(
find
(
'.board-blank-state'
))
do
click_button
(
"Nevermind, I'll use my own"
)
click_button
(
"Nevermind, I'll use my own"
)
end
end
expect
(
page
).
to
have_selector
(
'.board'
,
count:
2
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
1
)
end
end
it
'creates default lists'
do
it
'creates default lists'
do
lists
=
[
'
Backlog'
,
'
To Do'
,
'Doing'
,
'Done'
]
lists
=
[
'To Do'
,
'Doing'
,
'Done'
]
page
.
within
(
find
(
'.board-blank-state'
))
do
page
.
within
(
find
(
'.board-blank-state'
))
do
click_button
(
'Add default lists'
)
click_button
(
'Add default lists'
)
end
end
wait_for_vue_resource
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
|
page
.
all
(
'.board'
).
each_with_index
do
|
list
,
i
|
expect
(
list
.
find
(
'.board-title'
)).
to
have_content
(
lists
[
i
])
expect
(
list
.
find
(
'.board-title'
)).
to
have_content
(
lists
[
i
])
...
@@ -64,42 +64,41 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -64,42 +64,41 @@ describe 'Issue Boards', feature: true, js: true do
let!
(
:list1
)
{
create
(
:list
,
board:
board
,
label:
planning
,
position:
0
)
}
let!
(
:list1
)
{
create
(
:list
,
board:
board
,
label:
planning
,
position:
0
)
}
let!
(
:list2
)
{
create
(
:list
,
board:
board
,
label:
development
,
position:
1
)
}
let!
(
:list2
)
{
create
(
:list
,
board:
board
,
label:
development
,
position:
1
)
}
let!
(
:confidential_issue
)
{
create
(
:
issue
,
:confidential
,
project:
project
,
author:
user
)
}
let!
(
:confidential_issue
)
{
create
(
:
labeled_issue
,
:confidential
,
project:
project
,
author:
user
,
labels:
[
planning
]
)
}
let!
(
:issue1
)
{
create
(
:
issue
,
project:
project
,
assignee:
user
)
}
let!
(
:issue1
)
{
create
(
:
labeled_issue
,
project:
project
,
assignee:
user
,
labels:
[
planning
]
)
}
let!
(
:issue2
)
{
create
(
:
issue
,
project:
project
,
author:
user2
)
}
let!
(
:issue2
)
{
create
(
:
labeled_issue
,
project:
project
,
author:
user2
,
labels:
[
planning
]
)
}
let!
(
:issue3
)
{
create
(
:
issue
,
project:
project
)
}
let!
(
:issue3
)
{
create
(
:
labeled_issue
,
project:
project
,
labels:
[
planning
]
)
}
let!
(
:issue4
)
{
create
(
:
issue
,
project:
project
)
}
let!
(
:issue4
)
{
create
(
:
labeled_issue
,
project:
project
,
labels:
[
planning
]
)
}
let!
(
:issue5
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
],
milestone:
milestone
)
}
let!
(
:issue5
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
],
milestone:
milestone
)
}
let!
(
:issue6
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
,
development
])
}
let!
(
:issue6
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
,
development
])
}
let!
(
:issue7
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
development
])
}
let!
(
:issue7
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
development
])
}
let!
(
:issue8
)
{
create
(
:closed_issue
,
project:
project
)
}
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
before
do
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
wait_for_vue_resource
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(1)'
)).
to
have_selector
(
'.card'
)
expect
(
find
(
'.board:nth-child(2)'
)).
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(3)'
)).
to
have_selector
(
'.card'
)
expect
(
find
(
'.board:nth-child(4)'
)).
to
have_selector
(
'.card'
)
end
end
it
'shows lists'
do
it
'shows lists'
do
expect
(
page
).
to
have_selector
(
'.board'
,
count:
4
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
3
)
end
end
it
'shows description tooltip on list title'
do
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'
)
expect
(
find
(
'.board-title span.has-tooltip'
)[
:title
]).
to
eq
(
'Test'
)
end
end
end
end
it
'shows issues in lists'
do
it
'shows issues in lists'
do
wait_for_board_cards
(
1
,
8
)
wait_for_board_cards
(
2
,
2
)
wait_for_board_cards
(
2
,
2
)
wait_for_board_cards
(
3
,
2
)
end
end
it
'shows confidential issues with icon'
do
it
'shows confidential issues with icon'
do
...
@@ -108,19 +107,6 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -108,19 +107,6 @@ describe 'Issue Boards', feature: true, js: true do
end
end
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
it
'search done list'
do
page
.
within
(
'#js-boards-search'
)
do
page
.
within
(
'#js-boards-search'
)
do
find
(
'.form-control'
).
set
(
issue8
.
title
)
find
(
'.form-control'
).
set
(
issue8
.
title
)
...
@@ -130,8 +116,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -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(1)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(2)'
)).
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(3)'
)).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
find
(
'.board:nth-child(4)'
)).
to
have_selector
(
'.card'
,
count:
1
)
end
end
it
'search list'
do
it
'search list'
do
...
@@ -141,157 +126,135 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -141,157 +126,135 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
expect
(
find
(
'.board:nth-child(1)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(1)'
)).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
find
(
'.board:nth-child(2)'
)).
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(3)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(4)'
)).
to
have_selector
(
'.card'
,
count:
0
)
end
end
it
'allows user to delete board'
do
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
find
(
'.board-delete'
).
click
end
end
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
3
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
2
)
end
end
it
'removes checkmark in new list dropdown after deleting'
do
it
'removes checkmark in new list dropdown after deleting'
do
click_button
'Add list'
click_button
'Add list'
wait_for_ajax
wait_for_ajax
page
.
within
(
find
(
'.board:nth-child(
2
)'
))
do
page
.
within
(
find
(
'.board:nth-child(
1
)'
))
do
find
(
'.board-delete'
).
click
find
(
'.board-delete'
).
click
end
end
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
3
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
2
)
expect
(
find
(
".js-board-list-
#{
planning
.
id
}
"
,
visible:
false
)).
not_to
have_css
(
'.is-active'
)
end
end
it
'infinite scrolls list'
do
it
'infinite scrolls list'
do
50
.
times
do
50
.
times
do
create
(
:
issue
,
project:
project
)
create
(
:
labeled_issue
,
project:
project
,
labels:
[
planning
]
)
end
end
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
wait_for_vue_resource
wait_for_vue_resource
page
.
within
(
find
(
'.board'
,
match: :first
))
do
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_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"
)
evaluate_script
(
"document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight"
)
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.card'
,
count:
40
)
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"
)
evaluate_script
(
"document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight"
)
wait_for_vue_resource
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'
)
expect
(
page
).
to
have_content
(
'Showing all issues'
)
end
end
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
context
'done'
do
it
'shows list of done issues'
do
it
'shows list of done issues'
do
wait_for_board_cards
(
4
,
1
)
wait_for_board_cards
(
3
,
1
)
wait_for_ajax
wait_for_ajax
end
end
it
'moves issue to done'
do
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
(
2
,
2
)
wait_for_board_cards
(
3
,
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(1)'
)).
not_to
have_content
(
issue9
.
title
)
expect
(
find
(
'.board:nth-child(
4
)'
)).
to
have_selector
(
'.card'
,
count:
2
)
expect
(
find
(
'.board:nth-child(
3
)'
)).
to
have_selector
(
'.card'
,
count:
2
)
expect
(
find
(
'.board:nth-child(
4
)'
)).
to
have_content
(
issue9
.
title
)
expect
(
find
(
'.board:nth-child(
3
)'
)).
to
have_content
(
issue9
.
title
)
expect
(
find
(
'.board:nth-child(
4
)'
)).
not_to
have_content
(
planning
.
title
)
expect
(
find
(
'.board:nth-child(
3
)'
)).
not_to
have_content
(
planning
.
title
)
end
end
it
'removes all of the same issue to done'
do
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
(
1
,
7
)
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
2
)
wait_for_board_cards
(
3
,
1
)
wait_for_board_cards
(
3
,
2
)
wait_for_board_cards
(
4
,
2
)
expect
(
find
(
'.board:nth-child(
2)'
)).
not_to
have_content
(
issue6
.
title
)
expect
(
find
(
'.board:nth-child(
1)'
)).
not_to
have_content
(
issue9
.
title
)
expect
(
find
(
'.board:nth-child(
4)'
)).
to
have_content
(
issue6
.
title
)
expect
(
find
(
'.board:nth-child(
3)'
)).
to
have_content
(
issue9
.
title
)
expect
(
find
(
'.board:nth-child(
4
)'
)).
not_to
have_content
(
planning
.
title
)
expect
(
find
(
'.board:nth-child(
3
)'
)).
not_to
have_content
(
planning
.
title
)
end
end
end
end
context
'lists'
do
context
'lists'
do
it
'changes position of list'
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
(
1
,
2
)
wait_for_board_cards
(
2
,
2
)
wait_for_board_cards
(
2
,
8
)
wait_for_board_cards
(
3
,
2
)
wait_for_board_cards
(
3
,
1
)
wait_for_board_cards
(
4
,
1
)
expect
(
find
(
'.board:nth-child(
2
)'
)).
to
have_content
(
development
.
title
)
expect
(
find
(
'.board:nth-child(
1
)'
)).
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
(
planning
.
title
)
end
end
it
'issue moves between lists'
do
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
(
1
,
7
)
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
2
)
wait_for_board_cards
(
3
,
3
)
wait_for_board_cards
(
3
,
1
)
wait_for_board_cards
(
4
,
1
)
expect
(
find
(
'.board:nth-child(
3
)'
)).
to
have_content
(
issue6
.
title
)
expect
(
find
(
'.board:nth-child(
2
)'
)).
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
)'
).
all
(
'.card'
).
last
).
not_to
have_content
(
development
.
title
)
end
end
it
'issue moves between lists'
do
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
(
1
,
9
)
wait_for_board_cards
(
2
,
3
)
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
3
,
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(
1
)'
)).
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
)'
).
all
(
'.card'
).
first
).
not_to
have_content
(
planning
.
title
)
end
end
it
'issue moves from done'
do
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
)
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
(
2
,
3
)
wait_for_board_cards
(
3
,
2
)
wait_for_board_cards
(
3
,
0
)
wait_for_board_cards
(
4
,
0
)
end
end
context
'issue card'
do
context
'issue card'
do
...
@@ -324,7 +287,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -324,7 +287,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
5
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
4
)
end
end
it
'creates new list for Backlog label'
do
it
'creates new list for Backlog label'
do
...
@@ -337,7 +300,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -337,7 +300,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
5
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
4
)
end
end
it
'creates new list for Done label'
do
it
'creates new list for Done label'
do
...
@@ -350,7 +313,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -350,7 +313,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
5
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
4
)
end
end
it
'keeps dropdown open after adding new list'
do
it
'keeps dropdown open after adding new list'
do
...
@@ -366,21 +329,6 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -366,21 +329,6 @@ describe 'Issue Boards', feature: true, js: true do
expect
(
find
(
'.issue-boards-search'
)).
to
have_selector
(
'.open'
)
expect
(
find
(
'.issue-boards-search'
)).
to
have_selector
(
'.open'
)
end
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
it
'creates new list from a new label'
do
click_button
'Add list'
click_button
'Add list'
...
@@ -397,7 +345,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -397,7 +345,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_ajax
wait_for_ajax
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
5
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
4
)
end
end
end
end
end
end
...
@@ -418,7 +366,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -418,7 +366,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
wait_for_board_cards
(
1
,
1
)
wait_for_board_cards
(
1
,
1
)
wait_for_empty_boards
((
2
..
4
))
wait_for_empty_boards
((
2
..
3
))
end
end
it
'filters by assignee'
do
it
'filters by assignee'
do
...
@@ -437,7 +385,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -437,7 +385,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
wait_for_board_cards
(
1
,
1
)
wait_for_board_cards
(
1
,
1
)
wait_for_empty_boards
((
2
..
4
))
wait_for_empty_boards
((
2
..
3
))
end
end
it
'filters by milestone'
do
it
'filters by milestone'
do
...
@@ -454,10 +402,9 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -454,10 +402,9 @@ describe 'Issue Boards', feature: true, js: true do
end
end
wait_for_vue_resource
wait_for_vue_resource
wait_for_board_cards
(
1
,
0
)
wait_for_board_cards
(
1
,
1
)
wait_for_board_cards
(
2
,
1
)
wait_for_board_cards
(
2
,
0
)
wait_for_board_cards
(
3
,
0
)
wait_for_board_cards
(
3
,
0
)
wait_for_board_cards
(
4
,
0
)
end
end
it
'filters by label'
do
it
'filters by label'
do
...
@@ -474,7 +421,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -474,7 +421,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
wait_for_board_cards
(
1
,
1
)
wait_for_board_cards
(
1
,
1
)
wait_for_empty_boards
((
2
..
4
))
wait_for_empty_boards
((
2
..
3
))
end
end
it
'filters by label with space after reload'
do
it
'filters by label with space after reload'
do
...
@@ -530,7 +477,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -530,7 +477,7 @@ describe 'Issue Boards', feature: true, js: true do
it
'infinite scrolls list with label filter'
do
it
'infinite scrolls list with label filter'
do
50
.
times
do
50
.
times
do
create
(
:labeled_issue
,
project:
project
,
labels:
[
testing
])
create
(
:labeled_issue
,
project:
project
,
labels:
[
planning
,
testing
])
end
end
page
.
within
'.issues-filters'
do
page
.
within
'.issues-filters'
do
...
@@ -580,32 +527,12 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -580,32 +527,12 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
wait_for_board_cards
(
1
,
1
)
wait_for_board_cards
(
1
,
1
)
wait_for_empty_boards
((
2
..
4
))
wait_for_empty_boards
((
2
..
3
))
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
)
end
end
it
'filters by clicking label button on issue'
do
it
'filters by clicking label button on issue'
do
page
.
within
(
find
(
'.board'
,
match: :first
))
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
)
expect
(
find
(
'.card'
,
match: :first
)).
to
have_content
(
bug
.
title
)
click_button
(
bug
.
title
)
click_button
(
bug
.
title
)
wait_for_vue_resource
wait_for_vue_resource
...
@@ -614,7 +541,7 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -614,7 +541,7 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
wait_for_vue_resource
wait_for_board_cards
(
1
,
1
)
wait_for_board_cards
(
1
,
1
)
wait_for_empty_boards
((
2
..
4
))
wait_for_empty_boards
((
2
..
3
))
page
.
within
(
'.labels-filter'
)
do
page
.
within
(
'.labels-filter'
)
do
expect
(
find
(
'.dropdown-toggle-text'
)).
to
have_content
(
bug
.
title
)
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
...
@@ -6,6 +6,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let!
(
:list
)
{
create
(
:list
,
board:
board
,
position:
0
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
context
'authorized user'
do
context
'authorized user'
do
...
@@ -17,7 +18,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
...
@@ -17,7 +18,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
visit
namespace_project_board_path
(
project
.
namespace
,
project
,
board
)
wait_for_vue_resource
wait_for_vue_resource
expect
(
page
).
to
have_selector
(
'.board'
,
count:
3
)
expect
(
page
).
to
have_selector
(
'.board'
,
count:
2
)
end
end
it
'displays new issue button'
do
it
'displays new issue button'
do
...
@@ -25,7 +26,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
...
@@ -25,7 +26,7 @@ describe 'Issue Boards new issue', feature: true, js: true do
end
end
it
'does not display new issue button in done list'
do
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'
)
expect
(
page
).
not_to
have_selector
(
'.board-issue-count-holder .btn'
)
end
end
end
end
...
...
spec/features/boards/sidebar_spec.rb
View file @
3213dfd7
...
@@ -4,14 +4,17 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -4,14 +4,17 @@ describe 'Issue Boards', feature: true, js: true do
include
WaitForAjax
include
WaitForAjax
include
WaitForVueResource
include
WaitForVueResource
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let!
(
:label
)
{
create
(
:label
,
project:
project
)
}
let
(
:project
)
{
create
(
:empty_project
,
:public
)
}
let!
(
:label2
)
{
create
(
:label
,
project:
project
)
}
let!
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
let!
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
let!
(
:issue2
)
{
create
(
:labeled_issue
,
project:
project
,
assignee:
user
,
milestone:
milestone
,
labels:
[
label
])
}
let!
(
:development
)
{
create
(
:label
,
project:
project
,
name:
'Development'
)
}
let!
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
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
before
do
project
.
team
<<
[
user
,
:master
]
project
.
team
<<
[
user
,
:master
]
...
@@ -62,8 +65,22 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -62,8 +65,22 @@ describe 'Issue Boards', feature: true, js: true do
end
end
page
.
within
(
'.issue-boards-sidebar'
)
do
page
.
within
(
'.issue-boards-sidebar'
)
do
expect
(
page
).
to
have_content
(
issue
.
title
)
expect
(
page
).
to
have_content
(
issue2
.
title
)
expect
(
page
).
to
have_content
(
issue
.
to_reference
)
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
end
end
...
@@ -244,22 +261,22 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -244,22 +261,22 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_ajax
wait_for_ajax
click_link
label
.
title
click_link
bug
.
title
wait_for_vue_resource
wait_for_vue_resource
find
(
'.dropdown-menu-close-icon'
).
click
find
(
'.dropdown-menu-close-icon'
).
click
page
.
within
(
'.value'
)
do
page
.
within
(
'.value'
)
do
expect
(
page
).
to
have_selector
(
'.label'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.label'
,
count:
3
)
expect
(
page
).
to
have_content
(
label
.
title
)
expect
(
page
).
to
have_content
(
bug
.
title
)
end
end
end
end
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
first
(
'.card'
))
do
page
.
within
(
first
(
'.card'
))
do
expect
(
page
).
to
have_selector
(
'.label'
,
count:
1
)
expect
(
page
).
to
have_selector
(
'.label'
,
count:
2
)
expect
(
page
).
to
have_content
(
label
.
title
)
expect
(
page
).
to
have_content
(
bug
.
title
)
end
end
end
end
end
end
...
@@ -274,32 +291,32 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -274,32 +291,32 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_ajax
wait_for_ajax
click_link
label
.
title
click_link
bug
.
title
click_link
label2
.
title
click_link
regression
.
title
wait_for_vue_resource
wait_for_vue_resource
find
(
'.dropdown-menu-close-icon'
).
click
find
(
'.dropdown-menu-close-icon'
).
click
page
.
within
(
'.value'
)
do
page
.
within
(
'.value'
)
do
expect
(
page
).
to
have_selector
(
'.label'
,
count:
2
)
expect
(
page
).
to
have_selector
(
'.label'
,
count:
4
)
expect
(
page
).
to
have_content
(
label
.
title
)
expect
(
page
).
to
have_content
(
bug
.
title
)
expect
(
page
).
to
have_content
(
label2
.
title
)
expect
(
page
).
to
have_content
(
regression
.
title
)
end
end
end
end
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
first
(
'.card'
))
do
page
.
within
(
first
(
'.card'
))
do
expect
(
page
).
to
have_selector
(
'.label'
,
count:
2
)
expect
(
page
).
to
have_selector
(
'.label'
,
count:
3
)
expect
(
page
).
to
have_content
(
label
.
title
)
expect
(
page
).
to
have_content
(
bug
.
title
)
expect
(
page
).
to
have_content
(
label2
.
title
)
expect
(
page
).
to
have_content
(
regression
.
title
)
end
end
end
end
end
end
it
'removes a label'
do
it
'removes a label'
do
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
first
(
'.board'
))
do
fi
nd
(
'.card:nth-child(2)
'
).
click
fi
rst
(
'.card
'
).
click
end
end
page
.
within
(
'.labels'
)
do
page
.
within
(
'.labels'
)
do
...
@@ -307,22 +324,22 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -307,22 +324,22 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_ajax
wait_for_ajax
click_link
label
.
title
click_link
stretch
.
title
wait_for_vue_resource
wait_for_vue_resource
find
(
'.dropdown-menu-close-icon'
).
click
find
(
'.dropdown-menu-close-icon'
).
click
page
.
within
(
'.value'
)
do
page
.
within
(
'.value'
)
do
expect
(
page
).
to
have_selector
(
'.label'
,
count:
0
)
expect
(
page
).
to
have_selector
(
'.label'
,
count:
1
)
expect
(
page
).
not_to
have_content
(
label
.
title
)
expect
(
page
).
not_to
have_content
(
stretch
.
title
)
end
end
end
end
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
first
(
'.board'
))
do
page
.
within
(
fi
nd
(
'.card:nth-child(2)
'
))
do
page
.
within
(
fi
rst
(
'.card
'
))
do
expect
(
page
).
not_to
have_selector
(
'.label'
,
count:
1
)
expect
(
page
).
not_to
have_selector
(
'.label'
)
expect
(
page
).
not_to
have_content
(
label
.
title
)
expect
(
page
).
not_to
have_content
(
stretch
.
title
)
end
end
end
end
end
end
...
...
spec/fixtures/api/schemas/issue.json
View file @
3213dfd7
...
@@ -6,6 +6,7 @@
...
@@ -6,6 +6,7 @@
"confidential"
"confidential"
],
],
"properties"
:
{
"properties"
:
{
"id"
:
{
"type"
:
"integer"
},
"iid"
:
{
"type"
:
"integer"
},
"iid"
:
{
"type"
:
"integer"
},
"title"
:
{
"type"
:
"string"
},
"title"
:
{
"type"
:
"string"
},
"confidential"
:
{
"type"
:
"boolean"
},
"confidential"
:
{
"type"
:
"boolean"
},
...
...
spec/fixtures/api/schemas/list.json
View file @
3213dfd7
...
@@ -10,7 +10,7 @@
...
@@ -10,7 +10,7 @@
"id"
:
{
"type"
:
"integer"
},
"id"
:
{
"type"
:
"integer"
},
"list_type"
:
{
"list_type"
:
{
"type"
:
"string"
,
"type"
:
"string"
,
"enum"
:
[
"
backlog"
,
"
label"
,
"done"
]
"enum"
:
[
"label"
,
"done"
]
},
},
"label"
:
{
"label"
:
{
"type"
:
[
"object"
,
"null"
],
"type"
:
[
"object"
,
"null"
],
...
...
spec/javascripts/boards/boards_store_spec.js.es6
View file @
3213dfd7
...
@@ -23,7 +23,7 @@
...
@@ -23,7 +23,7 @@
describe('Store', () => {
describe('Store', () => {
beforeEach(() => {
beforeEach(() => {
Vue.http.interceptors.push(boardsMockInterceptor);
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();
gl.issueBoards.BoardsStore.create();
Cookies.set('issue_board_welcome_hidden', 'false', {
Cookies.set('issue_board_welcome_hidden', 'false', {
...
@@ -61,18 +61,6 @@ describe('Store', () => {
...
@@ -61,18 +61,6 @@ describe('Store', () => {
expect(list).toBeDefined();
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) => {
it('gets issue when new list added', (done) => {
gl.issueBoards.BoardsStore.addList(listObj);
gl.issueBoards.BoardsStore.addList(listObj);
const list = gl.issueBoards.BoardsStore.findList('id', 1);
const list = gl.issueBoards.BoardsStore.findList('id', 1);
...
@@ -117,10 +105,7 @@ describe('Store', () => {
...
@@ -117,10 +105,7 @@ describe('Store', () => {
expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false);
expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false);
});
});
it('check for blank state adding when backlog & done list exist', () => {
it('check for blank state adding when done list exist', () => {
gl.issueBoards.BoardsStore.addList({
list_type: 'backlog'
});
gl.issueBoards.BoardsStore.addList({
gl.issueBoards.BoardsStore.addList({
list_type: 'done'
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', () => {
...
@@ -20,7 +20,7 @@ describe('Issue model', () => {
let issue;
let issue;
beforeEach(() => {
beforeEach(() => {
gl.boardService = new BoardService('/test/issue-boards/board', '1');
gl.boardService = new BoardService('/test/issue-boards/board', '
', '
1');
gl.issueBoards.BoardsStore.create();
gl.issueBoards.BoardsStore.create();
issue = new ListIssue({
issue = new ListIssue({
...
...
spec/javascripts/boards/list_spec.js.es6
View file @
3213dfd7
...
@@ -24,7 +24,7 @@ describe('List model', () => {
...
@@ -24,7 +24,7 @@ describe('List model', () => {
beforeEach(() => {
beforeEach(() => {
Vue.http.interceptors.push(boardsMockInterceptor);
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();
gl.issueBoards.BoardsStore.create();
list = new List(listObj);
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 @@
...
@@ -21,5 +21,19 @@
expect(largeFont > regular).toBe(true);
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
...
@@ -19,13 +19,6 @@ describe List do
expect
(
subject
).
to
validate_uniqueness_of
(
:label_id
).
scoped_to
(
:board_id
)
expect
(
subject
).
to
validate_uniqueness_of
(
:label_id
).
scoped_to
(
:board_id
)
end
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
context
'when list_type is set to done'
do
subject
{
described_class
.
new
(
list_type: :done
)
}
subject
{
described_class
.
new
(
list_type: :done
)
}
...
@@ -41,12 +34,6 @@ describe List do
...
@@ -41,12 +34,6 @@ describe List do
expect
(
subject
.
destroy
).
to
be_truthy
expect
(
subject
.
destroy
).
to
be_truthy
end
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
it
'can not be destroyed when when list_type is set to done'
do
subject
=
create
(
:done_list
)
subject
=
create
(
:done_list
)
...
@@ -55,19 +42,13 @@ describe List do
...
@@ -55,19 +42,13 @@ describe List do
end
end
describe
'#destroyable?'
do
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
subject
.
list_type
=
:label
expect
(
subject
).
to
be_destroyable
expect
(
subject
).
to
be_destroyable
end
end
it
'retruns false when list_type is set to backlog'
do
it
'returns false when list_type is set to done'
do
subject
.
list_type
=
:backlog
expect
(
subject
).
not_to
be_destroyable
end
it
'retruns false when list_type is set to done'
do
subject
.
list_type
=
:done
subject
.
list_type
=
:done
expect
(
subject
).
not_to
be_destroyable
expect
(
subject
).
not_to
be_destroyable
...
@@ -75,19 +56,13 @@ describe List do
...
@@ -75,19 +56,13 @@ describe List do
end
end
describe
'#movable?'
do
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
subject
.
list_type
=
:label
expect
(
subject
).
to
be_movable
expect
(
subject
).
to
be_movable
end
end
it
'retruns false when list_type is set to backlog'
do
it
'returns false when list_type is set to done'
do
subject
.
list_type
=
:backlog
expect
(
subject
).
not_to
be_movable
end
it
'retruns false when list_type is set to done'
do
subject
.
list_type
=
:done
subject
.
list_type
=
:done
expect
(
subject
).
not_to
be_movable
expect
(
subject
).
not_to
be_movable
...
@@ -102,12 +77,6 @@ describe List do
...
@@ -102,12 +77,6 @@ describe List do
expect
(
subject
.
title
).
to
eq
'Development'
expect
(
subject
.
title
).
to
eq
'Development'
end
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
it
'returns Done when list_type is set to done'
do
subject
.
list_type
=
:done
subject
.
list_type
=
:done
...
...
spec/services/boards/create_service_spec.rb
View file @
3213dfd7
...
@@ -11,12 +11,11 @@ describe Boards::CreateService, services: true do
...
@@ -11,12 +11,11 @@ describe Boards::CreateService, services: true do
expect
{
service
.
execute
}.
to
change
(
Board
,
:count
).
by
(
1
)
expect
{
service
.
execute
}.
to
change
(
Board
,
:count
).
by
(
1
)
end
end
it
'creates default lists'
do
it
'creates
the
default lists'
do
board
=
service
.
execute
board
=
service
.
execute
expect
(
board
.
lists
.
size
).
to
eq
2
expect
(
board
.
lists
.
size
).
to
eq
1
expect
(
board
.
lists
.
first
).
to
be_backlog
expect
(
board
.
lists
.
first
).
to
be_done
expect
(
board
.
lists
.
last
).
to
be_done
end
end
end
end
...
...
spec/services/boards/issues/list_service_spec.rb
View file @
3213dfd7
...
@@ -13,7 +13,6 @@ describe Boards::Issues::ListService, services: true do
...
@@ -13,7 +13,6 @@ describe Boards::Issues::ListService, services: true do
let
(
:p2
)
{
create
(
:label
,
title:
'P2'
,
project:
project
,
priority:
2
)
}
let
(
:p2
)
{
create
(
:label
,
title:
'P2'
,
project:
project
,
priority:
2
)
}
let
(
:p3
)
{
create
(
:label
,
title:
'P3'
,
project:
project
,
priority:
3
)
}
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!
(
:list1
)
{
create
(
:list
,
board:
board
,
label:
development
,
position:
0
)
}
let!
(
:list2
)
{
create
(
:list
,
board:
board
,
label:
testing
,
position:
1
)
}
let!
(
:list2
)
{
create
(
:list
,
board:
board
,
label:
testing
,
position:
1
)
}
let!
(
:done
)
{
create
(
:done_list
,
board:
board
)
}
let!
(
:done
)
{
create
(
:done_list
,
board:
board
)
}
...
@@ -45,8 +44,8 @@ describe Boards::Issues::ListService, services: true do
...
@@ -45,8 +44,8 @@ describe Boards::Issues::ListService, services: true do
end
end
context
'sets default order to priority'
do
context
'sets default order to priority'
do
it
'returns opened issues when list
ing issues from Backlo
g'
do
it
'returns opened issues when list
id is missin
g'
do
params
=
{
board_id:
board
.
id
,
id:
backlog
.
id
}
params
=
{
board_id:
board
.
id
}
issues
=
described_class
.
new
(
project
,
user
,
params
).
execute
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
...
@@ -10,7 +10,6 @@ describe Boards::Issues::MoveService, services: true do
let
(
:development
)
{
create
(
:label
,
project:
project
,
name:
'Development'
)
}
let
(
:development
)
{
create
(
:label
,
project:
project
,
name:
'Development'
)
}
let
(
:testing
)
{
create
(
:label
,
project:
project
,
name:
'Testing'
)
}
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!
(
:list1
)
{
create
(
:list
,
board:
board1
,
label:
development
,
position:
0
)
}
let!
(
:list2
)
{
create
(
:list
,
board:
board1
,
label:
testing
,
position:
1
)
}
let!
(
:list2
)
{
create
(
:list
,
board:
board1
,
label:
testing
,
position:
1
)
}
let!
(
:done
)
{
create
(
:done_list
,
board:
board1
)
}
let!
(
:done
)
{
create
(
:done_list
,
board:
board1
)
}
...
@@ -19,41 +18,6 @@ describe Boards::Issues::MoveService, services: true do
...
@@ -19,41 +18,6 @@ describe Boards::Issues::MoveService, services: true do
project
.
team
<<
[
user
,
:developer
]
project
.
team
<<
[
user
,
:developer
]
end
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
context
'when moving an issue between lists'
do
let
(
:issue
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
bug
,
development
])
}
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
}
}
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
...
@@ -113,19 +77,6 @@ describe Boards::Issues::MoveService, services: true do
end
end
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
context
'when moving to same list'
do
let
(
:issue
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
bug
,
development
])
}
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
}
}
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
...
@@ -21,7 +21,7 @@ describe Boards::Lists::CreateService, services: true do
end
end
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
it
'creates a new list at beginning of the list'
do
list
=
service
.
execute
(
board
)
list
=
service
.
execute
(
board
)
...
@@ -40,7 +40,7 @@ describe Boards::Lists::CreateService, services: true do
...
@@ -40,7 +40,7 @@ describe Boards::Lists::CreateService, services: true do
end
end
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
it
'creates a new list at end of the label lists'
do
list1
=
create
(
:list
,
board:
board
,
position:
0
)
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
...
@@ -15,7 +15,6 @@ describe Boards::Lists::DestroyService, services: true do
end
end
it
'decrements position of higher lists'
do
it
'decrements position of higher lists'
do
backlog
=
board
.
backlog_list
development
=
create
(
:list
,
board:
board
,
position:
0
)
development
=
create
(
:list
,
board:
board
,
position:
0
)
review
=
create
(
:list
,
board:
board
,
position:
1
)
review
=
create
(
:list
,
board:
board
,
position:
1
)
staging
=
create
(
:list
,
board:
board
,
position:
2
)
staging
=
create
(
:list
,
board:
board
,
position:
2
)
...
@@ -23,20 +22,12 @@ describe Boards::Lists::DestroyService, services: true do
...
@@ -23,20 +22,12 @@ describe Boards::Lists::DestroyService, services: true do
described_class
.
new
(
project
,
user
).
execute
(
development
)
described_class
.
new
(
project
,
user
).
execute
(
development
)
expect
(
backlog
.
reload
.
position
).
to
be_nil
expect
(
review
.
reload
.
position
).
to
eq
0
expect
(
review
.
reload
.
position
).
to
eq
0
expect
(
staging
.
reload
.
position
).
to
eq
1
expect
(
staging
.
reload
.
position
).
to
eq
1
expect
(
done
.
reload
.
position
).
to
be_nil
expect
(
done
.
reload
.
position
).
to
be_nil
end
end
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
it
'does not remove list from board when list type is done'
do
list
=
board
.
done_list
list
=
board
.
done_list
service
=
described_class
.
new
(
project
,
user
)
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
...
@@ -10,7 +10,7 @@ describe Boards::Lists::ListService, services: true do
service
=
described_class
.
new
(
project
,
double
)
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
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
...
@@ -6,7 +6,6 @@ describe Boards::Lists::MoveService, services: true do
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let!
(
:backlog
)
{
create
(
:backlog_list
,
board:
board
)
}
let!
(
:planning
)
{
create
(
:list
,
board:
board
,
position:
0
)
}
let!
(
:planning
)
{
create
(
:list
,
board:
board
,
position:
0
)
}
let!
(
:development
)
{
create
(
:list
,
board:
board
,
position:
1
)
}
let!
(
:development
)
{
create
(
:list
,
board:
board
,
position:
1
)
}
let!
(
:review
)
{
create
(
:list
,
board:
board
,
position:
2
)
}
let!
(
:review
)
{
create
(
:list
,
board:
board
,
position:
2
)
}
...
@@ -87,14 +86,6 @@ describe Boards::Lists::MoveService, services: true do
...
@@ -87,14 +86,6 @@ describe Boards::Lists::MoveService, services: true do
end
end
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
it
'keeps position of lists when list type is done'
do
service
=
described_class
.
new
(
project
,
user
,
position:
2
)
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