Commit c495e5f1 authored by Fatih Acet's avatar Fatih Acet

Merge branch 'issues-modal-filters' into 'master'

Issue filters in boards modal

Closes #26205

See merge request !8903
parents 9b135435 b27f6638
/* global Vue */
const userFilter = require('./filters/user');
const milestoneFilter = require('./filters/milestone');
const labelFilter = require('./filters/label');
module.exports = Vue.extend({
name: 'modal-filters',
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
destroyed() {
gl.issueBoards.ModalStore.setDefaultFilter();
},
components: {
userFilter,
milestoneFilter,
labelFilter,
},
template: `
<div class="modal-filters">
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module.exports = Vue.extend({
name: 'filter-label',
props: {
labelPath: {
type: String,
required: true,
},
},
mounted() {
new LabelsSelect(this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module.exports = Vue.extend({
name: 'filter-milestone',
props: {
milestonePath: {
type: String,
required: true,
},
},
mounted() {
new MilestoneSelect(null, this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module.exports = Vue.extend({
name: 'filter-user',
props: {
toggleClassName: {
type: String,
required: true,
},
dropdownClassName: {
type: String,
required: false,
default: '',
},
toggleLabel: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
nullUser: {
type: Boolean,
required: false,
default: false,
},
projectId: {
type: Number,
required: true,
},
},
mounted() {
new UsersSelect(null, this.$refs.dropdown);
},
computed: {
currentUsername() {
return gon.current_username;
},
dropdownTitle() {
return `Filter by ${this.toggleLabel.toLowerCase()}`;
},
inputPlaceholder() {
return `Search ${this.toggleLabel.toLowerCase()}`;
},
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* global Vue */ /* global Vue */
require('./tabs'); require('./tabs');
const modalFilters = require('./filters');
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalHeader = Vue.extend({ gl.issueBoards.ModalHeader = Vue.extend({
mixins: [gl.issueBoards.ModalMixins], mixins: [gl.issueBoards.ModalMixins],
props: {
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
},
data() { data() {
return ModalStore.store; return ModalStore.store;
}, },
...@@ -31,6 +45,7 @@ require('./tabs'); ...@@ -31,6 +45,7 @@ require('./tabs');
}, },
components: { components: {
'modal-tabs': gl.issueBoards.ModalTabs, 'modal-tabs': gl.issueBoards.ModalTabs,
modalFilters,
}, },
template: ` template: `
<div> <div>
...@@ -51,6 +66,11 @@ require('./tabs'); ...@@ -51,6 +66,11 @@ require('./tabs');
<div <div
class="add-issues-search append-bottom-10" class="add-issues-search append-bottom-10"
v-if="showSearch"> v-if="showSearch">
<modal-filters
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input <input
placeholder="Search issues..." placeholder="Search issues..."
class="form-control" class="form-control"
......
...@@ -27,6 +27,18 @@ require('./empty_state'); ...@@ -27,6 +27,18 @@ require('./empty_state');
type: String, type: String,
required: true, required: true,
}, },
projectId: {
type: Number,
required: true,
},
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
},
}, },
data() { data() {
return ModalStore.store; return ModalStore.store;
...@@ -52,17 +64,27 @@ require('./empty_state'); ...@@ -52,17 +64,27 @@ require('./empty_state');
this.issuesCount = false; this.issuesCount = false;
} }
}, },
filter: {
handler() {
this.loadIssues(true);
},
deep: true,
},
}, },
methods: { methods: {
searchOperation: _.debounce(function searchOperationDebounce() { searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true); this.loadIssues(true);
}, 500), }, 500),
loadIssues(clearIssues = false) { loadIssues(clearIssues = false) {
return gl.boardService.getBacklog({ if (!this.showAddIssuesModal) return false;
const queryData = Object.assign({}, this.filter, {
search: this.searchTerm, search: this.searchTerm,
page: this.page, page: this.page,
per: this.perPage, per: this.perPage,
}).then((res) => { });
return gl.boardService.getBacklog(queryData).then((res) => {
const data = res.json(); const data = res.json();
if (clearIssues) { if (clearIssues) {
...@@ -112,8 +134,13 @@ require('./empty_state'); ...@@ -112,8 +134,13 @@ require('./empty_state');
class="add-issues-modal" class="add-issues-modal"
v-if="showAddIssuesModal"> v-if="showAddIssuesModal">
<div class="add-issues-container"> <div class="add-issues-container">
<modal-header></modal-header> <modal-header
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-header>
<modal-list <modal-list
:image="blankStateImage"
:issue-link-base="issueLinkBase" :issue-link-base="issueLinkBase"
:root-path="rootPath" :root-path="rootPath"
v-if="!loading && showList"></modal-list> v-if="!loading && showList"></modal-list>
......
...@@ -14,6 +14,10 @@ ...@@ -14,6 +14,10 @@
type: String, type: String,
required: true, required: true,
}, },
image: {
type: String,
required: true,
},
}, },
data() { data() {
return ModalStore.store; return ModalStore.store;
...@@ -110,6 +114,19 @@ ...@@ -110,6 +114,19 @@
<section <section
class="add-issues-list add-issues-list-columns" class="add-issues-list add-issues-list-columns"
ref="list"> ref="list">
<div
class="empty-state add-issues-empty-state-filter text-center"
v-if="issuesCount > 0 && issues.length === 0">
<div
class="svg-content"
v-html="image">
</div>
<div class="text-content">
<h4>
There are no issues to show.
</h4>
</div>
</div>
<div <div
v-for="group in groupedIssues" v-for="group in groupedIssues"
class="add-issues-list-column"> class="add-issues-list-column">
......
...@@ -18,6 +18,17 @@ ...@@ -18,6 +18,17 @@
page: 1, page: 1,
perPage: 50, perPage: 50,
}; };
this.setDefaultFilter();
}
setDefaultFilter() {
this.store.filter = {
author_id: '',
assignee_id: '',
milestone_title: '',
label_name: [],
};
} }
selectedCount() { selectedCount() {
......
...@@ -4,10 +4,17 @@ ...@@ -4,10 +4,17 @@
(function() { (function() {
this.LabelsSelect = (function() { this.LabelsSelect = (function() {
function LabelsSelect() { function LabelsSelect(els) {
var _this; var _this, $els;
_this = this; _this = this;
$('.js-label-select').each(function(i, dropdown) {
$els = $(els);
if (!els) {
$els = $('.js-label-select');
}
$els.each(function(i, dropdown) {
var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer; var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, namespacePath, projectPath, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, initialSelected, $toggleText, fieldName, useId, propertyName, showMenuAbove, $container, $dropdownContainer;
$dropdown = $(dropdown); $dropdown = $(dropdown);
$dropdownContainer = $dropdown.closest('.labels-filter'); $dropdownContainer = $dropdown.closest('.labels-filter');
...@@ -324,7 +331,7 @@ ...@@ -324,7 +331,7 @@
multiSelect: $dropdown.hasClass('js-multiselect'), multiSelect: $dropdown.hasClass('js-multiselect'),
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(label, $el, e, isMarking) { clicked: function(label, $el, e, isMarking) {
var isIssueIndex, isMRIndex, page; var isIssueIndex, isMRIndex, page, boardsModel;
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
...@@ -346,22 +353,31 @@ ...@@ -346,22 +353,31 @@
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
!$dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.ModalStore.store.filter;
}
if (boardsModel) {
if (label.isAny) { if (label.isAny) {
gl.issueBoards.BoardsStore.state.filters['label_name'] = []; boardsModel['label_name'] = [];
} }
else if ($el.hasClass('is-active')) { else if ($el.hasClass('is-active')) {
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title); boardsModel['label_name'].push(label.title);
} }
else { else {
var filters = gl.issueBoards.BoardsStore.state.filters['label_name']; var filters = boardsModel['label_name'];
filters = filters.filter(function (filteredLabel) { filters = filters.filter(function (filteredLabel) {
return filteredLabel !== label.title; return filteredLabel !== label.title;
}); });
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters; boardsModel['label_name'] = filters;
} }
if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault(); e.preventDefault();
return; return;
} }
......
...@@ -5,13 +5,20 @@ ...@@ -5,13 +5,20 @@
(function() { (function() {
this.MilestoneSelect = (function() { this.MilestoneSelect = (function() {
function MilestoneSelect(currentProject) { function MilestoneSelect(currentProject, els) {
var _this; var _this, $els;
if (currentProject != null) { if (currentProject != null) {
_this = this; _this = this;
this.currentProject = JSON.parse(currentProject); this.currentProject = JSON.parse(currentProject);
} }
$('.js-milestone-select').each(function(i, dropdown) {
$els = $(els);
if (!els) {
$els = $('.js-milestone-select');
}
$els.each(function(i, dropdown) {
var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove; var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, useId, showMenuAbove;
$dropdown = $(dropdown); $dropdown = $(dropdown);
projectId = $dropdown.data('project-id'); projectId = $dropdown.data('project-id');
...@@ -108,7 +115,7 @@ ...@@ -108,7 +115,7 @@
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(selected, $el, e) { clicked: function(selected, $el, e) {
var data, isIssueIndex, isMRIndex, page; var data, isIssueIndex, isMRIndex, page, boardsStore;
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index'); isMRIndex = (page === page && page === 'projects:merge_requests:index');
...@@ -116,9 +123,19 @@ ...@@ -116,9 +123,19 @@
e.preventDefault(); e.preventDefault();
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
!$dropdown.closest('.add-issues-modal').length) {
boardsStore = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
boardsStore = gl.issueBoards.ModalStore.store.filter;
}
if (boardsStore) {
boardsStore[$dropdown.data('field-name')] = selected.name;
if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault(); e.preventDefault();
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
if (selected.name != null) { if (selected.name != null) {
......
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
slice = [].slice; slice = [].slice;
this.UsersSelect = (function() { this.UsersSelect = (function() {
function UsersSelect(currentUser) { function UsersSelect(currentUser, els) {
var $els;
this.users = bind(this.users, this); this.users = bind(this.users, this);
this.user = bind(this.user, this); this.user = bind(this.user, this);
this.usersPath = "/autocomplete/users.json"; this.usersPath = "/autocomplete/users.json";
...@@ -20,7 +21,14 @@ ...@@ -20,7 +21,14 @@
this.currentUser = JSON.parse(currentUser); this.currentUser = JSON.parse(currentUser);
} }
} }
$('.js-user-search').each((function(_this) {
$els = $(els);
if (!els) {
$els = $('.js-user-search');
}
$els.each((function(_this) {
return function(i, dropdown) { return function(i, dropdown) {
var options = {}; var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove; var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
...@@ -193,7 +201,9 @@ ...@@ -193,7 +201,9 @@
selectedId = user.id; selectedId = user.id;
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) { if ($el.closest('.add-issues-modal').length) {
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
} else if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
selectedId = user.id; selectedId = user.id;
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
gl.issueBoards.BoardsStore.updateFiltersUrl(); gl.issueBoards.BoardsStore.updateFiltersUrl();
......
...@@ -389,6 +389,13 @@ ...@@ -389,6 +389,13 @@
flex: 1; flex: 1;
margin-top: 0; margin-top: 0;
&.add-issues-empty-state-filter {
-webkit-flex-direction: column;
flex-direction: column;
-webkit-justify-content: center;
justify-content: center;
}
> .row { > .row {
width: 100%; width: 100%;
margin: auto 0; margin: auto 0;
...@@ -416,6 +423,14 @@ ...@@ -416,6 +423,14 @@
.add-issues-search { .add-issues-search {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
.form-control {
margin-left: auto;
@media (min-width: $screen-sm-min) {
max-width: 200px;
}
}
} }
.add-issues-list-column { .add-issues-list-column {
...@@ -486,3 +501,24 @@ ...@@ -486,3 +501,24 @@
line-height: 15px; line-height: 15px;
border-radius: 50%; border-radius: 50%;
} }
.modal-filters {
display: flex;
> .dropdown {
display: none;
margin-right: 10px;
@media (min-width: $screen-sm-min) {
display: block;
}
}
.dropdown-menu-toggle {
width: 100px;
@media (min-width: $screen-md-min) {
width: 140px;
}
}
}
...@@ -29,5 +29,8 @@ ...@@ -29,5 +29,8 @@
= render "projects/boards/components/sidebar" = render "projects/boards/components/sidebar"
%board-add-issues-modal{ "blank-state-image" => render('shared/empty_states/icons/issues.svg'), %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), "new-issue-path" => new_namespace_project_issue_path(@project.namespace, @project),
"milestone-path" => milestones_filter_dropdown_path,
"label-path" => labels_filter_path,
":issue-link-base" => "issueLinkBase", ":issue-link-base" => "issueLinkBase",
":root-path" => "rootPath" } ":root-path" => "rootPath",
":project-id" => @project.try(:id) }
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :feature, :js do
include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) }
let(:board) { create(:board, project: project) }
let(:planning) { create(:label, project: project, name: 'Planning') }
let!(:list1) { create(:list, board: board, label: planning, position: 0) }
let(:user) { create(:user) }
let(:user2) { create(:user) }
let!(:issue1) { create(:issue, project: project) }
before do
project.team << [user, :master]
login_as(user)
end
it 'shows empty state when no results found' do
visit_board
page.within('.add-issues-modal') do
find('.form-control').native.send_keys('testing empty state')
wait_for_vue_resource
expect(page).to have_content('There are no issues to show.')
end
end
it 'restores filters when closing' do
visit_board
page.within('.add-issues-modal') do
click_button 'Milestone'
wait_for_ajax
click_link 'Upcoming'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 0)
click_button 'Cancel'
end
click_button('Add issues')
page.within('.add-issues-modal') do
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
context 'author' do
let!(:issue) { create(:issue, project: project, author: user2) }
before do
project.team << [user2, :developer]
visit_board
end
it 'filters by any author' do
page.within('.add-issues-modal') do
click_button 'Author'
wait_for_ajax
click_link 'Any Author'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 2)
end
end
it 'filters by selected user' do
page.within('.add-issues-modal') do
click_button 'Author'
wait_for_ajax
click_link user2.name
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
end
context 'assignee' do
let!(:issue) { create(:issue, project: project, assignee: user2) }
before do
project.team << [user2, :developer]
visit_board
end
it 'filters by any assignee' do
page.within('.add-issues-modal') do
click_button 'Assignee'
wait_for_ajax
click_link 'Any Assignee'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 2)
end
end
it 'filters by unassigned' do
page.within('.add-issues-modal') do
click_button 'Assignee'
wait_for_ajax
click_link 'Unassigned'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
it 'filters by selected user' do
page.within('.add-issues-modal') do
click_button 'Assignee'
wait_for_ajax
page.within '.dropdown-menu-user' do
click_link user2.name
end
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
end
context 'milestone' do
let(:milestone) { create(:milestone, project: project) }
let!(:issue) { create(:issue, project: project, milestone: milestone) }
before do
visit_board
end
it 'filters by any milestone' do
page.within('.add-issues-modal') do
click_button 'Milestone'
wait_for_ajax
click_link 'Any Milestone'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 2)
end
end
it 'filters by upcoming milestone' do
page.within('.add-issues-modal') do
click_button 'Milestone'
wait_for_ajax
click_link 'Upcoming'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 0)
end
end
it 'filters by selected milestone' do
page.within('.add-issues-modal') do
click_button 'Milestone'
wait_for_ajax
click_link milestone.name
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
end
context 'label' do
let(:label) { create(:label, project: project) }
let!(:issue) { create(:labeled_issue, project: project, labels: [label]) }
before do
visit_board
end
it 'filters by any label' do
page.within('.add-issues-modal') do
click_button 'Label'
wait_for_ajax
click_link 'Any Label'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 2)
end
end
it 'filters by no label' do
page.within('.add-issues-modal') do
click_button 'Label'
wait_for_ajax
click_link 'No Label'
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
it 'filters by label' do
page.within('.add-issues-modal') do
click_button 'Label'
wait_for_ajax
click_link label.title
wait_for_vue_resource
expect(page).to have_selector('.card', count: 1)
end
end
end
def visit_board
visit namespace_project_board_path(project.namespace, project, board)
wait_for_vue_resource
click_button('Add issues')
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment