@parca/profile 0.16.269 → 0.16.274

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 (33) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/GraphTooltipArrow/Content.js +9 -36
  3. package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +16 -25
  4. package/dist/GraphTooltipArrow/index.d.ts +2 -1
  5. package/dist/GraphTooltipArrow/index.js +3 -5
  6. package/dist/MetricsGraph/MetricsContextMenu/index.d.ts +16 -0
  7. package/dist/MetricsGraph/MetricsContextMenu/index.js +27 -0
  8. package/dist/MetricsGraph/MetricsTooltip/index.d.ts +1 -2
  9. package/dist/MetricsGraph/MetricsTooltip/index.js +3 -4
  10. package/dist/MetricsGraph/index.d.ts +9 -3
  11. package/dist/MetricsGraph/index.js +23 -17
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.d.ts +19 -0
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.js +75 -0
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +3 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +7 -8
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +20 -3
  17. package/dist/ProfileMetricsGraph/index.d.ts +7 -1
  18. package/dist/ProfileMetricsGraph/index.js +1 -1
  19. package/dist/ProfileSelector/index.js +23 -4
  20. package/dist/styles.css +1 -1
  21. package/package.json +10 -9
  22. package/src/GraphTooltipArrow/Content.tsx +24 -112
  23. package/src/GraphTooltipArrow/DockedGraphTooltip/index.tsx +34 -128
  24. package/src/GraphTooltipArrow/index.tsx +4 -6
  25. package/src/MetricsGraph/MetricsContextMenu/index.tsx +81 -0
  26. package/src/MetricsGraph/MetricsTooltip/index.tsx +17 -21
  27. package/src/MetricsGraph/index.tsx +48 -27
  28. package/src/ProfileIcicleGraph/IcicleGraphArrow/ContextMenu.tsx +181 -0
  29. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +10 -5
  30. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +63 -21
  31. package/src/ProfileMetricsGraph/index.tsx +4 -2
  32. package/src/ProfileSelector/index.tsx +27 -4
  33. package/yarn-error.log +28744 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.16.274](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.273...@parca/profile@0.16.274) (2023-10-10)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.273](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.272...@parca/profile@0.16.273) (2023-10-10)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
14
+ ## [0.16.272](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.271...@parca/profile@0.16.272) (2023-10-10)
15
+
16
+ **Note:** Version bump only for package @parca/profile
17
+
18
+ ## [0.16.271](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.270...@parca/profile@0.16.271) (2023-10-10)
19
+
20
+ **Note:** Version bump only for package @parca/profile
21
+
22
+ ## [0.16.270](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.269...@parca/profile@0.16.270) (2023-10-10)
23
+
24
+ **Note:** Version bump only for package @parca/profile
25
+
6
26
  ## [0.16.269](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.268...@parca/profile@0.16.269) (2023-10-04)
7
27
 
8
28
  **Note:** Version bump only for package @parca/profile
@@ -1,33 +1,14 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Copyright 2022 The Parca Authors
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- import { useState } from 'react';
15
- import cx from 'classnames';
16
- import { CopyToClipboard } from 'react-copy-to-clipboard';
17
- import { Tooltip } from 'react-tooltip';
18
- import { Button, IconButton, useParcaContext } from '@parca/components';
19
- import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
2
+ import { Icon } from '@iconify/react';
20
3
  import { getLastItem } from '@parca/utilities';
21
4
  import { hexifyAddress, truncateString, truncateStringReverse } from '../utils';
22
5
  import { ExpandOnHover } from './ExpandOnHoverValue';
23
6
  import { useGraphTooltip } from './useGraphTooltip';
24
7
  import { useGraphTooltipMetaInfo } from './useGraphTooltipMetaInfo';
25
- let timeoutHandle = null;
26
8
  const NoData = () => {
27
9
  return _jsx("span", { className: "rounded bg-gray-200 px-2 dark:bg-gray-800", children: "Not available" });
28
10
  };
