mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	refactor: lifecycle stage duration outside instance stats (#7442)
This commit is contained in:
		
							parent
							
								
									6a9a2c687d
								
							
						
					
					
						commit
						c3fa468a9d
					
				@ -3,11 +3,17 @@ import type {
 | 
				
			|||||||
    StageCount,
 | 
					    StageCount,
 | 
				
			||||||
    StageCountByProject,
 | 
					    StageCountByProject,
 | 
				
			||||||
} from './feature-lifecycle-read-model-type';
 | 
					} from './feature-lifecycle-read-model-type';
 | 
				
			||||||
import type { IFeatureLifecycleStage } from '../../types';
 | 
					import type {
 | 
				
			||||||
 | 
					    IFeatureLifecycleStage,
 | 
				
			||||||
 | 
					    IProjectLifecycleStageDuration,
 | 
				
			||||||
 | 
					} from '../../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FakeFeatureLifecycleReadModel
 | 
					export class FakeFeatureLifecycleReadModel
 | 
				
			||||||
    implements IFeatureLifecycleReadModel
 | 
					    implements IFeatureLifecycleReadModel
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    getAllWithStageDuration(): Promise<IProjectLifecycleStageDuration[]> {
 | 
				
			||||||
 | 
					        return Promise.resolve([]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    getStageCount(): Promise<StageCount[]> {
 | 
					    getStageCount(): Promise<StageCount[]> {
 | 
				
			||||||
        return Promise.resolve([]);
 | 
					        return Promise.resolve([]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ import type {
 | 
				
			|||||||
    FeatureLifecycleStage,
 | 
					    FeatureLifecycleStage,
 | 
				
			||||||
    IFeatureLifecycleStore,
 | 
					    IFeatureLifecycleStore,
 | 
				
			||||||
    FeatureLifecycleView,
 | 
					    FeatureLifecycleView,
 | 
				
			||||||
    FeatureLifecycleProjectItem,
 | 
					 | 
				
			||||||
} from './feature-lifecycle-store-type';
 | 
					} from './feature-lifecycle-store-type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FakeFeatureLifecycleStore implements IFeatureLifecycleStore {
 | 
					export class FakeFeatureLifecycleStore implements IFeatureLifecycleStore {
 | 
				
			||||||
@ -41,18 +40,6 @@ export class FakeFeatureLifecycleStore implements IFeatureLifecycleStore {
 | 
				
			|||||||
        return this.lifecycles[feature] || [];
 | 
					        return this.lifecycles[feature] || [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAll(): Promise<FeatureLifecycleProjectItem[]> {
 | 
					 | 
				
			||||||
        const result = Object.entries(this.lifecycles).flatMap(
 | 
					 | 
				
			||||||
            ([key, items]): FeatureLifecycleProjectItem[] =>
 | 
					 | 
				
			||||||
                items.map((item) => ({
 | 
					 | 
				
			||||||
                    ...item,
 | 
					 | 
				
			||||||
                    feature: key,
 | 
					 | 
				
			||||||
                    project: 'fake-project',
 | 
					 | 
				
			||||||
                })),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        return result;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async delete(feature: string): Promise<void> {
 | 
					    async delete(feature: string): Promise<void> {
 | 
				
			||||||
        this.lifecycles[feature] = [];
 | 
					        this.lifecycles[feature] = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,8 @@
 | 
				
			|||||||
import type { IFeatureLifecycleStage, StageName } from '../../types';
 | 
					import type {
 | 
				
			||||||
 | 
					    IFeatureLifecycleStage,
 | 
				
			||||||
 | 
					    IProjectLifecycleStageDuration,
 | 
				
			||||||
 | 
					    StageName,
 | 
				
			||||||
 | 
					} from '../../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type StageCount = {
 | 
					export type StageCount = {
 | 
				
			||||||
    stage: StageName;
 | 
					    stage: StageName;
 | 
				
			||||||
@ -15,4 +19,5 @@ export interface IFeatureLifecycleReadModel {
 | 
				
			|||||||
    ): Promise<IFeatureLifecycleStage | undefined>;
 | 
					    ): Promise<IFeatureLifecycleStage | undefined>;
 | 
				
			||||||
    getStageCount(): Promise<StageCount[]>;
 | 
					    getStageCount(): Promise<StageCount[]>;
 | 
				
			||||||
    getStageCountByProject(): Promise<StageCountByProject[]>;
 | 
					    getStageCountByProject(): Promise<StageCountByProject[]>;
 | 
				
			||||||
 | 
					    getAllWithStageDuration(): Promise<IProjectLifecycleStageDuration[]>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,8 +8,11 @@ import { getCurrentStage } from './get-current-stage';
 | 
				
			|||||||
import type {
 | 
					import type {
 | 
				
			||||||
    IFeatureLifecycleStage,
 | 
					    IFeatureLifecycleStage,
 | 
				
			||||||
    IFlagResolver,
 | 
					    IFlagResolver,
 | 
				
			||||||
 | 
					    IProjectLifecycleStageDuration,
 | 
				
			||||||
    StageName,
 | 
					    StageName,
 | 
				
			||||||
} from '../../types';
 | 
					} from '../../types';
 | 
				
			||||||
 | 
					import { calculateStageDurations } from './calculate-stage-durations';
 | 
				
			||||||
 | 
					import type { FeatureLifecycleProjectItem } from './feature-lifecycle-store-type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DBType = {
 | 
					type DBType = {
 | 
				
			||||||
    feature: string;
 | 
					    feature: string;
 | 
				
			||||||
@ -18,6 +21,10 @@ type DBType = {
 | 
				
			|||||||
    created_at: Date;
 | 
					    created_at: Date;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DBProjectType = DBType & {
 | 
				
			||||||
 | 
					    project: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
 | 
					export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
 | 
				
			||||||
    private db: Db;
 | 
					    private db: Db;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -106,4 +113,31 @@ export class FeatureLifecycleReadModel implements IFeatureLifecycleReadModel {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return getCurrentStage(stages);
 | 
					        return getCurrentStage(stages);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private async getAll(): Promise<FeatureLifecycleProjectItem[]> {
 | 
				
			||||||
 | 
					        const results = await this.db('feature_lifecycles as flc')
 | 
				
			||||||
 | 
					            .select('flc.feature', 'flc.stage', 'flc.created_at', 'f.project')
 | 
				
			||||||
 | 
					            .leftJoin('features as f', 'f.name', 'flc.feature')
 | 
				
			||||||
 | 
					            .orderBy('created_at', 'asc');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return results.map(
 | 
				
			||||||
 | 
					            ({ feature, stage, created_at, project }: DBProjectType) => ({
 | 
				
			||||||
 | 
					                feature,
 | 
				
			||||||
 | 
					                stage,
 | 
				
			||||||
 | 
					                project,
 | 
				
			||||||
 | 
					                enteredStageAt: new Date(created_at),
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async getAllWithStageDuration(): Promise<
 | 
				
			||||||
 | 
					        IProjectLifecycleStageDuration[]
 | 
				
			||||||
 | 
					    > {
 | 
				
			||||||
 | 
					        if (!this.flagResolver.isEnabled('featureLifecycleMetrics')) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const featureLifeCycles = await this.getAll();
 | 
				
			||||||
 | 
					        return calculateStageDurations(featureLifeCycles);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,6 @@ import {
 | 
				
			|||||||
    type IEventStore,
 | 
					    type IEventStore,
 | 
				
			||||||
    type IFeatureEnvironmentStore,
 | 
					    type IFeatureEnvironmentStore,
 | 
				
			||||||
    type IFlagResolver,
 | 
					    type IFlagResolver,
 | 
				
			||||||
    type IProjectLifecycleStageDuration,
 | 
					 | 
				
			||||||
    type IUnleashConfig,
 | 
					    type IUnleashConfig,
 | 
				
			||||||
} from '../../types';
 | 
					} from '../../types';
 | 
				
			||||||
import type {
 | 
					import type {
 | 
				
			||||||
@ -21,7 +20,6 @@ import EventEmitter from 'events';
 | 
				
			|||||||
import type { Logger } from '../../logger';
 | 
					import type { Logger } from '../../logger';
 | 
				
			||||||
import type EventService from '../events/event-service';
 | 
					import type EventService from '../events/event-service';
 | 
				
			||||||
import type { FeatureLifecycleCompletedSchema } from '../../openapi';
 | 
					import type { FeatureLifecycleCompletedSchema } from '../../openapi';
 | 
				
			||||||
import { calculateStageDurations } from './calculate-stage-durations';
 | 
					 | 
				
			||||||
import type { IClientMetricsEnv } from '../metrics/client-metrics/client-metrics-store-v2-type';
 | 
					import type { IClientMetricsEnv } from '../metrics/client-metrics/client-metrics-store-v2-type';
 | 
				
			||||||
import groupBy from 'lodash.groupby';
 | 
					import groupBy from 'lodash.groupby';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -231,11 +229,4 @@ export class FeatureLifecycleService extends EventEmitter {
 | 
				
			|||||||
        await this.featureLifecycleStore.delete(feature);
 | 
					        await this.featureLifecycleStore.delete(feature);
 | 
				
			||||||
        await this.featureInitialized(feature);
 | 
					        await this.featureInitialized(feature);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    public async getAllWithStageDuration(): Promise<
 | 
					 | 
				
			||||||
        IProjectLifecycleStageDuration[]
 | 
					 | 
				
			||||||
    > {
 | 
					 | 
				
			||||||
        const featureLifeCycles = await this.featureLifecycleStore.getAll();
 | 
					 | 
				
			||||||
        return calculateStageDurations(featureLifeCycles);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,6 @@ export type FeatureLifecycleProjectItem = FeatureLifecycleStage & {
 | 
				
			|||||||
export interface IFeatureLifecycleStore {
 | 
					export interface IFeatureLifecycleStore {
 | 
				
			||||||
    insert(featureLifecycleStages: FeatureLifecycleStage[]): Promise<void>;
 | 
					    insert(featureLifecycleStages: FeatureLifecycleStage[]): Promise<void>;
 | 
				
			||||||
    get(feature: string): Promise<FeatureLifecycleView>;
 | 
					    get(feature: string): Promise<FeatureLifecycleView>;
 | 
				
			||||||
    getAll(): Promise<FeatureLifecycleProjectItem[]>;
 | 
					 | 
				
			||||||
    stageExists(stage: FeatureLifecycleStage): Promise<boolean>;
 | 
					    stageExists(stage: FeatureLifecycleStage): Promise<boolean>;
 | 
				
			||||||
    delete(feature: string): Promise<void>;
 | 
					    delete(feature: string): Promise<void>;
 | 
				
			||||||
    deleteAll(): Promise<void>;
 | 
					    deleteAll(): Promise<void>;
 | 
				
			||||||
 | 
				
			|||||||
@ -40,10 +40,6 @@ import FakeSettingStore from '../../../test/fixtures/fake-setting-store';
 | 
				
			|||||||
import FakeSegmentStore from '../../../test/fixtures/fake-segment-store';
 | 
					import FakeSegmentStore from '../../../test/fixtures/fake-segment-store';
 | 
				
			||||||
import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store';
 | 
					import FakeStrategiesStore from '../../../test/fixtures/fake-strategies-store';
 | 
				
			||||||
import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
 | 
					import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
    createFakeFeatureLifecycleService,
 | 
					 | 
				
			||||||
    createFeatureLifecycleService,
 | 
					 | 
				
			||||||
} from '../feature-lifecycle/createFeatureLifecycle';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
 | 
					export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
 | 
				
			||||||
    const { eventBus, getLogger, flagResolver } = config;
 | 
					    const { eventBus, getLogger, flagResolver } = config;
 | 
				
			||||||
@ -88,10 +84,6 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
 | 
				
			|||||||
        getLogger,
 | 
					        getLogger,
 | 
				
			||||||
        flagResolver,
 | 
					        flagResolver,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const { featureLifecycleService } = createFeatureLifecycleService(
 | 
					 | 
				
			||||||
        db,
 | 
					 | 
				
			||||||
        config,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    const instanceStatsServiceStores = {
 | 
					    const instanceStatsServiceStores = {
 | 
				
			||||||
        featureToggleStore,
 | 
					        featureToggleStore,
 | 
				
			||||||
        userStore,
 | 
					        userStore,
 | 
				
			||||||
@ -133,7 +125,6 @@ export const createInstanceStatsService = (db: Db, config: IUnleashConfig) => {
 | 
				
			|||||||
        versionService,
 | 
					        versionService,
 | 
				
			||||||
        getActiveUsers,
 | 
					        getActiveUsers,
 | 
				
			||||||
        getProductionChanges,
 | 
					        getProductionChanges,
 | 
				
			||||||
        featureLifecycleService,
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return instanceStatsService;
 | 
					    return instanceStatsService;
 | 
				
			||||||
@ -156,8 +147,6 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
 | 
				
			|||||||
    const apiTokenStore = new FakeApiTokenStore();
 | 
					    const apiTokenStore = new FakeApiTokenStore();
 | 
				
			||||||
    const clientMetricsStoreV2 = new FakeClientMetricsStoreV2();
 | 
					    const clientMetricsStoreV2 = new FakeClientMetricsStoreV2();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { featureLifecycleService } =
 | 
					 | 
				
			||||||
        createFakeFeatureLifecycleService(config);
 | 
					 | 
				
			||||||
    const instanceStatsServiceStores = {
 | 
					    const instanceStatsServiceStores = {
 | 
				
			||||||
        featureToggleStore,
 | 
					        featureToggleStore,
 | 
				
			||||||
        userStore,
 | 
					        userStore,
 | 
				
			||||||
@ -194,7 +183,6 @@ export const createFakeInstanceStatsService = (config: IUnleashConfig) => {
 | 
				
			|||||||
        versionService,
 | 
					        versionService,
 | 
				
			||||||
        getActiveUsers,
 | 
					        getActiveUsers,
 | 
				
			||||||
        getProductionChanges,
 | 
					        getProductionChanges,
 | 
				
			||||||
        featureLifecycleService,
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return instanceStatsService;
 | 
					    return instanceStatsService;
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import createStores from '../../../test/fixtures/store';
 | 
				
			|||||||
import VersionService from '../../services/version-service';
 | 
					import VersionService from '../../services/version-service';
 | 
				
			||||||
import { createFakeGetActiveUsers } from './getActiveUsers';
 | 
					import { createFakeGetActiveUsers } from './getActiveUsers';
 | 
				
			||||||
import { createFakeGetProductionChanges } from './getProductionChanges';
 | 
					import { createFakeGetProductionChanges } from './getProductionChanges';
 | 
				
			||||||
import { createFakeFeatureLifecycleService } from '../feature-lifecycle/createFeatureLifecycle';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
let instanceStatsService: InstanceStatsService;
 | 
					let instanceStatsService: InstanceStatsService;
 | 
				
			||||||
let versionService: VersionService;
 | 
					let versionService: VersionService;
 | 
				
			||||||
@ -24,7 +23,6 @@ beforeEach(() => {
 | 
				
			|||||||
        versionService,
 | 
					        versionService,
 | 
				
			||||||
        createFakeGetActiveUsers(),
 | 
					        createFakeGetActiveUsers(),
 | 
				
			||||||
        createFakeGetProductionChanges(),
 | 
					        createFakeGetProductionChanges(),
 | 
				
			||||||
        createFakeFeatureLifecycleService(config).featureLifecycleService,
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    jest.spyOn(instanceStatsService, 'refreshAppCountSnapshot');
 | 
					    jest.spyOn(instanceStatsService, 'refreshAppCountSnapshot');
 | 
				
			||||||
 | 
				
			|||||||
@ -22,14 +22,12 @@ import {
 | 
				
			|||||||
    FEATURES_EXPORTED,
 | 
					    FEATURES_EXPORTED,
 | 
				
			||||||
    FEATURES_IMPORTED,
 | 
					    FEATURES_IMPORTED,
 | 
				
			||||||
    type IApiTokenStore,
 | 
					    type IApiTokenStore,
 | 
				
			||||||
    type IProjectLifecycleStageDuration,
 | 
					 | 
				
			||||||
    type IFlagResolver,
 | 
					    type IFlagResolver,
 | 
				
			||||||
} from '../../types';
 | 
					} from '../../types';
 | 
				
			||||||
import { CUSTOM_ROOT_ROLE_TYPE } from '../../util';
 | 
					import { CUSTOM_ROOT_ROLE_TYPE } from '../../util';
 | 
				
			||||||
import type { GetActiveUsers } from './getActiveUsers';
 | 
					import type { GetActiveUsers } from './getActiveUsers';
 | 
				
			||||||
import type { ProjectModeCount } from '../project/project-store';
 | 
					import type { ProjectModeCount } from '../project/project-store';
 | 
				
			||||||
import type { GetProductionChanges } from './getProductionChanges';
 | 
					import type { GetProductionChanges } from './getProductionChanges';
 | 
				
			||||||
import type { FeatureLifecycleService } from '../feature-lifecycle/feature-lifecycle-service';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type TimeRange = 'allTime' | '30d' | '7d';
 | 
					export type TimeRange = 'allTime' | '30d' | '7d';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,7 +61,6 @@ export interface InstanceStats {
 | 
				
			|||||||
        enabledCount: number;
 | 
					        enabledCount: number;
 | 
				
			||||||
        variantCount: number;
 | 
					        variantCount: number;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    featureLifeCycles: IProjectLifecycleStageDuration[];
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type InstanceStatsSigned = Omit<InstanceStats, 'projects'> & {
 | 
					export type InstanceStatsSigned = Omit<InstanceStats, 'projects'> & {
 | 
				
			||||||
@ -94,8 +91,6 @@ export class InstanceStatsService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private eventStore: IEventStore;
 | 
					    private eventStore: IEventStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private featureLifecycleService: FeatureLifecycleService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private apiTokenStore: IApiTokenStore;
 | 
					    private apiTokenStore: IApiTokenStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private versionService: VersionService;
 | 
					    private versionService: VersionService;
 | 
				
			||||||
@ -154,7 +149,6 @@ export class InstanceStatsService {
 | 
				
			|||||||
        versionService: VersionService,
 | 
					        versionService: VersionService,
 | 
				
			||||||
        getActiveUsers: GetActiveUsers,
 | 
					        getActiveUsers: GetActiveUsers,
 | 
				
			||||||
        getProductionChanges: GetProductionChanges,
 | 
					        getProductionChanges: GetProductionChanges,
 | 
				
			||||||
        featureLifecycleService: FeatureLifecycleService,
 | 
					 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        this.strategyStore = strategyStore;
 | 
					        this.strategyStore = strategyStore;
 | 
				
			||||||
        this.userStore = userStore;
 | 
					        this.userStore = userStore;
 | 
				
			||||||
@ -169,7 +163,6 @@ export class InstanceStatsService {
 | 
				
			|||||||
        this.settingStore = settingStore;
 | 
					        this.settingStore = settingStore;
 | 
				
			||||||
        this.eventStore = eventStore;
 | 
					        this.eventStore = eventStore;
 | 
				
			||||||
        this.clientInstanceStore = clientInstanceStore;
 | 
					        this.clientInstanceStore = clientInstanceStore;
 | 
				
			||||||
        this.featureLifecycleService = featureLifecycleService;
 | 
					 | 
				
			||||||
        this.logger = getLogger('services/stats-service.js');
 | 
					        this.logger = getLogger('services/stats-service.js');
 | 
				
			||||||
        this.getActiveUsers = getActiveUsers;
 | 
					        this.getActiveUsers = getActiveUsers;
 | 
				
			||||||
        this.getProductionChanges = getProductionChanges;
 | 
					        this.getProductionChanges = getProductionChanges;
 | 
				
			||||||
@ -257,7 +250,6 @@ export class InstanceStatsService {
 | 
				
			|||||||
            featureImports,
 | 
					            featureImports,
 | 
				
			||||||
            productionChanges,
 | 
					            productionChanges,
 | 
				
			||||||
            previousDayMetricsBucketsCount,
 | 
					            previousDayMetricsBucketsCount,
 | 
				
			||||||
            featureLifeCycles,
 | 
					 | 
				
			||||||
        ] = await Promise.all([
 | 
					        ] = await Promise.all([
 | 
				
			||||||
            this.getToggleCount(),
 | 
					            this.getToggleCount(),
 | 
				
			||||||
            this.getArchivedToggleCount(),
 | 
					            this.getArchivedToggleCount(),
 | 
				
			||||||
@ -281,7 +273,6 @@ export class InstanceStatsService {
 | 
				
			|||||||
            this.eventStore.filteredCount({ type: FEATURES_IMPORTED }),
 | 
					            this.eventStore.filteredCount({ type: FEATURES_IMPORTED }),
 | 
				
			||||||
            this.getProductionChanges(),
 | 
					            this.getProductionChanges(),
 | 
				
			||||||
            this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets(),
 | 
					            this.clientMetricsStore.countPreviousDayHourlyMetricsBuckets(),
 | 
				
			||||||
            this.getAllWithStageDuration(),
 | 
					 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
@ -314,7 +305,6 @@ export class InstanceStatsService {
 | 
				
			|||||||
            featureImports,
 | 
					            featureImports,
 | 
				
			||||||
            productionChanges,
 | 
					            productionChanges,
 | 
				
			||||||
            previousDayMetricsBucketsCount,
 | 
					            previousDayMetricsBucketsCount,
 | 
				
			||||||
            featureLifeCycles,
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -348,11 +338,4 @@ export class InstanceStatsService {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
        return { ...instanceStats, sum, projects: totalProjects };
 | 
					        return { ...instanceStats, sum, projects: totalProjects };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    async getAllWithStageDuration(): Promise<IProjectLifecycleStageDuration[]> {
 | 
					 | 
				
			||||||
        if (this.flagResolver.isEnabled('featureLifecycleMetrics')) {
 | 
					 | 
				
			||||||
            return this.featureLifecycleService.getAllWithStageDuration();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return [];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,14 +16,19 @@ import { InstanceStatsService } from './features/instance-stats/instance-stats-s
 | 
				
			|||||||
import VersionService from './services/version-service';
 | 
					import VersionService from './services/version-service';
 | 
				
			||||||
import { createFakeGetActiveUsers } from './features/instance-stats/getActiveUsers';
 | 
					import { createFakeGetActiveUsers } from './features/instance-stats/getActiveUsers';
 | 
				
			||||||
import { createFakeGetProductionChanges } from './features/instance-stats/getProductionChanges';
 | 
					import { createFakeGetProductionChanges } from './features/instance-stats/getProductionChanges';
 | 
				
			||||||
import type { IEnvironmentStore, IUnleashStores } from './types';
 | 
					import type {
 | 
				
			||||||
 | 
					    IEnvironmentStore,
 | 
				
			||||||
 | 
					    IFeatureLifecycleReadModel,
 | 
				
			||||||
 | 
					    IFeatureLifecycleStore,
 | 
				
			||||||
 | 
					    IUnleashStores,
 | 
				
			||||||
 | 
					} from './types';
 | 
				
			||||||
import FakeEnvironmentStore from './features/project-environments/fake-environment-store';
 | 
					import FakeEnvironmentStore from './features/project-environments/fake-environment-store';
 | 
				
			||||||
import { SchedulerService } from './services';
 | 
					import { SchedulerService } from './services';
 | 
				
			||||||
import noLogger from '../test/fixtures/no-logger';
 | 
					import noLogger from '../test/fixtures/no-logger';
 | 
				
			||||||
import { createFeatureLifecycleService } from './features';
 | 
					 | 
				
			||||||
import dbInit, { type ITestDb } from '../test/e2e/helpers/database-init';
 | 
					 | 
				
			||||||
import getLogger from '../test/fixtures/no-logger';
 | 
					import getLogger from '../test/fixtures/no-logger';
 | 
				
			||||||
import type { FeatureLifecycleStore } from './features/feature-lifecycle/feature-lifecycle-store';
 | 
					import dbInit, { type ITestDb } from '../test/e2e/helpers/database-init';
 | 
				
			||||||
 | 
					import { FeatureLifecycleStore } from './features/feature-lifecycle/feature-lifecycle-store';
 | 
				
			||||||
 | 
					import { FeatureLifecycleReadModel } from './features/feature-lifecycle/feature-lifecycle-read-model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const monitor = createMetricsMonitor();
 | 
					const monitor = createMetricsMonitor();
 | 
				
			||||||
const eventBus = new EventEmitter();
 | 
					const eventBus = new EventEmitter();
 | 
				
			||||||
@ -33,7 +38,8 @@ let environmentStore: IEnvironmentStore;
 | 
				
			|||||||
let statsService: InstanceStatsService;
 | 
					let statsService: InstanceStatsService;
 | 
				
			||||||
let stores: IUnleashStores;
 | 
					let stores: IUnleashStores;
 | 
				
			||||||
let schedulerService: SchedulerService;
 | 
					let schedulerService: SchedulerService;
 | 
				
			||||||
let featureLifeCycleStore: FeatureLifecycleStore;
 | 
					let featureLifeCycleStore: IFeatureLifecycleStore;
 | 
				
			||||||
 | 
					let featureLifeCycleReadModel: IFeatureLifecycleReadModel;
 | 
				
			||||||
let db: ITestDb;
 | 
					let db: ITestDb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
beforeAll(async () => {
 | 
					beforeAll(async () => {
 | 
				
			||||||
@ -59,8 +65,13 @@ beforeAll(async () => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
    db = await dbInit('metrics_test', getLogger);
 | 
					    db = await dbInit('metrics_test', getLogger);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { featureLifecycleService, featureLifecycleStore } =
 | 
					    featureLifeCycleReadModel = new FeatureLifecycleReadModel(
 | 
				
			||||||
        createFeatureLifecycleService(db.rawDatabase, config);
 | 
					        db.rawDatabase,
 | 
				
			||||||
 | 
					        config.flagResolver,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    stores.featureLifecycleReadModel = featureLifeCycleReadModel;
 | 
				
			||||||
 | 
					    featureLifeCycleStore = new FeatureLifecycleStore(db.rawDatabase);
 | 
				
			||||||
 | 
					    stores.featureLifecycleStore = featureLifeCycleStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    statsService = new InstanceStatsService(
 | 
					    statsService = new InstanceStatsService(
 | 
				
			||||||
        stores,
 | 
					        stores,
 | 
				
			||||||
@ -68,9 +79,7 @@ beforeAll(async () => {
 | 
				
			|||||||
        versionService,
 | 
					        versionService,
 | 
				
			||||||
        createFakeGetActiveUsers(),
 | 
					        createFakeGetActiveUsers(),
 | 
				
			||||||
        createFakeGetProductionChanges(),
 | 
					        createFakeGetProductionChanges(),
 | 
				
			||||||
        featureLifecycleService,
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    featureLifeCycleStore = featureLifecycleStore;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    schedulerService = new SchedulerService(
 | 
					    schedulerService = new SchedulerService(
 | 
				
			||||||
        noLogger,
 | 
					        noLogger,
 | 
				
			||||||
@ -303,9 +312,13 @@ test('should collect metrics for lifecycle', async () => {
 | 
				
			|||||||
            stage: 'initial',
 | 
					            stage: 'initial',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    ]);
 | 
					    ]);
 | 
				
			||||||
    const { featureLifeCycles } = await statsService.getStats();
 | 
					    const stageCount = await featureLifeCycleReadModel.getStageCountByProject();
 | 
				
			||||||
    expect(featureLifeCycles).toHaveLength(1);
 | 
					    const stageDurations =
 | 
				
			||||||
 | 
					        await featureLifeCycleReadModel.getAllWithStageDuration();
 | 
				
			||||||
 | 
					    expect(stageCount).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(stageDurations).toHaveLength(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const metrics = await prometheusRegister.metrics();
 | 
					    const metrics = await prometheusRegister.metrics();
 | 
				
			||||||
    expect(metrics).toMatch(/feature_lifecycle_stage_duration/);
 | 
					    expect(metrics).toMatch(/feature_lifecycle_stage_duration/);
 | 
				
			||||||
 | 
					    expect(metrics).toMatch(/stage_count_by_project/);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -306,12 +306,14 @@ export default class MetricsMonitor {
 | 
				
			|||||||
                    maxConstraintValuesResult,
 | 
					                    maxConstraintValuesResult,
 | 
				
			||||||
                    maxConstraintsPerStrategyResult,
 | 
					                    maxConstraintsPerStrategyResult,
 | 
				
			||||||
                    stageCountByProjectResult,
 | 
					                    stageCountByProjectResult,
 | 
				
			||||||
 | 
					                    stageDurationByProject,
 | 
				
			||||||
                ] = 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(),
 | 
					                    stores.featureLifecycleReadModel.getStageCountByProject(),
 | 
				
			||||||
 | 
					                    stores.featureLifecycleReadModel.getAllWithStageDuration(),
 | 
				
			||||||
                ]);
 | 
					                ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                featureFlagsTotal.reset();
 | 
					                featureFlagsTotal.reset();
 | 
				
			||||||
@ -326,7 +328,7 @@ export default class MetricsMonitor {
 | 
				
			|||||||
                serviceAccounts.reset();
 | 
					                serviceAccounts.reset();
 | 
				
			||||||
                serviceAccounts.set(stats.serviceAccounts);
 | 
					                serviceAccounts.set(stats.serviceAccounts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                stats.featureLifeCycles.forEach((stage) => {
 | 
					                stageDurationByProject.forEach((stage) => {
 | 
				
			||||||
                    featureLifecycleStageDuration
 | 
					                    featureLifecycleStageDuration
 | 
				
			||||||
                        .labels({
 | 
					                        .labels({
 | 
				
			||||||
                            stage: stage.stage,
 | 
					                            stage: stage.stage,
 | 
				
			||||||
 | 
				
			|||||||
@ -125,13 +125,6 @@ class InstanceAdminController extends Controller {
 | 
				
			|||||||
                variantCount: 100,
 | 
					                variantCount: 100,
 | 
				
			||||||
                enabledCount: 200,
 | 
					                enabledCount: 200,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            featureLifeCycles: [
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    project: 'default',
 | 
					 | 
				
			||||||
                    stage: 'archived',
 | 
					 | 
				
			||||||
                    duration: 2000,
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
            ],
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user