mirror of
https://github.com/Unleash/unleash.git
synced 2025-08-13 13:48:59 +02:00
feat: strategy variants in playground (#4281)
This commit is contained in:
parent
ce70f9f54e
commit
f1d1d7d49a
@ -162,7 +162,7 @@
|
|||||||
"stoppable": "^1.1.0",
|
"stoppable": "^1.1.0",
|
||||||
"ts-toolbelt": "^9.6.0",
|
"ts-toolbelt": "^9.6.0",
|
||||||
"type-is": "^1.6.18",
|
"type-is": "^1.6.18",
|
||||||
"unleash-client": "3.21.0",
|
"unleash-client": "4.1.0-beta.5",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -2,10 +2,10 @@ import { Strategy } from './strategy';
|
|||||||
import { FeatureInterface } from './feature';
|
import { FeatureInterface } from './feature';
|
||||||
import { RepositoryInterface } from './repository';
|
import { RepositoryInterface } from './repository';
|
||||||
import {
|
import {
|
||||||
Variant,
|
|
||||||
getDefaultVariant,
|
getDefaultVariant,
|
||||||
VariantDefinition,
|
|
||||||
selectVariant,
|
selectVariant,
|
||||||
|
Variant,
|
||||||
|
VariantDefinition,
|
||||||
} from './variant';
|
} from './variant';
|
||||||
import { Context } from './context';
|
import { Context } from './context';
|
||||||
import { SegmentForEvaluation } from './strategy/strategy';
|
import { SegmentForEvaluation } from './strategy/strategy';
|
||||||
@ -24,6 +24,8 @@ export type EvaluatedPlaygroundStrategy = Omit<
|
|||||||
|
|
||||||
export type FeatureStrategiesEvaluationResult = {
|
export type FeatureStrategiesEvaluationResult = {
|
||||||
result: boolean | typeof playgroundStrategyEvaluation.unknownResult;
|
result: boolean | typeof playgroundStrategyEvaluation.unknownResult;
|
||||||
|
variant?: Variant;
|
||||||
|
variants?: VariantDefinition[];
|
||||||
strategies: EvaluatedPlaygroundStrategy[];
|
strategies: EvaluatedPlaygroundStrategy[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -110,30 +112,45 @@ export default class UnleashClient {
|
|||||||
?.map(this.getSegment(this.repository))
|
?.map(this.getSegment(this.repository))
|
||||||
.filter(Boolean) ?? [];
|
.filter(Boolean) ?? [];
|
||||||
|
|
||||||
|
const evaluationResult = strategy.isEnabledWithConstraints(
|
||||||
|
strategySelector.parameters,
|
||||||
|
context,
|
||||||
|
strategySelector.constraints,
|
||||||
|
segments,
|
||||||
|
strategySelector.disabled,
|
||||||
|
strategySelector.variants,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: strategySelector.name,
|
name: strategySelector.name,
|
||||||
id: strategySelector.id,
|
id: strategySelector.id,
|
||||||
title: strategySelector.title,
|
title: strategySelector.title,
|
||||||
disabled: strategySelector.disabled || false,
|
disabled: strategySelector.disabled || false,
|
||||||
parameters: strategySelector.parameters,
|
parameters: strategySelector.parameters,
|
||||||
...strategy.isEnabledWithConstraints(
|
...evaluationResult,
|
||||||
strategySelector.parameters,
|
|
||||||
context,
|
|
||||||
strategySelector.constraints,
|
|
||||||
segments,
|
|
||||||
strategySelector.disabled,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Feature evaluation
|
// Feature evaluation
|
||||||
const overallStrategyResult = () => {
|
const overallStrategyResult = (): [
|
||||||
|
boolean | typeof playgroundStrategyEvaluation.unknownResult,
|
||||||
|
VariantDefinition[] | undefined,
|
||||||
|
Variant | undefined | null,
|
||||||
|
] => {
|
||||||
// if at least one strategy is enabled, then the feature is enabled
|
// if at least one strategy is enabled, then the feature is enabled
|
||||||
|
const enabledStrategy = strategies.find(
|
||||||
|
(strategy) => strategy.result.enabled === true,
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
strategies.some((strategy) => strategy.result.enabled === true)
|
enabledStrategy &&
|
||||||
|
enabledStrategy.result.evaluationStatus === 'complete'
|
||||||
) {
|
) {
|
||||||
return true;
|
return [
|
||||||
|
true,
|
||||||
|
enabledStrategy.result.variants,
|
||||||
|
enabledStrategy.result.variant,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// if at least one strategy is unknown, then the feature _may_ be enabled
|
// if at least one strategy is unknown, then the feature _may_ be enabled
|
||||||
@ -142,14 +159,21 @@ export default class UnleashClient {
|
|||||||
(strategy) => strategy.result.enabled === 'unknown',
|
(strategy) => strategy.result.enabled === 'unknown',
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return playgroundStrategyEvaluation.unknownResult;
|
return [
|
||||||
|
playgroundStrategyEvaluation.unknownResult,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return [false, undefined, undefined];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [result, variants, variant] = overallStrategyResult();
|
||||||
const evalResults: FeatureStrategiesEvaluationResult = {
|
const evalResults: FeatureStrategiesEvaluationResult = {
|
||||||
result: overallStrategyResult(),
|
result,
|
||||||
|
variant,
|
||||||
|
variants,
|
||||||
strategies,
|
strategies,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -197,8 +221,27 @@ export default class UnleashClient {
|
|||||||
): Variant {
|
): Variant {
|
||||||
const fallback = fallbackVariant || getDefaultVariant();
|
const fallback = fallbackVariant || getDefaultVariant();
|
||||||
const feature = this.repository.getToggle(name);
|
const feature = this.repository.getToggle(name);
|
||||||
|
|
||||||
|
if (typeof feature === 'undefined') {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
let enabled = true;
|
||||||
|
if (checkToggle) {
|
||||||
|
const result = this.isFeatureEnabled(feature, context, () =>
|
||||||
|
fallbackVariant ? fallbackVariant.enabled : false,
|
||||||
|
);
|
||||||
|
enabled = result.result === true;
|
||||||
|
const strategyVariant = result.variant;
|
||||||
|
if (enabled && strategyVariant) {
|
||||||
|
return strategyVariant;
|
||||||
|
}
|
||||||
|
if (!enabled) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof feature === 'undefined' ||
|
|
||||||
!feature.variants ||
|
!feature.variants ||
|
||||||
!Array.isArray(feature.variants) ||
|
!Array.isArray(feature.variants) ||
|
||||||
feature.variants.length === 0 ||
|
feature.variants.length === 0 ||
|
||||||
@ -207,17 +250,6 @@ export default class UnleashClient {
|
|||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
let enabled = true;
|
|
||||||
if (checkToggle) {
|
|
||||||
enabled =
|
|
||||||
this.isFeatureEnabled(feature, context, () =>
|
|
||||||
fallbackVariant ? fallbackVariant.enabled : false,
|
|
||||||
).result === true;
|
|
||||||
if (!enabled) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const variant: VariantDefinition | null = selectVariant(
|
const variant: VariantDefinition | null = selectVariant(
|
||||||
feature,
|
feature,
|
||||||
context,
|
context,
|
||||||
|
@ -3,6 +3,7 @@ import { PlaygroundSegmentSchema } from 'lib/openapi/spec/playground-segment-sch
|
|||||||
import { StrategyEvaluationResult } from '../client';
|
import { StrategyEvaluationResult } from '../client';
|
||||||
import { Constraint, operators } from '../constraint';
|
import { Constraint, operators } from '../constraint';
|
||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
|
import { selectVariantDefinition, VariantDefinition } from '../variant';
|
||||||
|
|
||||||
export type SegmentForEvaluation = {
|
export type SegmentForEvaluation = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -16,6 +17,7 @@ export interface StrategyTransportInterface {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
parameters: any;
|
parameters: any;
|
||||||
constraints: Constraint[];
|
constraints: Constraint[];
|
||||||
|
variants?: VariantDefinition[];
|
||||||
segments?: number[];
|
segments?: number[];
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
@ -114,11 +116,12 @@ export class Strategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isEnabledWithConstraints(
|
isEnabledWithConstraints(
|
||||||
parameters: unknown,
|
parameters: Record<string, unknown>,
|
||||||
context: Context,
|
context: Context,
|
||||||
constraints: Iterable<Constraint>,
|
constraints: Iterable<Constraint>,
|
||||||
segments: Array<SegmentForEvaluation>,
|
segments: Array<SegmentForEvaluation>,
|
||||||
disabled?: boolean,
|
disabled?: boolean,
|
||||||
|
variantDefinitions?: VariantDefinition[],
|
||||||
): StrategyEvaluationResult {
|
): StrategyEvaluationResult {
|
||||||
const constraintResults = this.checkConstraints(context, constraints);
|
const constraintResults = this.checkConstraints(context, constraints);
|
||||||
const enabledResult = this.isEnabled(parameters, context);
|
const enabledResult = this.isEnabled(parameters, context);
|
||||||
@ -127,10 +130,27 @@ export class Strategy {
|
|||||||
const overallResult =
|
const overallResult =
|
||||||
constraintResults.result && enabledResult && segmentResults.result;
|
constraintResults.result && enabledResult && segmentResults.result;
|
||||||
|
|
||||||
|
const variantDefinition = variantDefinitions
|
||||||
|
? selectVariantDefinition(
|
||||||
|
parameters.groupId as string,
|
||||||
|
variantDefinitions,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
const variant = variantDefinition
|
||||||
|
? {
|
||||||
|
name: variantDefinition.name,
|
||||||
|
enabled: true,
|
||||||
|
payload: variantDefinition.payload,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
enabled: disabled ? false : overallResult,
|
enabled: disabled ? false : overallResult,
|
||||||
evaluationStatus: 'complete',
|
evaluationStatus: 'complete',
|
||||||
|
variant,
|
||||||
|
variants: variant ? variantDefinitions : undefined,
|
||||||
},
|
},
|
||||||
constraints: constraintResults.constraints,
|
constraints: constraintResults.constraints,
|
||||||
segments: segmentResults.segments,
|
segments: segmentResults.segments,
|
||||||
|
@ -3,7 +3,6 @@ import { Context } from './context';
|
|||||||
import { FeatureInterface } from './feature';
|
import { FeatureInterface } from './feature';
|
||||||
import normalizedValue from './strategy/util';
|
import normalizedValue from './strategy/util';
|
||||||
import { resolveContextValue } from './helpers';
|
import { resolveContextValue } from './helpers';
|
||||||
import { PayloadType } from 'unleash-client';
|
|
||||||
|
|
||||||
interface Override {
|
interface Override {
|
||||||
contextName: string;
|
contextName: string;
|
||||||
@ -11,7 +10,7 @@ interface Override {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Payload {
|
export interface Payload {
|
||||||
type: PayloadType;
|
type: 'string' | 'csv' | 'json';
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,8 +18,8 @@ export interface VariantDefinition {
|
|||||||
name: string;
|
name: string;
|
||||||
weight: number;
|
weight: number;
|
||||||
stickiness?: string;
|
stickiness?: string;
|
||||||
payload: Payload;
|
payload?: Payload;
|
||||||
overrides: Override[];
|
overrides?: Override[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Variant {
|
export interface Variant {
|
||||||
@ -66,39 +65,40 @@ function overrideMatchesContext(context: Context): (o: Override) => boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findOverride(
|
function findOverride(
|
||||||
feature: FeatureInterface,
|
variants: VariantDefinition[],
|
||||||
context: Context,
|
context: Context,
|
||||||
): VariantDefinition | undefined {
|
): VariantDefinition | undefined {
|
||||||
return feature.variants
|
return variants
|
||||||
.filter((variant) => variant.overrides)
|
.filter((variant) => variant.overrides)
|
||||||
.find((variant) =>
|
.find((variant) =>
|
||||||
variant.overrides.some(overrideMatchesContext(context)),
|
variant.overrides?.some(overrideMatchesContext(context)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectVariant(
|
export function selectVariantDefinition(
|
||||||
feature: FeatureInterface,
|
featureName: string,
|
||||||
|
variants: VariantDefinition[],
|
||||||
context: Context,
|
context: Context,
|
||||||
): VariantDefinition | null {
|
): VariantDefinition | null {
|
||||||
const totalWeight = feature.variants.reduce((acc, v) => acc + v.weight, 0);
|
const totalWeight = variants.reduce((acc, v) => acc + v.weight, 0);
|
||||||
if (totalWeight <= 0) {
|
if (totalWeight <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const variantOverride = findOverride(feature, context);
|
const variantOverride = findOverride(variants, context);
|
||||||
if (variantOverride) {
|
if (variantOverride) {
|
||||||
return variantOverride;
|
return variantOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { stickiness } = feature.variants[0];
|
const { stickiness } = variants[0];
|
||||||
|
|
||||||
const target = normalizedValue(
|
const target = normalizedValue(
|
||||||
getSeed(context, stickiness),
|
getSeed(context, stickiness),
|
||||||
feature.name,
|
featureName,
|
||||||
totalWeight,
|
totalWeight,
|
||||||
);
|
);
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
const variant = feature.variants.find(
|
const variant = variants.find(
|
||||||
(v: VariantDefinition): VariantDefinition | undefined => {
|
(v: VariantDefinition): VariantDefinition | undefined => {
|
||||||
if (v.weight === 0) {
|
if (v.weight === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -112,3 +112,10 @@ export function selectVariant(
|
|||||||
);
|
);
|
||||||
return variant || null;
|
return variant || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectVariant(
|
||||||
|
feature: FeatureInterface,
|
||||||
|
context: Context,
|
||||||
|
): VariantDefinition | null {
|
||||||
|
return selectVariantDefinition(feature.name, feature.variants, context);
|
||||||
|
}
|
||||||
|
@ -250,6 +250,77 @@ describe('offline client', () => {
|
|||||||
expect(client.isEnabled(name, {}).result).toBeTruthy();
|
expect(client.isEnabled(name, {}).result).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns strategy variant over feature variant', async () => {
|
||||||
|
const name = 'toggle-name';
|
||||||
|
const client = await offlineUnleashClient({
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
strategies: [
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
values: ['my-app-name'],
|
||||||
|
inverted: false,
|
||||||
|
operator: 'IN' as 'IN',
|
||||||
|
contextName: 'appName',
|
||||||
|
caseInsensitive: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
name: 'ignoreNonMatchingStrategyVariant',
|
||||||
|
weightType: 'variable',
|
||||||
|
weight: 1000,
|
||||||
|
stickiness: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
values: ['client-test'],
|
||||||
|
inverted: false,
|
||||||
|
operator: 'IN' as 'IN',
|
||||||
|
contextName: 'appName',
|
||||||
|
caseInsensitive: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
name: 'strategyVariant',
|
||||||
|
weightType: 'variable',
|
||||||
|
weight: 1000,
|
||||||
|
stickiness: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
project: 'default',
|
||||||
|
stale: false,
|
||||||
|
enabled: true,
|
||||||
|
name,
|
||||||
|
type: 'experiment',
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
name: 'ignoreFeatureStrategyVariant',
|
||||||
|
weightType: 'variable',
|
||||||
|
weight: 1000,
|
||||||
|
stickiness: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
context: { appName: 'client-test' },
|
||||||
|
logError: console.log,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(client.getVariant(name, {}).name).toEqual('strategyVariant');
|
||||||
|
expect(client.getVariant(name, {}).enabled).toBeTruthy();
|
||||||
|
expect(client.isEnabled(name, {}).result).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it(`returns '${playgroundStrategyEvaluation.unknownResult}' if it can't evaluate a feature`, async () => {
|
it(`returns '${playgroundStrategyEvaluation.unknownResult}' if it can't evaluate a feature`, async () => {
|
||||||
const name = 'toggle-name';
|
const name = 'toggle-name';
|
||||||
const context = { appName: 'client-test' };
|
const context = { appName: 'client-test' };
|
||||||
|
@ -28,6 +28,13 @@ export const mapFeaturesForClient = (
|
|||||||
strategies: feature.strategies.map((strategy) => ({
|
strategies: feature.strategies.map((strategy) => ({
|
||||||
parameters: {},
|
parameters: {},
|
||||||
...strategy,
|
...strategy,
|
||||||
|
variants: (strategy.variants || []).map((variant) => ({
|
||||||
|
...variant,
|
||||||
|
payload: variant.payload && {
|
||||||
|
...variant.payload,
|
||||||
|
type: variant.payload.type as PayloadType,
|
||||||
|
},
|
||||||
|
})),
|
||||||
constraints:
|
constraints:
|
||||||
strategy.constraints &&
|
strategy.constraints &&
|
||||||
strategy.constraints.map((constraint) => ({
|
strategy.constraints.map((constraint) => ({
|
||||||
|
@ -180,7 +180,10 @@ export class PlaygroundService {
|
|||||||
name: feature.name,
|
name: feature.name,
|
||||||
environment,
|
environment,
|
||||||
context,
|
context,
|
||||||
variants: variantsMap[feature.name] || [],
|
variants:
|
||||||
|
strategyEvaluationResult.variants ||
|
||||||
|
variantsMap[feature.name] ||
|
||||||
|
[],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,19 @@ exports[`featureSchema constraints 1`] = `
|
|||||||
exports[`featureSchema variant override values must be an array 1`] = `
|
exports[`featureSchema variant override values must be an array 1`] = `
|
||||||
{
|
{
|
||||||
"errors": [
|
"errors": [
|
||||||
|
{
|
||||||
|
"instancePath": "/variants/0/payload/type",
|
||||||
|
"keyword": "enum",
|
||||||
|
"message": "must be equal to one of the allowed values",
|
||||||
|
"params": {
|
||||||
|
"allowedValues": [
|
||||||
|
"json",
|
||||||
|
"csv",
|
||||||
|
"string",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"schemaPath": "#/properties/payload/properties/type/enum",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"instancePath": "/variants/0/overrides/0/values",
|
"instancePath": "/variants/0/overrides/0/values",
|
||||||
"keyword": "type",
|
"keyword": "type",
|
||||||
|
@ -93,7 +93,7 @@ export const advancedPlaygroundEnvironmentFeatureSchema = {
|
|||||||
description: `The feature variant you receive based on the provided context or the _disabled
|
description: `The feature variant you receive based on the provided context or the _disabled
|
||||||
variant_. If a feature is disabled or doesn't have any
|
variant_. If a feature is disabled or doesn't have any
|
||||||
variants, you would get the _disabled variant_.
|
variants, you would get the _disabled variant_.
|
||||||
Otherwise, you'll get one of thefeature's defined variants.`,
|
Otherwise, you'll get one of the feature's defined variants.`,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: ['name', 'enabled'],
|
required: ['name', 'enabled'],
|
||||||
@ -118,7 +118,6 @@ export const advancedPlaygroundEnvironmentFeatureSchema = {
|
|||||||
type: {
|
type: {
|
||||||
description: 'The format of the payload.',
|
description: 'The format of the payload.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: ['json', 'csv', 'string'],
|
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -23,7 +23,7 @@ test('clientFeaturesSchema required fields', () => {
|
|||||||
weightType: 'fix',
|
weightType: 'fix',
|
||||||
stickiness: 'c',
|
stickiness: 'c',
|
||||||
payload: {
|
payload: {
|
||||||
type: 'a',
|
type: 'string',
|
||||||
value: 'b',
|
value: 'b',
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
|
@ -43,6 +43,7 @@ export const createStrategyVariantSchema = {
|
|||||||
description:
|
description:
|
||||||
'The type of the value. Commonly used types are string, json and csv.',
|
'The type of the value. Commonly used types are string, json and csv.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
enum: ['json', 'csv', 'string'],
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
description: 'The actual value of payload',
|
description: 'The actual value of payload',
|
||||||
|
@ -11,7 +11,7 @@ test('featureSchema', () => {
|
|||||||
weightType: 'fix',
|
weightType: 'fix',
|
||||||
stickiness: 'a',
|
stickiness: 'a',
|
||||||
overrides: [{ contextName: 'a', values: ['a'] }],
|
overrides: [{ contextName: 'a', values: ['a'] }],
|
||||||
payload: { type: 'a', value: 'b' },
|
payload: { type: 'string', value: 'b' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
environments: [
|
environments: [
|
||||||
|
@ -108,7 +108,6 @@ export const playgroundFeatureSchema = {
|
|||||||
type: {
|
type: {
|
||||||
description: 'The format of the payload.',
|
description: 'The format of the payload.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: ['json', 'csv', 'string'],
|
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -2,6 +2,8 @@ import { FromSchema } from 'json-schema-to-ts';
|
|||||||
import { parametersSchema } from './parameters-schema';
|
import { parametersSchema } from './parameters-schema';
|
||||||
import { playgroundConstraintSchema } from './playground-constraint-schema';
|
import { playgroundConstraintSchema } from './playground-constraint-schema';
|
||||||
import { playgroundSegmentSchema } from './playground-segment-schema';
|
import { playgroundSegmentSchema } from './playground-segment-schema';
|
||||||
|
import { variantSchema } from './variant-schema';
|
||||||
|
import { overrideSchema } from './override-schema';
|
||||||
|
|
||||||
export const playgroundStrategyEvaluation = {
|
export const playgroundStrategyEvaluation = {
|
||||||
evaluationComplete: 'complete',
|
evaluationComplete: 'complete',
|
||||||
@ -51,6 +53,55 @@ export const strategyEvaluationResults = {
|
|||||||
description:
|
description:
|
||||||
'Whether this strategy evaluates to true or not.',
|
'Whether this strategy evaluates to true or not.',
|
||||||
},
|
},
|
||||||
|
variant: {
|
||||||
|
description: `The feature variant you receive based on the provided context or the _disabled
|
||||||
|
variant_. If a feature is disabled or doesn't have any
|
||||||
|
variants, you would get the _disabled variant_.
|
||||||
|
Otherwise, you'll get one of the feature's defined variants.`,
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['name', 'enabled'],
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
"The variant's name. If there is no variant or if the toggle is disabled, this will be `disabled`",
|
||||||
|
example: 'red-variant',
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: 'boolean',
|
||||||
|
description:
|
||||||
|
"Whether the variant is enabled or not. If the feature is disabled or if it doesn't have variants, this property will be `false`",
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['type', 'value'],
|
||||||
|
description:
|
||||||
|
'An optional payload attached to the variant.',
|
||||||
|
properties: {
|
||||||
|
type: {
|
||||||
|
description: 'The format of the payload.',
|
||||||
|
type: 'string',
|
||||||
|
enum: ['json', 'csv', 'string'],
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: 'string',
|
||||||
|
description:
|
||||||
|
'The payload value stringified.',
|
||||||
|
example: '{"property": "value"}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
nullable: true,
|
||||||
|
example: { name: 'green', enabled: true },
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
type: 'array',
|
||||||
|
description: 'The feature variants.',
|
||||||
|
items: { $ref: variantSchema.$id },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -139,6 +190,8 @@ export const playgroundStrategySchema = {
|
|||||||
playgroundConstraintSchema,
|
playgroundConstraintSchema,
|
||||||
playgroundSegmentSchema,
|
playgroundSegmentSchema,
|
||||||
parametersSchema,
|
parametersSchema,
|
||||||
|
variantSchema,
|
||||||
|
overrideSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { FromSchema } from 'json-schema-to-ts';
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
import { PayloadType } from 'unleash-client';
|
|
||||||
|
|
||||||
export const proxyFeatureSchema = {
|
export const proxyFeatureSchema = {
|
||||||
$id: '#/components/schemas/proxyFeatureSchema',
|
$id: '#/components/schemas/proxyFeatureSchema',
|
||||||
@ -51,7 +50,7 @@ export const proxyFeatureSchema = {
|
|||||||
type: {
|
type: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The format of the payload.',
|
description: 'The format of the payload.',
|
||||||
enum: Object.values(PayloadType),
|
enum: ['json', 'csv', 'string'],
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -38,11 +38,13 @@ export const variantSchema = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['type', 'value'],
|
required: ['type', 'value'],
|
||||||
description: 'Extra data configured for this variant',
|
description: 'Extra data configured for this variant',
|
||||||
|
additionalProperties: false,
|
||||||
properties: {
|
properties: {
|
||||||
type: {
|
type: {
|
||||||
description:
|
description:
|
||||||
'The type of the value. Commonly used types are string, json and csv.',
|
'The type of the value. Commonly used types are string, json and csv.',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
enum: ['json', 'csv', 'string'],
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
description: 'The actual value of payload',
|
description: 'The actual value of payload',
|
||||||
|
@ -122,7 +122,7 @@ export interface IVariant {
|
|||||||
weight: number;
|
weight: number;
|
||||||
weightType: 'variable' | 'fix';
|
weightType: 'variable' | 'fix';
|
||||||
payload?: {
|
payload?: {
|
||||||
type: string;
|
type: 'json' | 'csv' | 'string';
|
||||||
value: string;
|
value: string;
|
||||||
};
|
};
|
||||||
stickiness: string;
|
stickiness: string;
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -7509,15 +7509,15 @@ universalify@^0.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
unleash-client@3.21.0:
|
unleash-client@4.1.0-beta.5:
|
||||||
version "3.21.0"
|
version "4.1.0-beta.5"
|
||||||
resolved "https://registry.yarnpkg.com/unleash-client/-/unleash-client-3.21.0.tgz#a31ab30acb42abfb3a21180aa83e4415a3124ec1"
|
resolved "https://registry.yarnpkg.com/unleash-client/-/unleash-client-4.1.0-beta.5.tgz#7407a9dae30411cb2cb849569a6e058cf6b6c47c"
|
||||||
integrity sha512-I7eYhRyOia3oBZ9Tu1v+IlNO+XJgsjcMEO2+j+e4A7LTTKZvGoV8WPfDGGxiMPKBPHNUACkERB3YhCQ9jzTGoQ==
|
integrity sha512-aN5PdvfAlVBc7Fm5cgQr7pc2j6rvbRtp6G9kow0O3FP4h3UCFbM2i0NvSB4r3F8AysXWonbv9IB/TyAC2CGsPA==
|
||||||
dependencies:
|
dependencies:
|
||||||
ip "^1.1.8"
|
ip "^1.1.8"
|
||||||
make-fetch-happen "^10.2.1"
|
make-fetch-happen "^10.2.1"
|
||||||
murmurhash3js "^3.0.1"
|
murmurhash3js "^3.0.1"
|
||||||
semver "^7.3.8"
|
semver "^7.5.3"
|
||||||
|
|
||||||
unpipe@1.0.0, unpipe@~1.0.0:
|
unpipe@1.0.0, unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user