diff options
15 files changed, 108 insertions, 81 deletions
diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js index 6d5dd747224..293154917fa 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -3,7 +3,6 @@ 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'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index ff046aa286a..b2add862051 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -3,11 +3,11 @@ import DropLab from '~/droplab/drop_lab'; import FilteredSearchContainer from './container'; class FilteredSearchDropdownManager { - constructor(baseEndpoint = '', tokenizer, page) { + 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; @@ -29,7 +29,15 @@ 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', @@ -64,12 +72,15 @@ class FilteredSearchDropdownManager { gl: 'DropdownEmoji', element: this.container.querySelector('#js-dropdown-my-reaction'), }, - hint: { - reference: null, - gl: 'DropdownHint', - element: this.container.querySelector('#js-dropdown-hint'), - }, }; + + supportedTokens.forEach((type) => { + if (availableMappings[type]) { + allowedMappings[type] = availableMappings[type]; + } + }); + + this.mapping = allowedMappings; } static addWordToInput(tokenName, tokenValue = '', clicked = false) { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 58ed0012f01..532a5fe1090 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -3,20 +3,33 @@ 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; + this.filteredSearchTokenKeys = filteredSearchTokenKeys; + this.stateFiltersSelector = stateFiltersSelector; + this.recentsStorageKeyNames = { + issues: 'issue-recent-searches', + merge_requests: 'merge-request-recent-searches', + }; this.recentSearchesStore = new RecentSearchesStore({ isLocalStorageAvailable: RecentSearchesService.isAvailable(), @@ -25,11 +38,7 @@ 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); } @@ -58,7 +67,13 @@ class FilteredSearchManager { if (this.filteredSearchInput) { this.tokenizer = gl.FilteredSearchTokenizer; - this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, this.page); + this.dropdownManager = new gl.FilteredSearchDropdownManager( + this.filteredSearchInput.getAttribute('data-base-endpoint') || '', + this.tokenizer, + this.page, + this.isGroup, + this.filteredSearchTokenKeys, + ); this.recentSearchesRoot = new RecentSearchesRoot( this.recentSearchesStore, @@ -86,40 +101,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); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index be595d7df1a..087ef5cd6f2 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -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; diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 78db543a64d..fbdfabd1e95 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -3,6 +3,8 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; export default () => { - initFilteredSearch(FILTERED_SEARCH.ISSUES); + initFilteredSearch({ + page: FILTERED_SEARCH.ISSUES, + }); projectSelect(); }; diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js index 9b3af4537e7..f6d284bf9ef 100644 --- a/app/assets/javascripts/pages/groups/merge_requests/index.js +++ b/app/assets/javascripts/pages/groups/merge_requests/index.js @@ -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(); }; diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index 39c043edc38..70fdb0ef40d 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -8,7 +8,9 @@ import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; document.addEventListener('DOMContentLoaded', () => { - initFilteredSearch(FILTERED_SEARCH.ISSUES); + initFilteredSearch({ + page: FILTERED_SEARCH.ISSUES, + }); new IssuableIndex(ISSUABLE_INDEX.ISSUE); new ShortcutsNavigation(); diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js index adadbf28e49..a7aa616319f 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js @@ -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 diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js index 44853636aea..250f9d992ab 100644 --- a/app/assets/javascripts/pages/search/init_filtered_search.js +++ b/app/assets/javascripts/pages/search/init_filtered_search.js @@ -1,7 +1,7 @@ -export default (page) => { +export default ({ page }) => { const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search'); if (filteredSearchEnabled) { - const filteredSearchManager = new gl.FilteredSearchManager(page); + const filteredSearchManager = new gl.FilteredSearchManager({ page }); filteredSearchManager.setup(); } }; diff --git a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js index 79447787fc9..4a516c517ef 100644 --- a/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js +++ b/spec/javascripts/filtered_search/components/recent_searches_dropdown_content_spec.js @@ -2,7 +2,7 @@ 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 +19,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; diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js b/spec/javascripts/filtered_search/dropdown_user_spec.js index 02415485d19..f1e6119253e 100644 --- a/spec/javascripts/filtered_search/dropdown_user_spec.js +++ b/spec/javascripts/filtered_search/dropdown_user_spec.js @@ -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, }); }); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js index b1b3d43f241..d6e1af105f1 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js @@ -1,6 +1,7 @@ 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() { diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js b/spec/javascripts/filtered_search/filtered_search_manager_spec.js index b8890e4cda1..0ed9a587dc1 100644 --- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js @@ -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', () => { diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js index 69b424c3af5..fbc3926d332 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js @@ -1,11 +1,11 @@ -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', () => { @@ -21,7 +21,7 @@ describe('Filtered Search Token Keys', () => { let conditions; beforeEach(() => { - conditions = gl.FilteredSearchTokenKeys.getConditions(); + conditions = FilteredSearchTokenKeys.getConditions(); }); it('should return conditions', () => { @@ -35,71 +35,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]); }); diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js index 585bea9b499..bf8b66f1110 100644 --- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js @@ -1,8 +1,8 @@ -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', () => { |