Merge branch 'issues-modal-filters' into 'master'
Issue filters in boards modal Closes #26205 See merge request !8903
This commit is contained in:
commit
c495e5f17c
14 changed files with 695 additions and 25 deletions
|
@ -0,0 +1,49 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/* 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>
|
||||
`,
|
||||
});
|
|
@ -1,12 +1,26 @@
|
|||
/* global Vue */
|
||||
|
||||
require('./tabs');
|
||||
const modalFilters = require('./filters');
|
||||
|
||||
(() => {
|
||||
const ModalStore = gl.issueBoards.ModalStore;
|
||||
|
||||
gl.issueBoards.ModalHeader = Vue.extend({
|
||||
mixins: [gl.issueBoards.ModalMixins],
|
||||
props: {
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
},
|
||||
|
@ -31,6 +45,7 @@ require('./tabs');
|
|||
},
|
||||
components: {
|
||||
'modal-tabs': gl.issueBoards.ModalTabs,
|
||||
modalFilters,
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
|
@ -51,6 +66,11 @@ require('./tabs');
|
|||
<div
|
||||
class="add-issues-search append-bottom-10"
|
||||
v-if="showSearch">
|
||||
<modal-filters
|
||||
:project-id="projectId"
|
||||
:milestone-path="milestonePath"
|
||||
:label-path="labelPath">
|
||||
</modal-filters>
|
||||
<input
|
||||
placeholder="Search issues..."
|
||||
class="form-control"
|
||||
|
|
|
@ -27,6 +27,18 @@ require('./empty_state');
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
milestonePath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
labelPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
|
@ -52,17 +64,27 @@ require('./empty_state');
|
|||
this.issuesCount = false;
|
||||
}
|
||||
},
|
||||
filter: {
|
||||
handler() {
|
||||
this.loadIssues(true);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
searchOperation: _.debounce(function searchOperationDebounce() {
|
||||
this.loadIssues(true);
|
||||
}, 500),
|
||||
loadIssues(clearIssues = false) {
|
||||
return gl.boardService.getBacklog({
|
||||
if (!this.showAddIssuesModal) return false;
|
||||
|
||||
const queryData = Object.assign({}, this.filter, {
|
||||
search: this.searchTerm,
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
}).then((res) => {
|
||||
});
|
||||
|
||||
return gl.boardService.getBacklog(queryData).then((res) => {
|
||||
const data = res.json();
|
||||
|
||||
if (clearIssues) {
|
||||
|
@ -112,8 +134,13 @@ require('./empty_state');
|
|||
class="add-issues-modal"
|
||||
v-if="showAddIssuesModal">
|
||||
<div class="add-issues-container">
|
||||
<modal-header></modal-header>
|
||||
<modal-header
|
||||
:project-id="projectId"
|
||||
:milestone-path="milestonePath"
|
||||
:label-path="labelPath">
|
||||
</modal-header>
|
||||
<modal-list
|
||||
:image="blankStateImage"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"
|
||||
v-if="!loading && showList"></modal-list>
|
||||
|
|
|
@ -14,6 +14,10 @@
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return ModalStore.store;
|
||||
|
@ -110,6 +114,19 @@
|
|||
<section
|
||||
class="add-issues-list add-issues-list-columns"
|
||||
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
|
||||
v-for="group in groupedIssues"
|
||||
class="add-issues-list-column">
|
||||
|
|
|
@ -18,6 +18,17 @@
|
|||
page: 1,
|
||||
perPage: 50,
|
||||
};
|
||||
|
||||
this.setDefaultFilter();
|
||||
}
|
||||
|
||||
setDefaultFilter() {
|
||||
this.store.filter = {
|
||||
author_id: '',
|
||||
assignee_id: '',
|
||||
milestone_title: '',
|
||||
label_name: [],
|
||||
};
|
||||
}
|
||||
|
||||
selectedCount() {
|
||||
|
|
|
@ -4,10 +4,17 @@
|
|||
|
||||
(function() {
|
||||
this.LabelsSelect = (function() {
|
||||
function LabelsSelect() {
|
||||
var _this;
|
||||
function LabelsSelect(els) {
|
||||
var _this, $els;
|
||||
_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;
|
||||
$dropdown = $(dropdown);
|
||||
$dropdownContainer = $dropdown.closest('.labels-filter');
|
||||
|
@ -324,7 +331,7 @@
|
|||
multiSelect: $dropdown.hasClass('js-multiselect'),
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: function(label, $el, e, isMarking) {
|
||||
var isIssueIndex, isMRIndex, page;
|
||||
var isIssueIndex, isMRIndex, page, boardsModel;
|
||||
|
||||
page = $('body').data('page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
|
@ -346,22 +353,31 @@
|
|||
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) {
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'] = [];
|
||||
boardsModel['label_name'] = [];
|
||||
}
|
||||
else if ($el.hasClass('is-active')) {
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'].push(label.title);
|
||||
boardsModel['label_name'].push(label.title);
|
||||
}
|
||||
else {
|
||||
var filters = gl.issueBoards.BoardsStore.state.filters['label_name'];
|
||||
var filters = boardsModel['label_name'];
|
||||
filters = filters.filter(function (filteredLabel) {
|
||||
return filteredLabel !== label.title;
|
||||
});
|
||||
gl.issueBoards.BoardsStore.state.filters['label_name'] = filters;
|
||||
boardsModel['label_name'] = filters;
|
||||
}
|
||||
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
if (!$dropdown.closest('.add-issues-modal').length) {
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
}
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -5,13 +5,20 @@
|
|||
|
||||
(function() {
|
||||
this.MilestoneSelect = (function() {
|
||||
function MilestoneSelect(currentProject) {
|
||||
var _this;
|
||||
function MilestoneSelect(currentProject, els) {
|
||||
var _this, $els;
|
||||
if (currentProject != null) {
|
||||
_this = this;
|
||||
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;
|
||||
$dropdown = $(dropdown);
|
||||
projectId = $dropdown.data('project-id');
|
||||
|
@ -108,7 +115,7 @@
|
|||
},
|
||||
vue: $dropdown.hasClass('js-issue-board-sidebar'),
|
||||
clicked: function(selected, $el, e) {
|
||||
var data, isIssueIndex, isMRIndex, page;
|
||||
var data, isIssueIndex, isMRIndex, page, boardsStore;
|
||||
page = $('body').data('page');
|
||||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = (page === page && page === 'projects:merge_requests:index');
|
||||
|
@ -116,9 +123,19 @@
|
|||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name;
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
|
||||
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();
|
||||
}
|
||||
e.preventDefault();
|
||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||
if (selected.name != null) {
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
slice = [].slice;
|
||||
|
||||
this.UsersSelect = (function() {
|
||||
function UsersSelect(currentUser) {
|
||||
function UsersSelect(currentUser, els) {
|
||||
var $els;
|
||||
this.users = bind(this.users, this);
|
||||
this.user = bind(this.user, this);
|
||||
this.usersPath = "/autocomplete/users.json";
|
||||
|
@ -20,7 +21,14 @@
|
|||
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) {
|
||||
var options = {};
|
||||
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove;
|
||||
|
@ -193,7 +201,9 @@
|
|||
selectedId = user.id;
|
||||
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;
|
||||
gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id;
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
|
|
|
@ -389,6 +389,13 @@
|
|||
flex: 1;
|
||||
margin-top: 0;
|
||||
|
||||
&.add-issues-empty-state-filter {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
> .row {
|
||||
width: 100%;
|
||||
margin: auto 0;
|
||||
|
@ -416,6 +423,14 @@
|
|||
.add-issues-search {
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
|
||||
.form-control {
|
||||
margin-left: auto;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-issues-list-column {
|
||||
|
@ -486,3 +501,24 @@
|
|||
line-height: 15px;
|
||||
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 @@
|
|||
= 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),
|
||||
"milestone-path" => milestones_filter_dropdown_path,
|
||||
"label-path" => labels_filter_path,
|
||||
":issue-link-base" => "issueLinkBase",
|
||||
":root-path" => "rootPath" }
|
||||
":root-path" => "rootPath",
|
||||
":project-id" => @project.try(:id) }
|
||||
|
|
259
spec/features/boards/modal_filter_spec.rb
Normal file
259
spec/features/boards/modal_filter_spec.rb
Normal file
|
@ -0,0 +1,259 @@
|
|||
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
|
Loading…
Reference in a new issue