Compare commits

...

2 Commits

Author SHA1 Message Date
Gaurav Tewari
6c947dfdbb feat(query-builder): drop null params and forward navigate options on redirect 2026-11-13 01:06:47 +05:30
Gaurav Tewari
98a0d906ed fix(navigation): keep an explicit replace: false in safeNavigate 2026-11-13 01:06:21 +05:30
6 changed files with 97 additions and 8 deletions

View File

@@ -0,0 +1,32 @@
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { back, getCurrentLocation } from 'lib/router/navigation';
import { act, AllTheProviders, renderHook, waitFor } from 'tests/test-utils';
// jest.config.ts maps `hooks/useSafeNavigate` to a mock; the `.ts` path loads the real hook.
const { useSafeNavigate } = jest.requireActual<
typeof import('hooks/useSafeNavigate')
>('hooks/useSafeNavigate.ts');
describe('useSafeNavigate', () => {
it('adds a history entry for replace: false even when the target adds params', async () => {
const initialSearch = `?${QueryParams.relativeTime}=15m`;
const { result } = renderHook(() => useSafeNavigate(), {
wrapper: ({ children }): JSX.Element => (
<AllTheProviders initialRoute={`${ROUTES.LOGS_EXPLORER}${initialSearch}`}>
{children}
</AllTheProviders>
),
});
act(() => {
result.current.safeNavigate(
`${ROUTES.LOGS_EXPLORER}?${QueryParams.startTime}=1000`,
{ replace: false },
);
});
act(() => back());
await waitFor(() => expect(getCurrentLocation().search).toBe(initialSearch));
});
});

View File

@@ -90,7 +90,7 @@ export const useHandleExplorerTabChange = (): {
},
redirectToUrl,
undefined,
newTab,
{ newTab },
);
},
[panelType, getUpdateQuery, redirectWithQueryBuilderData, viewKey],

View File

@@ -140,7 +140,7 @@ export const useSafeNavigate = (
const navigationOptions = {
...options,
replace: isDefaultParamsNavigation || options?.replace,
replace: options?.replace ?? isDefaultParamsNavigation,
};
if (typeof to === 'string') {

View File

@@ -949,7 +949,7 @@ export function QueryBuilderProvider({
searchParams?: Record<string, unknown>,
redirectingUrl?: (typeof ROUTES)[keyof typeof ROUTES],
shouldNotStringify?: boolean,
newTab?: boolean,
navigateOptions?: { newTab?: boolean; replace?: boolean },
) => {
const queryType =
!query.queryType || !Object.values(EQueryType).includes(query.queryType)
@@ -1001,14 +1001,18 @@ export function QueryBuilderProvider({
);
if (searchParams) {
Object.keys(searchParams).forEach((param) =>
Object.keys(searchParams).forEach((param) => {
if (searchParams[param] === null) {
urlQuery.delete(param);
return;
}
urlQuery.set(
param,
shouldNotStringify
? (searchParams[param] as string)
: JSON.stringify(searchParams[param]),
),
);
);
});
}
// Remove Hidden Filters from URL query parameters on query change
urlQuery.delete(QueryParams.activeLogId);
@@ -1017,7 +1021,7 @@ export function QueryBuilderProvider({
? `${redirectingUrl}?${urlQuery}`
: `${location.pathname}?${urlQuery}`;
safeNavigate(generatedUrl, { newTab });
safeNavigate(generatedUrl, navigateOptions);
},
[location.pathname, safeNavigate],
);

View File

@@ -0,0 +1,53 @@
import { safeNavigateMock } from '__tests__/safeNavigateMock';
import { QueryParams } from 'constants/query';
import { initialQueriesMap } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { act, AllTheProviders, renderHook } from 'tests/test-utils';
describe('redirectWithQueryBuilderData search params', () => {
it('removes params passed as null and JSON-encodes the rest', () => {
const { result } = renderHook(() => useQueryBuilder(), {
wrapper: ({ children }): JSX.Element => (
<AllTheProviders
initialRoute={`/unconstrained?${QueryParams.relativeTime}=6h&${QueryParams.viewName}=kept`}
>
{children}
</AllTheProviders>
),
});
act(() => {
result.current.redirectWithQueryBuilderData(initialQueriesMap.logs, {
[QueryParams.relativeTime]: null,
[QueryParams.startTime]: 1000,
});
});
const [url] = safeNavigateMock.mock.lastCall ?? [];
expect(url).toContain(`${QueryParams.startTime}=1000`);
expect(url).not.toContain(`${QueryParams.relativeTime}=`);
expect(url).toContain(`${QueryParams.viewName}=kept`);
});
it('passes navigate options to safeNavigate', () => {
const { result } = renderHook(() => useQueryBuilder(), {
wrapper: ({ children }): JSX.Element => (
<AllTheProviders initialRoute="/unconstrained">{children}</AllTheProviders>
),
});
act(() => {
result.current.redirectWithQueryBuilderData(
initialQueriesMap.logs,
{ [QueryParams.startTime]: 1000 },
undefined,
undefined,
{ replace: false },
);
});
expect(safeNavigateMock).toHaveBeenLastCalledWith(expect.any(String), {
replace: false,
});
});
});

View File

@@ -312,7 +312,7 @@ export type QueryBuilderContextType = {
searchParams?: Record<string, unknown>,
redirectToUrl?: (typeof ROUTES)[keyof typeof ROUTES],
shallStringify?: boolean,
newTab?: boolean,
navigateOptions?: { newTab?: boolean; replace?: boolean },
) => void;
handleRunQuery: () => void;
resetQuery: (newCurrentQuery?: QueryState) => void;