@parca/profile 0.19.139 → 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 (170) hide show
  1. package/CHANGELOG.md +8 -0
  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.js +103 -73
  9. package/dist/MatchersInput/SuggestionItem.js +91 -12
  10. package/dist/MatchersInput/SuggestionsList.d.ts +2 -1
  11. package/dist/MatchersInput/SuggestionsList.d.ts.map +1 -1
  12. package/dist/MatchersInput/SuggestionsList.js +371 -157
  13. package/dist/MatchersInput/SuggestionsList.test.d.ts +2 -0
  14. package/dist/MatchersInput/SuggestionsList.test.d.ts.map +1 -0
  15. package/dist/MatchersInput/index.js +308 -115
  16. package/dist/MetricsCircle/index.js +39 -3
  17. package/dist/MetricsGraph/MetricsContextMenu/index.js +119 -19
  18. package/dist/MetricsGraph/MetricsInfoPanel/index.js +81 -20
  19. package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
  20. package/dist/MetricsGraph/MetricsTooltip/index.js +107 -74
  21. package/dist/MetricsGraph/index.js +552 -203
  22. package/dist/MetricsGraph/useMetricsGraphDimensions.js +46 -25
  23. package/dist/MetricsGraph/utils/colorMapping.js +24 -17
  24. package/dist/MetricsSeries/index.js +70 -7
  25. package/dist/PreSelectedMatchers/index.d.ts.map +1 -1
  26. package/dist/PreSelectedMatchers/index.js +249 -102
  27. package/dist/ProfileExplorer/ProfileExplorerCompare.js +240 -49
  28. package/dist/ProfileExplorer/ProfileExplorerSingle.js +98 -11
  29. package/dist/ProfileExplorer/index.js +183 -32
  30. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.js +333 -148
  31. package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js +69 -35
  32. package/dist/ProfileFlameChart/SamplesStrips/index.js +645 -134
  33. package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.js +114 -55
  34. package/dist/ProfileFlameChart/index.js +266 -134
  35. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +287 -88
  36. package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.js +56 -20
  37. package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +211 -140
  38. package/dist/ProfileFlameGraph/FlameGraphArrow/MemoizedTooltip.js +133 -38
  39. package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.js +261 -216
  40. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
  41. package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +71 -45
  42. package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.d.ts.map +1 -1
  43. package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.js +58 -28
  44. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.d.ts.map +1 -1
  45. package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.js +59 -8
  46. package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +396 -179
  47. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts.map +1 -1
  48. package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.js +68 -50
  49. package/dist/ProfileFlameGraph/FlameGraphArrow/useMappingList.js +62 -38
  50. package/dist/ProfileFlameGraph/FlameGraphArrow/useNodeColor.js +14 -6
  51. package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +124 -82
  52. package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +160 -98
  53. package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.js +232 -112
  54. package/dist/ProfileFlameGraph/FlameGraphArrow/utils.js +137 -114
  55. package/dist/ProfileFlameGraph/benchmarks/benchdata/populateData.js +85 -0
  56. package/dist/ProfileFlameGraph/index.js +324 -148
  57. package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +140 -32
  58. package/dist/ProfileMetricsGraph/index.js +518 -259
  59. package/dist/ProfileSelector/CompareButton.js +132 -12
  60. package/dist/ProfileSelector/MetricsGraphSection.js +234 -67
  61. package/dist/ProfileSelector/index.d.ts.map +1 -1
  62. package/dist/ProfileSelector/index.js +730 -142
  63. package/dist/ProfileSelector/useAutoQuerySelector.js +249 -130
  64. package/dist/ProfileSource.js +230 -163
  65. package/dist/ProfileTypeSelector/index.js +214 -125
  66. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +50 -4
  67. package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +139 -33
  68. package/dist/ProfileView/components/ColorStackLegend.js +184 -55
  69. package/dist/ProfileView/components/DashboardItems/index.js +87 -28
  70. package/dist/ProfileView/components/DashboardLayout/index.js +108 -16
  71. package/dist/ProfileView/components/DiffLegend.js +172 -29
  72. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +199 -55
  73. package/dist/ProfileView/components/InvertCallStack/index.js +99 -10
  74. package/dist/ProfileView/components/ProfileFilters/filterPresets.js +260 -315
  75. package/dist/ProfileView/components/ProfileFilters/index.js +518 -215
  76. package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +370 -306
  77. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +188 -120
  78. package/dist/ProfileView/components/ProfileHeader/index.js +105 -11
  79. package/dist/ProfileView/components/ShareButton/ResultBox.js +119 -16
  80. package/dist/ProfileView/components/ShareButton/index.js +352 -62
  81. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  82. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +675 -195
  83. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +94 -7
  84. package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +198 -157
  85. package/dist/ProfileView/components/Toolbars/index.js +441 -21
  86. package/dist/ProfileView/components/ViewSelector/Dropdown.js +233 -22
  87. package/dist/ProfileView/components/ViewSelector/index.js +211 -91
  88. package/dist/ProfileView/components/VisualizationContainer/index.d.ts.map +1 -1
  89. package/dist/ProfileView/components/VisualizationContainer/index.js +52 -7
  90. package/dist/ProfileView/components/VisualizationPanel.js +185 -8
  91. package/dist/ProfileView/context/DashboardContext.js +84 -28
  92. package/dist/ProfileView/context/ProfileViewContext.js +56 -15
  93. package/dist/ProfileView/hooks/useAutoSelectDimension.js +71 -41
  94. package/dist/ProfileView/hooks/useProfileMetadata.js +50 -18
  95. package/dist/ProfileView/hooks/useResetFlameGraphState.js +31 -10
  96. package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +72 -29
  97. package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +39 -13
  98. package/dist/ProfileView/hooks/useVisualizationState.js +262 -87
  99. package/dist/ProfileView/index.js +383 -45
  100. package/dist/ProfileView/types/visualization.js +1 -13
  101. package/dist/ProfileView/utils/colorUtils.js +8 -7
  102. package/dist/ProfileViewWithData.js +332 -237
  103. package/dist/QueryControls/index.js +418 -47
  104. package/dist/Sandwich/components/CalleesSection.js +54 -4
  105. package/dist/Sandwich/components/CallersSection.js +97 -27
  106. package/dist/Sandwich/components/TableSection.js +77 -4
  107. package/dist/Sandwich/index.js +125 -12
  108. package/dist/Sandwich/utils/processRowData.js +48 -39
  109. package/dist/SelectWithRefresh/index.js +102 -28
  110. package/dist/SimpleMatchers/Select.js +520 -187
  111. package/dist/SimpleMatchers/index.js +590 -288
  112. package/dist/SourceView/Highlighter.js +230 -70
  113. package/dist/SourceView/LineNo.js +72 -17
  114. package/dist/SourceView/index.js +177 -101
  115. package/dist/SourceView/lang-detector/ext-to-lang.json +798 -798
  116. package/dist/SourceView/lang-detector/index.js +28 -14
  117. package/dist/SourceView/useSelectedLineRange.js +97 -16
  118. package/dist/Table/ColorCell.js +42 -1
  119. package/dist/Table/ColumnsVisibility.js +114 -6
  120. package/dist/Table/MoreDropdown.js +121 -27
  121. package/dist/Table/TableContextMenu.js +150 -139
  122. package/dist/Table/TableContextMenuWrapper.js +59 -14
  123. package/dist/Table/hooks/useColorManagement.js +58 -16
  124. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  125. package/dist/Table/hooks/useTableConfiguration.js +331 -168
  126. package/dist/Table/index.js +222 -126
  127. package/dist/Table/utils/functions.js +169 -144
  128. package/dist/Table/utils/topAndBottomExpandedRowModel.js +69 -52
  129. package/dist/TimelineGuide/index.js +209 -16
  130. package/dist/TopTable/benchmarks/benchdata/populateData.js +91 -0
  131. package/dist/TopTable/index.js +340 -122
  132. package/dist/contexts/LabelsQueryProvider.js +94 -32
  133. package/dist/contexts/UnifiedLabelsContext.js +114 -49
  134. package/dist/contexts/utils.js +37 -15
  135. package/dist/hooks/useCompareModeMeta.js +157 -94
  136. package/dist/hooks/useLabels.js +295 -52
  137. package/dist/hooks/useQueryState.js +371 -330
  138. package/dist/index.js +21 -16
  139. package/dist/testdata/fg-diff.json +3750 -0
  140. package/dist/testdata/fg-simple.json +1879 -0
  141. package/dist/testdata/link_data.json +56 -0
  142. package/dist/testdata/tabular.json +30 -0
  143. package/dist/testdata/test_flamegraph.json +26846 -0
  144. package/dist/testdata/test_graph.json +53 -0
  145. package/dist/useDelayedLoader.js +32 -18
  146. package/dist/useGrpcQuery/index.js +71 -11
  147. package/dist/useHasProfileData.js +90 -12
  148. package/dist/useQuery.js +205 -64
  149. package/dist/useSumBy.d.ts.map +1 -1
  150. package/dist/useSumBy.js +294 -138
  151. package/dist/utils.js +62 -30
  152. package/package.json +9 -9
  153. package/src/GraphTooltipArrow/index.tsx +3 -0
  154. package/src/MatchersInput/SuggestionsList.test.tsx +70 -0
  155. package/src/MatchersInput/SuggestionsList.tsx +11 -10
  156. package/src/MatchersInput/index.tsx +1 -1
  157. package/src/MetricsGraph/MetricsTooltip/index.tsx +22 -34
  158. package/src/PreSelectedMatchers/index.tsx +3 -0
  159. package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +3 -0
  160. package/src/ProfileFlameGraph/FlameGraphArrow/TooltipContext.tsx +3 -0
  161. package/src/ProfileFlameGraph/FlameGraphArrow/ZoomControls.tsx +3 -0
  162. package/src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts +3 -0
  163. package/src/ProfileSelector/index.tsx +30 -7
  164. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +3 -0
  165. package/src/ProfileView/components/VisualizationContainer/index.tsx +3 -0
  166. package/src/Table/hooks/useTableConfiguration.tsx +7 -13
  167. package/src/useDelayedLoader.ts +10 -10
  168. package/src/useSumBy.ts +12 -18
  169. package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +0 -541
  170. package/dist/hooks/useQueryState.test.js +0 -984
