mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
refactor: flag resolver should use stricter types (#2571)
Adding stricter types to `FlagResolver` can possibly help improve our DX - Help us prevent errors like typos, guide us to correctly add a flag when needed, and warn us of stray checks whenever we do a clean up at a later stage.
This commit is contained in:
parent
4b519ead4f
commit
7ce5b3de64
@ -67,6 +67,7 @@ exports[`should create default config 1`] = `
|
|||||||
"isEnabled": [Function],
|
"isEnabled": [Function],
|
||||||
},
|
},
|
||||||
"flags": {
|
"flags": {
|
||||||
|
"E": false,
|
||||||
"ENABLE_DARK_MODE_SUPPORT": false,
|
"ENABLE_DARK_MODE_SUPPORT": false,
|
||||||
"anonymiseEventLog": false,
|
"anonymiseEventLog": false,
|
||||||
"batchMetrics": false,
|
"batchMetrics": false,
|
||||||
@ -83,6 +84,7 @@ exports[`should create default config 1`] = `
|
|||||||
},
|
},
|
||||||
"flagResolver": FlagResolver {
|
"flagResolver": FlagResolver {
|
||||||
"experiments": {
|
"experiments": {
|
||||||
|
"E": false,
|
||||||
"ENABLE_DARK_MODE_SUPPORT": false,
|
"ENABLE_DARK_MODE_SUPPORT": false,
|
||||||
"anonymiseEventLog": false,
|
"anonymiseEventLog": false,
|
||||||
"batchMetrics": false,
|
"batchMetrics": false,
|
||||||
|
@ -1,70 +1,61 @@
|
|||||||
import { parseEnvVarBoolean } from '../util';
|
import { parseEnvVarBoolean } from '../util';
|
||||||
|
|
||||||
export type IFlags = Partial<Record<string, boolean>>;
|
export type IFlags = Partial<typeof flags>;
|
||||||
|
export type IFlagKey = keyof IFlags;
|
||||||
|
|
||||||
export const defaultExperimentalOptions = {
|
const flags = {
|
||||||
flags: {
|
E: false,
|
||||||
ENABLE_DARK_MODE_SUPPORT: false,
|
ENABLE_DARK_MODE_SUPPORT: false,
|
||||||
anonymiseEventLog: false,
|
anonymiseEventLog: false,
|
||||||
embedProxy: parseEnvVarBoolean(
|
embedProxy: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
|
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
changeRequests: parseEnvVarBoolean(
|
changeRequests: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_CHANGE_REQUESTS,
|
process.env.UNLEASH_EXPERIMENTAL_CHANGE_REQUESTS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
embedProxyFrontend: parseEnvVarBoolean(
|
embedProxyFrontend: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY_FRONTEND,
|
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY_FRONTEND,
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
batchMetrics: parseEnvVarBoolean(
|
batchMetrics: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_BATCH_METRICS,
|
process.env.UNLEASH_EXPERIMENTAL_BATCH_METRICS,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
responseTimeWithAppName: parseEnvVarBoolean(
|
responseTimeWithAppName: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_RESPONSE_TIME_WITH_APP_NAME,
|
process.env.UNLEASH_EXPERIMENTAL_RESPONSE_TIME_WITH_APP_NAME,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
proxyReturnAllToggles: parseEnvVarBoolean(
|
proxyReturnAllToggles: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_PROXY_RETURN_ALL_TOGGLES,
|
process.env.UNLEASH_EXPERIMENTAL_PROXY_RETURN_ALL_TOGGLES,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
variantsPerEnvironment: parseEnvVarBoolean(
|
variantsPerEnvironment: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_VARIANTS_PER_ENVIRONMENT,
|
process.env.UNLEASH_EXPERIMENTAL_VARIANTS_PER_ENVIRONMENT,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
favorites: parseEnvVarBoolean(
|
favorites: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_FAVORITES,
|
process.env.UNLEASH_EXPERIMENTAL_FAVORITES,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
maintenance: parseEnvVarBoolean(
|
networkView: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_MAINTENANCE,
|
process.env.UNLEASH_EXPERIMENTAL_NETWORK_VIEW,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
networkView: parseEnvVarBoolean(
|
maintenance: parseEnvVarBoolean(
|
||||||
process.env.UNLEASH_EXPERIMENTAL_NETWORK_VIEW,
|
process.env.UNLEASH_EXPERIMENTAL_MAINTENANCE,
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
},
|
};
|
||||||
|
|
||||||
|
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||||
|
flags,
|
||||||
externalResolver: { isEnabled: (): boolean => false },
|
externalResolver: { isEnabled: (): boolean => false },
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IExperimentalOptions {
|
export interface IExperimentalOptions {
|
||||||
flags: {
|
flags: IFlags;
|
||||||
[key: string]: boolean;
|
|
||||||
ENABLE_DARK_MODE_SUPPORT?: boolean;
|
|
||||||
embedProxy?: boolean;
|
|
||||||
embedProxyFrontend?: boolean;
|
|
||||||
batchMetrics?: boolean;
|
|
||||||
anonymiseEventLog?: boolean;
|
|
||||||
changeRequests?: boolean;
|
|
||||||
proxyReturnAllToggles?: boolean;
|
|
||||||
variantsPerEnvironment?: boolean;
|
|
||||||
favorites?: boolean;
|
|
||||||
networkView?: boolean;
|
|
||||||
maintenance?: boolean;
|
|
||||||
};
|
|
||||||
externalResolver: IExternalFlagResolver;
|
externalResolver: IExternalFlagResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,9 +65,9 @@ export interface IFlagContext {
|
|||||||
|
|
||||||
export interface IFlagResolver {
|
export interface IFlagResolver {
|
||||||
getAll: (context?: IFlagContext) => IFlags;
|
getAll: (context?: IFlagContext) => IFlags;
|
||||||
isEnabled: (expName: string, context?: IFlagContext) => boolean;
|
isEnabled: (expName: IFlagKey, context?: IFlagContext) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExternalFlagResolver {
|
export interface IExternalFlagResolver {
|
||||||
isEnabled: (flagName: string, context?: IFlagContext) => boolean;
|
isEnabled: (flagName: IFlagKey, context?: IFlagContext) => boolean;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { defaultExperimentalOptions } from '../types/experimental';
|
import { defaultExperimentalOptions, IFlagKey } from '../types/experimental';
|
||||||
import FlagResolver from './flag-resolver';
|
import FlagResolver from './flag-resolver';
|
||||||
|
import { IExperimentalOptions } from '../types/experimental';
|
||||||
|
|
||||||
test('should produce empty exposed flags', () => {
|
test('should produce empty exposed flags', () => {
|
||||||
const resolver = new FlagResolver(defaultExperimentalOptions);
|
const resolver = new FlagResolver(defaultExperimentalOptions);
|
||||||
@ -14,8 +15,9 @@ test('should produce UI flags with extra dynamic flags', () => {
|
|||||||
...defaultExperimentalOptions,
|
...defaultExperimentalOptions,
|
||||||
flags: { extraFlag: false },
|
flags: { extraFlag: false },
|
||||||
};
|
};
|
||||||
const resolver = new FlagResolver(config);
|
|
||||||
const result = resolver.getAll();
|
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||||
|
const result = resolver.getAll() as typeof config.flags;
|
||||||
|
|
||||||
expect(result.extraFlag).toBe(false);
|
expect(result.extraFlag).toBe(false);
|
||||||
});
|
});
|
||||||
@ -29,14 +31,14 @@ test('should use external resolver for dynamic flags', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolver = new FlagResolver({
|
const config = {
|
||||||
flags: {
|
flags: { extraFlag: false },
|
||||||
extraFlag: false,
|
|
||||||
},
|
|
||||||
externalResolver,
|
externalResolver,
|
||||||
});
|
};
|
||||||
|
|
||||||
const result = resolver.getAll();
|
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||||
|
|
||||||
|
const result = resolver.getAll() as typeof config.flags;
|
||||||
|
|
||||||
expect(result.extraFlag).toBe(true);
|
expect(result.extraFlag).toBe(true);
|
||||||
});
|
});
|
||||||
@ -48,15 +50,14 @@ test('should not use external resolver for enabled experiments', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolver = new FlagResolver({
|
const config = {
|
||||||
flags: {
|
flags: { should_be_enabled: true, extraFlag: false },
|
||||||
should_be_enabled: true,
|
|
||||||
extraFlag: false,
|
|
||||||
},
|
|
||||||
externalResolver,
|
externalResolver,
|
||||||
});
|
};
|
||||||
|
|
||||||
const result = resolver.getAll();
|
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||||
|
|
||||||
|
const result = resolver.getAll() as typeof config.flags;
|
||||||
|
|
||||||
expect(result.should_be_enabled).toBe(true);
|
expect(result.should_be_enabled).toBe(true);
|
||||||
});
|
});
|
||||||
@ -67,16 +68,16 @@ test('should load experimental flags', () => {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const resolver = new FlagResolver({
|
|
||||||
flags: {
|
|
||||||
extraFlag: false,
|
|
||||||
someFlag: true,
|
|
||||||
},
|
|
||||||
externalResolver,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(resolver.isEnabled('someFlag')).toBe(true);
|
const config = {
|
||||||
expect(resolver.isEnabled('extraFlag')).toBe(false);
|
flags: { extraFlag: false, someFlag: true },
|
||||||
|
externalResolver,
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||||
|
|
||||||
|
expect(resolver.isEnabled('someFlag' as IFlagKey)).toBe(true);
|
||||||
|
expect(resolver.isEnabled('extraFlag' as IFlagKey)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should load experimental flags from external provider', () => {
|
test('should load experimental flags from external provider', () => {
|
||||||
@ -88,14 +89,13 @@ test('should load experimental flags from external provider', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolver = new FlagResolver({
|
const config = {
|
||||||
flags: {
|
flags: { extraFlag: false, someFlag: true },
|
||||||
extraFlag: false,
|
|
||||||
someFlag: true,
|
|
||||||
},
|
|
||||||
externalResolver,
|
externalResolver,
|
||||||
});
|
};
|
||||||
|
|
||||||
expect(resolver.isEnabled('someFlag')).toBe(true);
|
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||||
expect(resolver.isEnabled('extraFlag')).toBe(true);
|
|
||||||
|
expect(resolver.isEnabled('someFlag' as IFlagKey)).toBe(true);
|
||||||
|
expect(resolver.isEnabled('extraFlag' as IFlagKey)).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
IFlagContext,
|
IFlagContext,
|
||||||
IFlags,
|
IFlags,
|
||||||
IFlagResolver,
|
IFlagResolver,
|
||||||
|
IFlagKey,
|
||||||
} from '../types/experimental';
|
} from '../types/experimental';
|
||||||
export default class FlagResolver implements IFlagResolver {
|
export default class FlagResolver implements IFlagResolver {
|
||||||
private experiments: IFlags;
|
private experiments: IFlags;
|
||||||
@ -18,7 +19,7 @@ export default class FlagResolver implements IFlagResolver {
|
|||||||
getAll(context?: IFlagContext): IFlags {
|
getAll(context?: IFlagContext): IFlags {
|
||||||
const flags: IFlags = { ...this.experiments };
|
const flags: IFlags = { ...this.experiments };
|
||||||
|
|
||||||
Object.keys(flags).forEach((flagName) => {
|
Object.keys(flags).forEach((flagName: IFlagKey) => {
|
||||||
if (!this.experiments[flagName])
|
if (!this.experiments[flagName])
|
||||||
flags[flagName] = this.externalResolver.isEnabled(
|
flags[flagName] = this.externalResolver.isEnabled(
|
||||||
flagName,
|
flagName,
|
||||||
@ -29,7 +30,7 @@ export default class FlagResolver implements IFlagResolver {
|
|||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled(expName: string, context?: IFlagContext): boolean {
|
isEnabled(expName: IFlagKey, context?: IFlagContext): boolean {
|
||||||
if (this.experiments[expName]) {
|
if (this.experiments[expName]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user