-
Notifications
You must be signed in to change notification settings - Fork 47.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add fiber summary tooltip to devtools profiling #18048
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
be77110
Add tooltip component
M-Izadmehr 9b5c730
Separate logic of ProfilerWhatChanged to a component
M-Izadmehr 1cfcd33
Add hovered Fiber info tooltip component
M-Izadmehr edd6e8e
Add flame graph chart tooltip
M-Izadmehr 255e741
Add commit ranked list tooltip
M-Izadmehr 8247667
Fix flow issues
M-Izadmehr 4995ac9
Minor improvement in filter
M-Izadmehr be5f04e
Fix flickering issue
M-Izadmehr e1e114c
Resolved issues on useCallbacks and mouse event listeners
M-Izadmehr 0bd6e07
Fix lints
M-Izadmehr 20caae7
Remove unnecessary useCallback
M-Izadmehr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
30 changes: 30 additions & 0 deletions
30
packages/react-devtools-shared/src/devtools/views/Components/ProfilerWhatChanged.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
.Component { | ||
margin-bottom: 1rem; | ||
} | ||
|
||
.Item { | ||
margin-top: 0.25rem; | ||
} | ||
|
||
.Key { | ||
font-family: var(--font-family-monospace); | ||
font-size: var(--font-size-monospace-small); | ||
line-height: 1; | ||
} | ||
|
||
.Key:first-of-type::before { | ||
content: ' ('; | ||
} | ||
|
||
.Key::after { | ||
content: ', '; | ||
} | ||
|
||
.Key:last-of-type::after { | ||
content: ')'; | ||
} | ||
|
||
.Label { | ||
font-weight: bold; | ||
margin-bottom: 0.5rem; | ||
} |
138 changes: 138 additions & 0 deletions
138
packages/react-devtools-shared/src/devtools/views/Components/ProfilerWhatChanged.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
/** | ||
* Copyright (c) Facebook, Inc. and its affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow | ||
*/ | ||
|
||
import React, {useContext} from 'react'; | ||
import {ProfilerContext} from '../Profiler/ProfilerContext'; | ||
import {StoreContext} from '../context'; | ||
|
||
import styles from './ProfilerWhatChanged.css'; | ||
|
||
type ProfilerWhatChangedProps = {| | ||
fiberID: number, | ||
|}; | ||
|
||
export default function ProfilerWhatChanged({ | ||
fiberID, | ||
}: ProfilerWhatChangedProps) { | ||
const {profilerStore} = useContext(StoreContext); | ||
const {rootID, selectedCommitIndex} = useContext(ProfilerContext); | ||
|
||
// TRICKY | ||
// Handle edge case where no commit is selected because of a min-duration filter update. | ||
// If the commit index is null, suspending for data below would throw an error. | ||
// TODO (ProfilerContext) This check should not be necessary. | ||
if (selectedCommitIndex === null) { | ||
return null; | ||
} | ||
|
||
const {changeDescriptions} = profilerStore.getCommitData( | ||
((rootID: any): number), | ||
selectedCommitIndex, | ||
); | ||
|
||
if (changeDescriptions === null) { | ||
return null; | ||
} | ||
|
||
const changeDescription = changeDescriptions.get(fiberID); | ||
if (changeDescription == null) { | ||
return null; | ||
} | ||
|
||
if (changeDescription.isFirstMount) { | ||
return ( | ||
<div className={styles.Component}> | ||
<label className={styles.Label}>Why did this render?</label> | ||
<div className={styles.Item}> | ||
This is the first time the component rendered. | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
const changes = []; | ||
|
||
if (changeDescription.context === true) { | ||
changes.push( | ||
<div key="context" className={styles.Item}> | ||
• Context changed | ||
</div>, | ||
); | ||
} else if ( | ||
typeof changeDescription.context === 'object' && | ||
changeDescription.context !== null && | ||
changeDescription.context.length !== 0 | ||
) { | ||
changes.push( | ||
<div key="context" className={styles.Item}> | ||
• Context changed: | ||
{changeDescription.context.map(key => ( | ||
<span key={key} className={styles.Key}> | ||
{key} | ||
</span> | ||
))} | ||
</div>, | ||
); | ||
} | ||
|
||
if (changeDescription.didHooksChange) { | ||
changes.push( | ||
<div key="hooks" className={styles.Item}> | ||
• Hooks changed | ||
</div>, | ||
); | ||
} | ||
|
||
if ( | ||
changeDescription.props !== null && | ||
changeDescription.props.length !== 0 | ||
) { | ||
changes.push( | ||
<div key="props" className={styles.Item}> | ||
• Props changed: | ||
{changeDescription.props.map(key => ( | ||
<span key={key} className={styles.Key}> | ||
{key} | ||
</span> | ||
))} | ||
</div>, | ||
); | ||
} | ||
|
||
if ( | ||
changeDescription.state !== null && | ||
changeDescription.state.length !== 0 | ||
) { | ||
changes.push( | ||
<div key="state" className={styles.Item}> | ||
• State changed: | ||
{changeDescription.state.map(key => ( | ||
<span key={key} className={styles.Key}> | ||
{key} | ||
</span> | ||
))} | ||
</div>, | ||
); | ||
} | ||
|
||
if (changes.length === 0) { | ||
changes.push( | ||
<div key="nothing" className={styles.Item}> | ||
The parent component rendered. | ||
</div>, | ||
); | ||
} | ||
|
||
return ( | ||
<div className={styles.Component}> | ||
<label className={styles.Label}>Why did this render?</label> | ||
{changes} | ||
</div> | ||
); | ||
} |
24 changes: 24 additions & 0 deletions
24
packages/react-devtools-shared/src/devtools/views/Components/Tooltip.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
.Tooltip { | ||
position: absolute; | ||
pointer-events: none; | ||
border: none; | ||
border-radius: 0.25rem; | ||
padding: 0.25rem 0.5rem; | ||
font-family: var(--font-family-sans); | ||
font-size: 12px; | ||
background-color: var(--color-tooltip-background); | ||
color: var(--color-tooltip-text); | ||
opacity: 1; | ||
/* Make sure this is above the DevTools, which are above the Overlay */ | ||
z-index: 10000002; | ||
} | ||
|
||
.Tooltip.hidden { | ||
opacity: 0; | ||
} | ||
|
||
|
||
.Container { | ||
width: -moz-max-content; | ||
width: -webkit-max-content; | ||
} |
106 changes: 106 additions & 0 deletions
106
packages/react-devtools-shared/src/devtools/views/Components/Tooltip.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/** @flow */ | ||
|
||
import React, {useRef} from 'react'; | ||
|
||
import styles from './Tooltip.css'; | ||
|
||
const initialTooltipState = {height: 0, mouseX: 0, mouseY: 0, width: 0}; | ||
|
||
export default function Tooltip({children, label}: any) { | ||
const containerRef = useRef(null); | ||
const tooltipRef = useRef(null); | ||
|
||
// update the position of the tooltip based on current mouse position | ||
const updateTooltipPosition = (event: SyntheticMouseEvent<*>) => { | ||
const element = tooltipRef.current; | ||
if (element != null) { | ||
// first find the mouse position | ||
const mousePosition = getMousePosition(containerRef.current, event); | ||
// use the mouse position to find the position of tooltip | ||
const {left, top} = getTooltipPosition(element, mousePosition); | ||
// update tooltip position | ||
element.style.left = left; | ||
element.style.top = top; | ||
} | ||
}; | ||
|
||
const onMouseMove = (event: SyntheticMouseEvent<*>) => { | ||
updateTooltipPosition(event); | ||
}; | ||
|
||
const tooltipClassName = label === null ? styles.hidden : ''; | ||
|
||
return ( | ||
<div | ||
className={styles.Container} | ||
onMouseMove={onMouseMove} | ||
ref={containerRef}> | ||
<div ref={tooltipRef} className={`${styles.Tooltip} ${tooltipClassName}`}> | ||
{label} | ||
</div> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
// Method used to find the position of the tooltip based on current mouse position | ||
function getTooltipPosition(element, mousePosition) { | ||
const {height, mouseX, mouseY, width} = mousePosition; | ||
const TOOLTIP_OFFSET_X = 5; | ||
const TOOLTIP_OFFSET_Y = 15; | ||
let top = 0; | ||
let left = 0; | ||
|
||
// Let's check the vertical position. | ||
if (mouseY + TOOLTIP_OFFSET_Y + element.offsetHeight >= height) { | ||
// The tooltip doesn't fit below the mouse cursor (which is our | ||
// default strategy). Therefore we try to position it either above the | ||
// mouse cursor or finally aligned with the window's top edge. | ||
if (mouseY - TOOLTIP_OFFSET_Y - element.offsetHeight > 0) { | ||
// We position the tooltip above the mouse cursor if it fits there. | ||
top = `${mouseY - element.offsetHeight - TOOLTIP_OFFSET_Y}px`; | ||
} else { | ||
// Otherwise we align the tooltip with the window's top edge. | ||
top = '0px'; | ||
} | ||
} else { | ||
top = `${mouseY + TOOLTIP_OFFSET_Y}px`; | ||
} | ||
|
||
// Now let's check the horizontal position. | ||
if (mouseX + TOOLTIP_OFFSET_X + element.offsetWidth >= width) { | ||
// The tooltip doesn't fit at the right of the mouse cursor (which is | ||
// our default strategy). Therefore we try to position it either at the | ||
// left of the mouse cursor or finally aligned with the window's left | ||
// edge. | ||
if (mouseX - TOOLTIP_OFFSET_X - element.offsetWidth > 0) { | ||
// We position the tooltip at the left of the mouse cursor if it fits | ||
// there. | ||
left = `${mouseX - element.offsetWidth - TOOLTIP_OFFSET_X}px`; | ||
} else { | ||
// Otherwise, align the tooltip with the window's left edge. | ||
left = '0px'; | ||
} | ||
} else { | ||
left = `${mouseX + TOOLTIP_OFFSET_X * 2}px`; | ||
} | ||
|
||
return {left, top}; | ||
} | ||
|
||
// method used to find the current mouse position inside the container | ||
function getMousePosition( | ||
relativeContainer, | ||
mouseEvent: SyntheticMouseEvent<*>, | ||
) { | ||
if (relativeContainer !== null) { | ||
const {height, top, width} = relativeContainer.getBoundingClientRect(); | ||
|
||
const mouseX = mouseEvent.clientX; | ||
const mouseY = mouseEvent.clientY - top; | ||
|
||
return {height, mouseX, mouseY, width}; | ||
} else { | ||
return initialTooltipState; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tiny nit, and I'm happy to do this post-merge- BUT! The new
Tooltip
andWhatChanged
components are only used by views in the "Profiler" tab, so they should be inviews/Profiler
rather thanviews/Components
(which is where views for the "Components" tab live). I would be happy to sort this out with agit mv
after the PR merge though!