@parca/profile 0.19.140 → 0.19.142

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 (253) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/dist/GraphTooltipArrow/Content.js +224 -30
  3. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +192 -33
  4. package/dist/GraphTooltipArrow/ExpandOnHoverValue.js +53 -3
  5. package/dist/GraphTooltipArrow/index.d.ts.map +1 -1
  6. package/dist/GraphTooltipArrow/index.js +86 -56
  7. package/dist/GraphTooltipArrow/useGraphTooltip/index.js +37 -37
  8. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.d.ts.map +1 -1
  9. package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +104 -72
  10. package/dist/MatchersInput/SuggestionItem.js +91 -12
  11. package/dist/MatchersInput/SuggestionsList.d.ts +2 -1
  12. package/dist/MatchersInput/SuggestionsList.d.ts.map +1 -1
  13. package/dist/MatchersInput/SuggestionsList.js +371 -157
  14. package/dist/MatchersInput/SuggestionsList.test.d.ts +2 -0
  15. package/dist/MatchersInput/SuggestionsList.test.d.ts.map +1 -0
  16. package/dist/MatchersInput/index.js +308 -115
  17. package/dist/MetricsCircle/index.js +39 -3
  18. package/dist/MetricsGraph/MetricsContextMenu/index.js +119 -19
  19. package/dist/MetricsGraph/MetricsInfoPanel/index.js +81 -20
  20. package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
  21. package/dist/MetricsGraph/MetricsTooltip/index.js +107 -74
  22. package/dist/MetricsGraph/index.js +552 -203
  23. package/dist/MetricsGraph/useMetricsGraphDimensions.js +46 -25
  24. package/dist/MetricsGraph/utils/colorMapping.js +24 -17
  25. package/dist/MetricsSeries/index.js +70 -7
  26. package/dist/PreSelectedMatchers/index.d.ts.map +1 -1
  27. package/dist/PreSelectedMatchers/index.js +249 -102
  28. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
  29. package/dist/ProfileExplorer/ProfileExplorerCompare.js +241 -45
  30. package/dist/ProfileExplorer/ProfileExplorerSingle.js +98 -11
  31. package/dist/ProfileExplorer/index.js +183 -32
  32. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.js +333 -148
  33. package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js +69 -35
  34. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts +2 -2
  35. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -1
  36. package/dist/ProfileFlameChart/SamplesStrips/index.js +645 -134
  37. package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.js +114 -55
  38. package/dist/ProfileFlameChart/index.d.ts.map +1 -1
  39. package/dist/ProfileFlameChart/index.js +267 -129
  40. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
  41. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +288 -89
  42. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.js +56 -20
  43. package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +211 -140
  44. package/dist/ProfileFlameGraph/FlameGraphArrow/MemoizedTooltip.js +133 -38
  45. package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.js +261 -216
  46. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
  47. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +72 -47
  48. package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.d.ts.map +1 -1
  49. package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.js +58 -28
  50. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.d.ts.map +1 -1
  51. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.js +59 -8
  52. package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +396 -179
  53. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts.map +1 -1
  54. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.js +68 -50
  55. package/dist/ProfileFlameGraph/FlameGraphArrow/useMappingList.js +62 -38
  56. package/dist/ProfileFlameGraph/FlameGraphArrow/useNodeColor.js +14 -6
  57. package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +124 -82
  58. package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +160 -98
  59. package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.js +232 -112
  60. package/dist/ProfileFlameGraph/FlameGraphArrow/utils.js +137 -114
  61. package/dist/ProfileFlameGraph/benchmarks/benchdata/populateData.js +85 -0
  62. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  63. package/dist/ProfileFlameGraph/index.js +324 -150
  64. package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +140 -32
  65. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  66. package/dist/ProfileMetricsGraph/index.js +519 -258
  67. package/dist/ProfileSelector/CompareButton.js +132 -12
  68. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  69. package/dist/ProfileSelector/MetricsGraphSection.js +236 -64
  70. package/dist/ProfileSelector/index.d.ts.map +1 -1
  71. package/dist/ProfileSelector/index.js +727 -141
  72. package/dist/ProfileSelector/useAutoQuerySelector.js +249 -130
  73. package/dist/ProfileSource.js +230 -163
  74. package/dist/ProfileTypeSelector/index.js +214 -125
  75. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +50 -4
  76. package/dist/ProfileView/components/ActionButtons/SortByDropdown.d.ts.map +1 -1
  77. package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +141 -35
  78. package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
  79. package/dist/ProfileView/components/ColorStackLegend.js +185 -55
  80. package/dist/ProfileView/components/DashboardItems/index.js +87 -28
  81. package/dist/ProfileView/components/DashboardLayout/index.js +108 -16
  82. package/dist/ProfileView/components/DiffLegend.js +172 -29
  83. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +199 -55
  84. package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -1
  85. package/dist/ProfileView/components/InvertCallStack/index.js +100 -12
  86. package/dist/ProfileView/components/ProfileFilters/filterPresets.js +260 -315
  87. package/dist/ProfileView/components/ProfileFilters/index.js +518 -215
  88. package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +370 -306
  89. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +2 -1
  90. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
  91. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +188 -118
  92. package/dist/ProfileView/components/ProfileHeader/index.js +105 -11
  93. package/dist/ProfileView/components/ShareButton/ResultBox.js +119 -16
  94. package/dist/ProfileView/components/ShareButton/index.js +352 -62
  95. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  96. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +678 -194
  97. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +94 -7
  98. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
  99. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +199 -157
  100. package/dist/ProfileView/components/Toolbars/index.d.ts +2 -2
  101. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  102. package/dist/ProfileView/components/Toolbars/index.js +441 -21
  103. package/dist/ProfileView/components/ViewSelector/Dropdown.js +233 -22
  104. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  105. package/dist/ProfileView/components/ViewSelector/index.js +212 -86
  106. package/dist/ProfileView/components/VisualizationContainer/index.d.ts.map +1 -1
  107. package/dist/ProfileView/components/VisualizationContainer/index.js +52 -7
  108. package/dist/ProfileView/components/VisualizationPanel.js +185 -8
  109. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  110. package/dist/ProfileView/context/DashboardContext.js +85 -29
  111. package/dist/ProfileView/context/ProfileViewContext.js +56 -15
  112. package/dist/ProfileView/hooks/useAutoSelectDimension.js +71 -41
  113. package/dist/ProfileView/hooks/useProfileMetadata.js +50 -18
  114. package/dist/ProfileView/hooks/useResetFlameGraphState.d.ts.map +1 -1
  115. package/dist/ProfileView/hooks/useResetFlameGraphState.js +32 -12
  116. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
  117. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +71 -27
  118. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.d.ts.map +1 -1
  119. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +40 -19
  120. package/dist/ProfileView/hooks/useVisualizationState.d.ts +3 -3
  121. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  122. package/dist/ProfileView/hooks/useVisualizationState.js +258 -67
  123. package/dist/ProfileView/index.js +383 -45
  124. package/dist/ProfileView/types/visualization.js +1 -13
  125. package/dist/ProfileView/utils/colorUtils.js +8 -7
  126. package/dist/ProfileViewWithData.d.ts.map +1 -1
  127. package/dist/ProfileViewWithData.js +332 -228
  128. package/dist/QueryControls/index.js +418 -47
  129. package/dist/Sandwich/components/CalleesSection.js +54 -4
  130. package/dist/Sandwich/components/CallersSection.js +97 -27
  131. package/dist/Sandwich/components/TableSection.js +77 -4
  132. package/dist/Sandwich/index.d.ts.map +1 -1
  133. package/dist/Sandwich/index.js +126 -14
  134. package/dist/Sandwich/utils/processRowData.js +48 -39
  135. package/dist/SelectWithRefresh/index.js +102 -28
  136. package/dist/SimpleMatchers/Select.js +520 -187
  137. package/dist/SimpleMatchers/index.js +590 -288
  138. package/dist/SourceView/Highlighter.js +230 -70
  139. package/dist/SourceView/LineNo.js +72 -17
  140. package/dist/SourceView/index.d.ts.map +1 -1
  141. package/dist/SourceView/index.js +178 -104
  142. package/dist/SourceView/lang-detector/ext-to-lang.json +798 -798
  143. package/dist/SourceView/lang-detector/index.js +28 -14
  144. package/dist/SourceView/useSelectedLineRange.d.ts.map +1 -1
  145. package/dist/SourceView/useSelectedLineRange.js +99 -23
  146. package/dist/Table/ColorCell.js +42 -1
  147. package/dist/Table/ColumnsVisibility.js +114 -6
  148. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  149. package/dist/Table/MoreDropdown.js +122 -25
  150. package/dist/Table/TableContextMenu.d.ts.map +1 -1
  151. package/dist/Table/TableContextMenu.js +151 -137
  152. package/dist/Table/TableContextMenuWrapper.js +59 -14
  153. package/dist/Table/hooks/useColorManagement.js +58 -16
  154. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  155. package/dist/Table/hooks/useTableConfiguration.js +333 -169
  156. package/dist/Table/index.d.ts.map +1 -1
  157. package/dist/Table/index.js +222 -128
  158. package/dist/Table/utils/functions.js +169 -144
  159. package/dist/Table/utils/topAndBottomExpandedRowModel.js +69 -52
  160. package/dist/TimelineGuide/index.js +209 -16
  161. package/dist/TopTable/benchmarks/benchdata/populateData.js +91 -0
  162. package/dist/TopTable/index.d.ts.map +1 -1
  163. package/dist/TopTable/index.js +342 -123
  164. package/dist/contexts/LabelsQueryProvider.js +94 -32
  165. package/dist/contexts/UnifiedLabelsContext.js +114 -49
  166. package/dist/contexts/utils.js +37 -15
  167. package/dist/hooks/useCompareModeMeta.d.ts.map +1 -1
  168. package/dist/hooks/useCompareModeMeta.js +158 -64
  169. package/dist/hooks/useLabels.js +295 -52
  170. package/dist/hooks/useQueryState.d.ts +3 -3
  171. package/dist/hooks/useQueryState.d.ts.map +1 -1
  172. package/dist/hooks/useQueryState.js +373 -332
  173. package/dist/index.d.ts +2 -3
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +22 -8
  176. package/dist/testdata/fg-diff.json +3750 -0
  177. package/dist/testdata/fg-simple.json +1879 -0
  178. package/dist/testdata/link_data.json +56 -0
  179. package/dist/testdata/tabular.json +30 -0
  180. package/dist/testdata/test_flamegraph.json +26846 -0
  181. package/dist/testdata/test_graph.json +53 -0
  182. package/dist/useDelayedLoader.js +32 -18
  183. package/dist/useGrpcQuery/index.js +71 -11
  184. package/dist/useHasProfileData.js +90 -12
  185. package/dist/useQuery.js +205 -64
  186. package/dist/useSumBy.d.ts +1 -1
  187. package/dist/useSumBy.d.ts.map +1 -1
  188. package/dist/useSumBy.js +294 -138
  189. package/dist/utils.js +62 -30
  190. package/package.json +9 -10
  191. package/src/GraphTooltipArrow/index.tsx +3 -0
  192. package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +13 -11
  193. package/src/MatchersInput/SuggestionsList.test.tsx +70 -0
  194. package/src/MatchersInput/SuggestionsList.tsx +11 -10
  195. package/src/MatchersInput/index.tsx +1 -1
  196. package/src/MetricsGraph/MetricsTooltip/index.tsx +22 -34
  197. package/src/PreSelectedMatchers/index.tsx +3 -0
  198. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +9 -4
  199. package/src/ProfileFlameChart/SamplesStrips/index.tsx +2 -2
  200. package/src/ProfileFlameChart/index.tsx +28 -21
  201. package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +9 -10
  202. package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +6 -5
  203. package/src/ProfileFlameGraph/FlameGraphArrow/TooltipContext.tsx +3 -0
  204. package/src/ProfileFlameGraph/FlameGraphArrow/ZoomControls.tsx +3 -0
  205. package/src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts +3 -0
  206. package/src/ProfileFlameGraph/index.tsx +9 -6
  207. package/src/ProfileMetricsGraph/index.tsx +8 -6
  208. package/src/ProfileSelector/MetricsGraphSection.tsx +10 -5
  209. package/src/ProfileSelector/index.tsx +61 -39
  210. package/src/ProfileView/components/ActionButtons/SortByDropdown.tsx +6 -10
  211. package/src/ProfileView/components/ColorStackLegend.tsx +4 -2
  212. package/src/ProfileView/components/InvertCallStack/index.tsx +4 -5
  213. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +192 -94
  214. package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +21 -21
  215. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +28 -24
  216. package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +5 -4
  217. package/src/ProfileView/components/Toolbars/index.tsx +3 -3
  218. package/src/ProfileView/components/ViewSelector/index.tsx +16 -9
  219. package/src/ProfileView/components/VisualizationContainer/index.tsx +3 -0
  220. package/src/ProfileView/context/DashboardContext.tsx +6 -6
  221. package/src/ProfileView/hooks/useResetFlameGraphState.ts +4 -6
  222. package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +26 -24
  223. package/src/ProfileView/hooks/useResetStateOnSeriesChange.ts +8 -16
  224. package/src/ProfileView/hooks/useVisualizationState.ts +69 -61
  225. package/src/ProfileViewWithData.tsx +35 -29
  226. package/src/Sandwich/index.tsx +3 -4
  227. package/src/SourceView/index.tsx +2 -4
  228. package/src/SourceView/useSelectedLineRange.ts +19 -34
  229. package/src/Table/MoreDropdown.tsx +11 -9
  230. package/src/Table/TableContextMenu.tsx +13 -10
  231. package/src/Table/hooks/useTableConfiguration.tsx +11 -16
  232. package/src/Table/index.tsx +21 -12
  233. package/src/TopTable/index.tsx +4 -3
  234. package/src/hooks/useCompareModeMeta.ts +91 -61
  235. package/src/hooks/useQueryState.test.tsx +345 -275
  236. package/src/hooks/useQueryState.ts +118 -136
  237. package/src/index.tsx +15 -16
  238. package/src/useDelayedLoader.ts +10 -10
  239. package/src/useSumBy.ts +15 -21
  240. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +0 -455
  241. package/dist/hooks/urlParsers.d.ts +0 -18
  242. package/dist/hooks/urlParsers.d.ts.map +0 -1
  243. package/dist/hooks/urlParsers.js +0 -32
  244. package/dist/hooks/useColorBy.d.ts +0 -5
  245. package/dist/hooks/useColorBy.d.ts.map +0 -1
  246. package/dist/hooks/useColorBy.js +0 -26
  247. package/dist/hooks/useDashboardItems.d.ts +0 -5
  248. package/dist/hooks/useDashboardItems.d.ts.map +0 -1
  249. package/dist/hooks/useDashboardItems.js +0 -27
  250. package/dist/hooks/useQueryState.test.js +0 -868
  251. package/src/hooks/urlParsers.ts +0 -38
  252. package/src/hooks/useColorBy.ts +0 -42
  253. package/src/hooks/useDashboardItems.ts +0 -46
