From 7df16a1c42629f90b843b9fb45b9fc0be8503f3e Mon Sep 17 00:00:00 2001 From: Artur Pata Date: Thu, 9 Jan 2025 15:01:09 +0200 Subject: [PATCH] Handle empty search string and badly formed search strings --- .../dashboard/util/url-search-params.test.ts | 24 +++++++++++++++++++ assets/js/dashboard/util/url-search-params.ts | 23 ++++++++++++++---- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/assets/js/dashboard/util/url-search-params.test.ts b/assets/js/dashboard/util/url-search-params.test.ts index c1a9ceef0a662..980e6f1ef02c0 100644 --- a/assets/js/dashboard/util/url-search-params.test.ts +++ b/assets/js/dashboard/util/url-search-params.test.ts @@ -136,8 +136,32 @@ describe(`${serializeSimpleSearchEntry.name} and ${parseSimpleSearchEntry.name}` ) }) +describe(`${parseSearch.name}`, () => { + it.each([ + ['?', {}, ''], + ['?=&&', {}, ''], + ['?=undefined', {}, ''], + ['?foo=', { foo: '' }, '?foo='], + ['??foo', { '?foo': '' }, '?%3Ffoo='], + [ + '?f=is,visit:page,/any/page&f', + { filters: [['is', 'visit:page', ['/any/page']]] }, + '?f=is,visit:page,/any/page' + ] + ])( + 'for search string %s, returns search record %p, which in turn stringifies to %s', + (searchString, expectedSearchRecord, expectedRestringifiedResult) => { + expect(parseSearch(searchString)).toEqual(expectedSearchRecord) + expect(stringifySearch(expectedSearchRecord)).toEqual( + expectedRestringifiedResult + ) + } + ) +}) + describe(`${stringifySearch.name}`, () => { it.each([ + [{}, ''], [ { filters: [['is', 'props:browser_language', ['en-US']]] diff --git a/assets/js/dashboard/util/url-search-params.ts b/assets/js/dashboard/util/url-search-params.ts index 1492ce94e562b..485cd1cf8563b 100644 --- a/assets/js/dashboard/util/url-search-params.ts +++ b/assets/js/dashboard/util/url-search-params.ts @@ -31,15 +31,27 @@ export function stringifySearch( return `?${serializedFilters.concat(serializedLabels).concat(definedSearchEntries).join('&')}` } +export function normalizeSearchString(searchString: string): string { + return searchString.startsWith('?') ? searchString.slice(1) : searchString +} + export function parseSearch(searchString: string): Record { const searchRecord: Record = {} const filters: Filter[] = [] const labels: FilterClauseLabels = {} - for (const param of searchString.startsWith('?') - ? searchString.slice(1).split('&') - : searchString.split('&')) { - const [key, rawValue] = param.split('=') + const normalizedSearchString = normalizeSearchString(searchString) + + if (!normalizedSearchString.length) { + return searchRecord + } + + const meaningfulParams = normalizedSearchString + .split('&') + .filter((i) => i.length > 0) + + for (const param of meaningfulParams) { + const [key, rawValue = ''] = param.split('=') switch (key) { case FILTER_URL_PARAM_NAME: { const filter = parseFilter(rawValue) @@ -55,6 +67,9 @@ export function parseSearch(searchString: string): Record { } break } + case '': { + break + } default: { const parsedValue = parseSimpleSearchEntry(rawValue) if (parsedValue !== null) {