mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: message banner (variants) (#3788)
-
https://linear.app/unleash/issue/2-546/fetch-the-payload-from-a-real-feature-flag
-
https://linear.app/unleash/issue/2-547/adapt-ui-to-use-the-feature-flag-information-were-fetching
Tackles the 2 tasks above.
Adapts our `FlagResolver` logic to support variants, so we can use them
for our message banner project but also anything else in the future.
Also adapts MessageBanner to the new logic.
- Add support for variants in `FlagResolver`;
- Adapt `MessageBanner` to a variants flag;
- Adds `sticky` support for the `MessageBanner`;
- Adds our first variants flag to `uiConfig` and `experimental`:
`messageBanner`;
- Adds a `variant-flag-schema` to make it easy to represent the variant
output that we specify in `uiConfig`;
- Adapts `experimental` to be able to represent default variants while
still maintaining type safety;
- Adds helpers to make it easy to use variants in our project, such as
`getVariantValue` and the `useVariant` hook;
- Adapts and adds new tests in `flag-resolver.test.ts`;
### Notes
- ~~The `as PayloadType` assertions need
https://github.com/Unleash/unleash-client-node/pull/454 since it
includes https://github.com/Unleash/unleash-client-node/pull/452~~
(50ccf60893
);
- ~~Enterprise needs a PR that will follow soon~~;
![image](https://github.com/Unleash/unleash/assets/14320932/034ff64f-3020-4ed0-863b-ed1fd9190430)
This commit is contained in:
parent
2487b990bd
commit
db61a8a40c
@ -6,23 +6,30 @@ import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { MessageBannerDialog } from './MessageBannerDialog/MessageBannerDialog';
|
||||
import { useState } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import { useVariant } from 'hooks/useVariant';
|
||||
|
||||
const StyledBar = styled('aside', {
|
||||
shouldForwardProp: prop => prop !== 'variant',
|
||||
})<{ variant: BannerVariant }>(({ theme, variant }) => ({
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(1),
|
||||
gap: theme.spacing(1),
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette[variant].border,
|
||||
background: theme.palette[variant].light,
|
||||
color: theme.palette[variant].dark,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
}));
|
||||
shouldForwardProp: prop => prop !== 'variant' && prop !== 'sticky',
|
||||
})<{ variant: BannerVariant; sticky?: boolean }>(
|
||||
({ theme, variant, sticky }) => ({
|
||||
position: sticky ? 'sticky' : 'relative',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(1),
|
||||
gap: theme.spacing(1),
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette[variant].border,
|
||||
background: theme.palette[variant].light,
|
||||
color: theme.palette[variant].dark,
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
...(sticky && {
|
||||
top: 0,
|
||||
zIndex: theme.zIndex.sticky,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const StyledIcon = styled('div', {
|
||||
shouldForwardProp: prop => prop !== 'variant',
|
||||
@ -44,6 +51,7 @@ interface IMessageFlag {
|
||||
enabled: boolean;
|
||||
message: string;
|
||||
variant?: BannerVariant;
|
||||
sticky?: boolean;
|
||||
icon?: string;
|
||||
link?: string;
|
||||
linkText?: string;
|
||||
@ -52,61 +60,30 @@ interface IMessageFlag {
|
||||
dialog?: string;
|
||||
}
|
||||
|
||||
// TODO: Grab a real feature flag instead
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const mockFlag: IMessageFlag = {
|
||||
enabled: true,
|
||||
message:
|
||||
'**Heads up!** It seems like one of your client instances might be misbehaving.',
|
||||
variant: 'warning',
|
||||
link: '/admin/network',
|
||||
linkText: 'View Network',
|
||||
plausibleEvent: 'network_warning',
|
||||
};
|
||||
|
||||
const mockFlag2: IMessageFlag = {
|
||||
enabled: true,
|
||||
message:
|
||||
'**Unleash v5 is finally here!** Check out what changed in the newest major release.',
|
||||
variant: 'secondary',
|
||||
link: 'dialog',
|
||||
linkText: "What's new?",
|
||||
plausibleEvent: 'change_log_v5',
|
||||
dialog: `![Unleash v5](https://www.getunleash.io/logos/unleash_pos.svg)
|
||||
## Unleash v5 🎉
|
||||
**Unleash v5 is finally here!**
|
||||
|
||||
Check out what changed in the newest major release:
|
||||
|
||||
- An Amazing Feature
|
||||
- Another Amazing Feature
|
||||
- We'll save the best for last
|
||||
- And the best is...
|
||||
- **Unleash v5 is finally here!**
|
||||
|
||||
You can read more about it on our newest [blog post](https://www.getunleash.io/blog).`,
|
||||
};
|
||||
|
||||
export const MessageBanner = () => {
|
||||
const { uiConfig } = useUiConfig();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const messageBanner = useVariant<IMessageFlag>(
|
||||
uiConfig.flags.messageBanner
|
||||
);
|
||||
|
||||
if (!messageBanner) return null;
|
||||
|
||||
const {
|
||||
enabled,
|
||||
message,
|
||||
variant = 'neutral',
|
||||
sticky,
|
||||
icon,
|
||||
link,
|
||||
linkText = 'More info',
|
||||
plausibleEvent,
|
||||
dialogTitle,
|
||||
dialog,
|
||||
} = { ...mockFlag2, enabled: uiConfig.flags.messageBanner };
|
||||
|
||||
if (!enabled) return null;
|
||||
} = messageBanner;
|
||||
|
||||
return (
|
||||
<StyledBar variant={variant}>
|
||||
<StyledBar variant={variant} sticky={sticky}>
|
||||
<StyledIcon variant={variant}>
|
||||
<BannerIcon icon={icon} variant={variant} />
|
||||
</StyledIcon>
|
||||
|
10
frontend/src/hooks/useVariant.ts
Normal file
10
frontend/src/hooks/useVariant.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Variant, getVariantValue } from 'utils/variants';
|
||||
|
||||
export const useVariant = <T = string>(variant?: Variant) => {
|
||||
return useMemo(() => {
|
||||
if (variant?.enabled) {
|
||||
return getVariantValue<T>(variant);
|
||||
}
|
||||
}, [variant]);
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Variant } from 'utils/variants';
|
||||
|
||||
export interface IUiConfig {
|
||||
authenticationType?: string;
|
||||
@ -38,7 +39,7 @@ export interface IFlags {
|
||||
UG?: boolean;
|
||||
embedProxyFrontend?: boolean;
|
||||
maintenanceMode?: boolean;
|
||||
messageBanner?: boolean;
|
||||
messageBanner?: Variant;
|
||||
featuresExportImport?: boolean;
|
||||
caseInsensitiveInOperators?: boolean;
|
||||
proPlanAutoCharge?: boolean;
|
||||
|
28
frontend/src/utils/variants.ts
Normal file
28
frontend/src/utils/variants.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export enum PayloadType {
|
||||
STRING = 'string',
|
||||
JSON = 'json',
|
||||
CSV = 'csv',
|
||||
}
|
||||
|
||||
export interface Payload {
|
||||
type: PayloadType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Variant {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
payload?: Payload;
|
||||
}
|
||||
|
||||
export const getVariantValue = <T = string>(
|
||||
variant: Variant | undefined
|
||||
): T | undefined => {
|
||||
if (variant?.payload !== undefined) {
|
||||
if (variant.payload.type === PayloadType.JSON) {
|
||||
return JSON.parse(variant.payload.value) as T;
|
||||
}
|
||||
|
||||
return variant.payload.value as T;
|
||||
}
|
||||
};
|
@ -146,7 +146,7 @@
|
||||
"stoppable": "^1.1.0",
|
||||
"ts-toolbelt": "^9.6.0",
|
||||
"type-is": "^1.6.18",
|
||||
"unleash-client": "3.18.1",
|
||||
"unleash-client": "3.20.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -63,6 +63,7 @@ exports[`should create default config 1`] = `
|
||||
},
|
||||
"experimental": {
|
||||
"externalResolver": {
|
||||
"getVariant": [Function],
|
||||
"isEnabled": [Function],
|
||||
},
|
||||
"flags": {
|
||||
@ -76,7 +77,14 @@ exports[`should create default config 1`] = `
|
||||
"googleAuthEnabled": false,
|
||||
"groupRootRoles": false,
|
||||
"maintenanceMode": false,
|
||||
"messageBanner": false,
|
||||
"messageBanner": {
|
||||
"enabled": false,
|
||||
"name": "message-banner",
|
||||
"payload": {
|
||||
"type": "json",
|
||||
"value": "",
|
||||
},
|
||||
},
|
||||
"migrationLock": false,
|
||||
"personalAccessTokensKillSwitch": false,
|
||||
"proPlanAutoCharge": false,
|
||||
@ -98,7 +106,14 @@ exports[`should create default config 1`] = `
|
||||
"googleAuthEnabled": false,
|
||||
"groupRootRoles": false,
|
||||
"maintenanceMode": false,
|
||||
"messageBanner": false,
|
||||
"messageBanner": {
|
||||
"enabled": false,
|
||||
"name": "message-banner",
|
||||
"payload": {
|
||||
"type": "json",
|
||||
"value": "",
|
||||
},
|
||||
},
|
||||
"migrationLock": false,
|
||||
"personalAccessTokensKillSwitch": false,
|
||||
"proPlanAutoCharge": false,
|
||||
@ -108,6 +123,7 @@ exports[`should create default config 1`] = `
|
||||
"variantMetrics": false,
|
||||
},
|
||||
"externalResolver": {
|
||||
"getVariant": [Function],
|
||||
"isEnabled": [Function],
|
||||
},
|
||||
},
|
||||
|
@ -134,6 +134,7 @@ import {
|
||||
validatePasswordSchema,
|
||||
validateTagTypeSchema,
|
||||
variantSchema,
|
||||
variantFlagSchema,
|
||||
variantsSchema,
|
||||
versionSchema,
|
||||
} from './spec';
|
||||
@ -319,6 +320,7 @@ export const schemas: UnleashSchemas = {
|
||||
validatePasswordSchema,
|
||||
validateTagTypeSchema,
|
||||
variantSchema,
|
||||
variantFlagSchema,
|
||||
variantsSchema,
|
||||
versionSchema,
|
||||
projectOverviewSchema,
|
||||
|
@ -168,6 +168,7 @@ const metaRules: Rule[] = [
|
||||
'validatePasswordSchema',
|
||||
'validateTagTypeSchema',
|
||||
'variantSchema',
|
||||
'variantFlagSchema',
|
||||
'versionSchema',
|
||||
'projectOverviewSchema',
|
||||
'importTogglesSchema',
|
||||
@ -277,6 +278,7 @@ const metaRules: Rule[] = [
|
||||
'validatePasswordSchema',
|
||||
'validateTagTypeSchema',
|
||||
'variantSchema',
|
||||
'variantFlagSchema',
|
||||
'variantsSchema',
|
||||
'versionSchema',
|
||||
'importTogglesSchema',
|
||||
|
@ -27,6 +27,7 @@ export * from './profile-schema';
|
||||
export * from './project-schema';
|
||||
export * from './segment-schema';
|
||||
export * from './variant-schema';
|
||||
export * from './variant-flag-schema';
|
||||
export * from './version-schema';
|
||||
export * from './features-schema';
|
||||
export * from './feedback-schema';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
import { PayloadType } from 'unleash-client';
|
||||
|
||||
export const proxyFeatureSchema = {
|
||||
$id: '#/components/schemas/proxyFeatureSchema',
|
||||
@ -31,7 +32,10 @@ export const proxyFeatureSchema = {
|
||||
additionalProperties: false,
|
||||
required: ['type', 'value'],
|
||||
properties: {
|
||||
type: { type: 'string', enum: ['string'] },
|
||||
type: {
|
||||
type: 'string',
|
||||
enum: Object.values(PayloadType),
|
||||
},
|
||||
value: { type: 'string' },
|
||||
},
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
import { versionSchema } from './version-schema';
|
||||
import { variantFlagSchema } from './variant-flag-schema';
|
||||
|
||||
export const uiConfigSchema = {
|
||||
$id: '#/components/schemas/uiConfigSchema',
|
||||
@ -52,7 +53,14 @@ export const uiConfigSchema = {
|
||||
flags: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'boolean',
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
$ref: '#/components/schemas/variantFlagSchema',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
links: {
|
||||
@ -79,6 +87,7 @@ export const uiConfigSchema = {
|
||||
components: {
|
||||
schemas: {
|
||||
versionSchema,
|
||||
variantFlagSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
30
src/lib/openapi/spec/variant-flag-schema.ts
Normal file
30
src/lib/openapi/spec/variant-flag-schema.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { FromSchema } from 'json-schema-to-ts';
|
||||
|
||||
export const variantFlagSchema = {
|
||||
$id: '#/components/schemas/variantFlagSchema',
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
payload: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {},
|
||||
} as const;
|
||||
|
||||
export type VariantFlagSchema = FromSchema<typeof variantFlagSchema>;
|
@ -1,9 +1,29 @@
|
||||
import { Variant, PayloadType } from 'unleash-client';
|
||||
import { parseEnvVarBoolean } from '../util';
|
||||
|
||||
export type IFlags = Partial<typeof flags>;
|
||||
export type IFlagKey = keyof IFlags;
|
||||
export type IFlagKey =
|
||||
| 'anonymiseEventLog'
|
||||
| 'embedProxy'
|
||||
| 'embedProxyFrontend'
|
||||
| 'responseTimeWithAppNameKillSwitch'
|
||||
| 'maintenanceMode'
|
||||
| 'messageBanner'
|
||||
| 'featuresExportImport'
|
||||
| 'caseInsensitiveInOperators'
|
||||
| 'strictSchemaValidation'
|
||||
| 'proPlanAutoCharge'
|
||||
| 'personalAccessTokensKillSwitch'
|
||||
| 'cleanClientApi'
|
||||
| 'groupRootRoles'
|
||||
| 'migrationLock'
|
||||
| 'demo'
|
||||
| 'strategyImprovements'
|
||||
| 'googleAuthEnabled'
|
||||
| 'variantMetrics';
|
||||
|
||||
const flags = {
|
||||
export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>;
|
||||
|
||||
const flags: IFlags = {
|
||||
anonymiseEventLog: false,
|
||||
embedProxy: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_EMBED_PROXY,
|
||||
@ -21,10 +41,18 @@ const flags = {
|
||||
process.env.UNLEASH_EXPERIMENTAL_MAINTENANCE_MODE,
|
||||
false,
|
||||
),
|
||||
messageBanner: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_MESSAGE_BANNER,
|
||||
false,
|
||||
),
|
||||
messageBanner: {
|
||||
name: 'message-banner',
|
||||
enabled: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_MESSAGE_BANNER,
|
||||
false,
|
||||
),
|
||||
payload: {
|
||||
type: PayloadType.JSON,
|
||||
value:
|
||||
process.env.UNLEASH_EXPERIMENTAL_MESSAGE_BANNER_PAYLOAD ?? '',
|
||||
},
|
||||
},
|
||||
featuresExportImport: parseEnvVarBoolean(
|
||||
process.env.UNLEASH_EXPERIMENTAL_FEATURES_EXPORT_IMPORT,
|
||||
true,
|
||||
@ -68,7 +96,10 @@ const flags = {
|
||||
|
||||
export const defaultExperimentalOptions: IExperimentalOptions = {
|
||||
flags,
|
||||
externalResolver: { isEnabled: (): boolean => false },
|
||||
externalResolver: {
|
||||
isEnabled: (): boolean => false,
|
||||
getVariant: () => undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export interface IExperimentalOptions {
|
||||
@ -83,8 +114,16 @@ export interface IFlagContext {
|
||||
export interface IFlagResolver {
|
||||
getAll: (context?: IFlagContext) => IFlags;
|
||||
isEnabled: (expName: IFlagKey, context?: IFlagContext) => boolean;
|
||||
getVariant: (
|
||||
expName: IFlagKey,
|
||||
context?: IFlagContext,
|
||||
) => Variant | undefined;
|
||||
}
|
||||
|
||||
export interface IExternalFlagResolver {
|
||||
isEnabled: (flagName: IFlagKey, context?: IFlagContext) => boolean;
|
||||
getVariant: (
|
||||
flagName: IFlagKey,
|
||||
context?: IFlagContext,
|
||||
) => Variant | undefined;
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ import { Context } from './context';
|
||||
import { FeatureInterface } from './feature';
|
||||
import normalizedValue from './strategy/util';
|
||||
import { resolveContextValue } from './helpers';
|
||||
|
||||
enum PayloadType {
|
||||
STRING = 'string',
|
||||
}
|
||||
import { PayloadType } from 'unleash-client';
|
||||
|
||||
interface Override {
|
||||
contextName: string;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { PayloadType } from 'unleash-client';
|
||||
import { defaultExperimentalOptions, IFlagKey } from '../types/experimental';
|
||||
import FlagResolver from './flag-resolver';
|
||||
import FlagResolver, { getVariantValue } from './flag-resolver';
|
||||
import { IExperimentalOptions } from '../types/experimental';
|
||||
|
||||
test('should produce empty exposed flags', () => {
|
||||
@ -29,6 +30,7 @@ test('should use external resolver for dynamic flags', () => {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
getVariant: () => undefined,
|
||||
};
|
||||
|
||||
const config = {
|
||||
@ -48,6 +50,7 @@ test('should not use external resolver for enabled experiments', () => {
|
||||
isEnabled: () => {
|
||||
return false;
|
||||
},
|
||||
getVariant: () => undefined,
|
||||
};
|
||||
|
||||
const config = {
|
||||
@ -67,6 +70,7 @@ test('should load experimental flags', () => {
|
||||
isEnabled: () => {
|
||||
return false;
|
||||
},
|
||||
getVariant: () => undefined,
|
||||
};
|
||||
|
||||
const config = {
|
||||
@ -87,6 +91,7 @@ test('should load experimental flags from external provider', () => {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
getVariant: () => undefined,
|
||||
};
|
||||
|
||||
const config = {
|
||||
@ -99,3 +104,60 @@ test('should load experimental flags from external provider', () => {
|
||||
expect(resolver.isEnabled('someFlag' as IFlagKey)).toBe(true);
|
||||
expect(resolver.isEnabled('extraFlag' as IFlagKey)).toBe(true);
|
||||
});
|
||||
|
||||
test('should support variant flags', () => {
|
||||
const variant = {
|
||||
enabled: true,
|
||||
name: 'variant',
|
||||
payload: {
|
||||
type: PayloadType.STRING,
|
||||
value: 'variant-A',
|
||||
},
|
||||
};
|
||||
|
||||
const externalResolver = {
|
||||
isEnabled: () => true,
|
||||
getVariant: (name: string) => {
|
||||
if (name === 'extraFlag') {
|
||||
return variant;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const config = {
|
||||
flags: { extraFlag: undefined, someFlag: true, otherflag: false },
|
||||
externalResolver,
|
||||
};
|
||||
|
||||
const resolver = new FlagResolver(config as IExperimentalOptions);
|
||||
|
||||
expect(resolver.getVariant('someFlag' as IFlagKey)).toBe(undefined);
|
||||
expect(resolver.getVariant('otherFlag' as IFlagKey)).toBe(undefined);
|
||||
expect(resolver.getVariant('extraFlag' as IFlagKey)).toStrictEqual(variant);
|
||||
});
|
||||
|
||||
test('should expose an helper to get variant value', () => {
|
||||
expect(
|
||||
getVariantValue({
|
||||
enabled: true,
|
||||
name: 'variant',
|
||||
payload: {
|
||||
type: PayloadType.STRING,
|
||||
value: 'variant-A',
|
||||
},
|
||||
}),
|
||||
).toBe('variant-A');
|
||||
|
||||
expect(
|
||||
getVariantValue({
|
||||
enabled: true,
|
||||
name: 'variant',
|
||||
payload: {
|
||||
type: PayloadType.JSON,
|
||||
value: `{"foo": "bar"}`,
|
||||
},
|
||||
}),
|
||||
).toStrictEqual({
|
||||
foo: 'bar',
|
||||
});
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Variant, PayloadType } from 'unleash-client';
|
||||
import {
|
||||
IExperimentalOptions,
|
||||
IExternalFlagResolver,
|
||||
@ -6,6 +7,7 @@ import {
|
||||
IFlagResolver,
|
||||
IFlagKey,
|
||||
} from '../types/experimental';
|
||||
|
||||
export default class FlagResolver implements IFlagResolver {
|
||||
private experiments: IFlags;
|
||||
|
||||
@ -20,20 +22,51 @@ export default class FlagResolver implements IFlagResolver {
|
||||
const flags: IFlags = { ...this.experiments };
|
||||
|
||||
Object.keys(flags).forEach((flagName: IFlagKey) => {
|
||||
if (!this.experiments[flagName])
|
||||
flags[flagName] = this.externalResolver.isEnabled(
|
||||
flagName,
|
||||
context,
|
||||
);
|
||||
if (!this.experiments[flagName]) {
|
||||
if (typeof flags[flagName] === 'boolean') {
|
||||
flags[flagName] = this.externalResolver.isEnabled(
|
||||
flagName,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
flags[flagName] = this.externalResolver.getVariant(
|
||||
flagName,
|
||||
context,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
isEnabled(expName: IFlagKey, context?: IFlagContext): boolean {
|
||||
if (this.experiments[expName]) {
|
||||
return true;
|
||||
const exp = this.experiments[expName];
|
||||
if (exp) {
|
||||
if (typeof exp === 'boolean') return exp;
|
||||
else return exp.enabled;
|
||||
}
|
||||
return this.externalResolver.isEnabled(expName, context);
|
||||
}
|
||||
|
||||
getVariant(expName: IFlagKey, context?: IFlagContext): Variant | undefined {
|
||||
const exp = this.experiments[expName];
|
||||
if (exp) {
|
||||
if (typeof exp === 'boolean') return undefined;
|
||||
else return exp;
|
||||
}
|
||||
return this.externalResolver.getVariant(expName, context);
|
||||
}
|
||||
}
|
||||
|
||||
export const getVariantValue = <T = string>(
|
||||
variant: Variant | undefined,
|
||||
): T | undefined => {
|
||||
if (variant?.payload !== undefined) {
|
||||
if (variant.payload.type === PayloadType.JSON) {
|
||||
return JSON.parse(variant.payload.value) as T;
|
||||
}
|
||||
|
||||
return variant.payload.value as T;
|
||||
}
|
||||
};
|
||||
|
@ -6,10 +6,7 @@ import { ISegment } from 'lib/types/model';
|
||||
import { serializeDates } from '../../lib/types/serialize-dates';
|
||||
import { Operator } from './feature-evaluator/constraint';
|
||||
import { FeatureInterface } from 'unleash-client/lib/feature';
|
||||
|
||||
enum PayloadType {
|
||||
STRING = 'string',
|
||||
}
|
||||
import { PayloadType } from 'unleash-client';
|
||||
|
||||
type NonEmptyList<T> = [T, ...T[]];
|
||||
|
||||
@ -24,7 +21,7 @@ export const mapFeaturesForClient = (
|
||||
...variant,
|
||||
payload: variant.payload && {
|
||||
...variant.payload,
|
||||
type: variant.payload.type as unknown as PayloadType,
|
||||
type: variant.payload.type as PayloadType,
|
||||
},
|
||||
})),
|
||||
project: feature.project,
|
||||
|
@ -2,6 +2,7 @@ import { start } from './lib/server-impl';
|
||||
import { createConfig } from './lib/create-config';
|
||||
import { LogLevel } from './lib/logger';
|
||||
import { ApiTokenType } from './lib/types/models/api-token';
|
||||
import { PayloadType } from 'unleash-client';
|
||||
|
||||
process.nextTick(async () => {
|
||||
try {
|
||||
@ -40,6 +41,23 @@ process.nextTick(async () => {
|
||||
responseTimeWithAppNameKillSwitch: false,
|
||||
variantMetrics: 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": "![Message Banner](https://www.getunleash.io/logos/unleash_pos.svg)\\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: {
|
||||
|
@ -3947,6 +3947,8 @@ Stats are divided into current and previous **windows**.
|
||||
"type": {
|
||||
"enum": [
|
||||
"string",
|
||||
"json",
|
||||
"csv",
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
@ -4823,7 +4825,14 @@ Stats are divided into current and previous **windows**.
|
||||
},
|
||||
"flags": {
|
||||
"additionalProperties": {
|
||||
"type": "boolean",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean",
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/variantFlagSchema",
|
||||
},
|
||||
],
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
@ -5269,6 +5278,30 @@ Stats are divided into current and previous **windows**.
|
||||
],
|
||||
"type": "object",
|
||||
},
|
||||
"variantFlagSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
},
|
||||
"payload": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
},
|
||||
"type": "object",
|
||||
},
|
||||
"variantSchema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
|
@ -7210,10 +7210,10 @@ universalify@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||
|
||||
unleash-client@3.18.1:
|
||||
version "3.18.1"
|
||||
resolved "https://registry.yarnpkg.com/unleash-client/-/unleash-client-3.18.1.tgz#d9e928f3cf0c11dafce27bae298b183b28615b4d"
|
||||
integrity sha512-fWVxeas4XzXkPPkTxLr2MKVvN4DUkYDVOKDG9zlnqQnmWvZQjLnRqOCOvf/vFkd4qJj+4fSWIYKTrMYQIpNUKw==
|
||||
unleash-client@3.20.0:
|
||||
version "3.20.0"
|
||||
resolved "https://registry.yarnpkg.com/unleash-client/-/unleash-client-3.20.0.tgz#1a6deb0e803eed0d0cefbed1bac17f3c3d3b0143"
|
||||
integrity sha512-CXseZTHH+lfT3qZY7nufpPKbnNcWvdt61Pgc313spFnQBV63r24fhMmwvQcltc+pp2z/14p2mM6iq11R2PYw3g==
|
||||
dependencies:
|
||||
ip "^1.1.8"
|
||||
make-fetch-happen "^10.2.1"
|
||||
|
Loading…
Reference in New Issue
Block a user