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

feat(27255): allow local modification for remote feature flags #29696

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d4f2f3f
feat(27255): allow local modification for remote feature flags
DDDDDanica Jan 14, 2025
887ed35
Update LavaMoat policies
metamaskbot Jan 14, 2025
4cef487
feat(27255): move the file to git ignore and add instructions to Read…
DDDDDanica Jan 16, 2025
5e3788b
feat(29629): Add _flags to webpack build only when in development; an…
DDDDDanica Jan 16, 2025
5c703f2
feat(29629): adapted to right pattern in transform function
DDDDDanica Jan 16, 2025
35fe1f0
feat(29629): relocate `getManifestFlags` to `shared/lib/manifestFlags`
DDDDDanica Jan 16, 2025
473a3bd
feat(29629): enrich `getRemoteFeatureFlags` return type
DDDDDanica Jan 16, 2025
caa4b4f
feat(29629): shallow merge states in `getRemoteFeatureFlags`
DDDDDanica Jan 16, 2025
0bff314
Update LavaMoat policies
metamaskbot Jan 16, 2025
d4e79a7
feat(29629): fix e2e test due to shallow merging
DDDDDanica Jan 17, 2025
8e88e7f
feat(29629): Update readme
DDDDDanica Jan 17, 2025
b1a52ae
feat(29629): Remove unused [] param in getWebpackConfig test
DDDDDanica Jan 17, 2025
b2b5862
feat(29629): Remove unnecessary --test flag in non-default options te…
DDDDDanica Jan 17, 2025
a5ea54f
feat(29629): Remove comment in json file to avoid copy error; read .m…
DDDDDanica Jan 17, 2025
5308bed
feat(29629): Move getRemoteFeatureFlags selector to a separate ts file
DDDDDanica Jan 19, 2025
2cc1560
feat(29629): Rename remote-feature-flags.ts file name
DDDDDanica Jan 19, 2025
248ec01
feat(29629): Refactor getRemoteFeatureFlags to use safe merge from lo…
DDDDDanica Jan 19, 2025
40f3c07
feat(29629): Rename gitnore comment
DDDDDanica Jan 19, 2025
3a16019
feat(29629): Remove asyn reading for loadManifestFlags in normal build
DDDDDanica Jan 19, 2025
77ad1e5
feat(29629): Fix unit test of file not exist
DDDDDanica Jan 19, 2025
b09f6a5
feat(29629): Fix lint
DDDDDanica Jan 20, 2025
9487c17
Merge branch 'main' into feature/remote-feature-flags-manifest-adaption
DDDDDanica Jan 26, 2025
51b6de6
fix(3742): fix lint
DDDDDanica Jan 26, 2025
f3c8e38
feat(27255): import json file from `.metamaskrc` instead of using rea…
DDDDDanica Jan 31, 2025
2c5ab32
feat(27255): fix lint
DDDDDanica Jan 31, 2025
945c42a
feat(27255): removed console log in build console
DDDDDanica Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ notes.txt
.metamaskrc
.metamaskprodrc

# Manifest customizated configuration
HowardBraham marked this conversation as resolved.
Show resolved Hide resolved
.manifest-flags.json

# Test results
test-results/

Expand Down
5 changes: 5 additions & 0 deletions .manifest-flags.json.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// This configuration file is used to manage values passing to _flags that will be injected into manifest.json
Copy link
Contributor

Choose a reason for hiding this comment

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

I had left a long comment on my previous review about how I do not like the idea of adding yet another build config option to our already complex and long list of build config options, but I can't find it in my previous review, maybe due to force pushes deleting the file I had previously left the comment on?

Anyway, I'll try to recreate it again now:

From the top of my head we have the following build-time config options: build.yml, metamaskrc, metamaskrc.dist, CLI options, ENV options, webpack --config, there is a PR that adds a test-time fixtures config option, manifestOverrides file in build.yml (e.g.,: ./app/build-types/beta/manifest/), and probably even more.

Unfortunately the webpack build doesn't currently read the manifestOverrides property to parse those overrides, but if we could prioritize getting that working in webpack (cc @desi, @danjm, @itsyoboieltr , @gauthierpetetin) that may be the best place to put this new config option. Related issue: #26260

I should note that in the issue linked about I actually explain why I don't like all these config options:

