Commit 86cd022a authored by Clement Ho's avatar Clement Ho

Merge branch '4032-add-filtered-search-epics' into 'master'

Filtered search support for Epics list page

Closes #4656

See merge request gitlab-org/gitlab-ee!4223
parents e6b32ce9 bb250cb8
/* eslint-disable class-methods-use-this */
import FilteredSearchTokenKeysIssues from 'ee/filtered_search/filtered_search_token_keys_issues';
import FilteredSearchContainer from '../filtered_search/container';
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
super('boards');
super({
page: 'boards',
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
stateFiltersSelector: '.issues-state-filters',
});
this.store = store;
this.updateUrl = updateUrl;
......
......@@ -178,6 +178,11 @@ var Dispatcher;
case 'groups:epics:show':
new ZenMode();
break;
case 'groups:epics:index':
import(/* webpackChunkName: "ee_epics_show" */ 'ee/pages/epics')
.then(callDefault)
.catch(fail);
break;
case 'projects:compare:show':
import('./pages/projects/compare/show')
.then(callDefault)
......
......@@ -3,12 +3,8 @@ import './dropdown_hint';
import './dropdown_non_user';
import './dropdown_user';
import './dropdown_utils';
import './filtered_search_token_keys';
import './filtered_search_dropdown_manager';
import './filtered_search_dropdown';
import './filtered_search_manager';
import './filtered_search_tokenizer';
import './filtered_search_visual_tokens';
// EE-only
import './filtered_search_token_keys_issues_ee';
......@@ -3,19 +3,15 @@ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', tokenizer, page, isGroup) {
constructor(baseEndpoint = '', tokenizer, page, isGroup, filteredSearchTokenKeys) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
this.groupsOnly = page === 'boards' && isGroup;
if (this.page === 'issues' || this.page === 'boards') {
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeysIssuesEE;
}
this.setupMapping();
this.cleanupWrapper = this.cleanup.bind(this);
......@@ -34,12 +30,30 @@ class FilteredSearchDropdownManager {
}
setupMapping() {
this.mapping = {
const supportedTokens = this.filteredSearchTokenKeys.getKeys();
const allowedMappings = {
hint: {
reference: null,
gl: 'DropdownHint',
element: this.container.querySelector('#js-dropdown-hint'),
},
};
const availableMappings = {
author: {
reference: null,
gl: 'DropdownUser',
element: this.container.querySelector('#js-dropdown-author'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json${this.groupsOnly ? '?only_group_labels=true' : ''}`,
symbol: '~',
preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing,
},
element: this.container.querySelector('#js-dropdown-label'),
},
assignee: {
reference: null,
gl: 'DropdownUser',
......@@ -54,35 +68,25 @@ class FilteredSearchDropdownManager {
},
element: this.container.querySelector('#js-dropdown-milestone'),
},
label: {
reference: null,
gl: 'DropdownNonUser',
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json${this.groupsOnly ? '?only_group_labels=true' : ''}`,
symbol: '~',
preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing,
},
element: this.container.querySelector('#js-dropdown-label'),
},
'my-reaction': {
reference: null,
gl: 'DropdownEmoji',
element: this.container.querySelector('#js-dropdown-my-reaction'),
},
hint: {
weight: {
reference: null,
gl: 'DropdownHint',
element: this.container.querySelector('#js-dropdown-hint'),
gl: 'DropdownNonUser',
element: this.container.querySelector('#js-dropdown-weight'),
},
};
if (this.page === 'issues' || this.page === 'boards') {
this.mapping.weight = {
reference: null,
gl: 'DropdownNonUser',
element: this.container.querySelector('#js-dropdown-weight'),
};
}
supportedTokens.forEach((type) => {
if (availableMappings[type]) {
allowedMappings[type] = availableMappings[type];
}
});
this.mapping = allowedMappings;
}
static addWordToInput(tokenName, tokenValue = '', clicked = false) {
......
......@@ -3,28 +3,36 @@ import { visitUrl } from '../lib/utils/url_utility';
import Flash from '../flash';
import FilteredSearchContainer from './container';
import RecentSearchesRoot from './recent_searches_root';
import FilteredSearchTokenKeys from './filtered_search_token_keys';
import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub';
import { addClassIfElementExists } from '../lib/utils/dom_utils';
class FilteredSearchManager {
constructor(page) {
constructor({
page,
filteredSearchTokenKeys = FilteredSearchTokenKeys,
stateFiltersSelector = '.issues-state-filters',
}) {
this.isGroup = false;
this.states = ['opened', 'closed', 'merged', 'all'];
this.page = page;
this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.filteredSearchInputForm = this.filteredSearchInput.form;
this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
gl.FilteredSearchTokenKeysIssuesEE.init({
multipleAssignees: this.filteredSearchInput.dataset.multipleAssignees,
});
this.filteredSearchTokenKeys = filteredSearchTokenKeys;
this.stateFiltersSelector = stateFiltersSelector;
this.recentsStorageKeyNames = {
issues: 'issue-recent-searches',
merge_requests: 'merge-request-recent-searches',
};
if (this.page === 'issues' || this.page === 'boards') {
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeysIssuesEE;
}
// EE specific setup
this.initEE();
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
......@@ -33,14 +41,30 @@ class FilteredSearchManager {
this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
const fullPath = this.searchHistoryDropdownElement ?
this.searchHistoryDropdownElement.dataset.fullPath : 'project';
let recentSearchesPagePrefix = 'issue-recent-searches';
if (this.page === 'merge_requests') {
recentSearchesPagePrefix = 'merge-request-recent-searches';
}
const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`;
const recentSearchesKey = `${fullPath}-${this.recentsStorageKeyNames[this.page]}`;
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
}
/**
* Do EE specific initializations
*/
initEE() {
// Setup token keys for multiple-assignees support
if (typeof this.filteredSearchTokenKeys.init === 'function') {
this.filteredSearchTokenKeys.init({
multipleAssignees: this.filteredSearchInput.dataset.multipleAssignees,
});
}
// Add localStorage key name for Epics recent searches
this.recentsStorageKeyNames.epics = 'epics-recent-searches';
// Update `isGroup` from DOM info
if (this.filteredSearchInput) {
this.isGroup = !!this.filteredSearchInput.getAttribute('data-group-id');
}
}
setup() {
// Fetch recent searches from localStorage
this.fetchingRecentSearchesPromise = this.recentSearchesService.fetch()
......@@ -70,7 +94,8 @@ class FilteredSearchManager {
this.filteredSearchInput.getAttribute('data-base-endpoint') || '',
this.tokenizer,
this.page,
Boolean(this.filteredSearchInput.getAttribute('data-group-id')),
this.isGroup,
this.filteredSearchTokenKeys,
);
this.recentSearchesRoot = new RecentSearchesRoot(
......@@ -99,40 +124,33 @@ class FilteredSearchManager {
}
bindStateEvents() {
this.stateFilters = document.querySelector('.container-fluid .issues-state-filters');
this.stateFilters = document.querySelector(`.container-fluid ${this.stateFiltersSelector}`);
if (this.stateFilters) {
this.searchStateWrapper = this.searchState.bind(this);
this.stateFilters.querySelector('[data-state="opened"]')
.addEventListener('click', this.searchStateWrapper);
this.stateFilters.querySelector('[data-state="closed"]')
.addEventListener('click', this.searchStateWrapper);
this.stateFilters.querySelector('[data-state="all"]')
.addEventListener('click', this.searchStateWrapper);
this.mergedState = this.stateFilters.querySelector('[data-state="merged"]');
if (this.mergedState) {
this.mergedState.addEventListener('click', this.searchStateWrapper);
}
this.applyToStateFilters((filterEl) => {
filterEl.addEventListener('click', this.searchStateWrapper);
});
}
}
unbindStateEvents() {
if (this.stateFilters) {
this.stateFilters.querySelector('[data-state="opened"]')
.removeEventListener('click', this.searchStateWrapper);
this.stateFilters.querySelector('[data-state="closed"]')
.removeEventListener('click', this.searchStateWrapper);
this.stateFilters.querySelector('[data-state="all"]')
.removeEventListener('click', this.searchStateWrapper);
if (this.mergedState) {
this.mergedState.removeEventListener('click', this.searchStateWrapper);
}
this.applyToStateFilters((filterEl) => {
filterEl.removeEventListener('click', this.searchStateWrapper);
});
}
}
applyToStateFilters(callback) {
this.stateFilters.querySelectorAll('a[data-state]').forEach((filterEl) => {
if (this.states.indexOf(filterEl.dataset.state) > -1) {
callback(filterEl);
}
});
}
bindEvents() {
this.handleFormSubmit = this.handleFormSubmit.bind(this);
this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
......
......@@ -71,7 +71,7 @@ const conditions = [{
value: 'none',
}];
class FilteredSearchTokenKeys {
export default class FilteredSearchTokenKeys {
static get() {
return tokenKeys;
}
......@@ -121,6 +121,3 @@ class FilteredSearchTokenKeys {
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import FilteredSearchTokenKeysIssues from 'ee/filtered_search/filtered_search_token_keys_issues';
export default () => {
initFilteredSearch(FILTERED_SEARCH.ISSUES);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
});
projectSelect();
};
......@@ -3,6 +3,8 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
export default () => {
initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
});
projectSelect();
};
......@@ -6,9 +6,13 @@ import UsersSelect from '~/users_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
import FilteredSearchTokenKeysIssues from 'ee/filtered_search/filtered_search_token_keys_issues';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.ISSUES);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
});
new IssuableIndex(ISSUABLE_INDEX.ISSUE);
new ShortcutsNavigation();
......
......@@ -6,7 +6,9 @@ import { FILTERED_SEARCH } from '~/pages/constants';
import { ISSUABLE_INDEX } from '~/pages/projects/constants';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch(FILTERED_SEARCH.MERGE_REQUESTS);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
});
new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new
new ShortcutsNavigation(); // eslint-disable-line no-new
new UsersSelect(); // eslint-disable-line no-new
......
export default (page) => {
export default ({ page, filteredSearchTokenKeys, stateFiltersSelector }) => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager(page);
const filteredSearchManager = new gl.FilteredSearchManager({
page,
filteredSearchTokenKeys,
stateFiltersSelector,
});
filteredSearchManager.setup();
}
};
/* eslint-disable class-methods-use-this */
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
const AUTHOR_PARAM_KEY = 'author_username';
export default class FilteredSearchServiceDesk extends gl.FilteredSearchManager {
constructor(supportBotData) {
super('service_desk');
super({
page: 'service_desk',
filteredSearchTokenKeys: FilteredSearchTokenKeys,
});
this.supportBotData = supportBotData;
}
......
---
title: Filtered search support for Epics list page
merge_request: 4223
author:
type: added
const tokenKeys = [{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
icon: 'pencil',
tag: '@author',
}];
const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const conditions = [{
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
}];
export default class FilteredSearchTokenKeysEpics {
static get() {
return tokenKeys;
}
static getKeys() {
return tokenKeys.map(i => i.key);
}
static getAlternatives() {
return alternativeTokenKeys;
}
static getConditions() {
return conditions;
}
static searchByKey(key) {
return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
static searchBySymbol(symbol) {
return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
static searchByKeyParam(keyParam) {
return tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
// e.g. 'my-reaction' => 'my_reaction'
tokenKeyParam = tokenKeyParam.replace('-', '_');
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
return keyParam === tokenKeyParam;
}) || null;
}
static searchByConditionUrl(url) {
return conditions.find(condition => condition.url === url) || null;
}
static searchByConditionKeyValue(key, value) {
return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
import './filtered_search_token_keys';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
const weightTokenKey = {
key: 'weight',
......@@ -26,7 +26,7 @@ const alternativeTokenKeys = [{
symbol: '@',
}];
class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
export default class FilteredSearchTokenKeysIssues extends FilteredSearchTokenKeys {
static init(availableFeatures) {
this.availableFeatures = availableFeatures;
}
......@@ -46,7 +46,7 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
}
static getKeys() {
const tokenKeys = FilteredSearchTokenKeysIssuesEE.get();
const tokenKeys = FilteredSearchTokenKeysIssues.get();
return tokenKeys.map(i => i.key);
}
......@@ -60,18 +60,18 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
}
static searchByKey(key) {
const tokenKeys = FilteredSearchTokenKeysIssuesEE.get();
const tokenKeys = FilteredSearchTokenKeysIssues.get();
return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
}
static searchBySymbol(symbol) {
const tokenKeys = FilteredSearchTokenKeysIssuesEE.get();
const tokenKeys = FilteredSearchTokenKeysIssues.get();
return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
}
static searchByKeyParam(keyParam) {
const tokenKeys = FilteredSearchTokenKeysIssuesEE.get();
const alternatives = FilteredSearchTokenKeysIssuesEE.getAlternatives();
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const alternatives = FilteredSearchTokenKeysIssues.getAlternatives();
const tokenKeysWithAlternative = tokenKeys.concat(alternatives);
return tokenKeysWithAlternative.find((tokenKey) => {
......@@ -90,16 +90,13 @@ class FilteredSearchTokenKeysIssuesEE extends gl.FilteredSearchTokenKeys {
}
static searchByConditionUrl(url) {
const conditions = FilteredSearchTokenKeysIssuesEE.getConditions();
const conditions = FilteredSearchTokenKeysIssues.getConditions();
return conditions.find(condition => condition.url === url) || null;
}
static searchByConditionKeyValue(key, value) {
const conditions = FilteredSearchTokenKeysIssuesEE.getConditions();
const conditions = FilteredSearchTokenKeysIssues.getConditions();
return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
}
}
window.gl = window.gl || {};
gl.FilteredSearchTokenKeysIssuesEE = FilteredSearchTokenKeysIssuesEE;
import FilteredSearchTokenKeysEpics from 'ee/filtered_search/filtered_search_token_keys_epics';
export default () => {
const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager({
page: 'epics',
filteredSearchTokenKeys: FilteredSearchTokenKeysEpics,
stateFiltersSelector: '.epics-state-filters',
});
filteredSearchManager.setup();
}
};
- page_title "Epics"
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
.top-area
= render 'shared/issuable/epic_nav', type: :epics
.nav-controls
- if can?(current_user, :create_epic, @group)
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'new_epic'
#new-epic-app{ data: { endpoint: request.url, 'align-right' => true } }
= render 'shared/epic/search_bar', type: :epics
- if @epics.to_a.any?
= render 'shared/epics'
......
.top-area
= render 'shared/issuable/epic_nav', type: :epics
.nav-controls
- if can?(current_user, :create_epic, @group)
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'new_epic'
#new-epic-app{ data: { endpoint: request.url, 'align-right' => true } }
%ul.content-list.issuable-list
= render partial: 'groups/epics/epic', collection: @epics
......
- type = local_assigns.fetch(:type)
- full_path = @project.present? ? @project.full_path : @group.full_path
.epics-filters
.epics-details-filters.filtered-search-block.row-content-block.second-block
= form_tag page_filter_path(without: [:author_id, :search]), method: :get, class: 'filter-form js-filter-form' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
.epics-other-filters.filtered-search-wrapper
.filtered-search-box
= dropdown_tag(custom_icon('icon_history'),
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
toggle_class: "filtered-search-history-dropdown-toggle-button",
dropdown_class: "filtered-search-history-dropdown",
content_class: "filtered-search-history-dropdown-content",
title: "Recent searches" }) do
.js-filtered-search-history-dropdown{ data: { full_path: full_path } }
.filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
%input.form-control.filtered-search{ search_filter_input_options(type) }
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { action: 'submit' } }
%button.btn.btn-link{ type: 'button' }
= icon('search')
%span
Press Enter or click to search
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' }
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
-# haml lint's ClassAttributeWithStaticValue
%i.fa{ class: "#{'{{icon}}'}" }
%span.js-filter-hint
{{hint}}
%span.js-filter-tag.dropdown-light-content
{{tag}}
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- if current_user
%ul{ data: { dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
%button.clear-search.hidden{ type: 'button' }
= icon('times')
.filter-dropdown-container
= render 'shared/epic/sort_dropdown'
- sorted_by = sort_options_hash[@sort]
.dropdown.inline.prepend-left-10
%button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } }
= sorted_by
= icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable.dropdown-menu-sort
%li
= sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by)
= sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated, label: true), sorted_by)
- type = local_assigns.fetch(:type, :epics)
- page_context_word = type.to_s.humanize(capitalize: false)
%ul.nav-links.issues-state-filters
%ul.nav-links.epics-state-filters
= render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all)
......@@ -22,6 +22,12 @@ describe 'epics list', :js do
expect(first('.nav-sidebar .active a .count')).to have_content('2')
end
it 'renders the filtered search bar correctly' do
page.within('.content-wrapper .content') do
expect(page).to have_css('.epics-filters')
end
end
it 'renders the list correctly' do
page.within('.content-wrapper .content') do
expect(find('.top-area')).to have_content('All 2')
......
import Vue from 'vue';
import eventHub from '~/filtered_search/event_hub';
import RecentSearchesDropdownContent from '~/filtered_search/components/recent_searches_dropdown_content';
import '~/filtered_search/filtered_search_token_keys';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
const createComponent = (propsData) => {
const Component = Vue.extend(RecentSearchesDropdownContent);
......@@ -19,14 +18,14 @@ const trimMarkupWhitespace = text => text.replace(/(\n|\s)+/gm, ' ').trim();
describe('RecentSearchesDropdownContent', () => {
const propsDataWithoutItems = {
items: [],
allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
allowedKeys: FilteredSearchTokenKeys.getKeys(),
};
const propsDataWithItems = {
items: [
'foo',
'author:@root label:~foo bar',
],
allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
allowedKeys: FilteredSearchTokenKeys.getKeys(),
};
let vm;
......
......@@ -3,6 +3,8 @@ import '~/filtered_search/filtered_search_tokenizer';
import '~/filtered_search/filtered_search_dropdown';
import '~/filtered_search/dropdown_user';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
describe('Dropdown User', () => {
describe('getSearchInput', () => {
let dropdownUser;
......@@ -14,7 +16,7 @@ describe('Dropdown User', () => {
spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
dropdownUser = new gl.DropdownUser({
tokenKeys: gl.FilteredSearchTokenKeys,
tokenKeys: FilteredSearchTokenKeys,
});
});
......
import '~/filtered_search/dropdown_utils';
import '~/filtered_search/filtered_search_tokenizer';
import '~/filtered_search/filtered_search_dropdown_manager';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
import FilteredSearchSpecHelper from '../helpers/filtered_search_spec_helper';
describe('Dropdown Utils', () => {
......@@ -137,7 +138,7 @@ describe('Dropdown Utils', () => {
`);
input = document.getElementById('test');
allowedKeys = gl.FilteredSearchTokenKeys.getKeys();
allowedKeys = FilteredSearchTokenKeys.getKeys();
});
function config() {
......
......@@ -3,8 +3,8 @@ import * as recentSearchesStoreSrc from '~/filtered_search/stores/recent_searche
import RecentSearchesService from '~/filtered_search/services/recent_searches_service';
import RecentSearchesServiceError from '~/filtered_search/services/recent_searches_service_error';
import RecentSearchesRoot from '~/filtered_search/recent_searches_root';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
import '~/lib/utils/common_utils';
import '~/filtered_search/filtered_search_token_keys';
import '~/filtered_search/filtered_search_tokenizer';
import '~/filtered_search/filtered_search_dropdown_manager';
import '~/filtered_search/filtered_search_manager';
......@@ -14,6 +14,7 @@ describe('Filtered Search Manager', () => {
let input;
let manager;
let tokensContainer;
const page = 'issues';
const placeholder = 'Search or filter results...';
function dispatchBackspaceEvent(element, eventType) {
......@@ -62,7 +63,7 @@ describe('Filtered Search Manager', () => {
input = document.querySelector('.filtered-search');
tokensContainer = document.querySelector('.tokens-container');
manager = new gl.FilteredSearchManager();
manager = new gl.FilteredSearchManager({ page });
manager.setup();
};
......@@ -80,19 +81,19 @@ describe('Filtered Search Manager', () => {
});
it('should instantiate RecentSearchesStore with isLocalStorageAvailable', () => {
manager = new gl.FilteredSearchManager();
manager = new gl.FilteredSearchManager({ page });
expect(RecentSearchesService.isAvailable).toHaveBeenCalled();
expect(recentSearchesStoreSrc.default).toHaveBeenCalledWith({
isLocalStorageAvailable,
allowedKeys: gl.FilteredSearchTokenKeys.getKeys(),
allowedKeys: FilteredSearchTokenKeys.getKeys(),
});
});
});
describe('setup', () => {
beforeEach(() => {
manager = new gl.FilteredSearchManager();
manager = new gl.FilteredSearchManager({ page });
});
it('should not instantiate Flash if an RecentSearchesServiceError is caught', () => {
......
import '~/filtered_search/filtered_search_token_keys_issues_ee';
(() => {
describe('Filtered Search Token Keys (Issues EE)', () => {
const weightTokenKey = {
key: 'weight',
type: 'string',
param: '',
symbol: '',
icon: 'balance-scale',
tag: 'weight',
};
describe('get', () => {
let tokenKeys;
beforeEach(() => {
gl.FilteredSearchTokenKeysIssuesEE.init({
multipleAssignees: true,
});
tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
});
it('should return tokenKeys', () => {
expect(tokenKeys !== null).toBe(true);
});
it('should return tokenKeys as an array', () => {
expect(tokenKeys instanceof Array).toBe(true);
});
it('should return weightTokenKey as part of tokenKeys', () => {
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
expect(match).toEqual(weightTokenKey);
});
it('should always return the same array', () => {
const tokenKeys2 = gl.FilteredSearchTokenKeysIssuesEE.get();
expect(tokenKeys).toEqual(tokenKeys2);
});
it('should return assignee as an array', () => {
const assignee = tokenKeys.find(tokenKey => tokenKey.key === 'assignee');
expect(assignee.type).toEqual('array');
});
});
describe('getKeys', () => {
it('should return keys', () => {
const getKeys = gl.FilteredSearchTokenKeysIssuesEE.getKeys();
const keys = gl.FilteredSearchTokenKeysIssuesEE.get().map(i => i.key);
keys.forEach((key, i) => {
expect(key).toEqual(getKeys[i]);
});
});
});
describe('getConditions', () => {
let conditions;
beforeEach(() => {
conditions = gl.FilteredSearchTokenKeysIssuesEE.getConditions();
});
it('should return conditions', () => {
expect(conditions !== null).toBe(true);
});
it('should return conditions as an array', () => {
expect(conditions instanceof Array).toBe(true);
});
it('should return weightConditions as part of conditions', () => {
const weightConditions = conditions.filter(c => c.tokenKey === 'weight');
expect(weightConditions.length).toBe(2);
});
});
describe('searchByKey', () => {
it('should return null when key not found', () => {
const tokenKey = gl.FilteredSearchTokenKeysIssuesEE.searchByKey('notakey');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
const result = gl.FilteredSearchTokenKeysIssuesEE.searchByKey(tokenKeys[0].key);
expect(result).toEqual(tokenKeys[0]);
});
it('should return weight tokenKey when found by weight key', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
const result = gl.FilteredSearchTokenKeysIssuesEE.searchByKey(weightTokenKey.key);
expect(result).toEqual(match);
});
});
describe('searchBySymbol', () => {
it('should return null when symbol not found', () => {
const tokenKey = gl.FilteredSearchTokenKeysIssuesEE.searchBySymbol('notasymbol');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by symbol', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
const result = gl.FilteredSearchTokenKeysIssuesEE.searchBySymbol(tokenKeys[0].symbol);
expect(result).toEqual(tokenKeys[0]);
});
it('should return weight tokenKey when found by weight symbol', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
const result = gl.FilteredSearchTokenKeysIssuesEE.searchBySymbol(weightTokenKey.symbol);
expect(result).toEqual(match);
});
});
describe('searchByKeyParam', () => {
it('should return null when key param not found', () => {
const tokenKey = gl.FilteredSearchTokenKeysIssuesEE.searchByKeyParam('notakeyparam');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
const result = gl.FilteredSearchTokenKeysIssuesEE
.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return alternative tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.getAlternatives();
const result = gl.FilteredSearchTokenKeysIssuesEE
.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return weight tokenKey when found by weight key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeysIssuesEE.get();
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
const result = gl.FilteredSearchTokenKeysIssuesEE.searchByKeyParam(weightTokenKey.key);
expect(result).toEqual(match);
});
});
describe('searchByConditionUrl', () => {
it('should return null when condition url not found', () => {
const condition = gl.FilteredSearchTokenKeysIssuesEE.searchByConditionUrl(null);
expect(condition === null).toBe(true);
});
it('should return condition when found by url', () => {
const conditions = gl.FilteredSearchTokenKeysIssuesEE.getConditions();
const result = gl.FilteredSearchTokenKeysIssuesEE
.searchByConditionUrl(conditions[0].url);
expect(result).toBe(conditions[0]);
});
it('should return weight condition when found by weight url', () => {
const conditions = gl.FilteredSearchTokenKeysIssuesEE.getConditions();
const weightConditions = conditions.filter(c => c.tokenKey === 'weight');
const result = gl.FilteredSearchTokenKeysIssuesEE
.searchByConditionUrl(weightConditions[0].url);
expect(result).toBe(weightConditions[0]);
});
});
describe('searchByConditionKeyValue', () => {
it('should return null when condition tokenKey and value not found', () => {
const condition = gl.FilteredSearchTokenKeysIssuesEE
.searchByConditionKeyValue(null, null);
expect(condition === null).toBe(true);
});
it('should return condition when found by tokenKey and value', () => {
const conditions = gl.FilteredSearchTokenKeysIssuesEE.getConditions();
const result = gl.FilteredSearchTokenKeysIssuesEE
.searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value);
expect(result).toEqual(conditions[0]);
});
it('should return weight condition when found by weight tokenKey and value', () => {
const conditions = gl.FilteredSearchTokenKeysIssuesEE.getConditions();
const weightConditions = conditions.filter(c => c.tokenKey === 'weight');
const result = gl.FilteredSearchTokenKeysIssuesEE
.searchByConditionKeyValue(weightConditions[0].tokenKey, weightConditions[0].value);
expect(result).toEqual(weightConditions[0]);
});
});
});
})();
import FilteredSearchTokenKeysIssues from 'ee/filtered_search/filtered_search_token_keys_issues';
describe('Filtered Search Token Keys (Issues EE)', () => {
const weightTokenKey = {
key: 'weight',
type: 'string',
param: '',
symbol: '',
icon: 'balance-scale',
tag: 'weight',
};
describe('get', () => {
let tokenKeys;
beforeEach(() => {
FilteredSearchTokenKeysIssues.init({
multipleAssignees: true,
});
tokenKeys = FilteredSearchTokenKeysIssues.get();
});
it('should return tokenKeys', () => {
expect(tokenKeys !== null).toBe(true);
});
it('should return tokenKeys as an array', () => {
expect(tokenKeys instanceof Array).toBe(true);
});
it('should return weightTokenKey as part of tokenKeys', () => {
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
expect(match).toEqual(weightTokenKey);
});
it('should always return the same array', () => {
const tokenKeys2 = FilteredSearchTokenKeysIssues.get();
expect(tokenKeys).toEqual(tokenKeys2);
});
it('should return assignee as an array', () => {
const assignee = tokenKeys.find(tokenKey => tokenKey.key === 'assignee');
expect(assignee.type).toEqual('array');
});
});
describe('getKeys', () => {
it('should return keys', () => {
const getKeys = FilteredSearchTokenKeysIssues.getKeys();
const keys = FilteredSearchTokenKeysIssues.get().map(i => i.key);
keys.forEach((key, i) => {
expect(key).toEqual(getKeys[i]);
});
});
});
describe('getConditions', () => {
let conditions;
beforeEach(() => {
conditions = FilteredSearchTokenKeysIssues.getConditions();
});
it('should return conditions', () => {
expect(conditions !== null).toBe(true);
});
it('should return conditions as an array', () => {
expect(conditions instanceof Array).toBe(true);
});
it('should return weightConditions as part of conditions', () => {
const weightConditions = conditions.filter(c => c.tokenKey === 'weight');
expect(weightConditions.length).toBe(2);
});
});
describe('searchByKey', () => {
it('should return null when key not found', () => {
const tokenKey = FilteredSearchTokenKeysIssues.searchByKey('notakey');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const result = FilteredSearchTokenKeysIssues.searchByKey(tokenKeys[0].key);
expect(result).toEqual(tokenKeys[0]);
});
it('should return weight tokenKey when found by weight key', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
const result = FilteredSearchTokenKeysIssues.searchByKey(weightTokenKey.key);
expect(result).toEqual(match);
});
});
describe('searchBySymbol', () => {
it('should return null when symbol not found', () => {
const tokenKey = FilteredSearchTokenKeysIssues.searchBySymbol('notasymbol');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by symbol', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const result = FilteredSearchTokenKeysIssues.searchBySymbol(tokenKeys[0].symbol);
expect(result).toEqual(tokenKeys[0]);
});
it('should return weight tokenKey when found by weight symbol', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
const result = FilteredSearchTokenKeysIssues.searchBySymbol(weightTokenKey.symbol);
expect(result).toEqual(match);
});
});
describe('searchByKeyParam', () => {
it('should return null when key param not found', () => {
const tokenKey = FilteredSearchTokenKeysIssues.searchByKeyParam('notakeyparam');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key param', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const result = FilteredSearchTokenKeysIssues
.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return alternative tokenKey when found by key param', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.getAlternatives();
const result = FilteredSearchTokenKeysIssues
.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return weight tokenKey when found by weight key param', () => {
const tokenKeys = FilteredSearchTokenKeysIssues.get();
const match = tokenKeys.find(tk => tk.key === weightTokenKey.key);
const result = FilteredSearchTokenKeysIssues.searchByKeyParam(weightTokenKey.key);
expect(result).toEqual(match);
});
});
describe('searchByConditionUrl', () => {
it('should return null when condition url not found', () => {
const condition = FilteredSearchTokenKeysIssues.searchByConditionUrl(null);
expect(condition === null).toBe(true);
});
it('should return condition when found by url', () => {
const conditions = FilteredSearchTokenKeysIssues.getConditions();
const result = FilteredSearchTokenKeysIssues
.searchByConditionUrl(conditions[0].url);
expect(result).toBe(conditions[0]);
});
it('should return weight condition when found by weight url', () => {
const conditions = FilteredSearchTokenKeysIssues.getConditions();
const weightConditions = conditions.filter(c => c.tokenKey === 'weight');
const result = FilteredSearchTokenKeysIssues
.searchByConditionUrl(weightConditions[0].url);
expect(result).toBe(weightConditions[0]);
});
});
describe('searchByConditionKeyValue', () => {
it('should return null when condition tokenKey and value not found', () => {
const condition = FilteredSearchTokenKeysIssues
.searchByConditionKeyValue(null, null);
expect(condition === null).toBe(true);
});
it('should return condition when found by tokenKey and value', () => {
const conditions = FilteredSearchTokenKeysIssues.getConditions();
const result = FilteredSearchTokenKeysIssues
.searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value);
expect(result).toEqual(conditions[0]);
});
it('should return weight condition when found by weight tokenKey and value', () => {
const conditions = FilteredSearchTokenKeysIssues.getConditions();
const weightConditions = conditions.filter(c => c.tokenKey === 'weight');
const result = FilteredSearchTokenKeysIssues
.searchByConditionKeyValue(weightConditions[0].tokenKey, weightConditions[0].value);
expect(result).toEqual(weightConditions[0]);
});
});
});
import '~/filtered_search/filtered_search_token_keys';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
describe('Filtered Search Token Keys', () => {
describe('get', () => {
let tokenKeys;
beforeEach(() => {
tokenKeys = gl.FilteredSearchTokenKeys.get();
tokenKeys = FilteredSearchTokenKeys.get();
});
it('should return tokenKeys', () => {
......@@ -19,8 +19,8 @@ describe('Filtered Search Token Keys', () => {
describe('getKeys', () => {
it('should return keys', () => {
const getKeys = gl.FilteredSearchTokenKeys.getKeys();
const keys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
const getKeys = FilteredSearchTokenKeys.getKeys();
const keys = FilteredSearchTokenKeys.get().map(i => i.key);
keys.forEach((key, i) => {
expect(key).toEqual(getKeys[i]);
......@@ -32,7 +32,7 @@ describe('Filtered Search Token Keys', () => {
let conditions;
beforeEach(() => {
conditions = gl.FilteredSearchTokenKeys.getConditions();
conditions = FilteredSearchTokenKeys.getConditions();
});
it('should return conditions', () => {
......@@ -46,71 +46,71 @@ describe('Filtered Search Token Keys', () => {
describe('searchByKey', () => {
it('should return null when key not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey');
const tokenKey = FilteredSearchTokenKeys.searchByKey('notakey');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key);
const tokenKeys = FilteredSearchTokenKeys.get();
const result = FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchBySymbol', () => {
it('should return null when symbol not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol');
const tokenKey = FilteredSearchTokenKeys.searchBySymbol('notasymbol');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by symbol', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol);
const tokenKeys = FilteredSearchTokenKeys.get();
const result = FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchByKeyParam', () => {
it('should return null when key param not found', () => {
const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam');
const tokenKey = FilteredSearchTokenKeys.searchByKeyParam('notakeyparam');
expect(tokenKey === null).toBe(true);
});
it('should return tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.get();
const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
const tokenKeys = FilteredSearchTokenKeys.get();
const result = FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
it('should return alternative tokenKey when found by key param', () => {
const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives();
const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
const tokenKeys = FilteredSearchTokenKeys.getAlternatives();
const result = FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
expect(result).toEqual(tokenKeys[0]);
});
});
describe('searchByConditionUrl', () => {
it('should return null when condition url not found', () => {
const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null);
const condition = FilteredSearchTokenKeys.searchByConditionUrl(null);
expect(condition === null).toBe(true);
});
it('should return condition when found by url', () => {
const conditions = gl.FilteredSearchTokenKeys.getConditions();
const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url);
const conditions = FilteredSearchTokenKeys.getConditions();
const result = FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url);
expect(result).toBe(conditions[0]);
});
});
describe('searchByConditionKeyValue', () => {
it('should return null when condition tokenKey and value not found', () => {
const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null);
const condition = FilteredSearchTokenKeys.searchByConditionKeyValue(null, null);
expect(condition === null).toBe(true);
});
it('should return condition when found by tokenKey and value', () => {
const conditions = gl.FilteredSearchTokenKeys.getConditions();
const result = gl.FilteredSearchTokenKeys
const conditions = FilteredSearchTokenKeys.getConditions();
const result = FilteredSearchTokenKeys
.searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value);
expect(result).toEqual(conditions[0]);
});
......
import '~/filtered_search/filtered_search_token_keys';
import FilteredSearchTokenKeys from '~/filtered_search/filtered_search_token_keys';
import '~/filtered_search/filtered_search_tokenizer';
describe('Filtered Search Tokenizer', () => {
const allowedKeys = gl.FilteredSearchTokenKeys.getKeys();
const allowedKeys = FilteredSearchTokenKeys.getKeys();
describe('processTokens', () => {
it('returns for input containing only search value', () => {
......
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