@parca/profile 0.16.209 → 0.16.211

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.
@@ -11,71 +11,20 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import React, {useEffect, useMemo, useState} from 'react';
14
+ import React, {useEffect, useState} from 'react';
15
15
 
16
- import {Table} from 'apache-arrow';
17
16
  import {pointer} from 'd3-selection';
18
- import {CopyToClipboard} from 'react-copy-to-clipboard';
19
17
  import {usePopper} from 'react-popper';
20
18
 
21
- import {
22
- CallgraphNode,
23
- CallgraphNodeMeta,
24
- FlamegraphNode,
25
- FlamegraphNodeMeta,
26
- FlamegraphRootNode,
27
- } from '@parca/client';
28
- import {
29
- Location,
30
- Mapping,
31
- Function as ParcaFunction,
32
- } from '@parca/client/dist/parca/metastore/v1alpha1/metastore';
33
19
  import {useKeyDown} from '@parca/components';
34
- import {selectHoveringRow, useAppSelector} from '@parca/store';
35
- import {divide, getLastItem, valueFormatter} from '@parca/utilities';
36
-
37
- import {hexifyAddress, truncateString, truncateStringReverse} from '../';
38
- import {
39
- FIELD_CUMULATIVE,
40
- FIELD_DIFF,
41
- FIELD_FUNCTION_FILE_NAME,
42
- FIELD_FUNCTION_NAME,
43
- FIELD_LOCATION_ADDRESS,
44
- FIELD_MAPPING_BUILD_ID,
45
- FIELD_MAPPING_FILE,
46
- } from '../ProfileIcicleGraph/IcicleGraphArrow';
47
- import {ExpandOnHover} from './ExpandOnHoverValue';
48
-
49
- const NoData = (): React.JSX.Element => {
50
- return <span className="rounded bg-gray-200 px-2 dark:bg-gray-800">Not available</span>;
51
- };
52
-
53
- interface ExtendedCallgraphNodeMeta extends CallgraphNodeMeta {
54
- lineIndex: number;
55
- locationIndex: number;
56
- }
57
-
58
- interface HoveringNode extends FlamegraphRootNode, FlamegraphNode, CallgraphNode {
59
- diff: bigint;
60
- meta?: FlamegraphNodeMeta | ExtendedCallgraphNodeMeta;
61
- }
62
20
 
63
21
  interface GraphTooltipProps {
64
- table: Table<any>;
22
+ children: React.ReactNode;
65
23
  x?: number;
66
24
  y?: number;
67
- unit: string;
68
- total: bigint;
69
- totalUnfiltered: bigint;
70
- hoveringNode?: HoveringNode;
71
25
  contextElement: Element | null;
72
26
  isFixed?: boolean;
73
27
  virtualContextElement?: boolean;
74
- strings?: string[];
75
- mappings?: Mapping[];
76
- locations?: Location[];
77
- functions?: ParcaFunction[];
78
- type?: string;
79
28
  }
80
29
 
81
30
  const virtualElement = {
@@ -105,369 +54,14 @@ function generateGetBoundingClientRect(contextElement: Element, x = 0, y = 0): (
105
54
  } as DOMRect);
106
55
  }
107
56
 
