@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.
- package/CHANGELOG.md +5 -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.d.ts.map +1 -1
- package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +104 -72
- 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 +241 -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.d.ts +2 -2
- package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -1
- package/dist/ProfileFlameChart/SamplesStrips/index.js +645 -134
- package/dist/ProfileFlameChart/SamplesStrips/labelSetUtils.js +114 -55
- package/dist/ProfileFlameChart/index.d.ts.map +1 -1
- package/dist/ProfileFlameChart/index.js +267 -129
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +288 -89
- 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 +72 -47
- 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.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/index.js +324 -150
- package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +140 -32
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +519 -258
- package/dist/ProfileSelector/CompareButton.js +132 -12
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +236 -64
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +727 -141
- package/dist/ProfileSelector/useAutoQuerySelector.js +249 -130
- 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.d.ts.map +1 -1
- package/dist/ProfileView/components/ActionButtons/SortByDropdown.js +141 -35
- package/dist/ProfileView/components/ColorStackLegend.d.ts.map +1 -1
- package/dist/ProfileView/components/ColorStackLegend.js +185 -55
- 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.d.ts.map +1 -1
- package/dist/ProfileView/components/InvertCallStack/index.js +100 -12
- 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.d.ts +2 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.js +188 -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 +678 -194
- package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +94 -7
- package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/TableColumnsDropdown.js +199 -157
- package/dist/ProfileView/components/Toolbars/index.d.ts +2 -2
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +441 -21
- package/dist/ProfileView/components/ViewSelector/Dropdown.js +233 -22
- package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +212 -86
- 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.d.ts.map +1 -1
- package/dist/ProfileView/context/DashboardContext.js +85 -29
- 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.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetFlameGraphState.js +32 -12
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +71 -27
- package/dist/ProfileView/hooks/useResetStateOnSeriesChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnSeriesChange.js +40 -19
- package/dist/ProfileView/hooks/useVisualizationState.d.ts +3 -3
- package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useVisualizationState.js +258 -67
- 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.d.ts.map +1 -1
- package/dist/ProfileViewWithData.js +332 -228
- 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.d.ts.map +1 -1
- package/dist/Sandwich/index.js +126 -14
- 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.d.ts.map +1 -1
- package/dist/SourceView/index.js +178 -104
- package/dist/SourceView/lang-detector/ext-to-lang.json +798 -798
- package/dist/SourceView/lang-detector/index.js +28 -14
- package/dist/SourceView/useSelectedLineRange.d.ts.map +1 -1
- package/dist/SourceView/useSelectedLineRange.js +99 -23
- package/dist/Table/ColorCell.js +42 -1
- package/dist/Table/ColumnsVisibility.js +114 -6
- package/dist/Table/MoreDropdown.d.ts.map +1 -1
- package/dist/Table/MoreDropdown.js +122 -25
- package/dist/Table/TableContextMenu.d.ts.map +1 -1
- package/dist/Table/TableContextMenu.js +151 -137
- 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 +333 -169
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +222 -128
- 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.d.ts.map +1 -1
- package/dist/TopTable/index.js +342 -123
- package/dist/contexts/LabelsQueryProvider.js +94 -32
- package/dist/contexts/UnifiedLabelsContext.js +114 -49
- package/dist/contexts/utils.js +37 -15
- package/dist/hooks/useCompareModeMeta.d.ts.map +1 -1
- package/dist/hooks/useCompareModeMeta.js +158 -64
- package/dist/hooks/useLabels.js +295 -52
- package/dist/hooks/useQueryState.d.ts +3 -3
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +373 -332
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -8
- 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 +1 -1
- package/dist/useSumBy.d.ts.map +1 -1
- package/dist/useSumBy.js +294 -138
- package/dist/utils.js +62 -30
- package/package.json +9 -10
- package/src/GraphTooltipArrow/index.tsx +3 -0
- package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +13 -11
- 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 -4
- package/src/ProfileFlameChart/SamplesStrips/index.tsx +2 -2
- package/src/ProfileFlameChart/index.tsx +28 -21
- package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +9 -10
- package/src/ProfileFlameGraph/FlameGraphArrow/TextWithEllipsis.tsx +6 -5
- 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/ProfileFlameGraph/index.tsx +9 -6
- package/src/ProfileMetricsGraph/index.tsx +8 -6
- package/src/ProfileSelector/MetricsGraphSection.tsx +10 -5
- package/src/ProfileSelector/index.tsx +61 -39
- package/src/ProfileView/components/ActionButtons/SortByDropdown.tsx +6 -10
- package/src/ProfileView/components/ColorStackLegend.tsx +4 -2
- package/src/ProfileView/components/InvertCallStack/index.tsx +4 -5
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.tsx +192 -94
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +21 -21
- package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +28 -24
- package/src/ProfileView/components/Toolbars/TableColumnsDropdown.tsx +5 -4
- package/src/ProfileView/components/Toolbars/index.tsx +3 -3
- package/src/ProfileView/components/ViewSelector/index.tsx +16 -9
- package/src/ProfileView/components/VisualizationContainer/index.tsx +3 -0
- package/src/ProfileView/context/DashboardContext.tsx +6 -6
- package/src/ProfileView/hooks/useResetFlameGraphState.ts +4 -6
- package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +26 -24
- package/src/ProfileView/hooks/useResetStateOnSeriesChange.ts +8 -16
- package/src/ProfileView/hooks/useVisualizationState.ts +69 -61
- package/src/ProfileViewWithData.tsx +35 -29
- package/src/Sandwich/index.tsx +3 -4
- package/src/SourceView/index.tsx +2 -4
- package/src/SourceView/useSelectedLineRange.ts +19 -34
- package/src/Table/MoreDropdown.tsx +11 -9
- package/src/Table/TableContextMenu.tsx +13 -10
- package/src/Table/hooks/useTableConfiguration.tsx +11 -16
- package/src/Table/index.tsx +21 -12
- package/src/TopTable/index.tsx +4 -3
- package/src/hooks/useCompareModeMeta.ts +91 -61
- package/src/hooks/useQueryState.test.tsx +345 -275
- package/src/hooks/useQueryState.ts +118 -136
- package/src/index.tsx +15 -16
- package/src/useDelayedLoader.ts +10 -10
- package/src/useSumBy.ts +15 -21
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.test.js +0 -455
- package/dist/hooks/urlParsers.d.ts +0 -18
- package/dist/hooks/urlParsers.d.ts.map +0 -1
- package/dist/hooks/urlParsers.js +0 -32
- package/dist/hooks/useColorBy.d.ts +0 -5
- package/dist/hooks/useColorBy.d.ts.map +0 -1
- package/dist/hooks/useColorBy.js +0 -26
- package/dist/hooks/useDashboardItems.d.ts +0 -5
- package/dist/hooks/useDashboardItems.d.ts.map +0 -1
- package/dist/hooks/useDashboardItems.js +0 -27
- package/dist/hooks/useQueryState.test.js +0 -868
- package/src/hooks/urlParsers.ts +0 -38
- package/src/hooks/useColorBy.ts +0 -42
- package/src/hooks/useDashboardItems.ts +0 -46
|
@@ -15,28 +15,78 @@ import {type ReactNode} from 'react';
|
|
|
15
15
|
|
|
16
16
|
// eslint-disable-next-line import/named
|
|
17
17
|
import {act, renderHook, waitFor} from '@testing-library/react';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import {
|
|
18
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
|
19
|
+
|
|
20
|
+
import {URLStateProvider} from '@parca/components';
|
|
21
21
|
|
|
22
22
|
import {type ProfileFilter} from './useProfileFilters';
|
|
23
23
|
import {decodeProfileFilters, useProfileFiltersUrlState} from './useProfileFiltersUrlState';
|
|
24
24
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
// Mock window.location
|
|
26
|
+
const mockLocation = {
|
|
27
|
+
pathname: '/test',
|
|
28
|
+
search: '',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Mock the navigate function
|
|
32
|
+
const mockNavigateTo = vi.fn((path: string, params: Record<string, string | string[]>) => {
|
|
33
|
+
const searchParams = new URLSearchParams();
|
|
34
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
35
|
+
if (value !== undefined && value !== null) {
|
|
36
|
+
if (Array.isArray(value)) {
|
|
37
|
+
searchParams.set(key, value.join(','));
|
|
38
|
+
} else {
|
|
39
|
+
searchParams.set(key, String(value));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
mockLocation.search = `?${searchParams.toString()}`;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Mock getQueryParamsFromURL
|
|
47
|
+
vi.mock('@parca/components/src/hooks/URLState/utils', async () => {
|
|
48
|
+
const actual = await vi.importActual('@parca/components/src/hooks/URLState/utils');
|
|
49
|
+
return {
|
|
50
|
+
...actual,
|
|
51
|
+
getQueryParamsFromURL: () => {
|
|
52
|
+
if (mockLocation.search === '') return {};
|
|
53
|
+
const params = new URLSearchParams(mockLocation.search);
|
|
54
|
+
const result: Record<string, string | string[]> = {};
|
|
55
|
+
for (const [key, value] of params.entries()) {
|
|
56
|
+
const decodedValue = decodeURIComponent(value);
|
|
57
|
+
const existing = result[key];
|
|
58
|
+
if (existing !== undefined) {
|
|
59
|
+
result[key] = Array.isArray(existing)
|
|
60
|
+
? [...existing, decodedValue]
|
|
61
|
+
: [existing, decodedValue];
|
|
62
|
+
} else {
|
|
63
|
+
result[key] = decodedValue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Helper to create wrapper with URLStateProvider
|
|
72
|
+
const createWrapper = (): (({children}: {children: ReactNode}) => JSX.Element) => {
|
|
30
73
|
const Wrapper = ({children}: {children: ReactNode}): JSX.Element => (
|
|
31
|
-
<
|
|
32
|
-
{children}
|
|
33
|
-
</NuqsTestingAdapter>
|
|
74
|
+
<URLStateProvider navigateTo={mockNavigateTo}>{children}</URLStateProvider>
|
|
34
75
|
);
|
|
35
|
-
Wrapper.displayName = '
|
|
76
|
+
Wrapper.displayName = 'URLStateProviderWrapper';
|
|
36
77
|
return Wrapper;
|
|
37
78
|
};
|
|
38
79
|
|
|
39
80
|
describe('useProfileFiltersUrlState', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
mockNavigateTo.mockClear();
|
|
83
|
+
Object.defineProperty(window, 'location', {
|
|
84
|
+
value: mockLocation,
|
|
85
|
+
writable: true,
|
|
86
|
+
});
|
|
87
|
+
mockLocation.search = '';
|
|
88
|
+
});
|
|
89
|
+
|
|
40
90
|
describe('decodeProfileFilters', () => {
|
|
41
91
|
it('should return empty array for empty string', () => {
|
|
42
92
|
expect(decodeProfileFilters('')).toEqual([]);
|
|
@@ -205,9 +255,9 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
205
255
|
});
|
|
206
256
|
|
|
207
257
|
it('should read filters from URL', async () => {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
});
|
|
258
|
+
mockLocation.search = '?profile_filters=s:fn:=:testFunc';
|
|
259
|
+
|
|
260
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
211
261
|
|
|
212
262
|
await waitFor(() => {
|
|
213
263
|
expect(result.current.appliedFilters).toHaveLength(1);
|
|
@@ -221,10 +271,7 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
221
271
|
});
|
|
222
272
|
|
|
223
273
|
it('should update URL when setting filters', async () => {
|
|
224
|
-
const
|
|
225
|
-
const {result} = renderHook(() => useProfileFiltersUrlState(), {
|
|
226
|
-
wrapper: createWrapper({}, onUrlUpdate),
|
|
227
|
-
});
|
|
274
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
228
275
|
|
|
229
276
|
const newFilters: ProfileFilter[] = [
|
|
230
277
|
{
|
|
@@ -241,26 +288,26 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
241
288
|
});
|
|
242
289
|
|
|
243
290
|
await waitFor(() => {
|
|
244
|
-
expect(
|
|
245
|
-
const
|
|
246
|
-
expect(
|
|
291
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
292
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
293
|
+
expect(params.profile_filters).toBe('f:b:!~:libc.so');
|
|
247
294
|
});
|
|
248
295
|
});
|
|
249
296
|
|
|
250
297
|
it('should clear URL param when setting empty filters', async () => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
});
|
|
298
|
+
mockLocation.search = '?profile_filters=s:fn:=:testFunc';
|
|
299
|
+
|
|
300
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
255
301
|
|
|
256
302
|
act(() => {
|
|
257
303
|
result.current.setAppliedFilters([]);
|
|
258
304
|
});
|
|
259
305
|
|
|
260
306
|
await waitFor(() => {
|
|
261
|
-
expect(
|
|
262
|
-
const
|
|
263
|
-
|
|
307
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
308
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
309
|
+
// When filters are empty, the param is either empty string or undefined (removed)
|
|
310
|
+
expect(params.profile_filters === '' || params.profile_filters === undefined).toBe(true);
|
|
264
311
|
});
|
|
265
312
|
});
|
|
266
313
|
});
|
|
@@ -273,10 +320,9 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
273
320
|
});
|
|
274
321
|
|
|
275
322
|
it('should force apply filters overwriting existing', async () => {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
323
|
+
mockLocation.search = '?profile_filters=s:fn:=:existingFunc';
|
|
324
|
+
|
|
325
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
280
326
|
|
|
281
327
|
// Verify existing filter is loaded
|
|
282
328
|
await waitFor(() => {
|
|
@@ -298,36 +344,33 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
298
344
|
});
|
|
299
345
|
|
|
300
346
|
await waitFor(() => {
|
|
301
|
-
expect(
|
|
302
|
-
const
|
|
303
|
-
expect(
|
|
347
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
348
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
349
|
+
expect(params.profile_filters).toBe('f:b:!~:forcedValue');
|
|
304
350
|
});
|
|
305
351
|
});
|
|
306
352
|
|
|
307
353
|
it('should clear filters when force applying empty array', async () => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
});
|
|
354
|
+
mockLocation.search = '?profile_filters=s:fn:=:existingFunc';
|
|
355
|
+
|
|
356
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
312
357
|
|
|
313
358
|
act(() => {
|
|
314
359
|
result.current.forceApplyFilters([]);
|
|
315
360
|
});
|
|
316
361
|
|
|
317
362
|
await waitFor(() => {
|
|
318
|
-
expect(
|
|
319
|
-
const
|
|
320
|
-
|
|
363
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
364
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
365
|
+
// When filters are empty, the param is either empty string or undefined (removed)
|
|
366
|
+
expect(params.profile_filters === '' || params.profile_filters === undefined).toBe(true);
|
|
321
367
|
});
|
|
322
368
|
});
|
|
323
369
|
});
|
|
324
370
|
|
|
325
371
|
describe('Preset filter encoding', () => {
|
|
326
372
|
it('should encode preset filters correctly', async () => {
|
|
327
|
-
const
|
|
328
|
-
const {result} = renderHook(() => useProfileFiltersUrlState(), {
|
|
329
|
-
wrapper: createWrapper({}, onUrlUpdate),
|
|
330
|
-
});
|
|
373
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
331
374
|
|
|
332
375
|
const presetFilters: ProfileFilter[] = [
|
|
333
376
|
{
|
|
@@ -342,17 +385,14 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
342
385
|
});
|
|
343
386
|
|
|
344
387
|
await waitFor(() => {
|
|
345
|
-
expect(
|
|
346
|
-
const
|
|
347
|
-
expect(
|
|
388
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
389
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
390
|
+
expect(params.profile_filters).toBe('p:hide_libc:enabled');
|
|
348
391
|
});
|
|
349
392
|
});
|
|
350
393
|
|
|
351
394
|
it('should handle mixed preset and regular filters', async () => {
|
|
352
|
-
const
|
|
353
|
-
const {result} = renderHook(() => useProfileFiltersUrlState(), {
|
|
354
|
-
wrapper: createWrapper({}, onUrlUpdate),
|
|
355
|
-
});
|
|
395
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
356
396
|
|
|
357
397
|
const mixedFilters: ProfileFilter[] = [
|
|
358
398
|
{
|
|
@@ -374,21 +414,16 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
374
414
|
});
|
|
375
415
|
|
|
376
416
|
await waitFor(() => {
|
|
377
|
-
expect(
|
|
378
|
-
const
|
|
379
|
-
expect(
|
|
380
|
-
'p:hide_libc:enabled,f:b:!~:node'
|
|
381
|
-
);
|
|
417
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
418
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
419
|
+
expect(params.profile_filters).toBe('p:hide_libc:enabled,f:b:!~:node');
|
|
382
420
|
});
|
|
383
421
|
});
|
|
384
422
|
});
|
|
385
423
|
|
|
386
424
|
describe('URL encoding edge cases', () => {
|
|
387
425
|
it('should handle special characters in filter values', async () => {
|
|
388
|
-
const
|
|
389
|
-
const {result} = renderHook(() => useProfileFiltersUrlState(), {
|
|
390
|
-
wrapper: createWrapper({}, onUrlUpdate),
|
|
391
|
-
});
|
|
426
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
392
427
|
|
|
393
428
|
const filtersWithSpecialChars: ProfileFilter[] = [
|
|
394
429
|
{
|
|
@@ -405,19 +440,15 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
405
440
|
});
|
|
406
441
|
|
|
407
442
|
await waitFor(() => {
|
|
408
|
-
expect(
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
expect(filterValue).toContain('std%3A%3Avector%3Cint%3E');
|
|
443
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
444
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
445
|
+
// Value should be URL encoded
|
|
446
|
+
expect(params.profile_filters).toContain('std%3A%3Avector%3Cint%3E');
|
|
413
447
|
});
|
|
414
448
|
});
|
|
415
449
|
|
|
416
450
|
it('should filter out incomplete filters when encoding', async () => {
|
|
417
|
-
const
|
|
418
|
-
const {result} = renderHook(() => useProfileFiltersUrlState(), {
|
|
419
|
-
wrapper: createWrapper({}, onUrlUpdate),
|
|
420
|
-
});
|
|
451
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
421
452
|
|
|
422
453
|
const incompleteFilters: ProfileFilter[] = [
|
|
423
454
|
{
|
|
@@ -445,10 +476,10 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
445
476
|
});
|
|
446
477
|
|
|
447
478
|
await waitFor(() => {
|
|
448
|
-
expect(
|
|
449
|
-
const
|
|
479
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
480
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
450
481
|
// Only the complete filter should be encoded
|
|
451
|
-
expect(
|
|
482
|
+
expect(params.profile_filters).toBe('f:b:!~:valid');
|
|
452
483
|
});
|
|
453
484
|
});
|
|
454
485
|
});
|
|
@@ -470,9 +501,9 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
470
501
|
});
|
|
471
502
|
|
|
472
503
|
it('should return correctly structured filters from URL', async () => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
});
|
|
504
|
+
mockLocation.search = '?profile_filters=s:fn:=:testFunc';
|
|
505
|
+
|
|
506
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
476
507
|
|
|
477
508
|
await waitFor(() => {
|
|
478
509
|
expect(result.current.appliedFilters).toHaveLength(1);
|
|
@@ -491,16 +522,15 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
491
522
|
|
|
492
523
|
describe('View switching scenarios', () => {
|
|
493
524
|
it('should completely replace filters when switching views using forceApplyFilters', async () => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
onUrlUpdate
|
|
499
|
-
),
|
|
500
|
-
});
|
|
525
|
+
// Start with View A's filters (2 filters)
|
|
526
|
+
mockLocation.search = '?profile_filters=s:fn:=:viewAFunc,f:b:!=:viewABinary';
|
|
527
|
+
|
|
528
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
501
529
|
|
|
502
530
|
await waitFor(() => {
|
|
503
531
|
expect(result.current.appliedFilters).toHaveLength(2);
|
|
532
|
+
expect(result.current.appliedFilters[0].value).toBe('viewAFunc');
|
|
533
|
+
expect(result.current.appliedFilters[1].value).toBe('viewABinary');
|
|
504
534
|
});
|
|
505
535
|
|
|
506
536
|
// Switch to View B (completely different filter)
|
|
@@ -519,28 +549,96 @@ describe('useProfileFiltersUrlState', () => {
|
|
|
519
549
|
});
|
|
520
550
|
|
|
521
551
|
await waitFor(() => {
|
|
522
|
-
expect(
|
|
523
|
-
const
|
|
524
|
-
const filterValue = lastCall.searchParams.get('profile_filters');
|
|
552
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
553
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
525
554
|
|
|
526
555
|
// View A's filters should be completely gone
|
|
527
|
-
expect(
|
|
528
|
-
expect(
|
|
556
|
+
expect(params.profile_filters).not.toContain('viewAFunc');
|
|
557
|
+
expect(params.profile_filters).not.toContain('viewABinary');
|
|
529
558
|
|
|
530
559
|
// Only View B's filter should be present
|
|
531
|
-
expect(
|
|
560
|
+
expect(params.profile_filters).toBe('f:fn:~:viewBOnly');
|
|
532
561
|
});
|
|
533
562
|
});
|
|
534
563
|
|
|
535
|
-
it('should
|
|
536
|
-
|
|
537
|
-
|
|
564
|
+
it('should handle sequential view switches correctly', async () => {
|
|
565
|
+
// Simulate: [default] -> [storage] -> [testing-view]
|
|
566
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
567
|
+
|
|
568
|
+
// View 1: default view (1 filter)
|
|
569
|
+
const defaultFilters: ProfileFilter[] = [{id: 'd-1', type: 'hide_libc', value: 'enabled'}];
|
|
570
|
+
|
|
571
|
+
act(() => {
|
|
572
|
+
result.current.forceApplyFilters(defaultFilters);
|
|
538
573
|
});
|
|
539
574
|
|
|
575
|
+
await waitFor(() => {
|
|
576
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
577
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
578
|
+
expect(params.profile_filters).toBe('p:hide_libc:enabled');
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
mockNavigateTo.mockClear();
|
|
582
|
+
|
|
583
|
+
// View 2: storage view (3 filters)
|
|
584
|
+
const storageFilters: ProfileFilter[] = [
|
|
585
|
+
{id: 's-1', type: 'stack', field: 'function_name', matchType: 'not_contains', value: 'io'},
|
|
586
|
+
{id: 's-2', type: 'frame', field: 'binary', matchType: 'not_contains', value: 'disk'},
|
|
587
|
+
{id: 's-3', type: 'frame', field: 'function_name', matchType: 'contains', value: 'storage'},
|
|
588
|
+
];
|
|
589
|
+
|
|
590
|
+
act(() => {
|
|
591
|
+
result.current.forceApplyFilters(storageFilters);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
await waitFor(() => {
|
|
595
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
596
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
597
|
+
// Default view's filter should be gone
|
|
598
|
+
expect(params.profile_filters).not.toContain('hide_libc');
|
|
599
|
+
// Storage view should have 3 filters
|
|
600
|
+
expect(params.profile_filters).toContain('io');
|
|
601
|
+
expect(params.profile_filters).toContain('disk');
|
|
602
|
+
expect(params.profile_filters).toContain('storage');
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
mockNavigateTo.mockClear();
|
|
606
|
+
|
|
607
|
+
// View 3: testing-view (2 filters)
|
|
608
|
+
const testingFilters: ProfileFilter[] = [
|
|
609
|
+
{id: 't-1', type: 'stack', field: 'function_name', matchType: 'equal', value: 'test_main'},
|
|
610
|
+
{id: 't-2', type: 'frame', field: 'binary', matchType: 'contains', value: 'test'},
|
|
611
|
+
];
|
|
612
|
+
|
|
613
|
+
act(() => {
|
|
614
|
+
result.current.forceApplyFilters(testingFilters);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
await waitFor(() => {
|
|
618
|
+
expect(mockNavigateTo).toHaveBeenCalled();
|
|
619
|
+
const [, params] = mockNavigateTo.mock.calls[mockNavigateTo.mock.calls.length - 1];
|
|
620
|
+
// Storage view's filters should be gone
|
|
621
|
+
expect(params.profile_filters).not.toContain('io');
|
|
622
|
+
expect(params.profile_filters).not.toContain('disk');
|
|
623
|
+
expect(params.profile_filters).not.toContain('storage');
|
|
624
|
+
// Testing view should have its 2 filters
|
|
625
|
+
expect(params.profile_filters).toContain('test_main');
|
|
626
|
+
expect(params.profile_filters).toContain('test');
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should not change filters when clicking the same view tab', async () => {
|
|
631
|
+
// Start with existing filters
|
|
632
|
+
mockLocation.search = '?profile_filters=s:fn:=:existingFilter';
|
|
633
|
+
|
|
634
|
+
const {result} = renderHook(() => useProfileFiltersUrlState(), {wrapper: createWrapper()});
|
|
635
|
+
|
|
540
636
|
await waitFor(() => {
|
|
541
637
|
expect(result.current.appliedFilters).toHaveLength(1);
|
|
542
638
|
});
|
|
543
639
|
|
|
640
|
+
mockNavigateTo.mockClear();
|
|
641
|
+
|
|
544
642
|
// Apply the same filters (simulating clicking the same view tab)
|
|
545
643
|
const sameFilters: ProfileFilter[] = [
|
|
546
644
|
{
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import {useCallback, useMemo} from 'react';
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
|
|
16
|
+
import {useURLStateBatch, useURLStateCustom, type ParamValueSetterCustom} from '@parca/components';
|
|
18
17
|
import {safeDecode} from '@parca/utilities';
|
|
19
18
|
|
|
20
19
|
import {isPresetKey} from './filterPresets';
|
|
@@ -138,32 +137,31 @@ export const decodeProfileFilters = (encoded: string): ProfileFilter[] => {
|
|
|
138
137
|
}
|
|
139
138
|
};
|
|
140
139
|
|
|
141
|
-
const profileFiltersParser = createParser<ProfileFilter[]>({
|
|
142
|
-
parse: (value: string) => decodeProfileFilters(value),
|
|
143
|
-
serialize: (value: ProfileFilter[]) => encodeProfileFilters(value),
|
|
144
|
-
eq: (a, b) => encodeProfileFilters(a) === encodeProfileFilters(b),
|
|
145
|
-
})
|
|
146
|
-
.withDefault([])
|
|
147
|
-
.withOptions({history: 'replace'});
|
|
148
|
-
|
|
149
140
|
export const useProfileFiltersUrlState = (): {
|
|
150
141
|
appliedFilters: ProfileFilter[];
|
|
151
|
-
setAppliedFilters:
|
|
142
|
+
setAppliedFilters: ParamValueSetterCustom<ProfileFilter[]>;
|
|
152
143
|
forceApplyFilters: (filters: ProfileFilter[]) => void;
|
|
153
144
|
} => {
|
|
154
|
-
const
|
|
145
|
+
const batchUpdates = useURLStateBatch();
|
|
146
|
+
|
|
147
|
+
// Store applied filters in URL state for persistence using compact encoding
|
|
148
|
+
const [appliedFilters, setAppliedFilters] = useURLStateCustom<ProfileFilter[]>(
|
|
149
|
+
`profile_filters`,
|
|
150
|
+
{
|
|
151
|
+
parse: value => {
|
|
152
|
+
return decodeProfileFilters(value as string);
|
|
153
|
+
},
|
|
154
|
+
stringify: value => {
|
|
155
|
+
return encodeProfileFilters(value);
|
|
156
|
+
},
|
|
157
|
+
defaultValue: [],
|
|
158
|
+
}
|
|
159
|
+
);
|
|
155
160
|
|
|
156
161
|
const memoizedAppliedFilters = useMemo(() => {
|
|
157
162
|
return appliedFilters ?? [];
|
|
158
163
|
}, [appliedFilters]);
|
|
159
164
|
|
|
160
|
-
const setAppliedFilters = useCallback(
|
|
161
|
-
(filters: ProfileFilter[]) => {
|
|
162
|
-
void setRawFilters(filters);
|
|
163
|
-
},
|
|
164
|
-
[setRawFilters]
|
|
165
|
-
);
|
|
166
|
-
|
|
167
165
|
// Force apply filters (bypasses preserve-existing strategy)
|
|
168
166
|
const forceApplyFilters = useCallback(
|
|
169
167
|
(filters: ProfileFilter[]) => {
|
|
@@ -174,9 +172,11 @@ export const useProfileFiltersUrlState = (): {
|
|
|
174
172
|
return f.value !== '' && f.type != null && f.field != null && f.matchType != null;
|
|
175
173
|
});
|
|
176
174
|
|
|
177
|
-
|
|
175
|
+
batchUpdates(() => {
|
|
176
|
+
setAppliedFilters(validFilters);
|
|
177
|
+
});
|
|
178
178
|
},
|
|
179
|
-
[setAppliedFilters]
|
|
179
|
+
[batchUpdates, setAppliedFilters]
|
|
180
180
|
);
|
|
181
181
|
|
|
182
182
|
return {
|
|
@@ -11,13 +11,15 @@
|
|
|
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';
|
|
17
19
|
import {Icon} from '@iconify/react';
|
|
18
20
|
import cx from 'classnames';
|
|
19
|
-
import {useQueryState} from 'nuqs';
|
|
20
21
|
|
|
22
|
+
import {useURLState} from '@parca/components';
|
|
21
23
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
22
24
|
import {ProfileType} from '@parca/parser';
|
|
23
25
|
|
|
@@ -27,7 +29,6 @@ import {
|
|
|
27
29
|
FIELD_LOCATION_ADDRESS,
|
|
28
30
|
FIELD_MAPPING_FILE,
|
|
29
31
|
} from '../../../ProfileFlameGraph/FlameGraphArrow';
|
|
30
|
-
import {boolParam, hiddenBinariesParser, stringParam} from '../../../hooks/urlParsers';
|
|
31
32
|
import {useProfileViewContext} from '../../context/ProfileViewContext';
|
|
32
33
|
import SwitchMenuItem from './SwitchMenuItem';
|
|
33
34
|
|
|
@@ -74,6 +75,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
74
75
|
customSubmenu,
|
|
75
76
|
renderAsDiv = false,
|
|
76
77
|
}) => {
|
|
78
|
+
'use no memo';
|
|
77
79
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
78
80
|
const [shouldOpenLeft, setShouldOpenLeft] = useState(false);
|
|
79
81
|
|
|
@@ -207,15 +209,14 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
207
209
|
}) => {
|
|
208
210
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
209
211
|
const [shouldOpenLeft, setShouldOpenLeft] = useState(false);
|
|
210
|
-
const [storeSortBy] =
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
);
|
|
212
|
+
const [storeSortBy] = useURLState('sort_by', {
|
|
213
|
+
defaultValue: FIELD_FUNCTION_NAME,
|
|
214
|
+
});
|
|
215
|
+
const [colorStackLegend, setStoreColorStackLegend] = useURLState('color_stack_legend');
|
|
216
|
+
const [hiddenBinaries, setHiddenBinaries] = useURLState('hidden_binaries', {
|
|
217
|
+
defaultValue: [],
|
|
218
|
+
alwaysReturnArray: true,
|
|
219
|
+
});
|
|
219
220
|
const {compareMode} = useProfileViewContext();
|
|
220
221
|
const [colorProfileName] = useUserPreference<string>(
|
|
221
222
|
USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
|
|
@@ -225,10 +226,11 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
225
226
|
|
|
226
227
|
// By default, we want delta profiles (CPU) to be relatively compared.
|
|
227
228
|
// For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
|
|
228
|
-
const compareAbsoluteDefault = profileType?.delta === false;
|
|
229
|
+
const compareAbsoluteDefault = profileType?.delta === false ? 'true' : 'false';
|
|
229
230
|
|
|
230
|
-
const [compareAbsolute, setCompareAbsolute] =
|
|
231
|
-
|
|
231
|
+
const [compareAbsolute = compareAbsoluteDefault, setCompareAbsolute] =
|
|
232
|
+
useURLState('compare_absolute');
|
|
233
|
+
const isCompareAbsolute = compareAbsolute === 'true';
|
|
232
234
|
|
|
233
235
|
useEffect(() => {
|
|
234
236
|
const checkOverflow = (): void => {
|
|
@@ -249,20 +251,20 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
249
251
|
}, [isTableVizOnly]);
|
|
250
252
|
|
|
251
253
|
const handleBinaryToggle = (index: number): void => {
|
|
252
|
-
const updatedBinaries = [...hiddenBinaries];
|
|
254
|
+
const updatedBinaries = [...(hiddenBinaries as string[])];
|
|
253
255
|
updatedBinaries.splice(index, 1);
|
|
254
|
-
|
|
256
|
+
setHiddenBinaries(updatedBinaries);
|
|
255
257
|
};
|
|
256
258
|
|
|
257
259
|
const setColorStackLegend = useCallback(
|
|
258
260
|
(value: string): void => {
|
|
259
|
-
|
|
261
|
+
setStoreColorStackLegend(value);
|
|
260
262
|
},
|
|
261
263
|
[setStoreColorStackLegend]
|
|
262
264
|
);
|
|
263
265
|
|
|
264
266
|
const resetLegend = (): void => {
|
|
265
|
-
|
|
267
|
+
setHiddenBinaries([]);
|
|
266
268
|
};
|
|
267
269
|
|
|
268
270
|
const menuItems: MenuItemType[] = [
|
|
@@ -330,7 +332,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
330
332
|
},
|
|
331
333
|
{
|
|
332
334
|
label: isCompareAbsolute ? 'Compare Relative' : 'Compare Absolute',
|
|
333
|
-
onclick: () =>
|
|
335
|
+
onclick: () => setCompareAbsolute(isCompareAbsolute ? 'false' : 'true'),
|
|
334
336
|
hide: !compareMode,
|
|
335
337
|
icon: isCompareAbsolute ? 'fluent-mdl2:compare' : 'fluent-mdl2:compare-uneven',
|
|
336
338
|
},
|
|
@@ -360,7 +362,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
360
362
|
},
|
|
361
363
|
{
|
|
362
364
|
label: 'Reset Legend',
|
|
363
|
-
hide: hiddenBinaries.length === 0,
|
|
365
|
+
hide: hiddenBinaries === undefined || hiddenBinaries.length === 0,
|
|
364
366
|
onclick: () => resetLegend(),
|
|
365
367
|
id: 'h-reset-legend-button',
|
|
366
368
|
icon: 'system-uicons:reset',
|
|
@@ -368,7 +370,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
368
370
|
{
|
|
369
371
|
label: 'Hidden Binaries',
|
|
370
372
|
id: 'h-hidden-binaries',
|
|
371
|
-
items: hiddenBinaries
|
|
373
|
+
items: (hiddenBinaries as string[])?.map((binary, index) => ({
|
|
372
374
|
label: binary,
|
|
373
375
|
customSubmenu: (
|
|
374
376
|
<div className="flex items-center gap-2 w-full">
|
|
@@ -384,7 +386,7 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
384
386
|
</div>
|
|
385
387
|
),
|
|
386
388
|
})),
|
|
387
|
-
hide: hiddenBinaries.length === 0,
|
|
389
|
+
hide: hiddenBinaries === undefined || hiddenBinaries.length === 0,
|
|
388
390
|
icon: 'ph:eye-closed',
|
|
389
391
|
},
|
|
390
392
|
];
|
|
@@ -425,8 +427,10 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
|
425
427
|
{...item}
|
|
426
428
|
onSelect={onSelect}
|
|
427
429
|
closeDropdown={close}
|
|
428
|
-
activeValueForSortBy={storeSortBy}
|
|
429
|
-
activeValueForColorBy={
|
|
430
|
+
activeValueForSortBy={storeSortBy as string}
|
|
431
|
+
activeValueForColorBy={
|
|
432
|
+
colorBy === undefined || colorBy === '' ? 'binary' : colorBy
|
|
433
|
+
}
|
|
430
434
|
activeValuesForLevel={groupBy}
|
|
431
435
|
renderAsDiv={item.renderAsDiv}
|
|
432
436
|
/>
|