29
11
  const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, level, isFixed, navigateTo, }) => {
30
- const [isCopied, setIsCopied] = useState(false);
31
12
  const graphTooltipData = useGraphTooltip({
32
13
  table,
33
14
  unit,
@@ -36,29 +17,21 @@ const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, le
36
17
  row,
37
18
  level,
38
19
  });
39
- const [_, setIsDocked] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
40
20
  if (graphTooltipData === null) {
41
21
  return _jsx(_Fragment, {});
42
22
  }
43
- const onCopy = () => {
44
- setIsCopied(true);
45
- if (timeoutHandle !== null) {
46
- clearTimeout(timeoutHandle);
47
- }
48
- timeoutHandle = setTimeout(() => setIsCopied(false), 3000);
49
- };
50
23
  const { name, locationAddress, cumulativeText, diffText, diff, row: rowNumber } = graphTooltipData;
51
- return (_jsx("div", { className: `flex text-sm ${isFixed ? 'w-full' : ''}`, children: _jsx("div", { className: `m-auto w-full ${isFixed ? 'w-full' : ''}`, children: _jsxs("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", children: [_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "mx-2", children: [_jsxs("div", { className: "flex h-10 items-start justify-between gap-4 break-all font-semibold", children: [row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })), _jsx(IconButton, { onClick: () => setIsDocked(true), icon: "mdi:dock-bottom", title: "Dock MetaInfo Panel" })] }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: cumulativeText, children: _jsx("button", { className: "cursor-pointer", children: cumulativeText }) }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: diffText, children: _jsx("button", { className: "cursor-pointer", children: diffText }) }) })] })), _jsx(TooltipMetaInfo, { table: table, row: rowNumber, onCopy: onCopy, navigateTo: navigateTo })] }) })] }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }) }));
24
+ return (_jsx("div", { className: `flex text-sm ${isFixed ? 'w-full' : ''}`, children: _jsx("div", { className: `m-auto w-full ${isFixed ? 'w-full' : ''}`, children: _jsxs("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", children: [_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "mx-2", children: [_jsx("div", { className: "flex h-10 items-start justify-between gap-4 break-all font-semibold", children: row === 0 ? (_jsx("p", { children: "root" })) : (_jsx("p", { children: name !== ''
25
+ ? name
26
+ : locationAddress !== 0n
27
+ ? hexifyAddress(locationAddress)
28
+ : 'unknown' })) }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx("div", { children: cumulativeText }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx("div", { children: diffText }) })] })), _jsx(TooltipMetaInfo, { table: table, row: rowNumber, navigateTo: navigateTo })] }) })] }) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to show context menu" })] })] }) }) }));
52
29
  };
