@parca/profile 0.19.140 → 0.19.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/dist/GraphTooltipArrow/Content.js +224 -30
- package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +192 -33
- package/dist/GraphTooltipArrow/ExpandOnHoverValue.js +53 -3
- package/dist/GraphTooltipArrow/index.d.ts.map +1 -1
- package/dist/GraphTooltipArrow/index.js +86 -56
- package/dist/GraphTooltipArrow/useGraphTooltip/index.js +37 -37
- package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +94 -68
- package/dist/MatchersInput/SuggestionItem.js +91 -12
- package/dist/MatchersInput/SuggestionsList.d.ts +2 -1
- package/dist/MatchersInput/SuggestionsList.d.ts.map +1 -1
- package/dist/MatchersInput/SuggestionsList.js +371 -157
- package/dist/MatchersInput/SuggestionsList.test.d.ts +2 -0
- package/dist/MatchersInput/SuggestionsList.test.d.ts.map +1 -0
- package/dist/MatchersInput/index.js +308 -115
- package/dist/MetricsCircle/index.js +39 -3
- package/dist/MetricsGraph/MetricsContextMenu/index.js +119 -19
- package/dist/MetricsGraph/MetricsInfoPanel/index.js +81 -20
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsTooltip/index.js +107 -74
- package/dist/MetricsGraph/index.js +552 -203
- package/dist/MetricsGraph/useMetricsGraphDimensions.js +46 -25
- package/dist/MetricsGraph/utils/colorMapping.js +24 -17
- package/dist/MetricsSeries/index.js +70 -7
- package/dist/PreSelectedMatchers/index.d.ts.map +1 -1
- package/dist/PreSelectedMatchers/index.js +249 -102
- package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
- package/dist/ProfileExplorer/ProfileExplorerCompare.js +240 -45
- package/dist/ProfileExplorer/ProfileExplorerSingle.js +98 -11
- package/dist/ProfileExplorer/index.js +183 -32
- package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.js +333 -148
- package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js +69 -35
- package/dist/ProfileFlameChart/SamplesStrips/index.js +645 -134
- package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.js +114 -55
- package/dist/ProfileFlameChart/index.js +260 -126
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +283 -85
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.js +56 -20
- package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +211 -140
- package/dist/ProfileFlameGraph/FlameGraphArrow/MemoizedTooltip.js +133 -38
- package/dist/ProfileFlameGraph/FlameGraphArrow/MiniMap.js +261 -216
- package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.js +71 -45
- package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/TooltipContext.js +58 -28
- package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ZoomControls.js +59 -8
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +396 -179
- package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.js +68 -50
- package/dist/ProfileFlameGraph/FlameGraphArrow/useMappingList.js +62 -38
- package/dist/ProfileFlameGraph/FlameGraphArrow/useNodeColor.js +14 -6
- package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +124 -82
- package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +160 -98
- package/dist/ProfileFlameGraph/FlameGraphArrow/useZoom.js +232 -112
- package/dist/ProfileFlameGraph/FlameGraphArrow/utils.js +137 -114
- package/dist/ProfileFlameGraph/benchmarks/benchdata/populateData.js +85 -0
- package/dist/ProfileFlameGraph/index.js +322 -147
- package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +140 -32
- package/dist/ProfileMetricsGraph/index.js +515 -256
- package/dist/ProfileSelector/CompareButton.js +132 -12
- package/dist/ProfileSelector/MetricsGraphSection.js +228 -63
- package/dist/ProfileSelector/index.d.ts +1 -1
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +734 -142
- package/dist/ProfileSelector/useAutoQuerySelector.d.ts +1 -3
- package/dist/ProfileSelector/useAutoQuerySelector.d.ts.map +1 -1
- package/dist/ProfileSelector/useAutoQuerySelector.js +280 -132
- package/dist/ProfileSource.js +230 -163
- package/dist/ProfileTypeSelector/index.js +214 -125
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +50 -4
- package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +137 -32
- package/dist/ProfileView/components/ColorStackLegend.js +182 -54
- package/dist/ProfileView/components/DashboardItems/index.js +87 -28
- package/dist/ProfileView/components/DashboardLayout/index.js +108 -16
- package/dist/ProfileView/components/DiffLegend.js +172 -29
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +199 -55
- package/dist/ProfileView/components/InvertCallStack/index.js +97 -9
- package/dist/ProfileView/components/ProfileFilters/filterPresets.js +260 -315
- package/dist/ProfileView/components/ProfileFilters/index.js +518 -215
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +370 -306
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +191 -118
- package/dist/ProfileView/components/ProfileHeader/index.js +105 -11
- package/dist/ProfileView/components/ShareButton/ResultBox.js +119 -16
- package/dist/ProfileView/components/ShareButton/index.js +352 -62
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +664 -192
- package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +94 -7
- package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +196 -155
- package/dist/ProfileView/components/Toolbars/index.js +441 -21
- package/dist/ProfileView/components/ViewSelector/Dropdown.js +233 -22
- package/dist/ProfileView/components/ViewSelector/index.js +186 -82
- package/dist/ProfileView/components/VisualizationContainer/index.d.ts.map +1 -1
- package/dist/ProfileView/components/VisualizationContainer/index.js +52 -7
- package/dist/ProfileView/components/VisualizationPanel.js +185 -8
- package/dist/ProfileView/context/DashboardContext.js +74 -26
- package/dist/ProfileView/context/ProfileViewContext.js +56 -15
- package/dist/ProfileView/hooks/useAutoSelectDimension.js +71 -41
- package/dist/ProfileView/hooks/useProfileMetadata.js +50 -18
- package/dist/ProfileView/hooks/useResetFlameGraphState.js +31 -10
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +71 -27
- package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +53 -17
- package/dist/ProfileView/hooks/useVisualizationState.js +229 -69
- package/dist/ProfileView/index.js +383 -45
- package/dist/ProfileView/types/visualization.js +1 -13
- package/dist/ProfileView/utils/colorUtils.js +8 -7
- package/dist/ProfileViewWithData.js +319 -225
- package/dist/QueryControls/index.js +418 -47
- package/dist/Sandwich/components/CalleesSection.js +54 -4
- package/dist/Sandwich/components/CallersSection.js +97 -27
- package/dist/Sandwich/components/TableSection.js +77 -4
- package/dist/Sandwich/index.js +125 -12
- package/dist/Sandwich/utils/processRowData.js +48 -39
- package/dist/SelectWithRefresh/index.js +102 -28
- package/dist/SimpleMatchers/Select.js +520 -187
- package/dist/SimpleMatchers/index.js +590 -288
- package/dist/SourceView/Highlighter.js +230 -70
- package/dist/SourceView/LineNo.js +72 -17
- package/dist/SourceView/index.js +177 -101
- package/dist/SourceView/lang-detector/ext-to-lang.json +798 -798
- package/dist/SourceView/lang-detector/index.js +28 -14
- package/dist/SourceView/useSelectedLineRange.js +72 -20
- package/dist/Table/ColorCell.js +42 -1
- package/dist/Table/ColumnsVisibility.js +114 -6
- package/dist/Table/MoreDropdown.js +107 -21
- package/dist/Table/TableContextMenu.js +144 -134
- package/dist/Table/TableContextMenuWrapper.js +59 -14
- package/dist/Table/hooks/useColorManagement.js +58 -16
- package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
- package/dist/Table/hooks/useTableConfiguration.js +323 -167
- package/dist/Table/index.js +217 -123
- package/dist/Table/utils/functions.js +169 -144
- package/dist/Table/utils/topAndBottomExpandedRowModel.js +69 -52
- package/dist/TimelineGuide/index.js +209 -16
- package/dist/TopTable/benchmarks/benchdata/populateData.js +91 -0
- package/dist/TopTable/index.js +325 -121
- package/dist/contexts/LabelsQueryProvider.js +94 -32
- package/dist/contexts/UnifiedLabelsContext.js +114 -49
- package/dist/contexts/utils.js +37 -15
- package/dist/hooks/urlParsers.js +27 -15
- package/dist/hooks/useColorBy.js +47 -10
- package/dist/hooks/useCompareModeMeta.js +112 -62
- package/dist/hooks/useDashboardItems.js +52 -11
- package/dist/hooks/useLabels.js +295 -52
- package/dist/hooks/useQueryState.d.ts +1 -1
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +375 -329
- package/dist/index.js +11 -6
- package/dist/testdata/fg-diff.json +3750 -0
- package/dist/testdata/fg-simple.json +1879 -0
- package/dist/testdata/link_data.json +56 -0
- package/dist/testdata/tabular.json +30 -0
- package/dist/testdata/test_flamegraph.json +26846 -0
- package/dist/testdata/test_graph.json +53 -0
- package/dist/useDelayedLoader.js +32 -18
- package/dist/useGrpcQuery/index.js +71 -11
- package/dist/useHasProfileData.js +90 -12
- package/dist/useQuery.js +205 -64
- package/dist/useSumBy.d.ts.map +1 -1
- package/dist/useSumBy.js +294 -138
- package/dist/utils.js +62 -30
- package/package.json +9 -9
- package/src/GraphTooltipArrow/index.tsx +3 -0
- package/src/MatchersInput/SuggestionsList.test.tsx +70 -0
- package/src/MatchersInput/SuggestionsList.tsx +11 -10
- package/src/MatchersInput/index.tsx +1 -1
- package/src/MetricsGraph/MetricsTooltip/index.tsx +22 -34
- package/src/PreSelectedMatchers/index.tsx +3 -0
- package/src/ProfileExplorer/ProfileExplorerCompare.tsx +9 -2
- package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +3 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/TooltipContext.tsx +3 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/ZoomControls.tsx +3 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts +3 -0
- package/src/ProfileSelector/index.tsx +31 -9
- package/src/ProfileSelector/useAutoQuerySelector.ts +64 -42
- package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +3 -0
- package/src/ProfileView/components/VisualizationContainer/index.tsx +3 -0
- package/src/Table/hooks/useTableConfiguration.tsx +7 -13
- package/src/hooks/useQueryState.ts +18 -3
- package/src/useDelayedLoader.ts +10 -10
- package/src/useSumBy.ts +12 -18
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +0 -455
- package/dist/hooks/useQueryState.test.js +0 -868
|
@@ -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
|
-
|
|
230
|
+
const el = inputRef.current;
|
|
231
|
+
if (el == null) {
|
|
231
232
|
return;
|
|
232
233
|
}
|
|
233
234
|
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
el.addEventListener('keydown', handleKeyDown);
|
|
236
|
+
el.addEventListener('keypress', handleKeyPress as any);
|
|
236
237
|
|
|
237
238
|
return () => {
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
el.removeEventListener('keydown', handleKeyDown);
|
|
240
|
+
el.removeEventListener('keypress', handleKeyPress as any);
|
|
240
241
|
};
|
|
241
|
-
}
|
|
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
|
|
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
|
-
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
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
|
-
|
|
86
|
-
|
|
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();
|
|
@@ -65,8 +65,15 @@ const ProfileExplorerCompare = ({
|
|
|
65
65
|
if (querySelectionB.expression === '' && querySelectionA.expression !== '') {
|
|
66
66
|
setDraftExpressionB(querySelectionA.expression);
|
|
67
67
|
setDraftTimeRangeB(querySelectionA.from, querySelectionA.to, querySelectionA.timeSelection);
|
|
68
|
-
// Commit
|
|
69
|
-
commitDraftB(
|
|
68
|
+
// Commit with explicit values since draft useState hasn't applied yet
|
|
69
|
+
commitDraftB(
|
|
70
|
+
{
|
|
71
|
+
from: querySelectionA.from,
|
|
72
|
+
to: querySelectionA.to,
|
|
73
|
+
timeSelection: querySelectionA.timeSelection,
|
|
74
|
+
},
|
|
75
|
+
querySelectionA.expression
|
|
76
|
+
);
|
|
70
77
|
}
|
|
71
78
|
}, [
|
|
72
79
|
isCompareMode,
|
|
@@ -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 {useQueryState} from 'nuqs';
|
|
@@ -66,6 +68,7 @@ function calculateTruncatedText(
|
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
function TextWithEllipsis({text, x, y, width}: Props): JSX.Element {
|
|
71
|
+
'use no memo';
|
|
69
72
|
const textRef = useRef<SVGTextElement>(null);
|
|
70
73
|
const [displayText, setDisplayText] = useState(text);
|
|
71
74
|
const [alignFunctionName] = useQueryState('align_function_name', stringParam.withDefault('left'));
|
|
@@ -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,
|
|
14
|
+
import {Dispatch, SetStateAction, useCallback, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
17
|
import {useQueryState as useNuqsQueryState} from 'nuqs';
|
|
@@ -104,7 +104,7 @@ const ProfileSelector = ({
|
|
|
104
104
|
closeProfile,
|
|
105
105
|
enforcedProfileName,
|
|
106
106
|
comparing,
|
|
107
|
-
navigateTo,
|
|
107
|
+
navigateTo: _navigateTo,
|
|
108
108
|
showMetricsGraph = true,
|
|
109
109
|
showSumBySelector = true,
|
|
110
110
|
showProfileTypeSelector = true,
|
|
@@ -157,7 +157,20 @@ const ProfileSelector = ({
|
|
|
157
157
|
);
|
|
158
158
|
|
|
159
159
|
// Sync local timeRangeSelection when URL state changes externally (e.g., "Switch to 1 minute" button)
|
|
160
|
-
|
|
160
|
+
const [prevQueryTimeSelection, setPrevQueryTimeSelection] = useState(
|
|
161
|
+
querySelection.timeSelection
|
|
162
|
+
);
|
|
163
|
+
const [prevQueryFrom, setPrevQueryFrom] = useState(querySelection.from);
|
|
164
|
+
const [prevQueryTo, setPrevQueryTo] = useState(querySelection.to);
|
|
165
|
+
|
|
166
|
+
if (
|
|
167
|
+
prevQueryTimeSelection !== querySelection.timeSelection ||
|
|
168
|
+
prevQueryFrom !== querySelection.from ||
|
|
169
|
+
prevQueryTo !== querySelection.to
|
|
170
|
+
) {
|
|
171
|
+
setPrevQueryTimeSelection(querySelection.timeSelection);
|
|
172
|
+
setPrevQueryFrom(querySelection.from);
|
|
173
|
+
setPrevQueryTo(querySelection.to);
|
|
161
174
|
setTimeRangeSelection(
|
|
162
175
|
DateTimeRange.fromRangeKey(
|
|
163
176
|
querySelection.timeSelection,
|
|
@@ -165,7 +178,7 @@ const ProfileSelector = ({
|
|
|
165
178
|
querySelection.to
|
|
166
179
|
)
|
|
167
180
|
);
|
|
168
|
-
}
|
|
181
|
+
}
|
|
169
182
|
|
|
170
183
|
const [queryExpressionString, setQueryExpressionString] = useState(draftSelection.expression);
|
|
171
184
|
|
|
@@ -201,18 +214,28 @@ const ProfileSelector = ({
|
|
|
201
214
|
return result.response?.labelNames === undefined ? [] : result.response.labelNames;
|
|
202
215
|
}, [result]);
|
|
203
216
|
|
|
204
|
-
|
|
217
|
+
const [prevEnforcedProfileName, setPrevEnforcedProfileName] = useState(enforcedProfileName);
|
|
218
|
+
const [prevQueryExpression, setPrevQueryExpression] = useState(querySelection.expression);
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
prevEnforcedProfileName !== enforcedProfileName ||
|
|
222
|
+
prevQueryExpression !== querySelection.expression
|
|
223
|
+
) {
|
|
224
|
+
setPrevEnforcedProfileName(enforcedProfileName);
|
|
225
|
+
setPrevQueryExpression(querySelection.expression);
|
|
205
226
|
if (enforcedProfileName !== '') {
|
|
206
227
|
const [q, changed] = Query.parse(querySelection.expression).setProfileName(
|
|
207
228
|
enforcedProfileName
|
|
208
229
|
);
|
|
209
230
|
if (changed) {
|
|
210
231
|
setQueryExpressionString(q.toString());
|
|
211
|
-
|
|
232
|
+
} else {
|
|
233
|
+
setQueryExpressionString(querySelection.expression);
|
|
212
234
|
}
|
|
235
|
+
} else {
|
|
236
|
+
setQueryExpressionString(querySelection.expression);
|
|
213
237
|
}
|
|
214
|
-
|
|
215
|
-
}, [enforcedProfileName, querySelection.expression]);
|
|
238
|
+
}
|
|
216
239
|
|
|
217
240
|
const enforcedProfileNameQuery = (): Query => {
|
|
218
241
|
const pq = Query.parse(queryExpressionString);
|
|
@@ -271,7 +294,6 @@ const ProfileSelector = ({
|
|
|
271
294
|
setProfileName,
|
|
272
295
|
setQueryExpression,
|
|
273
296
|
querySelection,
|
|
274
|
-
navigateTo,
|
|
275
297
|
loading: sumByLoading,
|
|
276
298
|
defaultProfileType: externalProfilerComponent?.defaultProfileType,
|
|
277
299
|
});
|
|
@@ -13,13 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
import {useEffect, useRef} from 'react';
|
|
15
15
|
|
|
16
|
+
import {useQueryStates} from 'nuqs';
|
|
17
|
+
|
|
16
18
|
import {ProfileTypesResponse} from '@parca/client';
|
|
17
19
|
import {selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector} from '@parca/store';
|
|
18
|
-
import {type NavigateFunction} from '@parca/utilities';
|
|
19
20
|
|
|
20
|
-
import {ProfileSelectionFromParams
|
|
21
|
+
import {ProfileSelectionFromParams} from '..';
|
|
21
22
|
import {QuerySelection} from '../ProfileSelector';
|
|
22
23
|
import {constructProfileName} from '../ProfileTypeSelector';
|
|
24
|
+
import {boolParam, stringParam} from '../hooks/urlParsers';
|
|
25
|
+
import {useDashboardItems} from '../hooks/useDashboardItems';
|
|
23
26
|
|
|
24
27
|
interface Props {
|
|
25
28
|
selectedProfileName: string;
|
|
@@ -27,7 +30,6 @@ interface Props {
|
|
|
27
30
|
setProfileName: (name: string) => void;
|
|
28
31
|
setQueryExpression: () => void;
|
|
29
32
|
querySelection: QuerySelection;
|
|
30
|
-
navigateTo: NavigateFunction;
|
|
31
33
|
loading: boolean;
|
|
32
34
|
defaultProfileType?: string;
|
|
33
35
|
}
|
|
@@ -38,18 +40,40 @@ export const useAutoQuerySelector = ({
|
|
|
38
40
|
setProfileName,
|
|
39
41
|
setQueryExpression,
|
|
40
42
|
querySelection,
|
|
41
|
-
navigateTo,
|
|
42
43
|
loading,
|
|
43
44
|
defaultProfileType,
|
|
44
45
|
}: Props): void => {
|
|
45
46
|
const autoQuery = useAppSelector(selectAutoQuery);
|
|
46
47
|
const dispatch = useAppDispatch();
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
|
|
49
|
+
const {setDashboardItems} = useDashboardItems();
|
|
50
|
+
|
|
51
|
+
const [compareState, setCompareParams] = useQueryStates(
|
|
52
|
+
{
|
|
53
|
+
compare_a: boolParam,
|
|
54
|
+
compare_b: boolParam,
|
|
55
|
+
expression_a: stringParam,
|
|
56
|
+
from_a: stringParam,
|
|
57
|
+
to_a: stringParam,
|
|
58
|
+
time_selection_a: stringParam,
|
|
59
|
+
sum_by_a: stringParam,
|
|
60
|
+
merge_from_a: stringParam,
|
|
61
|
+
merge_to_a: stringParam,
|
|
62
|
+
selection_a: stringParam,
|
|
63
|
+
expression_b: stringParam,
|
|
64
|
+
from_b: stringParam,
|
|
65
|
+
to_b: stringParam,
|
|
66
|
+
time_selection_b: stringParam,
|
|
67
|
+
sum_by_b: stringParam,
|
|
68
|
+
search_string: stringParam,
|
|
69
|
+
},
|
|
70
|
+
{history: 'replace'}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Read compare params through nuqs (not location.search) to stay in sync
|
|
74
|
+
const comparing = compareState.compare_a === true || compareState.compare_b === true;
|
|
75
|
+
const expressionA = compareState.expression_a;
|
|
76
|
+
const expressionB = compareState.expression_b;
|
|
53
77
|
|
|
54
78
|
// Track if we've already set up compare mode to prevent infinite loops
|
|
55
79
|
const hasSetupCompareMode = useRef(false);
|
|
@@ -64,13 +88,7 @@ export const useAutoQuerySelector = ({
|
|
|
64
88
|
// 2. expressionA exists
|
|
65
89
|
// 3. expressionB doesn't exist yet (meaning we need to set it up)
|
|
66
90
|
// 4. We haven't already set it up in this session
|
|
67
|
-
if (
|
|
68
|
-
comparing &&
|
|
69
|
-
expressionA !== null &&
|
|
70
|
-
expressionA !== undefined &&
|
|
71
|
-
expressionB === null &&
|
|
72
|
-
!hasSetupCompareMode.current
|
|
73
|
-
) {
|
|
91
|
+
if (comparing && expressionA !== null && expressionB === null && !hasSetupCompareMode.current) {
|
|
74
92
|
if (querySelection.expression === undefined) {
|
|
75
93
|
return;
|
|
76
94
|
}
|
|
@@ -87,42 +105,46 @@ export const useAutoQuerySelector = ({
|
|
|
87
105
|
sumBy: querySelection.sumBy,
|
|
88
106
|
};
|
|
89
107
|
|
|
90
|
-
const sumBy = queryA.sumBy?.join(',');
|
|
108
|
+
const sumBy = queryA.sumBy?.join(',') ?? null;
|
|
109
|
+
|
|
110
|
+
const mergeFromA = profileA != null ? profileA.HistoryParams().merge_from?.toString() : null;
|
|
111
|
+
const mergeToA = profileA != null ? profileA.HistoryParams().merge_to?.toString() : null;
|
|
112
|
+
const selectionA = profileA != null ? profileA.HistoryParams().selection?.toString() : null;
|
|
113
|
+
|
|
114
|
+
hasSetupCompareMode.current = true;
|
|
91
115
|
|
|
92
|
-
|
|
93
|
-
|
|
116
|
+
// Set all compare params atomically via nuqs
|
|
117
|
+
void setCompareParams({
|
|
118
|
+
compare_a: true,
|
|
119
|
+
compare_b: true,
|
|
94
120
|
expression_a: queryA.expression,
|
|
95
121
|
from_a: queryA.from.toString(),
|
|
96
122
|
to_a: queryA.to.toString(),
|
|
97
123
|
time_selection_a: queryA.timeSelection,
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
sum_by_a: sumBy,
|
|
125
|
+
merge_from_a: mergeFromA,
|
|
126
|
+
merge_to_a: mergeToA,
|
|
127
|
+
selection_a: selectionA,
|
|
100
128
|
expression_b: queryA.expression,
|
|
101
129
|
from_b: queryA.from.toString(),
|
|
102
130
|
to_b: queryA.to.toString(),
|
|
103
131
|
time_selection_b: queryA.timeSelection,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (sumBy != null) {
|
|
107
|
-
compareQuery.sum_by_a = sumBy;
|
|
108
|
-
compareQuery.sum_by_b = sumBy;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (profileA != null) {
|
|
112
|
-
compareQuery = {
|
|
113
|
-
...SuffixParams(profileA.HistoryParams(), '_a'),
|
|
114
|
-
...compareQuery,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
hasSetupCompareMode.current = true;
|
|
119
|
-
void navigateTo('/', {
|
|
120
|
-
...compareQuery,
|
|
121
|
-
search_string: '',
|
|
122
|
-
dashboard_items: ['flamegraph'],
|
|
132
|
+
sum_by_b: sumBy,
|
|
133
|
+
search_string: null,
|
|
123
134
|
});
|
|
135
|
+
|
|
136
|
+
setDashboardItems(['flamegraph']);
|
|
124
137
|
}
|
|
125
|
-
}, [
|
|
138
|
+
}, [
|
|
139
|
+
comparing,
|
|
140
|
+
querySelection,
|
|
141
|
+
expressionA,
|
|
142
|
+
expressionB,
|
|
143
|
+
dispatch,
|
|
144
|
+
loading,
|
|
145
|
+
setCompareParams,
|
|
146
|
+
setDashboardItems,
|
|
147
|
+
]);
|
|
126
148
|
|
|
127
149
|
// Effect to load some initial data on load when is no selection
|
|
128
150
|
useEffect(() => {
|
|
@@ -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';
|
|
@@ -74,6 +76,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
74
76
|
customSubmenu,
|
|
75
77
|
renderAsDiv = false,
|
|
76
78
|
}) => {
|
|
79
|
+
'use no memo';
|
|
77
80
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
78
81
|
const [shouldOpenLeft, setShouldOpenLeft] = useState(false);
|
|
79
82
|
|
|
@@ -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 {
|
|
14
|
+
import {useMemo} from 'react';
|
|
15
15
|
|
|
16
16
|
import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
|
|
17
17
|
import {useQueryState} from 'nuqs';
|
|
@@ -46,8 +46,8 @@ export function useTableConfiguration({
|
|
|
46
46
|
const columnHelper = createColumnHelper<Row>();
|
|
47
47
|
const [tableColumns] = useQueryState('table_columns', tableColumnsParser);
|
|
48
48
|
|
|
49
|
-
const
|
|
50
|
-
|
|
49
|
+
const columnVisibility = useMemo(() => {
|
|
50
|
+
const defaults: Record<string, boolean> = {
|
|
51
51
|
color: true,
|
|
52
52
|
flat: true,
|
|
53
53
|
flatPercentage: false,
|
|
@@ -62,19 +62,13 @@ export function useTableConfiguration({
|
|
|
62
62
|
functionFileName: false,
|
|
63
63
|
mappingFile: false,
|
|
64
64
|
};
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
useEffect(() => {
|
|
68
65
|
if (Array.isArray(tableColumns)) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
(Object.keys(newState) as ColumnName[]).forEach(column => {
|
|
72
|
-
newState[column] = tableColumns.includes(column);
|
|
73
|
-
});
|
|
74
|
-
return newState;
|
|
66
|
+
(Object.keys(defaults) as ColumnName[]).forEach(column => {
|
|
67
|
+
defaults[column] = tableColumns.includes(column);
|
|
75
68
|
});
|
|
76
69
|
}
|
|
77
|
-
|
|
70
|
+
return defaults;
|
|
71
|
+
}, [tableColumns, compareMode]);
|
|
78
72
|
|
|
79
73
|
const columns = useMemo<Array<ColumnDef<Row>>>(() => {
|
|
80
74
|
const baseColumns: Array<ColumnDef<Row>> = [
|