From 7b33133e723f2e9049ef9233201cb4c393e904cc Mon Sep 17 00:00:00 2001 From: Prithpal Sooriya Date: Tue, 18 Jun 2024 20:44:06 +0100 Subject: [PATCH 1/5] fix: Fix unit and e2e tests that were expected the date based privacy policy components to not be shown (#25390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Fixture / mock data in unit and e2e tests have been modified to handle the privacy policy toast. These tests previously assumed that the privacy policy toast, and in one case the updated metametrics component during onboarding, would not be shown. However, their display was date dependent. With this PR, the updated test data takes these dates into account, and tests should now pass. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25390?quickstart=1) ## **Related issues** Fixes: failing tests on develop ## **Manual testing steps** e2e and unit tests should pass ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --------- Co-authored-by: Dan Miller --- test/e2e/default-fixture.js | 2 + ...rs-after-init-opt-in-background-state.json | 4 +- .../errors-after-init-opt-in-ui-state.json | 4 +- ...s-before-init-opt-in-background-state.json | 2 + .../errors-before-init-opt-in-ui-state.json | 2 + .../__snapshots__/metametrics.test.js.snap | 85 +++++++++---------- .../onboarding-flow/onboarding-flow.test.js | 4 +- ui/pages/routes/routes.component.test.js | 13 ++- 8 files changed, 58 insertions(+), 58 deletions(-) diff --git a/test/e2e/default-fixture.js b/test/e2e/default-fixture.js index 98413671caca..b3075f3319ca 100644 --- a/test/e2e/default-fixture.js +++ b/test/e2e/default-fixture.js @@ -107,6 +107,8 @@ function defaultFixture(inputChainId = CHAIN_IDS.LOCALHOST) { '__FIXTURE_SUBSTITUTION__currentDateInMilliseconds', showTestnetMessageInDropdown: true, trezorModel: null, + newPrivacyPolicyToastClickedOrClosed: true, + newPrivacyPolicyToastShownDate: Date.now(), usedNetworks: { [CHAIN_IDS.MAINNET]: true, [CHAIN_IDS.LINEA_MAINNET]: true, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 8ab0a3b93cd0..dddbe2b554f4 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -39,8 +39,8 @@ "showAccountBanner": true, "trezorModel": null, "onboardingDate": "object", - "newPrivacyPolicyToastClickedOrClosed": "object", - "newPrivacyPolicyToastShownDate": "object", + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index 93650970b9a9..96dc9ca07e48 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -75,8 +75,8 @@ "showAccountBanner": true, "trezorModel": null, "onboardingDate": "object", - "newPrivacyPolicyToastClickedOrClosed": "object", - "newPrivacyPolicyToastShownDate": "object", + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "hadAdvancedGasFeesSetPriorToMigration92_3": false, "nftsDropdownState": {}, "termsOfUseLastAgreed": "number", diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json index 60379a1d0811..dd90d1071776 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-background-state.json @@ -46,6 +46,8 @@ "0x5": true, "0x539": true }, + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { diff --git a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json index 48392c6b4db3..fc4bb1d65eed 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-before-init-opt-in-ui-state.json @@ -46,6 +46,8 @@ "0x5": true, "0x539": true }, + "newPrivacyPolicyToastClickedOrClosed": "boolean", + "newPrivacyPolicyToastShownDate": "number", "snapsInstallPrivacyWarningShown": true }, "CurrencyController": { diff --git a/ui/pages/onboarding-flow/metametrics/__snapshots__/metametrics.test.js.snap b/ui/pages/onboarding-flow/metametrics/__snapshots__/metametrics.test.js.snap index f669f81f59d7..c68e7759e419 100644 --- a/ui/pages/onboarding-flow/metametrics/__snapshots__/metametrics.test.js.snap +++ b/ui/pages/onboarding-flow/metametrics/__snapshots__/metametrics.test.js.snap @@ -4,7 +4,7 @@ exports[`Onboarding Metametrics Component should match snapshot 1`] = `

- MetaMask would like to gather usage data to better understand how our users interact with MetaMask. This data will be used to provide the service, which includes improving the service based on your use. + We’d like to gather basic usage and diagnostics data to improve MetaMask. Know that we never sell the data you provide here.

- MetaMask will... + When we gather metrics, it will always be...

    -
  • - - Always allow you to opt-out via Settings -
  • -
  • - - Send anonymized click and pageview events -
  • @@ -50,9 +36,9 @@ exports[`Onboarding Metametrics Component should match snapshot 1`] = ` - Never + Private: - collect information we don’t need to provide the service (such as keys, addresses, transaction hashes, or balances) + clicks and views on the app are stored, but other details (like your public address) are not.
    @@ -62,8 +48,8 @@ exports[`Onboarding Metametrics Component should match snapshot 1`] = ` class="box box--flex-direction-row" > @@ -71,9 +57,9 @@ exports[`Onboarding Metametrics Component should match snapshot 1`] = ` - Never + General: - collect your full IP address* + we temporarily use your IP address to detect a general location (like your country or region), but it's never stored.

@@ -83,8 +69,8 @@ exports[`Onboarding Metametrics Component should match snapshot 1`] = ` class="box box--flex-direction-row" > @@ -92,42 +78,47 @@ exports[`Onboarding Metametrics Component should match snapshot 1`] = ` - Never + Optional: - sell data. Ever! + you decide if you want to share or delete your usage data via settings any time.
-
- This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. -
+ + + + + We’ll use this data to learn how you interact with our marketing communications. We may share relevant news (like product features). + +
- * When you use Infura as your default RPC provider in MetaMask, Infura will collect your IP address and your Ethereum wallet address when you send a transaction. We don’t store this information in a way that allows our systems to associate those two pieces of data. For more information on how MetaMask and Infura interact from a data collection perspective, see our update - - here - - . For more information on our privacy practices in general, see our + We’ll let you know if we decide to use this data for other purposes. You can review our - Privacy Policy here + Privacy Policy - . + for more information. Remember, you can go to settings and opt out at any time.
diff --git a/ui/pages/onboarding-flow/onboarding-flow.test.js b/ui/pages/onboarding-flow/onboarding-flow.test.js index cc471f5febd5..a02ba4f9b5ea 100644 --- a/ui/pages/onboarding-flow/onboarding-flow.test.js +++ b/ui/pages/onboarding-flow/onboarding-flow.test.js @@ -244,9 +244,7 @@ describe('Onboarding Flow', () => { ONBOARDING_METAMETRICS, ); - const onboardingMetametrics = queryByTestId( - 'onboarding-legacy-metametrics', - ); + const onboardingMetametrics = queryByTestId('onboarding-metametrics'); expect(onboardingMetametrics).toBeInTheDocument(); }); diff --git a/ui/pages/routes/routes.component.test.js b/ui/pages/routes/routes.component.test.js index 749939c227c7..fe215e737613 100644 --- a/ui/pages/routes/routes.component.test.js +++ b/ui/pages/routes/routes.component.test.js @@ -114,6 +114,7 @@ describe('Routes Component', () => { ticker: 'ETH', type: NETWORK_TYPES.MAINNET, }, + newPrivacyPolicyToastShownDate: new Date('0'), }, send: { ...mockSendState.send, @@ -130,7 +131,7 @@ describe('Routes Component', () => { }); describe('toast display', () => { - const testState = { + const getToastDisplayTestState = (date) => ({ ...mockState, metamask: { ...mockState.metamask, @@ -139,17 +140,21 @@ describe('toast display', () => { completedOnboarding: true, usedNetworks: [], swapsState: { swapsFeatureIsLive: true }, + newPrivacyPolicyToastShownDate: date, }, - }; + }); it('renders toastContainer on default route', async () => { - await render([DEFAULT_ROUTE], testState); + await render([DEFAULT_ROUTE], getToastDisplayTestState(new Date('9999'))); const toastContainer = document.querySelector('.toasts-container'); expect(toastContainer).toBeInTheDocument(); }); it('does not render toastContainer on confirmation route', async () => { - await render([CONFIRMATION_V_NEXT_ROUTE], testState); + await render( + [CONFIRMATION_V_NEXT_ROUTE], + getToastDisplayTestState(new Date(0)), + ); const toastContainer = document.querySelector('.toasts-container'); expect(toastContainer).not.toBeInTheDocument(); }); From 906f7d3eadecc82fafbc04aef73526bcd9f12e8e Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Tue, 18 Jun 2024 12:56:28 -0700 Subject: [PATCH 2/5] feat: multi RPC editor (#25219) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** Initial UI for a multi rpc editor in the network form. https://github.com/MetaMask/metamask-extension/assets/3500406/1b38f697-56e6-4594-8c74-6fd341c01796 [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25219?quickstart=1) ## **Related issues** ## **Manual testing steps** - build with yarn; ENABLE_NETWORK_UI_REDESIGN=1 yarn start - Settings -> networks - Should see multi rpc dropdown on ethereum network ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- app/_locales/en/messages.json | 6 + ui/pages/settings/networks-tab/index.scss | 34 +++++ .../networks-form/networks-form.js | 27 ++-- .../networks-form/rpc-url-editor.tsx | 144 ++++++++++++++++++ 4 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 ui/pages/settings/networks-tab/networks-form/rpc-url-editor.tsx diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2363bc51dc2d..b13024dec801 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -289,6 +289,9 @@ "addNfts": { "message": "Add NFTs" }, + "addRpcUrl": { + "message": "Add RPC URL" + }, "addSnapAccountToggle": { "message": "Enable \"Add account Snap (Beta)\"" }, @@ -1354,6 +1357,9 @@ "decryptRequest": { "message": "Decrypt request" }, + "defaultRpcUrl": { + "message": "Default RPC URL" + }, "delete": { "message": "Delete" }, diff --git a/ui/pages/settings/networks-tab/index.scss b/ui/pages/settings/networks-tab/index.scss index b8d1458a77ed..6c179e3dc323 100644 --- a/ui/pages/settings/networks-tab/index.scss +++ b/ui/pages/settings/networks-tab/index.scss @@ -1,6 +1,40 @@ @use "design-system"; .networks-tab { + &__rpc-header { + cursor: default; + } + + &__rpc-dropdown { + cursor: pointer; + } + + &__rpc-item { + position: relative; + } + + &__rpc-item:hover { + cursor: pointer; + background-color: var(--color-background-default-hover); + } + + &__rpc-item--selected, + &__rpc-item--selected:hover { + background-color: var(--color-primary-muted); + } + + &__rpc-selected-pill { + width: 4px; + height: calc(100% - 8px); + position: absolute; + top: 4px; + left: 4px; + } + + &__rpc-popover { + z-index: 1; + } + &__imageclose { cursor: pointer; color: var(--color-icon-default); diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.js b/ui/pages/settings/networks-tab/networks-form/networks-form.js index b5a072ac3312..6d74e2a67179 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.js @@ -62,6 +62,7 @@ import { getMatchedChain, getMatchedSymbols, } from '../../../../helpers/utils/network-helper'; +import { RpcUrlEditor } from './rpc-url-editor'; /** * Attempts to convert the given chainId to a decimal string, for display @@ -760,17 +761,21 @@ const NetworksForm = ({ disabled={viewOnly} dataTestId="network-form-network-name" /> - { - setIsEditing(true); - setRpcUrl(value); - }} - titleText={t('rpcUrl')} - value={displayRpcUrl} - disabled={viewOnly} - dataTestId="network-form-rpc-url" - /> + {window.metamaskFeatureFlags?.networkMenuRedesign ? ( + + ) : ( + { + setIsEditing(true); + setRpcUrl(value); + }} + titleText={t('rpcUrl')} + value={displayRpcUrl} + disabled={viewOnly} + dataTestId="network-form-rpc-url" + /> + )} { + // TODO: real endpoints + const dummyRpcUrls = [ + currentRpcUrl, + 'https://dummy.mainnet.public.blastapi.io', + 'https://dummy.io/v3/blockchain/node/dummy', + ]; + + const t = useI18nContext(); + const rpcDropdown = useRef(null); + const [isOpen, setIsOpen] = useState(false); + const [currentRpcEndpoint, setCurrentRpcEndpoint] = useState(currentRpcUrl); + + return ( + <> + + {t('defaultRpcUrl')} + + setIsOpen(!isOpen)} + className="networks-tab__rpc-dropdown" + display={Display.Flex} + justifyContent={JustifyContent.spaceBetween} + borderRadius={BorderRadius.MD} + borderColor={BorderColor.borderDefault} + borderWidth={1} + padding={2} + ref={rpcDropdown} + > + {currentRpcEndpoint} + + + + {dummyRpcUrls.map((rpcEndpoint) => ( + setCurrentRpcEndpoint(rpcEndpoint)} + className={classnames('networks-tab__rpc-item', { + 'networks-tab__rpc-item--selected': + rpcEndpoint === currentRpcEndpoint, + })} + > + {rpcEndpoint === currentRpcEndpoint && ( + + )} + + {rpcEndpoint} + + alert('TODO: delete confirmation modal')} + /> + + ))} + alert('TODO: add RPC modal')} + padding={4} + display={Display.Flex} + alignItems={AlignItems.center} + className="networks-tab__rpc-item" + > + + + {t('addRpcUrl')} + + + + + ); +}; + +export default RpcUrlEditor; From 1c8ad63f3af490a3163385fcbc8039ad46f02a95 Mon Sep 17 00:00:00 2001 From: Brian Bergeron Date: Tue, 18 Jun 2024 12:56:37 -0700 Subject: [PATCH 3/5] feat: Network buttons during advanced onboarding (#25276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** New UI for customizing network RPC during advanced onboarding. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25276?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. build with `yarn; ENABLE_NETWORK_UI_REDESIGN=1 yarn start` 2. during onboarding, click advanced configuration 3. Should see the new network buttons, clicks not enabled yet ## **Screenshots/Recordings** ### **Before** Screenshot 2024-06-12 at 2 28 25 PM ### **After** Screenshot 2024-06-12 at 2 19 58 PM ## **Pre-merge author checklist** - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --- ui/helpers/utils/feature-flags.js | 2 +- .../privacy-settings/index.scss | 4 + .../privacy-settings/privacy-settings.js | 140 +++++++++++++++--- 3 files changed, 123 insertions(+), 23 deletions(-) diff --git a/ui/helpers/utils/feature-flags.js b/ui/helpers/utils/feature-flags.js index 765bbcf532f9..1956ee75c4bc 100644 --- a/ui/helpers/utils/feature-flags.js +++ b/ui/helpers/utils/feature-flags.js @@ -1,3 +1,3 @@ export function getLocalNetworkMenuRedesignFeatureFlag() { - return window.metamaskFeatureFlags.networkMenuRedesign; + return window.metamaskFeatureFlags?.networkMenuRedesign; } diff --git a/ui/pages/onboarding-flow/privacy-settings/index.scss b/ui/pages/onboarding-flow/privacy-settings/index.scss index efbcab5034e1..53ce477fe7af 100644 --- a/ui/pages/onboarding-flow/privacy-settings/index.scss +++ b/ui/pages/onboarding-flow/privacy-settings/index.scss @@ -64,4 +64,8 @@ padding: 8px !important; } } + + &__customizable-network:hover { + cursor: pointer; + } } diff --git a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js index 60bd84965013..98b42d5e4d22 100644 --- a/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js +++ b/ui/pages/onboarding-flow/privacy-settings/privacy-settings.js @@ -1,6 +1,7 @@ import React, { useContext, useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; +import { ButtonVariant } from '@metamask/snaps-sdk'; import { addUrlProtocolPrefix } from '../../../../app/scripts/lib/util'; import { useSetIsProfileSyncingEnabled, @@ -25,9 +26,20 @@ import { ButtonPrimarySize, ButtonSecondary, ButtonSecondarySize, + Icon, + IconName, + ButtonLink, + AvatarNetwork, + ButtonIcon, + IconSize, } from '../../../components/component-library'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { + AlignItems, + Display, + FlexDirection, + JustifyContent, + TextAlign, TextColor, TextVariant, } from '../../../helpers/constants/design-system'; @@ -63,6 +75,12 @@ import { openBasicFunctionalityModal, } from '../../../ducks/app/app'; import IncomingTransactionToggle from '../../../components/app/incoming-trasaction-toggle/incoming-transaction-toggle'; +import { + CHAIN_IDS, + CHAIN_ID_TO_NETWORK_IMAGE_URL_MAP, + NETWORK_TO_NAME_MAP, +} from '../../../../shared/constants/network'; +import { getLocalNetworkMenuRedesignFeatureFlag } from '../../../helpers/utils/feature-flags'; import { Setting } from './setting'; /** @@ -153,6 +171,10 @@ export default function PrivacySettings() { externalServicesOnboardingToggleState, ); + const networkMenuRedesign = useSelector( + getLocalNetworkMenuRedesignFeatureFlag, + ); + const handleSubmit = () => { dispatch(toggleExternalServices(externalServicesOnboardingToggleState)); dispatch(setUsePhishDetect(phishingToggleState)); @@ -379,29 +401,103 @@ export default function PrivacySettings() { , ])} - - {currentNetwork ? ( -
- <> - dispatch(toggleNetworkMenu())} - /> - -
- ) : ( - { - e.preventDefault(); - dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' })); - }} + {networkMenuRedesign ? ( + + - {t('onboardingAdvancedPrivacyNetworkButton')} - - )} -
+ {[CHAIN_IDS.MAINNET, CHAIN_IDS.LINEA_MAINNET].map( + (chainId) => ( + + console.log(`chain ${chainId} clicked`) + } + display={Display.Flex} + alignItems={AlignItems.center} + justifyContent={JustifyContent.spaceBetween} + > + + + + + {NETWORK_TO_NAME_MAP[chainId]} + + + { + // Get just the protocol + domain, not the infura key in path + new URL( + allNetworks.find( + (network) => + network.chainId === chainId, + )?.rpcUrl, + )?.origin + } + + + + + + ), + )} + console.log('add a network clicked')} + justifyContent={JustifyContent.Left} + variant={ButtonVariant.link} + > + + + + {t('addANetwork')} + + + + + + ) : ( + + {currentNetwork ? ( +
+ <> + dispatch(toggleNetworkMenu())} + /> + +
+ ) : ( + { + e.preventDefault(); + dispatch( + showModal({ name: 'ONBOARDING_ADD_NETWORK' }), + ); + }} + > + {t('onboardingAdvancedPrivacyNetworkButton')} + + )} +
+ )} } /> From c306f20023e41dc577666da62878940b231ec618 Mon Sep 17 00:00:00 2001 From: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Tue, 18 Jun 2024 15:59:12 -0400 Subject: [PATCH 4/5] test: fix flaky tests that fail due to sentry's `sendClientReports` setting (#25235) Client reports are automatically sent when a page's visibility changes to "hidden", but cancelled (with an Error) that gets logged to the console. Our test infra sometimes reports these errors as unexpected failures, which results in test flakiness. We don't use these client reports, so we can safely turn them off by setting the `sendClientReports` option to `false`. --- app/scripts/lib/setupSentry.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index dfa04ab73236..5dc85389f449 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -629,6 +629,13 @@ export default function setupSentry({ release, getState }) { tracesSampleRate: 0.01, beforeSend: (report) => rewriteReport(report, getState), beforeBreadcrumb: beforeBreadcrumb(getState), + // Client reports are automatically sent when a page's visibility changes to + // "hidden", but cancelled (with an Error) that gets logged to the console. + // Our test infra sometimes reports these errors as unexpected failures, + // which results in test flakiness. We don't use these client reports, so + // we can safely turn them off by setting the `sendClientReports` option to + // `false`. + sendClientReports: false, }); /** From 85783b125ce16e2f9007e48fccf127516e8aa77c Mon Sep 17 00:00:00 2001 From: salimtb Date: Tue, 18 Jun 2024 23:09:37 +0200 Subject: [PATCH 5/5] feat: add custom network modal form (#25213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** the custom add network form flow is now available on the modal [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/25213?quickstart=1) ## **Related issues** Fixes: ## **Manual testing steps** 1. Run `yarn && ENABLE_NETWORK_UI_REDESIGN=1 yarn start` 2. Go to Settings -> Developer Options 3. Tun on the network new toggle 4. Go to the wallet page 5. Click on the network button ( see the video below ) 6. you should see the list of popular network 7.click on add network ( you should see the network form ) ## **Screenshots/Recordings** ### **Before** https://github.com/MetaMask/metamask-extension/assets/26223211/02ab2204-8160-4b11-9d85-20c194e201b7 ### **After** https://github.com/MetaMask/metamask-extension/assets/26223211/93c8a0f3-2939-4f0f-84de-2adc9afbec42 ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/develop/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **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. --------- Co-authored-by: Brian Bergeron --- app/_locales/en/messages.json | 7 + .../network-list-menu/network-list-menu.js | 307 ++++++++++-------- .../popular-network-list.tsx | 48 ++- .../add-network-modal.test.js.snap | 9 +- .../add-network-modal.test.js | 31 +- .../add-network-modal/index.js | 31 +- ui/pages/settings/networks-tab/index.scss | 8 +- .../networks-form/networks-form.js | 96 ++++-- .../networks-form/networks-form.test.js | 5 + .../networks-tab/networks-tab.test.js | 5 + 10 files changed, 359 insertions(+), 188 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b13024dec801..8c9b810bdfa1 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1635,6 +1635,9 @@ "enabled": { "message": "Enabled" }, + "enabledNetworks": { + "message": "Enabled networks" + }, "encryptionPublicKeyNotice": { "message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.", "description": "$1 is the web3 site name" @@ -3877,6 +3880,10 @@ "popularCustomNetworks": { "message": "Popular custom networks" }, + "popularNetworkAddToolTip": { + "message": "Some of these networks rely on third parties. The connections may be less reliable or enable third-parties to track activity. $1", + "description": "$1 is Learn more link" + }, "portfolio": { "message": "Portfolio" }, diff --git a/ui/components/multichain/network-list-menu/network-list-menu.js b/ui/components/multichain/network-list-menu/network-list-menu.js index 022ad7f055b0..6c276e326062 100644 --- a/ui/components/multichain/network-list-menu/network-list-menu.js +++ b/ui/components/multichain/network-list-menu/network-list-menu.js @@ -67,12 +67,23 @@ import { getIsUnlocked, } from '../../../ducks/metamask/metamask'; import { getLocalNetworkMenuRedesignFeatureFlag } from '../../../helpers/utils/feature-flags'; +import AddNetworkModal from '../../../pages/onboarding-flow/add-network-modal'; import PopularNetworkList from './popular-network-list/popular-network-list'; import NetworkListSearch from './network-list-search/network-list-search'; +const ACTION_MODES = { + // Displays the search box and network list + LIST: 'list', + // Displays the Add form + ADD: 'add', + // Displays the Edit form + EDIT: 'edit', +}; + export const NetworkListMenu = ({ onClose }) => { const t = useI18nContext(); + const [actionMode, setActionMode] = useState(ACTION_MODES.LIST); const nonTestNetworks = useSelector(getNonTestNetworks); const testNetworks = useSelector(getTestNetworks); const showTestNetworks = useSelector(getShowTestNetworks); @@ -99,6 +110,13 @@ export const NetworkListMenu = ({ onClose }) => { const isUnlocked = useSelector(getIsUnlocked); + let title = t('networkMenuHeading'); + if (actionMode === ACTION_MODES.ADD) { + title = t('addCustomNetwork'); + } else if (actionMode === ACTION_MODES.EDIT) { + title = currentNetwork.nickname; + } + const orderedNetworksList = useSelector(getOrderedNetworksList); const networkConfigurationChainIds = Object.values(networkConfigurations).map( @@ -315,6 +333,11 @@ export const NetworkListMenu = ({ onClose }) => { } }; + const headerAdditionalProps = + actionMode === ACTION_MODES.LIST + ? {} + : { onBack: () => setActionMode(ACTION_MODES.LIST) }; + return ( @@ -332,145 +355,165 @@ export const NetworkListMenu = ({ onClose }) => { paddingRight={4} paddingBottom={6} onClose={onClose} + {...headerAdditionalProps} > - {t('networkMenuHeading')} + {title} - <> - - {showBanner ? ( - - drag-and-drop - - } - onClose={() => hideNetworkBanner()} - description={t('dragAndDropBanner')} + {actionMode === ACTION_MODES.LIST ? ( + <> + - ) : null} - - {searchResults.length === 0 && focusSearch ? ( - - {t('noNetworksFound')} - - ) : ( - - - {(provided) => ( - - {searchResults.map((network, index) => { - const isCurrentNetwork = - currentNetwork.id === network.id; - - const canDeleteNetwork = - isUnlocked && !isCurrentNetwork && network.removable; - - const networkListItem = generateNetworkListItem({ - network, - isCurrentNetwork, - canDeleteNetwork, - }); - - return ( - - {(providedDrag) => ( - - {networkListItem} - - )} - - ); - })} - {provided.placeholder} - - )} - - - )} - {networkMenuRedesign ? ( - + drag-and-drop + + } + onClose={() => hideNetworkBanner()} + description={t('dragAndDropBanner')} /> ) : null} - - {t('showTestnetNetworks')} - - - {showTestNetworks || currentlyOnTestNetwork ? ( - - {generateMenuItems(searchTestNetworkResults)} + + + {t('enabledNetworks')} - ) : null} - - - { - if (isFullScreen) { - if (completedOnboarding) { - history.push(ADD_POPULAR_CUSTOM_NETWORK); - } else { - dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' })); + {searchResults.length === 0 && focusSearch ? ( + + {t('noNetworksFound')} + + ) : ( + + + {(provided) => ( + + {searchResults.map((network, index) => { + const isCurrentNetwork = + currentNetwork.id === network.id; + + const canDeleteNetwork = + isUnlocked && + !isCurrentNetwork && + network.removable; + + const networkListItem = generateNetworkListItem({ + network, + isCurrentNetwork, + canDeleteNetwork, + }); + + return ( + + {(providedDrag) => ( + + {networkListItem} + + )} + + ); + })} + {provided.placeholder} + + )} + + + )} + {networkMenuRedesign ? ( + + ) : null} + + {t('showTestnetNetworks')} + + + {showTestNetworks || currentlyOnTestNetwork ? ( + + {generateMenuItems(searchTestNetworkResults)} + + ) : null} + + + { + if (!networkMenuRedesign) { + if (isFullScreen) { + if (completedOnboarding) { + history.push(ADD_POPULAR_CUSTOM_NETWORK); + } else { + dispatch(showModal({ name: 'ONBOARDING_ADD_NETWORK' })); + } + } else { + global.platform.openExtensionInBrowser( + ADD_POPULAR_CUSTOM_NETWORK, + ); + } + dispatch(toggleNetworkMenu()); + return; } - } else { - global.platform.openExtensionInBrowser( - ADD_POPULAR_CUSTOM_NETWORK, - ); - } - dispatch(toggleNetworkMenu()); - trackEvent({ - event: MetaMetricsEventName.AddNetworkButtonClick, - category: MetaMetricsEventCategory.Network, - }); - }} - > - {t('addNetwork')} - - - + trackEvent({ + event: MetaMetricsEventName.AddNetworkButtonClick, + category: MetaMetricsEventCategory.Network, + }); + setActionMode(ACTION_MODES.ADD); + }} + > + {t('addNetwork')} + + + + ) : ( + + )} ); diff --git a/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx index 6d2cd02f5170..cbe1a99fd69f 100644 --- a/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx +++ b/ui/components/multichain/network-list-menu/popular-network-list/popular-network-list.tsx @@ -9,6 +9,11 @@ import { Button, AvatarNetworkSize, ButtonVariant, + IconName, + Icon, + IconSize, + ButtonLinkSize, + ButtonLink, } from '../../../component-library'; import { MetaMetricsNetworkEventSource } from '../../../../../shared/constants/metametrics'; import { @@ -26,8 +31,11 @@ import { Display, JustifyContent, TextColor, + IconColor, } from '../../../../helpers/constants/design-system'; import { RPCDefinition } from '../../../../../shared/constants/network'; +import Tooltip from '../../../ui/tooltip'; +import ZENDESK_URLS from '../../../../helpers/constants/zendesk-url'; const PopularNetworkList = ({ searchAddNetworkResults, @@ -53,7 +61,45 @@ const PopularNetworkList = ({ display={Display.Flex} justifyContent={JustifyContent.spaceBetween} > - {t('additionalNetworks')} + + {t('additionalNetworks')} + + {t('popularNetworkAddToolTip', [ + + { + global.platform.openTab({ + url: ZENDESK_URLS.UNKNOWN_NETWORK, + }); + }} + > + {t('learnMoreUpperCase')} + + , + , + ])} + + } + trigger="mouseenter" + > + + + + + )} diff --git a/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap b/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap index ace866477762..e422b5d8216b 100644 --- a/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap +++ b/ui/pages/onboarding-flow/add-network-modal/__snapshots__/add-network-modal.test.js.snap @@ -223,15 +223,10 @@ exports[`Add Network Modal should render 1`] = ` -
- {!viewOnly && ( - <> - {deletable && ( - + )} + + - )} - - - - )} -
+ + + + )} + + )} ); }; diff --git a/ui/pages/settings/networks-tab/networks-form/networks-form.test.js b/ui/pages/settings/networks-tab/networks-form/networks-form.test.js index a491dee64184..e4b338a8a580 100644 --- a/ui/pages/settings/networks-tab/networks-form/networks-form.test.js +++ b/ui/pages/settings/networks-tab/networks-form/networks-form.test.js @@ -43,6 +43,11 @@ const propNetworkDisplay = { addNewNetwork: false, }; +jest.mock('../../../../helpers/utils/feature-flags', () => ({ + ...jest.requireActual('../../../../helpers/utils/feature-flags'), + getLocalNetworkMenuRedesignFeatureFlag: () => false, +})); + describe('NetworkForm Component', () => { beforeAll(() => { nock.disableNetConnect(); diff --git a/ui/pages/settings/networks-tab/networks-tab.test.js b/ui/pages/settings/networks-tab/networks-tab.test.js index fe985de9ebe4..cead29634a70 100644 --- a/ui/pages/settings/networks-tab/networks-tab.test.js +++ b/ui/pages/settings/networks-tab/networks-tab.test.js @@ -20,6 +20,11 @@ const mockState = { }, }; +jest.mock('../../../helpers/utils/feature-flags', () => ({ + ...jest.requireActual('../../../helpers/utils/feature-flags'), + getLocalNetworkMenuRedesignFeatureFlag: () => false, +})); + const renderComponent = (props) => { const store = configureMockStore([])(mockState); return renderWithProvider(, store);