Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Composer/packages/lib/shared/src/featureFlagUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const getDefaultFeatureFlags = (): FeatureFlagMap => ({
FORM_DIALOG: {
displayName: formatMessage('Show Form Dialog'),
description: formatMessage('Show form dialog editor in the canvas'),
isHidden: false,
isHidden: true,
enabled: false,
},
});
107 changes: 51 additions & 56 deletions Composer/packages/server/src/services/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,69 +21,64 @@ export class FeatureFlagService {
private static updateFeatureFlags = () => {
const currentFeatureFlagKeys = Object.keys(FeatureFlagService.currentFeatureFlagMap);
const defaultFeatureFlagKeys = Object.keys(FeatureFlagService.defaultFeatureFlags);
const keysToAdd: string[] = [];
const keysToUpdateHidden: string[] = [];

defaultFeatureFlagKeys.forEach((key: string) => {
if (!currentFeatureFlagKeys.includes(key)) {
keysToAdd.push(key);
} else if (
currentFeatureFlagKeys.includes(key) &&
FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden !==
FeatureFlagService.defaultFeatureFlags[key as FeatureFlagKey].isHidden
) {
keysToUpdateHidden.push(key);
}
});

const keysToRemove = currentFeatureFlagKeys.filter((key: string) => {
if (!defaultFeatureFlagKeys.includes(key)) {
return key;
}
});

keysToAdd.forEach((key: string) => {
FeatureFlagService.currentFeatureFlagMap[key] = FeatureFlagService.defaultFeatureFlags[key];
// apply defaults to the loaded feature flags
Object.keys(FeatureFlagService.currentFeatureFlagMap).forEach((key) => {
FeatureFlagService.currentFeatureFlagMap[key] = {
...FeatureFlagService.defaultFeatureFlags[key],
...FeatureFlagService.currentFeatureFlagMap[key],
};
});

keysToRemove.forEach((key: string) => {
delete FeatureFlagService.currentFeatureFlagMap[key];
});

keysToUpdateHidden.forEach((key: string) => {
FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden =
FeatureFlagService.defaultFeatureFlags[key as FeatureFlagKey].isHidden;
});

const hiddenFeatureFlagUpdated = FeatureFlagService.updateHiddenFeatureFlags();

if (
keysToRemove?.length > 0 ||
keysToAdd?.length > 0 ||
keysToUpdateHidden?.length > 0 ||
hiddenFeatureFlagUpdated
) {
Store.set(storeKey, FeatureFlagService.currentFeatureFlagMap);
let saveNeeded = false;

// add any new keys defined in the defaults that aren't in current
defaultFeatureFlagKeys
.filter((key: string) => !currentFeatureFlagKeys.includes(key))
.forEach((key: string) => {
FeatureFlagService.currentFeatureFlagMap[key] = FeatureFlagService.defaultFeatureFlags[key];
saveNeeded = true;
});

// remove any keys no longer in default that are in current
currentFeatureFlagKeys
.filter((key: string) => !defaultFeatureFlagKeys.includes(key))
.forEach((key: string) => {
delete FeatureFlagService.currentFeatureFlagMap[key];
saveNeeded = true;
});

// set any hidden feature flags from the process
// process should override value for hidden features flags
Object.keys(FeatureFlagService.currentFeatureFlagMap)
.filter((key) => FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden)
.forEach((key) => {
const enabled = FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].enabled;
const processEnvEnabled = key in process.env;

if (enabled !== processEnvEnabled) {
FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].enabled = processEnvEnabled;
saveNeeded = true;
}
});

if (saveNeeded) {
FeatureFlagService.saveFeatureFlags();
}
};

private static updateHiddenFeatureFlags = (): boolean => {
const hiddenFeatureFlagKeys = Object.keys(FeatureFlagService.currentFeatureFlagMap).filter((key: string) => {
if (FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].isHidden) {
return key;
}
});
private static saveFeatureFlags() {
const saveFeatureFlagMap: FeatureFlagMap = {} as FeatureFlagMap;

let result = false;
hiddenFeatureFlagKeys.forEach((key: string) => {
if (process.env[key] && process.env[key] !== FeatureFlagService.currentFeatureFlagMap[key]) {
FeatureFlagService.currentFeatureFlagMap[key as FeatureFlagKey].enabled =
process.env[key]?.toLowerCase() === 'true';
result = true;
}
// only save user data to avoid replacing values from defaults like displayName
Object.keys(FeatureFlagService.currentFeatureFlagMap).forEach((key) => {
saveFeatureFlagMap[key] = {
enabled: FeatureFlagService.currentFeatureFlagMap[key].enabled,
isHidden: FeatureFlagService.currentFeatureFlagMap[key].isHidden,
};
});
return result;
};
Store.set(storeKey, saveFeatureFlagMap);
}

public static getFeatureFlags(): FeatureFlagMap {
FeatureFlagService.initialize();
Expand All @@ -92,7 +87,7 @@ export class FeatureFlagService {

public static updateFeatureFlag(newFeatureFlags: FeatureFlagMap) {
FeatureFlagService.currentFeatureFlagMap = newFeatureFlags;
Store.set(storeKey, newFeatureFlags);
FeatureFlagService.saveFeatureFlags();
}

public static getFeatureFlagValue(featureFlagKey: FeatureFlagKey): boolean {
Expand Down