1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-05 17:53:12 +02:00

Merge branch 'main' into fix/flagstats_nan

This commit is contained in:
andreas-unleash 2024-03-21 13:13:31 +02:00
commit 46d15957b1
No known key found for this signature in database
GPG Key ID: 86EF87A739B39099
5 changed files with 106 additions and 52 deletions

View File

@ -1,10 +1,6 @@
import type { Db, IUnleashConfig } from '../../server-impl'; import type { Db, IUnleashConfig } from '../../server-impl';
import FeatureToggleStore from '../feature-toggle/feature-toggle-store'; import FeatureToggleStore from '../feature-toggle/feature-toggle-store';
import ProjectStatsStore from '../../db/project-stats-store'; import ProjectStatsStore from '../../db/project-stats-store';
import {
createFakeFeatureToggleService,
createFeatureToggleService,
} from '../feature-toggle/createFeatureToggleService';
import FakeProjectStore from '../../../test/fixtures/fake-project-store'; import FakeProjectStore from '../../../test/fixtures/fake-project-store';
import FakeFeatureToggleStore from '../feature-toggle/fakes/fake-feature-toggle-store'; import FakeFeatureToggleStore from '../feature-toggle/fakes/fake-feature-toggle-store';
import FakeProjectStatsStore from '../../../test/fixtures/fake-project-stats-store'; import FakeProjectStatsStore from '../../../test/fixtures/fake-project-stats-store';
@ -14,6 +10,8 @@ import { ProjectInsightsService } from './project-insights-service';
import ProjectStore from '../project/project-store'; import ProjectStore from '../project/project-store';
import { ProjectInsightsReadModel } from './project-insights-read-model'; import { ProjectInsightsReadModel } from './project-insights-read-model';
import { FakeProjectInsightsReadModel } from './fake-project-insights-read-model'; import { FakeProjectInsightsReadModel } from './fake-project-insights-read-model';
import FeatureStrategiesStore from '../feature-toggle/feature-toggle-strategies-store';
import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
export const createProjectInsightsService = ( export const createProjectInsightsService = (
db: Db, db: Db,
@ -35,7 +33,12 @@ export const createProjectInsightsService = (
const featureTypeStore = new FeatureTypeStore(db, getLogger); const featureTypeStore = new FeatureTypeStore(db, getLogger);
const projectStatsStore = new ProjectStatsStore(db, eventBus, getLogger); const projectStatsStore = new ProjectStatsStore(db, eventBus, getLogger);
const featureToggleService = createFeatureToggleService(db, config); const featureStrategiesStore = new FeatureStrategiesStore(
db,
eventBus,
getLogger,
flagResolver,
);
const projectInsightsReadModel = new ProjectInsightsReadModel(db); const projectInsightsReadModel = new ProjectInsightsReadModel(db);
return new ProjectInsightsService( return new ProjectInsightsService(
@ -44,8 +47,8 @@ export const createProjectInsightsService = (
featureToggleStore, featureToggleStore,
featureTypeStore, featureTypeStore,
projectStatsStore, projectStatsStore,
featureStrategiesStore,
}, },
featureToggleService,
projectInsightsReadModel, projectInsightsReadModel,
); );
}; };
@ -57,7 +60,7 @@ export const createFakeProjectInsightsService = (
const featureToggleStore = new FakeFeatureToggleStore(); const featureToggleStore = new FakeFeatureToggleStore();
const featureTypeStore = new FakeFeatureTypeStore(); const featureTypeStore = new FakeFeatureTypeStore();
const projectStatsStore = new FakeProjectStatsStore(); const projectStatsStore = new FakeProjectStatsStore();
const featureToggleService = createFakeFeatureToggleService(config); const featureStrategiesStore = new FakeFeatureStrategiesStore();
const projectInsightsReadModel = new FakeProjectInsightsReadModel(); const projectInsightsReadModel = new FakeProjectInsightsReadModel();
return new ProjectInsightsService( return new ProjectInsightsService(
@ -66,8 +69,8 @@ export const createFakeProjectInsightsService = (
featureToggleStore, featureToggleStore,
featureTypeStore, featureTypeStore,
projectStatsStore, projectStatsStore,
featureStrategiesStore,
}, },
featureToggleService,
projectInsightsReadModel, projectInsightsReadModel,
); );
}; };

View File

@ -0,0 +1,66 @@
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import getLogger from '../../../test/fixtures/no-logger';
import type { IUser } from '../../types';
import type { IProjectInsightsReadModel } from './project-insights-read-model-type';
import {
type ChangeRequestDBState,
ProjectInsightsReadModel,
} from './project-insights-read-model';
let projectInsightsReadModel: IProjectInsightsReadModel;
let user: IUser;
let db: ITestDb;
const projectId = 'default';
beforeAll(async () => {
db = await dbInit('project_insights_read_model', getLogger);
projectInsightsReadModel = new ProjectInsightsReadModel(db.rawDatabase);
user = await db.stores.userStore.insert({
username: 'test',
});
});
afterAll(async () => {
await db.destroy();
});
beforeEach(async () => {
await db.rawDatabase.table('change_requests').delete();
});
const createChangeRequest = (id: number, state: string) =>
db.rawDatabase.table('change_requests').insert({
id,
state,
environment: 'default',
project: projectId,
created_by: user.id,
});
test('can read change request status counts', async () => {
const states: ChangeRequestDBState[] = [
'Approved',
'Approved',
'Applied',
'Rejected',
'Scheduled',
'In review',
'Draft',
'Cancelled',
];
await Promise.all(
states.map((state, id) => createChangeRequest(id, state)),
);
const changeRequests =
await projectInsightsReadModel.getChangeRequests(projectId);
expect(changeRequests).toEqual({
total: 6,
approved: 2,
applied: 1,
rejected: 1,
reviewRequired: 1,
scheduled: 1,
});
});

