@parca/profile 0.19.9 → 0.19.11
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 +8 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +2 -2
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts +0 -1
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +4 -141
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts +8 -0
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -0
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +57 -0
- package/dist/ProfileView/components/InvertCallStack/index.d.ts +3 -0
- package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -0
- package/dist/ProfileView/components/InvertCallStack/index.js +21 -0
- package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ShareButton/index.js +1 -1
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts +3 -0
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +78 -20
- package/dist/ProfileView/components/Toolbars/SwitchMenuItem.d.ts +9 -0
- package/dist/ProfileView/components/Toolbars/SwitchMenuItem.d.ts.map +1 -0
- package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +22 -0
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +4 -1
- package/dist/ProfileView/components/ViewSelector/Dropdown.js +1 -1
- package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +9 -12
- package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useVisualizationState.js +16 -6
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +7 -2
- package/src/ProfileView/components/ActionButtons/GroupByDropdown.tsx +7 -323
- package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +92 -0
- package/src/ProfileView/components/InvertCallStack/index.tsx +34 -0
- package/src/ProfileView/components/ShareButton/index.tsx +8 -4
- package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +134 -22
- package/src/ProfileView/components/Toolbars/SwitchMenuItem.tsx +50 -0
- package/src/ProfileView/components/Toolbars/index.tsx +25 -9
- package/src/ProfileView/components/ViewSelector/Dropdown.tsx +1 -1
- package/src/ProfileView/components/ViewSelector/index.tsx +15 -14
- package/src/ProfileView/hooks/useVisualizationState.ts +25 -6
|
@@ -11,17 +11,24 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import React, {useCallback} from 'react';
|
|
14
|
+
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {Menu} from '@headlessui/react';
|
|
17
17
|
import {Icon} from '@iconify/react';
|
|
18
|
+
import cx from 'classnames';
|
|
18
19
|
|
|
19
20
|
import {useURLState} from '@parca/components';
|
|
20
21
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
21
22
|
import {ProfileType} from '@parca/parser';
|
|
22
23
|
|
|
23
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
FIELD_FUNCTION_FILE_NAME,
|
|
26
|
+
FIELD_FUNCTION_NAME,
|
|
27
|
+
FIELD_LOCATION_ADDRESS,
|
|
28
|
+
FIELD_MAPPING_FILE,
|
|
29
|
+
} from '../../../ProfileIcicleGraph/IcicleGraphArrow';
|
|
24
30
|
import {useProfileViewContext} from '../../context/ProfileViewContext';
|
|
31
|
+
import SwitchMenuItem from './SwitchMenuItem';
|
|
25
32
|
|
|
26
33
|
interface MenuItemType {
|
|
27
34
|
label: string;
|
|
@@ -34,6 +41,7 @@ interface MenuItemType {
|
|
|
34
41
|
value?: string;
|
|
35
42
|
icon?: string;
|
|
36
43
|
customSubmenu?: React.ReactNode;
|
|
44
|
+
renderAsDiv?: boolean;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
type MenuItemProps = MenuItemType & {
|
|
@@ -43,6 +51,7 @@ type MenuItemProps = MenuItemType & {
|
|
|
43
51
|
isNested?: boolean;
|
|
44
52
|
activeValueForSortBy?: string;
|
|
45
53
|
activeValueForColorBy?: string;
|
|
54
|
+
activeValuesForLevel?: string[];
|
|
46
55
|
icon?: string;
|
|
47
56
|
};
|
|
48
57
|
|
|
@@ -57,12 +66,30 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
57
66
|
isNested = false,
|
|
58
67
|
activeValueForSortBy,
|
|
59
68
|
activeValueForColorBy,
|
|
69
|
+
activeValuesForLevel,
|
|
60
70
|
value,
|
|
61
71
|
disabled = false,
|
|
62
72
|
icon,
|
|
63
73
|
customSubmenu,
|
|
74
|
+
renderAsDiv = false,
|
|
64
75
|
}) => {
|
|
76
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
77
|
+
const [shouldOpenLeft, setShouldOpenLeft] = useState(false);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (items !== undefined && menuRef.current !== null) {
|
|
81
|
+
const rect = menuRef.current.getBoundingClientRect();
|
|
82
|
+
const viewportWidth = window.innerWidth;
|
|
83
|
+
const menuWidth = 224; // w-56 = 14rem = 224px
|
|
84
|
+
const spaceOnRight = viewportWidth - rect.right;
|
|
85
|
+
const spaceOnLeft = rect.left;
|
|
86
|
+
|
|
87
|
+
// Open to the left if there's not enough space on the right but enough on the left
|
|
88
|
+
setShouldOpenLeft(spaceOnRight < menuWidth && spaceOnLeft >= menuWidth);
|
|
89
|
+
}
|
|
90
|
+
}, [items]);
|
|
65
91
|
let isActive = false;
|
|
92
|
+
|
|
66
93
|
if (isNested) {
|
|
67
94
|
if (activeValueForSortBy !== undefined && value === activeValueForSortBy) {
|
|
68
95
|
isActive = true;
|
|
@@ -70,6 +97,9 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
70
97
|
if (activeValueForColorBy !== undefined && value === activeValueForColorBy) {
|
|
71
98
|
isActive = true;
|
|
72
99
|
}
|
|
100
|
+
if (activeValuesForLevel?.includes(value ?? '') ?? false) {
|
|
101
|
+
isActive = true;
|
|
102
|
+
}
|
|
73
103
|
}
|
|
74
104
|
|
|
75
105
|
const handleSelect = (): void => {
|
|
@@ -85,11 +115,12 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
85
115
|
};
|
|
86
116
|
|
|
87
117
|
return (
|
|
88
|
-
<div className="relative">
|
|
118
|
+
<div className="relative" ref={menuRef}>
|
|
89
119
|
<Menu>
|
|
90
120
|
{({close}) => (
|
|
91
121
|
<>
|
|
92
122
|
<Menu.Button
|
|
123
|
+
as={renderAsDiv ? 'div' : 'button'}
|
|
93
124
|
className={`w-full text-left px-4 py-2 text-sm ${
|
|
94
125
|
disabled
|
|
95
126
|
? 'text-gray-400'
|
|
@@ -105,9 +136,9 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
105
136
|
customSubmenu
|
|
106
137
|
) : (
|
|
107
138
|
<span className="flex items-center">
|
|
108
|
-
<div className="flex items-center">
|
|
139
|
+
<div className="flex items-center gap-2">
|
|
140
|
+
{icon !== undefined && <Icon icon={icon} className="h-4 w-4" />}
|
|
109
141
|
<span>{label}</span>
|
|
110
|
-
{icon !== undefined && <Icon icon={icon} className="ml-2 h-4 w-4" />}
|
|
111
142
|
</div>
|
|
112
143
|
{isActive && <Icon icon="heroicons-solid:check" className="ml-2 h-4 w-4" />}
|
|
113
144
|
</span>
|
|
@@ -117,7 +148,13 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
117
148
|
)}
|
|
118
149
|
</Menu.Button>
|
|
119
150
|
{items !== undefined && (
|
|
120
|
-
<Menu.Items
|
|
151
|
+
<Menu.Items
|
|
152
|
+
className={`absolute top-0 w-56 mt-0 bg-white border border-gray-200 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-900 dark:border-gray-600 ${
|
|
153
|
+
shouldOpenLeft
|
|
154
|
+
? 'right-full mr-1 origin-top-left'
|
|
155
|
+
: 'left-full ml-1 origin-top-right'
|
|
156
|
+
}`}
|
|
157
|
+
>
|
|
121
158
|
{items?.map((item, index) => (
|
|
122
159
|
<MenuItem
|
|
123
160
|
key={index}
|
|
@@ -132,6 +169,7 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
132
169
|
isNested={true}
|
|
133
170
|
activeValueForSortBy={activeValueForSortBy}
|
|
134
171
|
activeValueForColorBy={activeValueForColorBy}
|
|
172
|
+
activeValuesForLevel={activeValuesForLevel}
|
|
135
173
|
/>
|
|
136
174
|
))}
|
|
137
175
|
</Menu.Items>
|
|
@@ -146,9 +184,18 @@ const MenuItem: React.FC<MenuItemProps> = ({
|
|
|
146
184
|
interface MultiLevelDropdownProps {
|
|
147
185
|
onSelect: (path: string[]) => void;
|
|
148
186
|
profileType?: ProfileType;
|
|
187
|
+
groupBy: string[];
|
|
188
|
+
toggleGroupBy: (key: string) => void;
|
|
189
|
+
isTableVizOnly: boolean;
|
|
149
190
|
}
|
|
150
191
|
|
|
151
|
-
const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
192
|
+
const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({
|
|
193
|
+
onSelect,
|
|
194
|
+
profileType,
|
|
195
|
+
groupBy,
|
|
196
|
+
toggleGroupBy,
|
|
197
|
+
isTableVizOnly,
|
|
198
|
+
}) => {
|
|
152
199
|
const [storeSortBy] = useURLState('sort_by', {
|
|
153
200
|
defaultValue: FIELD_FUNCTION_NAME,
|
|
154
201
|
});
|
|
@@ -163,8 +210,6 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
163
210
|
const [colorProfileName] = useUserPreference<string>(
|
|
164
211
|
USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key
|
|
165
212
|
);
|
|
166
|
-
const [invertStack = '', setInvertStack] = useURLState('invert_call_stack');
|
|
167
|
-
const isInvert = invertStack === 'true';
|
|
168
213
|
const isColorStackLegendEnabled = colorStackLegend === 'true';
|
|
169
214
|
|
|
170
215
|
const [alignFunctionName, setAlignFunctionName] = useURLState('align_function_name');
|
|
@@ -196,9 +241,37 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
196
241
|
};
|
|
197
242
|
|
|
198
243
|
const menuItems: MenuItemType[] = [
|
|
244
|
+
{
|
|
245
|
+
label: 'Levels',
|
|
246
|
+
id: 'h-levels-filter',
|
|
247
|
+
items: [
|
|
248
|
+
{
|
|
249
|
+
label: 'Function',
|
|
250
|
+
onclick: () => toggleGroupBy(FIELD_FUNCTION_NAME),
|
|
251
|
+
value: FIELD_FUNCTION_NAME,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
label: 'Binary',
|
|
255
|
+
onclick: () => toggleGroupBy(FIELD_MAPPING_FILE),
|
|
256
|
+
value: FIELD_MAPPING_FILE,
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
label: 'Code',
|
|
260
|
+
onclick: () => toggleGroupBy(FIELD_FUNCTION_FILE_NAME),
|
|
261
|
+
value: FIELD_FUNCTION_FILE_NAME,
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
label: 'Address',
|
|
265
|
+
onclick: () => toggleGroupBy(FIELD_LOCATION_ADDRESS),
|
|
266
|
+
value: FIELD_LOCATION_ADDRESS,
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
hide: !!isTableVizOnly,
|
|
270
|
+
icon: 'heroicons-solid:bars-3',
|
|
271
|
+
},
|
|
199
272
|
{
|
|
200
273
|
label: 'Color by',
|
|
201
|
-
id: 'h-
|
|
274
|
+
id: 'h-color-by-filter',
|
|
202
275
|
items: [
|
|
203
276
|
{
|
|
204
277
|
label: 'Binary',
|
|
@@ -214,7 +287,6 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
214
287
|
hide: false,
|
|
215
288
|
icon: 'carbon:color-palette',
|
|
216
289
|
},
|
|
217
|
-
|
|
218
290
|
{
|
|
219
291
|
label: isColorStackLegendEnabled ? 'Hide legend' : 'Show legend',
|
|
220
292
|
onclick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'),
|
|
@@ -222,17 +294,11 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
222
294
|
id: 'h-show-legend-button',
|
|
223
295
|
icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye',
|
|
224
296
|
},
|
|
225
|
-
{
|
|
226
|
-
label: isInvert ? 'Original Call Stack' : 'Invert Call Stack',
|
|
227
|
-
onclick: () => setInvertStack(isInvert ? '' : 'true'),
|
|
228
|
-
hide: false,
|
|
229
|
-
icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending',
|
|
230
|
-
},
|
|
231
297
|
{
|
|
232
298
|
label: isLeftAligned ? 'Right-align function names' : 'Left-align function names',
|
|
233
299
|
onclick: () => setAlignFunctionName(isLeftAligned ? 'right' : 'left'),
|
|
234
300
|
id: 'h-align-function-names',
|
|
235
|
-
hide:
|
|
301
|
+
hide: !!isTableVizOnly,
|
|
236
302
|
icon: isLeftAligned
|
|
237
303
|
? 'ic:outline-align-horizontal-right'
|
|
238
304
|
: 'ic:outline-align-horizontal-left',
|
|
@@ -243,6 +309,42 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
243
309
|
hide: !compareMode,
|
|
244
310
|
icon: isCompareAbsolute ? 'fluent-mdl2:compare' : 'fluent-mdl2:compare-uneven',
|
|
245
311
|
},
|
|
312
|
+
{
|
|
313
|
+
label: 'Highlight matching nodes after filtering',
|
|
314
|
+
hide: !!isTableVizOnly,
|
|
315
|
+
customSubmenu: (
|
|
316
|
+
<SwitchMenuItem
|
|
317
|
+
label="Highlight matching nodes after filtering"
|
|
318
|
+
id="h-highlight-after-filtering"
|
|
319
|
+
userPreferenceDetails={USER_PREFERENCES.HIGHTLIGHT_AFTER_FILTERING}
|
|
320
|
+
/>
|
|
321
|
+
),
|
|
322
|
+
renderAsDiv: true,
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
label: 'Dock Graph MetaInfo',
|
|
326
|
+
hide: !!isTableVizOnly,
|
|
327
|
+
customSubmenu: (
|
|
328
|
+
<SwitchMenuItem
|
|
329
|
+
label="Dock graph tooltip"
|
|
330
|
+
id="h-dock-graph-meta-info"
|
|
331
|
+
userPreferenceDetails={USER_PREFERENCES.GRAPH_METAINFO_DOCKED}
|
|
332
|
+
/>
|
|
333
|
+
),
|
|
334
|
+
renderAsDiv: true,
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
label: 'Highlight similar stacks when hovering over a node',
|
|
338
|
+
hide: !!isTableVizOnly,
|
|
339
|
+
customSubmenu: (
|
|
340
|
+
<SwitchMenuItem
|
|
341
|
+
label="Highlight similar stacks when hovering over a node"
|
|
342
|
+
id="h-highlight-similar-stacks"
|
|
343
|
+
userPreferenceDetails={USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS}
|
|
344
|
+
/>
|
|
345
|
+
),
|
|
346
|
+
renderAsDiv: true,
|
|
347
|
+
},
|
|
246
348
|
{
|
|
247
349
|
label: 'Reset Legend',
|
|
248
350
|
hide: binaryFrameFilter === undefined || binaryFrameFilter.length === 0,
|
|
@@ -279,16 +381,24 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
279
381
|
<Menu>
|
|
280
382
|
{({open, close}) => (
|
|
281
383
|
<>
|
|
282
|
-
<Menu.Button className="
|
|
283
|
-
<
|
|
384
|
+
<Menu.Button className="flex dark:bg-gray-900 dark:border-gray-600 justify-center w-full px-4 py-2 text-sm font-normal text-gray-600 dark:text-gray-200 bg-white rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 border border-gray-200 pr-[1.7rem]">
|
|
385
|
+
<div className="flex items-center gap-2">
|
|
386
|
+
<Icon icon="pajamas:preferences" className="w-4 h-4" />
|
|
387
|
+
|
|
388
|
+
<span>Preferences</span>
|
|
389
|
+
</div>
|
|
284
390
|
|
|
285
391
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400">
|
|
286
392
|
<Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
|
|
287
393
|
</span>
|
|
288
394
|
</Menu.Button>
|
|
289
395
|
{open && (
|
|
290
|
-
<Menu.Items
|
|
291
|
-
|
|
396
|
+
<Menu.Items
|
|
397
|
+
className={cx(
|
|
398
|
+
isTableVizOnly ? 'w-64' : 'w-80',
|
|
399
|
+
'absolute z-30 left-0 mt-2 py-2 origin-top-right bg-white rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none border dark:bg-gray-900 dark:border-gray-600'
|
|
400
|
+
)}
|
|
401
|
+
>
|
|
292
402
|
{menuItems
|
|
293
403
|
.filter(item => item.hide !== undefined && !item.hide)
|
|
294
404
|
.map((item, index) => (
|
|
@@ -301,6 +411,8 @@ const MultiLevelDropdown: React.FC<MultiLevelDropdownProps> = ({onSelect, profil
|
|
|
301
411
|
activeValueForColorBy={
|
|
302
412
|
colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string)
|
|
303
413
|
}
|
|
414
|
+
activeValuesForLevel={groupBy}
|
|
415
|
+
renderAsDiv={item.renderAsDiv}
|
|
304
416
|
/>
|
|
305
417
|
))}
|
|
306
418
|
</Menu.Items>
|
|
@@ -0,0 +1,50 @@
|
|
|
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 {Switch} from '@headlessui/react';
|
|
15
|
+
|
|
16
|
+
import {useUserPreference, type UserPreferenceDetails} from '@parca/hooks';
|
|
17
|
+
|
|
18
|
+
interface SwitchMenuItemProps {
|
|
19
|
+
label: string;
|
|
20
|
+
id: string;
|
|
21
|
+
userPreferenceDetails: UserPreferenceDetails;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function SwitchMenuItem<T>({label, id, userPreferenceDetails}: SwitchMenuItemProps): JSX.Element {
|
|
25
|
+
const [enabledPreference, setEnabledPreference] = useUserPreference<T>(userPreferenceDetails.key);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex items-center justify-between w-full">
|
|
29
|
+
<span>{label}</span>
|
|
30
|
+
<Switch
|
|
31
|
+
id={id}
|
|
32
|
+
checked={enabledPreference as boolean}
|
|
33
|
+
onChange={(checked: boolean) => setEnabledPreference(checked as T)}
|
|
34
|
+
className={`${
|
|
35
|
+
(enabledPreference as boolean) ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-800'
|
|
36
|
+
}
|
|
37
|
+
relative inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`}
|
|
38
|
+
>
|
|
39
|
+
<span className="sr-only">Use setting</span>
|
|
40
|
+
<span
|
|
41
|
+
aria-hidden="true"
|
|
42
|
+
className={`${(enabledPreference as boolean) ? 'translate-x-5' : 'translate-x-0'}
|
|
43
|
+
pointer-events-none inline-block h-[20px] w-[20px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
|
|
44
|
+
/>
|
|
45
|
+
</Switch>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default SwitchMenuItem;
|
|
@@ -24,6 +24,7 @@ import {ProfileSource} from '../../../ProfileSource';
|
|
|
24
24
|
import {useDashboard} from '../../context/DashboardContext';
|
|
25
25
|
import GroupByDropdown from '../ActionButtons/GroupByDropdown';
|
|
26
26
|
import FilterByFunctionButton from '../FilterByFunctionButton';
|
|
27
|
+
import InvertCallStack from '../InvertCallStack';
|
|
27
28
|
import ShareButton from '../ShareButton';
|
|
28
29
|
import ViewSelector from '../ViewSelector';
|
|
29
30
|
import MultiLevelDropdown from './MultiLevelDropdown';
|
|
@@ -168,27 +169,34 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
168
169
|
const {dashboardItems} = useDashboard();
|
|
169
170
|
|
|
170
171
|
const isTableViz = dashboardItems?.includes('table');
|
|
172
|
+
const isTableVizOnly = dashboardItems?.length === 1 && isTableViz;
|
|
171
173
|
const isGraphViz = dashboardItems?.includes('icicle');
|
|
172
174
|
const isSandwichIcicleGraphViz = dashboardItems?.includes('sandwich');
|
|
175
|
+
|
|
176
|
+
const isTableView = isTableVizOnly || isSandwichIcicleGraphViz;
|
|
177
|
+
|
|
173
178
|
const req = profileSource?.QueryRequest();
|
|
174
179
|
if (req !== null && req !== undefined) {
|
|
175
180
|
req.groupBy = {
|
|
176
181
|
fields: groupBy ?? [],
|
|
177
182
|
};
|
|
178
183
|
}
|
|
184
|
+
|
|
179
185
|
return (
|
|
180
186
|
<>
|
|
181
187
|
<div className="flex w-full justify-between items-end">
|
|
182
188
|
<div className="flex gap-3 items-end">
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
{!isTableView && (
|
|
190
|
+
<>
|
|
191
|
+
<GroupByDropdown
|
|
192
|
+
groupBy={groupBy}
|
|
193
|
+
labels={groupByLabels}
|
|
194
|
+
setGroupByLabels={setGroupByLabels}
|
|
195
|
+
/>
|
|
196
|
+
|
|
197
|
+
<InvertCallStack />
|
|
198
|
+
</>
|
|
199
|
+
)}
|
|
192
200
|
|
|
193
201
|
<FilterByFunctionButton />
|
|
194
202
|
|
|
@@ -196,6 +204,14 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
196
204
|
</div>
|
|
197
205
|
<div className="flex gap-3">
|
|
198
206
|
{preferencesModal === true && <UserPreferencesModal />}
|
|
207
|
+
<MultiLevelDropdown
|
|
208
|
+
groupBy={groupBy}
|
|
209
|
+
toggleGroupBy={toggleGroupBy}
|
|
210
|
+
profileType={profileType}
|
|
211
|
+
onSelect={() => {}}
|
|
212
|
+
isTableVizOnly={isTableView}
|
|
213
|
+
/>
|
|
214
|
+
|
|
199
215
|
<ShareButton
|
|
200
216
|
profileSource={profileSource}
|
|
201
217
|
queryClient={queryClient}
|
|
@@ -174,7 +174,7 @@ const DropdownOption = ({option}: {option: DropdownItem}): JSX.Element => {
|
|
|
174
174
|
e.stopPropagation();
|
|
175
175
|
option.innerAction?.onClick();
|
|
176
176
|
}}
|
|
177
|
-
disabled={
|
|
177
|
+
disabled={option.innerAction?.isDisabled}
|
|
178
178
|
>
|
|
179
179
|
{option.innerAction.text}
|
|
180
180
|
{option.innerAction.text === 'Add Panel' && (
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
import {ReactNode} from 'react';
|
|
15
15
|
|
|
16
16
|
import {useParcaContext, useURLState} from '@parca/components';
|
|
17
|
-
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
18
17
|
|
|
19
18
|
import {ProfileSource} from '../../../ProfileSource';
|
|
20
19
|
import Dropdown, {DropdownElement, InnerAction} from './Dropdown';
|
|
@@ -32,8 +31,6 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
32
31
|
);
|
|
33
32
|
const {enableSourcesView, enableSandwichView} = useParcaContext();
|
|
34
33
|
|
|
35
|
-
const [enableicicleCharts] = useUserPreference<boolean>(USER_PREFERENCES.ENABLE_ICICLECHARTS.key);
|
|
36
|
-
|
|
37
34
|
const allItems: Array<{
|
|
38
35
|
key: string;
|
|
39
36
|
label?: string | ReactNode;
|
|
@@ -43,17 +40,7 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
43
40
|
}> = [
|
|
44
41
|
{key: 'table', label: 'Table', canBeSelected: !dashboardItems.includes('table')},
|
|
45
42
|
{key: 'icicle', label: 'icicle', canBeSelected: !dashboardItems.includes('icicle')},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (enableSandwichView === true) {
|
|
49
|
-
allItems.push({
|
|
50
|
-
key: 'sandwich',
|
|
51
|
-
label: 'sandwich',
|
|
52
|
-
canBeSelected: !dashboardItems.includes('sandwich'),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
if (enableicicleCharts) {
|
|
56
|
-
allItems.push({
|
|
43
|
+
{
|
|
57
44
|
key: 'iciclechart',
|
|
58
45
|
label: (
|
|
59
46
|
<span className="relative">
|
|
@@ -67,6 +54,19 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
67
54
|
!dashboardItems.includes('iciclechart') && profileSource?.ProfileType().delta !== true
|
|
68
55
|
? 'Iciclechart is not available for non-delta profiles'
|
|
69
56
|
: undefined,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
if (enableSandwichView === true) {
|
|
61
|
+
allItems.push({
|
|
62
|
+
key: 'sandwich',
|
|
63
|
+
label: (
|
|
64
|
+
<span className="relative">
|
|
65
|
+
Sandwich
|
|
66
|
+
<span className="absolute top-[-2px] text-xs lowercase text-red-500"> alpha</span>
|
|
67
|
+
</span>
|
|
68
|
+
),
|
|
69
|
+
canBeSelected: !dashboardItems.includes('sandwich'),
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -128,6 +128,7 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
128
128
|
setDashboardItems(dashboardItems.filter(v => v !== item.key));
|
|
129
129
|
}
|
|
130
130
|
},
|
|
131
|
+
isDisabled: dashboardItems.length === 1 && dashboardItems.includes('sandwich'),
|
|
131
132
|
};
|
|
132
133
|
};
|
|
133
134
|
|
|
@@ -11,11 +11,17 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useCallback, useState} from 'react';
|
|
14
|
+
import {useCallback, useMemo, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {JSONParser, JSONSerializer, useURLState, useURLStateCustom} from '@parca/components';
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
FIELD_FUNCTION_FILE_NAME,
|
|
20
|
+
FIELD_FUNCTION_NAME,
|
|
21
|
+
FIELD_LABELS,
|
|
22
|
+
FIELD_LOCATION_ADDRESS,
|
|
23
|
+
FIELD_MAPPING_FILE,
|
|
24
|
+
} from '../../ProfileIcicleGraph/IcicleGraphArrow';
|
|
19
25
|
import {CurrentPathFrame} from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
|
|
20
26
|
|
|
21
27
|
export const useVisualizationState = (): {
|
|
@@ -54,6 +60,16 @@ export const useVisualizationState = (): {
|
|
|
54
60
|
'sandwich_function_name'
|
|
55
61
|
);
|
|
56
62
|
|
|
63
|
+
const levelsOfProfiling = useMemo(
|
|
64
|
+
() => [
|
|
65
|
+
FIELD_FUNCTION_NAME,
|
|
66
|
+
FIELD_FUNCTION_FILE_NAME,
|
|
67
|
+
FIELD_LOCATION_ADDRESS,
|
|
68
|
+
FIELD_MAPPING_FILE,
|
|
69
|
+
],
|
|
70
|
+
[]
|
|
71
|
+
);
|
|
72
|
+
|
|
57
73
|
const setGroupBy = useCallback(
|
|
58
74
|
(keys: string[]): void => {
|
|
59
75
|
setStoreGroupBy(keys);
|
|
@@ -63,11 +79,14 @@ export const useVisualizationState = (): {
|
|
|
63
79
|
|
|
64
80
|
const toggleGroupBy = useCallback(
|
|
65
81
|
(key: string): void => {
|
|
66
|
-
groupBy.includes(key)
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
if (groupBy.includes(key)) {
|
|
83
|
+
setGroupBy(groupBy.filter(v => v !== key)); // remove
|
|
84
|
+
} else {
|
|
85
|
+
const filteredGroupBy = groupBy.filter(item => !levelsOfProfiling.includes(item));
|
|
86
|
+
setGroupBy([...filteredGroupBy, key]); // add
|
|
87
|
+
}
|
|
69
88
|
},
|
|
70
|
-
[groupBy, setGroupBy]
|
|
89
|
+
[groupBy, setGroupBy, levelsOfProfiling]
|
|
71
90
|
);
|
|
72
91
|
|
|
73
92
|
const setGroupByLabels = useCallback(
|