53
- const TooltipMetaInfo = ({ table,
54
- // total,
55
- // totalUnfiltered,
56
- onCopy, row, navigateTo, }) => {
57
- const { labelPairs, functionFilename, file, openFile, isSourceAvailable, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({ table, row, navigateTo });
58
- const { enableSourcesView } = useParcaContext();
30
+ const TooltipMetaInfo = ({ table, row, navigateTo, }) => {
31
+ const { labelPairs, functionFilename, file, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({ table, row, navigateTo });
59
32
  const labels = labelPairs.map((l) => (_jsx("span", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${l[0]}="${l[1]}"` }, l[0])));
60
33
  const isMappingBuildIDAvailable = mappingBuildID !== null && mappingBuildID !== '';
61
34
  const inlinedText = inlined === null ? 'merged' : inlined ? 'yes' : 'no';
62
- return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsxs("div", { className: "flex gap-4", children: [_jsx(CopyToClipboard, { onCopy: onCopy, text: file, children: _jsx("button", { className: "cursor-pointer whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) }), _jsxs("div", { className: cx('flex gap-2', { hidden: enableSourcesView === false }), children: [_jsx("div", { "data-tooltip-id": "open-source-button-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsx(Button, { variant: 'neutral', onClick: () => openFile(), className: "shrink-0", disabled: !isSourceAvailable, children: "open" }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "open-source-button-help" }) : null] })] })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer", children: hexifyAddress(locationAddress) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Inlined" }), _jsx("td", { className: "w-3/4 break-all", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: inlinedText, children: _jsx("button", { className: "cursor-pointer", children: inlinedText }) }) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: mappingFile === null ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingFile, children: _jsx("button", { className: "cursor-pointer", children: getLastItem(mappingFile) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: !isMappingBuildIDAvailable ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingBuildID, children: _jsx("button", { className: "cursor-pointer", children: truncateString(getLastItem(mappingBuildID), 28) }) })) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
35
+ return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsx("div", { className: "flex gap-4", children: _jsx("div", { className: "whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? _jsx(NoData, {}) : _jsx("div", { children: hexifyAddress(locationAddress) }) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Inlined" }), _jsx("td", { className: "w-3/4 break-all", children: inlinedText })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: (mappingFile != null ? getLastItem(mappingFile) : null) ?? _jsx(NoData, {}) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: isMappingBuildIDAvailable ? (_jsx("div", { children: truncateString(getLastItem(mappingBuildID), 28) })) : (_jsx(NoData, {})) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
63
36
  };
64
37
  export default GraphTooltipArrowContent;
@@ -11,35 +11,25 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- import { useState } from 'react';
14
+ import { Icon } from '@iconify/react';
15
15
  import cx from 'classnames';
16
- import { CopyToClipboard } from 'react-copy-to-clipboard';
17
- import { Tooltip } from 'react-tooltip';
18
16
  import { useWindowSize } from 'react-use';
19
- import { Button, IconButton, useParcaContext } from '@parca/components';
20
- import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
17
+ import { useParcaContext } from '@parca/components';
21
18
  import { getLastItem } from '@parca/utilities';
22
19
  import { hexifyAddress, truncateString, truncateStringReverse } from '../../utils';
23
- import { ExpandOnHover } from '../ExpandOnHoverValue';
24
20
  import { useGraphTooltip } from '../useGraphTooltip';
25
21
  import { useGraphTooltipMetaInfo } from '../useGraphTooltipMetaInfo';
26
- let timeoutHandle = null;
27
- const InfoSection = ({ title, value, onCopy, copyText, minWidth = '', }) => {
28
- return (_jsxs("div", { className: cx('flex shrink-0 flex-col gap-1 p-2', { [minWidth]: minWidth != null }), children: [_jsx("p", { className: "text-sm font-medium leading-5 text-gray-500 dark:text-gray-400", children: title }), _jsx("div", { className: "text-lg font-normal text-gray-900 dark:text-gray-50", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: copyText, children: _jsx("button", { children: value }) }) })] }));
22
+ const InfoSection = ({ title, value, minWidth = '', }) => {
23
+ return (_jsxs("div", { className: cx('flex shrink-0 flex-col gap-1 p-2', { [minWidth]: minWidth != null }), children: [_jsx("p", { className: "text-sm font-medium leading-5 text-gray-500 dark:text-gray-400", children: title }), _jsx("div", { className: "text-lg font-normal text-gray-900 dark:text-gray-50", children: value })] }));
24
+ };
25
+ const NoData = () => {
26
+ return _jsx("span", { className: "rounded bg-gray-200 px-2 dark:bg-gray-800", children: "Not available" });
29
27
  };
30
28
  export const DockedGraphTooltip = ({ table, unit, total, totalUnfiltered, row, level, }) => {
31
29
  let { width } = useWindowSize();
32
- const { profileExplorer, navigateTo, enableSourcesView } = useParcaContext();
30
+ const { profileExplorer, navigateTo } = useParcaContext();
33
31
  const { PaddingX } = profileExplorer ?? { PaddingX: 0 };
34
32
  width = width - PaddingX - 24;
35
- const [isCopied, setIsCopied] = useState(false);
36
- const onCopy = () => {
37
- setIsCopied(true);
38
- if (timeoutHandle !== null) {
39
- clearTimeout(timeoutHandle);
40
- }
41
- timeoutHandle = setTimeout(() => setIsCopied(false), 3000);
42
- };
43
33
  const graphTooltipData = useGraphTooltip({
44
34
  table,
45
35
  unit,
@@ -48,17 +38,18 @@ export const DockedGraphTooltip = ({ table, unit, total, totalUnfiltered, row, l
48
38
  row,
49
39
  level,
50
40
  });
51
- const { labelPairs, functionFilename, file, openFile, isSourceAvailable, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({ table, row: row ?? 0, navigateTo });
52
- const [_, setIsDocked] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
41
+ const { labelPairs, functionFilename, file, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({ table, row: row ?? 0, navigateTo });
53
42
  if (graphTooltipData === null) {
54
43
  return _jsx(_Fragment, {});
55
44
  }
56
45
  const { name, cumulativeText, diffText, diff } = graphTooltipData;
57
46
  const labels = labelPairs.map((l) => (_jsx("span", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${l[0]}="${l[1]}"` }, l[0])));
47
+ const isMappingBuildIDAvailable = mappingBuildID !== null && mappingBuildID !== '';
58
48
  const inlinedText = inlined === null ? 'merged' : inlined ? 'yes' : 'no';
59
- const addressText = locationAddress !== 0n ? hexifyAddress(locationAddress) : 'unknown';
60
- const fileText = functionFilename !== '' ? file : 'Not available';
61
- return (_jsx("div", { className: "fixed bottom-0 z-20 overflow-hidden rounded-t-lg border-l border-r border-t border-gray-400 bg-white bg-opacity-90 px-8 py-3 dark:border-gray-600 dark:bg-black dark:bg-opacity-80", style: { width }, children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex justify-between gap-4", children: [row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })), _jsx(IconButton, { onClick: () => setIsDocked(false), icon: "mdi:dock-window", title: "Undock MetaInfo Panel" })] }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx(InfoSection, { title: "Cumulative", value: cumulativeText, onCopy: onCopy, copyText: cumulativeText, minWidth: "w-44" }), diff !== 0n ? (_jsx(InfoSection, { title: "Diff", value: diffText, onCopy: onCopy, copyText: diffText, minWidth: "w-44" })) : null, _jsx(InfoSection, { title: "File", value: _jsxs("div", { className: "flex gap-2", children: [_jsx(ExpandOnHover, { value: fileText, displayValue: truncateStringReverse(fileText, 45) }), _jsxs("div", { className: cx('flex items-center gap-2', {
62
- hidden: enableSourcesView === false || functionFilename === '',
63
- }), children: [_jsx("div", { "data-tooltip-id": "open-source-button-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsx(Button, { variant: 'neutral', onClick: () => openFile(), className: "shrink-0", disabled: !isSourceAvailable, children: "open" }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "open-source-button-help" }) : null] })] }), onCopy: onCopy, copyText: file, minWidth: 'w-[460px]' }), _jsx(InfoSection, { title: "Address", value: addressText, onCopy: onCopy, copyText: addressText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Inlined", value: inlinedText, onCopy: onCopy, copyText: inlinedText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Binary", value: (mappingFile != null ? getLastItem(mappingFile) : null) ?? 'Not available', onCopy: onCopy, copyText: mappingFile ?? 'Not available', minWidth: "w-44" }), _jsx(InfoSection, { title: "Build ID", value: truncateString(getLastItem(mappingBuildID) ?? 'Not available', 28), onCopy: onCopy, copyText: mappingBuildID ?? 'Not available' })] }), _jsx("div", { children: _jsx("div", { className: "flex h-5 gap-1", children: labels }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }));
49
+ const addressText = locationAddress !== 0n ? hexifyAddress(locationAddress) : _jsx(NoData, {});
50
+ return (_jsxs("div", { className: "fixed bottom-0 z-20 overflow-hidden rounded-t-lg border-l border-r border-t border-gray-400 bg-white bg-opacity-90 px-8 py-3 dark:border-gray-600 dark:bg-black dark:bg-opacity-80", style: { width }, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx("div", { className: "flex justify-between gap-4", children: row === 0 ? (_jsx("p", { children: "root" })) : (_jsx("p", { children: name !== ''
51
+ ? name
52
+ : locationAddress !== 0n
53
+ ? hexifyAddress(locationAddress)
54
+ : 'unknown' })) }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx(InfoSection, { title: "Cumulative", value: cumulativeText, minWidth: "w-44" }), diff !== 0n ? _jsx(InfoSection, { title: "Diff", value: diffText, minWidth: "w-44" }) : null, _jsx(InfoSection, { title: "File", value: functionFilename !== '' ? truncateStringReverse(file, 45) : _jsx(NoData, {}), minWidth: 'w-[460px]' }), _jsx(InfoSection, { title: "Address", value: addressText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Inlined", value: inlinedText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Binary", value: (mappingFile != null ? getLastItem(mappingFile) : null) ?? _jsx(NoData, {}), minWidth: "w-44" }), _jsx(InfoSection, { title: "Build ID", value: isMappingBuildIDAvailable ? (_jsx("div", { children: truncateString(getLastItem(mappingBuildID), 28) })) : (_jsx(NoData, {})) })] }), _jsx("div", { children: _jsx("div", { className: "flex h-5 gap-1", children: labels }) })] }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to show context menu" })] })] }));
64
55
  };
@@ -6,6 +6,7 @@ interface GraphTooltipProps {
6
6
  contextElement: Element | null;
7
7
  isFixed?: boolean;
8
8
  virtualContextElement?: boolean;
9
+ isContextMenuOpen?: boolean;
9
10
  }
10
- declare const GraphTooltip: ({ children, x, y, contextElement, isFixed, virtualContextElement, }: GraphTooltipProps) => React.JSX.Element;
11
+ declare const GraphTooltip: ({ children, x, y, contextElement, isFixed, virtualContextElement, isContextMenuOpen, }: GraphTooltipProps) => React.JSX.Element;
11
12
  export default GraphTooltip;
@@ -14,7 +14,6 @@ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
14
14
  import { useEffect, useState } from 'react';
15
15
  import { pointer } from 'd3-selection';
16
16
  import { usePopper } from 'react-popper';
17
- import { useKeyDown } from '@parca/components';
18
17
  const virtualElement = {
19
18
  getBoundingClientRect: () =>
20
19
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
@@ -40,7 +39,7 @@ function generateGetBoundingClientRect(contextElement, x = 0, y = 0) {
40
39
  bottom: domRect.y + y,
41
40
  });
42
41
  }
43
- const GraphTooltip = ({ children, x, y, contextElement, isFixed = false, virtualContextElement = true, }) => {
42
+ const GraphTooltip = ({ children, x, y, contextElement, isFixed = false, virtualContextElement = true, isContextMenuOpen = false, }) => {
44
43
  const [popperElement, setPopperElement] = useState(null);
45
44
  const { styles, attributes, ...popperProps } = usePopper(virtualContextElement ? virtualElement : contextElement, popperElement, {
46
45
  placement: 'bottom-start',
@@ -61,12 +60,11 @@ const GraphTooltip = ({ children, x, y, contextElement, isFixed = false, virtual
61
60
  },
62
61
  ],
63
62
  });
64
- const { isShiftDown } = useKeyDown();
65
63
  useEffect(() => {
66
64
  if (contextElement === null)
67
65
  return;
68
66
  const onMouseMove = (e) => {
69
- if (isShiftDown) {
67
+ if (isContextMenuOpen) {
70
68
  return;
71
69
  }
72
70
  let tooltipX = x;
@@ -83,7 +81,7 @@ const GraphTooltip = ({ children, x, y, contextElement, isFixed = false, virtual
83
81
  return () => {
84
82
  contextElement.removeEventListener('mousemove', onMouseMove);
85
83
  };
86
- }, [contextElement, popperProps, isShiftDown, x, y]);
84
+ }, [contextElement, popperProps, x, y, isContextMenuOpen]);
87
85
  return isFixed ? (_jsx(_Fragment, { children: children })) : (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: children }));
88
86
  };
89
87
  export default GraphTooltip;
@@ -0,0 +1,16 @@
1
+ /// <reference types="react" />
2
+ import { HighlightedSeries } from '../';
3
+ interface MetricsContextMenuProps {
4
+ menuId: string;
5
+ onAddLabelMatcher: (labels: {
6
+ key: string;
7
+ value: string;
8
+ } | Array<{
9
+ key: string;
10
+ value: string;
11
+ }>) => void;
12
+ highlighted: HighlightedSeries | null;
13
+ trackVisibility: (isVisible: boolean) => void;
14
+ }
15
+ declare const MetricsContextMenu: ({ menuId, onAddLabelMatcher, highlighted, trackVisibility, }: MetricsContextMenuProps) => JSX.Element;
16
+ export default MetricsContextMenu;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { Icon } from '@iconify/react';
15
+ import { Item, Menu, Submenu } from 'react-contexify';
16
+ const MetricsContextMenu = ({ menuId, onAddLabelMatcher, highlighted, trackVisibility, }) => {
17
+ const labels = highlighted?.labels.filter((label) => label.name !== '__name__');
18
+ const handleFocusOnSingleSeries = () => {
19
+ const labelsToAdd = labels?.map((label) => ({
20
+ key: label.name,
21
+ value: label.value,
22
+ }));
23
+ labelsToAdd !== undefined && onAddLabelMatcher(labelsToAdd);
24
+ };
25
+ return (_jsxs(Menu, { id: menuId, onVisibilityChange: trackVisibility, children: [_jsx(Item, { id: "focus-on-single-series", onClick: handleFocusOnSingleSeries, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:star" }), _jsx("div", { children: "Focus only on this series" })] }) }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "material-symbols:add" }), _jsx("div", { children: "Add to query" })] }), style: { maxHeight: '300px', overflow: 'scroll' }, children: labels?.map((label) => (_jsx(Item, { id: label.name, onClick: () => onAddLabelMatcher({ key: label.name, value: label.value }), style: { maxWidth: '400px', overflow: 'hidden' }, children: _jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${label.name}="${label.value}"` }) }, label.name))) })] }));
26
+ };
27
+ export default MetricsContextMenu;
@@ -4,10 +4,9 @@ interface Props {
4
4
  x: number;
5
5
  y: number;
6
6
  highlighted: HighlightedSeries;
7
- onLabelClick: (labelName: string, labelValue: string) => void;
8
7
  contextElement: Element | null;
9
8
  sampleUnit: string;
10
9
  delta: boolean;
11
10
  }
12
- declare const MetricsTooltip: ({ x, y, highlighted, onLabelClick, contextElement, sampleUnit, delta, }: Props) => JSX.Element;
11
+ declare const MetricsTooltip: ({ x, y, highlighted, contextElement, sampleUnit, delta, }: Props) => JSX.Element;
13
12
  export default MetricsTooltip;
@@ -12,6 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
14
  import { useEffect, useState } from 'react';
15
+ import { Icon } from '@iconify/react';
15
16
  import { usePopper } from 'react-popper';
16
17
  import { TextWithTooltip } from '@parca/components';
17
18
  import { formatDate, valueFormatter } from '@parca/utilities';
@@ -42,7 +43,7 @@ function generateGetBoundingClientRect(contextElement, x = 0, y = 0) {
42
43
  bottom: domRect.y + y,
43
44
  });
44
45
  }
45
- const MetricsTooltip = ({ x, y, highlighted, onLabelClick, contextElement, sampleUnit, delta, }) => {
46
+ const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta, }) => {
46
47
  const [popperElement, setPopperElement] = useState(null);
47
48
  const { styles, attributes, ...popperProps } = usePopper(virtualElement, popperElement, {
48
49
  placement: 'auto-start',
@@ -74,8 +75,6 @@ const MetricsTooltip = ({ x, y, highlighted, onLabelClick, contextElement, sampl
74
75
  const highlightedNameLabel = nameLabel !== undefined ? nameLabel : { name: '', value: '' };
75
76
  return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] }), delta && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp, timeFormat) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
76
77
  .filter((label) => label.name !== '__name__')
77
- .map(function (label) {
78
- return (_jsx("button", { type: "button", className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", onClick: () => onLabelClick(label.name, label.value), children: _jsx(TextWithTooltip, { text: `${label.name}="${label.value}"`, maxTextLength: 37, id: `tooltip-${label.name}-${label.value}` }) }, label.name));
79
- }) }), _jsx("span", { className: "block text-xs text-gray-500", children: "Hold shift and click label to add to query." })] }) }) }) }) }) }));
78
+ .map((label) => (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: _jsx(TextWithTooltip, { text: `${label.name}="${label.value}"`, maxTextLength: 37, id: `tooltip-${label.name}-${label.value}` }) }, label.name))) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to add labels to query." })] })] }) }) }) }) }) }));
80
79
  };
