Merge branch 'issue-boards-new-search-bar' into 'master'
Added filtered search bar to issue boards Closes #28312 See merge request !9757
This commit is contained in:
commit
0046ffc024
|
@ -2,6 +2,9 @@
|
||||||
/* global Vue */
|
/* global Vue */
|
||||||
/* global BoardService */
|
/* global BoardService */
|
||||||
|
|
||||||
|
import FilteredSearchBoards from './filtered_search_boards';
|
||||||
|
import eventHub from './eventhub';
|
||||||
|
|
||||||
window.Vue = require('vue');
|
window.Vue = require('vue');
|
||||||
window.Vue.use(require('vue-resource'));
|
window.Vue.use(require('vue-resource'));
|
||||||
require('./models/issue');
|
require('./models/issue');
|
||||||
|
@ -59,6 +62,14 @@ $(() => {
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
|
||||||
|
|
||||||
|
this.filterManager = new FilteredSearchBoards(Store.filter, true);
|
||||||
|
|
||||||
|
// Listen for updateTokens event
|
||||||
|
eventHub.$on('updateTokens', this.updateTokens);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
eventHub.$off('updateTokens', this.updateTokens);
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
Store.disabled = this.disabled;
|
Store.disabled = this.disabled;
|
||||||
|
@ -77,11 +88,16 @@ $(() => {
|
||||||
Store.addBlankState();
|
Store.addBlankState();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
methods: {
|
||||||
|
updateTokens() {
|
||||||
|
this.filterManager.updateTokens();
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
gl.IssueBoardsSearch = new Vue({
|
gl.IssueBoardsSearch = new Vue({
|
||||||
el: document.getElementById('js-boards-search'),
|
el: document.getElementById('js-add-list'),
|
||||||
data: {
|
data: {
|
||||||
filters: Store.state.filters
|
filters: Store.state.filters
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,16 +28,16 @@ require('./board_list');
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
detailIssue: Store.detail,
|
detailIssue: Store.detail,
|
||||||
filters: Store.state.filters,
|
filter: Store.filter,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
filters: {
|
filter: {
|
||||||
handler () {
|
handler() {
|
||||||
this.list.page = 1;
|
this.list.page = 1;
|
||||||
this.list.getIssues(true);
|
this.list.getIssues(true);
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true,
|
||||||
},
|
},
|
||||||
detailIssue: {
|
detailIssue: {
|
||||||
handler () {
|
handler () {
|
||||||
|
|
|
@ -17,7 +17,8 @@ export default {
|
||||||
:list="list"
|
:list="list"
|
||||||
:issue="issue"
|
:issue="issue"
|
||||||
:issue-link-base="issueLinkBase"
|
:issue-link-base="issueLinkBase"
|
||||||
:root-path="rootPath" />
|
:root-path="rootPath"
|
||||||
|
:update-filters="true" />
|
||||||
</li>
|
</li>
|
||||||
`,
|
`,
|
||||||
components: {
|
components: {
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
/* global Vue */
|
/* global Vue */
|
||||||
|
import eventHub from '../eventhub';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const Store = gl.issueBoards.BoardsStore;
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
|
||||||
|
@ -23,6 +25,11 @@
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
updateFilters: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showLabel(label) {
|
showLabel(label) {
|
||||||
|
@ -31,29 +38,25 @@
|
||||||
return !this.list.label || label.id !== this.list.label.id;
|
return !this.list.label || label.id !== this.list.label.id;
|
||||||
},
|
},
|
||||||
filterByLabel(label, e) {
|
filterByLabel(label, e) {
|
||||||
let labelToggleText = label.title;
|
if (!this.updateFilters) return;
|
||||||
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
|
|
||||||
|
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
|
||||||
|
const labelTitle = encodeURIComponent(label.title);
|
||||||
|
const param = `label_name[]=${labelTitle}`;
|
||||||
|
const labelIndex = filterPath.indexOf(param);
|
||||||
$(e.currentTarget).tooltip('hide');
|
$(e.currentTarget).tooltip('hide');
|
||||||
|
|
||||||
if (labelIndex === -1) {
|
if (labelIndex === -1) {
|
||||||
Store.state.filters.label_name.push(label.title);
|
filterPath.push(param);
|
||||||
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
|
|
||||||
} else {
|
} else {
|
||||||
Store.state.filters.label_name.splice(labelIndex, 1);
|
filterPath.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;
|
gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
|
||||||
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();
|
Store.updateFiltersUrl();
|
||||||
|
|
||||||
|
eventHub.$emit('updateTokens');
|
||||||
},
|
},
|
||||||
labelStyle(label) {
|
labelStyle(label) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
|
||||||
|
export default new Vue();
|
|
@ -0,0 +1,34 @@
|
||||||
|
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
|
||||||
|
constructor(store, updateUrl = false) {
|
||||||
|
super('boards');
|
||||||
|
|
||||||
|
this.store = store;
|
||||||
|
this.updateUrl = updateUrl;
|
||||||
|
|
||||||
|
// Issue boards is slightly different, we handle all the requests async
|
||||||
|
// instead or reloading the page, we just re-fire the list ajax requests
|
||||||
|
this.isHandledAsync = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateObject(path) {
|
||||||
|
this.store.path = path.substr(1);
|
||||||
|
|
||||||
|
if (this.updateUrl) {
|
||||||
|
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTokens() {
|
||||||
|
const tokens = document.querySelectorAll('.js-visual-token');
|
||||||
|
|
||||||
|
// Remove all the tokens as they will be replaced by the search manager
|
||||||
|
[].forEach.call(tokens, (el) => {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadSearchParamsFromURL();
|
||||||
|
|
||||||
|
// Get the placeholder back if search is empty
|
||||||
|
this.filteredSearchInput.dispatchEvent(new Event('input'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ class List {
|
||||||
this.title = obj.title;
|
this.title = obj.title;
|
||||||
this.type = obj.list_type;
|
this.type = obj.list_type;
|
||||||
this.preset = ['done', 'blank'].indexOf(this.type) > -1;
|
this.preset = ['done', 'blank'].indexOf(this.type) > -1;
|
||||||
this.filters = gl.issueBoards.BoardsStore.state.filters;
|
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.loadingMore = false;
|
this.loadingMore = false;
|
||||||
|
@ -65,12 +64,27 @@ class List {
|
||||||
}
|
}
|
||||||
|
|
||||||
getIssues (emptyIssues = true) {
|
getIssues (emptyIssues = true) {
|
||||||
const filters = this.filters;
|
const data = gl.issueBoards.BoardsStore.filter.path.split('&').reduce((data, filterParam) => {
|
||||||
const data = { page: this.page };
|
if (filterParam === '') return data;
|
||||||
|
const paramSplit = filterParam.split('=');
|
||||||
|
const paramKeyNormalized = paramSplit[0].replace('[]', '');
|
||||||
|
const isArray = paramSplit[0].indexOf('[]');
|
||||||
|
const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
|
||||||
|
|
||||||
Object.keys(filters).forEach((key) => { data[key] = filters[key]; });
|
if (isArray !== -1) {
|
||||||
|
if (!data[paramKeyNormalized]) {
|
||||||
|
data[paramKeyNormalized] = [];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.label) {
|
data[paramKeyNormalized].push(value);
|
||||||
|
} else {
|
||||||
|
data[paramKeyNormalized] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}, { page: this.page });
|
||||||
|
|
||||||
|
if (this.label && data.label_name) {
|
||||||
data.label_name = data.label_name.filter(label => label !== this.label.title);
|
data.label_name = data.label_name.filter(label => label !== this.label.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
gl.issueBoards.BoardsStore = {
|
gl.issueBoards.BoardsStore = {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
filter: {
|
||||||
|
path: '',
|
||||||
|
},
|
||||||
state: {},
|
state: {},
|
||||||
detail: {
|
detail: {
|
||||||
issue: {}
|
issue: {}
|
||||||
|
@ -18,13 +21,7 @@
|
||||||
},
|
},
|
||||||
create () {
|
create () {
|
||||||
this.state.lists = [];
|
this.state.lists = [];
|
||||||
this.state.filters = {
|
this.filter.path = gl.utils.getUrlParamsArray().join('&');
|
||||||
author_id: gl.utils.getParameterValues('author_id')[0],
|
|
||||||
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
|
|
||||||
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
|
|
||||||
label_name: gl.utils.getParameterValues('label_name[]'),
|
|
||||||
search: ''
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
addList (listObj) {
|
addList (listObj) {
|
||||||
const list = new List(listObj);
|
const list = new List(listObj);
|
||||||
|
@ -123,7 +120,7 @@
|
||||||
})[0];
|
})[0];
|
||||||
},
|
},
|
||||||
updateFiltersUrl () {
|
updateFiltersUrl () {
|
||||||
history.pushState(null, null, `?${$.param(this.state.filters)}`);
|
history.pushState(null, null, `?${this.filter.path}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -106,8 +106,15 @@
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!activeElements.length) {
|
if (!activeElements.length) {
|
||||||
// Prevent droplab from opening dropdown
|
if (this.isHandledAsync) {
|
||||||
this.dropdownManager.destroyDroplab();
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
this.filteredSearchInput.blur();
|
||||||
|
this.dropdownManager.resetDropdowns();
|
||||||
|
} else {
|
||||||
|
// Prevent droplab from opening dropdown
|
||||||
|
this.dropdownManager.destroyDroplab();
|
||||||
|
}
|
||||||
|
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
|
@ -200,6 +207,10 @@
|
||||||
this.handleInputPlaceholder();
|
this.handleInputPlaceholder();
|
||||||
|
|
||||||
this.dropdownManager.resetDropdowns();
|
this.dropdownManager.resetDropdowns();
|
||||||
|
|
||||||
|
if (this.isHandledAsync) {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInputVisualToken() {
|
handleInputVisualToken() {
|
||||||
|
@ -346,7 +357,11 @@
|
||||||
|
|
||||||
const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
|
const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
|
||||||
|
|
||||||
gl.utils.visitUrl(parameterizedUrl);
|
if (this.updateObject) {
|
||||||
|
this.updateObject(parameterizedUrl);
|
||||||
|
} else {
|
||||||
|
gl.utils.visitUrl(parameterizedUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsernameParams() {
|
getUsernameParams() {
|
||||||
|
|
|
@ -353,31 +353,17 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
|
if ($dropdown.closest('.add-issues-modal').length) {
|
||||||
!$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;
|
boardsModel = gl.issueBoards.ModalStore.store.filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boardsModel) {
|
if (boardsModel) {
|
||||||
if (label.isAny) {
|
if (label.isAny) {
|
||||||
boardsModel['label_name'] = [];
|
boardsModel['label_name'] = [];
|
||||||
}
|
} else if ($el.hasClass('is-active')) {
|
||||||
else if ($el.hasClass('is-active')) {
|
|
||||||
boardsModel['label_name'].push(label.title);
|
boardsModel['label_name'].push(label.title);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
var filters = boardsModel['label_name'];
|
|
||||||
filters = filters.filter(function (filteredLabel) {
|
|
||||||
return filteredLabel !== label.title;
|
|
||||||
});
|
|
||||||
boardsModel['label_name'] = filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$dropdown.closest('.add-issues-modal').length) {
|
|
||||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,18 +124,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') &&
|
if ($dropdown.closest('.add-issues-modal').length) {
|
||||||
!$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;
|
boardsStore = gl.issueBoards.ModalStore.store.filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boardsStore) {
|
if (boardsStore) {
|
||||||
boardsStore[$dropdown.data('field-name')] = selected.name;
|
boardsStore[$dropdown.data('field-name')] = selected.name;
|
||||||
if (!$dropdown.closest('.add-issues-modal').length) {
|
|
||||||
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) {
|
||||||
|
|
|
@ -217,11 +217,6 @@
|
||||||
}
|
}
|
||||||
if ($el.closest('.add-issues-modal').length) {
|
if ($el.closest('.add-issues-modal').length) {
|
||||||
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
|
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();
|
|
||||||
e.preventDefault();
|
|
||||||
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
|
||||||
selectedId = user.id;
|
selectedId = user.id;
|
||||||
return Issuable.filterResults($dropdown.closest('form'));
|
return Issuable.filterResults($dropdown.closest('form'));
|
||||||
|
|
|
@ -156,7 +156,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid $border-color;
|
border: 1px solid $border-color;
|
||||||
background-color: $white-light;
|
background-color: $white-light;
|
||||||
max-width: 87%;
|
|
||||||
|
|
||||||
@media (max-width: $screen-xs-min) {
|
@media (max-width: $screen-xs-min) {
|
||||||
-webkit-flex: 1 1 100%;
|
-webkit-flex: 1 1 100%;
|
||||||
|
@ -219,6 +218,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-dropdown-container {
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.dropdown-menu .filter-dropdown-item {
|
.dropdown-menu .filter-dropdown-item {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
- content_for :page_specific_javascripts do
|
- content_for :page_specific_javascripts do
|
||||||
= page_specific_javascript_bundle_tag('common_vue')
|
= page_specific_javascript_bundle_tag('common_vue')
|
||||||
|
= page_specific_javascript_bundle_tag('filtered_search')
|
||||||
= page_specific_javascript_bundle_tag('boards')
|
= page_specific_javascript_bundle_tag('boards')
|
||||||
= page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
|
= page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
|
||||||
|
|
||||||
|
@ -12,7 +13,8 @@
|
||||||
|
|
||||||
= render "projects/issues/head"
|
= render "projects/issues/head"
|
||||||
|
|
||||||
= render 'shared/issuable/filter', type: :boards
|
.hidden-xs.hidden-sm
|
||||||
|
= render 'shared/issuable/search_bar', type: :boards
|
||||||
|
|
||||||
#board-app.boards-app{ "v-cloak" => true, data: board_data }
|
#board-app.boards-app{ "v-cloak" => true, data: board_data }
|
||||||
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
|
.boards-list{ ":class" => "{ 'is-compact': detailIssueVisible }" }
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
- finder = controller.controller_name == 'issues' || controller.controller_name == 'boards' ? issues_finder : merge_requests_finder
|
- finder = controller.controller_name == 'issues' ? issues_finder : merge_requests_finder
|
||||||
- boards_page = controller.controller_name == 'boards'
|
- boards_page = controller.controller_name == 'boards'
|
||||||
|
|
||||||
.issues-filters
|
.issues-filters
|
||||||
|
@ -34,21 +34,7 @@
|
||||||
%a{ href: page_filter_path(without: issuable_filter_params) } Reset filters
|
%a{ href: page_filter_path(without: issuable_filter_params) } Reset filters
|
||||||
|
|
||||||
.pull-right
|
.pull-right
|
||||||
- if boards_page
|
= render 'shared/sort_dropdown'
|
||||||
#js-boards-search.issue-boards-search
|
|
||||||
%input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" }
|
|
||||||
- if can?(current_user, :admin_list, @project)
|
|
||||||
#js-add-issues-btn.pull-right.prepend-left-10
|
|
||||||
.dropdown.pull-right
|
|
||||||
%button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } }
|
|
||||||
Add list
|
|
||||||
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
|
|
||||||
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
|
|
||||||
- if can?(current_user, :admin_label, @project)
|
|
||||||
= render partial: "shared/issuable/label_page_create"
|
|
||||||
= dropdown_loading
|
|
||||||
- else
|
|
||||||
= render 'shared/sort_dropdown'
|
|
||||||
|
|
||||||
- if @bulk_edit
|
- if @bulk_edit
|
||||||
.issues_bulk_update.hide
|
.issues_bulk_update.hide
|
||||||
|
|
|
@ -85,8 +85,20 @@
|
||||||
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
||||||
%span.label-title.js-data-value
|
%span.label-title.js-data-value
|
||||||
{{title}}
|
{{title}}
|
||||||
.pull-right.filter-dropdown-container
|
.filter-dropdown-container
|
||||||
= render 'shared/sort_dropdown'
|
- if type == :boards
|
||||||
|
- if can?(current_user, :admin_list, @project)
|
||||||
|
.dropdown.prepend-left-10#js-add-list
|
||||||
|
%button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: { toggle: "dropdown", labels: labels_filter_path, namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path) } }
|
||||||
|
Add list
|
||||||
|
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
|
||||||
|
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
|
||||||
|
- if can?(current_user, :admin_label, @project)
|
||||||
|
= render partial: "shared/issuable/label_page_create"
|
||||||
|
= dropdown_loading
|
||||||
|
#js-add-issues-btn.prepend-left-10
|
||||||
|
- else
|
||||||
|
= render 'shared/sort_dropdown'
|
||||||
|
|
||||||
- if @bulk_edit
|
- if @bulk_edit
|
||||||
.issues_bulk_update.hide
|
.issues_bulk_update.hide
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
title: Added new filtered search bar to issue boards
|
||||||
|
merge_request:
|
||||||
|
author:
|
|
@ -51,7 +51,7 @@ describe 'Issue Boards add issue modal', :feature, :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not show tooltip on add issues button' do
|
it 'does not show tooltip on add issues button' do
|
||||||
button = page.find('.issue-boards-search button', text: 'Add issues')
|
button = page.find('.filter-dropdown-container button', text: 'Add issues')
|
||||||
|
|
||||||
expect(button[:title]).not_to eq("Please add a list to your board first")
|
expect(button[:title]).not_to eq("Please add a list to your board first")
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows tooltip on add issues button' do
|
it 'shows tooltip on add issues button' do
|
||||||
button = page.find('.issue-boards-search button', text: 'Add issues')
|
button = page.find('.filter-dropdown-container button', text: 'Add issues')
|
||||||
|
|
||||||
expect(button[:"data-original-title"]).to eq("Please add a list to your board first")
|
expect(button[:"data-original-title"]).to eq("Please add a list to your board first")
|
||||||
end
|
end
|
||||||
|
@ -115,9 +115,8 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'search done list' do
|
it 'search done list' do
|
||||||
page.within('#js-boards-search') do
|
find('.filtered-search').set(issue8.title)
|
||||||
find('.form-control').set(issue8.title)
|
find('.filtered-search').native.send_keys(:enter)
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
@ -127,9 +126,8 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'search list' do
|
it 'search list' do
|
||||||
page.within('#js-boards-search') do
|
find('.filtered-search').set(issue5.title)
|
||||||
find('.form-control').set(issue5.title)
|
find('.filtered-search').native.send_keys(:enter)
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
@ -333,7 +331,7 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
expect(find('.issue-boards-search')).to have_selector('.open')
|
expect(page).to have_css('#js-add-list.open')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates new list from a new label' do
|
it 'creates new list from a new label' do
|
||||||
|
@ -359,17 +357,9 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
|
|
||||||
context 'filtering' do
|
context 'filtering' do
|
||||||
it 'filters by author' do
|
it 'filters by author' do
|
||||||
page.within '.issues-filters' do
|
set_filter("author", user2.username)
|
||||||
click_button('Author')
|
click_filter_link(user2.username)
|
||||||
wait_for_ajax
|
submit_filter
|
||||||
|
|
||||||
page.within '.dropdown-menu-author' do
|
|
||||||
click_link(user2.name)
|
|
||||||
end
|
|
||||||
wait_for_vue_resource
|
|
||||||
|
|
||||||
expect(find('.js-author-search')).to have_content(user2.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
wait_for_board_cards(1, 1)
|
wait_for_board_cards(1, 1)
|
||||||
|
@ -377,17 +367,9 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters by assignee' do
|
it 'filters by assignee' do
|
||||||
page.within '.issues-filters' do
|
set_filter("assignee", user.username)
|
||||||
click_button('Assignee')
|
click_filter_link(user.username)
|
||||||
wait_for_ajax
|
submit_filter
|
||||||
|
|
||||||
page.within '.dropdown-menu-assignee' do
|
|
||||||
click_link(user.name)
|
|
||||||
end
|
|
||||||
wait_for_vue_resource
|
|
||||||
|
|
||||||
expect(find('.js-assignee-search')).to have_content(user.name)
|
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
@ -396,17 +378,9 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters by milestone' do
|
it 'filters by milestone' do
|
||||||
page.within '.issues-filters' do
|
set_filter("milestone", "\"#{milestone.title}\"")
|
||||||
click_button('Milestone')
|
click_filter_link(milestone.title)
|
||||||
wait_for_ajax
|
submit_filter
|
||||||
|
|
||||||
page.within '.milestone-filter' do
|
|
||||||
click_link(milestone.title)
|
|
||||||
end
|
|
||||||
wait_for_vue_resource
|
|
||||||
|
|
||||||
expect(find('.js-milestone-select')).to have_content(milestone.title)
|
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
wait_for_board_cards(1, 1)
|
wait_for_board_cards(1, 1)
|
||||||
|
@ -415,16 +389,9 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters by label' do
|
it 'filters by label' do
|
||||||
page.within '.issues-filters' do
|
set_filter("label", testing.title)
|
||||||
click_button('Label')
|
click_filter_link(testing.title)
|
||||||
wait_for_ajax
|
submit_filter
|
||||||
|
|
||||||
page.within '.dropdown-menu-labels' do
|
|
||||||
click_link(testing.title)
|
|
||||||
wait_for_vue_resource
|
|
||||||
find('.dropdown-menu-close').click
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
wait_for_board_cards(1, 1)
|
wait_for_board_cards(1, 1)
|
||||||
|
@ -432,19 +399,14 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters by label with space after reload' do
|
it 'filters by label with space after reload' do
|
||||||
page.within '.issues-filters' do
|
set_filter("label", "\"#{accepting.title}\"")
|
||||||
click_button('Label')
|
click_filter_link(accepting.title)
|
||||||
wait_for_ajax
|
submit_filter
|
||||||
|
|
||||||
page.within '.dropdown-menu-labels' do
|
|
||||||
click_link(accepting.title)
|
|
||||||
wait_for_vue_resource(spinner: false)
|
|
||||||
find('.dropdown-menu-close').click
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Test after reload
|
# Test after reload
|
||||||
page.evaluate_script 'window.location.reload()'
|
page.evaluate_script 'window.location.reload()'
|
||||||
|
wait_for_board_cards(1, 1)
|
||||||
|
wait_for_empty_boards((2..3))
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
@ -460,26 +422,16 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes filtered labels' do
|
it 'removes filtered labels' do
|
||||||
wait_for_vue_resource
|
set_filter("label", testing.title)
|
||||||
|
click_filter_link(testing.title)
|
||||||
|
submit_filter
|
||||||
|
|
||||||
page.within '.labels-filter' do
|
wait_for_board_cards(1, 1)
|
||||||
click_button('Label')
|
|
||||||
wait_for_ajax
|
|
||||||
|
|
||||||
page.within '.dropdown-menu-labels' do
|
find('.clear-search').click
|
||||||
click_link(testing.title)
|
submit_filter
|
||||||
wait_for_vue_resource(spinner: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(page).to have_css('input[name="label_name[]"]', visible: false)
|
wait_for_board_cards(1, 8)
|
||||||
|
|
||||||
page.within '.dropdown-menu-labels' do
|
|
||||||
click_link(testing.title)
|
|
||||||
wait_for_vue_resource(spinner: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(page).not_to have_css('input[name="label_name[]"]', visible: false)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'infinite scrolls list with label filter' do
|
it 'infinite scrolls list with label filter' do
|
||||||
|
@ -487,16 +439,9 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
create(:labeled_issue, project: project, labels: [planning, testing])
|
create(:labeled_issue, project: project, labels: [planning, testing])
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.issues-filters' do
|
set_filter("label", testing.title)
|
||||||
click_button('Label')
|
click_filter_link(testing.title)
|
||||||
wait_for_ajax
|
submit_filter
|
||||||
|
|
||||||
page.within '.dropdown-menu-labels' do
|
|
||||||
click_link(testing.title)
|
|
||||||
wait_for_vue_resource
|
|
||||||
find('.dropdown-menu-close').click
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
@ -518,18 +463,13 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters by multiple labels' do
|
it 'filters by multiple labels' do
|
||||||
page.within '.issues-filters' do
|
set_filter("label", testing.title)
|
||||||
click_button('Label')
|
click_filter_link(testing.title)
|
||||||
wait_for_ajax
|
|
||||||
|
|
||||||
page.within(find('.dropdown-menu-labels')) do
|
set_filter("label", bug.title)
|
||||||
click_link(testing.title)
|
click_filter_link(bug.title)
|
||||||
wait_for_vue_resource
|
|
||||||
click_link(bug.title)
|
submit_filter
|
||||||
wait_for_vue_resource
|
|
||||||
find('.dropdown-menu-close').click
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
@ -545,14 +485,14 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
end
|
end
|
||||||
|
|
||||||
|
page.within('.tokens-container') do
|
||||||
|
expect(page).to have_content(bug.title)
|
||||||
|
end
|
||||||
|
|
||||||
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..3))
|
wait_for_empty_boards((2..3))
|
||||||
|
|
||||||
page.within('.labels-filter') do
|
|
||||||
expect(find('.dropdown-toggle-text')).to have_content(bug.title)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes label filter by clicking label button on issue' do
|
it 'removes label filter by clicking label button on issue' do
|
||||||
|
@ -560,16 +500,13 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
page.within(find('.card', match: :first)) do
|
page.within(find('.card', match: :first)) do
|
||||||
click_button(bug.title)
|
click_button(bug.title)
|
||||||
end
|
end
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
expect(page).to have_selector('.card', count: 1)
|
expect(page).to have_selector('.card', count: 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
wait_for_vue_resource
|
wait_for_vue_resource
|
||||||
|
|
||||||
page.within('.labels-filter') do
|
|
||||||
expect(find('.dropdown-toggle-text')).to have_content(bug.title)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -643,4 +580,20 @@ describe 'Issue Boards', feature: true, js: true do
|
||||||
wait_for_board_cards(board, 0)
|
wait_for_board_cards(board, 0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_filter(type, text)
|
||||||
|
find('.filtered-search').native.send_keys("#{type}:#{text}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def submit_filter
|
||||||
|
find('.filtered-search').native.send_keys(:enter)
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_filter_link(link_text)
|
||||||
|
page.within('.filtered-search-input-container') do
|
||||||
|
expect(page).to have_button(link_text)
|
||||||
|
|
||||||
|
click_button(link_text)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -176,7 +176,7 @@ describe 'Projects > Issuables > Default sort order', feature: true do
|
||||||
end
|
end
|
||||||
|
|
||||||
def selected_sort_order
|
def selected_sort_order
|
||||||
find('.pull-right .dropdown button').text.downcase
|
find('.filter-dropdown-container .dropdown button').text.downcase
|
||||||
end
|
end
|
||||||
|
|
||||||
def visit_merge_requests_with_state(project, state)
|
def visit_merge_requests_with_state(project, state)
|
||||||
|
|
Loading…
Reference in New Issue