@parca/profile 0.19.139 → 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.
Files changed (132) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.d.ts.map +1 -1
  3. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +11 -13
  4. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
  5. package/dist/ProfileExplorer/ProfileExplorerCompare.js +4 -9
  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 +13 -19
  10. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
  11. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +8 -8
  12. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
  13. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +4 -3
  14. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  15. package/dist/ProfileFlameGraph/index.js +6 -4
  16. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  17. package/dist/ProfileMetricsGraph/index.js +4 -6
  18. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  19. package/dist/ProfileSelector/MetricsGraphSection.js +5 -10
  20. package/dist/ProfileSelector/index.d.ts.map +1 -1
  21. package/dist/ProfileSelector/index.js +27 -25
  22. package/dist/ProfileView/components/ActionButtons/SortByDropdown.d.ts.map +1 -1
  23. package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +5 -5
  24. package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
  25. package/dist/ProfileView/components/ColorStackLegend.js +2 -3
  26. package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -1
  27. package/dist/ProfileView/components/InvertCallStack/index.js +5 -4
  28. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +1 -2
  29. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
  30. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +14 -16
  31. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +84 -170
  32. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  33. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +16 -20
  34. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  35. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +4 -5
  36. package/dist/ProfileView/components/Toolbars/index.d.ts +2 -2
  37. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  38. package/dist/ProfileView/components/Toolbars/index.js +1 -1
  39. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  40. package/dist/ProfileView/components/ViewSelector/index.js +8 -14
  41. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  42. package/dist/ProfileView/context/DashboardContext.js +6 -6
  43. package/dist/ProfileView/hooks/useResetFlameGraphState.d.ts.map +1 -1
  44. package/dist/ProfileView/hooks/useResetFlameGraphState.js +5 -4
  45. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  46. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +25 -26
  47. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.d.ts.map +1 -1
  48. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +13 -8
  49. package/dist/ProfileView/hooks/useVisualizationState.d.ts +3 -3
  50. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  51. package/dist/ProfileView/hooks/useVisualizationState.js +35 -51
  52. package/dist/ProfileViewWithData.d.ts.map +1 -1
  53. package/dist/ProfileViewWithData.js +19 -28
  54. package/dist/Sandwich/index.d.ts.map +1 -1
  55. package/dist/Sandwich/index.js +4 -3
  56. package/dist/SourceView/index.d.ts.map +1 -1
  57. package/dist/SourceView/index.js +4 -2
  58. package/dist/SourceView/useSelectedLineRange.d.ts.map +1 -1
  59. package/dist/SourceView/useSelectedLineRange.js +21 -16
  60. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  61. package/dist/Table/MoreDropdown.js +8 -11
  62. package/dist/Table/TableContextMenu.d.ts.map +1 -1
  63. package/dist/Table/TableContextMenu.js +10 -13
  64. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  65. package/dist/Table/hooks/useTableConfiguration.js +3 -4
  66. package/dist/Table/index.d.ts.map +1 -1
  67. package/dist/Table/index.js +11 -9
  68. package/dist/TopTable/index.d.ts.map +1 -1
  69. package/dist/TopTable/index.js +3 -4
  70. package/dist/hooks/urlParsers.d.ts +18 -0
  71. package/dist/hooks/urlParsers.d.ts.map +1 -0
  72. package/dist/hooks/urlParsers.js +32 -0
  73. package/dist/hooks/useColorBy.d.ts +5 -0
  74. package/dist/hooks/useColorBy.d.ts.map +1 -0
  75. package/dist/hooks/useColorBy.js +26 -0
  76. package/dist/hooks/useCompareModeMeta.d.ts.map +1 -1
  77. package/dist/hooks/useCompareModeMeta.js +55 -86
  78. package/dist/hooks/useDashboardItems.d.ts +5 -0
  79. package/dist/hooks/useDashboardItems.d.ts.map +1 -0
  80. package/dist/hooks/useDashboardItems.js +27 -0
  81. package/dist/hooks/useQueryState.d.ts +3 -3
  82. package/dist/hooks/useQueryState.d.ts.map +1 -1
  83. package/dist/hooks/useQueryState.js +105 -105
  84. package/dist/hooks/useQueryState.test.js +186 -302
  85. package/dist/index.d.ts +3 -2
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +3 -12
  88. package/dist/useSumBy.d.ts +1 -1
  89. package/dist/useSumBy.d.ts.map +1 -1
  90. package/dist/useSumBy.js +2 -2
  91. package/package.json +8 -7
  92. package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +11 -13
  93. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +4 -9
  94. package/src/ProfileFlameChart/SamplesStrips/index.tsx +2 -2
  95. package/src/ProfileFlameChart/index.tsx +21 -28
  96. package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +10 -9
  97. package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +5 -3
  98. package/src/ProfileFlameGraph/index.tsx +6 -9
  99. package/src/ProfileMetricsGraph/index.tsx +6 -8
  100. package/src/ProfileSelector/MetricsGraphSection.tsx +5 -10
  101. package/src/ProfileSelector/index.tsx +32 -31
  102. package/src/ProfileView/components/ActionButtons/SortByDropdown.tsx +10 -6
  103. package/src/ProfileView/components/ColorStackLegend.tsx +2 -4
  104. package/src/ProfileView/components/InvertCallStack/index.tsx +5 -4
  105. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +94 -192
  106. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +21 -21
  107. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +24 -25
  108. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +4 -5
  109. package/src/ProfileView/components/Toolbars/index.tsx +3 -3
  110. package/src/ProfileView/components/ViewSelector/index.tsx +9 -16
  111. package/src/ProfileView/context/DashboardContext.tsx +6 -6
  112. package/src/ProfileView/hooks/useResetFlameGraphState.ts +6 -4
  113. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +24 -26
  114. package/src/ProfileView/hooks/useResetStateOnSeriesChange.ts +16 -8
  115. package/src/ProfileView/hooks/useVisualizationState.ts +61 -69
  116. package/src/ProfileViewWithData.tsx +29 -35
  117. package/src/Sandwich/index.tsx +4 -3
  118. package/src/SourceView/index.tsx +4 -2
  119. package/src/SourceView/useSelectedLineRange.ts +34 -19
  120. package/src/Table/MoreDropdown.tsx +9 -11
  121. package/src/Table/TableContextMenu.tsx +10 -13
  122. package/src/Table/hooks/useTableConfiguration.tsx +3 -4
  123. package/src/Table/index.tsx +12 -21
  124. package/src/TopTable/index.tsx +3 -4
  125. package/src/hooks/urlParsers.ts +38 -0
  126. package/src/hooks/useColorBy.ts +42 -0
  127. package/src/hooks/useCompareModeMeta.ts +61 -91
  128. package/src/hooks/useDashboardItems.ts +46 -0
  129. package/src/hooks/useQueryState.test.tsx +275 -345
  130. package/src/hooks/useQueryState.ts +136 -118
  131. package/src/index.tsx +16 -15
  132. package/src/useSumBy.ts +3 -3
@@ -13,7 +13,9 @@
13
13
 
14
14
  import {useCallback, useEffect, useMemo, 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
@@ -67,9 +70,9 @@ interface UseQueryStateReturn {
67
70
  // parsed query
68
71
  parsedQuery: Query | null;
69
72
 
70
- setExpressionParam: (value: string | undefined) => void;
71
- setSumByParam: (value: string | undefined) => void;
72
- setGroupByParam: (value: string[] | undefined) => void;
73
+ setExpressionParam: (value: string | null) => void;
74
+ setSumByParam: (value: string | null) => void;
75
+ setGroupByParam: (value: string[] | null) => void;
73
76
  }
74
77
 
75
78
  export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryStateReturn => {
@@ -84,41 +87,65 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
84
87
  onProfileTypeChange,
85
88
  } = options;
86
89
 
87
- const batchUpdates = useURLStateBatch();
88
90
  const resetFlameGraphState = useResetFlameGraphState();
89
91
  const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
90
92
 
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
- });
93
+ // URL state hooks with appropriate suffixes via useQueryStates
94
+ const [queryParams, setQueryParams] = useQueryStates(
95
+ {
96
+ expression: stringParam,
97
+ from: stringParam,
98
+ to: stringParam,
99
+ time_selection: stringParam,
100
+ sum_by: stringParam,
101
+ merge_from: stringParam,
102
+ merge_to: stringParam,
103
+ selection: stringParam,
104
+ },
105
+ {
106
+ history: 'replace',
107
+ urlKeys: {
108
+ expression: `expression${suffix}`,
109
+ from: `from${suffix}`,
110
+ to: `to${suffix}`,
111
+ time_selection: `time_selection${suffix}`,
112
+ sum_by: `sum_by${suffix}`,
113
+ merge_from: `merge_from${suffix}`,
114
+ merge_to: `merge_to${suffix}`,
115
+ selection: `selection${suffix}`,
116
+ },
117
+ }
118
+ );
113
119
 
114
- const [mergeFrom, setMergeFromState] = useURLState<string>(`merge_from${suffix}`);
115
- const [mergeTo, setMergeToState] = useURLState<string>(`merge_to${suffix}`);
120
+ const expression = queryParams.expression ?? defaultExpression;
121
+ const from = queryParams.from ?? defaultFrom?.toString();
122
+ const to = queryParams.to ?? defaultTo?.toString();
123
+ const timeSelection = queryParams.time_selection ?? defaultTimeSelection;
124
+ const sumByParam = queryParams.sum_by;
125
+ const mergeFrom = queryParams.merge_from;
126
+ const mergeTo = queryParams.merge_to;
127
+ const selectionParam = queryParams.selection;
128
+
129
+ // Individual setters for direct access
130
+ const setExpressionState = useCallback(
131
+ (val: string | null) => void setQueryParams({expression: val}),
132
+ [setQueryParams]
133
+ );
134
+ const setSumByParam = useCallback(
135
+ (val: string | null) => void setQueryParams({sum_by: val}),
136
+ [setQueryParams]
137
+ );
116
138
 
117
- // ProfileSelection URL state hooks - reuses merge_from/merge_to but adds selection
118
- const [selectionParam, setSelectionParam] = useURLState<string>(`selection${suffix}`);
139
+ const [, setRawGroupByParam] = useNuqsQueryState('group_by', commaArrayParam);
140
+ const setGroupByParam = useCallback(
141
+ (val: string[] | null) => {
142
+ void setRawGroupByParam(val);
143
+ },
144
+ [setRawGroupByParam]
145
+ );
119
146
 
120
147
  // Parse sumBy from URL parameter format
121
- const sumBy = useSumByFromParams(sumByParam);
148
+ const sumBy = useSumByFromParams(sumByParam ?? undefined);
122
149
 
123
150
  // Draft state management
124
151
  const [draftExpression, setDraftExpression] = useState<string>(expression ?? defaultExpression);
@@ -203,8 +230,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
203
230
  // Sync computed sumBy to URL if URL doesn't already have a value
204
231
  // to ensure the shared URL can always pick it up.
205
232
  useEffect(() => {
206
- if (sumByParam === undefined && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
207
- setSumByParam(sumByToParam(computedSumByFromURL));
233
+ if (sumByParam === null && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
234
+ void setSumByParam(sumByToParam(computedSumByFromURL));
208
235
  }
209
236
  }, [sumByParam, computedSumByFromURL, sumBySelectionLoading, setSumByParam]);
210
237
 
@@ -212,8 +239,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
212
239
  const querySelection: QuerySelection = useMemo(() => {
213
240
  const range = DateTimeRange.fromRangeKey(
214
241
  timeSelection ?? defaultTimeSelection,
215
- from !== undefined && from !== '' ? parseInt(from) : defaultFrom,
216
- to !== undefined && to !== '' ? parseInt(to) : defaultTo
242
+ from != null && from !== '' ? parseInt(from) : defaultFrom,
243
+ to != null && to !== '' ? parseInt(to) : defaultTo
217
244
  );
218
245
 
219
246
  return {
@@ -222,7 +249,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
222
249
  to: range.getToMs(),
223
250
  timeSelection: range.getRangeKey(),
224
251
  sumBy: computedSumByFromURL,
225
- ...(mergeFrom !== undefined && mergeFrom !== '' && mergeTo !== undefined && mergeTo !== ''
252
+ ...(mergeFrom != null && mergeFrom !== '' && mergeTo != null && mergeTo !== ''
226
253
  ? {mergeFrom, mergeTo}
227
254
  : {}),
228
255
  };
@@ -275,7 +302,11 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
275
302
 
276
303
  // Compute ProfileSelection from URL params
277
304
  const profileSelection = useMemo<ProfileSelection | null>(() => {
278
- return ProfileSelectionFromParams(mergeFrom, mergeTo, selectionParam);
305
+ return ProfileSelectionFromParams(
306
+ mergeFrom ?? undefined,
307
+ mergeTo ?? undefined,
308
+ selectionParam ?? undefined
309
+ );
279
310
  }, [mergeFrom, mergeTo, selectionParam]);
280
311
 
281
312
  // Compute ProfileSource from ProfileSelection
@@ -293,83 +324,77 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
293
324
  refreshedTimeRange?: {from: number; to: number; timeSelection: string},
294
325
  expression?: string
295
326
  ) => {
296
- batchUpdates(() => {
297
- // Use provided expression or current draft expression
298
- const finalExpression = expression ?? draftExpression;
327
+ // Use provided expression or current draft expression
328
+ const finalExpression = expression ?? draftExpression;
299
329
 
300
- // Update draft state with new expression if provided
301
- if (expression !== undefined) {
302
- setDraftExpression(expression);
303
- }
330
+ // Update draft state with new expression if provided
331
+ if (expression !== undefined) {
332
+ setDraftExpression(expression);
333
+ }
304
334
 
305
- // Calculate the actual from/to values from draftSelection if not provided
306
- const calculatedFrom = draftSelection.from.toString();
307
- const calculatedTo = draftSelection.to.toString();
335
+ // Calculate the actual from/to values from draftSelection if not provided
336
+ const calculatedFrom = draftSelection.from.toString();
337
+ const calculatedTo = draftSelection.to.toString();
308
338
 
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;
339
+ const finalFrom =
340
+ refreshedTimeRange?.from?.toString() ?? (draftFrom !== '' ? draftFrom : calculatedFrom);
341
+ const finalTo =
342
+ refreshedTimeRange?.to?.toString() ?? (draftTo !== '' ? draftTo : calculatedTo);
343
+ const finalTimeSelection = refreshedTimeRange?.timeSelection ?? draftTimeSelection;
314
344
 
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
- }
345
+ // Update draft state with refreshed time range if provided
346
+ if (refreshedTimeRange?.from !== undefined) {
347
+ setDraftFrom(finalFrom);
348
+ }
349
+ if (refreshedTimeRange?.to !== undefined) {
350
+ setDraftTo(finalTo);
351
+ }
352
+ if (refreshedTimeRange?.timeSelection !== undefined) {
353
+ setDraftTimeSelection(finalTimeSelection);
354
+ }
325
355
 
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
- }
356
+ // Auto-calculate merge parameters for delta profiles
357
+ const finalQuery = Query.parse(finalExpression);
358
+ const isDelta = finalQuery.profileType().delta;
340
359
 
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?.();
360
+ const sumByValue = isDelta ? sumByToParam(draftSumBy) : sumByToParam(DEFAULT_EMPTY_SUM_BY);
361
+ let mergeFromValue: string | null = null;
362
+ let mergeToValue: string | null = null;
363
+ let selectionValue: string | null = null;
364
+
365
+ if (isDelta && finalFrom !== '' && finalTo !== '') {
366
+ const fromMs = parseInt(finalFrom);
367
+ const toMs = parseInt(finalTo);
368
+ mergeFromValue = (BigInt(fromMs) * 1_000_000n).toString();
369
+ mergeToValue = (BigInt(toMs) * 1_000_000n).toString();
370
+
371
+ if (!comparing) {
372
+ selectionValue = finalExpression;
368
373
  }
374
+ }
375
+
376
+ // Atomic URL update with all params at once
377
+ void setQueryParams({
378
+ expression: finalExpression,
379
+ from: finalFrom,
380
+ to: finalTo,
381
+ time_selection: finalTimeSelection,
382
+ sum_by: sumByValue,
383
+ merge_from: mergeFromValue,
384
+ merge_to: mergeToValue,
385
+ selection: selectionValue,
369
386
  });
387
+
388
+ resetFlameGraphState();
389
+ if (
390
+ draftProfileType.toString() !==
391
+ Query.parse(querySelection.expression).profileType().toString()
392
+ ) {
393
+ resetStateOnProfileTypeChange();
394
+ onProfileTypeChange?.();
395
+ }
370
396
  },
371
397
  [
372
- batchUpdates,
373
398
  draftExpression,
374
399
  draftFrom,
375
400
  draftTo,
@@ -378,14 +403,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
378
403
  draftSelection.from,
379
404
  draftSelection.to,
380
405
  comparing,
381
- setExpressionState,
382
- setFromState,
383
- setToState,
384
- setTimeSelectionState,
385
- setSumByParam,
386
- setMergeFromState,
387
- setMergeToState,
388
- setSelectionParam,
406
+ setQueryParams,
389
407
  resetFlameGraphState,
390
408
  resetStateOnProfileTypeChange,
391
409
  onProfileTypeChange,
@@ -434,13 +452,13 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
434
452
  // Set ProfileSelection (auto-commits to URL immediately)
435
453
  const setProfileSelection = useCallback(
436
454
  (mergeFrom: bigint, mergeTo: bigint, query: Query) => {
437
- batchUpdates(() => {
438
- setSelectionParam(query.toString());
439
- setMergeFromState(mergeFrom.toString());
440
- setMergeToState(mergeTo.toString());
455
+ void setQueryParams({
456
+ selection: query.toString(),
457
+ merge_from: mergeFrom.toString(),
458
+ merge_to: mergeTo.toString(),
441
459
  });
442
460
  },
443
- [batchUpdates, setSelectionParam, setMergeFromState, setMergeToState]
461
+ [setQueryParams]
444
462
  );
445
463
 
446
464
  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/useSumBy.ts CHANGED
@@ -183,16 +183,16 @@ export const useSumByFromParams = (param: string | string[] | undefined): string
183
183
  return sumBy;
184
184
  };
185
185
 
186
- export const sumByToParam = (sumBy: string[] | undefined): string | string[] | undefined => {
186
+ export const sumByToParam = (sumBy: string[] | undefined): string | null => {
187
187
  if (sumBy === undefined) {
188
- return undefined;
188
+ return null;
189
189
  }
190
190
 
191
191
  if (sumBy.length === 0) {
192
192
  return '__none__';
193
193
  }
194
194
 
195
- return sumBy;
195
+ return sumBy.join(',');
196
196
  };
197
197
 
198
198
  // Combined hook that handles all sumBy logic: fetching labels, computing defaults, and managing selection