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

Skip redux middleware frames on jumping to location #9485

Closed
wants to merge 18 commits into from

Conversation

Mokshit06
Copy link
Contributor

Experimenting with using babel to figure out which pause frames are of redux middlewares instead of dispatch.
It finds the identifier at the column and line where the first user frame paused and matches if the parent functions have the pattern store => next => action => {} using babel traversal. If so then skips that frame and moves to the next one.

This is a very basic test, and needs to check for more cases, like checking at each step that the parent function is returned the child one. eg. in next => action => {}, action => {} is the child function. It's parent function is next => {}, and next is returned action.

@vercel
Copy link

vercel bot commented Jul 11, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
devtools ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jul 31, 2023 6:50pm

@replay-io
Copy link

replay-io bot commented Jul 11, 2023

E2E Tests

71 replays were recorded for d1fe5da.

image 0 Failed
image 71 Passed

View test run on Replay ↗︎

Snapshot Tests

104 replays were recorded for d609cd2.

image 0 Failed
image 104 Passed

View test run on Replay ↗︎

} catch (e) {
return false;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

it'd be nice to write some jest unit tests for this function

describe("formatDuration", () => {

describe("getNonLoadingRegionTimesFromLoadingRegions", () => {

step.location.sourceId === preferredLocation.sourceId &&
step.location.line === preferredLocation.line
);
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that this function is getting more complex, it might be nice to pull this logic for getting the matching frame step into a util

function getMatchingFrameStep(frameSteps, preferredLocation) {
  ...
}

btw i'm not sure if this is the best API.

Copy link
Collaborator

Choose a reason for hiding this comment

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

FWIW the real answer here is to implement https://linear.app/replay/issue/BAC-2915/stack-frames-should-come-with-execution-points and stop trying to calculate this ourselves.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've moved it into a separate util for now since ReactPanel.tsx and ReduxDevtools.tsx are doing the same thing but yes having frames have their execution point would make it alot simpler

@Mokshit06
Copy link
Contributor Author

Mokshit06 commented Jul 11, 2023

This should now be able to give more accurate results. It can identify code like store => next => action => {} and store => next => { return action => {} } and any combination of implicit & explicit returns as redux middleware.

Need to add code for handling cases where the callback gets declared first, and is later returned

@markerikson
Copy link
Collaborator

Not sure this is working as intended atm. If I look at https://devtools-git-fork-mokshit06-redux-skip-middleware-recordreplay.vercel.app/recording/breakpoints-01-test-basic-breakpoint-functionality--326d54a3-1523-411a-95ca-018537f5a887 , the jump locations are all still landing inside of the context middleware.

@Mokshit06
Copy link
Contributor Author

Mokshit06 commented Jul 11, 2023

Oh I know why this example isn't working. Babel isn't able to parse the typescript code. Will need to include typescript plugin for ts/tsx files. Try running it on JS examples like https://devtools-dk0k2y9tu-recordreplay.vercel.app/recording/e2etestscenariosjoinsreproductions22859-multi-nested-joins-wrong-aliasingcyspecjs--77ed307a-f284-4d5d-b0e0-b5176d252326

@markerikson
Copy link
Collaborator

@Mokshit06 I just realized there might be a simpler approach here.

All Redux middleware are going to be going through applyMiddleware():

image

Can we do something to find the dispatch method in the call stack that was defined inside of applyMiddleware(), and go back one frame before that?

@Mokshit06
Copy link
Contributor Author

Oh that's a good point. How often does the name/path of this file change? Maybe that could be a good way to identify if it's the correct step. Another option could be to go through all the frame steps and based on the sourceOutlineCache check if the current pause location is between a function named applyMiddleware

@markerikson
Copy link
Collaborator

The filename shouldn't change, but there are some funky edge cases I've found where there can be more than one copy of node_modules/redux/es/redux.js in a project. In one case, Redux was getting added at the top level as node_modules/redux/, but also under node_modules/redux/@redux-saga/something/node_modules/redux/. So, there were two copies of redux.js, and only one of them was getting hit.

Was working on some of that logic over in #9410 for the perf analysis POC.

Want to try grabbing some of that "find the right Redux file" code from there and use it here to identify the right method here? I think you can actually borrow a bunch of the logic that was already trying to find "the right dispatch method inside of createStore", except it should be "inside of applyMiddleware instead".

@@ -79,6 +86,41 @@ export const ReduxDevToolsPanel = () => {
);
};

async function isInsideApplyMiddlwareFn(
Copy link
Collaborator

Choose a reason for hiding this comment

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

✋ It'll be smoother if we do the applyMiddleware function lookup once, then have a separate function that does the "is frame in function declaration" comparison synchronously

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not sure this is a good place for this code to go, but I also don't have a better suggestion atm :)

@markerikson
Copy link
Collaborator

Looks reasonable at this point - can you fix up the merge conflict?

@markerikson
Copy link
Collaborator

Hmm. I just tried it on a Metabase recording, and ended up exactly inside of the original saveAnnotations() call:

I definitely see the applyMiddleware -> dispatch method in the call stack, so we should have found that.

Can you do some more looking into this?

Comment on lines 163 to 202
dispatch(seek(jumpLocation.point, jumpLocation.time, true));
dispatch(seek(point, time, true));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah so while resolving the conflict I see this got changed to the initial point and time rather than the calculated ones.

I'm out right now but will fix this.

@jasonLaster
Copy link
Collaborator

@Mokshit06 looks like this needs a rebase

@Mokshit06
Copy link
Contributor Author

I tried this on a recording of the Redux starter template and a call to applyMiddleware() isn't there in the call stack for some reason, so it instead jumps inside an RTK middleware instead of the preferable onClick handler.

https://devtools-bihivkc27-recordreplay.vercel.app/recording/replay-of-localhost5173--b90a2bd3-563a-4682-9c6e-bdddc61c7042

@markerikson
Copy link
Collaborator

Per discussion in Discord, Mokshit is going to try looking at figuring out "are we in a middleware" by looking just at source outline data to determine if this is a triply-nested function.

@Mokshit06
Copy link
Contributor Author

@markerikson This seems to be working now. It tries to search the call stack initially for applyMiddlware call. If it finds one then uses that frame, else tries the source outline data, and if both fail then jumps to the third frame (the one after the 2 replay patched steps).

@markerikson
Copy link
Collaborator

Hah, I hate to keep asking you to make more tweaks, but I see one more filename we should exclude: bindActionCreators . Example, see https://devtools-git-fork-mokshit06-redux-skip-middleware-recordreplay.vercel.app/recording/breakpoints-01-test-basic-breakpoint-functionality--326d54a3-1523-411a-95ca-018537f5a887 , and jump to SET_FOCUSED_SOURCE_ITEM , which was dispatched by an older-style connect()-wrapped component.

@markerikson
Copy link
Collaborator

Okay, looks good! Just need to fix the merge conflicts and I think this is ready!

@markerikson
Copy link
Collaborator

Huh, looks like this is missing a process.env.AUTHENTICATED_TESTS_WORKSPACE_API_KEY somehow? that's causing all the E2E tests to fail and bail out.

@Mokshit06
Copy link
Contributor Author

Hm that's weird, I don't see anything in this PR that would change the env variables

@markerikson
Copy link
Collaborator

Superseded by #9573 .

@markerikson markerikson closed this Aug 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants