Skip to content
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 Component Highlighting to Profiler DevTools #17934

Closed
wants to merge 15 commits into from

Conversation

M-Izadmehr
Copy link
Contributor

Summary: Fix #17928

Inside the devTools (Components) we have component highlighting, and by hovering the mouse on components it will highlight them in DOM. However, this feature was missing from Profiler (both FlameGraph Chart and Ranked Chart). The purpose of this PR is to add this functionality to the profiler.

Demo

devtools

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jan 30, 2020

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit ce9b3a8:

Sandbox Source
holy-tree-1kvll Configuration

@sizebot
Copy link

sizebot commented Jan 30, 2020

No significant bundle size changes to report.

Size changes (stable)

Generated by 🚫 dangerJS against ce9b3a8

@sizebot
Copy link

sizebot commented Jan 30, 2020

No significant bundle size changes to report.

Size changes (experimental)

Generated by 🚫 dangerJS against ce9b3a8

Comment on lines 121 to 146
const highlightNativeElement = useCallback(
(id: number) => {
const element = store.getElementByID(id);
const rendererID = store.getRendererIDForElement(id);
if (element !== null && rendererID !== null) {
bridge.send('highlightNativeElement', {
displayName: element.displayName,
hideAfterTimeout: false,
id,
openNativeElementsPanel: false,
rendererID,
scrollIntoView: false,
});
}
},
[store, bridge],
);

// Highlight last hovered element.
const handleElementMouseEnter = useCallback(
id => {
highlightNativeElement(id);
},
[highlightNativeElement],
);

Copy link

@strdr4605 strdr4605 Jan 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is also repeated in packages/react-devtools-shared/src/devtools/views/Profiler/CommitRanked.js and also in
packages/react-devtools-shared/src/devtools/views/Components/Tree.js.

Maybe better to create a separate hook file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment, Yeah I absolutely agree, I will try to use a hook for the highlighting functionality:)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, now I extracted the highlighting logic to the hooks file, and created a hook called useNativeElementHighlighter, this hook exposes two methods:

  1. highlightNativeElement: method used to highlight an element using its id
  2. clearNativeElementHighlight: method used to clear highlighting of elements

);

// Highlight last hovered element.
const handleElementMouseEnter = useCallback(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@M-Izadmehr, can you please explain why you wrap in another useCallback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done:)
I removed the useless useCallback.

@bvaughn bvaughn self-assigned this Jan 30, 2020
@bvaughn
Copy link
Contributor

bvaughn commented Jan 30, 2020

I'm going to hold off doing a review for now since it sounds like there are planned changes. 😄

@M-Izadmehr
Copy link
Contributor Author

M-Izadmehr commented Feb 2, 2020

@bvaughn As a response to the reviews I made the following changes:

  1. I extracted the highlighting logic to the hooks file, and created a hook called useNativeElementHighlighter, which exposes two methods (highlightNativeElement and clearNativeElementHighlight).
  2. Added clear highlighting when you leave the element
  3. Added a logic when leaving an element, if another element is already selected use highlighting on that (see the gif)

ezgif-updated

I guess it is now in a good state to be merged:))

@bvaughn
Copy link
Contributor

bvaughn commented Feb 2, 2020

Might be worth profiling the profiler (in the test shell) before and after this change to see if we added any extra renders or otherwise did anything that would cause a regression in the flame chart views.

I'll review this sometime in the next couple of days though~

@bvaughn
Copy link
Contributor

bvaughn commented Feb 4, 2020

Might be worth profiling the profiler (in the test shell) before and after this change to see if we added any extra renders or otherwise did anything that would cause a regression in the flame chart views.

Any chance you were able to do this? 😁

@bvaughn
Copy link
Contributor

bvaughn commented Feb 4, 2020

I did a quick test. I don't see any big perf regressions, but I do see a UX problem: Highlights always get stuck. Looks like anytime I'm not moused over the root flamegraph node, its DOM element is highlighted - and mousing out of the graph doesn't clear the highlight either, leaving it stuck on the page.

