1313
1414import { Icon } from '@iconify/react' ;
1515import cx from 'classnames' ;
16- import { Item , Menu } from 'react-contexify' ;
16+ import { Item , Menu , Submenu } from 'react-contexify' ;
1717
1818import 'react-contexify/dist/ReactContexify.css' ;
1919
2020import { useParcaContext , useURLState } from '@parca/components' ;
21+ import { valueFormatter } from '@parca/utilities' ;
2122
2223import { type Row } from '.' ;
24+ import { getTextForCumulative } from '../ProfileFlameGraph/FlameGraphArrow/utils' ;
25+ import { truncateString } from '../utils' ;
26+ import { type ColumnName } from './utils/functions' ;
2327
2428interface TableContextMenuProps {
2529 menuId : string ;
2630 row : Row | null ;
31+ unit ?: string ;
32+ total ?: bigint ;
33+ totalUnfiltered ?: bigint ;
34+ columnVisibility ?: Record < ColumnName , boolean > ;
2735}
2836
29- const TableContextMenu = ( { menuId, row} : TableContextMenuProps ) : React . JSX . Element => {
37+ const TableContextMenu = ( {
38+ menuId,
39+ row,
40+ unit,
41+ total,
42+ totalUnfiltered,
43+ columnVisibility,
44+ } : TableContextMenuProps ) : React . JSX . Element => {
3045 const [ _ , setSandwichFunctionName ] = useURLState < string | undefined > ( 'sandwich_function_name' ) ;
3146 const [ dashboardItems , setDashboardItems ] = useURLState < string [ ] > ( 'dashboard_items' , {
3247 alwaysReturnArray : true ,
@@ -42,6 +57,133 @@ const TableContextMenu = ({menuId, row}: TableContextMenuProps): React.JSX.Eleme
4257 }
4358 } ;
4459
60+ const handleCopyItem = ( text : string ) : void => {
61+ void navigator . clipboard . writeText ( text ) ;
62+ } ;
63+
64+ const isColumnVisible = ( columnName : ColumnName ) : boolean => {
65+ return columnVisibility ?. [ columnName ] ?? true ;
66+ } ;
67+
68+ const valuesToCopy =
69+ row !== null
70+ ? [
71+ ...( isColumnVisible ( 'flat' )
72+ ? [
73+ {
74+ id : 'Flat' ,
75+ value :
76+ total !== null &&
77+ total !== undefined &&
78+ totalUnfiltered !== null &&
79+ totalUnfiltered !== undefined
80+ ? getTextForCumulative ( row . flat , total , totalUnfiltered , unit ?? '' )
81+ : valueFormatter ( row . flat , unit ?? '' , 1 ) ,
82+ } ,
83+ ]
84+ : [ ] ) ,
85+ ...( isColumnVisible ( 'flatPercentage' )
86+ ? [
87+ {
88+ id : 'Flat (%)' ,
89+ value :
90+ total !== null &&
91+ total !== undefined &&
92+ totalUnfiltered !== null &&
93+ totalUnfiltered !== undefined
94+ ? getTextForCumulative ( row . flat , total , totalUnfiltered , unit ?? '' )
95+ : valueFormatter ( row . flat , unit ?? '' , 1 ) ,
96+ } ,
97+ ]
98+ : [ ] ) ,
99+ ...( isColumnVisible ( 'flatDiff' )
100+ ? [
101+ {
102+ id : 'Flat Diff' ,
103+ value : row . flatDiff !== 0n ? valueFormatter ( row . flatDiff , unit ?? '' , 1 ) : '' ,
104+ } ,
105+ ]
106+ : [ ] ) ,
107+ ...( isColumnVisible ( 'flatDiffPercentage' )
108+ ? [
109+ {
110+ id : 'Flat Diff (%)' ,
111+ value : row . flatDiff !== 0n ? valueFormatter ( row . flatDiff , unit ?? '' , 1 ) : '' ,
112+ } ,
113+ ]
114+ : [ ] ) ,
115+ ...( isColumnVisible ( 'cumulative' )
116+ ? [
117+ {
118+ id : 'Cumulative' ,
119+ value :
120+ total !== null &&
121+ total !== undefined &&
122+ totalUnfiltered !== null &&
123+ totalUnfiltered !== undefined
124+ ? getTextForCumulative ( row . cumulative , total , totalUnfiltered , unit ?? '' )
125+ : valueFormatter ( row . cumulative , unit ?? '' , 1 ) ,
126+ } ,
127+ ]
128+ : [ ] ) ,
129+ ...( isColumnVisible ( 'cumulativePercentage' )
130+ ? [
131+ {
132+ id : 'Cumulative (%)' ,
133+ value :
134+ total !== null &&
135+ total !== undefined &&
136+ totalUnfiltered !== null &&
137+ totalUnfiltered !== undefined
138+ ? getTextForCumulative ( row . cumulative , total , totalUnfiltered , unit ?? '' )
139+ : valueFormatter ( row . cumulative , unit ?? '' , 1 ) ,
140+ } ,
141+ ]
142+ : [ ] ) ,
143+ ...( isColumnVisible ( 'cumulativeDiff' )
144+ ? [
145+ {
146+ id : 'Cumulative Diff' ,
147+ value :
148+ row . cumulativeDiff !== 0n
149+ ? valueFormatter ( row . cumulativeDiff , unit ?? '' , 1 )
150+ : '' ,
151+ } ,
152+ ]
153+ : [ ] ) ,
154+ ...( isColumnVisible ( 'cumulativeDiffPercentage' )
155+ ? [
156+ {
157+ id : 'Cumulative Diff (%)' ,
158+ value :
159+ row . cumulativeDiff !== 0n
160+ ? valueFormatter ( row . cumulativeDiff , unit ?? '' , 1 )
161+ : '' ,
162+ } ,
163+ ]
164+ : [ ] ) ,
165+ ...( isColumnVisible ( 'name' )
166+ ? [
167+ {
168+ id : 'Name' ,
169+ value : row . name ?? '' ,
170+ } ,
171+ ]
172+ : [ ] ) ,
173+ ...( isColumnVisible ( 'functionSystemName' )
174+ ? [ { id : 'Function System Name' , value : row . functionSystemName ?? '' } ]
175+ : [ ] ) ,
176+ ...( isColumnVisible ( 'functionFileName' )
177+ ? [ { id : 'Function File Name' , value : row . functionFileName ?? '' } ]
178+ : [ ] ) ,
179+ ...( isColumnVisible ( 'mappingFile' )
180+ ? [ { id : 'Mapping File' , value : row . mappingFile ?? '' } ]
181+ : [ ] ) ,
182+ ] . flat ( )
183+ : [ ] ;
184+
185+ const nonEmptyValuesToCopy = valuesToCopy . filter ( ( { value} ) => value !== '' ) ;
186+
45187 const isMenuDisabled = row === null || enableSandwichView !== true ;
46188
47189 return (
@@ -63,6 +205,31 @@ const TableContextMenu = ({menuId, row}: TableContextMenuProps): React.JSX.Eleme
63205 </ div >
64206 </ div >
65207 </ Item >
208+ < Submenu
209+ label = {
210+ < div className = "flex w-full items-center gap-2" >
211+ < Icon icon = "ph:copy" />
212+ < div > Copy</ div >
213+ </ div >
214+ }
215+ disabled = { row === null }
216+ >
217+ < div className = "max-h-[300px] overflow-scroll" >
218+ { nonEmptyValuesToCopy . map ( ( { id, value} : { id : string ; value : string } ) => (
219+ < Item
220+ key = { id }
221+ id = { id }
222+ onClick = { ( ) => handleCopyItem ( value ) }
223+ className = "dark:bg-gray-800"
224+ >
225+ < div className = "flex flex-col dark:text-gray-300 hover:dark:text-gray-100" >
226+ < div className = "text-sm" > { id } </ div >
227+ < div className = "text-xs" > { truncateString ( value , 30 ) } </ div >
228+ </ div >
229+ </ Item >
230+ ) ) }
231+ </ div >
232+ </ Submenu >
66233 </ Menu >
67234 ) ;
68235} ;
0 commit comments