diff --git a/src/lib/features/feature-links/feature-link-service.test.ts b/src/lib/features/feature-links/feature-link-service.test.ts index c7d1728299..277cfe992d 100644 --- a/src/lib/features/feature-links/feature-link-service.test.ts +++ b/src/lib/features/feature-links/feature-link-service.test.ts @@ -6,13 +6,21 @@ import { NotFoundError, OperationDeniedError, } from '../../error/index.js'; +import { fakeImpactMetricsResolver } from '../../../test/fixtures/fake-impact-metrics.js'; test('create, update and delete feature link', async () => { + const flagResolver = { impactMetrics: fakeImpactMetricsResolver() }; const { featureLinkStore, featureLinkService } = createFakeFeatureLinkService({ getLogger, + flagResolver, } as unknown as IUnleashConfig); + flagResolver.impactMetrics.defineCounter( + 'feature_link_count', + 'Count of feature links', + ); + const link = await featureLinkService.createLink( 'default', { @@ -29,6 +37,10 @@ test('create, update and delete feature link', async () => { domain: 'example', }); + expect( + flagResolver.impactMetrics.counters.get('feature_link_count')!.value, + ).toBe(1); + const newLink = await featureLinkService.updateLink( { projectId: 'default', linkId: link.id }, { @@ -53,8 +65,10 @@ test('create, update and delete feature link', async () => { }); test('cannot delete/update non existent link', async () => { + const flagResolver = { impactMetrics: fakeImpactMetricsResolver() }; const { featureLinkService } = createFakeFeatureLinkService({ getLogger, + flagResolver, } as unknown as IUnleashConfig); await expect( @@ -77,8 +91,10 @@ test('cannot delete/update non existent link', async () => { }); test('cannot create/update invalid link', async () => { + const flagResolver = { impactMetrics: fakeImpactMetricsResolver() }; const { featureLinkService } = createFakeFeatureLinkService({ getLogger, + flagResolver, } as unknown as IUnleashConfig); await expect( @@ -107,8 +123,10 @@ test('cannot create/update invalid link', async () => { }); test('cannot exceed allowed link count', async () => { + const flagResolver = { impactMetrics: fakeImpactMetricsResolver() }; const { featureLinkService } = createFakeFeatureLinkService({ getLogger, + flagResolver, } as unknown as IUnleashConfig); for (let i = 0; i < 10; i++) { diff --git a/src/lib/features/feature-links/feature-link-service.ts b/src/lib/features/feature-links/feature-link-service.ts index ad9d1715b6..3be6043eb6 100644 --- a/src/lib/features/feature-links/feature-link-service.ts +++ b/src/lib/features/feature-links/feature-link-service.ts @@ -4,6 +4,7 @@ import { FeatureLinkRemovedEvent, FeatureLinkUpdatedEvent, type IAuditUser, + type IFlagResolver, type IUnleashConfig, } from '../../types/index.js'; import type { @@ -18,6 +19,7 @@ import { } from '../../error/index.js'; import normalizeUrl from 'normalize-url'; import { parse } from 'tldts'; +import { FEAUTRE_LINK_COUNT } from '../metrics/impact/define-impact-metrics.js'; interface IFeatureLinkStoreObj { featureLinkStore: IFeatureLinkStore; @@ -27,15 +29,20 @@ export default class FeatureLinkService { private logger: Logger; private featureLinkStore: IFeatureLinkStore; private eventService: EventService; + private flagResolver: IFlagResolver; constructor( stores: IFeatureLinkStoreObj, - { getLogger }: Pick, + { + getLogger, + flagResolver, + }: Pick, eventService: EventService, ) { this.logger = getLogger('feature-links/feature-link-service.ts'); this.featureLinkStore = stores.featureLinkStore; this.eventService = eventService; + this.flagResolver = flagResolver; } async getAll(): Promise { @@ -72,6 +79,8 @@ export default class FeatureLinkService { domain: domainWithoutSuffix, }); + this.flagResolver.impactMetrics?.incrementCounter(FEAUTRE_LINK_COUNT); + await this.eventService.storeEvent( new FeatureLinkAddedEvent({ featureName: newLink.featureName, diff --git a/src/lib/features/feature-links/feature-link.e2e.test.ts b/src/lib/features/feature-links/feature-link.e2e.test.ts index 83a1a3c277..2a11b58c75 100644 --- a/src/lib/features/feature-links/feature-link.e2e.test.ts +++ b/src/lib/features/feature-links/feature-link.e2e.test.ts @@ -19,17 +19,7 @@ let featureLinkReadModel: IFeatureLinksReadModel; beforeAll(async () => { db = await dbInit('feature_link', getLogger); - app = await setupAppWithAuth( - db.stores, - { - experimental: { - flags: { - featureLinks: true, - }, - }, - }, - db.rawDatabase, - ); + app = await setupAppWithAuth(db.stores, {}, db.rawDatabase); eventStore = db.stores.eventStore; featureLinkStore = db.stores.featureLinkStore; featureLinkReadModel = new FeatureLinksReadModel( diff --git a/src/lib/features/metrics/impact/define-impact-metrics.ts b/src/lib/features/metrics/impact/define-impact-metrics.ts new file mode 100644 index 0000000000..68824fbf09 --- /dev/null +++ b/src/lib/features/metrics/impact/define-impact-metrics.ts @@ -0,0 +1,10 @@ +import type { IFlagResolver } from '../../../types/index.js'; + +export const FEAUTRE_LINK_COUNT = 'feature_link_count'; + +export const defineImpactMetrics = (flagResolver: IFlagResolver) => { + flagResolver.impactMetrics?.defineCounter( + FEAUTRE_LINK_COUNT, + 'Count of feature links', + ); +}; diff --git a/src/lib/server-impl.ts b/src/lib/server-impl.ts index 21e2cef94a..48999593a8 100644 --- a/src/lib/server-impl.ts +++ b/src/lib/server-impl.ts @@ -183,6 +183,7 @@ import { testDbPrefix } from '../test/e2e/helpers/database-init.js'; import type { RequestHandler } from 'express'; import { UPDATE_REVISION } from './features/feature-toggle/configuration-revision-service.js'; import type { IFeatureUsageInfo } from './services/version-service.js'; +import { defineImpactMetrics } from './features/metrics/impact/define-impact-metrics.js'; export async function initialServiceSetup( { authentication }: Pick, @@ -232,6 +233,7 @@ export async function createApp( scheduleServices(services, config); } + defineImpactMetrics(config.flagResolver); const metricsMonitor = fm.createMetricsMonitor(); const unleashSession = fm.createSessionDb(config, db); diff --git a/src/test/fixtures/fake-impact-metrics.ts b/src/test/fixtures/fake-impact-metrics.ts new file mode 100644 index 0000000000..440e79554d --- /dev/null +++ b/src/test/fixtures/fake-impact-metrics.ts @@ -0,0 +1,34 @@ +export const fakeImpactMetricsResolver = () => ({ + counters: new Map(), + gauges: new Map(), + + defineCounter(name: string, help: string) { + this.counters.set(name, { value: 0, help }); + }, + + defineGauge(name: string, help: string) { + this.gauges.set(name, { value: 0, help }); + }, + + incrementCounter(name: string, value: number = 1) { + const counter = this.counters.get(name); + + if (!counter) { + return; + } + + counter.value += value; + this.counters.set(name, counter); + }, + + updateGauge(name: string, value: number) { + const gauge = this.gauges.get(name); + + if (!gauge) { + return; + } + + gauge.value = value; + this.gauges.set(name, gauge); + }, +});