@@ -0,0 +1,70 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useRef} from 'react';
15
+
16
+ import {act, fireEvent, render} from '@testing-library/react';
17
+ import {beforeAll, describe, expect, it, vi} from 'vitest';
18
+
19
+ import SuggestionsList, {Suggestion, Suggestions} from './SuggestionsList';
20
+
21
+ vi.mock('@parca/components', () => ({
22
+ RefreshButton: ({title}: {title: string}) => <button type="button">{title}</button>,
23
+ useParcaContext: () => ({
24
+ loader: <div>loading</div>,
25
+ }),
26
+ }));
27
+
28
+ beforeAll(() => {
29
+ Element.prototype.scrollIntoView = vi.fn();
30
+ });
31
+
32
+ const TestHarness = ({inputKey = 'initial'}: {inputKey?: string}): JSX.Element => {
33
+ const inputRef = useRef<HTMLTextAreaElement | null>(null);
34
+ const suggestions = new Suggestions();
35
+ suggestions.labelNames.push(new Suggestion('labelName', 'na', 'namespace'));
36
+
37
+ return (
38
+ <div>
39
+ <textarea key={inputKey} ref={inputRef} />
40
+ <SuggestionsList
41
+ suggestions={suggestions}
42
+ applySuggestion={vi.fn()}
43
+ inputRef={inputRef}
44
+ runQuery={vi.fn()}
45
+ focusedInput
46
+ isLabelNamesLoading={false}
47
+ isLabelValuesLoading={false}
48
+ shouldTrimPrefix={false}
49
+ refetchLabelValues={vi.fn(async () => {})}
50
+ refetchLabelNames={vi.fn(async () => {})}
51
+ />
52
+ </div>
53
+ );
54
+ };
55
+
56
+ describe('SuggestionsList', () => {
57
+ it('rebinds keyboard listeners when the textarea ref points to a remounted node', () => {
58
+ const {rerender, getByRole, getByText} = render(<TestHarness inputKey="first" />);
59
+
60
+ rerender(<TestHarness inputKey="second" />);
61
+
62
+ const textarea = getByRole('textbox');
63
+ act(() => {
64
+ fireEvent.keyDown(textarea, {key: 'ArrowDown'});
65
+ });
66
+
67
+ // eslint-disable-next-line jest-dom/prefer-to-have-class
68
+ expect(getByText('namespace').className).toContain('bg-indigo-600');
69
+ });
70
+ });
@@ -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 {Fragment, useCallback, useEffect, useState} from 'react';
14
+ import React, {Fragment, useCallback, useEffect, useState} from 'react';
15
15
 