View File

@ -5,6 +5,8 @@ import type {
import type { Db } from '../../db/db'; import type { Db } from '../../db/db';
export type ChangeRequestDBState = export type ChangeRequestDBState =
| 'Draft'
| 'Cancelled'
| 'Approved' | 'Approved'
| 'In review' | 'In review'
| 'Applied' | 'Applied'

View File

@ -1,11 +1,11 @@
import type { import type {
IFeatureOverview,
IFeatureStrategiesStore,
IFeatureToggleStore, IFeatureToggleStore,
IFeatureTypeStore, IFeatureTypeStore,
IProjectHealth,
IProjectStore, IProjectStore,
IUnleashStores, IUnleashStores,
} from '../../types'; } from '../../types';
import type FeatureToggleService from '../feature-toggle/feature-toggle-service';
import { calculateAverageTimeToProd } from '../feature-toggle/time-to-production/time-to-production'; import { calculateAverageTimeToProd } from '../feature-toggle/time-to-production/time-to-production';
import type { IProjectStatsStore } from '../../types/stores/project-stats-store-type'; import type { IProjectStatsStore } from '../../types/stores/project-stats-store-type';
import type { ProjectDoraMetricsSchema } from '../../openapi'; import type { ProjectDoraMetricsSchema } from '../../openapi';
@ -19,7 +19,7 @@ export class ProjectInsightsService {
private featureTypeStore: IFeatureTypeStore; private featureTypeStore: IFeatureTypeStore;
private featureToggleService: FeatureToggleService; private featureStrategiesStore: IFeatureStrategiesStore;
private projectStatsStore: IProjectStatsStore; private projectStatsStore: IProjectStatsStore;
@ -31,20 +31,21 @@ export class ProjectInsightsService {
featureToggleStore, featureToggleStore,
featureTypeStore, featureTypeStore,
projectStatsStore, projectStatsStore,
featureStrategiesStore,
}: Pick< }: Pick<
IUnleashStores, IUnleashStores,
| 'projectStore' | 'projectStore'
| 'featureToggleStore' | 'featureToggleStore'
| 'projectStatsStore' | 'projectStatsStore'
| 'featureTypeStore' | 'featureTypeStore'
| 'featureStrategiesStore'
>, >,
featureToggleService: FeatureToggleService,
projectInsightsReadModel: IProjectInsightsReadModel, projectInsightsReadModel: IProjectInsightsReadModel,
) { ) {
this.projectStore = projectStore; this.projectStore = projectStore;
this.featureToggleStore = featureToggleStore; this.featureToggleStore = featureToggleStore;
this.featureTypeStore = featureTypeStore; this.featureTypeStore = featureTypeStore;
this.featureToggleService = featureToggleService; this.featureStrategiesStore = featureStrategiesStore;
this.projectStatsStore = projectStatsStore; this.projectStatsStore = projectStatsStore;
this.projectInsightsReadModel = projectInsightsReadModel; this.projectInsightsReadModel = projectInsightsReadModel;
} }
@ -101,6 +102,26 @@ export class ProjectInsightsService {
}; };
} }
private async getProjectHealth(
projectId: string,
archived: boolean = false,
userId?: number,
): Promise<{ health: number; features: IFeatureOverview[] }> {
const [project, features] = await Promise.all([
this.projectStore.get(projectId),
this.featureStrategiesStore.getFeatureOverview({
projectId,
archived,
userId,
}),
]);
return {
health: project.health || 0,
features: features,
};
}
async getProjectInsights(projectId: string) { async getProjectInsights(projectId: string) {
const result = { const result = {
members: { members: {
@ -112,7 +133,7 @@ export class ProjectInsightsService {
const [stats, featureTypeCounts, health, leadTime, changeRequests] = const [stats, featureTypeCounts, health, leadTime, changeRequests] =
await Promise.all([ await Promise.all([
this.projectStatsStore.getProjectStats(projectId), this.projectStatsStore.getProjectStats(projectId),
this.featureToggleService.getFeatureTypeCounts({ this.featureToggleStore.getFeatureTypeCounts({
projectId, projectId,
archived: false, archived: false,
}), }),
@ -130,40 +151,4 @@ export class ProjectInsightsService {
changeRequests, changeRequests,
}; };
} }
private async getProjectHealth(
projectId: string,
archived: boolean = false,
userId?: number,
): Promise<IProjectHealth> {
const [project, environments, features, members, projectStats] =
await Promise.all([
this.projectStore.get(projectId),
this.projectStore.getEnvironmentsForProject(projectId),
this.featureToggleService.getFeatureOverview({
projectId,
archived,
userId,
}),
this.projectStore.getMembersCountByProject(projectId),
this.projectStatsStore.getProjectStats(projectId),
]);
return {
stats: projectStats,
name: project.name,
description: project.description!,
mode: project.mode,
featureLimit: project.featureLimit,
featureNaming: project.featureNaming,
defaultStickiness: project.defaultStickiness,
health: project.health || 0,
updatedAt: project.updatedAt,
createdAt: project.createdAt,
environments,
features: features,
members,
version: 1,
};
}
} }

View File

@ -34,8 +34,6 @@ test('project insights happy path', async () => {
.expect('Content-Type', /json/) .expect('Content-Type', /json/)
.expect(200); .expect(200);
console.log(body);
expect(body).toMatchObject({ expect(body).toMatchObject({
stats: { stats: {
avgTimeToProdCurrentWindow: 0, avgTimeToProdCurrentWindow: 0,