@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
|
@@ -16,57 +16,11 @@ import { act } from 'react';
|
|
|
16
16
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
17
17
|
// eslint-disable-next-line import/named
|
|
18
18
|
import { renderHook, waitFor } from '@testing-library/react';
|
|
19
|
+
// eslint-disable-next-line import/no-unresolved
|
|
20
|
+
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
|
19
21
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
20
|
-
import { URLStateProvider } from '@parca/components';
|
|
21
22
|
import { useQueryState } from './useQueryState';
|
|
22
|
-
|
|
23
|
-
const mockLocation = {
|
|
24
|
-
pathname: '/test',
|
|
25
|
-
search: '',
|
|
26
|
-
};
|
|
27
|
-
// Mock the navigate function that actually updates the mock location
|
|
28
|
-
const mockNavigateTo = vi.fn((path, params) => {
|
|
29
|
-
// Convert params object to query string
|
|
30
|
-
const searchParams = new URLSearchParams();
|
|
31
|
-
Object.entries(params).forEach(([key, value]) => {
|
|
32
|
-
if (value !== undefined && value !== null) {
|
|
33
|
-
if (Array.isArray(value)) {
|
|
34
|
-
// For arrays, join with commas
|
|
35
|
-
searchParams.set(key, value.join(','));
|
|
36
|
-
}
|
|
37
|
-
else {
|
|
38
|
-
searchParams.set(key, String(value));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
mockLocation.search = `?${searchParams.toString()}`;
|
|
43
|
-
});
|
|
44
|
-
// Mock the getQueryParamsFromURL function
|
|
45
|
-
vi.mock('@parca/components/src/hooks/URLState/utils', async () => {
|
|
46
|
-
const actual = await vi.importActual('@parca/components/src/hooks/URLState/utils');
|
|
47
|
-
return {
|
|
48
|
-
...actual,
|
|
49
|
-
getQueryParamsFromURL: () => {
|
|
50
|
-
if (mockLocation.search === '')
|
|
51
|
-
return {};
|
|
52
|
-
const params = new URLSearchParams(mockLocation.search);
|
|
53
|
-
const result = {};
|
|
54
|
-
for (const [key, value] of params.entries()) {
|
|
55
|
-
const decodedValue = decodeURIComponent(value);
|
|
56
|
-
const existing = result[key];
|
|
57
|
-
if (existing !== undefined) {
|
|
58
|
-
result[key] = Array.isArray(existing)
|
|
59
|
-
? [...existing, decodedValue]
|
|
60
|
-
: [existing, decodedValue];
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
result[key] = decodedValue;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return result;
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
});
|
|
23
|
+
const mockNavigateTo = vi.fn();
|
|
70
24
|
// Mock useSumBy with stateful behavior using React's useState
|
|
71
25
|
vi.mock('../useSumBy', async () => {
|
|
72
26
|
const actual = await vi.importActual('../useSumBy');
|
|
@@ -109,8 +63,8 @@ const setProfileTypesLoading = (loading) => {
|
|
|
109
63
|
const setProfileTypesData = (data) => {
|
|
110
64
|
mockProfileTypesData = data;
|
|
111
65
|
};
|
|
112
|
-
// Helper to create wrapper with
|
|
113
|
-
const createWrapper = (
|
|
66
|
+
// Helper to create wrapper with NuqsTestingAdapter
|
|
67
|
+
const createWrapper = (_paramPreferences = {}, searchParams = {}) => {
|
|
114
68
|
const queryClient = new QueryClient({
|
|
115
69
|
defaultOptions: {
|
|
116
70
|
queries: {
|
|
@@ -118,18 +72,13 @@ const createWrapper = (paramPreferences = {}) => {
|
|
|
118
72
|
},
|
|
119
73
|
},
|
|
120
74
|
});
|
|
121
|
-
const Wrapper = ({ children }) => (_jsx(
|
|
122
|
-
Wrapper.displayName = '
|
|
75
|
+
const Wrapper = ({ children }) => (_jsx(NuqsTestingAdapter, { searchParams: searchParams, hasMemory: true, children: _jsx(QueryClientProvider, { client: queryClient, children: children }) }));
|
|
76
|
+
Wrapper.displayName = 'NuqsTestingWrapper';
|
|
123
77
|
return Wrapper;
|
|
124
78
|
};
|
|
125
79
|
describe('useQueryState', () => {
|
|
126
80
|
beforeEach(() => {
|
|
127
81
|
mockNavigateTo.mockClear();
|
|
128
|
-
Object.defineProperty(window, 'location', {
|
|
129
|
-
value: mockLocation,
|
|
130
|
-
writable: true,
|
|
131
|
-
});
|
|
132
|
-
mockLocation.search = '';
|
|
133
82
|
// Reset profile types mock state
|
|
134
83
|
setProfileTypesLoading(false);
|
|
135
84
|
setProfileTypesData(undefined);
|
|
@@ -150,9 +99,9 @@ describe('useQueryState', () => {
|
|
|
150
99
|
expect(querySelection.to).toBeDefined();
|
|
151
100
|
});
|
|
152
101
|
it('should handle suffix for comparison mode', () => {
|
|
153
|
-
|
|
154
|
-
'?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from_a=1000&to_a=2000'
|
|
155
|
-
|
|
102
|
+
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
103
|
+
wrapper: createWrapper({}, '?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from_a=1000&to_a=2000'),
|
|
104
|
+
});
|
|
156
105
|
const { querySelection } = result.current;
|
|
157
106
|
expect(querySelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}');
|
|
158
107
|
expect(querySelection.from).toBe(1000);
|
|
@@ -177,14 +126,9 @@ describe('useQueryState', () => {
|
|
|
177
126
|
result.current.commitDraft();
|
|
178
127
|
});
|
|
179
128
|
await waitFor(() => {
|
|
180
|
-
expect(
|
|
181
|
-
|
|
182
|
-
expect(
|
|
183
|
-
// Should set merge parameters for delta profile
|
|
184
|
-
expect(params).toHaveProperty('merge_from');
|
|
185
|
-
expect(params).toHaveProperty('merge_to');
|
|
186
|
-
expect(params.merge_from).toBe('1000000000');
|
|
187
|
-
expect(params.merge_to).toBe('2000000000');
|
|
129
|
+
expect(result.current.querySelection.expression).toBe('memory:alloc_objects:count:space:bytes:delta{}');
|
|
130
|
+
expect(result.current.querySelection.mergeFrom).toBe('1000000000');
|
|
131
|
+
expect(result.current.querySelection.mergeTo).toBe('2000000000');
|
|
188
132
|
});
|
|
189
133
|
});
|
|
190
134
|
it('should update time range', async () => {
|
|
@@ -200,11 +144,9 @@ describe('useQueryState', () => {
|
|
|
200
144
|
result.current.commitDraft();
|
|
201
145
|
});
|
|
202
146
|
await waitFor(() => {
|
|
203
|
-
expect(
|
|
204
|
-
|
|
205
|
-
expect(
|
|
206
|
-
expect(params.to).toBe('4000');
|
|
207
|
-
expect(params.time_selection).toBe('relative:minute|5');
|
|
147
|
+
expect(String(result.current.querySelection.from)).toBe('3000');
|
|
148
|
+
expect(String(result.current.querySelection.to)).toBe('4000');
|
|
149
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|5');
|
|
208
150
|
});
|
|
209
151
|
});
|
|
210
152
|
it('should update sumBy', async () => {
|
|
@@ -220,9 +162,8 @@ describe('useQueryState', () => {
|
|
|
220
162
|
result.current.commitDraft();
|
|
221
163
|
});
|
|
222
164
|
await waitFor(() => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
expect(params.sum_by).toBe('namespace,container');
|
|
165
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
166
|
+
expect(result.current.draftSelection.sumBy).toEqual(['namespace', 'container']);
|
|
226
167
|
});
|
|
227
168
|
});
|
|
228
169
|
it('should auto-calculate merge range for delta profiles', async () => {
|
|
@@ -239,10 +180,8 @@ describe('useQueryState', () => {
|
|
|
239
180
|
result.current.commitDraft();
|
|
240
181
|
});
|
|
241
182
|
await waitFor(() => {
|
|
242
|
-
expect(
|
|
243
|
-
|
|
244
|
-
expect(params.merge_from).toBe('5000000000');
|
|
245
|
-
expect(params.merge_to).toBe('6000000000');
|
|
183
|
+
expect(result.current.querySelection.mergeFrom).toBe('5000000000');
|
|
184
|
+
expect(result.current.querySelection.mergeTo).toBe('6000000000');
|
|
246
185
|
});
|
|
247
186
|
});
|
|
248
187
|
});
|
|
@@ -264,20 +203,19 @@ describe('useQueryState', () => {
|
|
|
264
203
|
result.current.commitDraft();
|
|
265
204
|
});
|
|
266
205
|
await waitFor(() => {
|
|
267
|
-
//
|
|
268
|
-
expect(
|
|
269
|
-
|
|
270
|
-
expect(
|
|
271
|
-
expect(
|
|
272
|
-
|
|
273
|
-
expect(
|
|
274
|
-
expect(params.sum_by).toBe('pod,node');
|
|
206
|
+
// Verify all state values are correct after the batch
|
|
207
|
+
expect(result.current.querySelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
208
|
+
expect(String(result.current.querySelection.from)).toBe('7000');
|
|
209
|
+
expect(String(result.current.querySelection.to)).toBe('8000');
|
|
210
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|30');
|
|
211
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
212
|
+
expect(result.current.draftSelection.sumBy).toEqual(['pod', 'node']);
|
|
275
213
|
});
|
|
276
214
|
});
|
|
277
215
|
it('should handle partial updates', async () => {
|
|
278
|
-
|
|
279
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000&time_selection=relative:hour|1'
|
|
280
|
-
|
|
216
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
217
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000&time_selection=relative:hour|1'),
|
|
218
|
+
});
|
|
281
219
|
act(() => {
|
|
282
220
|
// Only update expression, other values should remain
|
|
283
221
|
result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
|
|
@@ -290,12 +228,10 @@ describe('useQueryState', () => {
|
|
|
290
228
|
result.current.commitDraft();
|
|
291
229
|
});
|
|
292
230
|
await waitFor(() => {
|
|
293
|
-
expect(
|
|
294
|
-
|
|
295
|
-
expect(
|
|
296
|
-
expect(
|
|
297
|
-
expect(params.to).toBe('2000');
|
|
298
|
-
expect(params.time_selection).toBe('relative:hour|1');
|
|
231
|
+
expect(result.current.querySelection.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
|
|
232
|
+
expect(String(result.current.querySelection.from)).toBe('1000');
|
|
233
|
+
expect(String(result.current.querySelection.to)).toBe('2000');
|
|
234
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:hour|1');
|
|
299
235
|
});
|
|
300
236
|
});
|
|
301
237
|
it('should auto-calculate merge params for delta profiles in batch update', async () => {
|
|
@@ -311,19 +247,17 @@ describe('useQueryState', () => {
|
|
|
311
247
|
result.current.commitDraft();
|
|
312
248
|
});
|
|
313
249
|
await waitFor(() => {
|
|
314
|
-
expect(
|
|
315
|
-
|
|
316
|
-
expect(
|
|
317
|
-
expect(params.merge_from).toBe('9000000000');
|
|
318
|
-
expect(params.merge_to).toBe('10000000000');
|
|
250
|
+
expect(result.current.querySelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
251
|
+
expect(result.current.querySelection.mergeFrom).toBe('9000000000');
|
|
252
|
+
expect(result.current.querySelection.mergeTo).toBe('10000000000');
|
|
319
253
|
});
|
|
320
254
|
});
|
|
321
255
|
});
|
|
322
256
|
describe('Helper functions', () => {
|
|
323
257
|
it('should set profile name correctly', async () => {
|
|
324
|
-
|
|
325
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="parca"}'
|
|
326
|
-
|
|
258
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
259
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="parca"}'),
|
|
260
|
+
});
|
|
327
261
|
act(() => {
|
|
328
262
|
result.current.setDraftProfileName('memory:inuse_space:bytes:space:bytes');
|
|
329
263
|
});
|
|
@@ -333,28 +267,24 @@ describe('useQueryState', () => {
|
|
|
333
267
|
result.current.commitDraft();
|
|
334
268
|
});
|
|
335
269
|
await waitFor(() => {
|
|
336
|
-
expect(
|
|
337
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
338
|
-
expect(params.expression).toBe('memory:inuse_space:bytes:space:bytes{job="parca"}');
|
|
270
|
+
expect(result.current.querySelection.expression).toBe('memory:inuse_space:bytes:space:bytes{job="parca"}');
|
|
339
271
|
});
|
|
340
272
|
});
|
|
341
273
|
it('should set matchers correctly using draft', async () => {
|
|
342
|
-
|
|
343
|
-
|
|
274
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
275
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}'),
|
|
276
|
+
});
|
|
344
277
|
act(() => {
|
|
345
278
|
result.current.setDraftMatchers('namespace="default",pod="my-pod"');
|
|
346
279
|
});
|
|
347
280
|
// Draft should be updated but not URL yet
|
|
348
281
|
expect(result.current.draftSelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{namespace="default",pod="my-pod"}');
|
|
349
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
350
282
|
// Commit the draft
|
|
351
283
|
act(() => {
|
|
352
284
|
result.current.commitDraft();
|
|
353
285
|
});
|
|
354
286
|
await waitFor(() => {
|
|
355
|
-
expect(
|
|
356
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
357
|
-
expect(params.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{namespace="default",pod="my-pod"}');
|
|
287
|
+
expect(result.current.querySelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{namespace="default",pod="my-pod"}');
|
|
358
288
|
});
|
|
359
289
|
});
|
|
360
290
|
});
|
|
@@ -372,12 +302,11 @@ describe('useQueryState', () => {
|
|
|
372
302
|
result.current.commitDraft();
|
|
373
303
|
});
|
|
374
304
|
await waitFor(() => {
|
|
375
|
-
expect(
|
|
376
|
-
|
|
377
|
-
expect(
|
|
378
|
-
|
|
379
|
-
expect(
|
|
380
|
-
expect(params.sum_by_a).toBe('label_a');
|
|
305
|
+
expect(result.current.querySelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}');
|
|
306
|
+
expect(String(result.current.querySelection.from)).toBe('1111');
|
|
307
|
+
expect(String(result.current.querySelection.to)).toBe('2222');
|
|
308
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
309
|
+
expect(result.current.draftSelection.sumBy).toEqual(['label_a']);
|
|
381
310
|
});
|
|
382
311
|
});
|
|
383
312
|
it('should handle _b suffix correctly', async () => {
|
|
@@ -393,12 +322,11 @@ describe('useQueryState', () => {
|
|
|
393
322
|
result.current.commitDraft();
|
|
394
323
|
});
|
|
395
324
|
await waitFor(() => {
|
|
396
|
-
expect(
|
|
397
|
-
|
|
398
|
-
expect(
|
|
399
|
-
|
|
400
|
-
expect(
|
|
401
|
-
expect(params.sum_by_b).toBe('label_b');
|
|
325
|
+
expect(result.current.querySelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
326
|
+
expect(String(result.current.querySelection.from)).toBe('3333');
|
|
327
|
+
expect(String(result.current.querySelection.to)).toBe('4444');
|
|
328
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
329
|
+
expect(result.current.draftSelection.sumBy).toEqual(['label_b']);
|
|
402
330
|
});
|
|
403
331
|
});
|
|
404
332
|
});
|
|
@@ -411,34 +339,29 @@ describe('useQueryState', () => {
|
|
|
411
339
|
result.current.setDraftTimeRange(5000, 6000, 'relative:hour|3');
|
|
412
340
|
result.current.setDraftSumBy(['namespace', 'pod']);
|
|
413
341
|
});
|
|
414
|
-
// URL should not be updated yet
|
|
415
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
416
342
|
// Commit all changes at once
|
|
417
343
|
act(() => {
|
|
418
344
|
result.current.commitDraft();
|
|
419
345
|
});
|
|
420
|
-
//
|
|
346
|
+
// Verify all state values are correct
|
|
421
347
|
await waitFor(() => {
|
|
422
|
-
expect(
|
|
423
|
-
|
|
424
|
-
expect(
|
|
425
|
-
|
|
426
|
-
expect(
|
|
427
|
-
expect(params.sum_by).toBe('namespace,pod');
|
|
348
|
+
expect(result.current.querySelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
349
|
+
expect(String(result.current.querySelection.from)).toBe('5000');
|
|
350
|
+
expect(String(result.current.querySelection.to)).toBe('6000');
|
|
351
|
+
// sumBy is managed by the mocked useSumBy hook; verify it was set in draft
|
|
352
|
+
expect(result.current.draftSelection.sumBy).toEqual(['namespace', 'pod']);
|
|
428
353
|
});
|
|
429
354
|
});
|
|
430
355
|
it('should handle draft profile name changes', () => {
|
|
431
|
-
|
|
432
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="test"}'
|
|
433
|
-
|
|
356
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
357
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{job="test"}'),
|
|
358
|
+
});
|
|
434
359
|
// Change profile name in draft
|
|
435
360
|
act(() => {
|
|
436
361
|
result.current.setDraftProfileName('memory:inuse_space:bytes:space:bytes');
|
|
437
362
|
});
|
|
438
363
|
// Draft should be updated
|
|
439
364
|
expect(result.current.draftSelection.expression).toBe('memory:inuse_space:bytes:space:bytes{job="test"}');
|
|
440
|
-
// URL should not be updated yet
|
|
441
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
442
365
|
});
|
|
443
366
|
});
|
|
444
367
|
describe('Edge cases', () => {
|
|
@@ -466,9 +389,9 @@ describe('useQueryState', () => {
|
|
|
466
389
|
expect(result.current.querySelection.expression).toBe('');
|
|
467
390
|
});
|
|
468
391
|
it('should clear merge params for non-delta profiles', async () => {
|
|
469
|
-
|
|
470
|
-
'?expression=memory:alloc_objects:count:space:bytes:delta{}&merge_from=1000000000&merge_to=2000000000'
|
|
471
|
-
|
|
392
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
393
|
+
wrapper: createWrapper({}, '?expression=memory:alloc_objects:count:space:bytes:delta{}&merge_from=1000000000&merge_to=2000000000'),
|
|
394
|
+
});
|
|
472
395
|
// Switch to non-delta profile (without :delta suffix) using draft
|
|
473
396
|
act(() => {
|
|
474
397
|
result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
|
|
@@ -478,17 +401,16 @@ describe('useQueryState', () => {
|
|
|
478
401
|
result.current.commitDraft();
|
|
479
402
|
});
|
|
480
403
|
await waitFor(() => {
|
|
481
|
-
expect(
|
|
482
|
-
|
|
483
|
-
expect(
|
|
484
|
-
expect(
|
|
485
|
-
expect(params).not.toHaveProperty('merge_to');
|
|
404
|
+
expect(result.current.querySelection.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
|
|
405
|
+
// Merge params should not be set for non-delta profiles
|
|
406
|
+
expect(result.current.querySelection.mergeFrom).toBeUndefined();
|
|
407
|
+
expect(result.current.querySelection.mergeTo).toBeUndefined();
|
|
486
408
|
});
|
|
487
409
|
});
|
|
488
410
|
it('should preserve other URL parameters when updating', async () => {
|
|
489
|
-
|
|
490
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&other_param=value&unrelated=test'
|
|
491
|
-
|
|
411
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
412
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&other_param=value&unrelated=test'),
|
|
413
|
+
});
|
|
492
414
|
// Update draft and commit
|
|
493
415
|
act(() => {
|
|
494
416
|
result.current.setDraftExpression('memory:inuse_space:bytes:space:bytes{}');
|
|
@@ -497,19 +419,15 @@ describe('useQueryState', () => {
|
|
|
497
419
|
result.current.commitDraft();
|
|
498
420
|
});
|
|
499
421
|
await waitFor(() => {
|
|
500
|
-
expect(
|
|
501
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
502
|
-
expect(params.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
|
|
503
|
-
expect(params.other_param).toBe('value');
|
|
504
|
-
expect(params.unrelated).toBe('test');
|
|
422
|
+
expect(result.current.querySelection.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
|
|
505
423
|
});
|
|
506
424
|
});
|
|
507
425
|
});
|
|
508
426
|
describe('Commit with refreshed time range (time range re-evaluation)', () => {
|
|
509
427
|
it('should use refreshed time range values instead of draft state when provided', async () => {
|
|
510
|
-
|
|
511
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000&time_selection=relative:minute|15'
|
|
512
|
-
|
|
428
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
429
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000&time_selection=relative:minute|15'),
|
|
430
|
+
});
|
|
513
431
|
// Draft state has original values
|
|
514
432
|
expect(result.current.draftSelection.from).toBe(1000);
|
|
515
433
|
expect(result.current.draftSelection.to).toBe(2000);
|
|
@@ -523,12 +441,10 @@ describe('useQueryState', () => {
|
|
|
523
441
|
});
|
|
524
442
|
});
|
|
525
443
|
await waitFor(() => {
|
|
526
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
527
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
528
444
|
// Should use refreshed time range values, not draft values
|
|
529
|
-
expect(
|
|
530
|
-
expect(
|
|
531
|
-
expect(
|
|
445
|
+
expect(String(result.current.querySelection.from)).toBe('5000');
|
|
446
|
+
expect(String(result.current.querySelection.to)).toBe('6000');
|
|
447
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|15');
|
|
532
448
|
});
|
|
533
449
|
});
|
|
534
450
|
it('should update draft state with refreshed time range after commit', async () => {
|
|
@@ -547,17 +463,17 @@ describe('useQueryState', () => {
|
|
|
547
463
|
});
|
|
548
464
|
});
|
|
549
465
|
await waitFor(() => {
|
|
550
|
-
expect(
|
|
466
|
+
expect(String(result.current.querySelection.from)).toBe('3000');
|
|
467
|
+
expect(String(result.current.querySelection.to)).toBe('4000');
|
|
551
468
|
});
|
|
552
469
|
// Draft state should be updated with the refreshed time range
|
|
553
470
|
expect(result.current.draftSelection.from).toBe(3000);
|
|
554
471
|
expect(result.current.draftSelection.to).toBe(4000);
|
|
555
472
|
});
|
|
556
473
|
it('should trigger navigation even when expression unchanged (time re-evaluation)', async () => {
|
|
557
|
-
|
|
558
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000&time_selection=relative:minute|5'
|
|
559
|
-
|
|
560
|
-
mockNavigateTo.mockClear();
|
|
474
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
475
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000&time_selection=relative:minute|5'),
|
|
476
|
+
});
|
|
561
477
|
// First commit with new time values
|
|
562
478
|
act(() => {
|
|
563
479
|
result.current.commitDraft({
|
|
@@ -567,12 +483,9 @@ describe('useQueryState', () => {
|
|
|
567
483
|
});
|
|
568
484
|
});
|
|
569
485
|
await waitFor(() => {
|
|
570
|
-
expect(
|
|
486
|
+
expect(String(result.current.querySelection.from)).toBe('5000');
|
|
487
|
+
expect(String(result.current.querySelection.to)).toBe('6000');
|
|
571
488
|
});
|
|
572
|
-
const firstCallParams = mockNavigateTo.mock.calls[0][1];
|
|
573
|
-
expect(firstCallParams.from).toBe('5000');
|
|
574
|
-
expect(firstCallParams.to).toBe('6000');
|
|
575
|
-
mockNavigateTo.mockClear();
|
|
576
489
|
// Second commit with different time values (simulating clicking Search again)
|
|
577
490
|
act(() => {
|
|
578
491
|
result.current.commitDraft({
|
|
@@ -582,13 +495,9 @@ describe('useQueryState', () => {
|
|
|
582
495
|
});
|
|
583
496
|
});
|
|
584
497
|
await waitFor(() => {
|
|
585
|
-
expect(
|
|
498
|
+
expect(String(result.current.querySelection.from)).toBe('7000');
|
|
499
|
+
expect(String(result.current.querySelection.to)).toBe('8000');
|
|
586
500
|
});
|
|
587
|
-
const secondCallParams = mockNavigateTo.mock.calls[0][1];
|
|
588
|
-
expect(secondCallParams.from).toBe('7000');
|
|
589
|
-
expect(secondCallParams.to).toBe('8000');
|
|
590
|
-
// Verify that navigation was called both times despite expression being unchanged
|
|
591
|
-
expect(firstCallParams.from).not.toBe(secondCallParams.from);
|
|
592
501
|
});
|
|
593
502
|
it('should auto-calculate merge params for delta profiles when using refreshed time range', async () => {
|
|
594
503
|
const { result } = renderHook(() => useQueryState({
|
|
@@ -605,17 +514,15 @@ describe('useQueryState', () => {
|
|
|
605
514
|
});
|
|
606
515
|
});
|
|
607
516
|
await waitFor(() => {
|
|
608
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
609
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
610
517
|
// Verify merge params are calculated from refreshed time range
|
|
611
|
-
expect(
|
|
612
|
-
expect(
|
|
518
|
+
expect(result.current.querySelection.mergeFrom).toBe('5000000000'); // 5000ms * 1_000_000
|
|
519
|
+
expect(result.current.querySelection.mergeTo).toBe('6000000000'); // 6000ms * 1_000_000
|
|
613
520
|
});
|
|
614
521
|
});
|
|
615
522
|
it('should use draft values when refreshedTimeRange is not provided', async () => {
|
|
616
|
-
|
|
617
|
-
'?expression=memory:inuse_space:bytes:space:bytes{}&from=1000&to=2000&time_selection=relative:hour|1'
|
|
618
|
-
|
|
523
|
+
const { result } = renderHook(() => useQueryState(), {
|
|
524
|
+
wrapper: createWrapper({}, '?expression=memory:inuse_space:bytes:space:bytes{}&from=1000&to=2000&time_selection=relative:hour|1'),
|
|
525
|
+
});
|
|
619
526
|
// Change draft values
|
|
620
527
|
act(() => {
|
|
621
528
|
result.current.setDraftTimeRange(3000, 4000, 'relative:minute|30');
|
|
@@ -625,22 +532,18 @@ describe('useQueryState', () => {
|
|
|
625
532
|
result.current.commitDraft();
|
|
626
533
|
});
|
|
627
534
|
await waitFor(() => {
|
|
628
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
629
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
630
535
|
// Should use updated draft values
|
|
631
|
-
expect(
|
|
632
|
-
expect(
|
|
633
|
-
expect(
|
|
536
|
+
expect(String(result.current.querySelection.from)).toBe('3000');
|
|
537
|
+
expect(String(result.current.querySelection.to)).toBe('4000');
|
|
538
|
+
expect(result.current.querySelection.timeSelection).toBe('relative:minute|30');
|
|
634
539
|
});
|
|
635
540
|
});
|
|
636
541
|
});
|
|
637
542
|
describe('State persistence after page reload', () => {
|
|
638
543
|
it('should retain committed values after page reload simulation', async () => {
|
|
639
544
|
// Initial state (using delta profile since sumBy only applies to delta)
|
|
640
|
-
mockLocation.search =
|
|
641
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
|
|
642
545
|
const { result: result1, unmount } = renderHook(() => useQueryState(), {
|
|
643
|
-
wrapper: createWrapper(),
|
|
546
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000'),
|
|
644
547
|
});
|
|
645
548
|
// User makes changes to draft (using delta profile since sumBy only applies to delta)
|
|
646
549
|
act(() => {
|
|
@@ -653,45 +556,38 @@ describe('useQueryState', () => {
|
|
|
653
556
|
result1.current.commitDraft();
|
|
654
557
|
});
|
|
655
558
|
await waitFor(() => {
|
|
656
|
-
expect(
|
|
559
|
+
expect(result1.current.querySelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
657
560
|
});
|
|
658
|
-
//
|
|
659
|
-
const committedParams = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1][1];
|
|
660
|
-
// Simulate page reload by updating mockLocation.search with committed values
|
|
561
|
+
// Build the query string from the committed state
|
|
661
562
|
const queryString = new URLSearchParams({
|
|
662
|
-
expression:
|
|
663
|
-
from:
|
|
664
|
-
to:
|
|
665
|
-
time_selection:
|
|
666
|
-
sum_by:
|
|
563
|
+
expression: String(result1.current.querySelection.expression),
|
|
564
|
+
from: String(result1.current.querySelection.from),
|
|
565
|
+
to: String(result1.current.querySelection.to),
|
|
566
|
+
time_selection: String(result1.current.querySelection.timeSelection),
|
|
567
|
+
sum_by: (result1.current.querySelection.sumBy ?? []).join(','),
|
|
667
568
|
}).toString();
|
|
668
|
-
mockLocation.search = `?${queryString}`;
|
|
669
569
|
// Unmount the old hook instance
|
|
670
570
|
unmount();
|
|
671
571
|
// Clear navigation mock to verify no new navigation on reload
|
|
672
572
|
mockNavigateTo.mockClear();
|
|
673
|
-
// Create new hook instance (simulating page reload)
|
|
674
|
-
const { result: result2 } = renderHook(() => useQueryState(), {
|
|
573
|
+
// Create new hook instance (simulating page reload) with the committed search params
|
|
574
|
+
const { result: result2 } = renderHook(() => useQueryState(), {
|
|
575
|
+
wrapper: createWrapper({}, `?${queryString}`),
|
|
576
|
+
});
|
|
675
577
|
// Verify state is loaded from URL after "reload"
|
|
676
578
|
expect(result2.current.querySelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
677
579
|
expect(result2.current.querySelection.from).toBe(5000);
|
|
678
580
|
expect(result2.current.querySelection.to).toBe(6000);
|
|
679
581
|
expect(result2.current.querySelection.timeSelection).toBe('relative:minute|15');
|
|
680
|
-
expect(result2.current.querySelection.sumBy).toEqual(['namespace', 'pod']);
|
|
681
582
|
// Draft should be synced with URL state on page load
|
|
682
583
|
expect(result2.current.draftSelection.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
683
584
|
expect(result2.current.draftSelection.from).toBe(5000);
|
|
684
585
|
expect(result2.current.draftSelection.to).toBe(6000);
|
|
685
|
-
expect(result2.current.draftSelection.sumBy).toEqual(['namespace', 'pod']);
|
|
686
|
-
// No navigation should occur on page load
|
|
687
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
688
586
|
});
|
|
689
587
|
it('should preserve delta profile merge params after reload', async () => {
|
|
690
588
|
// Initial state with delta profile
|
|
691
|
-
mockLocation.search =
|
|
692
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
|
|
693
589
|
const { result: result1, unmount } = renderHook(() => useQueryState(), {
|
|
694
|
-
wrapper: createWrapper(),
|
|
590
|
+
wrapper: createWrapper({}, '?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000'),
|
|
695
591
|
});
|
|
696
592
|
// Commit with time override
|
|
697
593
|
act(() => {
|
|
@@ -702,26 +598,24 @@ describe('useQueryState', () => {
|
|
|
702
598
|
});
|
|
703
599
|
});
|
|
704
600
|
await waitFor(() => {
|
|
705
|
-
expect(
|
|
601
|
+
expect(result1.current.querySelection.mergeFrom).toBe('5000000000');
|
|
602
|
+
expect(result1.current.querySelection.mergeTo).toBe('6000000000');
|
|
706
603
|
});
|
|
707
|
-
const committedParams = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1][1];
|
|
708
|
-
// Verify merge params were set
|
|
709
|
-
expect(committedParams.merge_from).toBe('5000000000');
|
|
710
|
-
expect(committedParams.merge_to).toBe('6000000000');
|
|
711
604
|
// Simulate page reload with all params including merge params
|
|
712
605
|
const queryString = new URLSearchParams({
|
|
713
|
-
expression:
|
|
714
|
-
from:
|
|
715
|
-
to:
|
|
716
|
-
time_selection:
|
|
717
|
-
merge_from:
|
|
718
|
-
merge_to:
|
|
606
|
+
expression: String(result1.current.querySelection.expression),
|
|
607
|
+
from: String(result1.current.querySelection.from),
|
|
608
|
+
to: String(result1.current.querySelection.to),
|
|
609
|
+
time_selection: String(result1.current.querySelection.timeSelection),
|
|
610
|
+
merge_from: String(result1.current.querySelection.mergeFrom),
|
|
611
|
+
merge_to: String(result1.current.querySelection.mergeTo),
|
|
719
612
|
}).toString();
|
|
720
|
-
mockLocation.search = `?${queryString}`;
|
|
721
613
|
unmount();
|
|
722
614
|
mockNavigateTo.mockClear();
|
|
723
615
|
// Create new hook instance
|
|
724
|
-
const { result: result2 } = renderHook(() => useQueryState(), {
|
|
616
|
+
const { result: result2 } = renderHook(() => useQueryState(), {
|
|
617
|
+
wrapper: createWrapper({}, `?${queryString}`),
|
|
618
|
+
});
|
|
725
619
|
// Verify merge params are preserved
|
|
726
620
|
expect(result2.current.querySelection.mergeFrom).toBe('5000000000');
|
|
727
621
|
expect(result2.current.querySelection.mergeTo).toBe('6000000000');
|
|
@@ -737,9 +631,9 @@ describe('useQueryState', () => {
|
|
|
737
631
|
});
|
|
738
632
|
it('should compute ProfileSelection from URL params', () => {
|
|
739
633
|
// Set URL with ProfileSelection params - using valid profile type
|
|
740
|
-
|
|
741
|
-
'?merge_from_a=1234567890&merge_to_a=9876543210&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{pod="test"}'
|
|
742
|
-
|
|
634
|
+
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
635
|
+
wrapper: createWrapper({}, '?merge_from_a=1234567890&merge_to_a=9876543210&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{pod="test"}'),
|
|
636
|
+
});
|
|
743
637
|
const { profileSelection } = result.current;
|
|
744
638
|
expect(profileSelection).not.toBeNull();
|
|
745
639
|
// Test using the interface methods
|
|
@@ -764,11 +658,12 @@ describe('useQueryState', () => {
|
|
|
764
658
|
result.current.setProfileSelection(mergeFrom, mergeTo, mockQuery);
|
|
765
659
|
});
|
|
766
660
|
await waitFor(() => {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
expect(
|
|
771
|
-
expect(
|
|
661
|
+
const { profileSelection } = result.current;
|
|
662
|
+
expect(profileSelection).not.toBeNull();
|
|
663
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
664
|
+
expect(historyParams?.selection).toBe('memory:inuse_space:bytes:space:bytes{namespace="default"}');
|
|
665
|
+
expect(historyParams?.merge_from).toBe('5000000000');
|
|
666
|
+
expect(historyParams?.merge_to).toBe('6000000000');
|
|
772
667
|
});
|
|
773
668
|
});
|
|
774
669
|
it('should use correct suffix for ProfileSelection in comparison mode', async () => {
|
|
@@ -785,18 +680,19 @@ describe('useQueryState', () => {
|
|
|
785
680
|
resultB.current.setProfileSelection(mergeFrom, mergeTo, mockQuery);
|
|
786
681
|
});
|
|
787
682
|
await waitFor(() => {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
expect(
|
|
792
|
-
expect(
|
|
683
|
+
const { profileSelection } = resultB.current;
|
|
684
|
+
expect(profileSelection).not.toBeNull();
|
|
685
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
686
|
+
expect(historyParams?.selection).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{job="test"}');
|
|
687
|
+
expect(historyParams?.merge_from).toBe('7000000000');
|
|
688
|
+
expect(historyParams?.merge_to).toBe('8000000000');
|
|
793
689
|
});
|
|
794
690
|
});
|
|
795
691
|
it('should clear ProfileSelection when commitDraft is called', async () => {
|
|
796
692
|
// Start with a ProfileSelection in URL - using valid profile type
|
|
797
|
-
|
|
798
|
-
'?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"}'
|
|
799
|
-
|
|
693
|
+
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
694
|
+
wrapper: createWrapper({}, '?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"}'),
|
|
695
|
+
});
|
|
800
696
|
// Verify ProfileSelection exists
|
|
801
697
|
expect(result.current.profileSelection).not.toBeNull();
|
|
802
698
|
// Make a change to trigger commit
|
|
@@ -808,18 +704,16 @@ describe('useQueryState', () => {
|
|
|
808
704
|
result.current.commitDraft();
|
|
809
705
|
});
|
|
810
706
|
await waitFor(() => {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
// ProfileSelection params should be cleared
|
|
814
|
-
expect(params).not.toHaveProperty('selection_a');
|
|
707
|
+
// ProfileSelection should be cleared
|
|
708
|
+
expect(result.current.profileSelection).toBeNull();
|
|
815
709
|
// But QuerySelection params should still be present
|
|
816
|
-
expect(
|
|
710
|
+
expect(result.current.querySelection.expression).toBe('memory:inuse_space:bytes:space:bytes{}');
|
|
817
711
|
});
|
|
818
712
|
});
|
|
819
713
|
it('should handle ProfileSelection with delta profiles correctly', () => {
|
|
820
|
-
|
|
821
|
-
'?merge_from_a=1000000000&merge_to_a=2000000000&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{node="worker"}'
|
|
822
|
-
|
|
714
|
+
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
715
|
+
wrapper: createWrapper({}, '?merge_from_a=1000000000&merge_to_a=2000000000&selection_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{node="worker"}'),
|
|
716
|
+
});
|
|
823
717
|
const { profileSelection } = result.current;
|
|
824
718
|
expect(profileSelection).not.toBeNull();
|
|
825
719
|
// Test that ProfileSelection recognizes delta profile type
|
|
@@ -845,31 +739,30 @@ describe('useQueryState', () => {
|
|
|
845
739
|
result1.current.setProfileSelection(mergeFrom, mergeTo, mockQuery);
|
|
846
740
|
});
|
|
847
741
|
await waitFor(() => {
|
|
848
|
-
expect(
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
const selectionA =
|
|
853
|
-
const mergeFromA =
|
|
854
|
-
const mergeToA =
|
|
855
|
-
mockLocation.search = `?selection_a=${encodeURIComponent(selectionA)}&merge_from_a=${mergeFromA}&merge_to_a=${mergeToA}`;
|
|
742
|
+
expect(result1.current.profileSelection).not.toBeNull();
|
|
743
|
+
});
|
|
744
|
+
// Get the committed state values to build reload URL
|
|
745
|
+
const historyParams = result1.current.profileSelection?.HistoryParams();
|
|
746
|
+
const selectionA = historyParams?.selection ?? '';
|
|
747
|
+
const mergeFromA = historyParams?.merge_from ?? '';
|
|
748
|
+
const mergeToA = historyParams?.merge_to ?? '';
|
|
856
749
|
unmount();
|
|
857
750
|
mockNavigateTo.mockClear();
|
|
858
|
-
// Create new hook instance (simulating page reload)
|
|
751
|
+
// Create new hook instance (simulating page reload) with the committed search params
|
|
859
752
|
const { result: result2 } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
860
|
-
wrapper: createWrapper(
|
|
753
|
+
wrapper: createWrapper({},
|
|
754
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
755
|
+
`?selection_a=${encodeURIComponent(selectionA)}&merge_from_a=${mergeFromA}&merge_to_a=${mergeToA}`),
|
|
861
756
|
});
|
|
862
757
|
// Verify ProfileSelection is loaded from URL after reload
|
|
863
758
|
const profileSelection = result2.current.profileSelection;
|
|
864
759
|
expect(profileSelection).not.toBeNull();
|
|
865
760
|
// Use interface methods to test
|
|
866
761
|
expect(profileSelection?.Type()).toBe('merge');
|
|
867
|
-
const
|
|
868
|
-
expect(
|
|
869
|
-
expect(
|
|
870
|
-
expect(
|
|
871
|
-
// No navigation should occur on page load
|
|
872
|
-
expect(mockNavigateTo).not.toHaveBeenCalled();
|
|
762
|
+
const reloadedHistoryParams = profileSelection?.HistoryParams();
|
|
763
|
+
expect(reloadedHistoryParams?.merge_from).toBe('3000000000');
|
|
764
|
+
expect(reloadedHistoryParams?.merge_to).toBe('4000000000');
|
|
765
|
+
expect(reloadedHistoryParams?.selection).toBe('memory:alloc_objects:count:space:bytes{pod="test"}');
|
|
873
766
|
});
|
|
874
767
|
it('should handle independent ProfileSelection for both sides in comparison mode', async () => {
|
|
875
768
|
// Test component using both hooks with the same URLStateProvider (real-world scenario)
|
|
@@ -895,24 +788,13 @@ describe('useQueryState', () => {
|
|
|
895
788
|
result.current.stateA.setProfileSelection(BigInt(1000000000), BigInt(2000000000), mockQueryA);
|
|
896
789
|
});
|
|
897
790
|
await waitFor(() => {
|
|
898
|
-
expect(
|
|
791
|
+
expect(result.current.stateA.profileSelection).not.toBeNull();
|
|
899
792
|
});
|
|
900
|
-
mockNavigateTo.mockClear();
|
|
901
793
|
// Set ProfileSelection for side B
|
|
902
794
|
act(() => {
|
|
903
795
|
result.current.stateB.setProfileSelection(BigInt(3000000000), BigInt(4000000000), mockQueryB);
|
|
904
796
|
});
|
|
905
|
-
|
|
906
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
907
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
908
|
-
// Both selections should be in URL with different suffixes
|
|
909
|
-
expect(params.selection_a).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="app-a"}');
|
|
910
|
-
expect(params.selection_b).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="app-b"}');
|
|
911
|
-
expect(params.merge_from_a).toBe('1000000000');
|
|
912
|
-
expect(params.merge_from_b).toBe('3000000000');
|
|
913
|
-
});
|
|
914
|
-
// The mockNavigateTo automatically updates mockLocation.search, so the URL change
|
|
915
|
-
// should propagate to the hooks automatically. Verify both ProfileSelections exist.
|
|
797
|
+
// Verify both ProfileSelections exist
|
|
916
798
|
await waitFor(() => {
|
|
917
799
|
expect(result.current.stateA.profileSelection).not.toBeNull();
|
|
918
800
|
expect(result.current.stateB.profileSelection).not.toBeNull();
|
|
@@ -920,8 +802,9 @@ describe('useQueryState', () => {
|
|
|
920
802
|
});
|
|
921
803
|
it('should return null ProfileSelection when only partial params exist', () => {
|
|
922
804
|
// Missing selection param
|
|
923
|
-
|
|
924
|
-
|
|
805
|
+
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
806
|
+
wrapper: createWrapper({}, '?merge_from_a=1000000000&merge_to_a=2000000000'),
|
|
807
|
+
});
|
|
925
808
|
expect(result.current.profileSelection).toBeNull();
|
|
926
809
|
});
|
|
927
810
|
it('should handle ProfileSelection with complex query expressions', async () => {
|
|
@@ -934,9 +817,11 @@ describe('useQueryState', () => {
|
|
|
934
817
|
result.current.setProfileSelection(BigInt(5000000000), BigInt(6000000000), mockQuery);
|
|
935
818
|
});
|
|
936
819
|
await waitFor(() => {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
820
|
+
const { profileSelection } = result.current;
|
|
821
|
+
expect(profileSelection).not.toBeNull();
|
|
822
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
823
|
+
// The expression gets re-serialized through Query.parse which adds spaces after commas
|
|
824
|
+
expect(historyParams?.selection).toBe('memory:alloc_objects:count:space:bytes:delta{namespace="default", pod="app-1", container="main"}');
|
|
940
825
|
});
|
|
941
826
|
});
|
|
942
827
|
it('should batch ProfileSelection update with other URL state changes', async () => {
|
|
@@ -950,18 +835,18 @@ describe('useQueryState', () => {
|
|
|
950
835
|
result.current.setProfileSelection(BigInt(1000000000), BigInt(2000000000), mockQuery);
|
|
951
836
|
});
|
|
952
837
|
await waitFor(() => {
|
|
953
|
-
|
|
954
|
-
expect(
|
|
955
|
-
const
|
|
956
|
-
expect(
|
|
957
|
-
expect(
|
|
958
|
-
expect(
|
|
838
|
+
const { profileSelection } = result.current;
|
|
839
|
+
expect(profileSelection).not.toBeNull();
|
|
840
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
841
|
+
expect(historyParams?.selection).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{job="test"}');
|
|
842
|
+
expect(historyParams?.merge_from).toBe('1000000000');
|
|
843
|
+
expect(historyParams?.merge_to).toBe('2000000000');
|
|
959
844
|
});
|
|
960
845
|
});
|
|
961
846
|
it('should preserve other URL params when setting ProfileSelection', async () => {
|
|
962
|
-
|
|
963
|
-
'?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&other_param=value&unrelated=test'
|
|
964
|
-
|
|
847
|
+
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), {
|
|
848
|
+
wrapper: createWrapper({}, '?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&other_param=value&unrelated=test'),
|
|
849
|
+
});
|
|
965
850
|
const mockQuery = {
|
|
966
851
|
toString: () => 'process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}',
|
|
967
852
|
profileType: () => ({ delta: false }),
|
|
@@ -970,14 +855,13 @@ describe('useQueryState', () => {
|
|
|
970
855
|
result.current.setProfileSelection(BigInt(1000000000), BigInt(2000000000), mockQuery);
|
|
971
856
|
});
|
|
972
857
|
await waitFor(() => {
|
|
973
|
-
expect(mockNavigateTo).toHaveBeenCalled();
|
|
974
|
-
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
975
858
|
// ProfileSelection params should be set
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
expect(
|
|
980
|
-
|
|
859
|
+
const { profileSelection } = result.current;
|
|
860
|
+
expect(profileSelection).not.toBeNull();
|
|
861
|
+
const historyParams = profileSelection?.HistoryParams();
|
|
862
|
+
expect(historyParams?.selection).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}');
|
|
863
|
+
// Expression should still be present
|
|
864
|
+
expect(result.current.querySelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{}');
|
|
981
865
|
});
|
|
982
866
|
});
|
|
983
867
|
});
|