The reason this hasn't been done yet is because I think we should go about it differently. It seems that we sometimes use manifest.json overrides files for customizing fields, sometimes we use build.yml fields, and sometimes we use environment variables. Each way of customization doesn't always overlap with the other ways, but sometimes it does, I think? It's very confusing.
I'd like to eliminate one of these ways of customization in the primary build system, with the webpack build in mind, and then implement it in webpack.

Note: I'm not going to block the PR on this, though I suspect that if this does get merged with this new build config file (.manifest-flags.json) we'll never go back to try to simplify it, and we'll be forever burdened by an even more confusing and complex build configurations than we already have now.


Actually, another potential option I just thought of is to add a field to .metamaskrc:

; A JSON config file that can be used to override the default manifest values.
; e.g., `.manifest-overrides.json` where the contents might be something like:
; {
;  "_flags": {
;    "remoteFeatureFlags": { }
;  }
; }
; Note: properties are shallow merged into the manifest.json file.
MANIFEST_OVERRIDES=.manifest-overrides.json

If we do it this way we wouldn't have to add an extra "noisy" config files to the top level directory, developers could have multiple config files they could switch between just by updating the MANIFEST_OVERRIDES property, developers can modify any manifest.json property (not just _flags), and it doesn't further complicate where build config information comes from.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I would also like to reduce the number of configuration methods.

Danica and I discussed putting JSON directly in .metamaskrc, but the major disadvantage, as we discovered in a pair programming session, is that it's not easy to write JSON in that file. This MANIFEST_OVERRIDES is an interesting approach.

I agree that we should be able to override anything in the manifest.

And I would love to get rid of .metamaskrc entirely.