@M-Izadmehr
Copy link
Contributor Author

M-Izadmehr commented Feb 5, 2020

Thanks for the comment :)
Yeah, I think that was a bad decision on my side because I thought it might be a good idea if a fiber is already selected and the user is not hovering an element, highlight the clicked fiber. However, it results in some bad implications:

  1. If the user redirects to a new page (where the fiber does not exist anymore) the highlight still remains on the page.
  2. Also, in this way, we should keep track of valid fibers

As a result, I removed that unnecessary logic (ao that logic will be similar to components devtools).

@@ -115,9 +122,21 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
return null;
}, [chartData, selectedFiberID, selectedChartNodeIndex]);

// Highlight last hovered element.
const handleElementMouseEnter = id => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary function wrapper. I'd suggest just passing highlightNativeElement directly (or if you think the naming is clearer, just alias, e.g. const handleElementMouseEnter = highlightNativeElement )

Copy link
Contributor Author

@M-Izadmehr M-Izadmehr Mar 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After merging the other PR, this method now does two actions:

  1. Adding the highlighting,
  2. Setting the fiberData used for the tooltip,
    So, I guess we can keep the wrapper.


const selectedFiberIndex = useMemo(
() => getNodeIndex(chartData, selectedFiberID),
[chartData, selectedFiberID],
);

// Highlight last hovered element.
const handleElementMouseEnter = id => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment:)

@M-Izadmehr
Copy link
Contributor Author

Thanks for the comments,
I think, as two pull requests will have minor conflicts with each other as both are working on hovering the fiber and adding some custom event listeners to mouse, I think it might be better to first merge one of them and afterward I will apply the reviews to this one (as some of the changes are already fixed in the other one).

@bvaughn
Copy link
Contributor

bvaughn commented Feb 19, 2020

Sounds good @M-Izadmehr. The other PR has been merged!

@bvaughn
Copy link
Contributor

bvaughn commented Feb 27, 2020

@M-Izadmehr Still planning to update this PR? Or would you like me to finish it up?

@M-Izadmehr
Copy link
Contributor Author

@bvaughn Sorry last week was super busy with some releases, I actually wanted to do it in the weekend, does it sound good?:)

@bvaughn
Copy link
Contributor

bvaughn commented Feb 27, 2020

Sure sounds fine.

@M-Izadmehr
Copy link
Contributor Author

Sorry for the late update, I merged the other stuff to this PR, now it seems stable, I don't see any additional re-renders:)

Here is a demo with both tooltip and highlighting:
ezgif-7-3bd435cc231a

Copy link
Contributor

@bvaughn bvaughn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While reviewing this, I realized that if you load exported profiling data from a previous session, it would end up highlighting arbitrary components on the page you're currently viewing (since ids are just auto-incremented numbers). We could use actual GUIDs- less likely to conflict, but I don't really want to add the extra overhead. Maybe instead we should track whether the profiler data was imported or recorded live, and...disable the hover feature for imported data.

I don't know. I might want to think on that a little, although if you have any ideas I"d be open to discussing them 😄

@@ -96,9 +99,13 @@ type Props = {|
|};

function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
const [hoveredFiberData, hoverFiber] = useState<number | null>(null);
const [hoveredFiberData, setHoveredFiberData] = useState<number | null>(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Flow type looks wrong. (Looks like it's been wrong for a while though FWIW- which is a bit concerning.)

We aren't storing a number in this state, but an object with id and name props (TooltipFiberData).

const itemData = useMemo<ItemData>(
() => ({
chartData,
hoverFiber,
isHovered: hoveredFiberData && hoveredFiberData.id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this check not just hoveredFiberData !== null?

@@ -140,7 +159,7 @@ function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
}),
[
chartData,
hoverFiber,
setHoveredFiberData,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong. The dependencies array has setHoveredFiberData but the memoized value references hoveredFiberData.

I wonder why our lint rule isn't complaining about this.

hoverFiber,
isHovered: hoveredFiberData && hoveredFiberData.id,
onElementMouseEnter: handleElementMouseEnter,
onElementMouseLeave: handleElementMouseLeave,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong. Neither handleElementMouseEnter nor handleElementMouseLeave are mentioned in the dependencies array.

I wonder why our lint rule isn't complaining about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

@@ -94,19 +97,35 @@ type Props = {|
|};

function CommitRanked({chartData, commitTree, height, width}: Props) {
const [hoveredFiberData, hoverFiber] = useState<number | null>(null);
const [hoveredFiberData, setHoveredFiberData] = useState<number | null>(null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above wrt hook state type.

hoverFiber,
isHovered: hoveredFiberData && hoveredFiberData.id,
onElementMouseEnter: handleElementMouseEnter,
onElementMouseLeave: handleElementMouseLeave,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above wrt missing dependencies.

const itemData = useMemo<ItemData>(
() => ({
chartData,
hoverFiber,
isHovered: hoveredFiberData && hoveredFiberData.id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above wrt hoveredFiberData !== null

@bvaughn bvaughn mentioned this pull request Mar 3, 2020
@bvaughn
Copy link
Contributor

bvaughn commented Mar 3, 2020

Some follow up from my review yesterday.

Turns out our Flow coverage was really broken for DevTools. Oops! Fixed with #18204.

The "rules of hooks" lint rule isn't being run on this repo, but should be. I'll look to add it with a separate PR.

@M-Izadmehr
Copy link
Contributor Author

Oh I see, I can also try to check it out if we are using it on all the packages

@M-Izadmehr
Copy link
Contributor Author

@bvaughn Yeah, you are right, we can check if the element is imported, I think in the tooltip we already have information on that part (only if we are listening for changes for that), we can re-use the logic there for the highlighter too.
One simple way without performance reduction is to only enable highlighter, only when why rendered is true.
I will play around with it to see if there is any optimal solution for this:)

@gaearon
Copy link
Collaborator

gaearon commented Apr 1, 2020

@M-Izadmehr Would be great if we could get this landed. :-)

@M-Izadmehr
Copy link
Contributor Author

@gaearon Yeah, please don't close this one, I was really busy in the past month and couldn't come back to this issue, I will try to pick it up in the weekend (thanks to quarantine, we have so much free time now:) )

@bvaughn
Copy link
Contributor

bvaughn commented Apr 3, 2020

Hey @M-Izadmehr, PR #18479 is is going to cause a bit of a conflict for you b'c it also separates the highlight behavior into a hook. Looks like the hook is the same as yours though, so it should be easy for you to rebase and just use it instead. Just wanted to give you a heads up!

@gaearon
Copy link
Collaborator

gaearon commented Apr 9, 2020

@M-Izadmehr Friendly ping :-) Do you think you might be able to get this over the finish line?

@bl00mber
Copy link
Contributor

Hey @M-Izadmehr can you please explain how you managed to request a review and not be a member at the same time? 🤔

@M-Izadmehr
Copy link
Contributor Author

@bl00mber when you are finished with your stuff, you can ask @bvaughn to check it out (as it is related to DevTools, and he is the DevTools god:) )

@bvaughn
Copy link
Contributor

bvaughn commented Jun 19, 2020

@bl00mber I've reviewed several of your PRs in the past month or so 😄

It's important to keep in mind that there are only a half dozen or so React core members trying to keep up with PRs from > 1,000 contributors (while at the same time creating our own as well).

@bl00mber
Copy link
Contributor

@bvaughn yeah thanks, I've actually do not wanted to request a review now (may be only on stalled PRs), was just curious how the author of this PR is able to do it.

@stale
Copy link

stale bot commented Sep 20, 2020

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Sep 20, 2020
@stale
Copy link

stale bot commented Oct 4, 2020

Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you!

@stale stale bot closed this Oct 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Resolution: Stale Automatically closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Profiler should highlight host components (e.g. DOM elements) on mouseover
9 participants