@@ -13,9 +13,7 @@
13
13
 
14
14
  import {useCallback, useEffect, useMemo, useState} from 'react';
15
15
 
16
- import {useQueryState as useNuqsQueryState, useQueryStates} from 'nuqs';
17
-
18
- import {DateTimeRange, useParcaContext} from '@parca/components';
16
+ import {DateTimeRange, useParcaContext, useURLState, useURLStateBatch} from '@parca/components';
19
17
  import {Query} from '@parca/parser';
20
18
 
21
19
  import {QuerySelection} from '../ProfileSelector';
@@ -23,7 +21,6 @@ import {ProfileSelection, ProfileSelectionFromParams, ProfileSource} from '../Pr
23
21
  import {useResetFlameGraphState} from '../ProfileView/hooks/useResetFlameGraphState';
24
22
  import {useResetStateOnProfileTypeChange} from '../ProfileView/hooks/useResetStateOnProfileTypeChange';
25
23
  import {DEFAULT_EMPTY_SUM_BY, sumByToParam, useSumBy, useSumByFromParams} from '../useSumBy';
26
- import {commaArrayParam, stringParam} from './urlParsers';
27
24
 
28
25
  interface UseQueryStateOptions {
29
26
  suffix?: '_a' | '_b'; // For comparison mode
@@ -70,9 +67,9 @@ interface UseQueryStateReturn {
70
67
  // parsed query
71
68
  parsedQuery: Query | null;
72
69
 
73
- setExpressionParam: (value: string | null) => void;
74
- setSumByParam: (value: string | null) => void;
75
- setGroupByParam: (value: string[] | null) => void;
70
+ setExpressionParam: (value: string | undefined) => void;
71
+ setSumByParam: (value: string | undefined) => void;
72
+ setGroupByParam: (value: string[] | undefined) => void;
76
73
  }
77
74
 
78
75
  export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryStateReturn => {
@@ -87,65 +84,41 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
87
84
  onProfileTypeChange,
88
85
  } = options;
89
86
 
87
+ const batchUpdates = useURLStateBatch();
90
88
  const resetFlameGraphState = useResetFlameGraphState();
91
89
  const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
92
90
 
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
- );
91
+ // URL state hooks with appropriate suffixes
92
+ const [expression, setExpressionState] = useURLState<string>(`expression${suffix}`, {
93
+ defaultValue: defaultExpression,
94
+ });
119
95
 
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
- );
96
+ const [from, setFromState] = useURLState<string>(`from${suffix}`, {
97
+ defaultValue: defaultFrom?.toString(),
98
+ });
138
99
 
