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

refactor: routes.component.js and creation of ToastMaster #27735

Open
wants to merge 34 commits into
base: develop
Choose a base branch
from

Conversation

HowardBraham
Copy link
Contributor

@HowardBraham HowardBraham commented Oct 9, 2024

Description

This PR is part of the larger effort to add React.lazy to routes.

I would be very surprised if I did not break functionality somewhere, as this was a large and tough refactor. It needs some good QA, and Harika has been doing some testing that has passed.

Summary of what I did:

  • Pulled all Toast-related logic out of routes.component.js and into the new ToastMaster
  • Pulled everything else I could pull out into routes-helpers.js
  • Moved things out of the general selectors.js and actions.ts into toast-master-selectors.ts
  • Simplification of some Pointless Thunks™

For later PRs:

  • Convert routes.component.js to TypeScript
  • Convert routes.component.js to Hooks
  • Convert routes.component.js to react-router v6
  • Eliminate routes.container.js

Open in GitHub Codespaces

Related issues

Progresses: MetaMask/MetaMask-planning#2898

Manual testing steps

Test all the Routes and all the Toasts

Screenshots/Recordings

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@HowardBraham HowardBraham added the team-tiger Tiger team (for tech debt reduction + performance improvements) label Oct 9, 2024
@HowardBraham HowardBraham self-assigned this Oct 9, 2024
Copy link
Contributor

github-actions bot commented Oct 9, 2024

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@github-actions github-actions bot added the team-contributor-experience MetaMask Contributor Experience Group label Oct 9, 2024
@HowardBraham HowardBraham removed the team-contributor-experience MetaMask Contributor Experience Group label Oct 9, 2024
Comment on lines 99 to 106
export function setNewPrivacyPolicyToastShownDate(time: number) {
submitRequestToBackground('setNewPrivacyPolicyToastShownDate', [time]);
}

export function setNewPrivacyPolicyToastClickedOrClosed() {
submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed');
}
Copy link
Contributor

@MajorLift MajorLift Oct 9, 2024

Choose a reason for hiding this comment

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

I believe submitRequestToBackground would still need to be awaited/thenned even if it's no longer being called inside of a thunk callback.

As I understand it, the motivation for removing this function call from redux would be to turn the thunks it used to be in into synchronous actions, but this doesn't change the fact that submitRequestToBackground itself returns a promise.

