Skip to content

Commit 37b0d53

Browse files
chore(runway): cherry-pick fix: cp-7.49.0 Make confirmation_redesign feature flags work as true kill-switch (#16240)
- fix: cp-7.49.0 Make `confirmation_redesign` feature flags work as true kill-switch (#16238) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Refactored the confirmation redesign feature flag selector to use a simpler "kill switch" approach instead of validation-based fallbacks. ### **Simplified feature flag logic:** - Replaced validation and default fallback pattern with direct `remoteValues?.property !== false` checks - Remote flags now act purely as "kill switches" - they can disable features when explicitly set to `false` The logic now defaults to `true` for features when remote flags are not explicitly set to `false`, rather than falling back to predefined default values. This maintains the same kill switch functionality while simplifying the implementation. ## **Related issues** Fixes: https://github.com/MetaMask/MetaMask-planning/issues/5110 ## **Manual testing steps** N/A ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [X] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.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-mobile/blob/main/.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. [420127b](420127b) Co-authored-by: OGPoyraz <omergoktugpoyraz@gmail.com>
1 parent 4909a57 commit 37b0d53

2 files changed

Lines changed: 129 additions & 60 deletions

File tree

app/selectors/featureFlagController/confirmations/index.test.ts

Lines changed: 85 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@ jest.mock('../../../core/Engine', () => ({
66
init: () => mockedEngine.init(),
77
}));
88

9-
// Mock getFeatureFlagValue function
10-
jest.mock('../env', () => ({
11-
getFeatureFlagValue: jest.fn((_, defaultValue) => defaultValue)
12-
}));
13-
14-
// Mock process.env values
159
const originalEnv = process.env;
1610
beforeEach(() => {
1711
jest.resetModules();
@@ -27,42 +21,50 @@ afterEach(() => {
2721
jest.clearAllMocks();
2822
});
2923

30-
// Default values from the implementation
3124
const confirmationRedesignFlagsDefaultValues: ConfirmationRedesignRemoteFlags = {
3225
signatures: true,
33-
staking_confirmations: false,
34-
contract_interaction: false,
35-
transfer: false,
26+
staking_confirmations: true,
27+
contract_interaction: true,
28+
transfer: true,
3629
};
3730

38-
// Define mocked remote values for tests
3931
const mockedConfirmationRedesignFlags: ConfirmationRedesignRemoteFlags = {
4032
signatures: false,
4133
staking_confirmations: true,
4234
contract_interaction: true,
43-
transfer: true,
35+
transfer: false,
4436
};
4537

46-
// Update the mock state to include our feature flags
47-
jest.mock('../mocks', () => {
48-
const originalModule = jest.requireActual('../mocks');
49-
return {
50-
...originalModule,
51-
mockedState: {
52-
engine: {
53-
backgroundState: {
54-
RemoteFeatureFlagController: {
55-
remoteFeatureFlags: {
56-
confirmation_redesign: mockedConfirmationRedesignFlags
57-
}
38+
const mockedStateWithConfirmationFlags = {
39+
engine: {
40+
backgroundState: {
41+
RemoteFeatureFlagController: {
42+
remoteFeatureFlags: {
43+
confirmation_redesign: mockedConfirmationRedesignFlags
44+
},
45+
cacheTimestamp: 0,
46+
}
47+
}
48+
}
49+
};
50+
51+
const mockedStateWithPartialFlags = {
52+
engine: {
53+
backgroundState: {
54+
RemoteFeatureFlagController: {
55+
remoteFeatureFlags: {
56+
confirmation_redesign: {
57+
signatures: false,
58+
// Other flags are undefined, should default to true
5859
}
59-
}
60+
},
61+
cacheTimestamp: 0,
6062
}
6163
}
62-
};
63-
});
64+
}
65+
};
6466

65-
describe('confirmationRedesign Feature flag: selectConfirmationRedesignFlags selector', () => {
67+
describe('Confirmation Redesign Feature Flags', () => {
6668
const testFlagValues = (result: unknown, expected: ConfirmationRedesignRemoteFlags) => {
6769
const {
6870
signatures,
@@ -84,17 +86,70 @@ describe('confirmationRedesign Feature flag: selectConfirmationRedesignFlags sel
8486
expect(transfer).toEqual(expectedTransfer);
8587
};
8688

87-
it('returns default values when empty feature flag state', () => {
89+
it('returns default values (all true) when empty feature flag state', () => {
8890
testFlagValues(
8991
selectConfirmationRedesignFlags(mockedEmptyFlagsState),
9092
confirmationRedesignFlagsDefaultValues
9193
);
9294
});
9395

94-
it('returns default values when undefined RemoteFeatureFlagController state', () => {
96+
it('returns default values (all true) when undefined RemoteFeatureFlagController state', () => {
9597
testFlagValues(
9698
selectConfirmationRedesignFlags(mockedUndefinedFlagsState),
9799
confirmationRedesignFlagsDefaultValues
98100
);
99101
});
102+
103+
it('returns remote flag values when confirmation_redesign flags are set', () => {
104+
testFlagValues(
105+
selectConfirmationRedesignFlags(mockedStateWithConfirmationFlags),
106+
mockedConfirmationRedesignFlags
107+
);
108+
});
109+
110+
it('returns mix of remote and default values when only some flags are set', () => {
111+
const expected: ConfirmationRedesignRemoteFlags = {
112+
signatures: false, // explicitly set to false
113+
staking_confirmations: true, // undefined, defaults to true
114+
contract_interaction: true, // undefined, defaults to true
115+
transfer: true, // undefined, defaults to true
116+
};
117+
118+
testFlagValues(
119+
selectConfirmationRedesignFlags(mockedStateWithPartialFlags),
120+
expected
121+
);
122+
});
123+
124+
it('handles kill switch behavior - remote false overrides default true', () => {
125+
const killSwitchState = {
126+
engine: {
127+
backgroundState: {
128+
RemoteFeatureFlagController: {
129+
remoteFeatureFlags: {
130+
confirmation_redesign: {
131+
signatures: false, // Kill switch - disables the feature
132+
staking_confirmations: false,
133+
contract_interaction: false,
134+
transfer: false,
135+
}
136+
},
137+
cacheTimestamp: 0,
138+
}
139+
}
140+
}
141+
};
142+
143+
const expectedKillSwitchValues: ConfirmationRedesignRemoteFlags = {
144+
signatures: false,
145+
staking_confirmations: false,
146+
contract_interaction: false,
147+
transfer: false,
148+
};
149+
150+
testFlagValues(
151+
selectConfirmationRedesignFlags(killSwitchState),
152+
expectedKillSwitchValues
153+
);
154+
});
100155
});

app/selectors/featureFlagController/confirmations/index.ts

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Json, hasProperty, isObject } from '@metamask/utils';
21
import { createSelector } from 'reselect';
32
import { selectRemoteFeatureFlags } from '..';
43
import { getFeatureFlagValue } from '../env';
@@ -12,52 +11,67 @@ export type ConfirmationRedesignRemoteFlags = {
1211
transfer: boolean;
1312
};
1413

15-
const isRemoteFeatureFlagValuesValid = (
16-
obj: Json,
17-
): obj is ConfirmationRedesignRemoteFlags =>
18-
isObject(obj) &&
19-
hasProperty(obj, 'signatures') &&
20-
hasProperty(obj, 'staking_confirmations') &&
21-
hasProperty(obj, 'contract_interaction') &&
22-
hasProperty(obj, 'transfer');
23-
24-
const confirmationRedesignFlagsDefaultValues: ConfirmationRedesignRemoteFlags =
25-
{
26-
signatures: true,
27-
staking_confirmations: false,
28-
contract_interaction: false,
29-
transfer: false,
30-
};
31-
14+
/**
15+
* Determines the enabled state of confirmation redesign features by combining
16+
* local environment variables with remote feature flags.
17+
*
18+
* Remote feature flags act as a "kill switch" - when no local environment variable
19+
* is set, the remote flag value takes precedence. If a remote flag is explicitly
20+
* set to `false`, it can disable the feature remotely.
21+
*
22+
* ## Adding New Confirmation Flag
23+
*
24+
* **During Development:**
25+
* Use a local environment variable with a default fallback:
26+
* ```
27+
* const isNewConfirmationTypeEnabled = getFeatureFlagValue(
28+
* process.env.FEATURE_FLAG_REDESIGNED_NEW_CONFIRMATION_TYPE,
29+
* false,
30+
* );
31+
* ```
32+
*
33+
* **After Development (On Release):**
34+
* Replace the fallback with the remote kill switch:
35+
* ```
36+
* const isNewConfirmationTypeEnabled = getFeatureFlagValue(
37+
* process.env.FEATURE_FLAG_REDESIGNED_NEW_CONFIRMATION_TYPE,
38+
* remoteValues?.new_confirmation_type !== false,
39+
* );
40+
* ```
41+
*
42+
* **After Validation In Production For Certain Time(When old code is decided to be removed):**
43+
* Remove the both local environment variable and remote flag as kill switch is non-functional.
44+
* ```
45+
* const isNewConfirmationTypeEnabled = true;
46+
* ```
47+
*
48+
* @param remoteFeatureFlags - The remote feature flags object containing confirmation_redesign settings
49+
* @returns An object with boolean flags for each confirmation redesign feature
50+
*/
3251
export const selectConfirmationRedesignFlagsFromRemoteFeatureFlags = (
3352
remoteFeatureFlags: ReturnType<typeof selectRemoteFeatureFlags>,
34-
) => {
35-
const remoteValues = remoteFeatureFlags.confirmation_redesign;
36-
37-
const confirmationRedesignFlags = isRemoteFeatureFlagValuesValid(
38-
remoteValues,
39-
)
40-
? remoteValues
41-
: confirmationRedesignFlagsDefaultValues;
53+
): ConfirmationRedesignRemoteFlags => {
54+
const remoteValues =
55+
remoteFeatureFlags.confirmation_redesign as ConfirmationRedesignRemoteFlags;
4256

4357
const isSignaturesEnabled = getFeatureFlagValue(
4458
process.env.FEATURE_FLAG_REDESIGNED_SIGNATURES,
45-
confirmationRedesignFlags.signatures,
59+
remoteValues?.signatures !== false,
4660
);
4761

4862
const isStakingConfirmationsEnabled = getFeatureFlagValue(
4963
process.env.FEATURE_FLAG_REDESIGNED_STAKING_TRANSACTIONS,
50-
confirmationRedesignFlags.staking_confirmations,
64+
remoteValues?.staking_confirmations !== false,
5165
);
5266

5367
const isContractInteractionEnabled = getFeatureFlagValue(
5468
process.env.FEATURE_FLAG_REDESIGNED_CONTRACT_INTERACTION,
55-
confirmationRedesignFlags.contract_interaction,
69+
remoteValues?.contract_interaction !== false,
5670
);
5771

5872
const isTransferEnabled = getFeatureFlagValue(
5973
process.env.FEATURE_FLAG_REDESIGNED_TRANSFER,
60-
confirmationRedesignFlags.transfer,
74+
remoteValues?.transfer !== false,
6175
);
6276

6377
return {

0 commit comments

Comments
 (0)