81
80
  export default MetricsTooltip;
@@ -8,7 +8,13 @@ interface Props {
8
8
  to: number;
9
9
  profile: MergedProfileSelection | null;
10
10
  onSampleClick: (timestamp: number, value: number, labels: Label[]) => void;
11
- onLabelClick: (labelName: string, labelValue: string) => void;
11
+ addLabelMatcher: (labels: {
12
+ key: string;
13
+ value: string;
14
+ } | Array<{
15
+ key: string;
16
+ value: string;
17
+ }>) => void;
12
18
  setTimeRange: (range: DateTimeRange) => void;
13
19
  sampleUnit: string;
14
20
  width?: number;
@@ -25,7 +31,7 @@ export interface HighlightedSeries {
25
31
  x: number;
26
32
  y: number;
27
33
  }
28
- declare const MetricsGraph: ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, sampleUnit, width, height, margin, }: Props) => JSX.Element;
34
+ declare const MetricsGraph: ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, sampleUnit, width, height, margin, }: Props) => JSX.Element;
29
35
  export default MetricsGraph;
30
36
  export declare const parseValue: (value: string) => number | null;
31
- export declare const RawMetricsGraph: ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, width, height, margin, sampleUnit, }: Props) => JSX.Element;
37
+ export declare const RawMetricsGraph: ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, width, height, margin, sampleUnit, }: Props) => JSX.Element;
@@ -11,17 +11,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- import { Fragment, useRef, useState } from 'react';
14
+ import { Fragment, useCallback, useRef, useState } from 'react';
15
15
  import * as d3 from 'd3';
