mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
feat: add option to return disabled strategies (#5059)
Adds the option to include disabled strategies (behind the playgroundImprovements flag Closes # [1-1505](https://linear.app/unleash/issue/1-1505/return-disabled-strategies-in-the-playground-features-request) --------- Signed-off-by: andreas-unleash <andreas@getunleash.ai>
This commit is contained in:
parent
04568ebde8
commit
cf42a829f4
@ -70,6 +70,7 @@ export class FeatureToggleRowConverter {
|
|||||||
constraints: row.constraints || [],
|
constraints: row.constraints || [],
|
||||||
parameters: mapValues(row.parameters || {}, ensureStringValue),
|
parameters: mapValues(row.parameters || {}, ensureStringValue),
|
||||||
sortOrder: row.sort_order,
|
sortOrder: row.sort_order,
|
||||||
|
disabled: row.strategy_disabled,
|
||||||
};
|
};
|
||||||
strategy.variants = row.strategy_variants || [];
|
strategy.variants = row.strategy_variants || [];
|
||||||
return strategy;
|
return strategy;
|
||||||
@ -111,6 +112,7 @@ export class FeatureToggleRowConverter {
|
|||||||
row: any,
|
row: any,
|
||||||
feature: PartialDeep<IFeatureToggleClient>,
|
feature: PartialDeep<IFeatureToggleClient>,
|
||||||
featureQuery?: IFeatureToggleQuery,
|
featureQuery?: IFeatureToggleQuery,
|
||||||
|
includeDisabledStrategies?: boolean,
|
||||||
) => {
|
) => {
|
||||||
feature.impressionData = row.impression_data;
|
feature.impressionData = row.impression_data;
|
||||||
feature.enabled = !!row.enabled;
|
feature.enabled = !!row.enabled;
|
||||||
@ -123,7 +125,10 @@ export class FeatureToggleRowConverter {
|
|||||||
feature.variants = row.variants || [];
|
feature.variants = row.variants || [];
|
||||||
feature.project = row.project;
|
feature.project = row.project;
|
||||||
|
|
||||||
if (this.isUnseenStrategyRow(feature, row) && !row.strategy_disabled) {
|
if (
|
||||||
|
this.isUnseenStrategyRow(feature, row) &&
|
||||||
|
(includeDisabledStrategies ? true : !row.strategy_disabled)
|
||||||
|
) {
|
||||||
feature.strategies?.push(this.rowToStrategy(row));
|
feature.strategies?.push(this.rowToStrategy(row));
|
||||||
}
|
}
|
||||||
if (this.isNewTag(feature, row)) {
|
if (this.isNewTag(feature, row)) {
|
||||||
@ -141,13 +146,19 @@ export class FeatureToggleRowConverter {
|
|||||||
buildFeatureToggleListFromRows = (
|
buildFeatureToggleListFromRows = (
|
||||||
rows: any[],
|
rows: any[],
|
||||||
featureQuery?: IFeatureToggleQuery,
|
featureQuery?: IFeatureToggleQuery,
|
||||||
|
includeDisabledStrategies?: boolean,
|
||||||
): FeatureToggle[] => {
|
): FeatureToggle[] => {
|
||||||
const result = rows.reduce((acc, r) => {
|
const result = rows.reduce((acc, r) => {
|
||||||
let feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
let feature: PartialDeep<IFeatureToggleClient> = acc[r.name] ?? {
|
||||||
strategies: [],
|
strategies: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
feature = this.createBaseFeature(r, feature, featureQuery);
|
feature = this.createBaseFeature(
|
||||||
|
r,
|
||||||
|
feature,
|
||||||
|
featureQuery,
|
||||||
|
includeDisabledStrategies,
|
||||||
|
);
|
||||||
|
|
||||||
feature.createdAt = r.created_at;
|
feature.createdAt = r.created_at;
|
||||||
feature.favorite = r.favorite;
|
feature.favorite = r.favorite;
|
||||||
@ -162,6 +173,7 @@ export class FeatureToggleRowConverter {
|
|||||||
buildPlaygroundFeaturesFromRows = (
|
buildPlaygroundFeaturesFromRows = (
|
||||||
rows: any[],
|
rows: any[],
|
||||||
dependentFeaturesEnabled: boolean,
|
dependentFeaturesEnabled: boolean,
|
||||||
|
includeDisabledStrategies: boolean,
|
||||||
featureQuery?: IFeatureToggleQuery,
|
featureQuery?: IFeatureToggleQuery,
|
||||||
): FeatureConfigurationClient[] => {
|
): FeatureConfigurationClient[] => {
|
||||||
const result = rows.reduce((acc, r) => {
|
const result = rows.reduce((acc, r) => {
|
||||||
@ -169,7 +181,12 @@ export class FeatureToggleRowConverter {
|
|||||||
strategies: [],
|
strategies: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
feature = this.createBaseFeature(r, feature, featureQuery);
|
feature = this.createBaseFeature(
|
||||||
|
r,
|
||||||
|
feature,
|
||||||
|
featureQuery,
|
||||||
|
includeDisabledStrategies,
|
||||||
|
);
|
||||||
|
|
||||||
if (r.parent && dependentFeaturesEnabled) {
|
if (r.parent && dependentFeaturesEnabled) {
|
||||||
feature.dependencies = feature.dependencies || [];
|
feature.dependencies = feature.dependencies || [];
|
||||||
|
@ -165,6 +165,7 @@ export default class FakeFeatureToggleStore implements IFeatureToggleStore {
|
|||||||
|
|
||||||
async getPlaygroundFeatures(
|
async getPlaygroundFeatures(
|
||||||
dependentFeaturesEnabled: boolean,
|
dependentFeaturesEnabled: boolean,
|
||||||
|
includeDisabledStrategies: boolean,
|
||||||
query?: IFeatureToggleQuery,
|
query?: IFeatureToggleQuery,
|
||||||
): Promise<FeatureConfigurationClient[]> {
|
): Promise<FeatureConfigurationClient[]> {
|
||||||
return this.features.filter(
|
return this.features.filter(
|
||||||
|
@ -1056,6 +1056,7 @@ class FeatureToggleService {
|
|||||||
await this.clientFeatureToggleStore.getPlayground(query || {}),
|
await this.clientFeatureToggleStore.getPlayground(query || {}),
|
||||||
await this.featureToggleStore.getPlaygroundFeatures(
|
await this.featureToggleStore.getPlaygroundFeatures(
|
||||||
this.flagResolver.isEnabled('dependentFeatures'),
|
this.flagResolver.isEnabled('dependentFeatures'),
|
||||||
|
this.flagResolver.isEnabled('playgroundImprovements'),
|
||||||
query,
|
query,
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
@ -123,6 +123,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
featureQuery?: IFeatureToggleQuery,
|
featureQuery?: IFeatureToggleQuery,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
archived: boolean = false,
|
archived: boolean = false,
|
||||||
|
includeDisabledStrategies: boolean = false,
|
||||||
): Promise<FeatureToggle[]> {
|
): Promise<FeatureToggle[]> {
|
||||||
const environment = featureQuery?.environment || DEFAULT_ENV;
|
const environment = featureQuery?.environment || DEFAULT_ENV;
|
||||||
|
|
||||||
@ -150,11 +151,13 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
return this.featureToggleRowConverter.buildFeatureToggleListFromRows(
|
return this.featureToggleRowConverter.buildFeatureToggleListFromRows(
|
||||||
rows,
|
rows,
|
||||||
featureQuery,
|
featureQuery,
|
||||||
|
includeDisabledStrategies,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPlaygroundFeatures(
|
async getPlaygroundFeatures(
|
||||||
dependentFeaturesEnabled: boolean,
|
dependentFeaturesEnabled: boolean,
|
||||||
|
includeDisabledStrategies: boolean,
|
||||||
featureQuery: IFeatureToggleQuery,
|
featureQuery: IFeatureToggleQuery,
|
||||||
): Promise<FeatureConfigurationClient[]> {
|
): Promise<FeatureConfigurationClient[]> {
|
||||||
const environment = featureQuery?.environment || DEFAULT_ENV;
|
const environment = featureQuery?.environment || DEFAULT_ENV;
|
||||||
@ -177,6 +180,7 @@ export default class FeatureToggleStore implements IFeatureToggleStore {
|
|||||||
return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows(
|
return this.featureToggleRowConverter.buildPlaygroundFeaturesFromRows(
|
||||||
rows,
|
rows,
|
||||||
dependentFeaturesEnabled,
|
dependentFeaturesEnabled,
|
||||||
|
includeDisabledStrategies,
|
||||||
featureQuery,
|
featureQuery,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ export interface IFeatureToggleStore extends Store<FeatureToggle, string> {
|
|||||||
): Promise<FeatureToggle[]>;
|
): Promise<FeatureToggle[]>;
|
||||||
getPlaygroundFeatures(
|
getPlaygroundFeatures(
|
||||||
dependentFeaturesEnabled: boolean,
|
dependentFeaturesEnabled: boolean,
|
||||||
|
includeDisabledStrategies: boolean,
|
||||||
featureQuery?: IFeatureToggleQuery,
|
featureQuery?: IFeatureToggleQuery,
|
||||||
): Promise<FeatureConfigurationClient[]>;
|
): Promise<FeatureConfigurationClient[]>;
|
||||||
countByDate(queryModifiers: {
|
countByDate(queryModifiers: {
|
||||||
|
@ -11,7 +11,9 @@ let db: ITestDb;
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('advanced_playground', getLogger, {
|
db = await dbInit('advanced_playground', getLogger, {
|
||||||
experimental: { flags: { dependentFeatures: true } },
|
experimental: {
|
||||||
|
flags: { dependentFeatures: true, playgroundImprovements: true },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
app = await setupAppWithCustomConfig(
|
app = await setupAppWithCustomConfig(
|
||||||
db.stores,
|
db.stores,
|
||||||
@ -23,6 +25,9 @@ beforeAll(async () => {
|
|||||||
strategyVariant: true,
|
strategyVariant: true,
|
||||||
privateProjects: true,
|
privateProjects: true,
|
||||||
dependentFeatures: true,
|
dependentFeatures: true,
|
||||||
|
playgroundImprovements: true,
|
||||||
|
useLastSeenRefactor: true,
|
||||||
|
separateAdminClientApi: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -327,6 +332,7 @@ test('show matching variant from variants selection only for enabled toggles', a
|
|||||||
variants,
|
variants,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await enableToggle('test-playground-feature-with-variants');
|
await enableToggle('test-playground-feature-with-variants');
|
||||||
|
|
||||||
const { body: result } = await app.request
|
const { body: result } = await app.request
|
||||||
@ -357,3 +363,41 @@ test('show matching variant from variants selection only for enabled toggles', a
|
|||||||
expect(feature.variants).toMatchObject([]);
|
expect(feature.variants).toMatchObject([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should return disabled strategies with unevaluated result', async () => {
|
||||||
|
await createFeatureToggleWithStrategy(
|
||||||
|
'test-playground-feature-with-disabled-strategy',
|
||||||
|
{
|
||||||
|
name: 'flexibleRollout',
|
||||||
|
constraints: [],
|
||||||
|
disabled: true,
|
||||||
|
parameters: {
|
||||||
|
rollout: '50',
|
||||||
|
stickiness: 'random',
|
||||||
|
groupId: 'test-playground-feature-with-variants',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { body: result } = await app.request
|
||||||
|
.post('/api/admin/playground/advanced')
|
||||||
|
.send({
|
||||||
|
environments: ['default'],
|
||||||
|
projects: ['default'],
|
||||||
|
context: { appName: 'playground' },
|
||||||
|
})
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const typedResult: AdvancedPlaygroundResponseSchema = result;
|
||||||
|
|
||||||
|
const feature = typedResult.features.find(
|
||||||
|
(feature) =>
|
||||||
|
feature.name === 'test-playground-feature-with-disabled-strategy',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
feature?.environments.default[0].strategies.data[0].result
|
||||||
|
.evaluationStatus,
|
||||||
|
).toBe('unevaluated');
|
||||||
|
});
|
||||||
|
@ -44,7 +44,6 @@ export default class UnleashClient {
|
|||||||
!strategy ||
|
!strategy ||
|
||||||
!strategy.name ||
|
!strategy.name ||
|
||||||
typeof strategy.name !== 'string' ||
|
typeof strategy.name !== 'string' ||
|
||||||
!strategy.isEnabled ||
|
|
||||||
typeof strategy.isEnabled !== 'function'
|
typeof strategy.isEnabled !== 'function'
|
||||||
) {
|
) {
|
||||||
throw new Error('Invalid strategy data / interface');
|
throw new Error('Invalid strategy data / interface');
|
||||||
|
@ -145,9 +145,20 @@ export class Strategy {
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
return {
|
return {
|
||||||
result: {
|
result: {
|
||||||
enabled: disabled ? false : overallResult,
|
enabled: 'unknown',
|
||||||
|
evaluationStatus: 'unevaluated',
|
||||||
|
},
|
||||||
|
constraints: constraintResults.constraints,
|
||||||
|
segments: segmentResults.segments,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: {
|
||||||
|
enabled: overallResult,
|
||||||
evaluationStatus: 'complete',
|
evaluationStatus: 'complete',
|
||||||
variant,
|
variant,
|
||||||
variants: variant ? variantDefinitions : undefined,
|
variants: variant ? variantDefinitions : undefined,
|
||||||
|
@ -8,6 +8,7 @@ import { overrideSchema } from './override-schema';
|
|||||||
export const playgroundStrategyEvaluation = {
|
export const playgroundStrategyEvaluation = {
|
||||||
evaluationComplete: 'complete',
|
evaluationComplete: 'complete',
|
||||||
evaluationIncomplete: 'incomplete',
|
evaluationIncomplete: 'incomplete',
|
||||||
|
unevaluated: 'unevaluated',
|
||||||
unknownResult: 'unknown',
|
unknownResult: 'unknown',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -21,8 +22,11 @@ export const strategyEvaluationResults = {
|
|||||||
evaluationStatus: {
|
evaluationStatus: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description:
|
description:
|
||||||
"Signals that this strategy could not be evaluated. This is most likely because you're using a custom strategy that Unleash doesn't know about.",
|
"Signals that this strategy could not be evaluated. This is most likely because you're using a custom strategy that Unleash doesn't know about. The `unevaluated` result is also returned if the strategy is disabled.",
|
||||||
enum: [playgroundStrategyEvaluation.evaluationIncomplete],
|
enum: [
|
||||||
|
playgroundStrategyEvaluation.evaluationIncomplete,
|
||||||
|
playgroundStrategyEvaluation.unevaluated,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
enabled: {
|
enabled: {
|
||||||
description:
|
description:
|
||||||
|
@ -48,6 +48,7 @@ process.nextTick(async () => {
|
|||||||
dependentFeatures: true,
|
dependentFeatures: true,
|
||||||
useLastSeenRefactor: true,
|
useLastSeenRefactor: true,
|
||||||
separateAdminClientApi: true,
|
separateAdminClientApi: true,
|
||||||
|
playgroundImprovements: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authentication: {
|
authentication: {
|
||||||
|
Loading…
Reference in New Issue
Block a user