139
- const [, setRawGroupByParam] = useNuqsQueryState('group_by', commaArrayParam);
140
- const setGroupByParam = useCallback(
141
- (val: string[] | null) => {
142
- void setRawGroupByParam(val);
143
- },
144
- [setRawGroupByParam]
145
- );
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
+ });
113
+
114
+ const [mergeFrom, setMergeFromState] = useURLState<string>(`merge_from${suffix}`);
115
+ const [mergeTo, setMergeToState] = useURLState<string>(`merge_to${suffix}`);
116
+
117
+ // ProfileSelection URL state hooks - reuses merge_from/merge_to but adds selection
118
+ const [selectionParam, setSelectionParam] = useURLState<string>(`selection${suffix}`);
146
119
 
147
120
  // Parse sumBy from URL parameter format
148
- const sumBy = useSumByFromParams(sumByParam ?? undefined);
121
+ const sumBy = useSumByFromParams(sumByParam);
149
122
 
150
123
  // Draft state management
151
124
  const [draftExpression, setDraftExpression] = useState<string>(expression ?? defaultExpression);
@@ -230,8 +203,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
230
203
  // Sync computed sumBy to URL if URL doesn't already have a value
231
204
  // to ensure the shared URL can always pick it up.
232
205
  useEffect(() => {
233
- if (sumByParam === null && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
234
- void setSumByParam(sumByToParam(computedSumByFromURL));
206
+ if (sumByParam === undefined && computedSumByFromURL !== undefined && !sumBySelectionLoading) {
207
+ setSumByParam(sumByToParam(computedSumByFromURL));
235
208
  }
236
209
  }, [sumByParam, computedSumByFromURL, sumBySelectionLoading, setSumByParam]);
237
210
 
@@ -239,8 +212,8 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
239
212
  const querySelection: QuerySelection = useMemo(() => {
240
213
  const range = DateTimeRange.fromRangeKey(
241
214
  timeSelection ?? defaultTimeSelection,
242
- from != null && from !== '' ? parseInt(from) : defaultFrom,
243
- to != null && to !== '' ? parseInt(to) : defaultTo
215
+ from !== undefined && from !== '' ? parseInt(from) : defaultFrom,
216
+ to !== undefined && to !== '' ? parseInt(to) : defaultTo
244
217
  );
245
218
 
246
219
  return {
@@ -249,7 +222,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
249
222
  to: range.getToMs(),
250
223
  timeSelection: range.getRangeKey(),
251
224
  sumBy: computedSumByFromURL,
252
- ...(mergeFrom != null && mergeFrom !== '' && mergeTo != null && mergeTo !== ''
225
+ ...(mergeFrom !== undefined && mergeFrom !== '' && mergeTo !== undefined && mergeTo !== ''
253
226
  ? {mergeFrom, mergeTo}
254
227
  : {}),
255
228
  };
@@ -302,11 +275,7 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
302
275
 
303
276
  // Compute ProfileSelection from URL params
304
277
  const profileSelection = useMemo<ProfileSelection | null>(() => {
305
- return ProfileSelectionFromParams(
306
- mergeFrom ?? undefined,
307
- mergeTo ?? undefined,
308
- selectionParam ?? undefined
309
- );
278
+ return ProfileSelectionFromParams(mergeFrom, mergeTo, selectionParam);
310
279
  }, [mergeFrom, mergeTo, selectionParam]);
311
280
 
312
281
  // Compute ProfileSource from ProfileSelection
@@ -324,77 +293,83 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
324
293
  refreshedTimeRange?: {from: number; to: number; timeSelection: string},
325
294
  expression?: string
326
295
  ) => {
327
- // Use provided expression or current draft expression
328
- const finalExpression = expression ?? draftExpression;
329
-
330
- // Update draft state with new expression if provided
331
- if (expression !== undefined) {
332
- setDraftExpression(expression);
333
- }
334
-
335
- // Calculate the actual from/to values from draftSelection if not provided
336
- const calculatedFrom = draftSelection.from.toString();
337
- const calculatedTo = draftSelection.to.toString();
296
+ batchUpdates(() => {
297
+ // Use provided expression or current draft expression
298
+ const finalExpression = expression ?? draftExpression;
338
299
 
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;
344
-
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
- }
300
+ // Update draft state with new expression if provided
301
+ if (expression !== undefined) {
302
+ setDraftExpression(expression);
303
+ }
355
304
 
356
- // Auto-calculate merge parameters for delta profiles
357
- const finalQuery = Query.parse(finalExpression);
358
- const isDelta = finalQuery.profileType().delta;
305
+ // Calculate the actual from/to values from draftSelection if not provided
306
+ const calculatedFrom = draftSelection.from.toString();
307
+ const calculatedTo = draftSelection.to.toString();
359
308
 
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;
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;
364
314
 
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();
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
+ }
370
325
 
371
- if (!comparing) {
372
- selectionValue = finalExpression;
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);
373
339
  }
374
- }
375
340
 
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,
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?.();
368
+ }
386
369
  });
