From 283a8f4d8bb2d6118e5b2529e7b201d7a5e62602 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Mon, 25 Mar 2024 15:45:18 +0200 Subject: [PATCH] feat: dependant flag on feature search (#6684) --- .../feature-search/feature-search-store.ts | 17 +++++ .../feature-search/feature.search.e2e.test.ts | 54 +++++++++++++++ .../spec/feature-search-response-schema.ts | 67 ++++++------------- src/lib/types/model.ts | 1 + 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/src/lib/features/feature-search/feature-search-store.ts b/src/lib/features/feature-search/feature-search-store.ts index 323550dd91..a376b54f50 100644 --- a/src/lib/features/feature-search/feature-search-store.ts +++ b/src/lib/features/feature-search/feature-search-store.ts @@ -137,6 +137,11 @@ class FeatureSearchStore implements IFeatureSearchStore { this.db.raw( 'EXISTS (SELECT 1 FROM feature_strategies WHERE feature_strategies.feature_name = features.name AND feature_strategies.environment = feature_environments.environment AND (feature_strategies.disabled IS NULL OR feature_strategies.disabled = false)) as has_enabled_strategies', ), + this.db.raw(`CASE + WHEN dependent_features.parent = features.name THEN 'parent' + WHEN dependent_features.child = features.name THEN 'child' + ELSE null + END AS dependency`), ]; applyQueryParams(query, queryParams); @@ -197,6 +202,17 @@ class FeatureSearchStore implements IFeatureSearchStore { 'feature_strategy_segment.segment_id', 'segments.id', ) + .leftJoin('dependent_features', (qb) => { + qb.on( + 'dependent_features.parent', + '=', + 'features.name', + ).orOn( + 'dependent_features.child', + '=', + 'features.name', + ); + }) .leftJoin('client_metrics_env', (qb) => { qb.on( 'client_metrics_env.environment', @@ -335,6 +351,7 @@ class FeatureSearchStore implements IFeatureSearchStore { stale: row.stale, impressionData: row.impression_data, lastSeenAt: row.last_seen_at, + dependencyType: row.dependency, environments: [], segments: row.segment_name ? [row.segment_name] : [], }; diff --git a/src/lib/features/feature-search/feature.search.e2e.test.ts b/src/lib/features/feature-search/feature.search.e2e.test.ts index 0271d5ab5f..5294ef6dc3 100644 --- a/src/lib/features/feature-search/feature.search.e2e.test.ts +++ b/src/lib/features/feature-search/feature.search.e2e.test.ts @@ -977,3 +977,57 @@ test('should return environment usage metrics', async () => { ], }); }); + +test('should return dependencyType', async () => { + await app.createFeature({ + name: 'my_feature_a', + createdAt: '2023-01-29T15:21:39.975Z', + }); + await app.createFeature({ + name: 'my_feature_b', + createdAt: '2023-01-29T15:21:39.975Z', + }); + await app.createFeature({ + name: 'my_feature_c', + createdAt: '2023-01-29T15:21:39.975Z', + }); + await app.createFeature({ + name: 'my_feature_d', + createdAt: '2023-01-29T15:21:39.975Z', + }); + + await stores.dependentFeaturesStore.upsert({ + child: 'my_feature_b', + parent: 'my_feature_a', + enabled: true, + }); + await stores.dependentFeaturesStore.upsert({ + child: 'my_feature_c', + parent: 'my_feature_a', + enabled: true, + }); + + const { body } = await searchFeatures({ + query: 'my_feature', + }); + expect(body).toMatchObject({ + features: [ + { + name: 'my_feature_a', + dependencyType: 'parent', + }, + { + name: 'my_feature_b', + dependencyType: 'child', + }, + { + name: 'my_feature_c', + dependencyType: 'child', + }, + { + name: 'my_feature_d', + dependencyType: null, + }, + ], + }); +}); diff --git a/src/lib/openapi/spec/feature-search-response-schema.ts b/src/lib/openapi/spec/feature-search-response-schema.ts index d6268b0797..dbaac99c4e 100644 --- a/src/lib/openapi/spec/feature-search-response-schema.ts +++ b/src/lib/openapi/spec/feature-search-response-schema.ts @@ -12,7 +12,18 @@ export const featureSearchResponseSchema = { $id: '#/components/schemas/featureSearchResponseSchema', type: 'object', additionalProperties: false, - required: ['name'], + required: [ + 'name', + 'dependencyType', + 'type', + 'project', + 'stale', + 'favorite', + 'impressionData', + 'createdAt', + 'environments', + 'segments', + ], description: 'A feature toggle definition', properties: { name: { @@ -33,6 +44,14 @@ export const featureSearchResponseSchema = { 'Controls disabling of the comments section in case of an incident', description: 'Detailed description of the feature', }, + dependencyType: { + type: 'string', + enum: ['parent', 'child', null], + nullable: true, + example: 'parent', + description: + "The type of dependency. 'parent' means that the feature is a parent feature, 'child' means that the feature is a child feature.", + }, archived: { type: 'boolean', example: true, @@ -43,11 +62,6 @@ export const featureSearchResponseSchema = { example: 'dx-squad', description: 'Name of the project the feature belongs to', }, - enabled: { - type: 'boolean', - example: true, - description: '`true` if the feature is enabled, otherwise `false`.', - }, stale: { type: 'boolean', example: false, @@ -129,47 +143,6 @@ export const featureSearchResponseSchema = { nullable: true, description: 'The list of feature tags', }, - children: { - type: 'array', - description: - 'The list of child feature names. This is an experimental field and may change.', - items: { - type: 'string', - example: 'some-feature', - }, - }, - dependencies: { - type: 'array', - items: { - type: 'object', - additionalProperties: false, - required: ['feature'], - properties: { - feature: { - description: 'The name of the parent feature', - type: 'string', - example: 'some-feature', - }, - enabled: { - description: - 'Whether the parent feature is enabled or not', - type: 'boolean', - example: true, - }, - variants: { - description: - 'The list of variants the parent feature should resolve to. Only valid when feature is enabled.', - type: 'array', - items: { - example: 'some-feature-blue-variant', - type: 'string', - }, - }, - }, - }, - description: - 'The list of parent dependencies. This is an experimental field and may change.', - }, }, components: { schemas: { diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index 7af1ed22f5..530c1fbd13 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -223,6 +223,7 @@ export type IFeatureSearchOverview = Exclude< IFeatureOverview, 'environments' > & { + dependencyType: 'parent' | 'child' | null; environments: FeatureSearchEnvironmentSchema[]; };