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:
parent
8ef59cd45d
commit
6a9a2c687d
@ -47,6 +47,7 @@ import { ProjectOwnersReadModel } from '../features/project/project-owners-read-
|
|||||||
import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
|
import { FeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store';
|
||||||
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
|
import { ProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model';
|
||||||
import { FeatureStrategiesReadModel } from '../features/feature-toggle/feature-strategies-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 = (
|
export const createStores = (
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
@ -161,6 +162,10 @@ export const createStores = (
|
|||||||
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db),
|
projectFlagCreatorsReadModel: new ProjectFlagCreatorsReadModel(db),
|
||||||
featureLifecycleStore: new FeatureLifecycleStore(db),
|
featureLifecycleStore: new FeatureLifecycleStore(db),
|
||||||
featureStrategiesReadModel: new FeatureStrategiesReadModel(db),
|
featureStrategiesReadModel: new FeatureStrategiesReadModel(db),
|
||||||
|
featureLifecycleReadModel: new FeatureLifecycleReadModel(
|
||||||
|
db,
|
||||||
|
config.flagResolver,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,15 +4,25 @@ import { FeatureLifecycleReadModel } from './feature-lifecycle-read-model';
|
|||||||
import type { IFeatureLifecycleStore } from './feature-lifecycle-store-type';
|
import type { IFeatureLifecycleStore } from './feature-lifecycle-store-type';
|
||||||
import type { IFeatureLifecycleReadModel } from './feature-lifecycle-read-model-type';
|
import type { IFeatureLifecycleReadModel } from './feature-lifecycle-read-model-type';
|
||||||
import type { IFeatureToggleStore } from '../feature-toggle/types/feature-toggle-store-type';
|
import type { IFeatureToggleStore } from '../feature-toggle/types/feature-toggle-store-type';
|
||||||
|
import type { IFlagResolver } from '../../types';
|
||||||
|
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
let featureLifeycycleReadModel: IFeatureLifecycleReadModel;
|
let featureLifeycycleReadModel: IFeatureLifecycleReadModel;
|
||||||
let featureLifecycleStore: IFeatureLifecycleStore;
|
let featureLifecycleStore: IFeatureLifecycleStore;
|
||||||
let featureToggleStore: IFeatureToggleStore;
|
let featureToggleStore: IFeatureToggleStore;
|
||||||
|
|
||||||
|
const alwaysOnFlagResolver = {
|
||||||
|
isEnabled() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
} as unknown as IFlagResolver;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('feature_lifecycle_read_model', getLogger);
|
db = await dbInit('feature_lifecycle_read_model', getLogger);
|
||||||
featureLifeycycleReadModel = new FeatureLifecycleReadModel(db.rawDatabase);
|
featureLifeycycleReadModel = new FeatureLifecycleReadModel(
|
||||||
|
db.rawDatabase,
|
||||||
|
alwaysOnFlagResolver,
|
||||||
|
);
|
||||||
featureLifecycleStore = db.stores.featureLifecycleStore;
|
featureLifecycleStore = db.stores.featureLifecycleStore;
|
||||||
featureToggleStore = db.stores.featureToggleStore;
|
featureToggleStore = db.stores.featureToggleStore;
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,11 @@ import type {
|
|||||||
StageCountByProject,
|
StageCountByProject,
|
||||||
} from './feature-lifecycle-read-model-type';
|
} from './feature-lifecycle-read-model-type';
|
||||||
import { getCurrentStage } from './get-current-stage';
|
import { getCurrentStage } from './get-current-stage';
|
||||||
import type { IFeatureLifecycleStage, StageName } from '../../types';
|
import type {
|
||||||
|
IFeatureLifecycleStage,
|
||||||
|
IFlagResolver,
|
||||||
|
StageName,
|
||||||
|
} from '../../types';
|
||||||
|
|
||||||
type DBType = {
|
type DBType = {
|
||||||
feature: string;
|
feature: string;
|
||||||
@ -17,11 +21,18 @@ type DBType = {
|
|||||||
export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
|
export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
|
||||||
private db: Db;
|
private db: Db;
|
||||||
|
|
||||||
constructor(db: Db) {
|
private flagResolver: IFlagResolver;
|
||||||
|
|
||||||
|
constructor(db: Db, flagResolver: IFlagResolver) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.flagResolver = flagResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getStageCount(): Promise<StageCount[]> {
|
async getStageCount(): Promise<StageCount[]> {
|
||||||
|
if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const { rows } = await this.db.raw(`
|
const { rows } = await this.db.raw(`
|
||||||
SELECT
|
SELECT
|
||||||
stage,
|
stage,
|
||||||
@ -47,6 +58,10 @@ export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getStageCountByProject(): Promise<StageCountByProject[]> {
|
async getStageCountByProject(): Promise<StageCountByProject[]> {
|
||||||
|
if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const { rows } = await this.db.raw(`
|
const { rows } = await this.db.raw(`
|
||||||
SELECT
|
SELECT
|
||||||
f.project,
|
f.project,
|
||||||
|
@ -46,7 +46,10 @@ beforeAll(async () => {
|
|||||||
eventStore = db.stores.eventStore;
|
eventStore = db.stores.eventStore;
|
||||||
eventBus = app.config.eventBus;
|
eventBus = app.config.eventBus;
|
||||||
featureLifecycleService = app.services.featureLifecycleService;
|
featureLifecycleService = app.services.featureLifecycleService;
|
||||||
featureLifecycleReadModel = new FeatureLifecycleReadModel(db.rawDatabase);
|
featureLifecycleReadModel = new FeatureLifecycleReadModel(
|
||||||
|
db.rawDatabase,
|
||||||
|
app.config.flagResolver,
|
||||||
|
);
|
||||||
featureLifecycleStore = db.stores.featureLifecycleStore;
|
featureLifecycleStore = db.stores.featureLifecycleStore;
|
||||||
|
|
||||||
await app.request
|
await app.request
|
||||||
|
@ -124,7 +124,10 @@ export const createFeatureToggleService = (
|
|||||||
|
|
||||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
|
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
|
||||||
|
|
||||||
const featureLifecycleReadModel = new FeatureLifecycleReadModel(db);
|
const featureLifecycleReadModel = new FeatureLifecycleReadModel(
|
||||||
|
db,
|
||||||
|
config.flagResolver,
|
||||||
|
);
|
||||||
|
|
||||||
const dependentFeaturesService = createDependentFeaturesService(config)(db);
|
const dependentFeaturesService = createDependentFeaturesService(config)(db);
|
||||||
|
|
||||||
|
@ -285,6 +285,12 @@ export default class MetricsMonitor {
|
|||||||
help: 'Duration of feature lifecycle stages',
|
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({
|
const projectEnvironmentsDisabled = createCounter({
|
||||||
name: 'project_environments_disabled',
|
name: 'project_environments_disabled',
|
||||||
help: 'How many "environment disabled" events we have received for each project',
|
help: 'How many "environment disabled" events we have received for each project',
|
||||||
@ -299,11 +305,13 @@ export default class MetricsMonitor {
|
|||||||
maxEnvironmentStrategies,
|
maxEnvironmentStrategies,
|
||||||
maxConstraintValuesResult,
|
maxConstraintValuesResult,
|
||||||
maxConstraintsPerStrategyResult,
|
maxConstraintsPerStrategyResult,
|
||||||
|
stageCountByProjectResult,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
|
stores.featureStrategiesReadModel.getMaxFeatureStrategies(),
|
||||||
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
|
stores.featureStrategiesReadModel.getMaxFeatureEnvironmentStrategies(),
|
||||||
stores.featureStrategiesReadModel.getMaxConstraintValues(),
|
stores.featureStrategiesReadModel.getMaxConstraintValues(),
|
||||||
stores.featureStrategiesReadModel.getMaxConstraintsPerStrategy(),
|
stores.featureStrategiesReadModel.getMaxConstraintsPerStrategy(),
|
||||||
|
stores.featureLifecycleReadModel.getStageCountByProject(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
featureFlagsTotal.reset();
|
featureFlagsTotal.reset();
|
||||||
@ -327,6 +335,16 @@ export default class MetricsMonitor {
|
|||||||
.observe(stage.duration);
|
.observe(stage.duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stageCountByProject.reset();
|
||||||
|
stageCountByProjectResult.forEach((stageResult) =>
|
||||||
|
stageCountByProject
|
||||||
|
.labels({
|
||||||
|
project_id: stageResult.project,
|
||||||
|
stage: stageResult.stage,
|
||||||
|
})
|
||||||
|
.set(stageResult.count),
|
||||||
|
);
|
||||||
|
|
||||||
apiTokens.reset();
|
apiTokens.reset();
|
||||||
|
|
||||||
for (const [type, value] of stats.apiTokens) {
|
for (const [type, value] of stats.apiTokens) {
|
||||||
|
@ -160,7 +160,7 @@ export const createServices = (
|
|||||||
? new DependentFeaturesReadModel(db)
|
? new DependentFeaturesReadModel(db)
|
||||||
: new FakeDependentFeaturesReadModel();
|
: new FakeDependentFeaturesReadModel();
|
||||||
const featureLifecycleReadModel = db
|
const featureLifecycleReadModel = db
|
||||||
? new FeatureLifecycleReadModel(db)
|
? new FeatureLifecycleReadModel(db, config.flagResolver)
|
||||||
: new FakeFeatureLifecycleReadModel();
|
: new FakeFeatureLifecycleReadModel();
|
||||||
const segmentReadModel = db
|
const segmentReadModel = db
|
||||||
? new SegmentReadModel(db)
|
? new SegmentReadModel(db)
|
||||||
|
@ -44,6 +44,7 @@ import { IProjectOwnersReadModel } from '../features/project/project-owners-read
|
|||||||
import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type';
|
import { IFeatureLifecycleStore } from '../features/feature-lifecycle/feature-lifecycle-store-type';
|
||||||
import { IProjectFlagCreatorsReadModel } from '../features/project/project-flag-creators-read-model.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 { 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 {
|
export interface IUnleashStores {
|
||||||
accessStore: IAccessStore;
|
accessStore: IAccessStore;
|
||||||
@ -92,6 +93,7 @@ export interface IUnleashStores {
|
|||||||
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
|
projectFlagCreatorsReadModel: IProjectFlagCreatorsReadModel;
|
||||||
featureLifecycleStore: IFeatureLifecycleStore;
|
featureLifecycleStore: IFeatureLifecycleStore;
|
||||||
featureStrategiesReadModel: IFeatureStrategiesReadModel;
|
featureStrategiesReadModel: IFeatureStrategiesReadModel;
|
||||||
|
featureLifecycleReadModel: IFeatureLifecycleReadModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -139,4 +141,5 @@ export {
|
|||||||
IFeatureLifecycleStore,
|
IFeatureLifecycleStore,
|
||||||
IProjectFlagCreatorsReadModel,
|
IProjectFlagCreatorsReadModel,
|
||||||
IFeatureStrategiesReadModel,
|
IFeatureStrategiesReadModel,
|
||||||
|
IFeatureLifecycleReadModel,
|
||||||
};
|
};
|
||||||
|
2
src/test/fixtures/store.ts
vendored
2
src/test/fixtures/store.ts
vendored
@ -47,6 +47,7 @@ import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-proj
|
|||||||
import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
|
import { FakeFeatureLifecycleStore } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-store';
|
||||||
import { FakeProjectFlagCreatorsReadModel } from '../../lib/features/project/fake-project-flag-creators-read-model';
|
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 { 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 = {
|
const db = {
|
||||||
select: () => ({
|
select: () => ({
|
||||||
@ -103,6 +104,7 @@ const createStores: () => IUnleashStores = () => {
|
|||||||
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
|
projectFlagCreatorsReadModel: new FakeProjectFlagCreatorsReadModel(),
|
||||||
featureLifecycleStore: new FakeFeatureLifecycleStore(),
|
featureLifecycleStore: new FakeFeatureLifecycleStore(),
|
||||||
featureStrategiesReadModel: new FakeFeatureStrategiesReadModel(),
|
featureStrategiesReadModel: new FakeFeatureStrategiesReadModel(),
|
||||||
|
featureLifecycleReadModel: new FakeFeatureLifecycleReadModel(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user