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

feat: report feature links by domain (#9936)

This commit is contained in:
Mateusz Kwasniewski 2025-05-09 09:39:15 +02:00 committed by GitHub
parent 857ee7da5c
commit 43efaf7c47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 47 additions and 2 deletions

View File

@ -67,6 +67,7 @@ import { UniqueConnectionStore } from '../features/unique-connection/unique-conn
import { UniqueConnectionReadModel } from '../features/unique-connection/unique-connection-read-model';
import { FeatureLinkStore } from '../features/feature-links/feature-link-store';
import { UnknownFlagsStore } from '../features/metrics/unknown-flags/unknown-flags-store';
import { FeatureLinksReadModel } from '../features/feature-links/feature-links-read-model';
export const createStores = (
config: IUnleashConfig,
@ -205,6 +206,7 @@ export const createStores = (
new ReleasePlanMilestoneStrategyStore(db, config),
featureLinkStore: new FeatureLinkStore(db, config),
unknownFlagsStore: new UnknownFlagsStore(db),
featureLinkReadModel: new FeatureLinksReadModel(db, eventBus),
};
};

View File

@ -154,6 +154,11 @@ test('should manage feature links', async () => {
domain: 'example_another',
},
]);
const topDomainsMemoized = await featureLinkReadModel.getTopDomains();
expect(topDomainsMemoized).toMatchObject([
{ domain: 'example_another', count: 1 },
{ domain: 'example', count: 1 },
]);
const [event1, event2, event3] = await eventStore.getEvents();
expect([event1, event2, event3]).toMatchObject([

View File

@ -6,10 +6,15 @@ import type {
import metricsHelper from '../../util/metrics-helper';
import { DB_TIME } from '../../metric-events';
import type EventEmitter from 'events';
import memoizee from 'memoizee';
import { hoursToMilliseconds } from 'date-fns';
export class FeatureLinksReadModel implements IFeatureLinksReadModel {
private db: Db;
private timer: Function;
private _getTopDomainsMemoized: () => Promise<
{ domain: string; count: number }[]
>;
constructor(db: Db, eventBus: EventEmitter) {
this.db = db;
@ -18,9 +23,18 @@ export class FeatureLinksReadModel implements IFeatureLinksReadModel {
store: 'feature_links',
action,
});
this._getTopDomainsMemoized = memoizee(this._getTopDomains.bind(this), {
promise: true,
maxAge: hoursToMilliseconds(1),
});
}
async getTopDomains(): Promise<{ domain: string; count: number }[]> {
public getTopDomains(): Promise<{ domain: string; count: number }[]> {
return this._getTopDomainsMemoized();
}
async _getTopDomains(): Promise<{ domain: string; count: number }[]> {
const stopTimer = this.timer('getTopDomains');
const topDomains = await this.db
.from('feature_link')
@ -29,7 +43,7 @@ export class FeatureLinksReadModel implements IFeatureLinksReadModel {
.whereNotNull('domain')
.groupBy('domain')
.orderBy('count', 'desc')
.limit(20);
.limit(3);
stopTimer();
return topDomains.map(({ domain, count }) => ({

View File

@ -656,6 +656,25 @@ export function registerPrometheusMetrics(
})),
});
dbMetrics.registerGaugeDbMetric({
name: 'feature_link_by_domain',
help: 'Count most popular domains used in feature links',
labelNames: ['domain'],
query: () => {
if (flagResolver.isEnabled('featureLinks')) {
return stores.featureLinkReadModel.getTopDomains();
}
return Promise.resolve([]);
},
map: (result) =>
result.map(({ domain, count }) => ({
value: count,
labels: {
domain,
},
})),
});
const featureLifecycleStageEnteredCounter = createCounter({
name: 'feature_lifecycle_stage_entered',
help: 'Count how many features entered a given stage',

View File

@ -61,6 +61,7 @@ import { ReleasePlanMilestoneStore } from '../features/release-plans/release-pla
import { ReleasePlanMilestoneStrategyStore } from '../features/release-plans/release-plan-milestone-strategy-store';
import type { IFeatureLinkStore } from '../features/feature-links/feature-link-store-type';
import { IUnknownFlagsStore } from '../features/metrics/unknown-flags/unknown-flags-store';
import { IFeatureLinksReadModel } from '../features/feature-links/feature-links-read-model-type';
export interface IUnleashStores {
accessStore: IAccessStore;
@ -126,6 +127,7 @@ export interface IUnleashStores {
releasePlanMilestoneStrategyStore: ReleasePlanMilestoneStrategyStore;
featureLinkStore: IFeatureLinkStore;
unknownFlagsStore: IUnknownFlagsStore;
featureLinkReadModel: IFeatureLinksReadModel;
}
export {
@ -189,4 +191,5 @@ export {
ReleasePlanMilestoneStrategyStore,
type IFeatureLinkStore,
IUnknownFlagsStore,
IFeatureLinksReadModel,
};

View File

@ -64,6 +64,7 @@ import { FakeUniqueConnectionStore } from '../../lib/features/unique-connection/
import { UniqueConnectionReadModel } from '../../lib/features/unique-connection/unique-connection-read-model';
import FakeFeatureLinkStore from '../../lib/features/feature-links/fake-feature-link-store';
import { FakeUnknownFlagsStore } from '../../lib/features/metrics/unknown-flags/fake-unknown-flags-store';
import { FakeFeatureLinksReadModel } from '../../lib/features/feature-links/fake-feature-links-read-model';
const db = {
select: () => ({
@ -143,6 +144,7 @@ const createStores: () => IUnleashStores = () => {
{} as ReleasePlanMilestoneStrategyStore,
featureLinkStore: new FakeFeatureLinkStore(),
unknownFlagsStore,
featureLinkReadModel: new FakeFeatureLinksReadModel(),
};
};