From e868c3291f18fc8c50d0587313cfb32b3cbbfaeb Mon Sep 17 00:00:00 2001 From: Mateusz Kwasniewski Date: Fri, 5 Apr 2024 15:34:08 +0200 Subject: [PATCH] feat: feature lifecycle metrics from event bus (#6789) --- .../feature-lifecycle-service.test.ts | 9 +++++++-- .../feature-lifecycle-service.ts | 11 +++++++++-- .../feature-lifecycle.e2e.test.ts | 19 +++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts index 7ee3154b44..612952659a 100644 --- a/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts +++ b/src/lib/features/feature-lifecycle/feature-lifecycle-service.test.ts @@ -7,20 +7,23 @@ import { type IUnleashConfig, } from '../../types'; import { createFakeFeatureLifecycleService } from './createFeatureLifecycle'; +import EventEmitter from 'events'; function ms(timeMs) { return new Promise((resolve) => setTimeout(resolve, timeMs)); } test('can insert and read lifecycle stages', async () => { + const eventBus = new EventEmitter(); const { featureLifecycleService, eventStore, environmentStore } = createFakeFeatureLifecycleService({ flagResolver: { isEnabled: () => true }, + eventBus, } as unknown as IUnleashConfig); const featureName = 'testFeature'; async function emitMetricsEvent(environment: string) { - await eventStore.emit(CLIENT_METRICS, { featureName, environment }); + await eventBus.emit(CLIENT_METRICS, { featureName, environment }); await ms(1); } @@ -68,9 +71,11 @@ test('can insert and read lifecycle stages', async () => { }); test('ignores lifecycle state updates when flag disabled', async () => { + const eventBus = new EventEmitter(); const { featureLifecycleService, eventStore, environmentStore } = createFakeFeatureLifecycleService({ flagResolver: { isEnabled: () => false }, + eventBus, } as unknown as IUnleashConfig); const featureName = 'testFeature'; @@ -82,7 +87,7 @@ test('ignores lifecycle state updates when flag disabled', async () => { await eventStore.emit(FEATURE_CREATED, { featureName }); await eventStore.emit(FEATURE_COMPLETED, { featureName }); - await eventStore.emit(CLIENT_METRICS, { + await eventBus.emit(CLIENT_METRICS, { featureName, environment: 'development', }); diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts b/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts index 54bc637675..41e433ef33 100644 --- a/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts +++ b/src/lib/features/feature-lifecycle/feature-lifecycle-service.ts @@ -12,6 +12,7 @@ import type { FeatureLifecycleView, IFeatureLifecycleStore, } from './feature-lifecycle-store-type'; +import type EventEmitter from 'events'; export class FeatureLifecycleService { private eventStore: IEventStore; @@ -22,6 +23,8 @@ export class FeatureLifecycleService { private flagResolver: IFlagResolver; + private eventBus: EventEmitter; + constructor( { eventStore, @@ -32,12 +35,16 @@ export class FeatureLifecycleService { environmentStore: IEnvironmentStore; featureLifecycleStore: IFeatureLifecycleStore; }, - { flagResolver }: Pick, + { + flagResolver, + eventBus, + }: Pick, ) { this.eventStore = eventStore; this.featureLifecycleStore = featureLifecycleStore; this.environmentStore = environmentStore; this.flagResolver = flagResolver; + this.eventBus = eventBus; } private async checkEnabled(fn: () => Promise) { @@ -53,7 +60,7 @@ export class FeatureLifecycleService { this.featureInitialized(event.featureName), ); }); - this.eventStore.on(CLIENT_METRICS, async (event) => { + this.eventBus.on(CLIENT_METRICS, async (event) => { await this.checkEnabled(() => this.featureReceivedMetrics( event.featureName, diff --git a/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts b/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts index eb275f1434..108c0e35ec 100644 --- a/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts +++ b/src/lib/features/feature-lifecycle/feature-lifecycle.e2e.test.ts @@ -4,9 +4,15 @@ import { setupAppWithAuth, } from '../../../test/e2e/helpers/test-helper'; import getLogger from '../../../test/fixtures/no-logger'; +import { + FEATURE_ARCHIVED, + FEATURE_CREATED, + type IEventStore, +} from '../../types'; let app: IUnleashTest; let db: ITestDb; +let eventStore: IEventStore; beforeAll(async () => { db = await dbInit('feature_lifecycle', getLogger); @@ -21,6 +27,7 @@ beforeAll(async () => { }, db.rawDatabase, ); + eventStore = db.stores.eventStore; await app.request .post(`/auth/demo/login`) @@ -43,10 +50,18 @@ const getFeatureLifecycle = async (featureName: string, expectedCode = 200) => { .expect(expectedCode); }; +function ms(timeMs) { + return new Promise((resolve) => setTimeout(resolve, timeMs)); +} + test('should return lifecycle stages', async () => { - await app.createFeature('my_feature_a'); + await eventStore.emit(FEATURE_CREATED, { featureName: 'my_feature_a' }); + await eventStore.emit(FEATURE_ARCHIVED, { featureName: 'my_feature_a' }); const { body } = await getFeatureLifecycle('my_feature_a'); - expect(body).toEqual([]); + expect(body).toEqual([ + { stage: 'initial', enteredStageAt: expect.any(String) }, + { stage: 'archived', enteredStageAt: expect.any(String) }, + ]); });