387
-
388
- resetFlameGraphState();
389
- if (
390
- draftProfileType.toString() !==
391
- Query.parse(querySelection.expression).profileType().toString()
392
- ) {
393
- resetStateOnProfileTypeChange();
394
- onProfileTypeChange?.();
395
- }
396
370
  },
397
371
  [
372
+ batchUpdates,
398
373
  draftExpression,
399
374
  draftFrom,
400
375
  draftTo,
@@ -403,7 +378,14 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
403
378
  draftSelection.from,
404
379
  draftSelection.to,
405
380
  comparing,
406
- setQueryParams,
381
+ setExpressionState,
382
+ setFromState,
383
+ setToState,
384
+ setTimeSelectionState,
385
+ setSumByParam,
386
+ setMergeFromState,
387
+ setMergeToState,
388
+ setSelectionParam,
407
389
  resetFlameGraphState,
408
390
  resetStateOnProfileTypeChange,
409
391
  onProfileTypeChange,
@@ -452,13 +434,13 @@ export const useQueryState = (options: UseQueryStateOptions = {}): UseQueryState
452
434
  // Set ProfileSelection (auto-commits to URL immediately)
453
435
  const setProfileSelection = useCallback(
454
436
  (mergeFrom: bigint, mergeTo: bigint, query: Query) => {
455
- void setQueryParams({
456
- selection: query.toString(),
457
- merge_from: mergeFrom.toString(),
458
- merge_to: mergeTo.toString(),
437
+ batchUpdates(() => {
438
+ setSelectionParam(query.toString());
439
+ setMergeFromState(mergeFrom.toString());
440
+ setMergeToState(mergeTo.toString());
459
441
  });
460
442
  },
461
- [setQueryParams]
443
+ [batchUpdates, setSelectionParam, setMergeFromState, setMergeToState]
462
444
  );
463
445
 
464
446
  const draftParsedQuery = useMemo(() => {
package/src/index.tsx CHANGED
@@ -14,6 +14,8 @@
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
+
17
19
  import MatchersInput from './MatchersInput';
18
20
  import MetricsGraph, {type ContextMenuItemOrSubmenu, type Series} from './MetricsGraph';
19
21
  import ProfileExplorer from './ProfileExplorer';
@@ -58,23 +60,20 @@ export {QueryControls} from './QueryControls';
58
60
  export {default as ProfileFilters} from './ProfileView/components/ProfileFilters';
59
61
  export {useProfileFiltersUrlState} from './ProfileView/components/ProfileFilters/useProfileFiltersUrlState';
60
62
 
61
- export {useProfileTypes} from './ProfileSelector';
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';
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
75
 
76
- export {useDashboardItems} from './hooks/useDashboardItems';
77
- export {useColorBy} from './hooks/useColorBy';
76
+ export {useProfileTypes} from './ProfileSelector';
78
77
 
79
78
  export {
80
79
  ProfileExplorer,
@@ -18,20 +18,20 @@ interface DelayedLoaderOptions {
18
18
  }
19
19
 
20
20
  const useDelayedLoader = (isLoading = false, options?: DelayedLoaderOptions): boolean => {
21
+ 'use no memo';
21
22
  const {delay = 500} = options ?? {};
22
23
  const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
23
24
  useEffect(() => {
24
- let showLoaderTimeout: ReturnType<typeof setTimeout>;
25
- if (isLoading && !isLoaderVisible) {
26
- // if the request takes longer than half a second, show the loading icon
27
- showLoaderTimeout = setTimeout(() => {
28
- setIsLoaderVisible(true);
29
- }, delay);
30
- } else if (!isLoading && isLoaderVisible) {
25
+ if (!isLoading) return;
26
+ // if the request takes longer than half a second, show the loading icon
27
+ const showLoaderTimeout = setTimeout(() => {
28
+ setIsLoaderVisible(true);
29
+ }, delay);
30
+ return () => {
31
+ clearTimeout(showLoaderTimeout);
31
32
  setIsLoaderVisible(false);
32
- }
33
- return () => clearTimeout(showLoaderTimeout);
34
- }, [isLoading, isLoaderVisible, delay]);
33
+ };
34
+ }, [isLoading, delay]);
35
35
 
36
36
  return isLoaderVisible;
37
37
  };
package/src/useSumBy.ts CHANGED
@@ -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 {useCallback, useEffect, useMemo, useRef, useState} from 'react';
14
+ import {useCallback, useMemo, useState} from 'react';
15
15
 
16
16
  import {QueryServiceClient} from '@parca/client';
17
17
  import {DateTimeRange} from '@parca/components';
@@ -70,14 +70,19 @@ export const useSumBySelection = (
70
70
  );
71
71
 
72
72
  // Update userSelectedSumBy when defaultValue changes (e.g., during navigation)
73
- useEffect(() => {
73
+ const [prevProfileType, setPrevProfileType] = useState(profileType);
74
+ const [prevDefaultValue, setPrevDefaultValue] = useState(defaultValue);
75
+
76
+ if (prevProfileType !== profileType || prevDefaultValue !== defaultValue) {
77
+ setPrevProfileType(profileType);
78
+ setPrevDefaultValue(defaultValue);
74
79
  if (profileType != null && defaultValue !== undefined) {
75
80
  setUserSelectedSumBy(prev => ({
76
81
  ...prev,
77
82
  [profileType.toString()]: defaultValue,
78
83
  }));
79
84
  }
80
- }, [profileType, defaultValue]);
85
+ }
81
86
 
82
87
  const setSumBy = useCallback(
83
88
  (sumBy: string[]) => {
@@ -97,19 +102,11 @@ export const useSumBySelection = (
97
102
 
98
103
  const {defaultSumBy} = useDefaultSumBy(profileType, labelNamesLoading, labels);
99
104
 
100
- // Store the last valid sumBy value to return during loading
101
- const lastValidSumByRef = useRef<string[]>(DEFAULT_EMPTY_SUM_BY);
102
-
103
105
  const sumBy = useMemo(() => {
104
- if (labelNamesLoading) {
105
- // For smoother UX, return draftSumBy first if available during loading
106
- // as this must be recently computed with the draft time range labels.
107
- if (draftSumBy !== undefined) {
108
- return draftSumBy;
109
- }
110
- if (lastValidSumByRef.current == null) {
111
- return lastValidSumByRef.current;
112
- }
106
+ // For smoother UX, return draftSumBy first if available during loading
107
+ // as this must be recently computed with the draft time range labels.
108
+ if (labelNamesLoading && draftSumBy !== undefined) {
109
+ return draftSumBy;
113
110
  }
114
111
 
115
112
  // Prefer non-empty URL default over auto-computed default to avoid a
@@ -125,9 +122,6 @@ export const useSumBySelection = (
125
122
  result = DEFAULT_EMPTY_SUM_BY;
126
123
  }
127
124
 
128
- // Store the computed value for next loading state
129
- lastValidSumByRef.current = result;
130
-
131
125
  return result;
132
126
  }, [userSelectedSumBy, profileType, defaultSumBy, labelNamesLoading, draftSumBy, defaultValue]);
133
127
 
@@ -183,16 +177,16 @@ export const useSumByFromParams = (param: string | string[] | undefined): string
183
177
  return sumBy;
184
178
  };
185
179
 
186
- export const sumByToParam = (sumBy: string[] | undefined): string | null => {
180
+ export const sumByToParam = (sumBy: string[] | undefined): string | string[] | undefined => {
187
181
  if (sumBy === undefined) {
188
- return null;
182
+ return undefined;
189
183
  }
190
184
 
191
185
  if (sumBy.length === 0) {
192
186
  return '__none__';
193
187
  }
194
188
 
195
- return sumBy.join(',');
189
+ return sumBy;
196
190
  };
197
191
 
198
192
  // Combined hook that handles all sumBy logic: fetching labels, computing defaults, and managing selection