1
0
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:
andreas-unleash 2023-10-17 12:35:07 +03:00 committed by GitHub
parent 04568ebde8
commit cf42a829f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 91 additions and 8 deletions

View File

@ -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 || [];

View File

@ -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(

View File

@ -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,
), ),
]); ]);

View File

@ -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,
); );
} }

View File

@ -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: {

View File

@ -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');
});

View File

@ -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');

View File

@ -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,

View File

@ -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:

View File

@ -48,6 +48,7 @@ process.nextTick(async () => {
dependentFeatures: true, dependentFeatures: true,
useLastSeenRefactor: true, useLastSeenRefactor: true,
separateAdminClientApi: true, separateAdminClientApi: true,
playgroundImprovements: true,
}, },
}, },
authentication: { authentication: {