Suggested change
export function setNewPrivacyPolicyToastShownDate(time: number) {
submitRequestToBackground('setNewPrivacyPolicyToastShownDate', [time]);
}
export function setNewPrivacyPolicyToastClickedOrClosed() {
submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed');
}
export async function setNewPrivacyPolicyToastShownDate(time: number) {
await submitRequestToBackground('setNewPrivacyPolicyToastShownDate', [time]);
}
export async function setNewPrivacyPolicyToastClickedOrClosed() {
await submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed');
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Gudahtt I don't think there's any reason to wait for this to complete, but what do you think?

Copy link
Member

@matthewwalsh0 matthewwalsh0 Oct 11, 2024

Choose a reason for hiding this comment

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

My understanding is also that any requests to the background rely on the JSON-RPC API over the port streams and are therefore asynchronous, meaning we should await since submitRequestToBackground returns a promise.

I recall a contribution guideline that also recommends awaiting even when returning as it results in better stack traces in the event of errors.

https://github.com/MetaMask/contributor-docs/blob/main/docs/javascript.md#await-promises-before-returning-them

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 did a little research and experimentation on this, screenshots below. My conclusion is that our contributor guidelines may be incorrect, unless someone can show me a counterexample.

In this specific case, we actually do NOT want to wait for this. Waiting would slow down the main thread, and we don't need to wait for anything to happen before continuing.

Fake error in foreground, no async await

image

Fake error in foreground, with async await

image

Fake error in background, no async await

image

Fake error in background, with async await

image

Copy link
Contributor

Choose a reason for hiding this comment

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

By newly introducing floating promises into the codebase, wouldn't we be triggering uncaught exceptions that will crash the process (like the ones shown in the screenshots), when before this wasn't a concern?

I assume that for these background requests the consistency requirements are so weak that we don't need to log their failures. By the same token, aren't they also unimportant enough that we shouldn't be crashing the app over their failure?

Regarding the screenshots, I'm finding it confusing to understand what the promise chains being tested are. In the experiments above, what would be the equivalents of a, b, c from https://mathiasbynens.be/notes/async-stack-traces?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From that example, a is setNewPrivacyPolicyToastShownDate, b is submitRequestToBackground, and c is continuing on with function PrivacyPolicyToast(). But c doesn't have to wait for b to finish.

Copy link
Contributor

@MajorLift MajorLift Oct 16, 2024

Choose a reason for hiding this comment

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

I think we might be conflating the following in this discussion:

b.then(() => c());
b.then(() => ...);
c();

In the second example, execution for the b.then() clause does not synchronously block c.

With that in mind, how would you feel about at least removing the possibility of uncaught exceptions by adding error handling without relying on async/await syntax?

export function setNewPrivacyPolicyToastClickedOrClosed() {
  submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed')
    .catch((error) => ...);
}

In general, floating promises are forbidden by our eslint config with the @typescript-eslint/no-floating-promises rule, which is also included in the recommended ruleset for @typescript-eslint. The only reason that error isn't showing up here is because extension is using eslint configs that are three years old.

Edit: The reason I'm mentioning the lint rule is because someone will be fixing this code down the line so that it conforms with no-floating-promises. If we want to avoid the code from being altered a comment should be left at the very least, or a catch clause added so this is not flagged as a violation. Adding error handling would be the safe thing to do as well.

Copy link
Contributor

@MajorLift MajorLift Oct 16, 2024

Choose a reason for hiding this comment

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

a is setNewPrivacyPolicyToastShownDate, b is submitRequestToBackground, and c is continuing on with function PrivacyPolicyToast()

Having a bit of trouble understanding this. Wouldn't PrivacyPolicyToast be a since it's the outermost scope that the other two are nested in? a being an async function that contains b, c seems like an unalterable part of the premise discussed in the article.

Maybe the second half of a that follows b could be formulated as c, but then we don't seem to be examining the stack trace for an error in c? Not to mention a isn't an async function since it's a react component.

This discussion probably belongs in a dedicated ticket in contributor-docs. I think having the test code available would be helpful as well.

Comment on lines 99 to 119
export function setNewPrivacyPolicyToastShownDate(time: number) {
submitRequestToBackground('setNewPrivacyPolicyToastShownDate', [time]);
}

export function setNewPrivacyPolicyToastClickedOrClosed() {
submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed');
}

export function getNftDetectionEnablementToast(state: State): boolean {
return Boolean(state.appState?.showNftDetectionEnablementToast);
}

export function setShowNftDetectionEnablementToast(
value: boolean,
): PayloadAction<string | ReactFragment | undefined> {
return {
type: SHOW_NFT_DETECTION_ENABLEMENT_TOAST,
payload: value,
};
}
Copy link
Contributor

@MajorLift MajorLift Oct 9, 2024

Choose a reason for hiding this comment

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

The three setters here are actions not selectors. It looks like the first two are no longer returning callbacks for thunks but the last one is still a redux action. Should all three be in this 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.

Oh yes, I thought about this one. Do we prefer renaming this file to toast-master-selectors-and-actions.ts or creating a new toast-master-actions.ts?

Copy link
Contributor

@MajorLift MajorLift Oct 9, 2024

Choose a reason for hiding this comment

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

Both would work. I agree with the idea of removing these from the global actions.ts and co-locating them with other toast things.

Maybe a separate toast-master-actions.ts would be just a little bit better for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@Gudahtt I thought about it a little more, and actually, are plain submitRequestToBackground functions actions at all?

Copy link
Member

@matthewwalsh0 matthewwalsh0 Oct 11, 2024

Choose a reason for hiding this comment

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

Definitely would recommend a separate file for these, but no the submitRequestToBackground ones are not technically Redux actions as we're not dispatching them on a reducer, ultimately just utils.

@metamaskbot
Copy link
Collaborator

Builds ready [0eb601f]
Page Load Metrics (1892 ± 86 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint38321621787347166
domContentLoaded15282425186817684
load15362436189217886
domInteractive14107402110
Bundle size diffs [🚀 Bundle size reduced!]
  • background: 0 Bytes (0.00%)
  • ui: 3.7 KiB (0.05%)
  • common: -2.32 KiB (-0.03%)

@gauthierpetetin gauthierpetetin added area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. type-tech-debt Technical debt labels Oct 9, 2024
@metamaskbot
Copy link
Collaborator

Builds ready [789eb79]
Page Load Metrics (1815 ± 96 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint42625951754377181
domContentLoaded15892362177318488
load16082450181519996
domInteractive267443168
Bundle size diffs [🚀 Bundle size reduced!]
  • background: 0 Bytes (0.00%)
  • ui: 3.66 KiB (0.05%)
  • common: -2.32 KiB (-0.03%)

ui/components/app/toast-master/toast-master.js Outdated Show resolved Hide resolved
ui/components/app/toast-master/toast-master.js Outdated Show resolved Hide resolved
ui/components/app/toast-master/toast-master.js Outdated Show resolved Hide resolved
ui/components/app/toast-master/toast-master.js Outdated Show resolved Hide resolved
ui/components/app/toast-master/toast-master-selectors.ts Outdated Show resolved Hide resolved
ui/components/app/toast-master/toast-master-selectors.ts Outdated Show resolved Hide resolved
ui/pages/routes/routes-helpers.js Outdated Show resolved Hide resolved
ui/components/app/toast-master/toast-master.js Outdated Show resolved Hide resolved
Comment on lines 99 to 119
export function setNewPrivacyPolicyToastShownDate(time: number) {
submitRequestToBackground('setNewPrivacyPolicyToastShownDate', [time]);
}

export function setNewPrivacyPolicyToastClickedOrClosed() {
submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed');
}

export function getNftDetectionEnablementToast(state: State): boolean {
return Boolean(state.appState?.showNftDetectionEnablementToast);
}

export function setShowNftDetectionEnablementToast(
value: boolean,
): PayloadAction<string | ReactFragment | undefined> {
return {
type: SHOW_NFT_DETECTION_ENABLEMENT_TOAST,
payload: value,
};
}
Copy link
Member

@matthewwalsh0 matthewwalsh0 Oct 11, 2024

Choose a reason for hiding this comment

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

Definitely would recommend a separate file for these, but no the submitRequestToBackground ones are not technically Redux actions as we're not dispatching them on a reducer, ultimately just utils.

Comment on lines 99 to 106
export function setNewPrivacyPolicyToastShownDate(time: number) {
submitRequestToBackground('setNewPrivacyPolicyToastShownDate', [time]);
}

export function setNewPrivacyPolicyToastClickedOrClosed() {
submitRequestToBackground('setNewPrivacyPolicyToastClickedOrClosed');
}
Copy link
Member

@matthewwalsh0 matthewwalsh0 Oct 11, 2024

Choose a reason for hiding this comment

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

My understanding is also that any requests to the background rely on the JSON-RPC API over the port streams and are therefore asynchronous, meaning we should await since submitRequestToBackground returns a promise.

I recall a contribution guideline that also recommends awaiting even when returning as it results in better stack traces in the event of errors.

https://github.com/MetaMask/contributor-docs/blob/main/docs/javascript.md#await-promises-before-returning-them

@HowardBraham HowardBraham force-pushed the refactor-routes branch 2 times, most recently from ab9ab05 to 553bbe2 Compare October 12, 2024 06:05
@metamaskbot
Copy link
Collaborator

Builds ready [553bbe2]
Page Load Metrics (1858 ± 70 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint30021461774359172
domContentLoaded1601199118029646
load16252327185814770
domInteractive1884522010
Bundle size diffs [🚀 Bundle size reduced!]
  • background: 0 Bytes (0.00%)
  • ui: 3.66 KiB (0.05%)
  • common: -2.31 KiB (-0.03%)

@HowardBraham HowardBraham force-pushed the refactor-routes branch 3 times, most recently from 965772e to 7a54b36 Compare October 14, 2024 10:03
@metamaskbot
Copy link
Collaborator

Builds ready [7a54b36]
Page Load Metrics (2010 ± 314 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint48046071876703338
domContentLoaded156444361978650312
load161444972010653314
domInteractive26143502914
Bundle size diffs [🚀 Bundle size reduced!]
  • background: 0 Bytes (0.00%)
  • ui: 6.1 KiB (0.08%)
  • common: -2.68 KiB (-0.04%)

@metamaskbot
Copy link
Collaborator

Builds ready [2aad6bf]
Page Load Metrics (1779 ± 41 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint1655197617879144
domContentLoaded1644195317567838
load1656196117798541
domInteractive247845189
Bundle size diffs [🚀 Bundle size reduced!]
  • background: 0 Bytes (0.00%)
  • ui: 5.36 KiB (0.07%)
  • common: -2.68 KiB (-0.04%)

@metamaskbot
Copy link
Collaborator

Builds ready [bfd7b86]
Page Load Metrics (1828 ± 76 ms)
PlatformPageMetricMin (ms)Max (ms)Average (ms)StandardDeviation (ms)MarginOfError (ms)
ChromeHomefirstPaint15182202183014871
domContentLoaded14732023177611656
load14812215182815976
domInteractive16102512311
backgroundConnect9258556631
firstReactRender512851216230
getState4118283014
initialActions00000
loadScripts1071152713259445
setupStore10122393517
uiStartup166530632147329158
Bundle size diffs [🚀 Bundle size reduced!]
  • background: 0 Bytes (0.00%)
  • ui: 5.48 KiB (0.07%)
  • common: -2.67 KiB (-0.03%)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-performance Issues relating to slowness of app, cpu usage, and/or blank screens. team-tiger Tiger team (for tech debt reduction + performance improvements) type-tech-debt Technical debt
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants