@parca/profile 0.19.142 → 0.19.144

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.d.ts.map +1 -1
  3. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +22 -28
  4. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
  5. package/dist/ProfileExplorer/ProfileExplorerCompare.js +72 -73
  6. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts +2 -2
  7. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -1
  8. package/dist/ProfileFlameChart/index.d.ts.map +1 -1
  9. package/dist/ProfileFlameChart/index.js +20 -24
  10. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
  11. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +13 -14
  12. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
  13. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +6 -5
  14. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  15. package/dist/ProfileFlameGraph/index.js +8 -7
  16. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  17. package/dist/ProfileMetricsGraph/index.js +6 -8
  18. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  19. package/dist/ProfileSelector/MetricsGraphSection.js +48 -55
  20. package/dist/ProfileSelector/index.d.ts +1 -1
  21. package/dist/ProfileSelector/index.d.ts.map +1 -1
  22. package/dist/ProfileSelector/index.js +216 -210
  23. package/dist/ProfileSelector/useAutoQuerySelector.d.ts +1 -3
  24. package/dist/ProfileSelector/useAutoQuerySelector.d.ts.map +1 -1
  25. package/dist/ProfileSelector/useAutoQuerySelector.js +133 -104
  26. package/dist/ProfileView/components/ActionButtons/SortByDropdown.d.ts.map +1 -1
  27. package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +24 -25
  28. package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
  29. package/dist/ProfileView/components/ColorStackLegend.js +3 -5
  30. package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -1
  31. package/dist/ProfileView/components/InvertCallStack/index.js +47 -47
  32. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +1 -2
  33. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
  34. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +37 -34
  35. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  36. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +282 -294
  37. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  38. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +7 -8
  39. package/dist/ProfileView/components/Toolbars/index.d.ts +2 -2
  40. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  41. package/dist/ProfileView/components/Toolbars/index.js +1 -1
  42. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  43. package/dist/ProfileView/components/ViewSelector/index.js +53 -75
  44. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  45. package/dist/ProfileView/context/DashboardContext.js +36 -44
  46. package/dist/ProfileView/hooks/useResetFlameGraphState.d.ts.map +1 -1
  47. package/dist/ProfileView/hooks/useResetFlameGraphState.js +8 -7
  48. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  49. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +59 -59
  50. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.d.ts.map +1 -1
  51. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +37 -22
  52. package/dist/ProfileView/hooks/useVisualizationState.d.ts +3 -3
  53. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  54. package/dist/ProfileView/hooks/useVisualizationState.js +116 -147
  55. package/dist/ProfileViewWithData.d.ts.map +1 -1
  56. package/dist/ProfileViewWithData.js +35 -45
  57. package/dist/Sandwich/index.d.ts.map +1 -1
  58. package/dist/Sandwich/index.js +6 -5
  59. package/dist/SourceView/index.d.ts.map +1 -1
  60. package/dist/SourceView/index.js +6 -4
  61. package/dist/SourceView/useSelectedLineRange.d.ts.map +1 -1
  62. package/dist/SourceView/useSelectedLineRange.js +52 -76
  63. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  64. package/dist/Table/MoreDropdown.js +42 -53
  65. package/dist/Table/TableContextMenu.d.ts.map +1 -1
  66. package/dist/Table/TableContextMenu.js +15 -19
  67. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  68. package/dist/Table/hooks/useTableConfiguration.js +107 -115
  69. package/dist/Table/index.d.ts.map +1 -1
  70. package/dist/Table/index.js +16 -16
  71. package/dist/TopTable/index.d.ts.map +1 -1
  72. package/dist/TopTable/index.js +112 -127
  73. package/dist/hooks/urlParsers.d.ts +18 -0
  74. package/dist/hooks/urlParsers.d.ts.map +1 -0
  75. package/dist/hooks/urlParsers.js +44 -0
  76. package/dist/hooks/useColorBy.d.ts +5 -0
  77. package/dist/hooks/useColorBy.d.ts.map +1 -0
  78. package/dist/hooks/useColorBy.js +63 -0
  79. package/dist/hooks/useCompareModeMeta.d.ts.map +1 -1
  80. package/dist/hooks/useCompareModeMeta.js +94 -138
  81. package/dist/hooks/useDashboardItems.d.ts +5 -0
  82. package/dist/hooks/useDashboardItems.d.ts.map +1 -0
  83. package/dist/hooks/useDashboardItems.js +68 -0
  84. package/dist/hooks/useQueryState.d.ts +4 -4
  85. package/dist/hooks/useQueryState.d.ts.map +1 -1
  86. package/dist/hooks/useQueryState.js +127 -122
  87. package/dist/index.d.ts +3 -2
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +3 -12
  90. package/dist/useQuery.js +2 -1
  91. package/dist/useSumBy.d.ts +1 -1
  92. package/dist/useSumBy.d.ts.map +1 -1
  93. package/dist/useSumBy.js +2 -2
  94. package/package.json +4 -3
  95. package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +11 -13
  96. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +11 -9
  97. package/src/ProfileFlameChart/SamplesStrips/index.tsx +2 -2
  98. package/src/ProfileFlameChart/index.tsx +21 -28
  99. package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +10 -9
  100. package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +5 -3
  101. package/src/ProfileFlameGraph/index.tsx +6 -9
  102. package/src/ProfileMetricsGraph/index.tsx +6 -8
  103. package/src/ProfileSelector/MetricsGraphSection.tsx +5 -10
  104. package/src/ProfileSelector/index.tsx +33 -33
  105. package/src/ProfileSelector/useAutoQuerySelector.ts +64 -42
  106. package/src/ProfileView/components/ActionButtons/SortByDropdown.tsx +10 -6
  107. package/src/ProfileView/components/ColorStackLegend.tsx +2 -4
  108. package/src/ProfileView/components/InvertCallStack/index.tsx +5 -4
  109. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +94 -192
  110. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +21 -21
  111. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +24 -25
  112. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +4 -5
  113. package/src/ProfileView/components/Toolbars/index.tsx +3 -3
  114. package/src/ProfileView/components/ViewSelector/index.tsx +9 -16
  115. package/src/ProfileView/context/DashboardContext.tsx +6 -6
  116. package/src/ProfileView/hooks/useResetFlameGraphState.ts +6 -4
  117. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +24 -26
  118. package/src/ProfileView/hooks/useResetStateOnSeriesChange.ts +16 -8
  119. package/src/ProfileView/hooks/useVisualizationState.ts +61 -69
  120. package/src/ProfileViewWithData.tsx +29 -35
  121. package/src/Sandwich/index.tsx +4 -3
  122. package/src/SourceView/index.tsx +4 -2
  123. package/src/SourceView/useSelectedLineRange.ts +34 -19
  124. package/src/Table/MoreDropdown.tsx +9 -11
  125. package/src/Table/TableContextMenu.tsx +10 -13
  126. package/src/Table/hooks/useTableConfiguration.tsx +3 -4
  127. package/src/Table/index.tsx +12 -21
  128. package/src/TopTable/index.tsx +3 -4
  129. package/src/hooks/urlParsers.ts +38 -0
  130. package/src/hooks/useColorBy.ts +42 -0
  131. package/src/hooks/useCompareModeMeta.ts +61 -91
  132. package/src/hooks/useDashboardItems.ts +46 -0
  133. package/src/hooks/useQueryState.test.tsx +275 -345
  134. package/src/hooks/useQueryState.ts +153 -120
  135. package/src/index.tsx +16 -15
  136. package/src/useQuery.tsx +1 -1
  137. package/src/useSumBy.ts +3 -3