16
16
  import { pointer } from 'd3-selection';
17
17
  import throttle from 'lodash.throttle';
18
- import { DateTimeRange, useKeyDown } from '@parca/components';
18
+ import { useContextMenu } from 'react-contexify';
19
+ import { DateTimeRange } from '@parca/components';
19
20
  import { formatDate, formatForTimespan, sanitizeHighlightedValues, valueFormatter, } from '@parca/utilities';
20
21
  import MetricsCircle from '../MetricsCircle';
21
22
  import MetricsSeries from '../MetricsSeries';
23
+ import MetricsContextMenu from './MetricsContextMenu';
22
24
  import MetricsTooltip from './MetricsTooltip';
23
- const MetricsGraph = ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, sampleUnit, width = 0, height = 0, margin = 0, }) => {
24
- return (_jsx(RawMetricsGraph, { data: data, from: from, to: to, profile: profile, onSampleClick: onSampleClick, onLabelClick: onLabelClick, setTimeRange: setTimeRange, sampleUnit: sampleUnit, width: width, height: height, margin: margin }));
25
+ const MetricsGraph = ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, sampleUnit, width = 0, height = 0, margin = 0, }) => {
26
+ return (_jsx(RawMetricsGraph, { data: data, from: from, to: to, profile: profile, onSampleClick: onSampleClick, addLabelMatcher: addLabelMatcher, setTimeRange: setTimeRange, sampleUnit: sampleUnit, width: width, height: height, margin: margin }));
25
27
  };
