mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-22 01:16:07 +02:00
fix: properly handle flag resolver variants (#3808)
Variants were not being properly handled in the `flag-resolver`: The
fact that the default value of the variant is not falsy made it so we
never asked the external flag resolver for the value.
This also moves the logic from `Variant | undefined` to `Variant` where
we use the `getDefaultVariant()` helper method to return us a [default
variant](55274e4953/src/variant.ts (L37-L42)
).
This commit is contained in:
parent
149c54b0b3
commit
4adc977ba0
@ -18,7 +18,8 @@ export interface Variant {
|
|||||||
export const getVariantValue = <T = string>(
|
export const getVariantValue = <T = string>(
|
||||||
variant: Variant | undefined
|
variant: Variant | undefined
|
||||||
): T | undefined => {
|
): T | undefined => {
|
||||||
if (variant?.payload !== undefined) {
|
if (variant?.enabled) {
|
||||||
|
if (!variant.payload) return variant.name as T;
|
||||||
if (variant.payload.type === PayloadType.JSON) {
|
if (variant.payload.type === PayloadType.JSON) {
|
||||||
return JSON.parse(variant.payload.value) as T;
|
return JSON.parse(variant.payload.value) as T;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Variant, PayloadType } from 'unleash-client';
|
import { Variant, PayloadType } from 'unleash-client';
|
||||||
import { parseEnvVarBoolean } from '../util';
|
import { parseEnvVarBoolean } from '../util';
|
||||||
|
import { getDefaultVariant } from 'unleash-client/lib/variant';
|
||||||
|
|
||||||
export type IFlagKey =
|
export type IFlagKey =
|
||||||
| 'anonymiseEventLog'
|
| 'anonymiseEventLog'
|
||||||
@ -98,7 +99,7 @@ export const defaultExperimentalOptions: IExperimentalOptions = {
|
|||||||
flags,
|
flags,
|
||||||
externalResolver: {
|
externalResolver: {
|
||||||
isEnabled: (): boolean => false,
|
isEnabled: (): boolean => false,
|
||||||
getVariant: () => undefined,
|
getVariant: () => getDefaultVariant(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,16 +115,10 @@ export interface IFlagContext {
|
|||||||
export interface IFlagResolver {
|
export interface IFlagResolver {
|
||||||
getAll: (context?: IFlagContext) => IFlags;
|
getAll: (context?: IFlagContext) => IFlags;
|
||||||
isEnabled: (expName: IFlagKey, context?: IFlagContext) => boolean;
|
isEnabled: (expName: IFlagKey, context?: IFlagContext) => boolean;
|
||||||
getVariant: (
|
getVariant: (expName: IFlagKey, context?: IFlagContext) => Variant;
|
||||||
expName: IFlagKey,
|
|
||||||
context?: IFlagContext,
|
|
||||||
) => Variant | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExternalFlagResolver {
|
export interface IExternalFlagResolver {
|
||||||
isEnabled: (flagName: IFlagKey, context?: IFlagContext) => boolean;
|
isEnabled: (flagName: IFlagKey, context?: IFlagContext) => boolean;
|
||||||
getVariant: (
|
getVariant: (flagName: IFlagKey, context?: IFlagContext) => Variant;
|
||||||
flagName: IFlagKey,
|
|
||||||
context?: IFlagContext,
|
|
||||||
) => Variant | undefined;
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { PayloadType } from 'unleash-client';
|
|||||||
import { defaultExperimentalOptions, IFlagKey } from '../types/experimental';
|
import { defaultExperimentalOptions, IFlagKey } from '../types/experimental';
|
||||||
import FlagResolver, { getVariantValue } from './flag-resolver';
|
import FlagResolver, { getVariantValue } from './flag-resolver';
|
||||||
import { IExperimentalOptions } from '../types/experimental';
|
import { IExperimentalOptions } from '../types/experimental';
|
||||||
|
import { getDefaultVariant } from 'unleash-client/lib/variant';
|
||||||
|
|
||||||
test('should produce empty exposed flags', () => {
|
test('should produce empty exposed flags', () => {
|
||||||
const resolver = new FlagResolver(defaultExperimentalOptions);
|
const resolver = new FlagResolver(defaultExperimentalOptions);
|
||||||
@ -30,7 +31,7 @@ test('should use external resolver for dynamic flags', () => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getVariant: () => undefined,
|
getVariant: () => getDefaultVariant(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -50,7 +51,7 @@ test('should not use external resolver for enabled experiments', () => {
|
|||||||
isEnabled: () => {
|
isEnabled: () => {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
getVariant: () => undefined,
|
getVariant: () => getDefaultVariant(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -70,7 +71,7 @@ test('should load experimental flags', () => {
|
|||||||
isEnabled: () => {
|
isEnabled: () => {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
getVariant: () => undefined,
|
getVariant: () => getDefaultVariant(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -91,7 +92,7 @@ test('should load experimental flags from external provider', () => {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getVariant: () => undefined,
|
getVariant: () => getDefaultVariant(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
@ -121,6 +122,7 @@ test('should support variant flags', () => {
|
|||||||
if (name === 'extraFlag') {
|
if (name === 'extraFlag') {
|
||||||
return variant;
|
return variant;
|
||||||
}
|
}
|
||||||
|
return getDefaultVariant();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -131,22 +133,33 @@ test('should support variant flags', () => {
|
|||||||
|
|
||||||
const resolver = new FlagResolver(config as IExperimentalOptions);
|
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||||
|
|
||||||
expect(resolver.getVariant('someFlag' as IFlagKey)).toBe(undefined);
|
expect(resolver.getVariant('someFlag' as IFlagKey)).toStrictEqual(
|
||||||
expect(resolver.getVariant('otherFlag' as IFlagKey)).toBe(undefined);
|
getDefaultVariant(),
|
||||||
|
);
|
||||||
|
expect(resolver.getVariant('otherFlag' as IFlagKey)).toStrictEqual(
|
||||||
|
getDefaultVariant(),
|
||||||
|
);
|
||||||
expect(resolver.getVariant('extraFlag' as IFlagKey)).toStrictEqual(variant);
|
expect(resolver.getVariant('extraFlag' as IFlagKey)).toStrictEqual(variant);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should expose an helper to get variant value', () => {
|
test('should expose an helper to get variant value', () => {
|
||||||
|
expect(
|
||||||
|
getVariantValue({
|
||||||
|
enabled: true,
|
||||||
|
name: 'variant-A',
|
||||||
|
}),
|
||||||
|
).toBe('variant-A');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
getVariantValue({
|
getVariantValue({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
name: 'variant',
|
name: 'variant',
|
||||||
payload: {
|
payload: {
|
||||||
type: PayloadType.STRING,
|
type: PayloadType.STRING,
|
||||||
value: 'variant-A',
|
value: 'variant-B',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).toBe('variant-A');
|
).toBe('variant-B');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
getVariantValue({
|
getVariantValue({
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
IFlagResolver,
|
IFlagResolver,
|
||||||
IFlagKey,
|
IFlagKey,
|
||||||
} from '../types/experimental';
|
} from '../types/experimental';
|
||||||
|
import { getDefaultVariant } from 'unleash-client/lib/variant';
|
||||||
|
|
||||||
export default class FlagResolver implements IFlagResolver {
|
export default class FlagResolver implements IFlagResolver {
|
||||||
private experiments: IFlags;
|
private experiments: IFlags;
|
||||||
@ -22,13 +23,16 @@ export default class FlagResolver implements IFlagResolver {
|
|||||||
const flags: IFlags = { ...this.experiments };
|
const flags: IFlags = { ...this.experiments };
|
||||||
|
|
||||||
Object.keys(flags).forEach((flagName: IFlagKey) => {
|
Object.keys(flags).forEach((flagName: IFlagKey) => {
|
||||||
if (!this.experiments[flagName]) {
|
const flag = flags[flagName];
|
||||||
if (typeof flags[flagName] === 'boolean') {
|
if (typeof flag === 'boolean') {
|
||||||
|
if (!flag) {
|
||||||
flags[flagName] = this.externalResolver.isEnabled(
|
flags[flagName] = this.externalResolver.isEnabled(
|
||||||
flagName,
|
flagName,
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (!flag?.enabled) {
|
||||||
flags[flagName] = this.externalResolver.getVariant(
|
flags[flagName] = this.externalResolver.getVariant(
|
||||||
flagName,
|
flagName,
|
||||||
context,
|
context,
|
||||||
@ -49,10 +53,10 @@ export default class FlagResolver implements IFlagResolver {
|
|||||||
return this.externalResolver.isEnabled(expName, context);
|
return this.externalResolver.isEnabled(expName, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
getVariant(expName: IFlagKey, context?: IFlagContext): Variant | undefined {
|
getVariant(expName: IFlagKey, context?: IFlagContext): Variant {
|
||||||
const exp = this.experiments[expName];
|
const exp = this.experiments[expName];
|
||||||
if (exp) {
|
if (exp) {
|
||||||
if (typeof exp === 'boolean') return undefined;
|
if (typeof exp === 'boolean') return getDefaultVariant();
|
||||||
else return exp;
|
else return exp;
|
||||||
}
|
}
|
||||||
return this.externalResolver.getVariant(expName, context);
|
return this.externalResolver.getVariant(expName, context);
|
||||||
@ -62,7 +66,8 @@ export default class FlagResolver implements IFlagResolver {
|
|||||||
export const getVariantValue = <T = string>(
|
export const getVariantValue = <T = string>(
|
||||||
variant: Variant | undefined,
|
variant: Variant | undefined,
|
||||||
): T | undefined => {
|
): T | undefined => {
|
||||||
if (variant?.payload !== undefined) {
|
if (variant?.enabled) {
|
||||||
|
if (!variant.payload) return variant.name as T;
|
||||||
if (variant.payload.type === PayloadType.JSON) {
|
if (variant.payload.type === PayloadType.JSON) {
|
||||||
return JSON.parse(variant.payload.value) as T;
|
return JSON.parse(variant.payload.value) as T;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { start } from './lib/server-impl';
|
|||||||
import { createConfig } from './lib/create-config';
|
import { createConfig } from './lib/create-config';
|
||||||
import { LogLevel } from './lib/logger';
|
import { LogLevel } from './lib/logger';
|
||||||
import { ApiTokenType } from './lib/types/models/api-token';
|
import { ApiTokenType } from './lib/types/models/api-token';
|
||||||
import { PayloadType } from 'unleash-client';
|
|
||||||
|
|
||||||
process.nextTick(async () => {
|
process.nextTick(async () => {
|
||||||
try {
|
try {
|
||||||
@ -41,23 +40,6 @@ process.nextTick(async () => {
|
|||||||
responseTimeWithAppNameKillSwitch: false,
|
responseTimeWithAppNameKillSwitch: false,
|
||||||
variantMetrics: true,
|
variantMetrics: true,
|
||||||
strategyImprovements: true,
|
strategyImprovements: true,
|
||||||
messageBanner: {
|
|
||||||
name: 'message-banner',
|
|
||||||
enabled: true,
|
|
||||||
payload: {
|
|
||||||
type: PayloadType.JSON,
|
|
||||||
value: `{
|
|
||||||
"message": "**New message banner!** Check out this new feature.",
|
|
||||||
"variant": "secondary",
|
|
||||||
"sticky": true,
|
|
||||||
"link": "dialog",
|
|
||||||
"linkText": "What is this?",
|
|
||||||
"plausibleEvent": "message_banner",
|
|
||||||
"dialog": "\\n## Message Banner 🎉\\n**New message banner!**\\n\\nCheck out this new feature:\\n\\n- Get the latest announcements\\n- Get warnings about your Unleash instance\\n\\nYou can read more about it on our newest [blog post](https://www.getunleash.io/blog).",
|
|
||||||
"icon": "none"
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user