@parca/profile 0.19.138 → 0.19.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.d.ts.map +1 -1
- package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +11 -13
- package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
- package/dist/ProfileExplorer/ProfileExplorerCompare.js +4 -9
- package/dist/ProfileFlameChart/SamplesStrips/index.d.ts +2 -2
- package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -1
- package/dist/ProfileFlameChart/index.d.ts.map +1 -1
- package/dist/ProfileFlameChart/index.js +13 -19
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +8 -8
- package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +4 -3
- package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/index.js +6 -4
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +4 -6
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +5 -10
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +27 -25
- package/dist/ProfileSelector/useAutoQuerySelector.d.ts.map +1 -1
- package/dist/ProfileSelector/useAutoQuerySelector.js +3 -0
- package/dist/ProfileTypeSelector/index.d.ts.map +1 -1
- package/dist/ProfileTypeSelector/index.js +4 -0
- package/dist/ProfileView/components/ActionButtons/SortByDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +5 -5
- package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
- package/dist/ProfileView/components/ColorStackLegend.js +2 -3
- package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -1
- package/dist/ProfileView/components/InvertCallStack/index.js +5 -4
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +1 -2
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +14 -16
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +84 -170
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +16 -20
- package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +4 -5
- package/dist/ProfileView/components/Toolbars/index.d.ts +2 -2
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +1 -1
- package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +8 -14
- package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
- package/dist/ProfileView/context/DashboardContext.js +6 -6
- package/dist/ProfileView/hooks/useResetFlameGraphState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetFlameGraphState.js +5 -4
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +25 -26
- package/dist/ProfileView/hooks/useResetStateOnSeriesChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +13 -8
- package/dist/ProfileView/hooks/useVisualizationState.d.ts +3 -3
- package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useVisualizationState.js +35 -51
- package/dist/ProfileViewWithData.d.ts.map +1 -1
- package/dist/ProfileViewWithData.js +19 -28
- package/dist/Sandwich/index.d.ts.map +1 -1
- package/dist/Sandwich/index.js +4 -3
- package/dist/SourceView/index.d.ts.map +1 -1
- package/dist/SourceView/index.js +4 -2
- package/dist/SourceView/useSelectedLineRange.d.ts.map +1 -1
- package/dist/SourceView/useSelectedLineRange.js +21 -16
- package/dist/Table/MoreDropdown.d.ts.map +1 -1
- package/dist/Table/MoreDropdown.js +8 -11
- package/dist/Table/TableContextMenu.d.ts.map +1 -1
- package/dist/Table/TableContextMenu.js +10 -13
- package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
- package/dist/Table/hooks/useTableConfiguration.js +3 -4
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +11 -9
- package/dist/TopTable/index.d.ts.map +1 -1
- package/dist/TopTable/index.js +3 -4
- package/dist/hooks/urlParsers.d.ts +18 -0
- package/dist/hooks/urlParsers.d.ts.map +1 -0
- package/dist/hooks/urlParsers.js +32 -0
- package/dist/hooks/useColorBy.d.ts +5 -0
- package/dist/hooks/useColorBy.d.ts.map +1 -0
- package/dist/hooks/useColorBy.js +26 -0
- package/dist/hooks/useCompareModeMeta.d.ts.map +1 -1
- package/dist/hooks/useCompareModeMeta.js +55 -86
- package/dist/hooks/useDashboardItems.d.ts +5 -0
- package/dist/hooks/useDashboardItems.d.ts.map +1 -0
- package/dist/hooks/useDashboardItems.js +27 -0
- package/dist/hooks/useQueryState.d.ts +3 -3
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +105 -105
- package/dist/hooks/useQueryState.test.js +186 -302
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -12
- package/dist/useSumBy.d.ts +1 -1
- package/dist/useSumBy.d.ts.map +1 -1
- package/dist/useSumBy.js +2 -2
- package/package.json +12 -11
- package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +11 -13
- package/src/ProfileExplorer/ProfileExplorerCompare.tsx +4 -9
- package/src/ProfileFlameChart/SamplesStrips/index.tsx +2 -2
- package/src/ProfileFlameChart/index.tsx +21 -28
- package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +10 -9
- package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +5 -3
- package/src/ProfileFlameGraph/index.tsx +6 -9
- package/src/ProfileMetricsGraph/index.tsx +6 -8
- package/src/ProfileSelector/MetricsGraphSection.tsx +5 -10
- package/src/ProfileSelector/index.tsx +32 -31
- package/src/ProfileSelector/useAutoQuerySelector.ts +5 -0
- package/src/ProfileTypeSelector/index.tsx +4 -0
- package/src/ProfileView/components/ActionButtons/SortByDropdown.tsx +10 -6
- package/src/ProfileView/components/ColorStackLegend.tsx +2 -4
- package/src/ProfileView/components/InvertCallStack/index.tsx +5 -4
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +94 -192
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +21 -21
- package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +24 -25
- package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +4 -5
- package/src/ProfileView/components/Toolbars/index.tsx +3 -3
- package/src/ProfileView/components/ViewSelector/index.tsx +9 -16
- package/src/ProfileView/context/DashboardContext.tsx +6 -6
- package/src/ProfileView/hooks/useResetFlameGraphState.ts +6 -4
- package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +24 -26
- package/src/ProfileView/hooks/useResetStateOnSeriesChange.ts +16 -8
- package/src/ProfileView/hooks/useVisualizationState.ts +61 -69
- package/src/ProfileViewWithData.tsx +29 -35
- package/src/Sandwich/index.tsx +4 -3
- package/src/SourceView/index.tsx +4 -2
- package/src/SourceView/useSelectedLineRange.ts +34 -19
- package/src/Table/MoreDropdown.tsx +9 -11
- package/src/Table/TableContextMenu.tsx +10 -13
- package/src/Table/hooks/useTableConfiguration.tsx +3 -4
- package/src/Table/index.tsx +12 -21
- package/src/TopTable/index.tsx +3 -4
- package/src/hooks/urlParsers.ts +38 -0
- package/src/hooks/useColorBy.ts +42 -0
- package/src/hooks/useCompareModeMeta.ts +61 -91
- package/src/hooks/useDashboardItems.ts +46 -0
- package/src/hooks/useQueryState.test.tsx +275 -345
- package/src/hooks/useQueryState.ts +136 -118
- package/src/index.tsx +16 -15
- package/src/useSumBy.ts +3 -3
|
@@ -17,59 +17,13 @@ import {ReactNode, act} from 'react';
|
|
|
17
17
|
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
18
18
|
// eslint-disable-next-line import/named
|
|
19
19
|
import {renderHook, waitFor} from '@testing-library/react';
|
|
20
|
+
// eslint-disable-next-line import/no-unresolved
|
|
21
|
+
import {NuqsTestingAdapter} from 'nuqs/adapters/testing';
|
|
20
22
|
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
|
21
23
|
|
|
22
|
-
import {URLStateProvider} from '@parca/components';
|
|
23
|
-
|
|
24
24
|
import {useQueryState} from './useQueryState';
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
const mockLocation = {
|
|
28
|
-
pathname: '/test',
|
|
29
|
-
search: '',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Mock the navigate function that actually updates the mock location
|
|
33
|
-
const mockNavigateTo = vi.fn((path: string, params: Record<string, string | string[]>) => {
|
|
34
|
-
// Convert params object to query string
|
|
35
|
-
const searchParams = new URLSearchParams();
|
|
36
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
37
|
-
if (value !== undefined && value !== null) {
|
|
38
|
-
if (Array.isArray(value)) {
|
|
39
|
-
// For arrays, join with commas
|
|
40
|
-
searchParams.set(key, value.join(','));
|
|
41
|
-
} else {
|
|
42
|
-
searchParams.set(key, String(value));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
mockLocation.search = `?${searchParams.toString()}`;
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Mock the getQueryParamsFromURL function
|
|
50
|
-
vi.mock('@parca/components/src/hooks/URLState/utils', async () => {
|
|
51
|
-
const actual = await vi.importActual('@parca/components/src/hooks/URLState/utils');
|
|
52
|
-
return {
|
|
53
|
-
...actual,
|
|
54
|
-
getQueryParamsFromURL: () => {
|
|
55
|
-
if (mockLocation.search === '') return {};
|
|
56
|
-
const params = new URLSearchParams(mockLocation.search);
|
|
57
|
-
const result: Record<string, string | string[]> = {};
|
|
58
|
-
for (const [key, value] of params.entries()) {
|
|
59
|
-
const decodedValue = decodeURIComponent(value);
|
|
60
|
-
const existing = result[key];
|
|
61
|
-
if (existing !== undefined) {
|
|
62
|
-
result[key] = Array.isArray(existing)
|
|
63
|
-
? [...existing, decodedValue]
|
|
64
|
-
: [existing, decodedValue];
|
|
65
|
-
} else {
|
|
66
|
-
result[key] = decodedValue;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return result;
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
});
|
|
26
|
+
const mockNavigateTo = vi.fn();
|
|
73
27
|
|
|
74
28
|
// Mock useSumBy with stateful behavior using React's useState
|
|
75
29
|
vi.mock('../useSumBy', async () => {
|
|
@@ -138,9 +92,10 @@ const setProfileTypesData = (data: typeof mockProfileTypesData): void => {
|
|
|
138
92
|
mockProfileTypesData = data;
|
|
139
93
|
};
|
|
140
94
|
|
|
141
|
-
// Helper to create wrapper with
|
|
95
|
+
// Helper to create wrapper with NuqsTestingAdapter
|
|
142
96
|
const createWrapper = (
|
|
143
|
-
|
|
97
|
+
_paramPreferences = {},
|
|
98
|
+
searchParams: string | Record<string, string> = {}
|
|
144
99
|
): (({children}: {children: ReactNode}) => JSX.Element) => {
|
|
145
100
|
const queryClient = new QueryClient({
|
|
146
101
|
defaultOptions: {
|
|
@@ -150,24 +105,17 @@ const createWrapper = (
|
|
|
150
105
|
},
|
|
151
106
|
});
|
|
152
107
|
const Wrapper = ({children}: {children: ReactNode}): JSX.Element => (
|
|
153
|
-
<
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
</URLStateProvider>
|
|
157
|
-
</QueryClientProvider>
|
|
108
|
+
<NuqsTestingAdapter searchParams={searchParams} hasMemory={true}>
|
|
109
|
+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
110
|
+
</NuqsTestingAdapter>
|
|
158
111
|
);
|
|
159
|
-
Wrapper.displayName = '
|
|
112
|
+
Wrapper.displayName = 'NuqsTestingWrapper';
|
|
160
113
|
return Wrapper;
|
|
161
114
|
};
|
|
162
115
|
|
|
163
116
|
describe('useQueryState', () => {
|
|
164
117
|
beforeEach(() => {
|
|
165
118
|
mockNavigateTo.mockClear();
|
|
166
|
-
Object.defineProperty(window, 'location', {
|
|
167
|
-
value: mockLocation,
|
|
168
|
-
writable: true,
|
|
169
|
-
});
|
|
170
|
-
mockLocation.search = '';
|
|
171
119
|
// Reset profile types mock state
|
|
172
120
|
setProfileTypesLoading(false);
|
|
173
121
|
setProfileTypesData(undefined);
|
|
@@ -195,10 +143,12 @@ describe('useQueryState', () => {
|
|
|
195
143
|
});
|
|
196
144
|
|
|
197
145
|
it('should handle suffix for comparison mode', () => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
146
|
+
const {result} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
147
|
+
wrapper: createWrapper(
|
|
148
|
+
{},
|
|
149
|
+
'?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from_a=1000&to_a=2000'
|
|
150
|
+
),
|
|
151
|
+
});
|
|
202
152
|
|
|
203
153
|
const {querySelection} = result.current;
|
|
204
154
|
expect(querySelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}');
|
|
@@ -236,14 +186,11 @@ describe('useQueryState', () => {
|
|
|
236
186
|
});
|
|
237
187
|
|
|
238
188
|
await waitFor(() => {
|
|
239
|
-
expect(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
expect(
|
|
244
|
-
expect(params).toHaveProperty('merge_to');
|
|
245
|
-
expect(params.merge_from).toBe('1000000000');
|
|
246
|
-
expect(params.merge_to).toBe('2000000000');
|
|
189
|
+
expect(result.current.querySelection.expression).toBe(
|
|
190
|
+
'memory:alloc_objects:count:space:bytes:delta{}'
|
|
191
|
+
);
|
|
192
|
+
expect(result.current.querySelection.mergeFrom).toBe('1000000000');
|
|
193
|
+
expect(result.current.querySelection.mergeTo).toBe('2000000000');
|
|
247
194
|
});
|
|
248
195
|
});
|
|
249
196
|
|
|
@@ -264,11 +211,9 @@ describe('useQueryState', () => {
|
|
|
264
211
|
});
|
|
265
212
|
|
|
266
213
|
await waitFor(() => {
|
|
267
|
-
expect(
|
|
268
|
-
|
|
269
|
-
expect(
|
|
270
|
-
expect(params.to).toBe('4000');
|
|
271
|
-
expect(params.time_selection).toBe('relative:minute|5');
|
|
214
|
+
expect(String(result.current.querySelection.from)).toBe('3000');
|
|
215
|
+
expect(String(result.current.querySelection.to)).toBe('4000');
|
|
216
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|5');
|
|
272
217
|
});
|
|
273
218
|
});
|
|
274
219
|
|
|
@@ -289,9 +234,8 @@ describe('useQueryState', () => {
|
|
|
289
234
|
});
|
|
290
235
|
|
|
291
236
|
await waitFor(() => {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
expect(params.sum_by).toBe('namespace,container');
|
|
237
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
238
|
+
expect(result.current.draftSelection.sumBy).toEqual(['namespace', 'container']);
|
|
295
239
|
});
|
|
296
240
|
});
|
|
297
241
|
|
|
@@ -313,10 +257,8 @@ describe('useQueryState', () => {
|
|
|
313
257
|
});
|
|
314
258
|
|
|
315
259
|
await waitFor(() => {
|
|
316
|
-
expect(
|
|
317
|
-
|
|
318
|
-
expect(params.merge_from).toBe('5000000000');
|
|
319
|
-
expect(params.merge_to).toBe('6000000000');
|
|
260
|
+
expect(result.current.querySelection.mergeFrom).toBe('5000000000');
|
|
261
|
+
expect(result.current.querySelection.mergeTo).toBe('6000000000');
|
|
320
262
|
});
|
|
321
263
|
});
|
|
322
264
|
});
|
|
@@ -345,22 +287,25 @@ describe('useQueryState', () => {
|
|
|
345
287
|
});
|
|
346
288
|
|
|
347
289
|
await waitFor(() => {
|
|
348
|
-
//
|
|
349
|
-
expect(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
expect(
|
|
353
|
-
expect(
|
|
354
|
-
expect(
|
|
355
|
-
|
|
290
|
+
// Verify all state values are correct after the batch
|
|
291
|
+
expect(result.current.querySelection.expression).toBe(
|
|
292
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
293
|
+
);
|
|
294
|
+
expect(String(result.current.querySelection.from)).toBe('7000');
|
|
295
|
+
expect(String(result.current.querySelection.to)).toBe('8000');
|
|
296
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|30');
|
|
297
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
298
|
+
expect(result.current.draftSelection.sumBy).toEqual(['pod', 'node']);
|
|
356
299
|
});
|
|
357
300
|
});
|
|
358
301
|
|
|
359
302
|
it('should handle partial updates', async () => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
303
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
304
|
+
wrapper: createWrapper(
|
|
305
|
+
{},
|
|
306
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000&time_selection=relative:hour|1'
|
|
307
|
+
),
|
|
308
|
+
});
|
|
364
309
|
|
|
365
310
|
act(() => {
|
|
366
311
|
// Only update expression, other values should remain
|
|
@@ -379,12 +324,12 @@ describe('useQueryState', () => {
|
|
|
379
324
|
});
|
|
380
325
|
|
|
381
326
|
await waitFor(() => {
|
|
382
|
-
expect(
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
expect(
|
|
386
|
-
expect(
|
|
387
|
-
expect(
|
|
327
|
+
expect(result.current.querySelection.expression).toBe(
|
|
328
|
+
'memory:inuse_space:bytes:space:bytes{}'
|
|
329
|
+
);
|
|
330
|
+
expect(String(result.current.querySelection.from)).toBe('1000');
|
|
331
|
+
expect(String(result.current.querySelection.to)).toBe('2000');
|
|
332
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:hour|1');
|
|
388
333
|
});
|
|
389
334
|
});
|
|
390
335
|
|
|
@@ -405,21 +350,23 @@ describe('useQueryState', () => {
|
|
|
405
350
|
});
|
|
406
351
|
|
|
407
352
|
await waitFor(() => {
|
|
408
|
-
expect(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
expect(
|
|
412
|
-
expect(
|
|
353
|
+
expect(result.current.querySelection.expression).toBe(
|
|
354
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
355
|
+
);
|
|
356
|
+
expect(result.current.querySelection.mergeFrom).toBe('9000000000');
|
|
357
|
+
expect(result.current.querySelection.mergeTo).toBe('10000000000');
|
|
413
358
|
});
|
|
414
359
|
});
|
|
415
360
|
});
|
|
416
361
|
|
|
417
362
|
describe('Helper functions', () => {
|
|
418
363
|
it('should set profile name correctly', async () => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
364
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
365
|
+
wrapper: createWrapper(
|
|
366
|
+
{},
|
|
367
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="parca"}'
|
|
368
|
+
),
|
|
369
|
+
});
|
|
423
370
|
|
|
424
371
|
act(() => {
|
|
425
372
|
result.current.setDraftProfileName('memory:inuse_space:bytes:space:bytes');
|
|
@@ -435,16 +382,19 @@ describe('useQueryState', () => {
|
|
|
435
382
|
});
|
|
436
383
|
|
|
437
384
|
await waitFor(() => {
|
|
438
|
-
expect(
|
|
439
|
-
|
|
440
|
-
|
|
385
|
+
expect(result.current.querySelection.expression).toBe(
|
|
386
|
+
'memory:inuse_space:bytes:space:bytes{job="parca"}'
|
|
387
|
+
);
|
|
441
388
|
});
|
|
442
389
|
});
|
|
443
390
|
|
|
444
391
|
it('should set matchers correctly using draft', async () => {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
392
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
393
|
+
wrapper: createWrapper(
|
|
394
|
+
{},
|
|
395
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}'
|
|
396
|
+
),
|
|
397
|
+
});
|
|
448
398
|
|
|
449
399
|
act(() => {
|
|
450
400
|
result.current.setDraftMatchers('namespace="default",pod="my-pod"');
|
|
@@ -454,7 +404,6 @@ describe('useQueryState', () => {
|
|
|
454
404
|
expect(result.current.draftSelection.expression).toBe(
|
|
455
405
|
'process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{namespace="default",pod="my-pod"}'
|
|
456
406
|
);
|
|
457
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
458
407
|
|
|
459
408
|
// Commit the draft
|
|
460
409
|
act(() => {
|
|
@@ -462,9 +411,7 @@ describe('useQueryState', () => {
|
|
|
462
411
|
});
|
|
463
412
|
|
|
464
413
|
await waitFor(() => {
|
|
465
|
-
expect(
|
|
466
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
467
|
-
expect(params.expression).toBe(
|
|
414
|
+
expect(result.current.querySelection.expression).toBe(
|
|
468
415
|
'process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{namespace="default",pod="my-pod"}'
|
|
469
416
|
);
|
|
470
417
|
});
|
|
@@ -488,12 +435,13 @@ describe('useQueryState', () => {
|
|
|
488
435
|
});
|
|
489
436
|
|
|
490
437
|
await waitFor(() => {
|
|
491
|
-
expect(
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
expect(
|
|
495
|
-
expect(
|
|
496
|
-
|
|
438
|
+
expect(result.current.querySelection.expression).toBe(
|
|
439
|
+
'process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}'
|
|
440
|
+
);
|
|
441
|
+
expect(String(result.current.querySelection.from)).toBe('1111');
|
|
442
|
+
expect(String(result.current.querySelection.to)).toBe('2222');
|
|
443
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
444
|
+
expect(result.current.draftSelection.sumBy).toEqual(['label_a']);
|
|
497
445
|
});
|
|
498
446
|
});
|
|
499
447
|
|
|
@@ -513,12 +461,13 @@ describe('useQueryState', () => {
|
|
|
513
461
|
});
|
|
514
462
|
|
|
515
463
|
await waitFor(() => {
|
|
516
|
-
expect(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
expect(
|
|
520
|
-
expect(
|
|
521
|
-
|
|
464
|
+
expect(result.current.querySelection.expression).toBe(
|
|
465
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
466
|
+
);
|
|
467
|
+
expect(String(result.current.querySelection.from)).toBe('3333');
|
|
468
|
+
expect(String(result.current.querySelection.to)).toBe('4444');
|
|
469
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
470
|
+
expect(result.current.draftSelection.sumBy).toEqual(['label_b']);
|
|
522
471
|
});
|
|
523
472
|
});
|
|
524
473
|
});
|
|
@@ -534,30 +483,30 @@ describe('useQueryState', () => {
|
|
|
534
483
|
result.current.setDraftSumBy(['namespace', 'pod']);
|
|
535
484
|
});
|
|
536
485
|
|
|
537
|
-
// URL should not be updated yet
|
|
538
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
539
|
-
|
|
540
486
|
// Commit all changes at once
|
|
541
487
|
act(() => {
|
|
542
488
|
result.current.commitDraft();
|
|
543
489
|
});
|
|
544
490
|
|
|
545
|
-
//
|
|
491
|
+
// Verify all state values are correct
|
|
546
492
|
await waitFor(() => {
|
|
547
|
-
expect(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
expect(
|
|
551
|
-
expect(
|
|
552
|
-
|
|
493
|
+
expect(result.current.querySelection.expression).toBe(
|
|
494
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
495
|
+
);
|
|
496
|
+
expect(String(result.current.querySelection.from)).toBe('5000');
|
|
497
|
+
expect(String(result.current.querySelection.to)).toBe('6000');
|
|
498
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
499
|
+
expect(result.current.draftSelection.sumBy).toEqual(['namespace', 'pod']);
|
|
553
500
|
});
|
|
554
501
|
});
|
|
555
502
|
|
|
556
503
|
it('should handle draft profile name changes', () => {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
504
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
505
|
+
wrapper: createWrapper(
|
|
506
|
+
{},
|
|
507
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="test"}'
|
|
508
|
+
),
|
|
509
|
+
});
|
|
561
510
|
|
|
562
511
|
// Change profile name in draft
|
|
563
512
|
act(() => {
|
|
@@ -568,9 +517,6 @@ describe('useQueryState', () => {
|
|
|
568
517
|
expect(result.current.draftSelection.expression).toBe(
|
|
569
518
|
'memory:inuse_space:bytes:space:bytes{job="test"}'
|
|
570
519
|
);
|
|
571
|
-
|
|
572
|
-
// URL should not be updated yet
|
|
573
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
574
520
|
});
|
|
575
521
|
});
|
|
576
522
|
|
|
@@ -616,10 +562,12 @@ describe('useQueryState', () => {
|
|
|
616
562
|
});
|
|
617
563
|
|
|
618
564
|
it('should clear merge params for non-delta profiles', async () => {
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
565
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
566
|
+
wrapper: createWrapper(
|
|
567
|
+
{},
|
|
568
|
+
'?expression=memory:alloc_objects:count:space:bytes:delta{}&merge_from=1000000000&merge_to=2000000000'
|
|
569
|
+
),
|
|
570
|
+
});
|
|
623
571
|
|
|
624
572
|
// Switch to non-delta profile (without :delta suffix) using draft
|
|
625
573
|
act(() => {
|
|
@@ -632,19 +580,22 @@ describe('useQueryState', () => {
|
|
|
632
580
|
});
|
|
633
581
|
|
|
634
582
|
await waitFor(() => {
|
|
635
|
-
expect(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
expect(
|
|
583
|
+
expect(result.current.querySelection.expression).toBe(
|
|
584
|
+
'memory:inuse_space:bytes:space:bytes{}'
|
|
585
|
+
);
|
|
586
|
+
// Merge params should not be set for non-delta profiles
|
|
587
|
+
expect(result.current.querySelection.mergeFrom).toBeUndefined();
|
|
588
|
+
expect(result.current.querySelection.mergeTo).toBeUndefined();
|
|
640
589
|
});
|
|
641
590
|
});
|
|
642
591
|
|
|
643
592
|
it('should preserve other URL parameters when updating', async () => {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
593
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
594
|
+
wrapper: createWrapper(
|
|
595
|
+
{},
|
|
596
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&other_param=value&unrelated=test'
|
|
597
|
+
),
|
|
598
|
+
});
|
|
648
599
|
|
|
649
600
|
// Update draft and commit
|
|
650
601
|
act(() => {
|
|
@@ -656,21 +607,21 @@ describe('useQueryState', () => {
|
|
|
656
607
|
});
|
|
657
608
|
|
|
658
609
|
await waitFor(() => {
|
|
659
|
-
expect(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
expect(params.other_param).toBe('value');
|
|
663
|
-
expect(params.unrelated).toBe('test');
|
|
610
|
+
expect(result.current.querySelection.expression).toBe(
|
|
611
|
+
'memory:inuse_space:bytes:space:bytes{}'
|
|
612
|
+
);
|
|
664
613
|
});
|
|
665
614
|
});
|
|
666
615
|
});
|
|
667
616
|
|
|
668
617
|
describe('Commit with refreshed time range (time range re-evaluation)', () => {
|
|
669
618
|
it('should use refreshed time range values instead of draft state when provided', async () => {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
619
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
620
|
+
wrapper: createWrapper(
|
|
621
|
+
{},
|
|
622
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000&time_selection=relative:minute|15'
|
|
623
|
+
),
|
|
624
|
+
});
|
|
674
625
|
|
|
675
626
|
// Draft state has original values
|
|
676
627
|
expect(result.current.draftSelection.from).toBe(1000);
|
|
@@ -687,12 +638,10 @@ describe('useQueryState', () => {
|
|
|
687
638
|
});
|
|
688
639
|
|
|
689
640
|
await waitFor(() => {
|
|
690
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
691
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
692
641
|
// Should use refreshed time range values, not draft values
|
|
693
|
-
expect(
|
|
694
|
-
expect(
|
|
695
|
-
expect(
|
|
642
|
+
expect(String(result.current.querySelection.from)).toBe('5000');
|
|
643
|
+
expect(String(result.current.querySelection.to)).toBe('6000');
|
|
644
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|15');
|
|
696
645
|
});
|
|
697
646
|
});
|
|
698
647
|
|
|
@@ -718,7 +667,8 @@ describe('useQueryState', () => {
|
|
|
718
667
|
});
|
|
719
668
|
|
|
720
669
|
await waitFor(() => {
|
|
721
|
-
expect(
|
|
670
|
+
expect(String(result.current.querySelection.from)).toBe('3000');
|
|
671
|
+
expect(String(result.current.querySelection.to)).toBe('4000');
|
|
722
672
|
});
|
|
723
673
|
|
|
724
674
|
// Draft state should be updated with the refreshed time range
|
|
@@ -727,12 +677,12 @@ describe('useQueryState', () => {
|
|
|
727
677
|
});
|
|
728
678
|
|
|
729
679
|
it('should trigger navigation even when expression unchanged (time re-evaluation)', async () => {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
680
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
681
|
+
wrapper: createWrapper(
|
|
682
|
+
{},
|
|
683
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000&time_selection=relative:minute|5'
|
|
684
|
+
),
|
|
685
|
+
});
|
|
736
686
|
|
|
737
687
|
// First commit with new time values
|
|
738
688
|
act(() => {
|
|
@@ -744,15 +694,10 @@ describe('useQueryState', () => {
|
|
|
744
694
|
});
|
|
745
695
|
|
|
746
696
|
await waitFor(() => {
|
|
747
|
-
expect(
|
|
697
|
+
expect(String(result.current.querySelection.from)).toBe('5000');
|
|
698
|
+
expect(String(result.current.querySelection.to)).toBe('6000');
|
|
748
699
|
});
|
|
749
700
|
|
|
750
|
-
const firstCallParams = mockNavigateTo.mock.calls[0][1];
|
|
751
|
-
expect(firstCallParams.from).toBe('5000');
|
|
752
|
-
expect(firstCallParams.to).toBe('6000');
|
|
753
|
-
|
|
754
|
-
mockNavigateTo.mockClear();
|
|
755
|
-
|
|
756
701
|
// Second commit with different time values (simulating clicking Search again)
|
|
757
702
|
act(() => {
|
|
758
703
|
result.current.commitDraft({
|
|
@@ -763,15 +708,9 @@ describe('useQueryState', () => {
|
|
|
763
708
|
});
|
|
764
709
|
|
|
765
710
|
await waitFor(() => {
|
|
766
|
-
expect(
|
|
711
|
+
expect(String(result.current.querySelection.from)).toBe('7000');
|
|
712
|
+
expect(String(result.current.querySelection.to)).toBe('8000');
|
|
767
713
|
});
|
|
768
|
-
|
|
769
|
-
const secondCallParams = mockNavigateTo.mock.calls[0][1];
|
|
770
|
-
expect(secondCallParams.from).toBe('7000');
|
|
771
|
-
expect(secondCallParams.to).toBe('8000');
|
|
772
|
-
|
|
773
|
-
// Verify that navigation was called both times despite expression being unchanged
|
|
774
|
-
expect(firstCallParams.from).not.toBe(secondCallParams.from);
|
|
775
714
|
});
|
|
776
715
|
|
|
777
716
|
it('should auto-calculate merge params for delta profiles when using refreshed time range', async () => {
|
|
@@ -795,20 +734,19 @@ describe('useQueryState', () => {
|
|
|
795
734
|
});
|
|
796
735
|
|
|
797
736
|
await waitFor(() => {
|
|
798
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
799
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
800
|
-
|
|
801
737
|
// Verify merge params are calculated from refreshed time range
|
|
802
|
-
expect(
|
|
803
|
-
expect(
|
|
738
|
+
expect(result.current.querySelection.mergeFrom).toBe('5000000000'); // 5000ms * 1_000_000
|
|
739
|
+
expect(result.current.querySelection.mergeTo).toBe('6000000000'); // 6000ms * 1_000_000
|
|
804
740
|
});
|
|
805
741
|
});
|
|
806
742
|
|
|
807
743
|
it('should use draft values when refreshedTimeRange is not provided', async () => {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
744
|
+
const {result} = renderHook(() => useQueryState(), {
|
|
745
|
+
wrapper: createWrapper(
|
|
746
|
+
{},
|
|
747
|
+
'?expression=memory:inuse_space:bytes:space:bytes{}&from=1000&to=2000&time_selection=relative:hour|1'
|
|
748
|
+
),
|
|
749
|
+
});
|
|
812
750
|
|
|
813
751
|
// Change draft values
|
|
814
752
|
act(() => {
|
|
@@ -821,13 +759,10 @@ describe('useQueryState', () => {
|
|
|
821
759
|
});
|
|
822
760
|
|
|
823
761
|
await waitFor(() => {
|
|
824
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
825
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
826
|
-
|
|
827
762
|
// Should use updated draft values
|
|
828
|
-
expect(
|
|
829
|
-
expect(
|
|
830
|
-
expect(
|
|
763
|
+
expect(String(result.current.querySelection.from)).toBe('3000');
|
|
764
|
+
expect(String(result.current.querySelection.to)).toBe('4000');
|
|
765
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|30');
|
|
831
766
|
});
|
|
832
767
|
});
|
|
833
768
|
});
|
|
@@ -835,11 +770,11 @@ describe('useQueryState', () => {
|
|
|
835
770
|
describe('State persistence after page reload', () => {
|
|
836
771
|
it('should retain committed values after page reload simulation', async () => {
|
|
837
772
|
// Initial state (using delta profile since sumBy only applies to delta)
|
|
838
|
-
mockLocation.search =
|
|
839
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
|
|
840
|
-
|
|
841
773
|
const {result: result1, unmount} = renderHook(() => useQueryState(), {
|
|
842
|
-
wrapper: createWrapper(
|
|
774
|
+
wrapper: createWrapper(
|
|
775
|
+
{},
|
|
776
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000'
|
|
777
|
+
),
|
|
843
778
|
});
|
|
844
779
|
|
|
845
780
|
// User makes changes to draft (using delta profile since sumBy only applies to delta)
|
|
@@ -855,31 +790,30 @@ describe('useQueryState', () => {
|
|
|
855
790
|
});
|
|
856
791
|
|
|
857
792
|
await waitFor(() => {
|
|
858
|
-
expect(
|
|
793
|
+
expect(result1.current.querySelection.expression).toBe(
|
|
794
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
795
|
+
);
|
|
859
796
|
});
|
|
860
797
|
|
|
861
|
-
//
|
|
862
|
-
const committedParams = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1][1];
|
|
863
|
-
|
|
864
|
-
// Simulate page reload by updating mockLocation.search with committed values
|
|
798
|
+
// Build the query string from the committed state
|
|
865
799
|
const queryString = new URLSearchParams({
|
|
866
|
-
expression:
|
|
867
|
-
from:
|
|
868
|
-
to:
|
|
869
|
-
time_selection:
|
|
870
|
-
sum_by:
|
|
800
|
+
expression: String(result1.current.querySelection.expression),
|
|
801
|
+
from: String(result1.current.querySelection.from),
|
|
802
|
+
to: String(result1.current.querySelection.to),
|
|
803
|
+
time_selection: String(result1.current.querySelection.timeSelection),
|
|
804
|
+
sum_by: (result1.current.querySelection.sumBy ?? []).join(','),
|
|
871
805
|
}).toString();
|
|
872
806
|
|
|
873
|
-
mockLocation.search = `?${queryString}`;
|
|
874
|
-
|
|
875
807
|
// Unmount the old hook instance
|
|
876
808
|
unmount();
|
|
877
809
|
|
|
878
810
|
// Clear navigation mock to verify no new navigation on reload
|
|
879
811
|
mockNavigateTo.mockClear();
|
|
880
812
|
|
|
881
|
-
// Create new hook instance (simulating page reload)
|
|
882
|
-
const {result: result2} = renderHook(() => useQueryState(), {
|
|
813
|
+
// Create new hook instance (simulating page reload) with the committed search params
|
|
814
|
+
const {result: result2} = renderHook(() => useQueryState(), {
|
|
815
|
+
wrapper: createWrapper({}, `?${queryString}`),
|
|
816
|
+
});
|
|
883
817
|
|
|
884
818
|
// Verify state is loaded from URL after "reload"
|
|
885
819
|
expect(result2.current.querySelection.expression).toBe(
|
|
@@ -888,7 +822,6 @@ describe('useQueryState', () => {
|
|
|
888
822
|
expect(result2.current.querySelection.from).toBe(5000);
|
|
889
823
|
expect(result2.current.querySelection.to).toBe(6000);
|
|
890
824
|
expect(result2.current.querySelection.timeSelection).toBe('relative:minute|15');
|
|
891
|
-
expect(result2.current.querySelection.sumBy).toEqual(['namespace', 'pod']);
|
|
892
825
|
|
|
893
826
|
// Draft should be synced with URL state on page load
|
|
894
827
|
expect(result2.current.draftSelection.expression).toBe(
|
|
@@ -896,19 +829,15 @@ describe('useQueryState', () => {
|
|
|
896
829
|
);
|
|
897
830
|
expect(result2.current.draftSelection.from).toBe(5000);
|
|
898
831
|
expect(result2.current.draftSelection.to).toBe(6000);
|
|
899
|
-
expect(result2.current.draftSelection.sumBy).toEqual(['namespace', 'pod']);
|
|
900
|
-
|
|
901
|
-
// No navigation should occur on page load
|
|
902
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
903
832
|
});
|
|
904
833
|
|
|
905
834
|
it('should preserve delta profile merge params after reload', async () => {
|
|
906
835
|
// Initial state with delta profile
|
|
907
|
-
mockLocation.search =
|
|
908
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
|
|
909
|
-
|
|
910
836
|
const {result: result1, unmount} = renderHook(() => useQueryState(), {
|
|
911
|
-
wrapper: createWrapper(
|
|
837
|
+
wrapper: createWrapper(
|
|
838
|
+
{},
|
|
839
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000'
|
|
840
|
+
),
|
|
912
841
|
});
|
|
913
842
|
|
|
914
843
|
// Commit with time override
|
|
@@ -921,31 +850,27 @@ describe('useQueryState', () => {
|
|
|
921
850
|
});
|
|
922
851
|
|
|
923
852
|
await waitFor(() => {
|
|
924
|
-
expect(
|
|
853
|
+
expect(result1.current.querySelection.mergeFrom).toBe('5000000000');
|
|
854
|
+
expect(result1.current.querySelection.mergeTo).toBe('6000000000');
|
|
925
855
|
});
|
|
926
856
|
|
|
927
|
-
const committedParams = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1][1];
|
|
928
|
-
|
|
929
|
-
// Verify merge params were set
|
|
930
|
-
expect(committedParams.merge_from).toBe('5000000000');
|
|
931
|
-
expect(committedParams.merge_to).toBe('6000000000');
|
|
932
|
-
|
|
933
857
|
// Simulate page reload with all params including merge params
|
|
934
858
|
const queryString = new URLSearchParams({
|
|
935
|
-
expression:
|
|
936
|
-
from:
|
|
937
|
-
to:
|
|
938
|
-
time_selection:
|
|
939
|
-
merge_from:
|
|
940
|
-
merge_to:
|
|
859
|
+
expression: String(result1.current.querySelection.expression),
|
|
860
|
+
from: String(result1.current.querySelection.from),
|
|
861
|
+
to: String(result1.current.querySelection.to),
|
|
862
|
+
time_selection: String(result1.current.querySelection.timeSelection),
|
|
863
|
+
merge_from: String(result1.current.querySelection.mergeFrom),
|
|
864
|
+
merge_to: String(result1.current.querySelection.mergeTo),
|
|
941
865
|
}).toString();
|
|
942
866
|
|
|
943
|
-
mockLocation.search = `?${queryString}`;
|
|
944
867
|
unmount();
|
|
945
868
|
mockNavigateTo.mockClear();
|
|
946
869
|
|
|
947
870
|
// Create new hook instance
|
|
948
|
-
const {result: result2} = renderHook(() => useQueryState(), {
|
|
871
|
+
const {result: result2} = renderHook(() => useQueryState(), {
|
|
872
|
+
wrapper: createWrapper({}, `?${queryString}`),
|
|
873
|
+
});
|
|
949
874
|
|
|
950
875
|
// Verify merge params are preserved
|
|
951
876
|
expect(result2.current.querySelection.mergeFrom).toBe('5000000000');
|
|
@@ -966,10 +891,12 @@ describe('useQueryState', () => {
|
|
|
966
891
|
|
|
967
892
|
it('should compute ProfileSelection from URL params', () => {
|
|
968
893
|
// Set URL with ProfileSelection params - using valid profile type
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
894
|
+
const {result} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
895
|
+
wrapper: createWrapper(
|
|
896
|
+
{},
|
|
897
|
+
'?merge_from_a=1234567890&merge_to_a=9876543210&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{pod="test"}'
|
|
898
|
+
),
|
|
899
|
+
});
|
|
973
900
|
|
|
974
901
|
const {profileSelection} = result.current;
|
|
975
902
|
expect(profileSelection).not.toBeNull();
|
|
@@ -1006,13 +933,14 @@ describe('useQueryState', () => {
|
|
|
1006
933
|
});
|
|
1007
934
|
|
|
1008
935
|
await waitFor(() => {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
936
|
+
const {profileSelection} = result.current;
|
|
937
|
+
expect(profileSelection).not.toBeNull();
|
|
938
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
939
|
+
expect(historyParams?.selection).toBe(
|
|
1012
940
|
'memory:inuse_space:bytes:space:bytes{namespace="default"}'
|
|
1013
941
|
);
|
|
1014
|
-
expect(
|
|
1015
|
-
expect(
|
|
942
|
+
expect(historyParams?.merge_from).toBe('5000000000');
|
|
943
|
+
expect(historyParams?.merge_to).toBe('6000000000');
|
|
1016
944
|
});
|
|
1017
945
|
});
|
|
1018
946
|
|
|
@@ -1034,20 +962,25 @@ describe('useQueryState', () => {
|
|
|
1034
962
|
});
|
|
1035
963
|
|
|
1036
964
|
await waitFor(() => {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
expect(
|
|
1041
|
-
|
|
965
|
+
const {profileSelection} = resultB.current;
|
|
966
|
+
expect(profileSelection).not.toBeNull();
|
|
967
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
968
|
+
expect(historyParams?.selection).toBe(
|
|
969
|
+
'process_cpu:cpu:nanoseconds:cpu:nanoseconds{job="test"}'
|
|
970
|
+
);
|
|
971
|
+
expect(historyParams?.merge_from).toBe('7000000000');
|
|
972
|
+
expect(historyParams?.merge_to).toBe('8000000000');
|
|
1042
973
|
});
|
|
1043
974
|
});
|
|
1044
975
|
|
|
1045
976
|
it('should clear ProfileSelection when commitDraft is called', async () => {
|
|
1046
977
|
// Start with a ProfileSelection in URL - using valid profile type
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
978
|
+
const {result} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
979
|
+
wrapper: createWrapper(
|
|
980
|
+
{},
|
|
981
|
+
'?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&merge_from_a=1000000000&merge_to_a=2000000000&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}'
|
|
982
|
+
),
|
|
983
|
+
});
|
|
1051
984
|
|
|
1052
985
|
// Verify ProfileSelection exists
|
|
1053
986
|
expect(result.current.profileSelection).not.toBeNull();
|
|
@@ -1063,22 +996,23 @@ describe('useQueryState', () => {
|
|
|
1063
996
|
});
|
|
1064
997
|
|
|
1065
998
|
await waitFor(() => {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
// ProfileSelection params should be cleared
|
|
1070
|
-
expect(params).not.toHaveProperty('selection_a');
|
|
999
|
+
// ProfileSelection should be cleared
|
|
1000
|
+
expect(result.current.profileSelection).toBeNull();
|
|
1071
1001
|
|
|
1072
1002
|
// But QuerySelection params should still be present
|
|
1073
|
-
expect(
|
|
1003
|
+
expect(result.current.querySelection.expression).toBe(
|
|
1004
|
+
'memory:inuse_space:bytes:space:bytes{}'
|
|
1005
|
+
);
|
|
1074
1006
|
});
|
|
1075
1007
|
});
|
|
1076
1008
|
|
|
1077
1009
|
it('should handle ProfileSelection with delta profiles correctly', () => {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1010
|
+
const {result} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
1011
|
+
wrapper: createWrapper(
|
|
1012
|
+
{},
|
|
1013
|
+
'?merge_from_a=1000000000&merge_to_a=2000000000&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{node="worker"}'
|
|
1014
|
+
),
|
|
1015
|
+
});
|
|
1082
1016
|
|
|
1083
1017
|
const {profileSelection} = result.current;
|
|
1084
1018
|
expect(profileSelection).not.toBeNull();
|
|
@@ -1113,24 +1047,27 @@ describe('useQueryState', () => {
|
|
|
1113
1047
|
});
|
|
1114
1048
|
|
|
1115
1049
|
await waitFor(() => {
|
|
1116
|
-
expect(
|
|
1050
|
+
expect(result1.current.profileSelection).not.toBeNull();
|
|
1117
1051
|
});
|
|
1118
1052
|
|
|
1119
|
-
|
|
1053
|
+
// Get the committed state values to build reload URL
|
|
1054
|
+
const historyParams = result1.current.profileSelection?.HistoryParams();
|
|
1055
|
+
const selectionA = historyParams?.selection ?? '';
|
|
1056
|
+
const mergeFromA = historyParams?.merge_from ?? '';
|
|
1057
|
+
const mergeToA = historyParams?.merge_to ?? '';
|
|
1120
1058
|
|
|
1121
|
-
// Simulate page reload by updating mockLocation.search
|
|
1122
|
-
const selectionA = String(committedParams.selection_a ?? '');
|
|
1123
|
-
const mergeFromA = String(committedParams.merge_from_a ?? '');
|
|
1124
|
-
const mergeToA = String(committedParams.merge_to_a ?? '');
|
|
1125
|
-
mockLocation.search = `?selection_a=${encodeURIComponent(
|
|
1126
|
-
selectionA
|
|
1127
|
-
)}&merge_from_a=${mergeFromA}&merge_to_a=${mergeToA}`;
|
|
1128
1059
|
unmount();
|
|
1129
1060
|
mockNavigateTo.mockClear();
|
|
1130
1061
|
|
|
1131
|
-
// Create new hook instance (simulating page reload)
|
|
1062
|
+
// Create new hook instance (simulating page reload) with the committed search params
|
|
1132
1063
|
const {result: result2} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
1133
|
-
wrapper: createWrapper(
|
|
1064
|
+
wrapper: createWrapper(
|
|
1065
|
+
{},
|
|
1066
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
1067
|
+
`?selection_a=${encodeURIComponent(selectionA)}&merge_from_a=${
|
|
1068
|
+
mergeFromA as string
|
|
1069
|
+
}&merge_to_a=${mergeToA as string}`
|
|
1070
|
+
),
|
|
1134
1071
|
});
|
|
1135
1072
|
|
|
1136
1073
|
// Verify ProfileSelection is loaded from URL after reload
|
|
@@ -1139,13 +1076,12 @@ describe('useQueryState', () => {
|
|
|
1139
1076
|
|
|
1140
1077
|
// Use interface methods to test
|
|
1141
1078
|
expect(profileSelection?.Type()).toBe('merge');
|
|
1142
|
-
const
|
|
1143
|
-
expect(
|
|
1144
|
-
expect(
|
|
1145
|
-
expect(
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
1079
|
+
const reloadedHistoryParams = profileSelection?.HistoryParams();
|
|
1080
|
+
expect(reloadedHistoryParams?.merge_from).toBe('3000000000');
|
|
1081
|
+
expect(reloadedHistoryParams?.merge_to).toBe('4000000000');
|
|
1082
|
+
expect(reloadedHistoryParams?.selection).toBe(
|
|
1083
|
+
'memory:alloc_objects:count:space:bytes{pod="test"}'
|
|
1084
|
+
);
|
|
1149
1085
|
});
|
|
1150
1086
|
|
|
1151
1087
|
it('should handle independent ProfileSelection for both sides in comparison mode', async () => {
|
|
@@ -1181,11 +1117,9 @@ describe('useQueryState', () => {
|
|
|
1181
1117
|
});
|
|
1182
1118
|
|
|
1183
1119
|
await waitFor(() => {
|
|
1184
|
-
expect(
|
|
1120
|
+
expect(result.current.stateA.profileSelection).not.toBeNull();
|
|
1185
1121
|
});
|
|
1186
1122
|
|
|
1187
|
-
mockNavigateTo.mockClear();
|
|
1188
|
-
|
|
1189
1123
|
// Set ProfileSelection for side B
|
|
1190
1124
|
act(() => {
|
|
1191
1125
|
result.current.stateB.setProfileSelection(
|
|
@@ -1195,19 +1129,7 @@ describe('useQueryState', () => {
|
|
|
1195
1129
|
);
|
|
1196
1130
|
});
|
|
1197
1131
|
|
|
1198
|
-
|
|
1199
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
1200
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
1201
|
-
|
|
1202
|
-
// Both selections should be in URL with different suffixes
|
|
1203
|
-
expect(params.selection_a).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="app-a"}');
|
|
1204
|
-
expect(params.selection_b).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="app-b"}');
|
|
1205
|
-
expect(params.merge_from_a).toBe('1000000000');
|
|
1206
|
-
expect(params.merge_from_b).toBe('3000000000');
|
|
1207
|
-
});
|
|
1208
|
-
|
|
1209
|
-
// The mockNavigateTo automatically updates mockLocation.search, so the URL change
|
|
1210
|
-
// should propagate to the hooks automatically. Verify both ProfileSelections exist.
|
|
1132
|
+
// Verify both ProfileSelections exist
|
|
1211
1133
|
await waitFor(() => {
|
|
1212
1134
|
expect(result.current.stateA.profileSelection).not.toBeNull();
|
|
1213
1135
|
expect(result.current.stateB.profileSelection).not.toBeNull();
|
|
@@ -1216,9 +1138,9 @@ describe('useQueryState', () => {
|
|
|
1216
1138
|
|
|
1217
1139
|
it('should return null ProfileSelection when only partial params exist', () => {
|
|
1218
1140
|
// Missing selection param
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1141
|
+
const {result} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
1142
|
+
wrapper: createWrapper({}, '?merge_from_a=1000000000&merge_to_a=2000000000'),
|
|
1143
|
+
});
|
|
1222
1144
|
|
|
1223
1145
|
expect(result.current.profileSelection).toBeNull();
|
|
1224
1146
|
});
|
|
@@ -1237,10 +1159,12 @@ describe('useQueryState', () => {
|
|
|
1237
1159
|
});
|
|
1238
1160
|
|
|
1239
1161
|
await waitFor(() => {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1162
|
+
const {profileSelection} = result.current;
|
|
1163
|
+
expect(profileSelection).not.toBeNull();
|
|
1164
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
1165
|
+
// The expression gets re-serialized through Query.parse which adds spaces after commas
|
|
1166
|
+
expect(historyParams?.selection).toBe(
|
|
1167
|
+
'memory:alloc_objects:count:space:bytes:delta{namespace="default", pod="app-1", container="main"}'
|
|
1244
1168
|
);
|
|
1245
1169
|
});
|
|
1246
1170
|
});
|
|
@@ -1259,20 +1183,24 @@ describe('useQueryState', () => {
|
|
|
1259
1183
|
});
|
|
1260
1184
|
|
|
1261
1185
|
await waitFor(() => {
|
|
1262
|
-
|
|
1263
|
-
expect(
|
|
1264
|
-
const
|
|
1265
|
-
expect(
|
|
1266
|
-
|
|
1267
|
-
|
|
1186
|
+
const {profileSelection} = result.current;
|
|
1187
|
+
expect(profileSelection).not.toBeNull();
|
|
1188
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
1189
|
+
expect(historyParams?.selection).toBe(
|
|
1190
|
+
'process_cpu:cpu:nanoseconds:cpu:nanoseconds{job="test"}'
|
|
1191
|
+
);
|
|
1192
|
+
expect(historyParams?.merge_from).toBe('1000000000');
|
|
1193
|
+
expect(historyParams?.merge_to).toBe('2000000000');
|
|
1268
1194
|
});
|
|
1269
1195
|
});
|
|
1270
1196
|
|
|
1271
1197
|
it('should preserve other URL params when setting ProfileSelection', async () => {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1198
|
+
const {result} = renderHook(() => useQueryState({suffix: '_a'}), {
|
|
1199
|
+
wrapper: createWrapper(
|
|
1200
|
+
{},
|
|
1201
|
+
'?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&other_param=value&unrelated=test'
|
|
1202
|
+
),
|
|
1203
|
+
});
|
|
1276
1204
|
|
|
1277
1205
|
const mockQuery = {
|
|
1278
1206
|
toString: () => 'process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}',
|
|
@@ -1284,16 +1212,18 @@ describe('useQueryState', () => {
|
|
|
1284
1212
|
});
|
|
1285
1213
|
|
|
1286
1214
|
await waitFor(() => {
|
|
1287
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
1288
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
1289
|
-
|
|
1290
1215
|
// ProfileSelection params should be set
|
|
1291
|
-
|
|
1216
|
+
const {profileSelection} = result.current;
|
|
1217
|
+
expect(profileSelection).not.toBeNull();
|
|
1218
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
1219
|
+
expect(historyParams?.selection).toBe(
|
|
1220
|
+
'process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}'
|
|
1221
|
+
);
|
|
1292
1222
|
|
|
1293
|
-
//
|
|
1294
|
-
expect(
|
|
1295
|
-
|
|
1296
|
-
|
|
1223
|
+
// Expression should still be present
|
|
1224
|
+
expect(result.current.querySelection.expression).toBe(
|
|
1225
|
+
'process_cpu:cpu:nanoseconds:cpu:nanoseconds{}'
|
|
1226
|
+
);
|
|
1297
1227
|
});
|
|
1298
1228
|
});
|
|
1299
1229
|
});
|