diff options
Diffstat (limited to 'app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js')
-rw-r--r-- | app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js | 67 |
1 files changed, 57 insertions, 10 deletions
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js index e5c8d29e09b..37436de907f 100644 --- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js +++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_utils.js @@ -2,7 +2,7 @@ import { isEmpty, uniqWith, isEqual } from 'lodash'; import AccessorUtilities from '~/lib/utils/accessor'; import { queryToObject } from '~/lib/utils/url_utility'; -import { MAX_RECENT_TOKENS_SIZE } from './constants'; +import { MAX_RECENT_TOKENS_SIZE, FILTERED_SEARCH_TERM } from './constants'; /** * Strips enclosing quotations from a string if it has one. @@ -23,7 +23,7 @@ export const stripQuotes = (value) => value.replace(/^('|")(.*)('|")$/, '$2'); export const uniqueTokens = (tokens) => { const knownTokens = []; return tokens.reduce((uniques, token) => { - if (typeof token === 'object' && token.type !== 'filtered-search-term') { + if (typeof token === 'object' && token.type !== FILTERED_SEARCH_TERM) { const tokenString = `${token.type}${token.value.operator}${token.value.data}`; if (!knownTokens.includes(tokenString)) { uniques.push(token); @@ -86,21 +86,37 @@ export function processFilters(filters) { }, {}); } +function filteredSearchQueryParam(filter) { + return filter + .map(({ value }) => value) + .join(' ') + .trim(); +} + /** * This function takes a filter object and maps it into a query object. Example filter: - * { myFilterName: { value: 'foo', operator: '=' } } + * { myFilterName: { value: 'foo', operator: '=' }, search: [{ value: 'my' }, { value: 'search' }] } * gets translated into: - * { myFilterName: 'foo', 'not[myFilterName]': null } + * { myFilterName: 'foo', 'not[myFilterName]': null, search: 'my search' } * @param {Object} filters - * @param {Object.myFilterName} a single filter value or an array of filters + * @param {Object} filters.myFilterName a single filter value or an array of filters + * @param {Object} options + * @param {Object} [options.filteredSearchTermKey] if set, 'filtered-search-term' filters are assigned to this key, 'search' is suggested * @return {Object} query object with both filter name and not-name with values */ -export function filterToQueryObject(filters = {}) { +export function filterToQueryObject(filters = {}, options = {}) { + const { filteredSearchTermKey } = options; + return Object.keys(filters).reduce((memo, key) => { const filter = filters[key]; + if (typeof filteredSearchTermKey === 'string' && key === FILTERED_SEARCH_TERM) { + return { ...memo, [filteredSearchTermKey]: filteredSearchQueryParam(filter) }; + } + let selected; let unselected; + if (Array.isArray(filter)) { selected = filter.filter((item) => item.operator === '=').map((item) => item.value); unselected = filter.filter((item) => item.operator === '!=').map((item) => item.value); @@ -125,7 +141,7 @@ export function filterToQueryObject(filters = {}) { * and returns the operator with it depending on the filter name * @param {String} filterName from url * @return {Object} - * @return {Object.filterName} extracted filtern ame + * @return {Object.filterName} extracted filter name * @return {Object.operator} `=` or `!=` */ function extractNameAndOperator(filterName) { @@ -138,21 +154,52 @@ function extractNameAndOperator(filterName) { } /** + * Gathers search term as values + * @param {String|Array} value + * @returns {Array} List of search terms split by word + */ +function filteredSearchTermValue(value) { + const values = Array.isArray(value) ? value : [value]; + return values + .filter((term) => term) + .join(' ') + .split(' ') + .map((term) => ({ value: term })); +} + +/** * This function takes a URL query string and maps it into a filter object. Example query string: * '?myFilterName=foo' * gets translated into: * { myFilterName: { value: 'foo', operator: '=' } } - * @param {String} query URL quert string, e.g. from `window.location.search` + * @param {String} query URL query string, e.g. from `window.location.search` + * @param {Object} options + * @param {Object} options + * @param {String} [options.filteredSearchTermKey] if set, a FILTERED_SEARCH_TERM filter is created to this parameter. `'search'` is suggested + * @param {String[]} [options.filterNamesAllowList] if set, only this list of filters names is mapped + * @param {Boolean} [options.legacySpacesDecode] if set, plus symbols (+) are not encoded as spaces. `false` is suggested * @return {Object} filter object with filter names and their values */ -export function urlQueryToFilter(query = '') { - const filters = queryToObject(query, { gatherArrays: true }); +export function urlQueryToFilter(query = '', options = {}) { + const { filteredSearchTermKey, filterNamesAllowList, legacySpacesDecode = true } = options; + + const filters = queryToObject(query, { gatherArrays: true, legacySpacesDecode }); return Object.keys(filters).reduce((memo, key) => { const value = filters[key]; if (!value) { return memo; } + if (key === filteredSearchTermKey) { + return { + ...memo, + [FILTERED_SEARCH_TERM]: filteredSearchTermValue(value), + }; + } + const { filterName, operator } = extractNameAndOperator(key); + if (filterNamesAllowList && !filterNamesAllowList.includes(filterName)) { + return memo; + } let previousValues = []; if (Array.isArray(memo[filterName])) { previousValues = memo[filterName]; |