1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-31 13:47:02 +02:00

feat: count created feature links with impact metrics

This commit is contained in:
kwasniew 2025-06-24 10:57:51 +02:00
parent 4289a1c105
commit 783270599b
No known key found for this signature in database
GPG Key ID: 43A7CBC24C119560
6 changed files with 75 additions and 12 deletions

View File

@ -6,13 +6,21 @@ import {
NotFoundError, NotFoundError,
OperationDeniedError, OperationDeniedError,
} from '../../error/index.js'; } from '../../error/index.js';
import { fakeImpactMetricsResolver } from '../../../test/fixtures/fake-impact-metrics.js';
test('create, update and delete feature link', async () => { test('create, update and delete feature link', async () => {
const flagResolver = { impactMetrics: fakeImpactMetricsResolver() };
const { featureLinkStore, featureLinkService } = const { featureLinkStore, featureLinkService } =
createFakeFeatureLinkService({ createFakeFeatureLinkService({
getLogger, getLogger,
flagResolver,
} as unknown as IUnleashConfig); } as unknown as IUnleashConfig);
flagResolver.impactMetrics.defineCounter(
'feature_link_count',
'Count of feature links',
);
const link = await featureLinkService.createLink( const link = await featureLinkService.createLink(
'default', 'default',
{ {
@ -29,6 +37,10 @@ test('create, update and delete feature link', async () => {
domain: 'example', domain: 'example',
}); });
expect(
flagResolver.impactMetrics.counters.get('feature_link_count')!.value,
).toBe(1);
const newLink = await featureLinkService.updateLink( const newLink = await featureLinkService.updateLink(
{ projectId: 'default', linkId: link.id }, { 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 () => { test('cannot delete/update non existent link', async () => {
const flagResolver = { impactMetrics: fakeImpactMetricsResolver() };
const { featureLinkService } = createFakeFeatureLinkService({ const { featureLinkService } = createFakeFeatureLinkService({
getLogger, getLogger,
flagResolver,
} as unknown as IUnleashConfig); } as unknown as IUnleashConfig);
await expect( await expect(
@ -77,8 +91,10 @@ test('cannot delete/update non existent link', async () => {
}); });
test('cannot create/update invalid link', async () => { test('cannot create/update invalid link', async () => {
const flagResolver = { impactMetrics: fakeImpactMetricsResolver() };
const { featureLinkService } = createFakeFeatureLinkService({ const { featureLinkService } = createFakeFeatureLinkService({
getLogger, getLogger,
flagResolver,
} as unknown as IUnleashConfig); } as unknown as IUnleashConfig);
await expect( await expect(
@ -107,8 +123,10 @@ test('cannot create/update invalid link', async () => {
}); });
test('cannot exceed allowed link count', async () => { test('cannot exceed allowed link count', async () => {
const flagResolver = { impactMetrics: fakeImpactMetricsResolver() };
const { featureLinkService } = createFakeFeatureLinkService({ const { featureLinkService } = createFakeFeatureLinkService({
getLogger, getLogger,
flagResolver,
} as unknown as IUnleashConfig); } as unknown as IUnleashConfig);
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {

View File

@ -4,6 +4,7 @@ import {
FeatureLinkRemovedEvent, FeatureLinkRemovedEvent,
FeatureLinkUpdatedEvent, FeatureLinkUpdatedEvent,
type IAuditUser, type IAuditUser,
type IFlagResolver,
type IUnleashConfig, type IUnleashConfig,
} from '../../types/index.js'; } from '../../types/index.js';
import type { import type {
@ -18,6 +19,7 @@ import {
} from '../../error/index.js'; } from '../../error/index.js';
import normalizeUrl from 'normalize-url'; import normalizeUrl from 'normalize-url';
import { parse } from 'tldts'; import { parse } from 'tldts';
import { FEAUTRE_LINK_COUNT } from '../metrics/impact/define-impact-metrics.js';
interface IFeatureLinkStoreObj { interface IFeatureLinkStoreObj {
featureLinkStore: IFeatureLinkStore; featureLinkStore: IFeatureLinkStore;
@ -27,15 +29,20 @@ export default class FeatureLinkService {
private logger: Logger; private logger: Logger;
private featureLinkStore: IFeatureLinkStore; private featureLinkStore: IFeatureLinkStore;
private eventService: EventService; private eventService: EventService;
private flagResolver: IFlagResolver;
constructor( constructor(
stores: IFeatureLinkStoreObj, stores: IFeatureLinkStoreObj,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, {
getLogger,
flagResolver,
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
eventService: EventService, eventService: EventService,
) { ) {
this.logger = getLogger('feature-links/feature-link-service.ts'); this.logger = getLogger('feature-links/feature-link-service.ts');
this.featureLinkStore = stores.featureLinkStore; this.featureLinkStore = stores.featureLinkStore;
this.eventService = eventService; this.eventService = eventService;
this.flagResolver = flagResolver;
} }
async getAll(): Promise<IFeatureLink[]> { async getAll(): Promise<IFeatureLink[]> {
@ -72,6 +79,8 @@ export default class FeatureLinkService {
domain: domainWithoutSuffix, domain: domainWithoutSuffix,
}); });
this.flagResolver.impactMetrics?.incrementCounter(FEAUTRE_LINK_COUNT);
await this.eventService.storeEvent( await this.eventService.storeEvent(
new FeatureLinkAddedEvent({ new FeatureLinkAddedEvent({
featureName: newLink.featureName, featureName: newLink.featureName,

View File

@ -19,17 +19,7 @@ let featureLinkReadModel: IFeatureLinksReadModel;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_link', getLogger); db = await dbInit('feature_link', getLogger);
app = await setupAppWithAuth( app = await setupAppWithAuth(db.stores, {}, db.rawDatabase);
db.stores,
{
experimental: {
flags: {
featureLinks: true,
},
},
},
db.rawDatabase,
);
eventStore = db.stores.eventStore; eventStore = db.stores.eventStore;
featureLinkStore = db.stores.featureLinkStore; featureLinkStore = db.stores.featureLinkStore;
featureLinkReadModel = new FeatureLinksReadModel( featureLinkReadModel = new FeatureLinksReadModel(

View File

@ -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',
);
};

View File

@ -183,6 +183,7 @@ import { testDbPrefix } from '../test/e2e/helpers/database-init.js';
import type { RequestHandler } from 'express'; import type { RequestHandler } from 'express';
import { UPDATE_REVISION } from './features/feature-toggle/configuration-revision-service.js'; import { UPDATE_REVISION } from './features/feature-toggle/configuration-revision-service.js';
import type { IFeatureUsageInfo } from './services/version-service.js'; import type { IFeatureUsageInfo } from './services/version-service.js';
import { defineImpactMetrics } from './features/metrics/impact/define-impact-metrics.js';
export async function initialServiceSetup( export async function initialServiceSetup(
{ authentication }: Pick<IUnleashConfig, 'authentication'>, { authentication }: Pick<IUnleashConfig, 'authentication'>,
@ -232,6 +233,7 @@ export async function createApp(
scheduleServices(services, config); scheduleServices(services, config);
} }
defineImpactMetrics(config.flagResolver);
const metricsMonitor = fm.createMetricsMonitor(); const metricsMonitor = fm.createMetricsMonitor();
const unleashSession = fm.createSessionDb(config, db); const unleashSession = fm.createSessionDb(config, db);

View File

@ -0,0 +1,34 @@
export const fakeImpactMetricsResolver = () => ({
counters: new Map<string, { value: number; help: string }>(),
gauges: new Map<string, { value: number; help: string }>(),
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);
},
});