diff --git a/frontend/src/hooks/api/getters/useFeatureSearch/searchToQueryParams.test.ts b/frontend/src/hooks/api/getters/useFeatureSearch/searchToQueryParams.test.ts new file mode 100644 index 0000000000..dc91fece29 --- /dev/null +++ b/frontend/src/hooks/api/getters/useFeatureSearch/searchToQueryParams.test.ts @@ -0,0 +1,50 @@ +import { translateToQueryParams } from './searchToQueryParams'; + +describe('translateToQueryParams', () => { + describe.each([ + ['search', 'query=search'], + [' search', 'query=search'], + [' search ', 'query=search'], + ['search ', 'query=search'], + ['search with space', 'query=search with space'], + ['search type:release', 'query=search&type[]=release'], + [' search type:release ', 'query=search&type[]=release'], + [ + 'search type:release,experiment', + 'query=search&type[]=release&type[]=experiment', + ], + [ + 'search type:release ,experiment', + 'query=search&type[]=release&type[]=experiment', + ], + [ + 'search type:release, experiment', + 'query=search&type[]=release&type[]=experiment', + ], + [ + 'search type:release , experiment', + 'query=search&type[]=release&type[]=experiment', + ], + [ + 'search type: release , experiment', + 'query=search&type[]=release&type[]=experiment', + ], + ['type:release', 'type[]=release'], + ['type: release', 'type[]=release'], + ['production:enabled', 'status[]=production:enabled'], + [ + 'development:enabled,disabled', + 'status[]=development:enabled&status[]=development:disabled', + ], + ['tag:simple:web', 'tag[]=simple:web'], + ['tag:enabled:enabled', 'tag[]=enabled:enabled'], + [ + 'tag:simple:web,complex:native', + 'tag[]=simple:web&tag[]=complex:native', + ], + ])('when input is "%s"', (input, expected) => { + it(`returns "${expected}"`, () => { + expect(translateToQueryParams(input)).toBe(expected); + }); + }); +}); diff --git a/frontend/src/hooks/api/getters/useFeatureSearch/searchToQueryParams.ts b/frontend/src/hooks/api/getters/useFeatureSearch/searchToQueryParams.ts new file mode 100644 index 0000000000..67a4572b11 --- /dev/null +++ b/frontend/src/hooks/api/getters/useFeatureSearch/searchToQueryParams.ts @@ -0,0 +1,117 @@ +const splitInputQuery = (searchString: string): string[] => + searchString.trim().split(/ (?=\w+:)/); + +const isFilter = (part: string): boolean => part.includes(':'); + +const isStatusFilter = (key: string, values: string[]): boolean => + values.every((value) => value === 'enabled' || value === 'disabled'); + +const addStatusFilters = ( + key: string, + values: string[], + filterParams: Record, +): Record => { + const newStatuses = values.map((value) => `${key}:${value}`); + return { + ...filterParams, + status: [...(filterParams.status || []), ...newStatuses], + }; +}; + +const addTagFilters = ( + values: string[], + filterParams: Record, +): Record => ({ + ...filterParams, + tag: [...(filterParams.tag || []), ...values], +}); + +const addRegularFilters = ( + key: string, + values: string[], + filterParams: Record, +): Record => ({ + ...filterParams, + [key]: [...(filterParams[key] || []), ...values], +}); + +const handleFilter = ( + part: string, + filterParams: Record, +): Record => { + const [key, ...valueParts] = part.split(':'); + const valueString = valueParts.join(':').trim(); + const values = valueString.split(',').map((value) => value.trim()); + + if (isStatusFilter(key, values)) { + return addStatusFilters(key, values, filterParams); + } else if (key === 'tag') { + return addTagFilters(values, filterParams); + } else { + return addRegularFilters(key, values, filterParams); + } +}; + +const handleSearchTerm = ( + part: string, + filterParams: Record, +): Record => ({ + ...filterParams, + query: filterParams.query + ? `${filterParams.query} ${part.trim()}` + : part.trim(), +}); + +const appendFilterParamsToQueryParts = ( + params: Record, +): string[] => { + let newQueryParts: string[] = []; + + for (const [key, value] of Object.entries(params)) { + if (Array.isArray(value)) { + newQueryParts = [ + ...newQueryParts, + ...value.map((item) => `${key}[]=${item}`), + ]; + } else { + newQueryParts.push(`${key}=${value}`); + } + } + + return newQueryParts; +}; + +const convertToQueryString = ( + params: Record, +): string => { + const { query, ...filterParams } = params; + let queryParts: string[] = []; + + if (query) { + queryParts.push(`query=${query}`); + } + + queryParts = queryParts.concat( + appendFilterParamsToQueryParts(filterParams), + ); + + return queryParts.join('&'); +}; + +const buildSearchParams = ( + input: string, +): Record => { + const parts = splitInputQuery(input); + return parts.reduce( + (searchAndFilterParams, part) => + isFilter(part) + ? handleFilter(part, searchAndFilterParams) + : handleSearchTerm(part, searchAndFilterParams), + {}, + ); +}; + +export const translateToQueryParams = (searchString: string): string => { + const searchParams = buildSearchParams(searchString); + return convertToQueryString(searchParams); +};