1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-09 00:18:00 +01:00

feat: stage count by project metric (#7441)

This commit is contained in:
Mateusz Kwasniewski 2024-06-25 09:54:26 +02:00 committed by GitHub
parent 8ef59cd45d
commit 6a9a2c687d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 65 additions and 6 deletions

View File

@ -47,6 +47,7 @@ import { ProjectOwnersReadModel } from '../features/project/project-owners-read-
import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
import { FeatureStrategiesReadModel } from '../features/feature-toggle/feature-strategies-read-model';
import { FeatureLifecycleReadModel } from '../features/feature-lifecycle/feature-lifecycle-read-model';
export const createStores = (
config: IUnleashConfig,
@ -161,6 +162,10 @@ export const createStores = (
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db),
featureLifecycleStore: new FeatureLifecycleStore(db),
featureStrategiesReadModel: new FeatureStrategiesReadModel(db),
featureLifecycleReadModel: new FeatureLifecycleReadModel(
db,
config.flagResolver,
),
};
};

View File

@ -4,15 +4,25 @@ import { FeatureLifecycleReadModel } from './feature-lifecycle-read-model';
import type { IFeatureLifecycleStore } from './feature-lifecycle-store-type';
import type { IFeatureLifecycleReadModel } from './feature-lifecycle-read-model-type';
import type { IFeatureToggleStore } from '../feature-toggle/types/feature-toggle-store-type';
import type { IFlagResolver } from '../../types';
let db: ITestDb;
let featureLifeycycleReadModel: IFeatureLifecycleReadModel;
let featureLifecycleStore: IFeatureLifecycleStore;
let featureToggleStore: IFeatureToggleStore;
const alwaysOnFlagResolver = {
isEnabled() {
return true;
},
} as unknown as IFlagResolver;
beforeAll(async () => {
db = await dbInit('feature_lifecycle_read_model', getLogger);
featureLifeycycleReadModel = new FeatureLifecycleReadModel(db.rawDatabase);
featureLifeycycleReadModel = new FeatureLifecycleReadModel(
db.rawDatabase,
alwaysOnFlagResolver,
);
featureLifecycleStore = db.stores.featureLifecycleStore;
featureToggleStore = db.stores.featureToggleStore;
});

View File

@ -5,7 +5,11 @@ import type {
StageCountByProject,
} from './feature-lifecycle-read-model-type';
import { getCurrentStage } from './get-current-stage';
import type { IFeatureLifecycleStage, StageName } from '../../types';
import type {
IFeatureLifecycleStage,
IFlagResolver,
StageName,
} from '../../types';
type DBType = {
feature: string;
@ -17,11 +21,18 @@ type DBType = {
export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
private db: Db;
constructor(db: Db) {
private flagResolver: IFlagResolver;
constructor(db: Db, flagResolver: IFlagResolver) {
this.db = db;
this.flagResolver = flagResolver;
}
async getStageCount(): Promise<StageCount[]> {
if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
return [];
}
const { rows } = await this.db.raw(`
SELECT
stage,
@ -47,6 +58,10 @@ export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
}
async getStageCountByProject(): Promise<StageCountByProject[]> {
if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
return [];
}
const { rows } = await this.db.raw(`
SELECT
f.project,

View File

@ -46,7 +46,10 @@ beforeAll(async () => {
eventStore = db.stores.eventStore;
eventBus = app.config.eventBus;
featureLifecycleService = app.services.featureLifecycleService;
featureLifecycleReadModel = new FeatureLifecycleReadModel(db.rawDatabase);
featureLifecycleReadModel = new FeatureLifecycleReadModel(
db.rawDatabase,
app.config.flagResolver,
);
featureLifecycleStore = db.stores.featureLifecycleStore;
await app.request

View File

@ -124,7 +124,10 @@ export const createFeatureToggleService = (
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
const featureLifecycleReadModel = new FeatureLifecycleReadModel(db);
const featureLifecycleReadModel = new FeatureLifecycleReadModel(
db,
config.flagResolver,
);
const dependentFeaturesService = createDependentFeaturesService(config)(db);

View File

@ -285,6 +285,12 @@ export default class MetricsMonitor {
help: 'Duration of feature lifecycle stages',
});
const stageCountByProject = createGauge({
name: 'stage_count_by_project',
help: 'Count features in a given stage by project id',
labelNames: ['stage', 'project_id'],
});
const projectEnvironmentsDisabled = createCounter({
name: 'project_environments_disabled',
help: 'How many "environment disabled" events we have received for each project',
@ -299,11 +305,13 @@ export default class MetricsMonitor {
maxEnvironmentStrategies,
maxConstraintValuesResult,
maxConstraintsPerStrategyResult,
stageCountByProjectResult,
] = await Promise.all([
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
stores.featureStrategiesReadModel.getMaxConstraintValues(),
stores.featureStrategiesReadModel.getMaxConstraintsPerStrategy(),
stores.featureLifecycleReadModel.getStageCountByProject(),
]);
featureFlagsTotal.reset();
@ -327,6 +335,16 @@ export default class MetricsMonitor {
.observe(stage.duration);
});
stageCountByProject.reset();
stageCountByProjectResult.forEach((stageResult) =>
stageCountByProject
.labels({
project_id: stageResult.project,
stage: stageResult.stage,
})
.set(stageResult.count),
);
apiTokens.reset();
for (const [type, value] of stats.apiTokens) {

View File

@ -160,7 +160,7 @@ export const createServices = (
? new DependentFeaturesReadModel(db)
: new FakeDependentFeaturesReadModel();
const featureLifecycleReadModel = db
? new FeatureLifecycleReadModel(db)
? new FeatureLifecycleReadModel(db, config.flagResolver)
: new FakeFeatureLifecycleReadModel();
const segmentReadModel = db
? new SegmentReadModel(db)

View File

@ -44,6 +44,7 @@ import { IProjectOwnersReadModel } from '../features/project/project-owners-read
import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type';
import { IProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model.type';
import { IFeatureStrategiesReadModel } from '../features/feature-toggle/types/feature-strategies-read-model-type';
import { IFeatureLifecycleReadModel } from '../features/feature-lifecycle/feature-lifecycle-read-model-type';
export interface IUnleashStores {
accessStore: IAccessStore;
@ -92,6 +93,7 @@ export interface IUnleashStores {
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
featureLifecycleStore: IFeatureLifecycleStore;
featureStrategiesReadModel: IFeatureStrategiesReadModel;
featureLifecycleReadModel: IFeatureLifecycleReadModel;
}
export {
@ -139,4 +141,5 @@ export {
IFeatureLifecycleStore,
IProjectFlagCreatorsReadModel,
IFeatureStrategiesReadModel,
IFeatureLifecycleReadModel,
};

View File

@ -47,6 +47,7 @@ import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-proj
import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
import { FakeProjectFlagCreatorsReadModel } from '../../lib/features/project/fake-project-flag-creators-read-model';
import { FakeFeatureStrategiesReadModel } from '../../lib/features/feature-toggle/fake-feature-strategies-read-model';
import { FakeFeatureLifecycleReadModel } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-read-model';
const db = {
select: () => ({
@ -103,6 +104,7 @@ const createStores: () => IUnleashStores = () => {
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
featureLifecycleStore: new FakeFeatureLifecycleStore(),
featureStrategiesReadModel: new FakeFeatureStrategiesReadModel(),
featureLifecycleReadModel: new FakeFeatureLifecycleReadModel(),
};
};