@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  3. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +2 -2
  4. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts +0 -1
  5. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts.map +1 -1
  6. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +4 -141
  7. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts +8 -0
  8. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -0
  9. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +57 -0
  10. package/dist/ProfileView/components/InvertCallStack/index.d.ts +3 -0
  11. package/dist/ProfileView/components/InvertCallStack/index.d.ts.map +1 -0
  12. package/dist/ProfileView/components/InvertCallStack/index.js +21 -0
  13. package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
  14. package/dist/ProfileView/components/ShareButton/index.js +1 -1
  15. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts +3 -0
  16. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.d.ts.map +1 -1
  17. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +78 -20
  18. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.d.ts +9 -0
  19. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.d.ts.map +1 -0
  20. package/dist/ProfileView/components/Toolbars/SwitchMenuItem.js +22 -0
  21. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  22. package/dist/ProfileView/components/Toolbars/index.js +4 -1
  23. package/dist/ProfileView/components/ViewSelector/Dropdown.js +1 -1
  24. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  25. package/dist/ProfileView/components/ViewSelector/index.js +9 -12
  26. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  27. package/dist/ProfileView/hooks/useVisualizationState.js +16 -6
  28. package/dist/styles.css +1 -1
  29. package/package.json +2 -2
  30. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +7 -2
  31. package/src/ProfileView/components/ActionButtons/GroupByDropdown.tsx +7 -323
  32. package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +92 -0
  33. package/src/ProfileView/components/InvertCallStack/index.tsx +34 -0
  34. package/src/ProfileView/components/ShareButton/index.tsx +8 -4
  35. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +134 -22
  36. package/src/ProfileView/components/Toolbars/SwitchMenuItem.tsx +50 -0
  37. package/src/ProfileView/components/Toolbars/index.tsx +25 -9
  38. package/src/ProfileView/components/ViewSelector/Dropdown.tsx +1 -1
  39. package/src/ProfileView/components/ViewSelector/index.tsx +15 -14
  40. 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 {FIELD_FUNCTION_NAME} from '../../../ProfileIcicleGraph/IcicleGraphArrow';
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 className="absolute left-full top-0 w-56 mt-0 origin-top-right bg-white border border-gray-200 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:bg-gray-900 ml-1 dark:border-gray-600">
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> = ({onSelect, profileType}) => {
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-solor-by-filter',
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: false,
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="inline-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]">
283
- <span>More</span>
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 className="absolute z-30 left-0 w-64 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">
291
- <span className="text-xs text-gray-400 capitalize px-4 py-3">actions</span>
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
- <GroupByDropdown
185
- groupBy={groupBy}
186
- toggleGroupBy={toggleGroupBy}
187
- labels={groupByLabels}
188
- setGroupByLabels={setGroupByLabels}
189
- />
190
- <MultiLevelDropdown profileType={profileType} onSelect={() => {}} />
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={disabled || option.innerAction.isDisabled}
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">&nbsp;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 {FIELD_FUNCTION_NAME, FIELD_LABELS} from '../../ProfileIcicleGraph/IcicleGraphArrow';
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
- ? setGroupBy(groupBy.filter(v => v !== key)) // remove
68
- : setGroupBy([...groupBy, key]); // add
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(