26
28
  export default MetricsGraph;
27
29
  export const parseValue = (value) => {
@@ -32,14 +34,14 @@ export const parseValue = (value) => {
32
34
  };
33
35
  const lineStroke = '1px';
34
36
  const lineStrokeHover = '2px';
35
- export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, width, height = 50, margin = 0, sampleUnit, }) => {
37
+ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, width, height = 50, margin = 0, sampleUnit, }) => {
36
38
  const graph = useRef(null);
37
39
  const [dragging, setDragging] = useState(false);
38
40
  const [hovering, setHovering] = useState(false);
39
41
  const [relPos, setRelPos] = useState(-1);
40
42
  const [pos, setPos] = useState([0, 0]);
43
+ const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
41
44
  const metricPointRef = useRef(null);
42
- const { isShiftDown } = useKeyDown();
43
45
  // the time of the selected point is the start of the merge window
44
46
  const time = parseFloat(profile?.HistoryParams().merge_from);
45
47
  if (width === undefined || width == null) {
@@ -118,10 +120,6 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabe
118
120
  };
119
121
  const highlighted = getClosest();
120
122
  const onMouseDown = (e) => {
121
- // if shift is down, disable mouse behavior
122
- if (isShiftDown) {
123
- return;
124
- }
125
123
  // only left mouse button
126
124
  if (e.button !== 0) {
127
125
  return;
@@ -144,9 +142,6 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabe
144
142
  }
145
143
  };
146
144
  const onMouseUp = (e) => {
147
- if (isShiftDown) {
148
- return;
149
- }
150
145
  setDragging(false);
151
146
  if (relPos === -1) {
152
147
  // MouseDown happened outside of this element.
@@ -173,8 +168,7 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabe
173
168
  };
174
169
  const throttledSetPos = throttle(setPos, 20);
175
170
  const onMouseMove = (e) => {
176
- // do not update position if shift is down because this means the user is locking the tooltip
177
- if (isShiftDown) {
171
+ if (isContextMenuOpen) {
178
172
  return;
179
173
  }
180
174
  // X/Y coordinate array relative to svg
@@ -228,9 +222,21 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabe
228
222
  };
229
223
  };
230
224
  const selected = findSelectedProfile();
231
- return (_jsxs(_Fragment, { children: [highlighted != null && hovering && !dragging && pos[0] !== 0 && pos[1] !== 0 && (_jsx("div", { onMouseMove: onMouseMove, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false), children: _jsx(MetricsTooltip, { x: pos[0] + margin, y: pos[1] + margin, highlighted: highlighted, onLabelClick: onLabelClick, contextElement: graph.current, sampleUnit: sampleUnit, delta: profile !== null ? profile?.query.profType.delta : false }) })), _jsx("div", { ref: graph, onMouseEnter: function () {
225
+ const MENU_ID = 'metrics-context-menu';
226
+ const { show } = useContextMenu({
227
+ id: MENU_ID,
228
+ });
229
+ const displayMenu = useCallback((e) => {
230
+ show({
231
+ event: e,
232
+ });
233
+ }, [show]);
234
+ const trackVisibility = (isVisible) => {
235
+ setIsContextMenuOpen(isVisible);
236
+ };
237
+ return (_jsxs(_Fragment, { children: [_jsx(MetricsContextMenu, { onAddLabelMatcher: addLabelMatcher, menuId: MENU_ID, highlighted: highlighted, trackVisibility: trackVisibility }), highlighted != null && hovering && !dragging && pos[0] !== 0 && pos[1] !== 0 && (_jsx("div", { onMouseMove: onMouseMove, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false), children: !isContextMenuOpen && (_jsx(MetricsTooltip, { x: pos[0] + margin, y: pos[1] + margin, highlighted: highlighted, contextElement: graph.current, sampleUnit: sampleUnit, delta: profile !== null ? profile?.query.profType.delta : false })) })), _jsx("div", { ref: graph, onMouseEnter: function () {
232
238
  setHovering(true);
233
- }, onMouseLeave: () => setHovering(false), children: _jsxs("svg", { width: `${width}px`, height: `${height + margin}px`, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onMouseMove: onMouseMove, children: [_jsx("g", { transform: `translate(${margin}, 0)`, children: dragging && (_jsx("g", { className: "zoom-time-rect", children: _jsx("rect", { className: "bar", x: pos[0] - relPos < 0 ? pos[0] : relPos, y: 0, height: height, width: Math.abs(pos[0] - relPos), fill: 'rgba(0, 0, 0, 0.125)' }) })) }), _jsxs("g", { transform: `translate(${margin}, ${margin})`, children: [_jsxs("g", { className: "y axis", textAnchor: "end", fontSize: "10", fill: "none", children: [yScale.ticks(5).map((d, i) => (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
239
+ }, onMouseLeave: () => setHovering(false), onContextMenu: displayMenu, children: _jsxs("svg", { width: `${width}px`, height: `${height + margin}px`, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onMouseMove: onMouseMove, children: [_jsx("g", { transform: `translate(${margin}, 0)`, children: dragging && (_jsx("g", { className: "zoom-time-rect", children: _jsx("rect", { className: "bar", x: pos[0] - relPos < 0 ? pos[0] : relPos, y: 0, height: height, width: Math.abs(pos[0] - relPos), fill: 'rgba(0, 0, 0, 0.125)' }) })) }), _jsxs("g", { transform: `translate(${margin}, ${margin})`, children: [_jsxs("g", { className: "y axis", textAnchor: "end", fontSize: "10", fill: "none", children: [yScale.ticks(5).map((d, i) => (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
234
240
  /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
235
241
  transform: `translate(0, ${yScale(d)})`, children: [_jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x2: -6 }), _jsx("text", { fill: "currentColor", x: -9, dy: '0.32em', children: valueFormatter(d, sampleUnit, 1) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: yScale(d), y2: yScale(d) }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`))), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: 0, x2: 0, y1: 0, y2: height - margin }), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(to), x2: xScale(to), y1: 0, y2: height - margin })] }), _jsxs("g", { className: "x axis", fill: "none", fontSize: "10", textAnchor: "middle", transform: `translate(0,${height - margin})`, children: [xScale.ticks(5).map((d, i) => (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
236
242
  /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
@@ -0,0 +1,19 @@
1
+ /// <reference types="react" />
2
+ import { Table } from 'apache-arrow';
3
+ import { type NavigateFunction } from '@parca/utilities';
4
+ interface ContextMenuProps {
5
+ menuId: string;
6
+ table: Table<any>;
7
+ unit: string;
8
+ total: bigint;
9
+ totalUnfiltered: bigint;
10
+ row: number;
11
+ level: number;
12
+ navigateTo: NavigateFunction;
13
+ trackVisibility: (isVisible: boolean) => void;
14
+ curPath: string[];
15
+ setCurPath: (path: string[]) => void;
16
+ hideMenu: () => void;
17
+ }
18
+ declare const ContextMenu: ({ menuId, table, unit, total, totalUnfiltered, row, level, navigateTo, trackVisibility, curPath, setCurPath, hideMenu, }: ContextMenuProps) => JSX.Element;
19
+ export default ContextMenu;
@@ -0,0 +1,75 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { Icon } from '@iconify/react';
15
+ import { Item, Menu, Separator, Submenu } from 'react-contexify';
16
+ import { Tooltip } from 'react-tooltip';
17
+ import { useParcaContext } from '@parca/components';
18
+ import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
19
+ import { useGraphTooltip } from '../../GraphTooltipArrow/useGraphTooltip';
20
+ import { useGraphTooltipMetaInfo } from '../../GraphTooltipArrow/useGraphTooltipMetaInfo';
21
+ import { hexifyAddress, truncateString } from '../../utils';
22
+ const ContextMenu = ({ menuId, table, unit, total, totalUnfiltered, row, level, navigateTo, trackVisibility, curPath, setCurPath, hideMenu, }) => {
23
+ const { enableSourcesView } = useParcaContext();
24
+ const [isGraphTooltipDocked, setIsDocked] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
25
+ const contextMenuData = useGraphTooltip({
26
+ table,
27
+ unit,
28
+ total,
29
+ totalUnfiltered,
30
+ row,
31
+ level,
32
+ });
33
+ const { functionFilename, file, openFile, isSourceAvailable, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({ table, row, navigateTo });
34
+ if (contextMenuData === null) {
35
+ return _jsx(_Fragment, {});
36
+ }
37
+ const { name, cumulativeText, diffText, diff } = contextMenuData;
38
+ const isMappingBuildIDAvailable = mappingBuildID !== null && mappingBuildID !== '';
39
+ const handleViewSourceFile = () => openFile();
40
+ const handleResetView = () => {
41
+ setCurPath([]);
42
+ return hideMenu();
43
+ };
44
+ const handleDockTooltip = () => {
45
+ return isGraphTooltipDocked ? setIsDocked(false) : setIsDocked(true);
46
+ };
47
+ const handleCopyItem = (text) => {
48
+ void navigator.clipboard.writeText(text);
49
+ };
50
+ const functionName = row === 0
51
+ ? ''
52
+ : name !== ''
53
+ ? name
54
+ : locationAddress !== 0n
55
+ ? hexifyAddress(locationAddress)
56
+ : '';
57
+ const buildIdText = !isMappingBuildIDAvailable ? '' : mappingBuildID;
58
+ const inlinedText = inlined === null ? 'merged' : inlined ? 'yes' : 'no';
59
+ const valuesToCopy = [
60
+ { id: 'Function name', value: functionName },
61
+ { id: 'Cumulative', value: cumulativeText ?? '' },
62
+ { id: 'Diff', value: diff !== 0n ? diffText : '' },
63
+ {
64
+ id: 'File',
65
+ value: functionFilename === '' ? functionFilename : file,
66
+ },
67
+ { id: 'Address', value: locationAddress === 0n ? '' : hexifyAddress(locationAddress) },
68
+ { id: 'Inlined', value: inlinedText },
69
+ { id: 'Binary', value: mappingFile ?? '' },
70
+ { id: 'Build Id', value: buildIdText },
71
+ ];
72
+ const nonEmptyValuesToCopy = valuesToCopy.filter(({ value }) => value !== '');
73
+ return (_jsxs(Menu, { id: menuId, onVisibilityChange: trackVisibility, children: [_jsxs(Item, { id: "view-source-file", onClick: handleViewSourceFile, disabled: enableSourcesView === false || !isSourceAvailable, children: [_jsx("div", { "data-tooltip-id": "view-source-file-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "wpf:view-file" }), _jsx("div", { children: "View source file" })] }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "view-source-file-help" }) : null] }), _jsx(Item, { id: "reset-view", onClick: handleResetView, disabled: curPath.length === 0, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "system-uicons:reset" }), _jsx("div", { children: "Reset view" })] }) }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:copy" }), _jsx("div", { children: "Copy" })] }), children: nonEmptyValuesToCopy.map(({ id, value }) => (_jsx(Item, { id: id, onClick: () => handleCopyItem(value), children: _jsxs("div", { className: "flex flex-col", children: [_jsx("div", { className: "text-sm", children: id }), _jsx("div", { className: "text-xs", children: truncateString(value, 30) })] }) }, id))) }), _jsx(Separator, {}), _jsx(Item, { id: "dock-tooltip", onClick: handleDockTooltip, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:dock-bottom" }), isGraphTooltipDocked ? 'Undock tooltip' : 'Dock tooltip'] }) })] }));
74
+ };
75
+ export default ContextMenu;
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Table } from 'apache-arrow';
3
+ import 'react-contexify/dist/ReactContexify.css';
3
4
  export declare const RowHeight = 26;
4
5
  interface IcicleGraphNodesProps {
5
6
  table: Table<any>;
@@ -21,6 +22,7 @@ interface IcicleGraphNodesProps {
21
22
  sortBy: string;
22
23
  darkMode: boolean;
23
24
  compareMode: boolean;
25
+ isContextMenuOpen: boolean;
24
26
  }
25
27
  export declare const IcicleGraphNodes: React.NamedExoticComponent<IcicleGraphNodesProps>;
26
28
  export interface mappingColors {
@@ -47,6 +49,7 @@ interface IcicleNodeProps {
47
49
  sortBy: string;
48
50
  darkMode: boolean;
49
51
  compareMode: boolean;
52
+ isContextMenuOpen: boolean;
50
53
  }
51
54
  export declare const IcicleNode: React.NamedExoticComponent<IcicleNodeProps>;
52
55
  export {};