@parca/profile 0.19.109 → 0.19.111
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 +8 -0
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +9 -1
- package/dist/ProfileSelector/useAutoQuerySelector.js +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts +2 -0
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +3 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +1 -0
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +17 -3
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.d.ts +2 -0
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.d.ts.map +1 -0
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +541 -0
- package/dist/QueryControls/index.d.ts.map +1 -1
- package/dist/QueryControls/index.js +1 -3
- package/dist/SourceView/Highlighter.d.ts +5 -2
- package/dist/SourceView/Highlighter.d.ts.map +1 -1
- package/dist/SourceView/Highlighter.js +3 -2
- package/dist/SourceView/index.d.ts.map +1 -1
- package/dist/SourceView/index.js +40 -11
- package/dist/hooks/useLabels.d.ts.map +1 -1
- package/dist/hooks/useLabels.js +0 -7
- package/dist/hooks/useQueryState.d.ts +4 -0
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +29 -5
- package/dist/hooks/useQueryState.test.js +72 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/package.json +3 -3
- package/src/ProfileSelector/index.tsx +14 -1
- package/src/ProfileSelector/useAutoQuerySelector.ts +1 -1
- package/src/ProfileView/components/ProfileFilters/useProfileFilters.ts +5 -1
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +663 -0
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +24 -3
- package/src/QueryControls/index.tsx +1 -3
- package/src/SourceView/Highlighter.tsx +6 -5
- package/src/SourceView/index.tsx +45 -12
- package/src/hooks/useLabels.ts +0 -10
- package/src/hooks/useQueryState.test.tsx +90 -11
- package/src/hooks/useQueryState.ts +36 -4
- package/src/index.tsx +4 -0
package/dist/SourceView/index.js
CHANGED
|
@@ -35,28 +35,57 @@ export const SourceView = React.memo(function SourceView({ data, loading, total,
|
|
|
35
35
|
id: MENU_ID,
|
|
36
36
|
});
|
|
37
37
|
const { startLine, endLine } = useLineRange();
|
|
38
|
-
const
|
|
38
|
+
const sourceTable = useMemo(() => {
|
|
39
39
|
if (data === undefined) {
|
|
40
|
-
return
|
|
40
|
+
return null;
|
|
41
41
|
}
|
|
42
42
|
const table = tableFromIPC(data.record);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
return {
|
|
44
|
+
numRows: table.numRows,
|
|
45
|
+
lineNumbers: table.getChild('line_number'),
|
|
46
|
+
cumulative: table.getChild('cumulative'),
|
|
47
|
+
flat: table.getChild('flat'),
|
|
48
|
+
};
|
|
46
49
|
}, [data]);
|
|
50
|
+
const getLineData = useCallback((lineNumber) => {
|
|
51
|
+
if (sourceTable === null || sourceTable.lineNumbers === null) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const { numRows, lineNumbers, cumulative, flat } = sourceTable;
|
|
55
|
+
let lo = 0;
|
|
56
|
+
let hi = numRows - 1;
|
|
57
|
+
while (lo <= hi) {
|
|
58
|
+
const mid = (lo + hi) >>> 1;
|
|
59
|
+
const midVal = Number(lineNumbers.get(mid));
|
|
60
|
+
if (midVal === lineNumber) {
|
|
61
|
+
return {
|
|
62
|
+
cumulative: cumulative?.get(mid) ?? 0n,
|
|
63
|
+
flat: flat?.get(mid) ?? 0n,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (midVal < lineNumber) {
|
|
67
|
+
lo = mid + 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
hi = mid - 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}, [sourceTable]);
|
|
47
75
|
const getProfileDataForLine = useCallback((line, newLine) => {
|
|
48
|
-
|
|
76
|
+
const data = getLineData(line);
|
|
77
|
+
if (data === undefined) {
|
|
49
78
|
return undefined;
|
|
50
79
|
}
|
|
51
|
-
if (cumulative
|
|
80
|
+
if (data.cumulative === 0n && data.flat === 0n) {
|
|
52
81
|
return undefined;
|
|
53
82
|
}
|
|
54
83
|
return {
|
|
55
84
|
line: newLine,
|
|
56
|
-
cumulative: Number(cumulative
|
|
57
|
-
flat: Number(flat
|
|
85
|
+
cumulative: Number(data.cumulative),
|
|
86
|
+
flat: Number(data.flat),
|
|
58
87
|
};
|
|
59
|
-
}, [
|
|
88
|
+
}, [getLineData]);
|
|
60
89
|
const [selectedCode, profileData] = useMemo(() => {
|
|
61
90
|
if (startLine === -1 && endLine === -1) {
|
|
62
91
|
return ['', []];
|
|
@@ -97,6 +126,6 @@ export const SourceView = React.memo(function SourceView({ data, loading, total,
|
|
|
97
126
|
event,
|
|
98
127
|
});
|
|
99
128
|
};
|
|
100
|
-
return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: [_jsx(Highlighter, { file: sourceFileName, content: data.source, renderer: profileAwareRenderer(
|
|
129
|
+
return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: [_jsx(Highlighter, { file: sourceFileName, content: data.source, renderer: profileAwareRenderer(getLineData, total, filtered, onContextMenu) }), sourceViewContextMenuItems.length > 0 ? (_jsx(Menu, { id: MENU_ID, children: sourceViewContextMenuItems.map(item => (_jsx(Item, { onClick: () => item.action(selectedCode, profileData), children: item.label }, item.id))) })) : null] }, "source-view-loaded") }));
|
|
101
130
|
});
|
|
102
131
|
export default SourceView;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLabels.d.ts","sourceRoot":"","sources":["../../src/hooks/useLabels.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useLabels.d.ts","sourceRoot":"","sources":["../../src/hooks/useLabels.ts"],"names":[],"mappings":"AAaA,OAAO,EAAgB,cAAc,EAAE,kBAAkB,EAAgB,MAAM,eAAe,CAAC;AAM/F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,UAAU,cAAc;IACtB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,GACxB,QAAQ,kBAAkB,EAC1B,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,QAAQ,MAAM,EAAE,KACf,aA8BF,CAAC;AAEF,eAAO,MAAM,cAAc,GACzB,QAAQ,kBAAkB,EAC1B,WAAW,MAAM,EACjB,aAAa,MAAM,EACnB,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,cA+BF,CAAC"}
|
package/dist/hooks/useLabels.js
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
|
-
import { useEffect } from 'react';
|
|
14
13
|
import { useGrpcMetadata } from '@parca/components';
|
|
15
14
|
import { millisToProtoTimestamp, sanitizeLabelValue } from '@parca/utilities';
|
|
16
15
|
import useGrpcQuery from '../useGrpcQuery';
|
|
@@ -35,9 +34,6 @@ export const useLabelNames = (client, profileType, start, end, match) => {
|
|
|
35
34
|
keepPreviousData: false,
|
|
36
35
|
},
|
|
37
36
|
});
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
console.log('Label names query result:', { data, error, isLoading });
|
|
40
|
-
}, [data, error, isLoading]);
|
|
41
37
|
return {
|
|
42
38
|
result: { response: data, error: error },
|
|
43
39
|
loading: isLoading,
|
|
@@ -67,9 +63,6 @@ export const useLabelValues = (client, labelName, profileType, start, end) => {
|
|
|
67
63
|
keepPreviousData: false,
|
|
68
64
|
},
|
|
69
65
|
});
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
console.log('Label values query result:', { data, error, isLoading, labelName });
|
|
72
|
-
}, [data, error, isLoading, labelName]);
|
|
73
66
|
return {
|
|
74
67
|
result: { response: data ?? [], error: error },
|
|
75
68
|
loading: isLoading,
|
|
@@ -8,6 +8,7 @@ interface UseQueryStateOptions {
|
|
|
8
8
|
defaultFrom?: number;
|
|
9
9
|
defaultTo?: number;
|
|
10
10
|
comparing?: boolean;
|
|
11
|
+
onProfileTypeChange?: () => void;
|
|
11
12
|
}
|
|
12
13
|
interface UseQueryStateReturn {
|
|
13
14
|
querySelection: QuerySelection;
|
|
@@ -28,6 +29,9 @@ interface UseQueryStateReturn {
|
|
|
28
29
|
sumByLoading: boolean;
|
|
29
30
|
draftParsedQuery: Query | null;
|
|
30
31
|
parsedQuery: Query | null;
|
|
32
|
+
setExpressionParam: (value: string | undefined) => void;
|
|
33
|
+
setSumByParam: (value: string | undefined) => void;
|
|
34
|
+
setGroupByParam: (value: string[] | undefined) => void;
|
|
31
35
|
}
|
|
32
36
|
export declare const useQueryState: (options?: UseQueryStateOptions) => UseQueryStateReturn;
|
|
33
37
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQueryState.d.ts","sourceRoot":"","sources":["../../src/hooks/useQueryState.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAEpC,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAC,gBAAgB,EAA8B,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAK7F,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useQueryState.d.ts","sourceRoot":"","sources":["../../src/hooks/useQueryState.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAEpC,OAAO,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAC,gBAAgB,EAA8B,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAK7F,UAAU,oBAAoB;IAC5B,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAClC;AAED,UAAU,mBAAmB;IAE3B,cAAc,EAAE,cAAc,CAAC;IAG/B,cAAc,EAAE,cAAc,CAAC;IAG/B,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7E,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;IACrD,mBAAmB,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAG7C,WAAW,EAAE,CAAC,kBAAkB,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAC,KAAK,IAAI,CAAC;IAG9F,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAG1C,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;IAGpC,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAGhF,YAAY,EAAE,OAAO,CAAC;IAGtB,gBAAgB,EAAE,KAAK,GAAG,IAAI,CAAC;IAG/B,WAAW,EAAE,KAAK,GAAG,IAAI,CAAC;IAE1B,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACxD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACnD,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;CACxD;AAED,eAAO,MAAM,aAAa,GAAI,UAAS,oBAAyB,KAAG,mBA2alE,CAAC"}
|
|
@@ -20,7 +20,7 @@ import { DEFAULT_EMPTY_SUM_BY, sumByToParam, useSumBy, useSumByFromParams } from
|
|
|
20
20
|
export const useQueryState = (options = {}) => {
|
|
21
21
|
const { queryServiceClient: queryClient } = useParcaContext();
|
|
22
22
|
const { suffix = '', defaultExpression = '', defaultTimeSelection = 'relative:minute|15', // Default to 15 minutes relative
|
|
23
|
-
defaultFrom, defaultTo, comparing = false, } = options;
|
|
23
|
+
defaultFrom, defaultTo, comparing = false, onProfileTypeChange, } = options;
|
|
24
24
|
const batchUpdates = useURLStateBatch();
|
|
25
25
|
const resetFlameGraphState = useResetFlameGraphState();
|
|
26
26
|
const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
|
|
@@ -38,6 +38,9 @@ export const useQueryState = (options = {}) => {
|
|
|
38
38
|
defaultValue: defaultTimeSelection,
|
|
39
39
|
});
|
|
40
40
|
const [sumByParam, setSumByParam] = useURLState(`sum_by${suffix}`);
|
|
41
|
+
const [, setGroupByParam] = useURLState('group_by', {
|
|
42
|
+
alwaysReturnArray: true,
|
|
43
|
+
});
|
|
41
44
|
const [mergeFrom, setMergeFromState] = useURLState(`merge_from${suffix}`);
|
|
42
45
|
const [mergeTo, setMergeToState] = useURLState(`merge_to${suffix}`);
|
|
43
46
|
// ProfileSelection URL state hooks - reuses merge_from/merge_to but adds selection
|
|
@@ -54,7 +57,11 @@ export const useQueryState = (options = {}) => {
|
|
|
54
57
|
try {
|
|
55
58
|
return Query.parse(draftExpression ?? '');
|
|
56
59
|
}
|
|
57
|
-
catch {
|
|
60
|
+
catch (error) {
|
|
61
|
+
console.warn('Failed to parse draft expression', {
|
|
62
|
+
expression: draftExpression,
|
|
63
|
+
error: error instanceof Error ? error.message : String(error),
|
|
64
|
+
});
|
|
58
65
|
return Query.parse('');
|
|
59
66
|
}
|
|
60
67
|
}, [draftExpression]);
|
|
@@ -62,7 +69,11 @@ export const useQueryState = (options = {}) => {
|
|
|
62
69
|
try {
|
|
63
70
|
return Query.parse(expression ?? '');
|
|
64
71
|
}
|
|
65
|
-
catch {
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.warn('Failed to parse expression', {
|
|
74
|
+
expression,
|
|
75
|
+
error: error instanceof Error ? error.message : String(error),
|
|
76
|
+
});
|
|
66
77
|
return Query.parse('');
|
|
67
78
|
}
|
|
68
79
|
}, [expression]);
|
|
@@ -232,6 +243,7 @@ export const useQueryState = (options = {}) => {
|
|
|
232
243
|
if (draftProfileType.toString() !==
|
|
233
244
|
Query.parse(querySelection.expression).profileType().toString()) {
|
|
234
245
|
resetStateOnProfileTypeChange();
|
|
246
|
+
onProfileTypeChange?.();
|
|
235
247
|
}
|
|
236
248
|
});
|
|
237
249
|
}, [
|
|
@@ -254,6 +266,7 @@ export const useQueryState = (options = {}) => {
|
|
|
254
266
|
setSelectionParam,
|
|
255
267
|
resetFlameGraphState,
|
|
256
268
|
resetStateOnProfileTypeChange,
|
|
269
|
+
onProfileTypeChange,
|
|
257
270
|
draftProfileType,
|
|
258
271
|
querySelection.expression,
|
|
259
272
|
]);
|
|
@@ -290,7 +303,11 @@ export const useQueryState = (options = {}) => {
|
|
|
290
303
|
try {
|
|
291
304
|
return Query.parse(draftSelection.expression ?? '');
|
|
292
305
|
}
|
|
293
|
-
catch {
|
|
306
|
+
catch (error) {
|
|
307
|
+
console.warn('Failed to parse draft selection expression', {
|
|
308
|
+
expression: draftSelection.expression,
|
|
309
|
+
error: error instanceof Error ? error.message : String(error),
|
|
310
|
+
});
|
|
294
311
|
return Query.parse('');
|
|
295
312
|
}
|
|
296
313
|
}, [draftSelection.expression]);
|
|
@@ -298,7 +315,11 @@ export const useQueryState = (options = {}) => {
|
|
|
298
315
|
try {
|
|
299
316
|
return Query.parse(querySelection.expression ?? '');
|
|
300
317
|
}
|
|
301
|
-
catch {
|
|
318
|
+
catch (error) {
|
|
319
|
+
console.warn('Failed to parse query selection expression', {
|
|
320
|
+
expression: querySelection.expression,
|
|
321
|
+
error: error instanceof Error ? error.message : String(error),
|
|
322
|
+
});
|
|
302
323
|
return Query.parse('');
|
|
303
324
|
}
|
|
304
325
|
}, [querySelection.expression]);
|
|
@@ -323,5 +344,8 @@ export const useQueryState = (options = {}) => {
|
|
|
323
344
|
sumByLoading: isDraftSumByLoading || sumBySelectionLoading,
|
|
324
345
|
draftParsedQuery,
|
|
325
346
|
parsedQuery,
|
|
347
|
+
setExpressionParam: setExpressionState,
|
|
348
|
+
setSumByParam,
|
|
349
|
+
setGroupByParam,
|
|
326
350
|
};
|
|
327
351
|
};
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { act } from 'react';
|
|
2
15
|
// eslint-disable-next-line import/named
|
|
3
|
-
import {
|
|
16
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
17
|
+
// eslint-disable-next-line import/named
|
|
18
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
4
19
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
20
|
import { URLStateProvider } from '@parca/components';
|
|
6
21
|
import { useQueryState } from './useQueryState';
|
|
@@ -72,9 +87,38 @@ vi.mock('../useSumBy', async () => {
|
|
|
72
87
|
},
|
|
73
88
|
};
|
|
74
89
|
});
|
|
90
|
+
// Track profile types loading state for tests
|
|
91
|
+
let mockProfileTypesLoading = false;
|
|
92
|
+
let mockProfileTypesData;
|
|
93
|
+
// Mock useProfileTypes to control loading state in tests
|
|
94
|
+
vi.mock('../ProfileSelector', async () => {
|
|
95
|
+
const actual = await vi.importActual('../ProfileSelector');
|
|
96
|
+
return {
|
|
97
|
+
...actual,
|
|
98
|
+
useProfileTypes: () => ({
|
|
99
|
+
loading: mockProfileTypesLoading,
|
|
100
|
+
data: mockProfileTypesData,
|
|
101
|
+
error: null,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
// Helper to set profile types loading state for tests
|
|
106
|
+
const setProfileTypesLoading = (loading) => {
|
|
107
|
+
mockProfileTypesLoading = loading;
|
|
108
|
+
};
|
|
109
|
+
const setProfileTypesData = (data) => {
|
|
110
|
+
mockProfileTypesData = data;
|
|
111
|
+
};
|
|
75
112
|
// Helper to create wrapper with URLStateProvider
|
|
76
113
|
const createWrapper = (paramPreferences = {}) => {
|
|
77
|
-
const
|
|
114
|
+
const queryClient = new QueryClient({
|
|
115
|
+
defaultOptions: {
|
|
116
|
+
queries: {
|
|
117
|
+
retry: false,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
const Wrapper = ({ children }) => (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(URLStateProvider, { navigateTo: mockNavigateTo, paramPreferences: paramPreferences, children: children }) }));
|
|
78
122
|
Wrapper.displayName = 'URLStateProviderWrapper';
|
|
79
123
|
return Wrapper;
|
|
80
124
|
};
|
|
@@ -86,17 +130,20 @@ describe('useQueryState', () => {
|
|
|
86
130
|
writable: true,
|
|
87
131
|
});
|
|
88
132
|
mockLocation.search = '';
|
|
133
|
+
// Reset profile types mock state
|
|
134
|
+
setProfileTypesLoading(false);
|
|
135
|
+
setProfileTypesData(undefined);
|
|
89
136
|
});
|
|
90
137
|
describe('Basic functionality', () => {
|
|
91
138
|
it('should initialize with default values', () => {
|
|
92
139
|
const { result } = renderHook(() => useQueryState({
|
|
93
|
-
defaultExpression: 'process_cpu{}',
|
|
140
|
+
defaultExpression: 'process_cpu:cpu:nanoseconds:cpu:nanoseconds{}',
|
|
94
141
|
defaultTimeSelection: 'relative:hour|1',
|
|
95
142
|
defaultFrom: 1000,
|
|
96
143
|
defaultTo: 2000,
|
|
97
144
|
}), { wrapper: createWrapper() });
|
|
98
145
|
const { querySelection } = result.current;
|
|
99
|
-
expect(querySelection.expression).toBe('process_cpu{}');
|
|
146
|
+
expect(querySelection.expression).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{}');
|
|
100
147
|
expect(querySelection.timeSelection).toBe('relative:hour|1');
|
|
101
148
|
// From/to should be calculated from the range
|
|
102
149
|
expect(querySelection.from).toBeDefined();
|
|
@@ -395,12 +442,28 @@ describe('useQueryState', () => {
|
|
|
395
442
|
});
|
|
396
443
|
});
|
|
397
444
|
describe('Edge cases', () => {
|
|
398
|
-
it('should handle invalid expression gracefully', () => {
|
|
445
|
+
it('should handle invalid expression gracefully and log warning', () => {
|
|
446
|
+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
399
447
|
const { result } = renderHook(() => useQueryState({
|
|
400
448
|
defaultExpression: 'invalid{{}expression',
|
|
401
449
|
}), { wrapper: createWrapper() });
|
|
402
|
-
// Should not throw error
|
|
450
|
+
// Should not throw error - invalid expressions are caught and logged
|
|
451
|
+
expect(() => result.current.querySelection).not.toThrow();
|
|
452
|
+
// Should fall back to empty expression
|
|
453
|
+
expect(result.current.querySelection.expression).toBe('invalid{{}expression');
|
|
454
|
+
// Should log a warning about the parse failure
|
|
455
|
+
expect(consoleSpy).toHaveBeenCalledWith('Failed to parse expression', expect.objectContaining({
|
|
456
|
+
expression: 'invalid{{}expression',
|
|
457
|
+
}));
|
|
458
|
+
consoleSpy.mockRestore();
|
|
459
|
+
});
|
|
460
|
+
it('should handle empty expression gracefully', () => {
|
|
461
|
+
const { result } = renderHook(() => useQueryState({
|
|
462
|
+
defaultExpression: '',
|
|
463
|
+
}), { wrapper: createWrapper() });
|
|
464
|
+
// Should not throw error with empty expression
|
|
403
465
|
expect(() => result.current.querySelection).not.toThrow();
|
|
466
|
+
expect(result.current.querySelection.expression).toBe('');
|
|
404
467
|
});
|
|
405
468
|
it('should clear merge params for non-delta profiles', async () => {
|
|
406
469
|
mockLocation.search =
|
|
@@ -896,7 +959,8 @@ describe('useQueryState', () => {
|
|
|
896
959
|
});
|
|
897
960
|
});
|
|
898
961
|
it('should preserve other URL params when setting ProfileSelection', async () => {
|
|
899
|
-
mockLocation.search =
|
|
962
|
+
mockLocation.search =
|
|
963
|
+
'?expression_a=process_cpu:cpu:nanoseconds:cpu:nanoseconds{}&other_param=value&unrelated=test';
|
|
900
964
|
const { result } = renderHook(() => useQueryState({ suffix: '_a' }), { wrapper: createWrapper() });
|
|
901
965
|
const mockQuery = {
|
|
902
966
|
toString: () => 'process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}',
|
|
@@ -911,7 +975,7 @@ describe('useQueryState', () => {
|
|
|
911
975
|
// ProfileSelection params should be set
|
|
912
976
|
expect(params.selection_a).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{pod="test"}');
|
|
913
977
|
// Other params should be preserved
|
|
914
|
-
expect(params.expression_a).toBe('process_cpu{}');
|
|
978
|
+
expect(params.expression_a).toBe('process_cpu:cpu:nanoseconds:cpu:nanoseconds{}');
|
|
915
979
|
expect(params.other_param).toBe('value');
|
|
916
980
|
expect(params.unrelated).toBe('test');
|
|
917
981
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { useQueryState } from './hooks/useQueryState';
|
|
|
11
11
|
export { useMetricsGraphDimensions } from './MetricsGraph/useMetricsGraphDimensions';
|
|
12
12
|
export * from './ProfileFlameGraph';
|
|
13
13
|
export * from './ProfileSource';
|
|
14
|
-
export { convertToProtoFilters, convertFromProtoFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
14
|
+
export { convertToProtoFilters, convertFromProtoFilters, useProfileFilters, type ProfileFilter, } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
15
15
|
export * from './ProfileView';
|
|
16
16
|
export * from './ProfileViewWithData';
|
|
17
17
|
export * from './utils';
|
|
@@ -23,5 +23,6 @@ export { QueryControls } from './QueryControls';
|
|
|
23
23
|
export { default as ProfileFilters } from './ProfileView/components/ProfileFilters';
|
|
24
24
|
export { useProfileFiltersUrlState } from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
|
|
25
25
|
export declare const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: ParamPreferences;
|
|
26
|
+
export { useProfileTypes } from './ProfileSelector';
|
|
26
27
|
export { ProfileExplorer, ProfileTypeSelector, CustomSelect, SelectWithRefresh, useLabelNames, MetricsGraph, type ContextMenuItemOrSubmenu, type Series, LabelsQueryProvider, useLabelsQueryProvider, UnifiedLabelsProvider, useUnifiedLabels, useQueryState, type LabelsQueryProviderContextType, };
|
|
27
28
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AAExD,OAAO,YAAY,EAAE,EAAC,KAAK,wBAAwB,EAAE,KAAK,MAAM,EAAC,MAAM,gBAAgB,CAAC;AACxF,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,8BAA8B,EACpC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,qBAAqB,EAAE,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AACxF,OAAO,EAAC,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAC,yBAAyB,EAAC,MAAM,0CAA0C,CAAC;AAEnF,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,uBAAuB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,mBAAmB,CAAC;AAExD,OAAO,YAAY,EAAE,EAAC,KAAK,wBAAwB,EAAE,KAAK,MAAM,EAAC,MAAM,gBAAgB,CAAC;AACxF,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAC,iBAAiB,EAAC,MAAM,qBAAqB,CAAC;AACtD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,KAAK,8BAA8B,EACpC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAC,qBAAqB,EAAE,gBAAgB,EAAC,MAAM,iCAAiC,CAAC;AACxF,OAAO,EAAC,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAEpD,OAAO,EAAC,yBAAyB,EAAC,MAAM,0CAA0C,CAAC;AAEnF,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,KAAK,aAAa,GACnB,MAAM,2DAA2D,CAAC;AACnE,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,SAAS,CAAC;AACxB,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAC,aAAa,EAAC,MAAM,iBAAiB,CAAC;AAE9C,OAAO,EAAC,OAAO,IAAI,cAAc,EAAC,MAAM,yCAAyC,CAAC;AAClF,OAAO,EAAC,yBAAyB,EAAC,MAAM,mEAAmE,CAAC;AAE5G,eAAO,MAAM,qCAAqC,EAAE,gBAQnD,CAAC;AAEF,OAAO,EAAC,eAAe,EAAC,MAAM,mBAAmB,CAAC;AAElD,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,MAAM,EACX,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,aAAa,EACb,KAAK,8BAA8B,GACpC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import { useQueryState } from './hooks/useQueryState';
|
|
|
22
22
|
export { useMetricsGraphDimensions } from './MetricsGraph/useMetricsGraphDimensions';
|
|
23
23
|
export * from './ProfileFlameGraph';
|
|
24
24
|
export * from './ProfileSource';
|
|
25
|
-
export { convertToProtoFilters, convertFromProtoFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
25
|
+
export { convertToProtoFilters, convertFromProtoFilters, useProfileFilters, } from './ProfileView/components/ProfileFilters/useProfileFilters';
|
|
26
26
|
export * from './ProfileView';
|
|
27
27
|
export * from './ProfileViewWithData';
|
|
28
28
|
export * from './utils';
|
|
@@ -42,4 +42,5 @@ export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
|
|
|
42
42
|
splitOnCommas: true,
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
|
+
export { useProfileTypes } from './ProfileSelector';
|
|
45
46
|
export { ProfileExplorer, ProfileTypeSelector, CustomSelect, SelectWithRefresh, useLabelNames, MetricsGraph, LabelsQueryProvider, useLabelsQueryProvider, UnifiedLabelsProvider, useUnifiedLabels, useQueryState, };
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.111",
|
|
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
9
|
"@parca/client": "0.17.16",
|
|
10
|
-
"@parca/components": "0.16.
|
|
10
|
+
"@parca/components": "0.16.396",
|
|
11
11
|
"@parca/dynamicsize": "0.16.72",
|
|
12
12
|
"@parca/hooks": "0.0.116",
|
|
13
13
|
"@parca/icons": "0.16.79",
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"access": "public",
|
|
85
85
|
"registry": "https://registry.npmjs.org/"
|
|
86
86
|
},
|
|
87
|
-
"gitHead": "
|
|
87
|
+
"gitHead": "77f4ccf0e86c3d2c62f8a24d2d5316b78f24dec7"
|
|
88
88
|
}
|
|
@@ -30,6 +30,10 @@ import {TEST_IDS, testId} from '@parca/test-utils';
|
|
|
30
30
|
import {millisToProtoTimestamp, type NavigateFunction} from '@parca/utilities';
|
|
31
31
|
|
|
32
32
|
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
33
|
+
import {
|
|
34
|
+
ProfileFilter,
|
|
35
|
+
useProfileFilters,
|
|
36
|
+
} from '../ProfileView/components/ProfileFilters/useProfileFilters';
|
|
33
37
|
import {QueryControls} from '../QueryControls';
|
|
34
38
|
import {LabelsQueryProvider, useLabelsQueryProvider} from '../contexts/LabelsQueryProvider';
|
|
35
39
|
import {UnifiedLabelsProvider} from '../contexts/UnifiedLabelsContext';
|
|
@@ -119,6 +123,15 @@ const ProfileSelector = ({
|
|
|
119
123
|
const [queryBrowserMode, setQueryBrowserMode] = useURLState('query_browser_mode');
|
|
120
124
|
const batchUpdates = useURLStateBatch();
|
|
121
125
|
|
|
126
|
+
const profileFilterDefaults = viewComponent?.profileFilterDefaults as ProfileFilter[] | undefined;
|
|
127
|
+
const {forceApplyFilters} = useProfileFilters();
|
|
128
|
+
|
|
129
|
+
const handleProfileTypeChange = useCallback(() => {
|
|
130
|
+
if (profileFilterDefaults != null && profileFilterDefaults.length > 0) {
|
|
131
|
+
forceApplyFilters(profileFilterDefaults);
|
|
132
|
+
}
|
|
133
|
+
}, [forceApplyFilters, profileFilterDefaults]);
|
|
134
|
+
|
|
122
135
|
// Use the new useQueryState hook - reads directly from URL params
|
|
123
136
|
const {
|
|
124
137
|
querySelection,
|
|
@@ -133,7 +146,7 @@ const ProfileSelector = ({
|
|
|
133
146
|
setProfileSelection,
|
|
134
147
|
sumByLoading,
|
|
135
148
|
draftParsedQuery,
|
|
136
|
-
} = useQueryState({suffix});
|
|
149
|
+
} = useQueryState({suffix, onProfileTypeChange: handleProfileTypeChange});
|
|
137
150
|
|
|
138
151
|
// Use draft state for local state instead of committed state
|
|
139
152
|
const [timeRangeSelection, setTimeRangeSelection] = useState(
|
|
@@ -137,7 +137,7 @@ export const useAutoQuerySelector = ({
|
|
|
137
137
|
}
|
|
138
138
|
dispatch(setAutoQuery('true'));
|
|
139
139
|
let profileType = profileTypesData.types.find(
|
|
140
|
-
type => type.name === 'parca_agent' && type.delta
|
|
140
|
+
type => type.name === 'parca_agent' && type.sampleType === 'samples' && type.delta
|
|
141
141
|
);
|
|
142
142
|
if (profileType == null) {
|
|
143
143
|
profileType = profileTypesData.types.find(
|
|
@@ -237,8 +237,10 @@ export const useProfileFilters = (): {
|
|
|
237
237
|
removeFilter: (id: string) => void;
|
|
238
238
|
updateFilter: (id: string, updates: Partial<ProfileFilter>) => void;
|
|
239
239
|
resetFilters: () => void;
|
|
240
|
+
setAppliedFilters: (filters: ProfileFilter[]) => void;
|
|
241
|
+
forceApplyFilters: (filters: ProfileFilter[]) => void;
|
|
240
242
|
} => {
|
|
241
|
-
const {appliedFilters, setAppliedFilters} = useProfileFiltersUrlState();
|
|
243
|
+
const {appliedFilters, setAppliedFilters, forceApplyFilters} = useProfileFiltersUrlState();
|
|
242
244
|
const resetFlameGraphState = useResetFlameGraphState();
|
|
243
245
|
|
|
244
246
|
const [localFilters, setLocalFilters] = useState<ProfileFilter[]>(appliedFilters ?? []);
|
|
@@ -422,5 +424,7 @@ export const useProfileFilters = (): {
|
|
|
422
424
|
removeFilter,
|
|
423
425
|
updateFilter,
|
|
424
426
|
resetFilters,
|
|
427
|
+
setAppliedFilters,
|
|
428
|
+
forceApplyFilters,
|
|
425
429
|
};
|
|
426
430
|
};
|