16
16
  import {Transition} from '@headlessui/react';
17
17
  import {usePopper} from 'react-popper';
@@ -48,7 +48,7 @@ export class Suggestions {
48
48
  interface Props {
49
49
  suggestions: Suggestions;
50
50
  applySuggestion: (suggestion: Suggestion) => void;
51
- inputRef: HTMLTextAreaElement | null;
51
+ inputRef: React.RefObject<HTMLTextAreaElement | null>;
52
52
  runQuery: () => void;
53
53
  focusedInput: boolean;
54
54
  isLabelNamesLoading: boolean;
@@ -82,7 +82,7 @@ const SuggestionsList = ({
82
82
  refetchLabelNames,
83
83
  }: Props): JSX.Element => {
84
84
  const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
85
- const {styles, attributes} = usePopper(inputRef, popperElement, {
85
+ const {styles, attributes} = usePopper(inputRef.current, popperElement, {
86
86
  placement: 'bottom-start',
87
87
  });
88
88
  const [highlightedSuggestionIndex, setHighlightedSuggestionIndex] = useState<number>(-1);
@@ -227,18 +227,19 @@ const SuggestionsList = ({
227
227
  );
228
228
 
229
229
  useEffect(() => {
230
- if (inputRef == null) {
230
+ const el = inputRef.current;
231
+ if (el == null) {
231
232
  return;
232
233
  }
233
234
 
234
- inputRef.addEventListener('keydown', handleKeyDown);
235
- inputRef.addEventListener('keypress', handleKeyPress as any);
235
+ el.addEventListener('keydown', handleKeyDown);
236
+ el.addEventListener('keypress', handleKeyPress as any);
236
237
 
237
238
  return () => {
238
- inputRef.removeEventListener('keydown', handleKeyDown);
239
- inputRef.removeEventListener('keypress', handleKeyPress as any);
239
+ el.removeEventListener('keydown', handleKeyDown);
240
+ el.removeEventListener('keypress', handleKeyPress as any);
240
241
  };
241
- }, [inputRef, highlightedSuggestionIndex, suggestions, handleKeyPress, handleKeyDown]);
242
+ });
242
243
 
243
244
  useEffect(() => {
244
245
  if (suggestionsLength > 0 && focusedInput) {
@@ -263,7 +264,7 @@ const SuggestionsList = ({
263
264
  leaveTo="opacity-0"
264
265
  >
265
266
  <div
266
- style={{width: inputRef?.offsetWidth}}
267
+ style={{width: inputRef.current?.offsetWidth}}
267
268
  className="absolute z-10 mt-1 max-h-[400px] rounded-md bg-gray-50 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-900 sm:text-sm flex flex-col"
268
269
  >
269
270
  <div className="flex-1 overflow-auto min-h-0">
@@ -211,7 +211,7 @@ const MatchersInput = ({setDraftMatchers, draftParsedQuery, commitDraft}: Props)
211
211
  isLabelNamesLoading={isLabelNamesLoading}
212
212
  suggestions={suggestionSections}
213
213
  applySuggestion={applySuggestion}
214
- inputRef={inputRef.current}
214
+ inputRef={inputRef}
215
215
  runQuery={commitDraft}
216
216
  focusedInput={focusedInput}
217
217
  isLabelValuesLoading={
@@ -11,7 +11,9 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useEffect, useMemo, useState} from 'react';
14
+ /* eslint-disable react-hooks/refs */
15
+
16
+ import {useLayoutEffect, useRef, useState} from 'react';
15
17
 
16
18
  import {usePopper} from 'react-popper';
17
19
 
@@ -28,21 +30,16 @@ interface Props {
28
30
  content: React.ReactNode;
29
31
  }
30
32
 
31
- const virtualElement: VirtualElement = {
32
- getBoundingClientRect: () => {
33
- const emptyRect: DOMRect = {
34
- width: 0,
35
- height: 0,
36
- top: 0,
37
- right: 0,
38
- bottom: 0,
39
- left: 0,
40
- x: 0,
41
- y: 0,
42
- toJSON: () => ({}),
43
- };
44
- return emptyRect;
45
- },
33
+ const emptyRect: DOMRect = {
34
+ width: 0,
35
+ height: 0,
36
+ top: 0,
37
+ right: 0,
38
+ bottom: 0,
39
+ left: 0,
40
+ x: 0,
41
+ y: 0,
42
+ toJSON: () => ({}),
46
43
  };
47
44
 
48
45
  const createDomRect = (x: number, y: number): DOMRect => {
@@ -61,9 +58,13 @@ const createDomRect = (x: number, y: number): DOMRect => {
61
58
  };
62
59
 
63
60
  const MetricsTooltip = ({x, y, contextElement, content}: Props): JSX.Element => {
61
+ 'use no memo';
64
62
  const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
63
+ const virtualElementRef = useRef<VirtualElement>({
64
+ getBoundingClientRect: () => emptyRect,
65
+ });
65
66
 
66
- const {styles, attributes, update} = usePopper(virtualElement, popperElement, {
67
+ const {styles, attributes, update} = usePopper(virtualElementRef.current, popperElement, {
67
68
  placement: 'auto-start',
68
69
  strategy: 'absolute',
69
70
  modifiers: [
@@ -82,26 +83,13 @@ const MetricsTooltip = ({x, y, contextElement, content}: Props): JSX.Element =>
82
83
  ],
83
84
  });
84
85
 
85
- useMemo(() => {
86
- virtualElement.getBoundingClientRect = (): DOMRect => {
87
- const domRect: DOMRect = (contextElement as Element)?.getBoundingClientRect() ?? {
88
- width: 0,
89
- height: 0,
90
- top: 0,
91
- right: 0,
92
- bottom: 0,
93
- left: 0,
94
- x: 0,
95
- y: 0,
96
- toJSON: () => ({}),
97
- };
86
+ useLayoutEffect(() => {
87
+ virtualElementRef.current.getBoundingClientRect = (): DOMRect => {
88
+ const domRect: DOMRect = (contextElement as Element)?.getBoundingClientRect() ?? emptyRect;
98
89
  return createDomRect(domRect.x + x, domRect.y + y);
99
90
  };
100
- }, [x, y, contextElement]);
101
-
102
- useEffect(() => {
103
91
  void update?.();
104
- }, [x, y, update]);
92
+ }, [x, y, contextElement, update]);
105
93
 
106
94
  // Don't render anything if content is null or undefined
107
95
  if (content == null) {
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/refs */
15
+
14
16
  import React, {useCallback, useEffect, useRef, useState} from 'react';
15
17
 
16
18
  import {Icon} from '@iconify/react';
@@ -29,6 +31,7 @@ interface Props {
29
31
  }
30
32
 
31
33
  const PreSelectedMatchers: React.FC<Props> = ({labelNames}) => {
34
+ 'use no memo';
32
35
  const [labelValuesMap, setLabelValuesMap] = useState<Record<string, string[]>>({});
33
36
  const [isLoading, setIsLoading] = useState<Record<string, boolean>>({});
34
37
  const metadata = useGrpcMetadata();
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/set-state-in-effect */
15
+
14
16
  import {useEffect, useRef, useState} from 'react';
15
17
 
16
18
  import {useURLState} from '@parca/components';
@@ -64,6 +66,7 @@ function calculateTruncatedText(
64
66
  }
65
67
 
66
68
  function TextWithEllipsis({text, x, y, width}: Props): JSX.Element {
69
+ 'use no memo';
67
70
  const textRef = useRef<SVGTextElement>(null);
68
71
  const [displayText, setDisplayText] = useState(text);
69
72
  const [alignFunctionName] = useURLState('align_function_name');
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/refs */
15
+
14
16
  import React, {createContext, useCallback, useContext, useMemo, useRef} from 'react';
15
17
 
16
18
  import {Table} from '@uwdata/flechette';
@@ -68,6 +70,7 @@ export const TooltipProvider: React.FC<TooltipProviderProps> = ({
68
70
  onTooltipUpdate,
69
71
  tooltipId = 'default',
70
72
  }) => {
73
+ 'use no memo';
71
74
  const tooltipStateRef = useRef<TooltipState>({row: null, x: 0, y: 0});
72
75
 
73
76
  const updateTooltip = useCallback(
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/refs */
15
+
14
16
  import React from 'react';
15
17
 
16
18
  import {Icon} from '@iconify/react';
@@ -33,6 +35,7 @@ export const ZoomControls = ({
33
35
  resetZoom,
34
36
  portalRef,
35
37
  }: ZoomControlsProps): React.JSX.Element => {
38
+ 'use no memo';
36
39
  const controls = (
37
40
  <div className="flex items-center gap-1 rounded-md border border-gray-200 bg-white/90 px-1 py-0.5 shadow-sm backdrop-blur-sm dark:border-gray-600 dark:bg-gray-800/90">
38
41
  <button
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/set-state-in-effect */
15
+
14
16
  import {useEffect, useRef, useState} from 'react';
15
17
 
16
18
  interface UseBatchedRenderingOptions {
@@ -29,6 +31,7 @@ export const useBatchedRendering = <T>(
29
31
  items: T[],
30
32
  options: UseBatchedRenderingOptions = {}
31
33
  ): UseBatchedRenderingResult<T> => {
34
+ 'use no memo';
32
35
  const {batchSize = 500, batchDelay = 0} = options;
33
36
 
34
37
  const [renderedCount, setRenderedCount] = useState(0);
@@ -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 {Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState} from 'react';
14
+ import {Dispatch, SetStateAction, useCallback, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {RpcError} from '@protobuf-ts/runtime-rpc';
17
17
 
@@ -154,7 +154,20 @@ const ProfileSelector = ({
154
154
  );
155
155
 
156
156
  // Sync local timeRangeSelection when URL state changes externally (e.g., "Switch to 1 minute" button)
157
- useEffect(() => {
157
+ const [prevQueryTimeSelection, setPrevQueryTimeSelection] = useState(
158
+ querySelection.timeSelection
159
+ );
160
+ const [prevQueryFrom, setPrevQueryFrom] = useState(querySelection.from);
161
+ const [prevQueryTo, setPrevQueryTo] = useState(querySelection.to);
162
+
163
+ if (
164
+ prevQueryTimeSelection !== querySelection.timeSelection ||
165
+ prevQueryFrom !== querySelection.from ||
166
+ prevQueryTo !== querySelection.to
167
+ ) {
168
+ setPrevQueryTimeSelection(querySelection.timeSelection);
169
+ setPrevQueryFrom(querySelection.from);
170
+ setPrevQueryTo(querySelection.to);
158
171
  setTimeRangeSelection(
159
172
  DateTimeRange.fromRangeKey(
160
173
  querySelection.timeSelection,
@@ -162,7 +175,7 @@ const ProfileSelector = ({
162
175
  querySelection.to
163
176
  )
164
177
  );
165
- }, [querySelection.timeSelection, querySelection.from, querySelection.to]);
178
+ }
166
179
 
167
180
  const [queryExpressionString, setQueryExpressionString] = useState(draftSelection.expression);
168
181
 
@@ -198,18 +211,28 @@ const ProfileSelector = ({
198
211
  return result.response?.labelNames === undefined ? [] : result.response.labelNames;
199
212
  }, [result]);
200
213
 
201
- useEffect(() => {
214
+ const [prevEnforcedProfileName, setPrevEnforcedProfileName] = useState(enforcedProfileName);
215
+ const [prevQueryExpression, setPrevQueryExpression] = useState(querySelection.expression);
216
+
217
+ if (
218
+ prevEnforcedProfileName !== enforcedProfileName ||
219
+ prevQueryExpression !== querySelection.expression
220
+ ) {
221
+ setPrevEnforcedProfileName(enforcedProfileName);
222
+ setPrevQueryExpression(querySelection.expression);
202
223
  if (enforcedProfileName !== '') {
203
224
  const [q, changed] = Query.parse(querySelection.expression).setProfileName(
204
225
  enforcedProfileName
205
226
  );
206
227
  if (changed) {
207
228
  setQueryExpressionString(q.toString());
208
- return;
229
+ } else {
230
+ setQueryExpressionString(querySelection.expression);
209
231
  }
232
+ } else {
233
+ setQueryExpressionString(querySelection.expression);
210
234
  }
211
- setQueryExpressionString(querySelection.expression);
212
- }, [enforcedProfileName, querySelection.expression]);
235
+ }
213
236
 
214
237
  const enforcedProfileNameQuery = (): Query => {
215
238
  const pq = Query.parse(queryExpressionString);
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/set-state-in-effect */
15
+
14
16
  import React, {useCallback, useEffect, useRef, useState} from 'react';
15
17
 
16
18
  import {Menu} from '@headlessui/react';
@@ -73,6 +75,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
73
75
  customSubmenu,
74
76
  renderAsDiv = false,
75
77
  }) => {
78
+ 'use no memo';
76
79
  const menuRef = useRef<HTMLDivElement>(null);
77
80
  const [shouldOpenLeft, setShouldOpenLeft] = useState(false);
78
81
 
@@ -11,6 +11,8 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ /* eslint-disable react-hooks/refs */
15
+
14
16
  import {FC} from 'react';
15
17
 
16
18
  import cx from 'classnames';
@@ -42,6 +44,7 @@ export const VisualizationContainer: FC<VisualizationContainerProps> = ({
42
44
  index,
43
45
  actionButtons,
44
46
  }) => {
47
+ 'use no memo';
45
48
  const {handleClosePanel} = useDashboard();
46
49
 
47
50
  return (
@@ -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 {useEffect, useMemo, useState} from 'react';
14
+ import {useMemo} from 'react';
15
15
 
16
16
  import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
17
17
 
@@ -47,8 +47,8 @@ export function useTableConfiguration({
47
47
  alwaysReturnArray: true,
48
48
  });
49
49
 
50
- const [columnVisibility, setColumnVisibility] = useState(() => {
51
- return {
50
+ const columnVisibility = useMemo(() => {
51
+ const defaults: Record<string, boolean> = {
52
52
  color: true,
53
53
  flat: true,
54
54
  flatPercentage: false,
@@ -63,19 +63,13 @@ export function useTableConfiguration({
63
63
  functionFileName: false,
64
64
  mappingFile: false,
65
65
  };
66
- });
67
-
68
- useEffect(() => {
69
66
  if (Array.isArray(tableColumns)) {
70
- setColumnVisibility(prevState => {
71
- const newState = {...prevState};
72
- (Object.keys(newState) as ColumnName[]).forEach(column => {
73
- newState[column] = tableColumns.includes(column);
74
- });
75
- return newState;
67
+ (Object.keys(defaults) as ColumnName[]).forEach(column => {
68
+ defaults[column] = tableColumns.includes(column);
76
69
  });
77
70
  }
78
- }, [tableColumns]);
71
+ return defaults;
72
+ }, [tableColumns, compareMode]);
79
73
 
80
74
  const columns = useMemo<Array<ColumnDef<Row>>>(() => {
81
75
  const baseColumns: Array<ColumnDef<Row>> = [
@@ -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