From afb2a70d462a49cd1719429f1abd9edee664e9e2 Mon Sep 17 00:00:00 2001 From: Yomi Eluwande Date: Wed, 11 Dec 2024 13:46:02 +0100 Subject: [PATCH 1/3] area/ui: Add a preference to render function names from the right --- .../components/src/UserPreferences/index.tsx | 4 + .../hooks/src/useUserPreference/index.ts | 8 ++ .../IcicleGraphArrow/IcicleGraphNodes.tsx | 17 +++- .../IcicleGraphArrow/TextWithEllipsis.tsx | 90 +++++++++++++++++++ .../IcicleGraphArrow/index.tsx | 3 + 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/TextWithEllipsis.tsx diff --git a/ui/packages/shared/components/src/UserPreferences/index.tsx b/ui/packages/shared/components/src/UserPreferences/index.tsx index e6b7cf610ac..bb19305958a 100644 --- a/ui/packages/shared/components/src/UserPreferences/index.tsx +++ b/ui/packages/shared/components/src/UserPreferences/index.tsx @@ -70,6 +70,10 @@ const UserPreferences = ({modal}: {modal?: boolean}): JSX.Element => { id="h-highlight-similar-stacks" userPreferenceDetails={USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS} /> + diff --git a/ui/packages/shared/hooks/src/useUserPreference/index.ts b/ui/packages/shared/hooks/src/useUserPreference/index.ts index 4bbd16fdad5..127b27cb7f3 100644 --- a/ui/packages/shared/hooks/src/useUserPreference/index.ts +++ b/ui/packages/shared/hooks/src/useUserPreference/index.ts @@ -64,6 +64,14 @@ export const USER_PREFERENCES: {[key: string]: UserPreferenceDetails} = { description: "When enabled, this option automatically highlights stacks that are similar to the one you're currently hovering over in the Icicle graph.", }, + SHOW_FUNCTION_NAME_FROM_LEFT: { + name: 'Show function name from left side', + key: 'SHOW_FUNCTION_NAME_FROM_LEFT', + type: 'boolean', + default: true, + description: + 'When enabled, function names in the graph will be shown starting from the left side. When disabled, names will be shown from the right side, which can be more useful for languages where the most distinctive part of the function name appears at the end.', + }, } as const; export type UserPreference = keyof typeof USER_PREFERENCES; diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx index 5f7871f64d9..940cd4a3b25 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx @@ -23,6 +23,7 @@ import 'react-contexify/dist/ReactContexify.css'; import {ProfileType} from '@parca/parser'; +import TextWithEllipsis from './TextWithEllipsis'; import { FIELD_CHILDREN, FIELD_CUMULATIVE, @@ -412,9 +413,19 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ /> {width > 5 && ( - - {name} - + {/* + {name} + */} + )} diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/TextWithEllipsis.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/TextWithEllipsis.tsx new file mode 100644 index 00000000000..776f7006a04 --- /dev/null +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/TextWithEllipsis.tsx @@ -0,0 +1,90 @@ +// Copyright 2022 The Parca Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {useEffect, useRef, useState} from 'react'; + +import cx from 'classnames'; + +import {USER_PREFERENCES, useUserPreference} from '@parca/hooks'; + +interface TextWithEllipsisProps { + text: string; + x: number; + y: number; + width: number; +} + +function TextWithEllipsis({text, x, y, width}: TextWithEllipsisProps): React.ReactNode { + const textRef = useRef(null); + const [displayText, setDisplayText] = useState(text); + + const [showFunctionNameFromLeft] = useUserPreference( + USER_PREFERENCES.SHOW_FUNCTION_NAME_FROM_LEFT.key + ); + + useEffect(() => { + if (showFunctionNameFromLeft) { + setDisplayText(text); + return; + } + + const textElement = textRef.current; + if (textElement === null) return; + + const textWidth = textElement.getComputedTextLength(); + if (textWidth <= width) { + setDisplayText(text); + return; + } + + // Binary search to find the maximum text that fits + let start = 0; + let end = text.length; + let result = text; + + while (start < end) { + const mid = Math.floor((start + end + 1) / 2); + const truncated = !showFunctionNameFromLeft + ? `...${text.slice(-mid)}` + : `${text.slice(0, mid)}...`; + + textElement.textContent = truncated; + const currentWidth = textElement.getComputedTextLength(); + + if (currentWidth <= width) { + result = truncated; + start = mid; + } else { + end = mid - 1; + } + } + + setDisplayText(result); + }, [text, width, showFunctionNameFromLeft]); + + if (showFunctionNameFromLeft) { + return ( + + {displayText} + + ); + } + + return ( + + {displayText} + + ); +} + +export default TextWithEllipsis; diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx index 0de487550bb..9f554645515 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx @@ -119,6 +119,9 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key ); const [dockedMetainfo] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key); + const [showFunctionNameFromLeft] = useUserPreference( + USER_PREFERENCES.SHOW_FUNCTION_NAME_FROM_LEFT.key + ); const isDarkMode = useAppSelector(selectDarkMode); const table: Table = useMemo(() => { From 37208b62f96cce41f6000000f737de07231b6938 Mon Sep 17 00:00:00 2001 From: Yomi Eluwande Date: Wed, 11 Dec 2024 14:19:53 +0100 Subject: [PATCH 2/3] Refactor TextWithEllipsis component --- .../IcicleGraphArrow/IcicleGraphNodes.tsx | 7 -- .../IcicleGraphArrow/TextWithEllipsis.tsx | 93 ++++++++++--------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx index 940cd4a3b25..3d3715c12ba 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx @@ -413,13 +413,6 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ /> {width > 5 && ( - {/* - {name} - */} (null); const [displayText, setDisplayText] = useState(text); - const [showFunctionNameFromLeft] = useUserPreference( USER_PREFERENCES.SHOW_FUNCTION_NAME_FROM_LEFT.key ); useEffect(() => { - if (showFunctionNameFromLeft) { - setDisplayText(text); - return; - } - const textElement = textRef.current; if (textElement === null) return; - const textWidth = textElement.getComputedTextLength(); - if (textWidth <= width) { - setDisplayText(text); - return; - } + const newText = showFunctionNameFromLeft + ? text + : calculateTruncatedText(text, textElement, width); - // Binary search to find the maximum text that fits - let start = 0; - let end = text.length; - let result = text; - - while (start < end) { - const mid = Math.floor((start + end + 1) / 2); - const truncated = !showFunctionNameFromLeft - ? `...${text.slice(-mid)}` - : `${text.slice(0, mid)}...`; - - textElement.textContent = truncated; - const currentWidth = textElement.getComputedTextLength(); - - if (currentWidth <= width) { - result = truncated; - start = mid; - } else { - end = mid - 1; - } - } - - setDisplayText(result); + setDisplayText(newText); }, [text, width, showFunctionNameFromLeft]); - if (showFunctionNameFromLeft) { - return ( - - {displayText} - - ); - } - return ( {displayText} From 70275cb529b2d24492b6bcb6ca5e1cd2af4f5788 Mon Sep 17 00:00:00 2001 From: Yomi Eluwande Date: Wed, 11 Dec 2024 14:52:17 +0100 Subject: [PATCH 3/3] Resolve linter --- .../profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx | 3 --- .../profile/src/ProfileView/components/Toolbars/index.tsx | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx index 9f554645515..0de487550bb 100644 --- a/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +++ b/ui/packages/shared/profile/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx @@ -119,9 +119,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key ); const [dockedMetainfo] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key); - const [showFunctionNameFromLeft] = useUserPreference( - USER_PREFERENCES.SHOW_FUNCTION_NAME_FROM_LEFT.key - ); const isDarkMode = useAppSelector(selectDarkMode); const table: Table = useMemo(() => { diff --git a/ui/packages/shared/profile/src/ProfileView/components/Toolbars/index.tsx b/ui/packages/shared/profile/src/ProfileView/components/Toolbars/index.tsx index 03b90a1fdfb..1544c8b9934 100644 --- a/ui/packages/shared/profile/src/ProfileView/components/Toolbars/index.tsx +++ b/ui/packages/shared/profile/src/ProfileView/components/Toolbars/index.tsx @@ -157,7 +157,7 @@ export const VisualisationToolbar: FC = ({ {profileViewExternalSubActions != null ? profileViewExternalSubActions : null}
- {preferencesModal != null ? : null} + {preferencesModal === true && }