From 80049c2fa9e945497cc7b78a9a732821bc5093d7 Mon Sep 17 00:00:00 2001 From: kwasniew Date: Tue, 14 May 2024 14:28:10 +0200 Subject: [PATCH] feat: feature environments variants usage ui flag --- src/lib/db/feature-environment-store.ts | 8 +++++ .../metrics/instance/instance-service.test.ts | 5 +++ .../metrics/instance/instance-service.ts | 14 +++++++- src/lib/routes/admin-api/config.test.ts | 36 +++++++++++++++++++ src/lib/routes/admin-api/config.ts | 13 +++++++ .../types/stores/feature-environment-store.ts | 2 ++ src/test/e2e/api/admin/config.e2e.test.ts | 9 +++++ .../fake-feature-environment-store.ts | 8 +++++ 8 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/lib/db/feature-environment-store.ts b/src/lib/db/feature-environment-store.ts index 2d08914a8b..5590bc089f 100644 --- a/src/lib/db/feature-environment-store.ts +++ b/src/lib/db/feature-environment-store.ts @@ -490,4 +490,12 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { ); } } + + async variantExists(): Promise { + const result = await this.db.raw( + `SELECT EXISTS (SELECT 1 FROM ${T.featureEnvs} WHERE variants <> '[]'::jsonb) AS present`, + ); + const { present } = result.rows[0]; + return present; + } } diff --git a/src/lib/features/metrics/instance/instance-service.test.ts b/src/lib/features/metrics/instance/instance-service.test.ts index 5c79419807..f8ecee33ac 100644 --- a/src/lib/features/metrics/instance/instance-service.test.ts +++ b/src/lib/features/metrics/instance/instance-service.test.ts @@ -8,6 +8,7 @@ import FakeClientMetricsStoreV2 from '../client-metrics/fake-client-metrics-stor import FakeStrategiesStore from '../../../../test/fixtures/fake-strategies-store'; import FakeFeatureToggleStore from '../../feature-toggle/fakes/fake-feature-toggle-store'; import type { IApplicationOverview } from './models'; +import FakeFeatureEnvironmentStore from '../../../../test/fixtures/fake-feature-environment-store'; let config: IUnleashConfig; beforeAll(() => { @@ -29,6 +30,7 @@ test('Multiple registrations of same appname and instanceid within same time per featureToggleStore: new FakeFeatureToggleStore(), clientApplicationsStore, clientInstanceStore, + featureEnvironmentStore: new FakeFeatureEnvironmentStore(), eventStore: new FakeEventStore(), }, config, @@ -79,6 +81,7 @@ test('Multiple unique clients causes multiple registrations', async () => { featureToggleStore: new FakeFeatureToggleStore(), clientApplicationsStore, clientInstanceStore, + featureEnvironmentStore: new FakeFeatureEnvironmentStore(), eventStore: new FakeEventStore(), }, config, @@ -129,6 +132,7 @@ test('Same client registered outside of dedup interval will be registered twice' featureToggleStore: new FakeFeatureToggleStore(), clientApplicationsStore, clientInstanceStore, + featureEnvironmentStore: new FakeFeatureEnvironmentStore(), eventStore: new FakeEventStore(), }, config, @@ -179,6 +183,7 @@ test('No registrations during a time period will not call stores', async () => { featureToggleStore: new FakeFeatureToggleStore(), clientApplicationsStore, clientInstanceStore, + featureEnvironmentStore: new FakeFeatureEnvironmentStore(), eventStore: new FakeEventStore(), }, config, diff --git a/src/lib/features/metrics/instance/instance-service.ts b/src/lib/features/metrics/instance/instance-service.ts index 2855ac5d27..c8a43943c2 100644 --- a/src/lib/features/metrics/instance/instance-service.ts +++ b/src/lib/features/metrics/instance/instance-service.ts @@ -1,6 +1,9 @@ import { APPLICATION_CREATED, CLIENT_REGISTER } from '../../../types/events'; import type { IApplication, IApplicationOverview } from './models'; -import type { IUnleashStores } from '../../../types/stores'; +import type { + IFeatureEnvironmentStore, + IUnleashStores, +} from '../../../types/stores'; import type { IUnleashConfig } from '../../../types/option'; import type { IEventStore } from '../../../types/stores/event-store'; import type { @@ -46,6 +49,8 @@ export default class ClientInstanceService { private privateProjectChecker: IPrivateProjectChecker; + private featureEnvironmentStore: IFeatureEnvironmentStore; + private flagResolver: IFlagResolver; constructor( @@ -55,6 +60,7 @@ export default class ClientInstanceService { featureToggleStore, clientInstanceStore, clientApplicationsStore, + featureEnvironmentStore, eventStore, }: Pick< IUnleashStores, @@ -63,6 +69,7 @@ export default class ClientInstanceService { | 'featureToggleStore' | 'clientApplicationsStore' | 'clientInstanceStore' + | 'featureEnvironmentStore' | 'eventStore' >, { @@ -78,6 +85,7 @@ export default class ClientInstanceService { this.clientInstanceStore = clientInstanceStore; this.eventStore = eventStore; this.privateProjectChecker = privateProjectChecker; + this.featureEnvironmentStore = featureEnvironmentStore; this.flagResolver = flagResolver; this.logger = getLogger( '/services/client-metrics/client-instance-service.ts', @@ -294,4 +302,8 @@ export default class ClientInstanceService { } }); } + + async usesFeatureEnvironmentVariants() { + return this.featureEnvironmentStore.variantExists(); + } } diff --git a/src/lib/routes/admin-api/config.test.ts b/src/lib/routes/admin-api/config.test.ts index 6ac6a13f9c..4038c069ca 100644 --- a/src/lib/routes/admin-api/config.test.ts +++ b/src/lib/routes/admin-api/config.test.ts @@ -98,3 +98,39 @@ describe('displayUpgradeEdgeBanner', () => { expect(body.flags.displayUpgradeEdgeBanner).toEqual(false); }); }); + +describe('displayFeatureEnvironmentVariants', () => { + test('ui config should have displayFeatureEnvironmentVariants flag disabled if no env variants are used', async () => { + const { body } = await request + .get(`${base}/api/admin/ui-config`) + .expect('Content-Type', /json/) + .expect(200); + expect(body.flags).toBeTruthy(); + expect(body.flags.displayFeatureEnvironmentVariants).toEqual(false); + }); + test('ui config should have displayFeatureEnvironmentVariants flag enabled if env variants are used', async () => { + await stores.featureEnvironmentStore.addEnvironmentToFeature( + 'test', + 'default', + true, + ); + await stores.featureEnvironmentStore.addVariantsToFeatureEnvironment( + 'test', + 'default', + [ + { + name: 'a', + weight: 1, + weightType: 'fix', + stickiness: 'default', + }, + ], + ); + const { body } = await request + .get(`${base}/api/admin/ui-config`) + .expect('Content-Type', /json/) + .expect(200); + expect(body.flags).toBeTruthy(); + expect(body.flags.displayFeatureEnvironmentVariants).toEqual(true); + }); +}); diff --git a/src/lib/routes/admin-api/config.ts b/src/lib/routes/admin-api/config.ts index c7eef8d322..ec54fd4b08 100644 --- a/src/lib/routes/admin-api/config.ts +++ b/src/lib/routes/admin-api/config.ts @@ -44,6 +44,8 @@ class ConfigController extends Controller { private usesOldEdgeFunction: () => Promise; + private usesFeatureEnvironmentVariants: () => Promise; + private readonly openApiService: OpenApiService; constructor( @@ -86,6 +88,14 @@ class ConfigController extends Controller { maxAge: minutesToMilliseconds(10), }, ); + this.usesFeatureEnvironmentVariants = memoizee( + async () => + this.clientInstanceService.usesFeatureEnvironmentVariants(), + { + promise: true, + maxAge: minutesToMilliseconds(10), + }, + ); this.route({ method: 'get', @@ -134,11 +144,13 @@ class ConfigController extends Controller { simpleAuthSettings, maintenanceMode, usesOldEdge, + usesFeatureEnvironmentVariants, ] = await Promise.all([ this.frontendApiService.getFrontendSettings(false), this.settingService.get(simpleAuthSettingsKey), this.maintenanceService.isMaintenanceMode(), this.usesOldEdgeFunction(), + this.usesFeatureEnvironmentVariants(), ]); const disablePasswordAuth = @@ -155,6 +167,7 @@ class ConfigController extends Controller { displayUpgradeEdgeBanner: usesOldEdge || this.config.flagResolver.isEnabled('displayEdgeBanner'), + displayFeatureEnvironmentVariants: usesFeatureEnvironmentVariants, }; const response: UiConfigSchema = { diff --git a/src/lib/types/stores/feature-environment-store.ts b/src/lib/types/stores/feature-environment-store.ts index d423d2e07b..c127ec5ea4 100644 --- a/src/lib/types/stores/feature-environment-store.ts +++ b/src/lib/types/stores/feature-environment-store.ts @@ -86,4 +86,6 @@ export interface IFeatureEnvironmentStore ): Promise; clonePreviousVariants(environment: string, project: string): Promise; + + variantExists(): Promise; } diff --git a/src/test/e2e/api/admin/config.e2e.test.ts b/src/test/e2e/api/admin/config.e2e.test.ts index 9d4f6f3680..55cd3399d1 100644 --- a/src/test/e2e/api/admin/config.e2e.test.ts +++ b/src/test/e2e/api/admin/config.e2e.test.ts @@ -98,3 +98,12 @@ test('sets ui config with frontendSettings', async () => { expect(res.body.frontendApiOrigins).toEqual(frontendApiOrigins), ); }); + +test('ui config should have displayFeatureEnvironmentVariants flag disabled if no env variants are used', async () => { + const { body } = await app.request + .get('/api/admin/ui-config') + .expect('Content-Type', /json/) + .expect(200); + expect(body.flags).toBeTruthy(); + expect(body.flags.displayFeatureEnvironmentVariants).toEqual(false); +}); diff --git a/src/test/fixtures/fake-feature-environment-store.ts b/src/test/fixtures/fake-feature-environment-store.ts index 82ba2de6cb..6de94be24c 100644 --- a/src/test/fixtures/fake-feature-environment-store.ts +++ b/src/test/fixtures/fake-feature-environment-store.ts @@ -245,4 +245,12 @@ export default class FakeFeatureEnvironmentStore features.includes(featureEnv.featureName), ); } + + async variantExists() { + return this.featureEnvironments.some( + (featureEnvironment) => + featureEnvironment.variants && + featureEnvironment.variants.length > 0, + ); + } }