Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
bec04d7
feat: add override functionality to remote feature flags
asalsys Nov 30, 2025
e78270a
fix lint
asalsys Dec 1, 2025
fe7db8b
fix lint
asalsys Dec 2, 2025
4413b2d
fix lint
asalsys Dec 2, 2025
e07e67c
fix lint
asalsys Dec 2, 2025
ec17188
edit changelog
asalsys Dec 2, 2025
fbdbd14
update pr info on changelog
asalsys Dec 2, 2025
c9cd5aa
fix test
asalsys Dec 2, 2025
4d02e47
fix transaction-controller test
asalsys Dec 2, 2025
e3a4f8f
fix prettier
asalsys Dec 2, 2025
35b8470
fix transaction pay test
asalsys Dec 2, 2025
4201e40
remove get actions
asalsys Dec 4, 2025
7ae47a3
update changelog and fix lint
asalsys Dec 4, 2025
71e4b7a
fix lint
asalsys Dec 8, 2025
ca479c8
fix tests
asalsys Dec 9, 2025
0063bce
update tests
asalsys Dec 9, 2025
9bbf745
update objects in tests
asalsys Dec 9, 2025
414f0f0
Merge branch 'main' into feat/override-functionality-to-remote-featur…
asalsys Dec 9, 2025
f17c947
fix tests
asalsys Dec 9, 2025
4d349ed
update to use the processVersionBasedFlag
asalsys Dec 10, 2025
6a6b98f
spread state
asalsys Dec 11, 2025
7e3500d
fix lint
asalsys Dec 11, 2025
434c892
Merge branch 'main' into feat/override-functionality-to-remote-featur…
asalsys Dec 11, 2025
6c75509
use rawRemoteFeatureFlags instead of processing each flag
asalsys Dec 11, 2025
e562809
fix update Cache
asalsys Dec 11, 2025
a09ceaa
fix lint
asalsys Dec 11, 2025
ffd58e9
update tests
asalsys Dec 11, 2025
c1af969
fix lint
asalsys Dec 11, 2025
9d99264
Update packages/remote-feature-flag-controller/src/remote-feature-fla…
asalsys Dec 12, 2025
1f49998
Update packages/remote-feature-flag-controller/CHANGELOG.md
asalsys Dec 12, 2025
e8f2ff1
Update packages/remote-feature-flag-controller/src/remote-feature-fla…
asalsys Dec 12, 2025
83017de
Update packages/remote-feature-flag-controller/src/remote-feature-fla…
asalsys Dec 12, 2025
6447545
Update packages/remote-feature-flag-controller/src/remote-feature-fla…
asalsys Dec 12, 2025
28a8548
Update packages/remote-feature-flag-controller/src/remote-feature-fla…
asalsys Dec 12, 2025
c3d09e5
Update packages/remote-feature-flag-controller/CHANGELOG.md
asalsys Dec 12, 2025
10c168d
add changes
asalsys Dec 12, 2025
d1357b7
address comments and fix changes
asalsys Dec 12, 2025
fb818a8
fix lint
asalsys Dec 12, 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
Prev Previous commit
Next Next commit
update changelog and fix lint
  • Loading branch information
