Skip to content

Commit 24a4a27

Browse files
feat(4186): add doc for remote feature flag (#125)
Fix: MetaMask/MetaMask-planning#4186 Create a documentation to explain other teams how to use remote feature flags. This also includes the documentation from mobile side by @joaoloureirop 🙏🏻 https://www.notion.so/metamask-consensys/Remote-feature-flags-with-Launch-Darkly-197f86d67d6880be86ffecbd313f62cf --------- Co-authored-by: João Loureiro <175489935+joaoloureirop@users.noreply.github.com>
1 parent 07f237a commit 24a4a27

File tree

2 files changed

+221
-11
lines changed

2 files changed

+221
-11
lines changed

docs/remote-feature-flags.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Remote Feature Flags
2+
3+
## Overview
4+
5+
This document outlines the process for adding and managing remote feature flags in MetaMask (Extension and Mobile). Remote feature flags allow us to control feature availability and behavior in production without requiring a new release. They are created on the LaunchDarkly platform and distributed by our internal API [ClientConfigAPI](https://github.com/consensys-vertical-apps/mmwp-client-config-api). Please read the [Remote feature flags ADR](https://github.com/MetaMask/decisions/pull/43) for more details.
6+
7+
### Choosing the Right Feature Flag Type
8+
9+
Choose the appropriate feature flag type based on your needs:
10+
11+
**1. Boolean flag**: Use when you need a simple ON/OFF toggle for a feature. In this example: `my-feature` is enabled for all users.
12+
13+
```json
14+
{
15+
"name": "my-feature",
16+
"value": true
17+
}
18+
```
19+
20+
**2. Object flag with scope based on "threshold"**: Use for:
21+
22+
- controlling % of users who see a feature
23+
In this example: the feature is enabled for 30% of users and disabled for 70% of users.
24+
25+
```json
26+
[
27+
{
28+
"name": "feature is ON",
29+
"scope": {
30+
"type": "threshold",
31+
"value": 0.3
32+
},
33+
"value": true
34+
},
35+
{
36+
"name": "feature is OFF",
37+
"scope": {
38+
"type": "threshold",
39+
"value": 1
40+
},
41+
"value": false
42+
}
43+
]
44+
```
45+
46+
- A/B testing different feature variants
47+
In this configuration, the swap button will be blue for 25% of users, red for 25% of users, green for 25% of users, and yellow for 25% of users
48+
49+
```json
50+
[[
51+
{
52+
"name": "blue",
53+
"scope": {
54+
"type": "threshold",
55+
"value": 0.25
56+
},
57+
"value": "0000FF"
58+
},
59+
{
60+
"name": "red",
61+
"scope": {
62+
"type": "threshold",
63+
"value": 0.5
64+
},
65+
"value": "FF0000"
66+
},
67+
{
68+
"name": "green",
69+
"scope": {
70+
"type": "threshold",
71+
"value": 0.75
72+
},
73+
"value": "00FF00"
74+
},
75+
{
76+
"name": "yellow",
77+
"scope": {
78+
"type": "threshold",
79+
"value": 1
80+
},
81+
"value": "FFF500"
82+
}
83+
]
84+
```
85+
86+
The distribution is deterministic based on the user's `metametricsId`, ensuring consistent group assignment across sessions.
87+
88+
## Implementation Guide
89+
90+
### 1. Creating feature flag in LaunchDarkly.
91+
92+
1. Name your flag with team prefix (e.g., `confirmations-blockaid`, `ramps-card`)
93+
2. Request LaunchDarkly access from TechOps. Here's a template email:
94+
95+
```
96+
Subject: Requesting access to LaunchDarkly for [team]
97+
98+
Dear TechOps,
99+
Can I please get access to LaunchDarkly with the "Metamask Extension and Mobile" role? (role key: `metamask-config`)
100+
101+
Thanks!
102+
```
103+
104+
3. Select appropriate project in LaunchDarkly:
105+
- Extension: "MetaMask Client Config API - Extension"
106+
- Mobile: "MetaMask Client Config API - Mobile"
107+
4. Create the flag following the [LaunchDarkly flag creation guide](https://docs.launchdarkly.com/home/flags/new)
108+
109+
### 2. Using Remote Feature Flags in MetaMask clients
110+
111+
#### Extension
112+
113+
To use a remote feature flag in the MetaMask extension, follow these steps:
114+
115+
1. Add selector from `import { getRemoteFeatureFlags } from 'path/to/selectors';` in your component file.
116+
2. Use the selector to get the feature flag value.
117+
118+
```typescript
119+
const { testFeatureFlag } = getRemoteFeatureFlags(state);
120+
```
121+
122+
3. Use the feature flag value in your component.
123+
124+
```typescript
125+
if (testFeatureFlag) {
126+
// Use the feature flag value
127+
}
128+
```
129+
130+
#### Mobile
131+
132+
##### Create a selector for your feature flag
133+
134+
- Location: `app/selectors/featureFlagsController`
135+
- Create a new folder with your feature flag name
136+
- Follow the structure from `app/selectors/featureFlagsController/minimumAppVersion`
137+
138+
Your selector must include:
139+
140+
- State selectors for each feature flag value
141+
- Fallback values for invalid remote flag values
142+
- TypeScript types
143+
- Unit tests
144+
- Any required business logic for value manipulation
145+
146+
> [!IMPORTANT]
147+
> Always use the specific feature flag selector when accessing values. Accessing feature flags directly from Redux state or the main selector bypasses fallback values and can cause crashes.
148+
149+
### 3. Testing Remote Feature Flags
150+
151+
#### Extension
152+
153+
##### Local Feature Flag Override
154+
155+
- Developers can override `remoteFeatureFlag` values by defining them in `.manifest-overrides.json` and enable `MANIFEST_OVERRIDES=.manifest-overrides.json` in the `.metamaskrc.dist` locally.
156+
- These overrides take precedence over values received from the controller
157+
- Accessible in all development environments:
158+
- Local builds
159+
- Webpack builds
160+
- E2E tests
161+
162+
##### Developer Validation
163+
164+
##### A. Local Build
165+
166+
1. Define overrides in `remoteFeatureFlags` in `manifest-overrides.json`:
167+
168+
```json
169+
{
170+
"_flags": {
171+
"remoteFeatureFlags": {
172+
"testFlagForThreshold": {
173+
"name": "test-flag",
174+
"value": "121212"
175+
}
176+
}
177+
}
178+
}
179+
```
180+
181+
2. Verify in Developer Options:
182+
- Open extension
183+
- Click on the account menu (top-right corner)
184+
- Select "Settings" > "Developer Options"
185+
- Look for "Remote Feature Flags" section to verify your overrides
186+
187+
##### B. E2E Test
188+
189+
Add the customized value in your test configuration:
190+
191+
```typescript
192+
fixtures: new FixtureBuilder()
193+
.withMetaMetricsController({
194+
metaMetricsId: MOCK_META_METRICS_ID,
195+
participateInMetaMetrics: true,
196+
})
197+
.build(),
198+
```
199+
200+
#### Mobile
201+
202+
##### Local Feature Flag Override
203+
204+
1. Set environment variable in `.js.env` and run the app. This forces the fallback values to override any remote values.
205+
206+
```
207+
OVERRIDE_REMOTE_FEATURE_FLAGS=TRUE
208+
```
209+
210+
2. Test different feature flag scenarios:
211+
- Modify the default values in your feature flag selector
212+
- Run the application to verify behavior with different values
213+
- Remember to test both override and non-override scenarios

docs/testing/e2e/mobile-e2e-mocking.md

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Mocking APIs in MetaMask Mobile for Enhanced E2E Testing
32

43
## Introduction
@@ -17,6 +16,7 @@ We use mocking to enhance our E2E testing, especially for scenarios where E2E al
1716
## File Structure
1817

1918
We keep E2E and mock tests separate with file naming conventions:
19+
2020
```
2121
root/
2222
├── e2e/
@@ -32,6 +32,7 @@ root/
3232
│ │ ├── mock-responses/
3333
│ │ │ ├── gas-api-responses.json
3434
```
35+
3536
This structure promotes clear organisation and makes managing tests simpler.
3637

3738
## Mock Server Implementation
@@ -58,12 +59,8 @@ The `startMockServer` function in `e2e/api-mocking/mock-server.js` starts the mo
5859
import { mockEvents } from '../api-mocking/mock-config/mock-events';
5960

6061
mockServer = await startMockServer({
61-
GET: [
62-
mockEvents.GET.suggestedGasApiErrorResponse,
63-
],
64-
POST: [
65-
mockEvents.POST.suggestedGasApiPostResponse,
66-
],
62+
GET: [mockEvents.GET.suggestedGasApiErrorResponse],
63+
POST: [mockEvents.POST.suggestedGasApiPostResponse],
6764
});
6865
```
6966

@@ -98,6 +95,7 @@ export const mockEvents = {
9895
Mock responses are stored in individual JSON files for each API or service within the `mock-responses` folder, making them easier to maintain and manage. Each API service has its own JSON response file, such as `gasApiResponse.json` for gas-related responses and `ethpriceResponse.json` for Ethereum price responses. This organisation enables clear separation of mock data and simplifies updates or additions.
9996

10097
**Example:** `gasApiResponse.json`
98+
10199
```json
102100
{
103101
"suggestedGasApiResponses": {
@@ -117,7 +115,6 @@ The mock server logs response statuses and bodies to help track mocked requests,
117115

118116
## Using Mock Testing Effectively
119117

120-
121118
### When to Use Mocks:
122119

123120
- For testing isolated features without relying on live data
@@ -126,14 +123,13 @@ The mock server logs response statuses and bodies to help track mocked requests,
126123

127124
### When Not to Use Mocks:
128125

129-
- Stable Live Environments: When APIs and services are reliable, testing live ensures production-like accuracy.
126+
- Stable Live Environments: When APIs and services are reliable, testing live ensures production-like accuracy.
130127
- Integration Testing: Live tests validate interactions with third-party services, capturing real-world behaviour.
131128
- Performance Testing: Only live environments provide accurate latency and throughput metrics.
132129
- Dynamic Data Scenarios: Features relying on user data or complex workflows may reveal issues that mocks miss.
133130

134131
There should be a mix of tests that verify real-life services and some that use mocks, when applicable, to achieve comprehensive coverage.
135132

136-
137133
### Utilizing Fixtures with testSpecificMock
138134

139135
For more complex mock events or criteria, you can use the `mockSpecificTest` object to define custom mock events.
@@ -145,7 +141,8 @@ test.use({
145141
testSpecificMock: {
146142
GET: [
147143
{
148-
urlEndpoint: 'https://gas.api.cx.metamask.io/networks/1/suggestedGasFees',
144+
urlEndpoint:
145+
'https://gas.api.cx.metamask.io/networks/1/suggestedGasFees',
149146
response: { status: 200, message: 'Custom Success Response' },
150147
},
151148
],

0 commit comments

Comments
 (0)