Last thought here... maybe we should use a .js file instead of a .json file, mostly for comments support.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wouldn't go to JS for this (unless we have a linting rule that disallows actual JS in it :-) ). I thought about using JSON with comments as well. We could parse with JSON5 which supports comments, but won't get us in the situation where our config files are Turing Complete 😓 (which isn't always horrible, but can end in madness). If we do switch to JSON5 (or any "JSON with comments" flavor), we'll need to be sure we update our .gitattributes file so CSVode/IDEs and github know how to highlight the comment lines correctly.

Copy link
Contributor

@itsyoboieltr itsyoboieltr Jan 21, 2025

Choose a reason for hiding this comment

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

I would say that recently it has become the standard for js libraries and applications to use .(js|ts) for configuration.

I would propose something like:

  • metamask.config.(js|ts)

Similarly to the drizzle config linked above, it would only have one export default (nothing else), and would look something like:

import { defineConfig } from "metamask-config";

export default defineConfig({
  // you can put comments here for context
  build: "beta",
  // typescript types and jsdoc will further help understanding, so likely comments are not even needed
  out: "./dist",
  ...
});

What do you guys think about this approach? @davidmurdoch @HowardBraham

Copy link
Contributor

Choose a reason for hiding this comment

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

@itsyoboieltr I would vote for this if the goal is to eventually replace .metamaskrc with metamask.config.ts

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, not environment variables, but the properties included in the object returned by browser.runtime.getManifest()

Copy link
Contributor

@danjm danjm Jan 30, 2025

Choose a reason for hiding this comment

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

David previously wrote:

Actually, another potential option I just thought of is to add a field to .metamaskrc:

; A JSON config file that can be used to override the default manifest values.
; e.g., .manifest-overrides.json where the contents might be something like:
; {
; "_flags": {
; "remoteFeatureFlags": { }
; }
; }
; Note: properties are shallow merged into the manifest.json file.
MANIFEST_OVERRIDES=.manifest-overrides.json
If we do it this way we wouldn't have to add an extra "noisy" config files to the top level directory, developers could have multiple config files they could switch between just by updating the MANIFEST_OVERRIDES property, developers can modify any manifest.json property (not just _flags), and it doesn't further complicate where build config information comes from.

This seems good to me. Is anyone opposed to this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, to modify "the properties included in the object returned by browser.runtime.getManifest()"

I'm okay with the MANIFEST_OVERRIDES=.manifest-overrides.json proposal, but I would like to (in the longer-term) move to a model where .metamaskrc:

  • contains only secrets and no configuration
  • never gives errors like Tried to modify a variable "INFURA_PROD_PROJECT_ID" that wasn't declared in builds.yml

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you all for your valuable comments and suggestions ❤️.
I agree with David's suggestion that we need to streamline how manifest overrides are handled, where developers could manage overrides in a more centralized and simpler manner.

This is what I believe is a good approach to implement the customized way to overwrite manifest.json:

  • Add a template (.manifest-overrides.json) to add customized values for development.
  • Read these values from MANIFEST_OVERRIDES=.manifest-overrides.json in the env file .metamaskrc .
  • Read customized values from the manifest file during the building process.
  • Allow modification of the manifest.json file in the dist directory directly after the build process as well.
  • To that end, introduce a selector mechanism to read from the manifest.json file and apply overrides from the new manifestFlags system.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in f3c8e38

{
"remoteFeatureFlags": {
}
}
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ If you are not a MetaMask Internal Developer, or are otherwise developing on a f
- If debugging MetaMetrics, you'll need to add a value for `SEGMENT_WRITE_KEY` [Segment write key](https://segment.com/docs/connections/find-writekey/), see [Developing on MetaMask - Segment](./development/README.md#segment).
- If debugging unhandled exceptions, you'll need to add a value for `SENTRY_DSN` [Sentry Dsn](https://docs.sentry.io/product/sentry-basics/dsn-explainer/), see [Developing on MetaMask - Sentry](./development/README.md#sentry).
- Optionally, replace the `PASSWORD` value with your development wallet password to avoid entering it each time you open the app.
- Duplicate `manifest-flags.json.dist` within the root and rename it to `manifest-flags.json` by running `cp .manifest-flags.json{.dist,}`. This file is used to add flags to `.manifest.json` build files for the extension. You can add flags to the file to be used in the build process, for example:
Copy link
Contributor

@HowardBraham HowardBraham Jan 17, 2025

Choose a reason for hiding this comment

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

No action required yet, but when we figure out what to call the file, we have to be consistent everywhere. Sometimes in the code and documents it has a dot in front, and sometimes it doesn't.

This is an optional step (not everyone who develops locally has to do this immediately) and that should be specified.

```json
{
"remoteFeatureFlags": {
"testFlagForThreshold": {
"name": "test-flag",
"value": "test-value"
}
}
}
```
- Run `yarn install` to install the dependencies.
- Build the project to the `./dist/` folder with `yarn dist` (for Chromium-based browsers) or `yarn dist:mv2` (for Firefox)

Expand Down
2 changes: 1 addition & 1 deletion app/scripts/lib/setupSentry.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as Sentry from '@sentry/browser';
import { logger } from '@sentry/utils';
import browser from 'webextension-polyfill';
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
import { getManifestFlags } from '../../../shared/lib/manifestFlags';
import extractEthjsErrorMessage from './extractEthjsErrorMessage';
import { getManifestFlags } from './manifestFlags';
import { filterEvents } from './sentry-filter-events';

const projectLogger = createProjectLogger('sentry');
Expand Down
27 changes: 26 additions & 1 deletion development/build/manifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,29 @@ const { getEnvironment, getBuildName } = require('./utils');

module.exports = createManifestTasks;

async function loadManifestFlags() {
try {
return JSON.parse(
await fs.readFile(
path.join(__dirname, '../../.manifest-flags.json'),
'utf8',
),
);
} catch (error) {
return { remoteFeatureFlags: {} };
}
}

// Initialize with default value
let manifestFlags = { remoteFeatureFlags: {} };

// Load flags asynchronously
loadManifestFlags().then((flags) => {
manifestFlags = flags;
});

module.exports = createManifestTasks;

Copy link
Contributor

Choose a reason for hiding this comment

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

While I don't think the race condition this introduces will ever show its face because the rest of the build process is slow, it's still adding a race condition, which generally makes me uncomfortable. It also hides parsing errors the dev may have made in their .manifest-flags.json file. I think reading synchronously is fine.

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 suggestion, addressed 3a16019

function createManifestTasks({
browserPlatforms,
browserVersionMap,
Expand Down Expand Up @@ -47,8 +70,10 @@ function createManifestTasks({
browserVersionMap[platform],
await getBuildModifications(buildType, platform),
customArrayMerge,
{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Add customized local manifestFlags to manifest file (old build system)

_flags: manifestFlags,
},
);

modifyNameAndDescForNonProd(result);

const dir = path.join('.', 'dist', platform);
Expand Down
2 changes: 1 addition & 1 deletion development/lib/get-manifest-flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { exec as callbackExec } from 'node:child_process';
import { hasProperty } from '@metamask/utils';
import { merge } from 'lodash';

import type { ManifestFlags } from '../../app/scripts/lib/manifestFlags';
import type { ManifestFlags } from '../../shared/lib/manifestFlags';

const exec = promisify(callbackExec);
const PR_BODY_FILEPATH = path.resolve(
Expand Down
2 changes: 1 addition & 1 deletion development/webpack/test/plugins.ManifestPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ describe('ManifestPlugin', () => {
function runTest(baseManifest: Combination<typeof manifestMatrix>) {
const manifest = baseManifest as unknown as chrome.runtime.Manifest;
const hasTabsPermission = (manifest.permissions || []).includes('tabs');
const transform = transformManifest(args);
const transform = transformManifest(args, false);

if (args.test && hasTabsPermission) {
it("throws in test mode when manifest already contains 'tabs' permission", () => {
Expand Down
8 changes: 6 additions & 2 deletions development/webpack/test/webpack.config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ ${Object.entries(env)
}

it('should have the correct defaults', () => {
const config: Configuration = getWebpackConfig();
const config: Configuration = getWebpackConfig([]);
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
// check that options are valid
const { options } = webpack(config);
assert.strictEqual(options.name, 'MetaMask – development');
Expand Down Expand Up @@ -163,6 +163,9 @@ ${Object.entries(env)
manifest_version: 3,
name: 'name',
version: '1.2.3',
_flags: {
remoteFeatureFlags: {},
},
content_scripts: [
{
js: ['scripts/contentscript.js', 'scripts/inpage.js'],
Expand Down Expand Up @@ -191,6 +194,7 @@ ${Object.entries(env)
'--no-progress',
'--no-cache',
'--zip',
'--test',
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
...removeUnsupportedFeatures,
],
{
Expand Down Expand Up @@ -231,7 +235,7 @@ ${Object.entries(env)
assert.deepStrictEqual(manifestPlugin.options.description, null);
assert.deepStrictEqual(manifestPlugin.options.zip, true);
assert(manifestPlugin.options.zipOptions, 'Zip options should be present');
assert.strictEqual(manifestPlugin.options.transform, undefined);
assert.notEqual(manifestPlugin.options.transform, undefined);
DDDDDanica marked this conversation as resolved.
Show resolved Hide resolved

const progressPlugin = instance.options.plugins.find(
(plugin) => plugin && plugin.constructor.name === 'ProgressPlugin',
Expand Down
27 changes: 26 additions & 1 deletion development/webpack/utils/plugins/ManifestPlugin/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
let manifestFlags: { remoteFeatureFlags: Record<string, unknown> };
try {
manifestFlags = require('../../../../../.manifest-flags.json');
} catch (error) {
manifestFlags = { remoteFeatureFlags: {} };
}

/**
* Returns a function that will transform a manifest JSON object based on the
* given build args.
Expand All @@ -9,11 +16,15 @@
* @param args
* @param args.lockdown
* @param args.test
* @param isDevelopment
* @returns a function that will transform the manifest JSON object
* @throws an error if the manifest already contains the "tabs" permission and
* `test` is `true`
*/
export function transformManifest(args: { lockdown: boolean; test: boolean }) {
export function transformManifest(
args: { lockdown: boolean; test: boolean },
isDevelopment: boolean,
) {
const transforms: ((manifest: chrome.runtime.Manifest) => void)[] = [];

function removeLockdown(browserManifest: chrome.runtime.Manifest) {
Expand All @@ -29,6 +40,20 @@ export function transformManifest(args: { lockdown: boolean; test: boolean }) {
transforms.push(removeLockdown);
}

/**
davidmurdoch marked this conversation as resolved.
Show resolved Hide resolved
* This function sets predefined flags in the manifest's _flags property.
*
* @param browserManifest - The Chrome extension manifest object to modify
*/
function addManifestFlags(browserManifest: chrome.runtime.Manifest) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Add customized local manifestFlags to manifest file (webpack build system)

browserManifest._flags = manifestFlags;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of reading in the .manifest-flags.json file at the top level scope, I think it's best to just try to read it synchronously here.

Also, hiding JSON.parse errors from the end user by catching them will hide formatting errors from the developer, which I don't think they'd like. Probably better to only try/catch the attempt to readFileSync, and then only ignore the caught error if the file does not exist (the error.code property will be "ENOENT"); all other errors readFileSync can throw the developer will probably need to know about.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Add sync reading + error catching here a5ea54f

}

if (isDevelopment) {
// Add manifest flags only for development builds
transforms.push(addManifestFlags);
}

function addTabsPermission(browserManifest: chrome.runtime.Manifest) {
if (browserManifest.permissions) {
if (browserManifest.permissions.includes('tabs')) {
Expand Down
2 changes: 1 addition & 1 deletion development/webpack/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const plugins: WebpackPluginInstance[] = [
version: version.version,
versionName: version.versionName,
browsers: args.browser,
transform: transformManifest(args),
transform: transformManifest(args, isDevelopment),
zip: args.zip,
...(args.zip
? {
Expand Down
12 changes: 12 additions & 0 deletions app/scripts/lib/manifestFlags.ts → shared/lib/manifestFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ export type ManifestFlags = {
*/
forceEnable?: boolean;
};
/**
* Feature flags to control business logic behavior
*/
remoteFeatureFlags?: {
/**
* A test remote featureflag for threshold
*/
testFlagForThreshold: {
name: string;
value: string;
};
Comment on lines +70 to +73
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use better types here? maybe something like testFlagForThreshold: Record<string, any> ( or maybe unknown instead of any, but that may complicate some things).

Or if you really want to go the extra mile, you can define the type as any valid JSON:

type JSONValue =
  | string
  | number
  | boolean
  | null
  | JSONValue[]
  | { [key: string]: JSONValue };

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For this one, the name and value are the same in request, and controller will choose one of the value to return based on threshold => https://client-config.api.cx.metamask.io/v1/flags?client=extension&distribution=main&environment=dev

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, so it's an array of name, value, and scope properties? I think this type should reflect that

};
};

// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- you can't extend a type, we want this to be an interface
Expand Down
18 changes: 18 additions & 0 deletions test/e2e/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,21 @@ export enum ACCOUNT_TYPE {
/* Meta metricsId generated by generateMetaMetricsId */
export const MOCK_META_METRICS_ID =
'0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420';

/* Mock remote feature flags response */
export const MOCK_REMOTE_FEATURE_FLAGS_RESPONSE = {
feature1: true,
feature2: false,
feature3: {
name: 'groupC',
value: 'valueC',
},
};

/* Mock customized remote feature flags response*/
export const MOCK_CUSTOMIZED_REMOTE_FEATURE_FLAGS = {
feature3: {
name: 'groupA',
value: 'valueA',
},
};
23 changes: 23 additions & 0 deletions test/e2e/page-objects/pages/developer-options-page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { strict as assert } from 'assert';
import { Driver } from '../../webdriver/driver';
import {
MOCK_REMOTE_FEATURE_FLAGS_RESPONSE,
MOCK_CUSTOMIZED_REMOTE_FEATURE_FLAGS,
} from '../../constants';

class DevelopOptions {
private readonly driver: Driver;
Expand All @@ -12,6 +17,9 @@ class DevelopOptions {
css: 'h4',
};

private readonly developerOptionsRemoteFeatureFlagsState: string =
'[data-testid="developer-options-remote-feature-flags"]';

constructor(driver: Driver) {
this.driver = driver;
}
Expand All @@ -33,6 +41,21 @@ class DevelopOptions {
console.log('Generate a page crash in Developer option page');
await this.driver.clickElement(this.generatePageCrashButton);
}

async validateRemoteFeatureFlagState(): Promise<void> {
console.log('Validate remote feature flags state in Developer option page');
const element = await this.driver.findElement(
this.developerOptionsRemoteFeatureFlagsState,
);
const remoteFeatureFlagsState = await element.getText();
assert.equal(
remoteFeatureFlagsState,
JSON.stringify({
...MOCK_REMOTE_FEATURE_FLAGS_RESPONSE,
...MOCK_CUSTOMIZED_REMOTE_FEATURE_FLAGS,
}),
);
}
}

export default DevelopOptions;
2 changes: 1 addition & 1 deletion test/e2e/set-manifest-flags.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import fs from 'fs';
import { merge } from 'lodash';
import { ManifestFlags } from '../../app/scripts/lib/manifestFlags';
import { ManifestFlags } from '../../shared/lib/manifestFlags';
import { fetchManifestFlagsFromPRAndGit } from '../../development/lib/get-manifest-flag';

export const folder = `dist/${process.env.SELENIUM_BROWSER}`;
Expand Down
39 changes: 37 additions & 2 deletions test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import { getCleanAppState, withFixtures } from '../../helpers';
import FixtureBuilder from '../../fixture-builder';
import { TestSuiteArguments } from '../confirmations/transactions/shared';
import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow';
import { MOCK_META_METRICS_ID } from '../../constants';
import { MOCK_REMOTE_FEATURE_FLAGS_RESPONSE } from './mock-data';
import HeaderNavbar from '../../page-objects/pages/header-navbar';
import SettingsPage from '../../page-objects/pages/settings/settings-page';
import DevelopOptions from '../../page-objects/pages/developer-options-page';
import {
MOCK_CUSTOMIZED_REMOTE_FEATURE_FLAGS,
MOCK_META_METRICS_ID,
MOCK_REMOTE_FEATURE_FLAGS_RESPONSE,
} from '../../constants';

describe('Remote feature flag', function (this: Suite) {
it('should be fetched with threshold value when basic functionality toggle is on', async function () {
Expand Down Expand Up @@ -45,4 +51,33 @@ describe('Remote feature flag', function (this: Suite) {
},
);
});

it('offers the option to pass into manifest file for developers along with original response', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder()
.withMetaMetricsController({
metaMetricsId: MOCK_META_METRICS_ID,
participateInMetaMetrics: true,
})
.build(),
manifestFlags: {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. Usage in e2e test: override remoteFeatureFlags and assert the result to be MOCK_CUSTOMIZED_REMOTE_FEATURE_FLAGS

remoteFeatureFlags: MOCK_CUSTOMIZED_REMOTE_FEATURE_FLAGS,
},
title: this.test?.fullTitle(),
},
async ({ driver }: TestSuiteArguments) => {
await loginWithBalanceValidation(driver);
const headerNavbar = new HeaderNavbar(driver);
await headerNavbar.openSettingsPage();
const settingsPage = new SettingsPage(driver);
await settingsPage.check_pageIsLoaded();
await settingsPage.goToDevelopOptionSettings();

const developOptionsPage = new DevelopOptions(driver);
await developOptionsPage.check_pageIsLoaded();
await developOptionsPage.validateRemoteFeatureFlagState();
},
);
});
});
3 changes: 1 addition & 2 deletions ui/helpers/utils/mm-lazy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
// eslint-disable-next-line import/no-restricted-paths
import { getManifestFlags } from '../../../app/scripts/lib/manifestFlags';
import { getManifestFlags } from '../../../shared/lib/manifestFlags';
import { endTrace, trace, TraceName } from '../../../shared/lib/trace';

type DynamicImportType = () => Promise<{ default: React.ComponentType }>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,45 @@ exports[`Develop options tab should match snapshot 1`] = `
>
States
</p>
<p
class="mm-box mm-text settings-page__security-tab-sub-header mm-text--body-md mm-box--padding-top-6 mm-box--color-text-alternative"
>
Current States
</p>
<div
class="settings-page__content-padded"
>
<div
class="mm-box settings-page__content-row mm-box--display-flex mm-box--gap-4 mm-box--flex-direction-row mm-box--justify-content-space-between"
>
<div
class="settings-page__content-item"
>
<span>
Remote feature flags
</span>
<div
class="settings-page__content-description"
>
The remote feature flags here by
<b>
getRemoteFeatureFlags()
</b>
is retrieved from one of the following sources:
<br />
1) manifest-flags.json file 2) RemoteFeatureFlagsController
<br />
Modify the manifest-flags.json file will change the state locally.
</div>
</div>
<div
class="settings-page__content-description"
data-testid="developer-options-remote-feature-flags"
>
{"feature1":"value1"}
</div>
</div>
</div>
<p
class="mm-box mm-text settings-page__security-tab-sub-header mm-text--body-md mm-box--padding-top-6 mm-box--color-text-alternative"
>
Expand Down
Loading
Loading