@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.
- package/CHANGELOG.md +8 -0
- package/dist/GraphTooltipArrow/Content.js +1 -1
- package/dist/GraphTooltipArrow/index.js +2 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +12 -3
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.js +9 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +1 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -3
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.js +17 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +23 -8
- package/dist/ProfileIcicleGraph/index.d.ts +3 -1
- package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
- package/dist/ProfileIcicleGraph/index.js +6 -4
- package/dist/ProfileSelector/QueryControls.d.ts.map +1 -1
- package/dist/ProfileSelector/QueryControls.js +1 -1
- package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
- package/dist/ProfileView/components/DashboardItems/index.js +1 -1
- 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/index.d.ts +0 -2
- package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +4 -5
- package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +18 -11
- package/dist/ProfileView/context/DashboardContext.d.ts.map +1 -1
- package/dist/ProfileView/context/DashboardContext.js +5 -0
- package/dist/ProfileView/index.d.ts.map +1 -1
- package/dist/ProfileView/index.js +4 -3
- package/dist/Sandwich/components/CalleesSection.d.ts +1 -2
- package/dist/Sandwich/components/CalleesSection.d.ts.map +1 -1
- package/dist/Sandwich/components/CalleesSection.js +2 -6
- package/dist/Sandwich/components/CallersSection.d.ts +4 -2
- package/dist/Sandwich/components/CallersSection.d.ts.map +1 -1
- package/dist/Sandwich/components/CallersSection.js +45 -9
- package/dist/Sandwich/components/TableSection.js +1 -1
- package/dist/Sandwich/index.d.ts +0 -1
- package/dist/Sandwich/index.d.ts.map +1 -1
- package/dist/Sandwich/index.js +27 -79
- package/dist/Table/MoreDropdown.d.ts.map +1 -1
- package/dist/Table/MoreDropdown.js +1 -2
- package/dist/Table/TableContextMenu.d.ts +9 -0
- package/dist/Table/TableContextMenu.d.ts.map +1 -0
- package/dist/Table/TableContextMenu.js +38 -0
- package/dist/Table/TableContextMenuWrapper.d.ts +10 -0
- package/dist/Table/TableContextMenuWrapper.d.ts.map +1 -0
- package/dist/Table/TableContextMenuWrapper.js +30 -0
- package/dist/Table/hooks/useTableConfiguration.d.ts.map +1 -1
- package/dist/Table/hooks/useTableConfiguration.js +2 -20
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +65 -5
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/src/GraphTooltipArrow/Content.tsx +3 -3
- package/src/GraphTooltipArrow/index.tsx +2 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +19 -3
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenuWrapper.tsx +10 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +19 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/MemoizedTooltip.tsx +20 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +40 -6
- package/src/ProfileIcicleGraph/index.tsx +20 -2
- package/src/ProfileSelector/QueryControls.tsx +1 -0
- package/src/ProfileView/components/DashboardItems/index.tsx +0 -1
- package/src/ProfileView/components/ShareButton/index.tsx +9 -3
- package/src/ProfileView/components/Toolbars/index.tsx +7 -23
- package/src/ProfileView/components/ViewSelector/index.tsx +20 -11
- package/src/ProfileView/context/DashboardContext.tsx +6 -0
- package/src/ProfileView/index.tsx +12 -4
- package/src/Sandwich/components/CalleesSection.tsx +1 -7
- package/src/Sandwich/components/CallersSection.tsx +92 -35
- package/src/Sandwich/components/TableSection.tsx +2 -2
- package/src/Sandwich/index.tsx +20 -107
- package/src/Table/MoreDropdown.tsx +1 -2
- package/src/Table/TableContextMenu.tsx +70 -0
- package/src/Table/TableContextMenuWrapper.tsx +48 -0
- package/src/Table/hooks/useTableConfiguration.tsx +2 -25
- package/src/Table/index.tsx +84 -5
package/src/Sandwich/index.tsx
CHANGED
|
@@ -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, {
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
274
|
-
|
|
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"> 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 {
|
|
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
|
|
203
|
+
}, [unit, total, filtered, columnHelper]);
|
|
227
204
|
|
|
228
205
|
const initialSorting = useMemo(() => {
|
|
229
206
|
return [
|
package/src/Table/index.tsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
<
|
|
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}
|