mirror of
https://github.com/Unleash/unleash.git
synced 2024-12-22 19:07:54 +01:00
feat: Client api dependent features (#4778)
This commit is contained in:
parent
9704f4a04d
commit
85c7f84f8d
@ -67,6 +67,8 @@ export default class FeatureToggleClientStore
|
||||
const isPlayground = requestType === 'playground';
|
||||
const environment = featureQuery?.environment || DEFAULT_ENV;
|
||||
const stopTimer = this.timer('getFeatureAdmin');
|
||||
const dependentFeaturesEnabled =
|
||||
this.flagResolver.isEnabled('dependentFeatures');
|
||||
|
||||
let selectColumns = [
|
||||
'features.name as name',
|
||||
@ -91,6 +93,9 @@ export default class FeatureToggleClientStore
|
||||
'fs.variants as strategy_variants',
|
||||
'segments.id as segment_id',
|
||||
'segments.constraints as segment_constraints',
|
||||
'df.parent as parent',
|
||||
'df.variants as parent_variants',
|
||||
'df.enabled as parent_enabled',
|
||||
] as (string | Raw<any>)[];
|
||||
|
||||
let query = this.db('features')
|
||||
@ -122,7 +127,8 @@ export default class FeatureToggleClientStore
|
||||
`fss.feature_strategy_id`,
|
||||
`fs.id`,
|
||||
)
|
||||
.leftJoin('segments', `segments.id`, `fss.segment_id`);
|
||||
.leftJoin('segments', `segments.id`, `fss.segment_id`)
|
||||
.leftJoin('dependent_features as df', 'df.child', 'features.name');
|
||||
|
||||
if (isAdmin) {
|
||||
query = query.leftJoin(
|
||||
@ -195,6 +201,16 @@ export default class FeatureToggleClientStore
|
||||
) {
|
||||
this.addSegmentIdsToStrategy(feature, r);
|
||||
}
|
||||
if (r.parent && !isAdmin && dependentFeaturesEnabled) {
|
||||
feature.dependencies = feature.dependencies || [];
|
||||
feature.dependencies.push({
|
||||
feature: r.parent,
|
||||
enabled: r.parent_enabled,
|
||||
...(r.parent_enabled
|
||||
? { variants: r.parent_variants }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
feature.impressionData = r.impression_data;
|
||||
feature.enabled = !!r.enabled;
|
||||
feature.name = r.name;
|
||||
|
@ -17,20 +17,20 @@ export class DependentFeaturesService {
|
||||
}
|
||||
|
||||
async upsertFeatureDependency(
|
||||
parentFeature: string,
|
||||
childFeature: string,
|
||||
dependentFeature: CreateDependentFeatureSchema,
|
||||
): Promise<void> {
|
||||
const { enabled, feature, variants } = dependentFeature;
|
||||
const featureDependency: FeatureDependency =
|
||||
enabled === false
|
||||
? {
|
||||
parent: parentFeature,
|
||||
child: feature,
|
||||
parent: feature,
|
||||
child: childFeature,
|
||||
enabled,
|
||||
}
|
||||
: {
|
||||
parent: parentFeature,
|
||||
child: feature,
|
||||
parent: feature,
|
||||
child: childFeature,
|
||||
enabled: true,
|
||||
variants,
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ import { featureStrategySchema } from './feature-strategy-schema';
|
||||
import { variantSchema } from './variant-schema';
|
||||
import { overrideSchema } from './override-schema';
|
||||
import { strategyVariantSchema } from './strategy-variant-schema';
|
||||
import { dependentFeatureSchema } from './dependent-feature-schema';
|
||||
|
||||
export const clientFeatureSchema = {
|
||||
$id: '#/components/schemas/clientFeatureSchema',
|
||||
@ -73,6 +74,13 @@ export const clientFeatureSchema = {
|
||||
},
|
||||
nullable: true,
|
||||
},
|
||||
dependencies: {
|
||||
type: 'array',
|
||||
description: 'Feature dependencies for this toggle',
|
||||
items: {
|
||||
$ref: '#/components/schemas/dependentFeatureSchema',
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
@ -82,6 +90,7 @@ export const clientFeatureSchema = {
|
||||
strategyVariantSchema,
|
||||
variantSchema,
|
||||
overrideSchema,
|
||||
dependentFeatureSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@ -9,6 +9,7 @@ import { featureStrategySchema } from './feature-strategy-schema';
|
||||
import { clientFeatureSchema } from './client-feature-schema';
|
||||
import { variantSchema } from './variant-schema';
|
||||
import { strategyVariantSchema } from './strategy-variant-schema';
|
||||
import { dependentFeatureSchema } from './dependent-feature-schema';
|
||||
|
||||
export const clientFeaturesSchema = {
|
||||
$id: '#/components/schemas/clientFeaturesSchema',
|
||||
@ -57,6 +58,7 @@ export const clientFeaturesSchema = {
|
||||
featureStrategySchema,
|
||||
strategyVariantSchema,
|
||||
variantSchema,
|
||||
dependentFeatureSchema,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
@ -986,6 +986,7 @@ class FeatureToggleService {
|
||||
variants,
|
||||
description,
|
||||
impressionData,
|
||||
dependencies,
|
||||
}) => ({
|
||||
name,
|
||||
type,
|
||||
@ -996,6 +997,7 @@ class FeatureToggleService {
|
||||
variants,
|
||||
description,
|
||||
impressionData,
|
||||
dependencies,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ export interface IFeatureToggleClient {
|
||||
variants: IVariant[];
|
||||
enabled: boolean;
|
||||
strategies: Omit<IStrategyConfig, 'disabled'>[];
|
||||
dependencies?: IDependency[];
|
||||
impressionData?: boolean;
|
||||
lastSeenAt?: Date;
|
||||
createdAt?: Date;
|
||||
@ -133,6 +134,12 @@ export interface IVariant {
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface IDependency {
|
||||
feature: string;
|
||||
variants?: string[];
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export type IStrategyVariant = Omit<IVariant, 'overrides'>;
|
||||
|
||||
export interface IEnvironment {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
FeatureToggleWithEnvironment,
|
||||
IDependency,
|
||||
IFeatureOverview,
|
||||
IFeatureStrategy,
|
||||
IStrategyConfig,
|
||||
@ -16,6 +17,7 @@ export interface FeatureConfigurationClient {
|
||||
stale: boolean;
|
||||
strategies: IStrategyConfig[];
|
||||
variants: IVariant[];
|
||||
dependencies?: IDependency[];
|
||||
}
|
||||
export interface IFeatureStrategiesStore
|
||||
extends Store<IFeatureStrategy, string> {
|
||||
|
@ -10,7 +10,9 @@ let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('feature_api_client', getLogger);
|
||||
db = await dbInit('feature_api_client', getLogger, {
|
||||
experimental: { flags: { dependentFeatures: true } },
|
||||
});
|
||||
app = await setupAppWithCustomConfig(db.stores, {
|
||||
experimental: {
|
||||
flags: {
|
||||
@ -36,6 +38,7 @@ beforeAll(async () => {
|
||||
},
|
||||
'test',
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.createFeatureToggle(
|
||||
'default',
|
||||
{
|
||||
@ -52,6 +55,16 @@ beforeAll(async () => {
|
||||
},
|
||||
'test',
|
||||
);
|
||||
// depend on enabled feature with variant
|
||||
await app.services.dependentFeaturesService.upsertFeatureDependency(
|
||||
'featureY',
|
||||
{ feature: 'featureX', variants: ['featureXVariant'] },
|
||||
);
|
||||
// depend on parent being disabled
|
||||
await app.services.dependentFeaturesService.upsertFeatureDependency(
|
||||
'featureY',
|
||||
{ feature: 'featureZ', enabled: false },
|
||||
);
|
||||
|
||||
await app.services.featureToggleServiceV2.archiveToggle(
|
||||
'featureArchivedX',
|
||||
@ -127,6 +140,30 @@ test('returns four feature toggles', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('returns dependencies', async () => {
|
||||
return app.request
|
||||
.get('/api/client/features')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200)
|
||||
.expect((res) => {
|
||||
expect(res.body.features[0]).toMatchObject({
|
||||
name: 'featureY',
|
||||
dependencies: [
|
||||
{
|
||||
feature: 'featureX',
|
||||
enabled: true,
|
||||
variants: ['featureXVariant'],
|
||||
},
|
||||
{
|
||||
feature: 'featureZ',
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(res.body.features[1].dependencies).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
test('returns four feature toggles without createdAt', async () => {
|
||||
return app.request
|
||||
.get('/api/client/features')
|
||||
|
Loading…
Reference in New Issue
Block a user