@parca/profile 0.19.94 → 0.19.102
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 +32 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +2 -0
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +1 -1
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +1 -2
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +8 -0
- package/dist/SimpleMatchers/Select.d.ts.map +1 -1
- package/dist/SimpleMatchers/Select.js +3 -3
- package/dist/hooks/useLabels.d.ts.map +1 -1
- package/dist/hooks/useLabels.js +4 -1
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +53 -23
- package/dist/hooks/useQueryState.test.js +32 -22
- package/dist/useSumBy.d.ts +10 -2
- package/dist/useSumBy.d.ts.map +1 -1
- package/dist/useSumBy.js +30 -7
- package/package.json +15 -10
- package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +2 -0
- package/src/ProfileSelector/MetricsGraphSection.tsx +2 -2
- package/src/ProfileSelector/index.tsx +1 -5
- package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +8 -0
- package/src/SimpleMatchers/Select.tsx +3 -3
- package/src/hooks/useLabels.ts +5 -1
- package/src/hooks/useQueryState.test.tsx +41 -22
- package/src/hooks/useQueryState.ts +72 -31
- package/src/useSumBy.ts +58 -4
package/dist/useSumBy.js
CHANGED
|
@@ -31,7 +31,7 @@ const getDefaultSumBy = (profile, labels) => {
|
|
|
31
31
|
}
|
|
32
32
|
return undefined;
|
|
33
33
|
};
|
|
34
|
-
export const useSumBySelection = (profileType, labelNamesLoading, labels, { defaultValue, } = {}) => {
|
|
34
|
+
export const useSumBySelection = (profileType, labelNamesLoading, labels, draftSumBy, { defaultValue, } = {}) => {
|
|
35
35
|
const [userSelectedSumBy, setUserSelectedSumBy] = useState(profileType != null ? { [profileType.toString()]: defaultValue } : {});
|
|
36
36
|
// Update userSelectedSumBy when defaultValue changes (e.g., during navigation)
|
|
37
37
|
useEffect(() => {
|
|
@@ -57,9 +57,15 @@ export const useSumBySelection = (profileType, labelNamesLoading, labels, { defa
|
|
|
57
57
|
// Store the last valid sumBy value to return during loading
|
|
58
58
|
const lastValidSumByRef = useRef(DEFAULT_EMPTY_SUM_BY);
|
|
59
59
|
const sumBy = useMemo(() => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
if (labelNamesLoading) {
|
|
61
|
+
// For smoother UX, return draftSumBy first if available during loading
|
|
62
|
+
// as this must be recently computed with the draft time range labels.
|
|
63
|
+
if (draftSumBy !== undefined) {
|
|
64
|
+
return draftSumBy;
|
|
65
|
+
}
|
|
66
|
+
if (lastValidSumByRef.current == null) {
|
|
67
|
+
return lastValidSumByRef.current;
|
|
68
|
+
}
|
|
63
69
|
}
|
|
64
70
|
let result = userSelectedSumBy[profileType?.toString() ?? ''] ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY;
|
|
65
71
|
if (profileType?.delta !== true) {
|
|
@@ -68,7 +74,7 @@ export const useSumBySelection = (profileType, labelNamesLoading, labels, { defa
|
|
|
68
74
|
// Store the computed value for next loading state
|
|
69
75
|
lastValidSumByRef.current = result;
|
|
70
76
|
return result;
|
|
71
|
-
}, [userSelectedSumBy, profileType, defaultSumBy, labelNamesLoading]);
|
|
77
|
+
}, [userSelectedSumBy, profileType, defaultSumBy, labelNamesLoading, draftSumBy]);
|
|
72
78
|
return [
|
|
73
79
|
sumBy,
|
|
74
80
|
setSumBy,
|
|
@@ -118,15 +124,32 @@ export const sumByToParam = (sumBy) => {
|
|
|
118
124
|
return sumBy;
|
|
119
125
|
};
|
|
120
126
|
// Combined hook that handles all sumBy logic: fetching labels, computing defaults, and managing selection
|
|
121
|
-
export const useSumBy = (queryClient, profileType, timeRange, defaultValue) => {
|
|
127
|
+
export const useSumBy = (queryClient, profileType, timeRange, draftProfileType, draftTimeRange, defaultValue) => {
|
|
122
128
|
const { loading: labelNamesLoading, result } = useLabelNames(queryClient, profileType?.toString() ?? '', timeRange.getFromMs(), timeRange.getToMs());
|
|
129
|
+
const { draftSumBy, setDraftSumBy, isDraftSumByLoading } = useDraftSumBy(queryClient, draftProfileType, draftTimeRange, defaultValue);
|
|
123
130
|
const labels = useMemo(() => {
|
|
124
131
|
return result.response?.labelNames === undefined ? [] : result.response.labelNames;
|
|
125
132
|
}, [result]);
|
|
126
|
-
const [sumBySelection, setSumByInternal, { isLoading }] = useSumBySelection(profileType, labelNamesLoading, labels, { defaultValue });
|
|
133
|
+
const [sumBySelection, setSumByInternal, { isLoading }] = useSumBySelection(profileType, labelNamesLoading, labels, draftSumBy, { defaultValue });
|
|
127
134
|
return {
|
|
128
135
|
sumBy: sumBySelection,
|
|
129
136
|
setSumBy: setSumByInternal,
|
|
130
137
|
isLoading,
|
|
138
|
+
draftSumBy,
|
|
139
|
+
setDraftSumBy,
|
|
140
|
+
isDraftSumByLoading,
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
export const useDraftSumBy = (queryClient, profileType, timeRange, defaultValue) => {
|
|
144
|
+
const [draftSumBy, setDraftSumBy] = useState(defaultValue);
|
|
145
|
+
const { loading: labelNamesLoading, result } = useLabelNames(queryClient, profileType?.toString() ?? '', timeRange.getFromMs(), timeRange.getToMs());
|
|
146
|
+
const labels = useMemo(() => {
|
|
147
|
+
return result.response?.labelNames === undefined ? [] : result.response.labelNames;
|
|
148
|
+
}, [result]);
|
|
149
|
+
const { defaultSumBy, isLoading } = useDefaultSumBy(profileType, labelNamesLoading, labels);
|
|
150
|
+
return {
|
|
151
|
+
draftSumBy: draftSumBy ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY,
|
|
152
|
+
setDraftSumBy: setDraftSumBy,
|
|
153
|
+
isDraftSumByLoading: isLoading,
|
|
131
154
|
};
|
|
132
155
|
};
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.102",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@floating-ui/react": "^0.27.12",
|
|
7
7
|
"@headlessui/react": "^1.7.19",
|
|
8
8
|
"@iconify/react": "^4.0.0",
|
|
9
|
-
"@parca/client": "0.17.
|
|
10
|
-
"@parca/components": "0.16.
|
|
11
|
-
"@parca/dynamicsize": "0.16.
|
|
12
|
-
"@parca/hooks": "0.0.
|
|
13
|
-
"@parca/icons": "0.16.
|
|
14
|
-
"@parca/parser": "0.16.
|
|
15
|
-
"@parca/store": "0.16.
|
|
9
|
+
"@parca/client": "0.17.16",
|
|
10
|
+
"@parca/components": "0.16.392",
|
|
11
|
+
"@parca/dynamicsize": "0.16.72",
|
|
12
|
+
"@parca/hooks": "0.0.116",
|
|
13
|
+
"@parca/icons": "0.16.79",
|
|
14
|
+
"@parca/parser": "0.16.86",
|
|
15
|
+
"@parca/store": "0.16.199",
|
|
16
16
|
"@parca/test-utils": "0.0.17",
|
|
17
|
-
"@parca/utilities": "0.0.
|
|
17
|
+
"@parca/utilities": "0.0.122",
|
|
18
18
|
"@popperjs/core": "^2.11.8",
|
|
19
19
|
"@protobuf-ts/runtime-rpc": "^2.5.0",
|
|
20
20
|
"@storybook/preview-api": "^8.4.3",
|
|
@@ -75,9 +75,14 @@
|
|
|
75
75
|
"keywords": [],
|
|
76
76
|
"author": "",
|
|
77
77
|
"license": "ISC",
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "https://github.com/parca-dev/parca",
|
|
81
|
+
"directory": "ui/packages/shared/profile"
|
|
82
|
+
},
|
|
78
83
|
"publishConfig": {
|
|
79
84
|
"access": "public",
|
|
80
85
|
"registry": "https://registry.npmjs.org/"
|
|
81
86
|
},
|
|
82
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "71d719bdbc5b9591b427c668b4ab9685eeb44b4a"
|
|
83
88
|
}
|
|
@@ -194,11 +194,13 @@ const ContextMenu = ({
|
|
|
194
194
|
|
|
195
195
|
if (dashboardItems.includes('sandwich')) {
|
|
196
196
|
setSandwichFunctionName(functionName);
|
|
197
|
+
hideMenu();
|
|
197
198
|
return;
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
setSandwichFunctionName(functionName);
|
|
201
202
|
setDashboardItems([...dashboardItems, 'sandwich']);
|
|
203
|
+
hideMenu();
|
|
202
204
|
}}
|
|
203
205
|
disabled={functionName === '' || functionName == null}
|
|
204
206
|
>
|
|
@@ -29,7 +29,7 @@ interface MetricsGraphSectionProps {
|
|
|
29
29
|
querySelection: QuerySelection;
|
|
30
30
|
profileSelection: ProfileSelection | null;
|
|
31
31
|
comparing: boolean;
|
|
32
|
-
sumBy: string[] |
|
|
32
|
+
sumBy: string[] | undefined;
|
|
33
33
|
defaultSumByLoading: boolean;
|
|
34
34
|
queryClient: QueryServiceClient;
|
|
35
35
|
queryExpressionString: string;
|
|
@@ -170,7 +170,7 @@ export function MetricsGraphSection({
|
|
|
170
170
|
to={querySelection.to}
|
|
171
171
|
profile={profileSelection}
|
|
172
172
|
comparing={comparing}
|
|
173
|
-
sumBy={
|
|
173
|
+
sumBy={sumBy ?? []}
|
|
174
174
|
sumByLoading={defaultSumByLoading}
|
|
175
175
|
setTimeRange={handleTimeRangeChange}
|
|
176
176
|
addLabelMatcher={addLabelMatcher}
|
|
@@ -209,10 +209,6 @@ const ProfileSelector = ({
|
|
|
209
209
|
const currentTo = timeRangeSelection.getToMs(true);
|
|
210
210
|
const currentRangeKey = timeRangeSelection.getRangeKey();
|
|
211
211
|
// Commit with refreshed time range
|
|
212
|
-
console.log(
|
|
213
|
-
'[draftExpression] setQueryExpression: committing with refreshed time range:',
|
|
214
|
-
draftSelection.expression
|
|
215
|
-
);
|
|
216
212
|
commitDraft({
|
|
217
213
|
from: currentFrom,
|
|
218
214
|
to: currentTo,
|
|
@@ -332,7 +328,7 @@ const ProfileSelector = ({
|
|
|
332
328
|
querySelection={querySelection}
|
|
333
329
|
profileSelection={profileSelection}
|
|
334
330
|
comparing={comparing}
|
|
335
|
-
sumBy={querySelection.sumBy
|
|
331
|
+
sumBy={querySelection.sumBy}
|
|
336
332
|
defaultSumByLoading={sumByLoading}
|
|
337
333
|
queryClient={queryClient}
|
|
338
334
|
queryExpressionString={queryExpressionString}
|
|
@@ -18,6 +18,8 @@ import {useProfileFilters} from '../components/ProfileFilters/useProfileFilters'
|
|
|
18
18
|
export const useResetStateOnProfileTypeChange = (): (() => void) => {
|
|
19
19
|
const [groupBy, setGroupBy] = useURLState('group_by');
|
|
20
20
|
const [curPath, setCurPath] = useURLState('cur_path');
|
|
21
|
+
const [sumByA, setSumByA] = useURLState('sum_by_a');
|
|
22
|
+
const [sumByB, setSumByB] = useURLState('sum_by_b');
|
|
21
23
|
const {resetFilters} = useProfileFilters();
|
|
22
24
|
const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
|
|
23
25
|
const batchUpdates = useURLStateBatch();
|
|
@@ -34,6 +36,12 @@ export const useResetStateOnProfileTypeChange = (): (() => void) => {
|
|
|
34
36
|
if (sandwichFunctionName !== undefined) {
|
|
35
37
|
setSandwichFunctionName(undefined);
|
|
36
38
|
}
|
|
39
|
+
if (sumByA !== undefined) {
|
|
40
|
+
setSumByA(undefined);
|
|
41
|
+
}
|
|
42
|
+
if (sumByB !== undefined) {
|
|
43
|
+
setSumByB(undefined);
|
|
44
|
+
}
|
|
37
45
|
|
|
38
46
|
resetFilters();
|
|
39
47
|
});
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
|
14
|
+
import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {Icon} from '@iconify/react';
|
|
17
17
|
import cx from 'classnames';
|
|
@@ -350,7 +350,7 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
|
|
|
350
350
|
</div>
|
|
351
351
|
) : (
|
|
352
352
|
groupedFilteredItems.map(group => (
|
|
353
|
-
|
|
353
|
+
<Fragment key={group.type}>
|
|
354
354
|
{groupedFilteredItems.length > 1 &&
|
|
355
355
|
groupedFilteredItems.every(g => g.type !== '') &&
|
|
356
356
|
group.type !== '' ? (
|
|
@@ -369,7 +369,7 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
|
|
|
369
369
|
handleSelection={handleSelection}
|
|
370
370
|
/>
|
|
371
371
|
))}
|
|
372
|
-
|
|
372
|
+
</Fragment>
|
|
373
373
|
))
|
|
374
374
|
)}
|
|
375
375
|
</div>
|
package/src/hooks/useLabels.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
+
import {useEffect} from 'react';
|
|
15
|
+
|
|
14
16
|
import {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
|
|
15
17
|
import {useGrpcMetadata} from '@parca/components';
|
|
16
18
|
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
|
|
@@ -68,7 +70,9 @@ export const useLabelNames = (
|
|
|
68
70
|
},
|
|
69
71
|
});
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
console.log('Label names query result:', {data, error, isLoading});
|
|
75
|
+
}, [data, error, isLoading]);
|
|
72
76
|
|
|
73
77
|
return {
|
|
74
78
|
result: {response: data, error: error as Error},
|
|
@@ -69,16 +69,33 @@ vi.mock('@parca/components/src/hooks/URLState/utils', async () => {
|
|
|
69
69
|
};
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
// Mock useSumBy
|
|
72
|
+
// Mock useSumBy with stateful behavior using React's useState
|
|
73
73
|
vi.mock('../useSumBy', async () => {
|
|
74
74
|
const actual = await vi.importActual('../useSumBy');
|
|
75
|
+
const react = await import('react');
|
|
76
|
+
|
|
75
77
|
return {
|
|
76
78
|
...actual,
|
|
77
|
-
useSumBy: (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
useSumBy: (
|
|
80
|
+
_queryClient: any,
|
|
81
|
+
_profileType: any,
|
|
82
|
+
_timeRange: any,
|
|
83
|
+
_draftProfileType: any,
|
|
84
|
+
_draftTimeRange: any,
|
|
85
|
+
defaultValue: any
|
|
86
|
+
) => {
|
|
87
|
+
const [draftSumBy, setDraftSumBy] = react.useState<string[] | undefined>(defaultValue);
|
|
88
|
+
const [sumBy, setSumBy] = react.useState<string[] | undefined>(defaultValue);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
sumBy,
|
|
92
|
+
setSumBy,
|
|
93
|
+
isLoading: false,
|
|
94
|
+
draftSumBy,
|
|
95
|
+
setDraftSumBy,
|
|
96
|
+
isDraftSumByLoading: false,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
82
99
|
};
|
|
83
100
|
});
|
|
84
101
|
|
|
@@ -207,7 +224,9 @@ describe('useQueryState', () => {
|
|
|
207
224
|
it('should update sumBy', async () => {
|
|
208
225
|
const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
|
|
209
226
|
|
|
227
|
+
// sumBy only applies to delta profiles, so we need to set one first
|
|
210
228
|
act(() => {
|
|
229
|
+
result.current.setDraftExpression('process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}');
|
|
211
230
|
result.current.setDraftSumBy(['namespace', 'container']);
|
|
212
231
|
});
|
|
213
232
|
|
|
@@ -256,15 +275,15 @@ describe('useQueryState', () => {
|
|
|
256
275
|
const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
|
|
257
276
|
|
|
258
277
|
act(() => {
|
|
259
|
-
// Update multiple draft values
|
|
260
|
-
result.current.setDraftExpression('memory:
|
|
278
|
+
// Update multiple draft values (using delta profile since sumBy only applies to delta)
|
|
279
|
+
result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
261
280
|
result.current.setDraftTimeRange(7000, 8000, 'relative:minute|30');
|
|
262
281
|
result.current.setDraftSumBy(['pod', 'node']);
|
|
263
282
|
});
|
|
264
283
|
|
|
265
284
|
// All drafts should be updated
|
|
266
285
|
expect(result.current.draftSelection.expression).toBe(
|
|
267
|
-
'memory:
|
|
286
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
268
287
|
);
|
|
269
288
|
expect(result.current.draftSelection.from).toBe(7000);
|
|
270
289
|
expect(result.current.draftSelection.to).toBe(8000);
|
|
@@ -278,7 +297,7 @@ describe('useQueryState', () => {
|
|
|
278
297
|
// Should only navigate once for all updates
|
|
279
298
|
expect(mockNavigateTo).toHaveBeenCalledTimes(1);
|
|
280
299
|
const [, params] = mockNavigateTo.mock.calls[0];
|
|
281
|
-
expect(params.expression).toBe('memory:
|
|
300
|
+
expect(params.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
282
301
|
expect(params.from).toBe('7000');
|
|
283
302
|
expect(params.to).toBe('8000');
|
|
284
303
|
expect(params.time_selection).toBe('relative:minute|30');
|
|
@@ -430,9 +449,9 @@ describe('useQueryState', () => {
|
|
|
430
449
|
it('should handle _b suffix correctly', async () => {
|
|
431
450
|
const {result} = renderHook(() => useQueryState({suffix: '_b'}), {wrapper: createWrapper()});
|
|
432
451
|
|
|
433
|
-
// Update draft state
|
|
452
|
+
// Update draft state (using delta profile since sumBy only applies to delta)
|
|
434
453
|
act(() => {
|
|
435
|
-
result.current.setDraftExpression('memory:
|
|
454
|
+
result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
436
455
|
result.current.setDraftTimeRange(3333, 4444, 'relative:hour|2');
|
|
437
456
|
result.current.setDraftSumBy(['label_b']);
|
|
438
457
|
});
|
|
@@ -445,7 +464,7 @@ describe('useQueryState', () => {
|
|
|
445
464
|
await waitFor(() => {
|
|
446
465
|
expect(mockNavigateTo).toHaveBeenCalled();
|
|
447
466
|
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
448
|
-
expect(params.expression_b).toBe('memory:
|
|
467
|
+
expect(params.expression_b).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
449
468
|
expect(params.from_b).toBe('3333');
|
|
450
469
|
expect(params.to_b).toBe('4444');
|
|
451
470
|
expect(params.sum_by_b).toBe('label_b');
|
|
@@ -457,9 +476,9 @@ describe('useQueryState', () => {
|
|
|
457
476
|
it('should not update URL until commit', async () => {
|
|
458
477
|
const {result} = renderHook(() => useQueryState(), {wrapper: createWrapper()});
|
|
459
478
|
|
|
460
|
-
// Make multiple draft changes
|
|
479
|
+
// Make multiple draft changes (using delta profile since sumBy only applies to delta)
|
|
461
480
|
act(() => {
|
|
462
|
-
result.current.setDraftExpression('memory:
|
|
481
|
+
result.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
463
482
|
result.current.setDraftTimeRange(5000, 6000, 'relative:hour|3');
|
|
464
483
|
result.current.setDraftSumBy(['namespace', 'pod']);
|
|
465
484
|
});
|
|
@@ -476,7 +495,7 @@ describe('useQueryState', () => {
|
|
|
476
495
|
await waitFor(() => {
|
|
477
496
|
expect(mockNavigateTo).toHaveBeenCalledTimes(1);
|
|
478
497
|
const [, params] = mockNavigateTo.mock.calls[0];
|
|
479
|
-
expect(params.expression).toBe('memory:
|
|
498
|
+
expect(params.expression).toBe('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
480
499
|
expect(params.from).toBe('5000');
|
|
481
500
|
expect(params.to).toBe('6000');
|
|
482
501
|
expect(params.sum_by).toBe('namespace,pod');
|
|
@@ -737,17 +756,17 @@ describe('useQueryState', () => {
|
|
|
737
756
|
|
|
738
757
|
describe('State persistence after page reload', () => {
|
|
739
758
|
it('should retain committed values after page reload simulation', async () => {
|
|
740
|
-
// Initial state
|
|
759
|
+
// Initial state (using delta profile since sumBy only applies to delta)
|
|
741
760
|
mockLocation.search =
|
|
742
|
-
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&from=1000&to=2000';
|
|
761
|
+
'?expression=process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta{}&from=1000&to=2000';
|
|
743
762
|
|
|
744
763
|
const {result: result1, unmount} = renderHook(() => useQueryState(), {
|
|
745
764
|
wrapper: createWrapper(),
|
|
746
765
|
});
|
|
747
766
|
|
|
748
|
-
// User makes changes to draft
|
|
767
|
+
// User makes changes to draft (using delta profile since sumBy only applies to delta)
|
|
749
768
|
act(() => {
|
|
750
|
-
result1.current.setDraftExpression('memory:
|
|
769
|
+
result1.current.setDraftExpression('memory:alloc_space:bytes:space:bytes:delta{}');
|
|
751
770
|
result1.current.setDraftTimeRange(5000, 6000, 'relative:minute|15');
|
|
752
771
|
result1.current.setDraftSumBy(['namespace', 'pod']);
|
|
753
772
|
});
|
|
@@ -786,7 +805,7 @@ describe('useQueryState', () => {
|
|
|
786
805
|
|
|
787
806
|
// Verify state is loaded from URL after "reload"
|
|
788
807
|
expect(result2.current.querySelection.expression).toBe(
|
|
789
|
-
'memory:
|
|
808
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
790
809
|
);
|
|
791
810
|
expect(result2.current.querySelection.from).toBe(5000);
|
|
792
811
|
expect(result2.current.querySelection.to).toBe(6000);
|
|
@@ -795,7 +814,7 @@ describe('useQueryState', () => {
|
|
|
795
814
|
|
|
796
815
|
// Draft should be synced with URL state on page load
|
|
797
816
|
expect(result2.current.draftSelection.expression).toBe(
|
|
798
|
-
'memory:
|
|
817
|
+
'memory:alloc_space:bytes:space:bytes:delta{}'
|
|
799
818
|
);
|
|
800
819
|
expect(result2.current.draftSelection.from).toBe(5000);
|
|
801
820
|
expect(result2.current.draftSelection.to).toBe(6000);
|
|
@@ -19,7 +19,8 @@ import {Query} from '@parca/parser';
|
|
|
19
19
|
import {QuerySelection} from '../ProfileSelector';
|
|
20
20
|
import {ProfileSelection, ProfileSelectionFromParams, ProfileSource} from '../ProfileSource';
|
|
21
21
|
import {useResetFlameGraphState} from '../ProfileView/hooks/useResetFlameGraphState';
|
|
22
|
-
import {
|
|
22
|
+
import {useResetStateOnProfileTypeChange} from '../ProfileView/hooks/useResetStateOnProfileTypeChange';
|
|
23
|
+
import {DEFAULT_EMPTY_SUM_BY, sumByToParam, useSumBy, useSumByFromParams} from '../useSumBy';
|
|
23
24
|
|
|
24
25
|
interface UseQueryStateOptions {
|
|
25
26
|
suffix?: '_a' | '_b'; // For comparison mode
|
|
@@ -79,6 +80,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
79
80
|
|
|
80
81
|
const batchUpdates = useURLStateBatch();
|
|
81
82
|
const resetFlameGraphState = useResetFlameGraphState();
|
|
83
|
+
const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
|
|
82
84
|
|
|
83
85
|
// URL state hooks with appropriate suffixes
|
|
84
86
|
const [expression, setExpressionState] = useURLState<string>(`expression${suffix}`, {
|
|
@@ -115,29 +117,6 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
115
117
|
const [draftTimeSelection, setDraftTimeSelection] = useState<string>(
|
|
116
118
|
timeSelection ?? defaultTimeSelection
|
|
117
119
|
);
|
|
118
|
-
const [draftSumBy, setDraftSumBy] = useState<string[] | undefined>(sumBy);
|
|
119
|
-
|
|
120
|
-
// Sync draft state with URL state when URL changes externally
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
setDraftExpression(expression ?? defaultExpression);
|
|
123
|
-
}, [expression, defaultExpression]);
|
|
124
|
-
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
setDraftFrom(from ?? defaultFrom?.toString() ?? '');
|
|
127
|
-
}, [from, defaultFrom]);
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
setDraftTo(to ?? defaultTo?.toString() ?? '');
|
|
131
|
-
}, [to, defaultTo]);
|
|
132
|
-
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
setDraftTimeSelection(timeSelection ?? defaultTimeSelection);
|
|
135
|
-
}, [timeSelection, defaultTimeSelection]);
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
setDraftSumBy(sumBy);
|
|
139
|
-
}, [sumBy]);
|
|
140
|
-
|
|
141
120
|
// Parse the draft query to extract profile information
|
|
142
121
|
const draftQuery = useMemo(() => {
|
|
143
122
|
try {
|
|
@@ -147,8 +126,16 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
147
126
|
}
|
|
148
127
|
}, [draftExpression]);
|
|
149
128
|
|
|
129
|
+
const query = useMemo(() => {
|
|
130
|
+
try {
|
|
131
|
+
return Query.parse(expression ?? '');
|
|
132
|
+
} catch {
|
|
133
|
+
return Query.parse('');
|
|
134
|
+
}
|
|
135
|
+
}, [expression]);
|
|
150
136
|
const draftProfileType = useMemo(() => draftQuery.profileType(), [draftQuery]);
|
|
151
137
|
const draftProfileName = useMemo(() => draftQuery.profileName(), [draftQuery]);
|
|
138
|
+
const profileType = useMemo(() => query.profileType(), [query]);
|
|
152
139
|
|
|
153
140
|
// Compute draft time range for label fetching
|
|
154
141
|
const draftTimeRange = useMemo(() => {
|
|
@@ -159,13 +146,50 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
159
146
|
);
|
|
160
147
|
}, [draftTimeSelection, draftFrom, draftTo, defaultTimeSelection, defaultFrom, defaultTo]);
|
|
161
148
|
// Use combined sumBy hook for fetching labels and computing defaults (based on committed state)
|
|
162
|
-
const {
|
|
149
|
+
const {
|
|
150
|
+
sumBy: computedSumByFromURL,
|
|
151
|
+
isLoading: sumBySelectionLoading,
|
|
152
|
+
draftSumBy,
|
|
153
|
+
setDraftSumBy,
|
|
154
|
+
isDraftSumByLoading,
|
|
155
|
+
} = useSumBy(
|
|
163
156
|
queryClient,
|
|
157
|
+
profileType?.profileName !== '' ? profileType : draftProfileType,
|
|
158
|
+
draftTimeRange,
|
|
164
159
|
draftProfileType,
|
|
165
160
|
draftTimeRange,
|
|
166
161
|
sumBy
|
|
167
162
|
);
|
|
168
163
|
|
|
164
|
+
// Sync draft state with URL state when URL changes externally
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
setDraftExpression(expression ?? defaultExpression);
|
|
167
|
+
}, [expression, defaultExpression]);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
setDraftFrom(from ?? defaultFrom?.toString() ?? '');
|
|
171
|
+
}, [from, defaultFrom]);
|
|
172
|
+
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
setDraftTo(to ?? defaultTo?.toString() ?? '');
|
|
175
|
+
}, [to, defaultTo]);
|
|
176
|
+
|
|
177
|
+
useEffect(() => {
|
|
178
|
+
setDraftTimeSelection(timeSelection ?? defaultTimeSelection);
|
|
179
|
+
}, [timeSelection, defaultTimeSelection]);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
setDraftSumBy(sumBy);
|
|
183
|
+
}, [sumBy, setDraftSumBy]);
|
|
184
|
+
|
|
185
|
+
// Sync computed sumBy to URL if URL doesn't already have a value
|
|
186
|
+
// to ensure the shared URL can always pick it up.
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (sumByParam === undefined && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
|
|
189
|
+
setSumByParam(sumByToParam(computedSumByFromURL));
|
|
190
|
+
}
|
|
191
|
+
}, [sumByParam, computedSumByFromURL, sumBySelectionLoading, setSumByParam]);
|
|
192
|
+
|
|
169
193
|
// Construct the QuerySelection object (committed state from URL)
|
|
170
194
|
const querySelection: QuerySelection = useMemo(() => {
|
|
171
195
|
const range = DateTimeRange.fromRangeKey(
|
|
@@ -285,12 +309,16 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
285
309
|
setFromState(finalFrom);
|
|
286
310
|
setToState(finalTo);
|
|
287
311
|
setTimeSelectionState(finalTimeSelection);
|
|
288
|
-
setSumByParam(sumByToParam(draftSumBy));
|
|
289
312
|
|
|
290
313
|
// Auto-calculate merge parameters for delta profiles
|
|
291
314
|
// Parse the final expression to check if it's a delta profile
|
|
292
315
|
const finalQuery = Query.parse(finalExpression);
|
|
293
316
|
const isDelta = finalQuery.profileType().delta;
|
|
317
|
+
if (isDelta) {
|
|
318
|
+
setSumByParam(sumByToParam(draftSumBy));
|
|
319
|
+
} else {
|
|
320
|
+
setSumByParam(DEFAULT_EMPTY_SUM_BY);
|
|
321
|
+
}
|
|
294
322
|
|
|
295
323
|
if (isDelta && finalFrom !== '' && finalTo !== '') {
|
|
296
324
|
const fromMs = parseInt(finalFrom);
|
|
@@ -313,6 +341,12 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
313
341
|
setSelectionParam(undefined);
|
|
314
342
|
}
|
|
315
343
|
resetFlameGraphState();
|
|
344
|
+
if (
|
|
345
|
+
draftProfileType.toString() !==
|
|
346
|
+
Query.parse(querySelection.expression).profileType().toString()
|
|
347
|
+
) {
|
|
348
|
+
resetStateOnProfileTypeChange();
|
|
349
|
+
}
|
|
316
350
|
});
|
|
317
351
|
},
|
|
318
352
|
[
|
|
@@ -334,6 +368,9 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
334
368
|
setMergeToState,
|
|
335
369
|
setSelectionParam,
|
|
336
370
|
resetFlameGraphState,
|
|
371
|
+
resetStateOnProfileTypeChange,
|
|
372
|
+
draftProfileType,
|
|
373
|
+
querySelection.expression,
|
|
337
374
|
]
|
|
338
375
|
);
|
|
339
376
|
|
|
@@ -346,9 +383,12 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
346
383
|
[]
|
|
347
384
|
);
|
|
348
385
|
|
|
349
|
-
const setDraftSumByCallback = useCallback(
|
|
350
|
-
|
|
351
|
-
|
|
386
|
+
const setDraftSumByCallback = useCallback(
|
|
387
|
+
(newSumBy: string[] | undefined) => {
|
|
388
|
+
setDraftSumBy(newSumBy);
|
|
389
|
+
},
|
|
390
|
+
[setDraftSumBy]
|
|
391
|
+
);
|
|
352
392
|
|
|
353
393
|
const setDraftProfileName = useCallback(
|
|
354
394
|
(newProfileName: string) => {
|
|
@@ -357,9 +397,10 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
357
397
|
const [newQuery, changed] = draftQuery.setProfileName(newProfileName);
|
|
358
398
|
if (changed) {
|
|
359
399
|
setDraftExpression(newQuery.toString());
|
|
400
|
+
setDraftSumBy(undefined);
|
|
360
401
|
}
|
|
361
402
|
},
|
|
362
|
-
[draftQuery]
|
|
403
|
+
[draftQuery, setDraftSumBy]
|
|
363
404
|
);
|
|
364
405
|
|
|
365
406
|
const setDraftMatchers = useCallback(
|
|
@@ -421,7 +462,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
|
|
|
421
462
|
setProfileSelection,
|
|
422
463
|
|
|
423
464
|
// Loading state
|
|
424
|
-
sumByLoading: sumBySelectionLoading,
|
|
465
|
+
sumByLoading: isDraftSumByLoading || sumBySelectionLoading,
|
|
425
466
|
|
|
426
467
|
draftParsedQuery,
|
|
427
468
|
parsedQuery,
|