asalsys committed Dec 8, 2025
commit 7ae47a3844dfdf24f57514a09a35c695ce83bcaa
6 changes: 0 additions & 6 deletions packages/remote-feature-flag-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add override functionality to remote feature flags ([#7271](https://github.com/MetaMask/core/pull/7271))
- `setFlagOverride(flagName, value)` - Set a local override for a specific feature flag
- `getFlagOverride(flagName)` - Get the current override value for a specific feature flag
- `clearFlagOverride(flagName)` - Clear the local override for a specific feature flag
- `clearAllOverrides()` - Clear all local feature flag overrides
- `getFlag(flagName)` - Get the effective value of a feature flag (override takes precedence over remote)
- `getAllFlags()` - Get all effective feature flags, combining remote flags with local overrides
- Add A/B test visibility functionality ([#7271](https://github.com/MetaMask/core/pull/7271))
- `getAvailableABTestGroups(flagName)` - Get available A/B test groups for a specific feature flag
- `getAllABTestFlags()` - Get all feature flags that have A/B test groups available
- Add new controller state properties ([#7271](https://github.com/MetaMask/core/pull/7271))
- `localOverrides` - Local overrides for feature flags that take precedence over remote flags
- `rawProcessedRemoteFeatureFlags` - Raw flag value for arrays that were processed from arrays to single value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,9 @@ describe('RemoteFeatureFlagController', () => {
controller.setFlagOverride('testFlagForThreshold', 'overriddenValue');

// Access flag value with override taking precedence
const flagValue = controller.state.localOverrides['testFlagForThreshold'] ?? controller.state.remoteFeatureFlags['testFlagForThreshold'];
const flagValue =
controller.state.localOverrides.testFlagForThreshold ??
Copy link
Contributor

@mcmire mcmire Dec 11, 2025

Choose a reason for hiding this comment

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

Why are we using ?? here? Do we not expect controller.state.localOverrides.testFlagForThreshold to be set, and if not, why?

Copy link
Author

Choose a reason for hiding this comment

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

removed test

controller.state.remoteFeatureFlags.testFlagForThreshold;
expect(flagValue).toBe('overriddenValue');
});
});
Expand All @@ -799,9 +801,8 @@ describe('RemoteFeatureFlagController', () => {

await controller.updateRemoteFeatureFlags();

const groups = controller.state.rawProcessedRemoteFeatureFlags[
'testFlagForThreshold'
];
const groups =
controller.state.rawProcessedRemoteFeatureFlags.testFlagForThreshold;

expect(groups).toStrictEqual([
{
Expand Down Expand Up @@ -831,15 +832,15 @@ describe('RemoteFeatureFlagController', () => {
await controller.updateRemoteFeatureFlags();

expect(
controller.state.rawProcessedRemoteFeatureFlags['simpleFlag']
controller.state.rawProcessedRemoteFeatureFlags.simpleFlag,
).toBeUndefined();
});

it('returns undefined for non-existent flags', () => {
const controller = createController();

expect(
controller.state.rawProcessedRemoteFeatureFlags['nonExistentFlag']
controller.state.rawProcessedRemoteFeatureFlags.nonExistentFlag,
).toBeUndefined();
});
});
Expand Down Expand Up @@ -886,13 +887,17 @@ describe('RemoteFeatureFlagController', () => {

await controller.updateRemoteFeatureFlags();

expect(controller.state.rawProcessedRemoteFeatureFlags).toStrictEqual({});
expect(controller.state.rawProcessedRemoteFeatureFlags).toStrictEqual(
{},
);
});

it('returns empty object when no flags exist', () => {
const controller = createController();

expect(controller.state.rawProcessedRemoteFeatureFlags).toStrictEqual({});
expect(controller.state.rawProcessedRemoteFeatureFlags).toStrictEqual(
{},
);
});
});

Expand All @@ -911,15 +916,17 @@ describe('RemoteFeatureFlagController', () => {
await controller.updateRemoteFeatureFlags();

// Only A/B test flags should be stored in rawProcessedRemoteFeatureFlags
expect(Object.keys(controller.state.rawProcessedRemoteFeatureFlags)).toStrictEqual([
'testFlagForThreshold',
]);
expect(
Object.keys(controller.state.rawProcessedRemoteFeatureFlags),
).toStrictEqual(['testFlagForThreshold']);
expect(
controller.state.rawProcessedRemoteFeatureFlags.testFlagForThreshold,
).toStrictEqual(MOCK_FLAGS_WITH_THRESHOLD.testFlagForThreshold);

// Simple flags should not be in rawProcessedRemoteFeatureFlags
expect(controller.state.rawProcessedRemoteFeatureFlags.simpleFlag).toBeUndefined();
expect(
controller.state.rawProcessedRemoteFeatureFlags.simpleFlag,
).toBeUndefined();
expect(
controller.state.rawProcessedRemoteFeatureFlags.anotherSimpleFlag,
).toBeUndefined();
Expand All @@ -939,9 +946,8 @@ describe('RemoteFeatureFlagController', () => {
await controller.updateRemoteFeatureFlags();

// Get available groups
const groups = controller.state.rawProcessedRemoteFeatureFlags[
'testFlagForThreshold'
];
const groups =
controller.state.rawProcessedRemoteFeatureFlags.testFlagForThreshold;
expect(groups).toBeDefined();
// Ensure groups is not undefined before accessing array elements
if (!groups) {
Expand All @@ -950,12 +956,18 @@ describe('RemoteFeatureFlagController', () => {
expect(groups).toHaveLength(3); // Expecting 3 groups based on MOCK_FLAGS_WITH_THRESHOLD

// Override with a specific group value
const firstGroup = Array.isArray(groups) && groups.length > 0 ? groups[0] : null;
const groupValue = firstGroup && typeof firstGroup === 'object' && 'value' in firstGroup ? firstGroup.value : 'valueA';
const firstGroup =
Array.isArray(groups) && groups.length > 0 ? groups[0] : null;
const groupValue =
firstGroup && typeof firstGroup === 'object' && 'value' in firstGroup
? firstGroup.value
: 'valueA';
controller.setFlagOverride('testFlagForThreshold', groupValue); // groupA value

// Access flag value with override taking precedence
const flagValue = controller.state.localOverrides['testFlagForThreshold'] ?? controller.state.remoteFeatureFlags['testFlagForThreshold'];
const flagValue =
controller.state.localOverrides.testFlagForThreshold ??
controller.state.remoteFeatureFlags.testFlagForThreshold;
expect(flagValue).toBe('valueA');
});

Expand All @@ -969,9 +981,8 @@ describe('RemoteFeatureFlagController', () => {
controller.setFlagOverride('testFlagForThreshold', 'overrideValue');

// A/B test raw flags should still be available
const groups = controller.state.rawProcessedRemoteFeatureFlags[
'testFlagForThreshold'
];
const groups =
controller.state.rawProcessedRemoteFeatureFlags.testFlagForThreshold;
expect(groups).toHaveLength(3);
expect(
controller.state.rawProcessedRemoteFeatureFlags.testFlagForThreshold,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
FeatureFlags,
ServiceResponse,
FeatureFlagScopeValue,
FeatureFlagScope,
} from './remote-feature-flag-controller-types';
import {
generateDeterministicRandomNumber,
Expand Down Expand Up @@ -97,7 +96,7 @@
| RemoteFeatureFlagControllerUpdateRemoteFeatureFlagsAction
| RemoteFeatureFlagControllerSetFlagOverrideAction
| RemoteFeatureFlagControllerClearFlagOverrideAction
| RemoteFeatureFlagControllerClearAllOverridesAction
| RemoteFeatureFlagControllerClearAllOverridesAction;

export type RemoteFeatureFlagControllerStateChangeEvent =
ControllerStateChangeEvent<
Expand Down Expand Up @@ -274,9 +273,10 @@
return getVersionData(flagValue, this.#clientVersion);
}

async #processRemoteFeatureFlags(
remoteFeatureFlags: FeatureFlags,
): Promise<{ processedFlags: FeatureFlags; rawProcessedRemoteFeatureFlags: FeatureFlags }> {
async #processRemoteFeatureFlags(remoteFeatureFlags: FeatureFlags): Promise<{
processedFlags: FeatureFlags;
rawProcessedRemoteFeatureFlags: FeatureFlags;
}> {
const processedRemoteFeatureFlags: FeatureFlags = {};
const rawProcessedRemoteFeatureFlags: FeatureFlags = {};
const metaMetricsId = this.#getMetaMetricsId();
Expand All @@ -295,8 +295,9 @@

if (Array.isArray(processedValue) && thresholdValue) {
// Store the raw A/B test array for later use
rawProcessedRemoteFeatureFlags[remoteFeatureFlagName] = remoteFeatureFlagValue;
rawProcessedRemoteFeatureFlags[remoteFeatureFlagName] =
remoteFeatureFlagValue;

Check failure on line 300 in packages/remote-feature-flag-controller/src/remote-feature-flag-controller.ts

View workflow job for this annotation

GitHub Actions / Lint, build, and test / Lint (22.x)

Delete `········`
const selectedGroup = processedValue.find(
(featureFlag): featureFlag is FeatureFlagScopeValue => {
if (!isFeatureFlagWithScopeValue(featureFlag)) {
Expand All @@ -316,7 +317,10 @@

processedRemoteFeatureFlags[remoteFeatureFlagName] = processedValue;
}
return { processedFlags: processedRemoteFeatureFlags, rawProcessedRemoteFeatureFlags };
return {
processedFlags: processedRemoteFeatureFlags,
rawProcessedRemoteFeatureFlags,
};
}

/**
Expand Down Expand Up @@ -347,12 +351,13 @@
...this.state.localOverrides,
[flagName]: value,
},
rawProcessedRemoteFeatureFlags: this.state.rawProcessedRemoteFeatureFlags,
rawProcessedRemoteFeatureFlags:
this.state.rawProcessedRemoteFeatureFlags,
cacheTimestamp: this.state.cacheTimestamp,
};
});
}

/**
* Clears the local override for a specific feature flag.
*
Expand All @@ -365,7 +370,8 @@
return {
remoteFeatureFlags: this.state.remoteFeatureFlags,
localOverrides: newOverrides,
Copy link
Contributor

Choose a reason for hiding this comment

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

spread ..this.state for unaffected states

rawProcessedRemoteFeatureFlags: this.state.rawProcessedRemoteFeatureFlags,
rawProcessedRemoteFeatureFlags:
this.state.rawProcessedRemoteFeatureFlags,
cacheTimestamp: this.state.cacheTimestamp,
};
});
Expand All @@ -379,7 +385,8 @@
return {
remoteFeatureFlags: this.state.remoteFeatureFlags,
localOverrides: {},
Copy link
Contributor

Choose a reason for hiding this comment

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

Spread state for unaffected states

rawProcessedRemoteFeatureFlags: this.state.rawProcessedRemoteFeatureFlags,
rawProcessedRemoteFeatureFlags:
this.state.rawProcessedRemoteFeatureFlags,
cacheTimestamp: this.state.cacheTimestamp,
};
});
Expand Down
Loading