mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-22 01:40:32 +01:00
Compare commits
2 Commits
refactor/i
...
fix/date-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79ceb0e4b7 | ||
|
|
b54f7bdef0 |
@@ -0,0 +1,258 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-15m"
|
||||
onClick={(): void => onSelect('15m')}
|
||||
>
|
||||
15m
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-6h"
|
||||
onClick={(): void => onSelect('6h')}
|
||||
>
|
||||
6h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-custom"
|
||||
onClick={(): void => onSelect('custom')}
|
||||
>
|
||||
Custom
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Edge Cases', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
describe('Fresh Params at Navigation Time (Core Fix)', () => {
|
||||
it('should read params at navigation time, not render time', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="relativeTime=30m"
|
||||
onUrlUpdate={(event): void => {
|
||||
currentSearchParams = event.searchParams;
|
||||
}}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&externalParam=addedLater',
|
||||
);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('externalParam=addedLater');
|
||||
});
|
||||
|
||||
it('should preserve multiple externally added params', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&yAxisUnit=bytes&groupBy=host&view=table',
|
||||
);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-6h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=6h');
|
||||
expect(navigatedUrl).toContain('yAxisUnit=bytes');
|
||||
expect(navigatedUrl).toContain('groupBy=host');
|
||||
expect(navigatedUrl).toContain('view=table');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Empty and Special Values', () => {
|
||||
it('should handle empty URL params gracefully', async () => {
|
||||
currentSearchParams = new URLSearchParams('');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-15m'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=15m');
|
||||
});
|
||||
|
||||
it('should handle special characters in preserved params', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&filter=name%3D%22test%22',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&filter=name%3D%22test%22">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('filter=');
|
||||
});
|
||||
|
||||
it('should not navigate when selecting custom (opens picker instead)', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-custom'));
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
const customNavigationCalls = mockSafeNavigate.mock.calls.filter((call) => {
|
||||
const url = call[0] as string;
|
||||
return url.includes('startTime=') || url.includes('endTime=');
|
||||
});
|
||||
|
||||
expect(customNavigationCalls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
let mockOnCustomDateHandler: ((range: [unknown, unknown]) => void) | null =
|
||||
null;
|
||||
let mockOnValidCustomDateChange: ((data: { timeStr: string }) => void) | null =
|
||||
null;
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
onCustomDateHandler,
|
||||
onValidCustomDateChange,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
onCustomDateHandler?: (range: [unknown, unknown]) => void;
|
||||
onValidCustomDateChange?: (data: { timeStr: string }) => void;
|
||||
}): JSX.Element => {
|
||||
mockOnCustomDateHandler = onCustomDateHandler || null;
|
||||
mockOnValidCustomDateChange = onValidCustomDateChange || null;
|
||||
|
||||
return (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Modal Mode', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
mockOnCustomDateHandler = null;
|
||||
mockOnValidCustomDateChange = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
it('should call onTimeChange instead of navigating for relative time', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
const mockOnTimeChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection
|
||||
showAutoRefresh
|
||||
isModalTimeSelection
|
||||
onTimeChange={mockOnTimeChange}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnTimeChange).toHaveBeenCalledWith('1h');
|
||||
});
|
||||
|
||||
expect(mockSafeNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onTimeChange with custom and timestamps for custom date', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
const mockOnTimeChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection
|
||||
showAutoRefresh
|
||||
isModalTimeSelection
|
||||
onTimeChange={mockOnTimeChange}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const startMoment = { toDate: (): Date => new Date(1700000000000) };
|
||||
const endMoment = { toDate: (): Date => new Date(1700003600000) };
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnCustomDateHandler?.([startMoment, endMoment]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnTimeChange).toHaveBeenCalledWith(
|
||||
'custom',
|
||||
[1700000000000, 1700003600000],
|
||||
);
|
||||
});
|
||||
|
||||
expect(mockSafeNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onTimeChange for valid custom date string in modal', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
const mockOnTimeChange = jest.fn();
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m">
|
||||
<DateTimeSelection
|
||||
showAutoRefresh
|
||||
isModalTimeSelection
|
||||
onTimeChange={mockOnTimeChange}
|
||||
/>
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnValidCustomDateChange?.({ timeStr: '4h' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnTimeChange).toHaveBeenCalledWith('4h');
|
||||
});
|
||||
|
||||
expect(mockSafeNavigate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter, Route } from 'react-router-dom';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import store from 'store';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { refetchOnWindowFocus: false, retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
function NuqsParamSetter({ paramValue }: { paramValue: string }): JSX.Element {
|
||||
const [, setYAxisUnit] = useQueryState(
|
||||
'yAxisUnit',
|
||||
parseAsString.withDefault(''),
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="set-nuqs-param"
|
||||
onClick={(): void => {
|
||||
setYAxisUnit(paramValue);
|
||||
}}
|
||||
>
|
||||
Set yAxisUnit
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
initialSearchParams?: string;
|
||||
initialPath?: string;
|
||||
onUrlUpdate?: (event: { searchParams: URLSearchParams }) => void;
|
||||
}
|
||||
|
||||
function TestWrapper({
|
||||
children,
|
||||
initialSearchParams = '',
|
||||
initialPath = '/services',
|
||||
onUrlUpdate,
|
||||
}: WrapperProps): JSX.Element {
|
||||
const initialEntry = initialSearchParams
|
||||
? `${initialPath}?${initialSearchParams}`
|
||||
: initialPath;
|
||||
|
||||
const mockedStore = mockStore({
|
||||
...store.getState(),
|
||||
app: {
|
||||
...store.getState().app,
|
||||
role: 'ADMIN',
|
||||
user: {
|
||||
userId: 'test-user-id',
|
||||
email: 'test@signoz.io',
|
||||
name: 'TestUser',
|
||||
profilePictureURL: '',
|
||||
accessJwt: '',
|
||||
refreshJwt: '',
|
||||
},
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<CompatRouter>
|
||||
<NuqsTestingAdapter
|
||||
searchParams={initialSearchParams}
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={mockedStore}>
|
||||
<AppContext.Provider value={getAppContextMock('ADMIN')}>
|
||||
<TimezoneProvider>
|
||||
<QueryBuilderProvider>
|
||||
<Route path="*">{children}</Route>
|
||||
</QueryBuilderProvider>
|
||||
</TimezoneProvider>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</NuqsTestingAdapter>
|
||||
</CompatRouter>
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
describe('REGRESSION: DateTimeSelectionV2 preserves nuqs query params on time change', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
|
||||
// Initialize with test's initial search params
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
it('should preserve yAxisUnit param set via nuqs when changing time selection', async () => {
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="relativeTime=30m"
|
||||
onUrlUpdate={(event): void => {
|
||||
// Sync nuqs URL updates to our mock getter
|
||||
// This simulates how window.location.search would be updated in real browser
|
||||
currentSearchParams = event.searchParams;
|
||||
}}
|
||||
>
|
||||
<NuqsParamSetter paramValue="bytes" />
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('set-nuqs-param'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(currentSearchParams.get('yAxisUnit')).toBe('bytes');
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('yAxisUnit=bytes');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="custom-time-picker" />,
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Route-Specific Behavior', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
describe('Alert Pages', () => {
|
||||
it('should set default time for alert overview when no time params', async () => {
|
||||
currentSearchParams = new URLSearchParams('otherParam=value');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="otherParam=value"
|
||||
initialPath={ROUTES.ALERT_OVERVIEW}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[0][0] as string;
|
||||
expect(navigatedUrl).toContain('relativeTime=6h');
|
||||
expect(navigatedUrl).toContain('otherParam=value');
|
||||
});
|
||||
|
||||
it('should set default time for alert history when no time params', async () => {
|
||||
currentSearchParams = new URLSearchParams('filter=active');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="filter=active"
|
||||
initialPath={ROUTES.ALERT_HISTORY}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[0][0] as string;
|
||||
expect(navigatedUrl).toContain('relativeTime=6h');
|
||||
});
|
||||
|
||||
it('should NOT override existing time params on alert pages', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=1h&filter=active');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams="relativeTime=1h&filter=active"
|
||||
initialPath={ROUTES.ALERT_OVERVIEW}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh defaultRelativeTime="6h" />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const calls = mockSafeNavigate.mock.calls;
|
||||
if (calls.length > 0) {
|
||||
const lastUrl = calls[calls.length - 1][0] as string;
|
||||
expect(lastUrl).toContain('relativeTime=1h');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('disableUrlSync Behavior', () => {
|
||||
it('should not sync URL on mount when disableUrlSync is true', async () => {
|
||||
currentSearchParams = new URLSearchParams('');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="" initialPath="/services">
|
||||
<DateTimeSelection showAutoRefresh disableUrlSync />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const syncCalls = mockSafeNavigate.mock.calls.filter((call) => {
|
||||
const url = call[0] as string;
|
||||
return url.includes('relativeTime=') && !url.includes('services?');
|
||||
});
|
||||
|
||||
expect(syncCalls).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,353 @@
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
|
||||
import DateTimeSelection from '../index';
|
||||
import {
|
||||
__resetSearchParamsGetter,
|
||||
__setSearchParamsGetterForTest,
|
||||
} from '../utils/getUnstableCurrentSearchParams';
|
||||
import { queryClient, TestWrapper, createMockMoment } from './testUtils';
|
||||
|
||||
const mockSafeNavigate = jest.fn();
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): { safeNavigate: jest.Mock } => ({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/NewExplorerCTA', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
|
||||
let mockOnCustomDateHandler: ((range: [unknown, unknown]) => void) | null =
|
||||
null;
|
||||
let mockOnValidCustomDateChange: ((data: { timeStr: string }) => void) | null =
|
||||
null;
|
||||
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
onSelect,
|
||||
onCustomDateHandler,
|
||||
onValidCustomDateChange,
|
||||
}: {
|
||||
onSelect: (value: string) => void;
|
||||
onCustomDateHandler?: (range: [unknown, unknown]) => void;
|
||||
onValidCustomDateChange?: (data: { timeStr: string }) => void;
|
||||
}): JSX.Element => {
|
||||
mockOnCustomDateHandler = onCustomDateHandler || null;
|
||||
mockOnValidCustomDateChange = onValidCustomDateChange || null;
|
||||
|
||||
return (
|
||||
<div data-testid="custom-time-picker">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-15m"
|
||||
onClick={(): void => onSelect('15m')}
|
||||
>
|
||||
15m
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-1h"
|
||||
onClick={(): void => onSelect('1h')}
|
||||
>
|
||||
1h
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="select-6h"
|
||||
onClick={(): void => onSelect('6h')}
|
||||
>
|
||||
6h
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
|
||||
describe('DateTimeSelectionV2 - Time Selection', () => {
|
||||
let currentSearchParams: URLSearchParams;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockSafeNavigate.mockClear();
|
||||
queryClient.clear();
|
||||
mockOnCustomDateHandler = null;
|
||||
mockOnValidCustomDateChange = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
__resetSearchParamsGetter();
|
||||
});
|
||||
|
||||
describe('Relative Time', () => {
|
||||
it('should update relativeTime and remove startTime/endTime', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'startTime=1000&endTime=2000&otherParam=keep',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="startTime=1000&endTime=2000&otherParam=keep">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-15m'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=15m');
|
||||
expect(navigatedUrl).not.toContain('startTime=');
|
||||
expect(navigatedUrl).not.toContain('endTime=');
|
||||
expect(navigatedUrl).toContain('otherParam=keep');
|
||||
});
|
||||
|
||||
it('should remove activeLogId param on time change', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m&activeLogId=log123',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&activeLogId=log123">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).not.toContain('activeLogId');
|
||||
});
|
||||
|
||||
it('should update compositeQuery with new ID when present', async () => {
|
||||
const compositeQuery = encodeURIComponent(
|
||||
JSON.stringify({ id: 'old-id', builder: { queryData: [] } }),
|
||||
);
|
||||
currentSearchParams = new URLSearchParams(
|
||||
`relativeTime=30m&compositeQuery=${compositeQuery}`,
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams={`relativeTime=30m&compositeQuery=${compositeQuery}`}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-6h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('compositeQuery=');
|
||||
expect(navigatedUrl).not.toContain('old-id');
|
||||
});
|
||||
|
||||
it('should preserve all non-time URL params', async () => {
|
||||
currentSearchParams = new URLSearchParams(
|
||||
'relativeTime=30m¶m1=a¶m2=b¶m3=c',
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m¶m1=a¶m2=b¶m3=c">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('select-1h'));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=1h');
|
||||
expect(navigatedUrl).toContain('param1=a');
|
||||
expect(navigatedUrl).toContain('param2=b');
|
||||
expect(navigatedUrl).toContain('param3=c');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Custom Date Range', () => {
|
||||
it('should set startTime/endTime and remove relativeTime', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m&keepThis=yes');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&keepThis=yes">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const startMoment = createMockMoment(1700000000000);
|
||||
const endMoment = createMockMoment(1700003600000);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnCustomDateHandler?.([startMoment, endMoment]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('startTime=1700000000000');
|
||||
expect(navigatedUrl).toContain('endTime=1700003600000');
|
||||
expect(navigatedUrl).not.toContain('relativeTime=');
|
||||
expect(navigatedUrl).toContain('keepThis=yes');
|
||||
});
|
||||
|
||||
it('should update compositeQuery when present for custom date', async () => {
|
||||
const compositeQuery = encodeURIComponent(
|
||||
JSON.stringify({ id: 'old-id', builder: { queryData: [] } }),
|
||||
);
|
||||
currentSearchParams = new URLSearchParams(
|
||||
`relativeTime=30m&compositeQuery=${compositeQuery}`,
|
||||
);
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper
|
||||
initialSearchParams={`relativeTime=30m&compositeQuery=${compositeQuery}`}
|
||||
>
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const startMoment = createMockMoment(1700000000000);
|
||||
const endMoment = createMockMoment(1700003600000);
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnCustomDateHandler?.([startMoment, endMoment]);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('compositeQuery=');
|
||||
expect(navigatedUrl).not.toContain('old-id');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Valid Custom Date String', () => {
|
||||
it('should handle shorthand date format and preserve params', async () => {
|
||||
currentSearchParams = new URLSearchParams('relativeTime=30m&filter=active');
|
||||
__setSearchParamsGetterForTest(() => currentSearchParams);
|
||||
|
||||
render(
|
||||
<TestWrapper initialSearchParams="relativeTime=30m&filter=active">
|
||||
<DateTimeSelection showAutoRefresh />
|
||||
</TestWrapper>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('custom-time-picker')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
mockSafeNavigate.mockClear();
|
||||
|
||||
act(() => {
|
||||
mockOnValidCustomDateChange?.({ timeStr: '2h' });
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const navigatedUrl = mockSafeNavigate.mock.calls[
|
||||
mockSafeNavigate.mock.calls.length - 1
|
||||
][0] as string;
|
||||
|
||||
expect(navigatedUrl).toContain('relativeTime=2h');
|
||||
expect(navigatedUrl).toContain('filter=active');
|
||||
expect(navigatedUrl).not.toContain('startTime=');
|
||||
expect(navigatedUrl).not.toContain('endTime=');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter, Route } from 'react-router-dom';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import store from 'store';
|
||||
import { getAppContextMock } from 'tests/test-utils';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { refetchOnWindowFocus: false, retry: false },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
export const mockStore = configureStore([thunk]);
|
||||
|
||||
interface WrapperProps {
|
||||
children: React.ReactNode;
|
||||
initialSearchParams?: string;
|
||||
initialPath?: string;
|
||||
onUrlUpdate?: (event: { searchParams: URLSearchParams }) => void;
|
||||
}
|
||||
|
||||
export function TestWrapper({
|
||||
children,
|
||||
initialSearchParams = '',
|
||||
initialPath = '/services',
|
||||
onUrlUpdate,
|
||||
}: WrapperProps): JSX.Element {
|
||||
const initialEntry = initialSearchParams
|
||||
? `${initialPath}?${initialSearchParams}`
|
||||
: initialPath;
|
||||
|
||||
const mockedStore = mockStore({
|
||||
...store.getState(),
|
||||
app: {
|
||||
...store.getState().app,
|
||||
role: 'ADMIN',
|
||||
user: {
|
||||
userId: 'test-user-id',
|
||||
email: 'test@signoz.io',
|
||||
name: 'TestUser',
|
||||
profilePictureURL: '',
|
||||
accessJwt: '',
|
||||
refreshJwt: '',
|
||||
},
|
||||
isLoggedIn: true,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter initialEntries={[initialEntry]}>
|
||||
<CompatRouter>
|
||||
<NuqsTestingAdapter
|
||||
searchParams={initialSearchParams}
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={mockedStore}>
|
||||
<AppContext.Provider value={getAppContextMock('ADMIN')}>
|
||||
<TimezoneProvider>
|
||||
<QueryBuilderProvider>
|
||||
<Route path="*">{children}</Route>
|
||||
</QueryBuilderProvider>
|
||||
</TimezoneProvider>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</NuqsTestingAdapter>
|
||||
</CompatRouter>
|
||||
</MemoryRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export function createMockMoment(timestamp: number): {
|
||||
toDate: () => Date;
|
||||
toISOString: () => string;
|
||||
format: () => string;
|
||||
toString: () => string;
|
||||
} {
|
||||
const date = new Date(timestamp);
|
||||
return {
|
||||
toDate: (): Date => date,
|
||||
toISOString: (): string => date.toISOString(),
|
||||
format: (): string => date.toISOString(),
|
||||
toString: (): string => date.toString(),
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { connect, useDispatch, useSelector } from 'react-redux';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
import { useNavigationType, useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { useNavigationType } from 'react-router-dom-v5-compat';
|
||||
import { RefreshCw, Undo } from '@signozhq/icons';
|
||||
import { Button } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
} from 'store/globalTime';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { isValidShortHandDateTimeFormat } from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import { cloneDeep, isObject } from 'lodash-es';
|
||||
@@ -54,6 +53,7 @@ import {
|
||||
Time,
|
||||
TimeRange,
|
||||
} from './types';
|
||||
import { getUnstableCurrentSearchParams } from './utils/getUnstableCurrentSearchParams';
|
||||
|
||||
import './DateTimeSelectionV2.styles.scss';
|
||||
|
||||
@@ -90,10 +90,12 @@ function DateTimeSelection({
|
||||
const [hasSelectedTimeError, setHasSelectedTimeError] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const searchStartTime = urlQuery.get('startTime');
|
||||
const searchEndTime = urlQuery.get('endTime');
|
||||
const relativeTimeFromUrl = urlQuery.get(QueryParams.relativeTime);
|
||||
const currentSearchParams = getUnstableCurrentSearchParams();
|
||||
const searchStartTime = currentSearchParams.get('startTime');
|
||||
const searchEndTime = currentSearchParams.get('endTime');
|
||||
const relativeTimeFromUrl = currentSearchParams.get(QueryParams.relativeTime);
|
||||
const hasTimeParamsInUrl =
|
||||
(searchStartTime && searchEndTime) || relativeTimeFromUrl;
|
||||
|
||||
// Prioritize props for initial modal time, fallback to URL params
|
||||
let initialModalStartTime = 0;
|
||||
@@ -115,8 +117,6 @@ function DateTimeSelection({
|
||||
);
|
||||
const [modalEndTime, setModalEndTime] = useState<number>(initialModalEndTime);
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Effect to update modal time state when props change
|
||||
useEffect(() => {
|
||||
if (modalInitialStartTime !== undefined) {
|
||||
@@ -323,6 +323,7 @@ function DateTimeSelection({
|
||||
return;
|
||||
}
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
|
||||
@@ -330,7 +331,7 @@ function DateTimeSelection({
|
||||
// Remove Hidden Filters from URL query parameters on time change
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
|
||||
if (searchParams.has(QueryParams.compositeQuery)) {
|
||||
if (urlQuery.has(QueryParams.compositeQuery)) {
|
||||
const updatedCompositeQuery = getUpdatedCompositeQuery();
|
||||
urlQuery.set(QueryParams.compositeQuery, updatedCompositeQuery);
|
||||
}
|
||||
@@ -349,8 +350,6 @@ function DateTimeSelection({
|
||||
getUpdatedCompositeQuery,
|
||||
updateLocalStorageForRoutes,
|
||||
updateTimeInterval,
|
||||
urlQuery,
|
||||
searchParams,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -414,6 +413,7 @@ function DateTimeSelection({
|
||||
|
||||
updateLocalStorageForRoutes(JSON.stringify({ startTime, endTime }));
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.set(
|
||||
QueryParams.startTime,
|
||||
startTime?.toDate().getTime().toString(),
|
||||
@@ -421,7 +421,7 @@ function DateTimeSelection({
|
||||
urlQuery.set(QueryParams.endTime, endTime?.toDate().getTime().toString());
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
|
||||
if (searchParams.has(QueryParams.compositeQuery)) {
|
||||
if (urlQuery.has(QueryParams.compositeQuery)) {
|
||||
const updatedCompositeQuery = getUpdatedCompositeQuery();
|
||||
urlQuery.set(QueryParams.compositeQuery, updatedCompositeQuery);
|
||||
}
|
||||
@@ -441,6 +441,7 @@ function DateTimeSelection({
|
||||
updateTimeInterval(dateTimeStr);
|
||||
updateLocalStorageForRoutes(dateTimeStr);
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
|
||||
@@ -595,13 +596,12 @@ function DateTimeSelection({
|
||||
|
||||
// set the default relative time for alert history and overview pages if relative time is not specified
|
||||
if (
|
||||
(!urlQuery.has(QueryParams.startTime) ||
|
||||
!urlQuery.has(QueryParams.endTime)) &&
|
||||
!urlQuery.has(QueryParams.relativeTime) &&
|
||||
!hasTimeParamsInUrl &&
|
||||
(currentRoute === ROUTES.ALERT_OVERVIEW ||
|
||||
currentRoute === ROUTES.ALERT_HISTORY)
|
||||
) {
|
||||
updateTimeInterval(defaultRelativeTime);
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
urlQuery.set(QueryParams.relativeTime, defaultRelativeTime);
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
safeNavigate(generatedUrl);
|
||||
@@ -625,6 +625,7 @@ function DateTimeSelection({
|
||||
updateTimeInterval(updatedTime, [preStartTime, preEndTime]);
|
||||
}
|
||||
|
||||
const urlQuery = getUnstableCurrentSearchParams();
|
||||
if (updatedTime !== 'custom') {
|
||||
urlQuery.delete('startTime');
|
||||
urlQuery.delete('endTime');
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* This was introduced to fix a sync bug between Nuqs and react-router-dom
|
||||
*
|
||||
* We are using the wrong adapter for nuqs because the correct one only supports v6/v7,
|
||||
* and we are at version v5. This causes the nuqs/react-router-dom to be out of sync.
|
||||
*
|
||||
* We can revert this commit once we migrate react-router-dom to v6, or once we migrate
|
||||
* to DateTimeSelectionV3
|
||||
*/
|
||||
|
||||
/**
|
||||
* This was created to help testing the regression introduced between nuqs/react-router-dom
|
||||
*/
|
||||
type SearchParamsGetter = () => URLSearchParams;
|
||||
let getter: SearchParamsGetter = (): URLSearchParams =>
|
||||
new URLSearchParams(window.location.search);
|
||||
|
||||
/**
|
||||
* This function will return a fresh instance of URLSearchParams every time it's called.
|
||||
*
|
||||
* DO NOT USE IT FOR useEffect/useCallback dependencies, use Nuqs instead.
|
||||
*/
|
||||
export function getUnstableCurrentSearchParams(): URLSearchParams {
|
||||
return getter();
|
||||
}
|
||||
|
||||
// Testing helpers
|
||||
export function __setSearchParamsGetterForTest(fn: SearchParamsGetter): void {
|
||||
getter = fn;
|
||||
}
|
||||
|
||||
export function __resetSearchParamsGetter(): void {
|
||||
getter = (): URLSearchParams => new URLSearchParams(window.location.search);
|
||||
}
|
||||
Reference in New Issue
Block a user