108
- const TooltipMetaInfo = ({
109
- hoveringNode,
110
- onCopy,
111
- strings,
112
- mappings,
113
- locations,
114
- functions,
115
- type = 'flamegraph',
116
- }: {
117
- hoveringNode: HoveringNode;
118
- onCopy: () => void;
119
- strings?: string[];
120
- mappings?: Mapping[];
121
- locations?: Location[];
122
- functions?: ParcaFunction[];
123
- type?: string;
124
- }): React.JSX.Element => {
125
- // populate meta from the flamegraph metadata tables
126
- if (
127
- type === 'flamegraph' &&
128
- locations !== undefined &&
129
- hoveringNode.meta?.locationIndex !== undefined &&
130
- hoveringNode.meta.locationIndex !== 0
131
- ) {
132
- const location = locations[hoveringNode.meta.locationIndex - 1];
133
- hoveringNode.meta.location = location;
134
-
135
- if (location !== undefined) {
136
- if (
137
- mappings !== undefined &&
138
- location.mappingIndex !== undefined &&
139
- location.mappingIndex !== 0
140
- ) {
141
- const mapping = mappings[location.mappingIndex - 1];
142
- if (strings !== undefined && mapping !== undefined) {
143
- mapping.file =
144
- mapping?.fileStringIndex !== undefined ? strings[mapping.fileStringIndex] : '';
145
- mapping.buildId =
146
- mapping?.buildIdStringIndex !== undefined ? strings[mapping.buildIdStringIndex] : '';
147
- }
148
- hoveringNode.meta.mapping = mapping;
149
- }
150
-
151
- if (
152
- functions !== undefined &&
153
- location.lines !== undefined &&
154
- hoveringNode.meta.lineIndex !== undefined &&
155
- hoveringNode.meta.lineIndex < location.lines.length
156
- ) {
157
- const func = functions[location.lines[hoveringNode.meta.lineIndex].functionIndex - 1];
158
- if (strings !== undefined) {
159
- func.name = strings[func.nameStringIndex];
160
- func.systemName = strings[func.systemNameStringIndex];
161
- func.filename = strings[func.filenameStringIndex];
162
- }
163
- hoveringNode.meta.function = func;
164
- }
165
- }
166
- }
167
-
168
- const getTextForFile = (hoveringNode: HoveringNode): string => {
169
- if (hoveringNode.meta?.function == null) return '<unknown>';
170
-
171
- return `${hoveringNode.meta.function.filename} ${
172
- hoveringNode.meta.line?.line !== undefined && hoveringNode.meta.line?.line !== 0n
173
- ? ` +${hoveringNode.meta.line.line.toString()}`
174
- : `${
175
- hoveringNode.meta.function?.startLine !== undefined &&
176
- hoveringNode.meta.function?.startLine !== 0n
177
- ? ` +${hoveringNode.meta.function.startLine}`
178
- : ''
179
- }`
180
- }`;
181
- };
182
- const file = getTextForFile(hoveringNode);
183
-
184
- return (
185
- <>
186
- <tr>
187
- <td className="w-1/4">File</td>
188
- <td className="w-3/4 break-all">
189
- {hoveringNode.meta?.function?.filename == null ||
190
- hoveringNode.meta?.function.filename === '' ? (
191
- <NoData />
192
- ) : (
193
- <CopyToClipboard onCopy={onCopy} text={file}>
194
- <button className="cursor-pointer whitespace-nowrap text-left">
195
- <ExpandOnHover value={file} displayValue={truncateStringReverse(file, 40)} />
196
- </button>
197
- </CopyToClipboard>
198
- )}
199
- </td>
200
- </tr>
201
-
202
- <tr>
203
- <td className="w-1/4">Address</td>
204
- <td className="w-3/4 break-all">
205
- {hoveringNode.meta?.location?.address == null ||
206
- hoveringNode.meta?.location.address === 0n ? (
207
- <NoData />
208
- ) : (
209
- <CopyToClipboard
210
- onCopy={onCopy}
211
- text={hexifyAddress(hoveringNode.meta.location.address)}
212
- >
213
- <button className="cursor-pointer">
214
- {hexifyAddress(hoveringNode.meta.location.address)}
215
- </button>
216
- </CopyToClipboard>
217
- )}
218
- </td>
219
- </tr>
220
- <tr>
221
- <td className="w-1/4">Binary</td>
222
- <td className="w-3/4 break-all">
223
- {hoveringNode.meta?.mapping == null || hoveringNode.meta.mapping.file === '' ? (
224
- <NoData />
225
- ) : (
226
- <CopyToClipboard onCopy={onCopy} text={hoveringNode.meta.mapping.file}>
227
- <button className="cursor-pointer">
228
- {getLastItem(hoveringNode.meta.mapping.file)}
229
- </button>
230
- </CopyToClipboard>
231
- )}
232
- </td>
233
- </tr>
234
-
235
- <tr>
236
- <td className="w-1/4">Build Id</td>
237
- <td className="w-3/4 break-all">
238
- {hoveringNode.meta?.mapping == null || hoveringNode.meta?.mapping.buildId === '' ? (
239
- <NoData />
240
- ) : (
241
- <CopyToClipboard onCopy={onCopy} text={hoveringNode.meta.mapping.buildId}>
242
- <button className="cursor-pointer">
243
- {truncateString(getLastItem(hoveringNode.meta.mapping.buildId) as string, 28)}
244
- </button>
245
- </CopyToClipboard>
246
- )}
247
- </td>
248
- </tr>
249
- </>
250
- );
251
- };
252
-
253
- let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
254
-
255
- export const GraphTooltipContent = ({
256
- hoveringNode,
257
- unit,
258
- total,
259
- totalUnfiltered,
260
- isFixed,
261
- strings,
262
- mappings,
263
- locations,
264
- functions,
265
- type = 'flamegraph',
266
- }: {
267
- hoveringNode: HoveringNode;
268
- unit: string;
269
- total: bigint;
270
- totalUnfiltered: bigint;
271
- isFixed: boolean;
272
- strings?: string[];
273
- mappings?: Mapping[];
274
- locations?: Location[];
275
- functions?: ParcaFunction[];
276
- type?: string;
277
- }): React.JSX.Element => {
278
- const [isCopied, setIsCopied] = useState<boolean>(false);
279
-
280
- const onCopy = (): void => {
281
- setIsCopied(true);
282
-
283
- if (timeoutHandle !== null) {
284
- clearTimeout(timeoutHandle);
285
- }
286
- timeoutHandle = setTimeout(() => setIsCopied(false), 3000);
287
- };
288
-
289
- const hoveringNodeCumulative = hoveringNode.cumulative;
290
- const diff = hoveringNode.diff;
291
- const prevValue = hoveringNodeCumulative - diff;
292
- const diffRatio = diff !== 0n ? divide(diff, prevValue) : 0;
293
- const diffSign = diff > 0 ? '+' : '';
294
- const diffValueText = diffSign + valueFormatter(diff, unit, 1);
295
- const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
296
- const diffText = `${diffValueText} (${diffPercentageText})`;
297
-
298
- const getTextForCumulative = (hoveringNodeCumulative: bigint): string => {
299
- const filtered =
300
- totalUnfiltered > total
301
- ? ` / ${(100 * divide(hoveringNodeCumulative, total)).toFixed(2)}% of filtered`
302
- : '';
303
- return `${valueFormatter(hoveringNodeCumulative, unit, 2)}
304
- (${(100 * divide(hoveringNodeCumulative, totalUnfiltered)).toFixed(2)}%${filtered})`;
305
- };
306
-
307
- return (
308
- <div className={`flex text-sm ${isFixed ? 'w-full' : ''}`}>
309
- <div className={`m-auto w-full ${isFixed ? 'w-full' : ''}`}>
310
- <div className="min-h-52 flex w-[500px] flex-col justify-between rounded-lg border border-gray-300 bg-gray-50 p-3 shadow-lg dark:border-gray-500 dark:bg-gray-900">
311
- <div className="flex flex-row">
312
- <div className="mx-2">
313
- <div className="flex h-10 items-center break-all font-semibold">
314
- {hoveringNode.meta === undefined ? (
315
- <p>root</p>
316
- ) : (
317
- <>
318
- {hoveringNode.meta.function !== undefined &&
319
- hoveringNode.meta.function.name !== '' ? (
320
- <CopyToClipboard onCopy={onCopy} text={hoveringNode.meta.function.name}>
321
- <button className="cursor-pointer text-left">
322
- {hoveringNode.meta.function.name}
323
- </button>
324
- </CopyToClipboard>
325
- ) : (
326
- <>
327
- {hoveringNode.meta.location !== undefined &&
328
- hoveringNode.meta.location.address !== 0n ? (
329
- <CopyToClipboard
330
- onCopy={onCopy}
331
- text={hexifyAddress(hoveringNode.meta.location.address)}
332
- >
333
- <button className="cursor-pointer text-left">
334
- {hexifyAddress(hoveringNode.meta.location.address)}
335
- </button>
336
- </CopyToClipboard>
337
- ) : (
338
- <p>unknown</p>
339
- )}
340
- </>
341
- )}
342
- </>
343
- )}
344
- </div>
345
- <table className="my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300">
346
- <tbody>
347
- <tr>
348
- <td className="w-1/4">Cumulative</td>
349
-
350
- <td className="w-3/4">
351
- <CopyToClipboard
352
- onCopy={onCopy}
353
- text={getTextForCumulative(hoveringNodeCumulative)}
354
- >
355
- <button className="cursor-pointer">
356
- {getTextForCumulative(hoveringNodeCumulative)}
357
- </button>
358
- </CopyToClipboard>
359
- </td>
360
- </tr>
361
- {hoveringNode.diff !== undefined && diff !== 0n && (
362
- <tr>
363
- <td className="w-1/4">Diff</td>
364
- <td className="w-3/4">
365
- <CopyToClipboard onCopy={onCopy} text={diffText}>
366
- <button className="cursor-pointer">{diffText}</button>
367
- </CopyToClipboard>
368
- </td>
369
- </tr>
370
- )}
371
- <TooltipMetaInfo
372
- onCopy={onCopy}
373
- hoveringNode={hoveringNode}
374
- strings={strings}
375
- mappings={mappings}
376
- locations={locations}
377
- functions={functions}
378
- type={type}
379
- />
380
- </tbody>
381
- </table>
382
- </div>
383
- </div>
384
- <span className="mx-2 block text-xs text-gray-500">
385
- {isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.'}
386
- </span>
387
- </div>
388
- </div>
389
- </div>
390
- );
391
- };
392
-
393
57
  const GraphTooltip = ({
394
- table,
58
+ children,
395
59
  x,
396
60
  y,
397
- unit,
398
- total,
399
- totalUnfiltered,
400
61
  contextElement,
401
62
  isFixed = false,
402
63
  virtualContextElement = true,
403
- strings,
404
- mappings,
405
- locations,
406
- functions,
407
- type = 'flamegraph',
408
64
  }: GraphTooltipProps): React.JSX.Element => {
409
- const hoveringNodeState = useAppSelector(selectHoveringRow);
410
- const hoveringNode = useMemo<HoveringNode | undefined>(() => {
411
- const s = hoveringNodeState;
412
- if (s === undefined) {
413
- return undefined;
414
- }
415
-
416
- const mappingFile: string = table.getChild(FIELD_MAPPING_FILE)?.get(s.row) ?? '';
417
- const mappingBuildID: string = table.getChild(FIELD_MAPPING_BUILD_ID)?.get(s.row) ?? '';
418
- const locationAddress: bigint = table.getChild(FIELD_LOCATION_ADDRESS)?.get(s.row) ?? 0n;
419
- const functionName: string = table.getChild(FIELD_FUNCTION_NAME)?.get(s.row) ?? '';
420
- const functionFileName: string = table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(s.row) ?? '';
421
- const cumulative: bigint = table.getChild(FIELD_CUMULATIVE)?.get(s.row) ?? 0n;
422
- const diff: bigint = table.getChild(FIELD_DIFF)?.get(s.row) ?? 0n;
423
-
424
- const fhn: HoveringNode = {
425
- id: '',
426
- flat: 0n,
427
- children: [],
428
-
429
- cumulative,
430
- diff: diff === null ? 0n : diff,
431
- meta: {
432
- locationIndex: 0,
433
- lineIndex: 0,
434
- mapping: {
435
- id: '',
436
- start: 0n,
437
- limit: 0n,
438
- offset: 0n,
439
- file: mappingFile,
440
- buildId: mappingBuildID,
441
- hasFunctions: false,
442
- hasFilenames: false,
443
- hasLineNumbers: false,
444
- hasInlineFrames: false,
445
- fileStringIndex: 0,
446
- buildIdStringIndex: 0,
447
- },
448
- location: {
449
- id: '',
450
- address: locationAddress,
451
- mappingId: '',
452
- isFolded: false,
453
- lines: [],
454
- mappingIndex: 0,
455
- },
456
- function: {
457
- id: '',
458
- startLine: 0n,
459
- name: functionName,
460
- systemName: '',
461
- filename: functionFileName,
462
- nameStringIndex: 0,
463
- systemNameStringIndex: 0,
464
- filenameStringIndex: 0,
465
- },
466
- },
467
- };
468
- return fhn;
469
- }, [table, hoveringNodeState]);
470
-
471
65
  const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
472
66
 
473
67
  const {styles, attributes, ...popperProps} = usePopper(
@@ -532,31 +126,11 @@ const GraphTooltip = ({
532
126
  };
533
127
  }, [contextElement, popperProps, isShiftDown, x, y]);
534
128
 
535
- if (hoveringNode === undefined || hoveringNode == null) return <></>;
536
-
537
129
  return isFixed ? (
538
- <GraphTooltipContent
539
- hoveringNode={hoveringNode}
540
- unit={unit}
541
- total={total}
542
- totalUnfiltered={totalUnfiltered}
543
- isFixed={isFixed}
544
- type={type}
545
- />
130
+ <>{children}</>
546
131
  ) : (
547
132
  <div ref={setPopperElement} style={styles.popper} {...attributes.popper}>
548
- <GraphTooltipContent
549
- hoveringNode={hoveringNode}
550
- unit={unit}
551
- total={total}
552
- totalUnfiltered={totalUnfiltered}
553
- isFixed={isFixed}
554
- strings={strings}
555
- mappings={mappings}
556
- locations={locations}
557
- functions={functions}
558
- type={type}
559
- />
133
+ {children}
560
134
  </div>
561
135
  );
562
136
  };
@@ -17,7 +17,7 @@ import {Table} from 'apache-arrow';
17
17
  import cx from 'classnames';
18
18
 
19
19
  import {useKeyDown} from '@parca/components';
20
- import {selectBinaries, setHoveringRow, useAppDispatch, useAppSelector} from '@parca/store';
20
+ import {selectBinaries, useAppSelector} from '@parca/store';
21
21
  import {isSearchMatch, scaleLinear} from '@parca/utilities';
22
22
 
23
23
  import {
@@ -44,6 +44,7 @@ interface IcicleGraphNodesProps {
44
44
  level: number;
45
45
  curPath: string[];
46
46
  setCurPath: (path: string[]) => void;
47
+ setHoveringRow: (row: number | null) => void;
47
48
  path: string[];
48
49
  xScale: (value: bigint) => number;
49
50
  searchString?: string;
@@ -64,6 +65,7 @@ export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({
64
65
  level,
65
66
  path,
66
67
  setCurPath,
68
+ setHoveringRow,
67
69
  curPath,
68
70
  sortBy,
69
71
  searchString,
@@ -100,6 +102,7 @@ export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({
100
102
  height={RowHeight}
101
103
  path={path}
102
104
  setCurPath={setCurPath}
105
+ setHoveringRow={setHoveringRow}
103
106
  level={level}
104
107
  curPath={curPath}
105
108
  total={total}
@@ -132,6 +135,7 @@ interface IcicleNodeProps {
132
135
  path: string[];
133
136
  total: bigint;
134
137
  setCurPath: (path: string[]) => void;
138
+ setHoveringRow: (row: number | null) => void;
135
139
  xScale: (value: bigint) => number;
136
140
  isRoot?: boolean;
137
141
  searchString?: string;
@@ -166,12 +170,12 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
166
170
  xScale,
167
171
  isRoot = false,
168
172
  searchString,
173
+ setHoveringRow,
169
174
  sortBy,
170
175
  darkMode,
171
176
  compareMode,
172
177
  }: IcicleNodeProps): React.JSX.Element {
173
178
  const {isShiftDown} = useKeyDown();
174
- const dispatch = useAppDispatch();
175
179
 
176
180
  // get the columns to read from
177
181
  const mappingColumn = table.getChild(FIELD_MAPPING_FILE);
@@ -272,12 +276,12 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
272
276
 
273
277
  const onMouseEnter = (): void => {
274
278
  if (isShiftDown) return;
275
- dispatch(setHoveringRow({row}));
279
+ setHoveringRow(row);
276
280
  };
277
281
 
278
282
  const onMouseLeave = (): void => {
279
283
  if (isShiftDown) return;
280
- dispatch(setHoveringRow(undefined));
284
+ setHoveringRow(null);
281
285
  };
282
286
 
283
287
  return (
@@ -326,6 +330,7 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
326
330
  path={nextPath}
327
331
  curPath={nextCurPath}
328
332
  setCurPath={setCurPath}
333
+ setHoveringRow={setHoveringRow}
329
334
  searchString={searchString}
330
335
  sortBy={sortBy}
331
336
  darkMode={darkMode}
@@ -32,6 +32,7 @@ import {
32
32
  } from '@parca/utilities';
33
33
 
34
34
  import GraphTooltipArrow from '../../GraphTooltipArrow';
35
+ import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
35
36
  import ColorStackLegend from './ColorStackLegend';
36
37
  import {IcicleNode, RowHeight, mappingColors} from './IcicleGraphNodes';
37
38
  import {extractFeature} from './utils';
@@ -39,9 +40,12 @@ import {extractFeature} from './utils';
39
40
  export const FIELD_MAPPING_FILE = 'mapping_file';
40
41
  export const FIELD_MAPPING_BUILD_ID = 'mapping_build_id';
41
42
  export const FIELD_LOCATION_ADDRESS = 'location_address';
43
+ export const FIELD_LOCATION_LINE = 'location_line';
42
44
  export const FIELD_FUNCTION_NAME = 'function_name';
43
45
  export const FIELD_FUNCTION_FILE_NAME = 'function_file_name';
46
+ export const FIELD_FUNCTION_START_LINE = 'function_startline';
44
47
  export const FIELD_CHILDREN = 'children';
48
+ export const FIELD_LABELS = 'labels';
45
49
  export const FIELD_CUMULATIVE = 'cumulative';
46
50
  export const FIELD_DIFF = 'diff';
47
51
 
@@ -73,6 +77,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
73
77
  const isDarkMode = useAppSelector(selectDarkMode);
74
78
 
75
79
  const [height, setHeight] = useState(0);
80
+ const [hoveringRow, setHoveringRow] = useState<number | null>(null);
76
81
  const sortBy = FIELD_FUNCTION_NAME; // TODO: make this configurable via UI
77
82
  const svg = useRef(null);
78
83
  const ref = useRef<SVGGElement>(null);
@@ -166,13 +171,16 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
166
171
  navigateTo={navigateTo}
167
172
  compareMode={compareMode}
168
173
  />
169
- <GraphTooltipArrow
170
- table={table}
171
- unit={sampleUnit}
172
- total={total}
173
- totalUnfiltered={total + filtered}
174
- contextElement={svg.current}
175
- />
174
+ <GraphTooltipArrow contextElement={svg.current}>
175
+ <GraphTooltipArrowContent
176
+ table={table}
177
+ row={hoveringRow}
178
+ isFixed={false}
179
+ total={total}
180
+ totalUnfiltered={total + filtered}
181
+ unit={sampleUnit}
182
+ />
183
+ </GraphTooltipArrow>
176
184
  <svg
177
185
  className="font-robotoMono"
178
186
  width={width}
@@ -198,6 +206,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
198
206
  level={0}
199
207
  isRoot={true}
200
208
  searchString={currentSearchString}
209
+ setHoveringRow={setHoveringRow}
201
210
  sortBy={sortBy}
202
211
  darkMode={isDarkMode}
203
212
  compareMode={compareMode}
@@ -118,7 +118,7 @@ const ProfileIcicleGraph = ({
118
118
  return (
119
119
  <div className="relative">
120
120
  {compareMode && <DiffLegend />}
121
- <div ref={ref}>
121
+ <div ref={ref} className="min-h-48">
122
122
  {graph !== undefined && (
123
123
  <IcicleGraph
124
124
  width={dimensions?.width}