@parca/profile 0.19.11 → 0.19.13

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 (82) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.js +1 -1
  3. package/dist/GraphTooltipArrow/index.js +2 -2
  4. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
  5. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +12 -3
  6. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -1
  7. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
  8. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js +9 -1
  9. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +1 -0
  10. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
  11. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -3
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +17 -2
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +23 -8
  17. package/dist/ProfileIcicleGraph/index.d.ts +3 -1
  18. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  19. package/dist/ProfileIcicleGraph/index.js +6 -4
  20. package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
  21. package/dist/ProfileSelector/QueryControls.js +1 -1
  22. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  23. package/dist/ProfileView/components/DashboardItems/index.js +1 -1
  24. package/dist/ProfileView/components/ShareButton/index.d.ts.map +1 -1
  25. package/dist/ProfileView/components/ShareButton/index.js +1 -1
  26. package/dist/ProfileView/components/Toolbars/index.d.ts +0 -2
  27. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  28. package/dist/ProfileView/components/Toolbars/index.js +4 -5
  29. package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
  30. package/dist/ProfileView/components/ViewSelector/index.js +18 -11
  31. package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
  32. package/dist/ProfileView/context/DashboardContext.js +5 -0
  33. package/dist/ProfileView/index.d.ts.map +1 -1
  34. package/dist/ProfileView/index.js +4 -3
  35. package/dist/Sandwich/components/CalleesSection.d.ts +1 -2
  36. package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
  37. package/dist/Sandwich/components/CalleesSection.js +2 -6
  38. package/dist/Sandwich/components/CallersSection.d.ts +4 -2
  39. package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
  40. package/dist/Sandwich/components/CallersSection.js +45 -9
  41. package/dist/Sandwich/components/TableSection.js +1 -1
  42. package/dist/Sandwich/index.d.ts +0 -1
  43. package/dist/Sandwich/index.d.ts.map +1 -1
  44. package/dist/Sandwich/index.js +27 -79
  45. package/dist/Table/MoreDropdown.d.ts.map +1 -1
  46. package/dist/Table/MoreDropdown.js +1 -2
  47. package/dist/Table/TableContextMenu.d.ts +9 -0
  48. package/dist/Table/TableContextMenu.d.ts.map +1 -0
  49. package/dist/Table/TableContextMenu.js +38 -0
  50. package/dist/Table/TableContextMenuWrapper.d.ts +10 -0
  51. package/dist/Table/TableContextMenuWrapper.d.ts.map +1 -0
  52. package/dist/Table/TableContextMenuWrapper.js +30 -0
  53. package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
  54. package/dist/Table/hooks/useTableConfiguration.js +2 -20
  55. package/dist/Table/index.d.ts.map +1 -1
  56. package/dist/Table/index.js +65 -5
  57. package/dist/styles.css +1 -1
  58. package/package.json +3 -3
  59. package/src/GraphTooltipArrow/Content.tsx +3 -3
  60. package/src/GraphTooltipArrow/index.tsx +2 -2
  61. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -3
  62. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +10 -2
  63. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +19 -2
  64. package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +20 -2
  65. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +40 -6
  66. package/src/ProfileIcicleGraph/index.tsx +20 -2
  67. package/src/ProfileSelector/QueryControls.tsx +1 -0
  68. package/src/ProfileView/components/DashboardItems/index.tsx +0 -1
  69. package/src/ProfileView/components/ShareButton/index.tsx +9 -3
  70. package/src/ProfileView/components/Toolbars/index.tsx +7 -23
  71. package/src/ProfileView/components/ViewSelector/index.tsx +20 -11
  72. package/src/ProfileView/context/DashboardContext.tsx +6 -0
  73. package/src/ProfileView/index.tsx +12 -4
  74. package/src/Sandwich/components/CalleesSection.tsx +1 -7
  75. package/src/Sandwich/components/CallersSection.tsx +92 -35
  76. package/src/Sandwich/components/TableSection.tsx +2 -2
  77. package/src/Sandwich/index.tsx +20 -107
  78. package/src/Table/MoreDropdown.tsx +1 -2
  79. package/src/Table/TableContextMenu.tsx +70 -0
  80. package/src/Table/TableContextMenuWrapper.tsx +48 -0
  81. package/src/Table/hooks/useTableConfiguration.tsx +2 -25
  82. package/src/Table/index.tsx +84 -5
@@ -11,32 +11,28 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
14
+ import React, {useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {type Row as TableRow} from '@tanstack/table-core';
17
17
  import {tableFromIPC} from 'apache-arrow';
18
18
  import {AnimatePresence, motion} from 'framer-motion';
19
19
 
20
20
  import {QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
21
- import {TableSkeleton, useParcaContext, useURLState} from '@parca/components';
21
+ import {useParcaContext, useURLState} from '@parca/components';
22
22
  import {useCurrentColorProfile} from '@parca/hooks';
23
23
  import {ProfileType} from '@parca/parser';
24
- import {isSearchMatch} from '@parca/utilities';
25
24
 
26
25
  import useMappingList, {
27
26
  useFilenamesList,
28
27
  } from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
29
28
  import {ProfileSource} from '../ProfileSource';
30
- import {useProfileViewContext} from '../ProfileView/context/ProfileViewContext';
29
+ import {useDashboard} from '../ProfileView/context/DashboardContext';
31
30
  import {useVisualizationState} from '../ProfileView/hooks/useVisualizationState';
32
31
  import {FIELD_FUNCTION_NAME, Row} from '../Table';
33
32
  import {useColorManagement} from '../Table/hooks/useColorManagement';
34
- import {useTableConfiguration} from '../Table/hooks/useTableConfiguration';
35
- import {type DataRow} from '../Table/utils/functions';
36
33
  import {useQuery} from '../useQuery';
37
34
  import {CalleesSection} from './components/CalleesSection';
38
35
  import {CallersSection} from './components/CallersSection';
39
- import {TableSection} from './components/TableSection';
40
36
  import {processRowData} from './utils/processRowData';
41
37
 
42
38
  interface Props {
@@ -45,7 +41,6 @@ interface Props {
45
41
  filtered: bigint;
46
42
  profileType?: ProfileType;
47
43
  loading: boolean;
48
- isHalfScreen: boolean;
49
44
  unit?: string;
50
45
  metadataMappingFiles?: string[];
51
46
  queryClient?: QueryServiceClient;
@@ -54,30 +49,26 @@ interface Props {
54
49
 
55
50
  const Sandwich = React.memo(function Sandwich({
56
51
  data,
57
- total,
58
52
  filtered,
59
53
  profileType,
60
54
  loading,
61
- isHalfScreen,
62
55
  unit,
63
56
  metadataMappingFiles,
64
57
  queryClient,
65
58
  profileSource,
66
59
  }: Props): React.JSX.Element {
67
60
  const currentColorProfile = useCurrentColorProfile();
61
+ const {dashboardItems} = useDashboard();
62
+ const [sandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
68
63
 
69
- const [sandwichFunctionName, setSandwichFunctionName] = useURLState<string | undefined>(
70
- 'sandwich_function_name'
71
- );
72
64
  const {isDarkMode} = useParcaContext();
73
65
  const [selectedRow, setSelectedRow] = useState<TableRow<Row> | null>(null);
74
66
  const callersRef = React.useRef<HTMLDivElement | null>(null);
75
67
  const calleesRef = React.useRef<HTMLDivElement | null>(null);
68
+ const [isExpanded, setIsExpanded] = useState(false);
69
+ const defaultMaxFrames = 10;
76
70
 
77
71
  const callersCalleesContainerRef = useRef<HTMLDivElement | null>(null);
78
- const [tableHeight, setTableHeight] = useState<number | undefined>(undefined);
79
-
80
- const {compareMode} = useProfileViewContext();
81
72
 
82
73
  const {colorBy, setColorBy, curPathArrow, setCurPathArrow} = useVisualizationState();
83
74
 
@@ -160,15 +151,6 @@ const Sandwich = React.memo(function Sandwich({
160
151
 
161
152
  unit = useMemo(() => unit ?? profileType?.sampleUnit ?? '', [unit, profileType?.sampleUnit]);
162
153
 
163
- const tableConfig = useTableConfiguration({
164
- unit,
165
- total,
166
- filtered,
167
- compareMode,
168
- });
169
-
170
- const {columns, initialSorting, columnVisibility} = tableConfig;
171
-
172
154
  const rows = useMemo(() => {
173
155
  if (table == null || table.numRows === 0) {
174
156
  return [];
@@ -194,71 +176,6 @@ const Sandwich = React.memo(function Sandwich({
194
176
  }
195
177
  }, [sandwichFunctionName, rows, selectedRow]);
196
178
 
197
- // Update table height based on callers/callees container height
198
- useEffect(() => {
199
- const updateTableHeight = (): void => {
200
- if (callersCalleesContainerRef.current != null) {
201
- const containerHeight = callersCalleesContainerRef.current.getBoundingClientRect().height;
202
- setTableHeight(containerHeight);
203
- }
204
- };
205
-
206
- // Initial measurement
207
- updateTableHeight();
208
-
209
- // Update on window resize
210
- window.addEventListener('resize', updateTableHeight);
211
-
212
- // Use ResizeObserver if available for more accurate updates
213
- let resizeObserver: ResizeObserver | null = null;
214
- if (callersCalleesContainerRef.current != null && 'ResizeObserver' in window) {
215
- resizeObserver = new ResizeObserver(updateTableHeight);
216
- resizeObserver.observe(callersCalleesContainerRef.current);
217
- }
218
-
219
- return () => {
220
- window.removeEventListener('resize', updateTableHeight);
221
- if (resizeObserver != null) {
222
- resizeObserver.disconnect();
223
- }
224
- };
225
- }, [sandwichFunctionName, callersFlamegraphResponse, calleesFlamegraphResponse]);
226
-
227
- const onRowClick = useCallback(
228
- (row: DataRow) => {
229
- setSelectedRow(row as unknown as TableRow<Row>);
230
- setSandwichFunctionName(row.name.trim());
231
- },
232
- [setSandwichFunctionName]
233
- );
234
-
235
- const enableHighlighting = useMemo(() => {
236
- return sandwichFunctionName != null && sandwichFunctionName?.length > 0;
237
- }, [sandwichFunctionName]);
238
-
239
- const shouldHighlightRow = useCallback(
240
- (row: Row) => {
241
- if (!('name' in row)) {
242
- return false;
243
- }
244
- const name = row.name;
245
- return isSearchMatch(sandwichFunctionName as string, name);
246
- },
247
- [sandwichFunctionName]
248
- );
249
-
250
- if (loading) {
251
- return (
252
- <div className="overflow-clip h-[700px] min-h-[700px]">
253
- <TableSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
254
- </div>
255
- );
256
- }
257
-
258
- if (rows.length === 0) {
259
- return <div className="mx-auto text-center">Profile has no samples</div>;
260
- }
261
-
262
179
  return (
263
180
  <section className="flex flex-row h-full w-full">
264
181
  <AnimatePresence>
@@ -270,24 +187,10 @@ const Sandwich = React.memo(function Sandwich({
270
187
  transition={{duration: 0.5}}
271
188
  >
272
189
  <div className="relative flex flex-row">
273
- <TableSection
274
- rows={rows}
275
- columns={columns}
276
- initialSorting={initialSorting}
277
- columnVisibility={columnVisibility}
278
- selectedRow={selectedRow}
279
- onRowClick={onRowClick}
280
- shouldHighlightRow={shouldHighlightRow}
281
- enableHighlighting={enableHighlighting}
282
- height={tableHeight}
283
- sandwichFunctionName={sandwichFunctionName}
284
- />
285
-
286
- {sandwichFunctionName !== undefined && (
287
- <div className="w-[50%] flex flex-col" ref={callersCalleesContainerRef}>
190
+ {sandwichFunctionName !== undefined ? (
191
+ <div className="w-full flex flex-col" ref={callersCalleesContainerRef}>
288
192
  <CallersSection
289
193
  callersRef={callersRef}
290
- isHalfScreen={isHalfScreen}
291
194
  callersFlamegraphResponse={
292
195
  callersFlamegraphResponse?.report.oneofKind === 'flamegraphArrow'
293
196
  ? {
@@ -306,11 +209,13 @@ const Sandwich = React.memo(function Sandwich({
306
209
  curPathArrow={curPathArrow}
307
210
  setCurPathArrow={setCurPathArrow}
308
211
  metadataMappingFiles={metadataMappingFiles}
212
+ isExpanded={isExpanded}
213
+ setIsExpanded={setIsExpanded}
214
+ defaultMaxFrames={defaultMaxFrames}
309
215
  />
310
216
  <div className="h-4" />
311
217
  <CalleesSection
312
218
  calleesRef={calleesRef}
313
- isHalfScreen={isHalfScreen}
314
219
  calleesFlamegraphResponse={
315
220
  calleesFlamegraphResponse?.report.oneofKind === 'flamegraphArrow'
316
221
  ? {
@@ -331,6 +236,14 @@ const Sandwich = React.memo(function Sandwich({
331
236
  metadataMappingFiles={metadataMappingFiles}
332
237
  />
333
238
  </div>
239
+ ) : (
240
+ <div className="items-center justify-center flex h-full w-full">
241
+ <p className="text-sm">
242
+ {dashboardItems.includes('table')
243
+ ? 'Please select a function to view its callers and callees.'
244
+ : 'Use the right-click menu on the flame graph to choose a function to view its callers and callees.'}
245
+ </p>
246
+ </div>
334
247
  )}
335
248
  </div>
336
249
  </motion.div>
@@ -18,7 +18,6 @@ import {useParcaContext, useURLState} from '@parca/components';
18
18
 
19
19
  const MoreDropdown = ({functionName}: {functionName: string}): React.JSX.Element | null => {
20
20
  const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
21
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
22
21
  const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
23
22
  alwaysReturnArray: true,
24
23
  });
@@ -26,7 +25,7 @@ const MoreDropdown = ({functionName}: {functionName: string}): React.JSX.Element
26
25
 
27
26
  const onSandwichViewSelect = (): void => {
28
27
  setSandwichFunctionName(functionName.trim());
29
- setDashboardItems(['sandwich']);
28
+ setDashboardItems([...dashboardItems, 'sandwich']);
30
29
  };
31
30
 
32
31
  const menuItems: Array<{label: string; action: () => void}> = [];
@@ -0,0 +1,70 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {Icon} from '@iconify/react';
15
+ import cx from 'classnames';
16
+ import {Item, Menu} from 'react-contexify';
17
+
18
+ import 'react-contexify/dist/ReactContexify.css';
19
+
20
+ import {useParcaContext, useURLState} from '@parca/components';
21
+
22
+ import {type Row} from '.';
23
+
24
+ interface TableContextMenuProps {
25
+ menuId: string;
26
+ row: Row | null;
27
+ }
28
+
29
+ const TableContextMenu = ({menuId, row}: TableContextMenuProps): React.JSX.Element => {
30
+ const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
31
+ const [dashboardItems, setDashboardItems] = useURLState<string[]>('dashboard_items', {
32
+ alwaysReturnArray: true,
33
+ });
34
+ const {enableSandwichView, isDarkMode} = useParcaContext();
35
+
36
+ const onSandwichViewSelect = (): void => {
37
+ if (row?.name != null && row.name.length > 0) {
38
+ setSandwichFunctionName(row.name.trim());
39
+ if (!dashboardItems.includes('sandwich')) {
40
+ setDashboardItems([...dashboardItems, 'sandwich']);
41
+ }
42
+ }
43
+ };
44
+
45
+ const isMenuDisabled = row === null || enableSandwichView !== true;
46
+
47
+ return (
48
+ <Menu
49
+ id={menuId}
50
+ theme={isDarkMode ? 'dark' : ''}
51
+ className={cx(
52
+ dashboardItems.includes('sandwich') ? 'min-w-[350px] w-[350px]' : 'min-w-[260px] w-[260px]'
53
+ )}
54
+ >
55
+ <Item id="sandwich-view" onClick={onSandwichViewSelect} disabled={isMenuDisabled}>
56
+ <div className="flex w-full items-center gap-2">
57
+ <Icon icon="tdesign:sandwich-filled" />
58
+ <div className="relative">
59
+ {dashboardItems.includes('sandwich')
60
+ ? 'Focus sandwich on this frame.'
61
+ : 'Show in sandwich'}
62
+ <span className="absolute top-[-2px] text-xs lowercase text-red-500">&nbsp;alpha</span>
63
+ </div>
64
+ </div>
65
+ </Item>
66
+ </Menu>
67
+ );
68
+ };
69
+
70
+ export default TableContextMenu;
@@ -0,0 +1,48 @@
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 {forwardRef, useImperativeHandle, useState} from 'react';
15
+
16
+ import {type Row} from '.';
17
+ import TableContextMenu from './TableContextMenu';
18
+
19
+ interface TableContextMenuWrapperProps {
20
+ menuId: string;
21
+ }
22
+
23
+ export interface TableContextMenuWrapperRef {
24
+ setRow: (row: Row | null, callback?: () => void) => void;
25
+ }
26
+
27
+ const TableContextMenuWrapper = forwardRef<
28
+ TableContextMenuWrapperRef,
29
+ TableContextMenuWrapperProps
30
+ >(({menuId}, ref) => {
31
+ const [row, setRow] = useState<Row | null>(null);
32
+
33
+ useImperativeHandle(ref, () => ({
34
+ setRow: (newRow: Row | null, callback?: () => void) => {
35
+ setRow(newRow);
36
+ // Execute callback after state update using requestAnimationFrame
37
+ if (callback != null) {
38
+ requestAnimationFrame(callback);
39
+ }
40
+ },
41
+ }));
42
+
43
+ return <TableContextMenu menuId={menuId} row={row} />;
44
+ });
45
+
46
+ TableContextMenuWrapper.displayName = 'TableContextMenuWrapper';
47
+
48
+ export default TableContextMenuWrapper;
@@ -15,12 +15,11 @@ import {useEffect, useMemo, useState} from 'react';
15
15
 
16
16
  import {createColumnHelper, type ColumnDef} from '@tanstack/table-core';
17
17
 
18
- import {useParcaContext, useURLState} from '@parca/components';
18
+ import {useURLState} from '@parca/components';
19
19
  import {valueFormatter} from '@parca/utilities';
20
20
 
21
21
  import {type Row} from '..';
22
22
  import {ColorCell} from '../ColorCell';
23
- import MoreDropdown from '../MoreDropdown';
24
23
  import {addPlusSign, ratioString, type ColumnName} from '../utils/functions';
25
24
 
26
25
  interface UseTableConfigurationProps {
@@ -47,10 +46,6 @@ export function useTableConfiguration({
47
46
  const [tableColumns] = useURLState<string[]>('table_columns', {
48
47
  alwaysReturnArray: true,
49
48
  });
50
- const [dashboardItems] = useURLState<string[]>('dashboard_items', {
51
- alwaysReturnArray: true,
52
- });
53
- const {enableSandwichView} = useParcaContext();
54
49
 
55
50
  const [columnVisibility, setColumnVisibility] = useState(() => {
56
51
  return {
@@ -204,26 +199,8 @@ export function useTableConfiguration({
204
199
  }),
205
200
  ];
206
201
 
207
- if (
208
- dashboardItems.length === 1 &&
209
- dashboardItems[0] === 'table' &&
210
- enableSandwichView === true
211
- ) {
212
- baseColumns.unshift(
213
- columnHelper.accessor('moreActions', {
214
- id: 'moreActions',
215
- header: '',
216
- cell: info => {
217
- return <MoreDropdown functionName={info.row.original.name} />;
218
- },
219
- size: 10,
220
- enableSorting: false,
221
- })
222
- );
223
- }
224
-
225
202
  return baseColumns;
226
- }, [unit, total, filtered, columnHelper, dashboardItems, enableSandwichView]);
203
+ }, [unit, total, filtered, columnHelper]);
227
204
 
228
205
  const initialSorting = useMemo(() => {
229
206
  return [
@@ -11,10 +11,11 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useCallback, useEffect, useMemo, useState} from 'react';
14
+ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
15
15
 
16
16
  import {tableFromIPC} from 'apache-arrow';
17
17
  import {AnimatePresence, motion} from 'framer-motion';
18
+ import {useContextMenu} from 'react-contexify';
18
19
 
19
20
  import {
20
21
  Table as TableComponent,
@@ -30,6 +31,7 @@ import useMappingList, {
30
31
  useFilenamesList,
31
32
  } from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
32
33
  import {useProfileViewContext} from '../ProfileView/context/ProfileViewContext';
34
+ import TableContextMenuWrapper, {TableContextMenuWrapperRef} from './TableContextMenuWrapper';
33
35
  import {useColorManagement} from './hooks/useColorManagement';
34
36
  import {useTableConfiguration} from './hooks/useTableConfiguration';
35
37
  import {DataRow, ROW_HEIGHT, RowName, getRowColor} from './utils/functions';
@@ -78,13 +80,19 @@ export const Table = React.memo(function Table({
78
80
  const [dashboardItems] = useURLState<string[]>('dashboard_items', {
79
81
  alwaysReturnArray: true,
80
82
  });
81
-
83
+ const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
82
84
  const [colorBy, setColorBy] = useURLState('color_by');
83
85
  const {isDarkMode} = useParcaContext();
84
86
  const [scrollToIndex, setScrollToIndex] = useState<number | undefined>(undefined);
85
87
 
86
88
  const {compareMode} = useProfileViewContext();
87
89
 
90
+ const MENU_ID = 'table-context-menu';
91
+ const contextMenuRef = useRef<TableContextMenuWrapperRef>(null);
92
+ const {show} = useContextMenu({
93
+ id: MENU_ID,
94
+ });
95
+
88
96
  const table = useMemo(() => {
89
97
  if (loading || data == null) {
90
98
  return null;
@@ -130,9 +138,13 @@ export const Table = React.memo(function Table({
130
138
 
131
139
  const selectSpan = useCallback(
132
140
  (span: string): void => {
133
- setSearchString(span.trim());
141
+ if (dashboardItems.includes('icicle')) {
142
+ setSearchString(span.trim());
143
+ } else {
144
+ setSandwichFunctionName(span.trim());
145
+ }
134
146
  },
135
- [setSearchString]
147
+ [setSearchString, setSandwichFunctionName, dashboardItems]
136
148
  );
137
149
 
138
150
  const onRowClick = useCallback(
@@ -213,6 +225,72 @@ export const Table = React.memo(function Table({
213
225
  return rows;
214
226
  }, [table, colorByColors, colorBy]);
215
227
 
228
+ const handleTableContextMenu = useCallback(
229
+ (e: React.MouseEvent) => {
230
+ e.preventDefault();
231
+
232
+ // Find the closest table row element
233
+ const target = e.target as Element;
234
+ const rowElement = target.closest('tr');
235
+
236
+ if (rowElement !== null) {
237
+ // Look for a data attribute that might contain the actual row ID
238
+ const rowId = rowElement.getAttribute('data-row-id') ?? rowElement.getAttribute('data-id');
239
+
240
+ if (rowId != null && rowId.length > 0) {
241
+ // Find the row by ID
242
+ const actualRowIndex = parseInt(rowId, 10);
243
+
244
+ if (actualRowIndex >= 0 && actualRowIndex < rows.length) {
245
+ const row = rows[actualRowIndex];
246
+
247
+ contextMenuRef.current?.setRow(row, () => {
248
+ show({
249
+ event: e,
250
+ });
251
+ });
252
+ return;
253
+ }
254
+ }
255
+
256
+ // Fallback: try to find row by matching text content
257
+ const nameCell = rowElement.querySelector('td:last-child'); // Name is usually the last column
258
+ if (nameCell !== null) {
259
+ const cellText = nameCell.textContent?.trim();
260
+
261
+ if (cellText != null && cellText.length > 0) {
262
+ // First try exact match
263
+ let matchingRow = rows.find(row => row.name === cellText);
264
+
265
+ // If no exact match, try partial match (in case of truncation)
266
+ if (matchingRow == null) {
267
+ matchingRow = rows.find(
268
+ row => row.name.includes(cellText) || cellText.includes(row.name)
269
+ );
270
+ }
271
+
272
+ // If still no match, try matching the end of the name (for cases like package.function)
273
+ if (matchingRow == null) {
274
+ matchingRow = rows.find(
275
+ row =>
276
+ row.name.endsWith(cellText) || cellText.endsWith(row.name.split('.').pop() ?? '')
277
+ );
278
+ }
279
+
280
+ if (matchingRow != null) {
281
+ contextMenuRef.current?.setRow(matchingRow, () => {
282
+ show({
283
+ event: e,
284
+ });
285
+ });
286
+ }
287
+ }
288
+ }
289
+ }
290
+ },
291
+ [rows, show]
292
+ );
293
+
216
294
  useEffect(() => {
217
295
  setTimeout(() => {
218
296
  if (currentSearchString == null || rows.length === 0) return;
@@ -249,7 +327,8 @@ export const Table = React.memo(function Table({
249
327
  transition={{duration: 0.5}}
250
328
  >
251
329
  <div className="relative">
252
- <div className="font-robotoMono h-[80vh] w-full">
330
+ <TableContextMenuWrapper ref={contextMenuRef} menuId={MENU_ID} />
331
+ <div className="font-robotoMono h-[80vh] w-full" onContextMenu={handleTableContextMenu}>
253
332
  <TableComponent
254
333
  data={rows}
255
334
  columns={columns}