@@ -11,9 +11,11 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useCallback, useEffect, useMemo, useState} from 'react';
14
+ import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
- import {DateTimeRange, useParcaContext, useURLState, useURLStateBatch} from '@parca/components';
16
+ import {useQueryState as useNuqsQueryState, useQueryStates} from 'nuqs';
17
+
18
+ import {DateTimeRange, useParcaContext} from '@parca/components';
17
19
  import {Query} from '@parca/parser';
18
20
 
19
21
  import {QuerySelection} from '../ProfileSelector';
@@ -21,6 +23,7 @@ import {ProfileSelection, ProfileSelectionFromParams, ProfileSource} from '../Pr
21
23
  import {useResetFlameGraphState} from '../ProfileView/hooks/useResetFlameGraphState';
22
24
  import {useResetStateOnProfileTypeChange} from '../ProfileView/hooks/useResetStateOnProfileTypeChange';
23
25
  import {DEFAULT_EMPTY_SUM_BY, sumByToParam, useSumBy, useSumByFromParams} from '../useSumBy';
26
+ import {commaArrayParam, stringParam} from './urlParsers';
24
27
 
25
28
  interface UseQueryStateOptions {
26
29
  suffix?: '_a' | '_b'; // For comparison mode
@@ -47,7 +50,10 @@ interface UseQueryStateReturn {
47
50
  setDraftMatchers: (matchers: string) => void;
48
51
 
49
52
  // Commit function
50
- commitDraft: (refreshedTimeRange?: {from: number; to: number; timeSelection: string}) => void;
53
+ commitDraft: (
54
+ refreshedTimeRange?: {from: number; to: number; timeSelection: string},
55
+ expression?: string
56
+ ) => void;
51
57
 
52
58
  // ProfileSelection state (separate from QuerySelection)
53
59
  profileSelection: ProfileSelection | null;
@@ -67,9 +73,9 @@ interface UseQueryStateReturn {
67
73
  // parsed query
68
74
  parsedQuery: Query | null;
69
75
 
70
- setExpressionParam: (value: string | undefined) => void;
71
- setSumByParam: (value: string | undefined) => void;
72
- setGroupByParam: (value: string[] | undefined) => void;
76
+ setExpressionParam: (value: string | null) => void;
77
+ setSumByParam: (value: string | null) => void;
78
+ setGroupByParam: (value: string[] | null) => void;
73
79
  }
74
80
 
75
81
  export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryStateReturn => {
@@ -84,41 +90,65 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
84
90
  onProfileTypeChange,
85
91
  } = options;
86
92
 
87
- const batchUpdates = useURLStateBatch();
88
93
  const resetFlameGraphState = useResetFlameGraphState();
89
94
  const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
90
95
 
91
- // URL state hooks with appropriate suffixes
92
- const [expression, setExpressionState] = useURLState<string>(`expression${suffix}`, {
93
- defaultValue: defaultExpression,
94
- });
95
-
96
- const [from, setFromState] = useURLState<string>(`from${suffix}`, {
97
- defaultValue: defaultFrom?.toString(),
98
- });
99
-
100
- const [to, setToState] = useURLState<string>(`to${suffix}`, {
101
- defaultValue: defaultTo?.toString(),
102
- });
103
-
104
- const [timeSelection, setTimeSelectionState] = useURLState<string>(`time_selection${suffix}`, {
105
- defaultValue: defaultTimeSelection,
106
- });
107
-
108
- const [sumByParam, setSumByParam] = useURLState<string>(`sum_by${suffix}`);
109
-
110
- const [, setGroupByParam] = useURLState<string>('group_by', {
111
- alwaysReturnArray: true,
112
- });
96
+ // URL state hooks with appropriate suffixes via useQueryStates
97
+ const [queryParams, setQueryParams] = useQueryStates(
98
+ {
99
+ expression: stringParam,
100
+ from: stringParam,
101
+ to: stringParam,
102
+ time_selection: stringParam,
103
+ sum_by: stringParam,
104
+ merge_from: stringParam,
105
+ merge_to: stringParam,
106
+ selection: stringParam,
107
+ },
108
+ {
109
+ history: 'replace',
110
+ urlKeys: {
111
+ expression: `expression${suffix}`,
112
+ from: `from${suffix}`,
113
+ to: `to${suffix}`,
114
+ time_selection: `time_selection${suffix}`,
115
+ sum_by: `sum_by${suffix}`,
116
+ merge_from: `merge_from${suffix}`,
117
+ merge_to: `merge_to${suffix}`,
118
+ selection: `selection${suffix}`,
119
+ },
120
+ }
121
+ );
113
122
 
114
- const [mergeFrom, setMergeFromState] = useURLState<string>(`merge_from${suffix}`);
115
- const [mergeTo, setMergeToState] = useURLState<string>(`merge_to${suffix}`);
123
+ const expression = queryParams.expression ?? defaultExpression;
124
+ const from = queryParams.from ?? defaultFrom?.toString();
125
+ const to = queryParams.to ?? defaultTo?.toString();
126
+ const timeSelection = queryParams.time_selection ?? defaultTimeSelection;
127
+ const sumByParam = queryParams.sum_by;
128
+ const mergeFrom = queryParams.merge_from;
129
+ const mergeTo = queryParams.merge_to;
130
+ const selectionParam = queryParams.selection;
131
+
132
+ // Individual setters for direct access
133
+ const setExpressionState = useCallback(
134
+ (val: string | null) => void setQueryParams({expression: val}),
135
+ [setQueryParams]
136
+ );
137
+ const setSumByParam = useCallback(
138
+ (val: string | null) => void setQueryParams({sum_by: val}),
139
+ [setQueryParams]
140
+ );
116
141
 
117
- // ProfileSelection URL state hooks - reuses merge_from/merge_to but adds selection
118
- const [selectionParam, setSelectionParam] = useURLState<string>(`selection${suffix}`);
142
+ const [, setRawGroupByParam] = useNuqsQueryState('group_by', commaArrayParam);
143
+ const setGroupByParam = useCallback(
144
+ (val: string[] | null) => {
145
+ void setRawGroupByParam(val);
146
+ },
147
+ [setRawGroupByParam]
148
+ );
119
149
 
120
150
  // Parse sumBy from URL parameter format
121
- const sumBy = useSumByFromParams(sumByParam);
151
+ const sumBy = useSumByFromParams(sumByParam ?? undefined);
122
152
 
123
153
  // Draft state management
124
154
  const [draftExpression, setDraftExpression] = useState<string>(expression ?? defaultExpression);
@@ -202,9 +232,21 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
202
232
 
203
233
  // Sync computed sumBy to URL if URL doesn't already have a value
204
234
  // to ensure the shared URL can always pick it up.
235
+ // Only run once (when sumByParam first becomes available), not on every change,
236
+ // to avoid oscillation with external writers (useViewQueryState).
237
+ const hasSyncedSumByRef = useRef(false);
205
238
  useEffect(() => {
206
- if (sumByParam === undefined && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
207
- setSumByParam(sumByToParam(computedSumByFromURL));
239
+ if (sumByParam !== null) {
240
+ hasSyncedSumByRef.current = true;
241
+ }
242
+ if (
243
+ !hasSyncedSumByRef.current &&
244
+ sumByParam === null &&
245
+ computedSumByFromURL !== undefined &&
246
+ !sumBySelectionLoading
247
+ ) {
248
+ hasSyncedSumByRef.current = true;
249
+ void setSumByParam(sumByToParam(computedSumByFromURL));
208
250
  }
209
251
  }, [sumByParam, computedSumByFromURL, sumBySelectionLoading, setSumByParam]);
210
252
 
@@ -212,8 +254,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
212
254
  const querySelection: QuerySelection = useMemo(() => {
213
255
  const range = DateTimeRange.fromRangeKey(
214
256
  timeSelection ?? defaultTimeSelection,
215
- from !== undefined && from !== '' ? parseInt(from) : defaultFrom,
216
- to !== undefined && to !== '' ? parseInt(to) : defaultTo
257
+ from != null && from !== '' ? parseInt(from) : defaultFrom,
258
+ to != null && to !== '' ? parseInt(to) : defaultTo
217
259
  );
218
260
 
219
261
  return {
@@ -222,7 +264,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
222
264
  to: range.getToMs(),
223
265
  timeSelection: range.getRangeKey(),
224
266
  sumBy: computedSumByFromURL,
225
- ...(mergeFrom !== undefined && mergeFrom !== '' && mergeTo !== undefined && mergeTo !== ''
267
+ ...(mergeFrom != null && mergeFrom !== '' && mergeTo != null && mergeTo !== ''
226
268
  ? {mergeFrom, mergeTo}
227
269
  : {}),
228
270
  };
@@ -275,7 +317,11 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
275
317
 
276
318
  // Compute ProfileSelection from URL params
277
319
  const profileSelection = useMemo<ProfileSelection | null>(() => {
278
- return ProfileSelectionFromParams(mergeFrom, mergeTo, selectionParam);
320
+ return ProfileSelectionFromParams(
321
+ mergeFrom ?? undefined,
322
+ mergeTo ?? undefined,
323
+ selectionParam ?? undefined
324
+ );
279
325
  }, [mergeFrom, mergeTo, selectionParam]);
280
326
 
281
327
  // Compute ProfileSource from ProfileSelection
@@ -293,83 +339,77 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
293
339
  refreshedTimeRange?: {from: number; to: number; timeSelection: string},
294
340
  expression?: string
295
341
  ) => {
296
- batchUpdates(() => {
297
- // Use provided expression or current draft expression
298
- const finalExpression = expression ?? draftExpression;
342
+ // Use provided expression or current draft expression
343
+ const finalExpression = expression ?? draftExpression;
299
344
 
300
- // Update draft state with new expression if provided
301
- if (expression !== undefined) {
302
- setDraftExpression(expression);
303
- }
345
+ // Update draft state with new expression if provided
346
+ if (expression !== undefined) {
347
+ setDraftExpression(expression);
348
+ }
304
349
 
305
- // Calculate the actual from/to values from draftSelection if not provided
306
- const calculatedFrom = draftSelection.from.toString();
307
- const calculatedTo = draftSelection.to.toString();
350
+ // Calculate the actual from/to values from draftSelection if not provided
351
+ const calculatedFrom = draftSelection.from.toString();
352
+ const calculatedTo = draftSelection.to.toString();
308
353
 
309
- const finalFrom =
310
- refreshedTimeRange?.from?.toString() ?? (draftFrom !== '' ? draftFrom : calculatedFrom);
311
- const finalTo =
312
- refreshedTimeRange?.to?.toString() ?? (draftTo !== '' ? draftTo : calculatedTo);
313
- const finalTimeSelection = refreshedTimeRange?.timeSelection ?? draftTimeSelection;
354
+ const finalFrom =
355
+ refreshedTimeRange?.from?.toString() ?? (draftFrom !== '' ? draftFrom : calculatedFrom);
356
+ const finalTo =
357
+ refreshedTimeRange?.to?.toString() ?? (draftTo !== '' ? draftTo : calculatedTo);
358
+ const finalTimeSelection = refreshedTimeRange?.timeSelection ?? draftTimeSelection;
314
359
 
315
- // Update draft state with refreshed time range if provided
316
- if (refreshedTimeRange?.from !== undefined) {
317
- setDraftFrom(finalFrom);
318
- }
319
- if (refreshedTimeRange?.to !== undefined) {
320
- setDraftTo(finalTo);
321
- }
322
- if (refreshedTimeRange?.timeSelection !== undefined) {
323
- setDraftTimeSelection(finalTimeSelection);
324
- }
360
+ // Update draft state with refreshed time range if provided
361
+ if (refreshedTimeRange?.from !== undefined) {
362
+ setDraftFrom(finalFrom);
363
+ }
364
+ if (refreshedTimeRange?.to !== undefined) {
365
+ setDraftTo(finalTo);
366
+ }
367
+ if (refreshedTimeRange?.timeSelection !== undefined) {
368
+ setDraftTimeSelection(finalTimeSelection);
369
+ }
325
370
 
326
- setExpressionState(finalExpression);
327
- setFromState(finalFrom);
328
- setToState(finalTo);
329
- setTimeSelectionState(finalTimeSelection);
330
-
331
- // Auto-calculate merge parameters for delta profiles
332
- // Parse the final expression to check if it's a delta profile
333
- const finalQuery = Query.parse(finalExpression);
334
- const isDelta = finalQuery.profileType().delta;
335
- if (isDelta) {
336
- setSumByParam(sumByToParam(draftSumBy));
337
- } else {
338
- setSumByParam(DEFAULT_EMPTY_SUM_BY);
339
- }
371
+ // Auto-calculate merge parameters for delta profiles
372
+ const finalQuery = Query.parse(finalExpression);
373
+ const isDelta = finalQuery.profileType().delta;
340
374
 
341
- if (isDelta && finalFrom !== '' && finalTo !== '') {
342
- const fromMs = parseInt(finalFrom);
343
- const toMs = parseInt(finalTo);
344
- setMergeFromState((BigInt(fromMs) * 1_000_000n).toString());
345
- setMergeToState((BigInt(toMs) * 1_000_000n).toString());
346
-
347
- // Auto-select the time range for delta profiles (but not in compare mode)
348
- // This applies both on initial load AND when Search is clicked
349
- // The selection will use the final expression and the updated time range
350
- if (!comparing) {
351
- setSelectionParam(finalExpression);
352
- } else {
353
- setSelectionParam(undefined);
354
- }
355
- } else {
356
- setMergeFromState(undefined);
357
- setMergeToState(undefined);
358
- // Clear ProfileSelection for non-delta profiles
359
- setSelectionParam(undefined);
360
- }
361
- resetFlameGraphState();
362
- if (
363
- draftProfileType.toString() !==
364
- Query.parse(querySelection.expression).profileType().toString()
365
- ) {
366
- resetStateOnProfileTypeChange();
367
- onProfileTypeChange?.();
375
+ const sumByValue = isDelta ? sumByToParam(draftSumBy) : sumByToParam(DEFAULT_EMPTY_SUM_BY);
376
+ let mergeFromValue: string | null = null;
377
+ let mergeToValue: string | null = null;
378
+ let selectionValue: string | null = null;
379
+
380
+ if (isDelta && finalFrom !== '' && finalTo !== '') {
381
+ const fromMs = parseInt(finalFrom);
382
+ const toMs = parseInt(finalTo);
383
+ mergeFromValue = (BigInt(fromMs) * 1_000_000n).toString();
384
+ mergeToValue = (BigInt(toMs) * 1_000_000n).toString();
385
+
386
+ if (!comparing) {
387
+ selectionValue = finalExpression;
368
388
  }
389
+ }
390
+
391
+ // Atomic URL update with all params at once
392
+ void setQueryParams({
393
+ expression: finalExpression,
394
+ from: finalFrom,
395
+ to: finalTo,
396
+ time_selection: finalTimeSelection,
397
+ sum_by: sumByValue,
398
+ merge_from: mergeFromValue,
399
+ merge_to: mergeToValue,
400
+ selection: selectionValue,
369
401
  });
402
+
403
+ resetFlameGraphState();
404
+ if (
405
+ draftProfileType.toString() !==
406
+ Query.parse(querySelection.expression).profileType().toString()
407
+ ) {
408
+ resetStateOnProfileTypeChange();
409
+ onProfileTypeChange?.();
410
+ }
370
411
  },
371
412
  [
372
- batchUpdates,
373
413
  draftExpression,
374
414
  draftFrom,
375
415
  draftTo,
@@ -378,14 +418,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
378
418
  draftSelection.from,
379
419
  draftSelection.to,
380
420
  comparing,
381
- setExpressionState,
382
- setFromState,
383
- setToState,
384
- setTimeSelectionState,
385
- setSumByParam,
386
- setMergeFromState,
387
- setMergeToState,
388
- setSelectionParam,
421
+ setQueryParams,
389
422
  resetFlameGraphState,
390
423
  resetStateOnProfileTypeChange,
391
424
  onProfileTypeChange,
@@ -434,13 +467,13 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
434
467
  // Set ProfileSelection (auto-commits to URL immediately)
435
468
  const setProfileSelection = useCallback(
436
469
  (mergeFrom: bigint, mergeTo: bigint, query: Query) => {
437
- batchUpdates(() => {
438
- setSelectionParam(query.toString());
439
- setMergeFromState(mergeFrom.toString());
440
- setMergeToState(mergeTo.toString());
470
+ void setQueryParams({
471
+ selection: query.toString(),
472
+ merge_from: mergeFrom.toString(),
473
+ merge_to: mergeTo.toString(),
441
474
  });
442
475
  },
443
- [batchUpdates, setSelectionParam, setMergeFromState, setMergeToState]
476
+ [setQueryParams]
444
477
  );
445
478
 
446
479
  const draftParsedQuery = useMemo(() => {
package/src/index.tsx CHANGED
@@ -14,8 +14,6 @@
14
14
  import {CompressionType, setCompressionCodec} from '@uwdata/flechette';
15
15
  import * as lz4 from 'lz4js';
16
16
 
17
- import type {ParamPreferences} from '@parca/components';
18
-
19
17
  import MatchersInput from './MatchersInput';
20
18
  import MetricsGraph, {type ContextMenuItemOrSubmenu, type Series} from './MetricsGraph';
21
19
  import ProfileExplorer from './ProfileExplorer';
@@ -60,21 +58,24 @@ export {QueryControls} from './QueryControls';
60
58
  export {default as ProfileFilters} from './ProfileView/components/ProfileFilters';
61
59
  export {useProfileFiltersUrlState} from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
62
60
 
63
- export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES: ParamPreferences = {
64
- dashboard_items: {
65
- defaultValue: 'flamegraph',
66
- splitOnCommas: true, // This param should split on commas for array values
67
- },
68
- group_by: {
69
- splitOnCommas: true,
70
- },
71
- flamechart_dimension: {
72
- splitOnCommas: true,
73
- },
74
- };
75
-
76
61
  export {useProfileTypes} from './ProfileSelector';
77
62
 
63
+ export {
64
+ stringParam,
65
+ boolParam,
66
+ intParam,
67
+ commaArrayParam,
68
+ invertCallStackParser,
69
+ groupByParser,
70
+ flamechartDimensionParser,
71
+ tableColumnsParser,
72
+ hiddenBinariesParser,
73
+ jsonParser,
74
+ } from './hooks/urlParsers';
75
+
76
+ export {useDashboardItems} from './hooks/useDashboardItems';
77
+ export {useColorBy} from './hooks/useColorBy';
78
+
78
79
  export {
79
80
  ProfileExplorer,
80
81
  ProfileTypeSelector,
package/src/useQuery.tsx CHANGED
@@ -57,7 +57,7 @@ export const useQuery = (
57
57
  const {data, isLoading, error, refetch} = useGrpcQuery<QueryResponse | undefined>({
58
58
  key: [
59
59
  'query',
60
- profileSource.toKey(),
60
+ profileSource?.toKey() ?? '',
61
61
  reportType,
62
62
  options?.nodeTrimThreshold,
63
63
  options?.groupBy,
package/src/useSumBy.ts CHANGED
@@ -177,16 +177,16 @@ export const useSumByFromParams = (param: string | string[] | undefined): string
177
177
  return sumBy;
178
178
  };
179
179
 
180
- export const sumByToParam = (sumBy: string[] | undefined): string | string[] | undefined => {
180
+ export const sumByToParam = (sumBy: string[] | undefined): string | null => {
181
181
  if (sumBy === undefined) {
182
- return undefined;
182
+ return null;
183
183
  }
184
184
 
185
185
  if (sumBy.length === 0) {
186
186
  return '__none__';
187
187
  }
188
188
 
189
- return sumBy;
189
+ return sumBy.join(',');
190
190
  };
191
191
 
192
192
  // Combined hook that handles all sumBy logic: fetching labels, computing defaults, and managing selection