1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat(1-3085): hook up lifecycle read model data to endpoint (#8709)

This PR hooks up the project lifecycle summary read model to the service
and exposes the lifecycle summary data in the controller.
This commit is contained in:
Thomas Heartman 2024-11-11 11:22:28 +01:00 committed by GitHub
parent 92f7acbfc4
commit 8493bee272
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 103 additions and 0 deletions

View File

@ -10,6 +10,10 @@ import SegmentStore from '../segment/segment-store';
import FakeSegmentStore from '../../../test/fixtures/fake-segment-store';
import { PersonalDashboardReadModel } from '../personal-dashboard/personal-dashboard-read-model';
import { FakePersonalDashboardReadModel } from '../personal-dashboard/fake-personal-dashboard-read-model';
import {
createFakeProjectLifecycleSummaryReadModel,
createProjectLifecycleSummaryReadModel,
} from './project-lifecycle-read-model/createProjectLifecycleSummaryReadModel';
export const createProjectStatusService = (
db: Db,
@ -34,6 +38,8 @@ export const createProjectStatusService = (
config.getLogger,
config.flagResolver,
);
const projectLifecycleSummaryReadModel =
createProjectLifecycleSummaryReadModel(db, config);
return new ProjectStatusService(
{
@ -43,6 +49,7 @@ export const createProjectStatusService = (
segmentStore,
},
new PersonalDashboardReadModel(db),
projectLifecycleSummaryReadModel,
);
};
@ -59,6 +66,7 @@ export const createFakeProjectStatusService = () => {
segmentStore,
},
new FakePersonalDashboardReadModel(),
createFakeProjectLifecycleSummaryReadModel(),
);
return {

View File

@ -7,6 +7,7 @@ import type {
IUnleashStores,
} from '../../types';
import type { IPersonalDashboardReadModel } from '../personal-dashboard/personal-dashboard-read-model-type';
import type { IProjectLifecycleSummaryReadModel } from './project-lifecycle-read-model/project-lifecycle-read-model-type';
export class ProjectStatusService {
private eventStore: IEventStore;
@ -14,6 +15,7 @@ export class ProjectStatusService {
private apiTokenStore: IApiTokenStore;
private segmentStore: ISegmentStore;
private personalDashboardReadModel: IPersonalDashboardReadModel;
private projectLifecycleSummaryReadModel: IProjectLifecycleSummaryReadModel;
constructor(
{
@ -26,12 +28,14 @@ export class ProjectStatusService {
'eventStore' | 'projectStore' | 'apiTokenStore' | 'segmentStore'
>,
personalDashboardReadModel: IPersonalDashboardReadModel,
projectLifecycleReadModel: IProjectLifecycleSummaryReadModel,
) {
this.eventStore = eventStore;
this.projectStore = projectStore;
this.apiTokenStore = apiTokenStore;
this.segmentStore = segmentStore;
this.personalDashboardReadModel = personalDashboardReadModel;
this.projectLifecycleSummaryReadModel = projectLifecycleReadModel;
}
async getProjectStatus(projectId: string): Promise<ProjectStatusSchema> {
@ -42,6 +46,7 @@ export class ProjectStatusService {
segments,
activityCountByDate,
healthScores,
lifecycleSummary,
] = await Promise.all([
this.projectStore.getConnectedEnvironmentCountForProject(projectId),
this.projectStore.getMembersCountByProject(projectId),
@ -49,6 +54,9 @@ export class ProjectStatusService {
this.segmentStore.getProjectSegmentCount(projectId),
this.eventStore.getProjectRecentEventActivity(projectId),
this.personalDashboardReadModel.getLatestHealthScores(projectId, 4),
this.projectLifecycleSummaryReadModel.getProjectLifecycleSummary(
projectId,
),
]);
const averageHealth = healthScores.length
@ -65,6 +73,7 @@ export class ProjectStatusService {
},
activityCountByDate,
averageHealth,
lifecycleSummary,
};
}
}

View File

@ -256,3 +256,33 @@ test('project health should be correct average', async () => {
expect(body.averageHealth).toBe(40);
});
test('project status contains lifecycle data', async () => {
const { body } = await app.request
.get('/api/admin/projects/default/status')
.expect('Content-Type', /json/)
.expect(200);
expect(body.lifecycleSummary).toMatchObject({
initial: {
averageDays: null,
currentFlags: 0,
},
preLive: {
averageDays: null,
currentFlags: 0,
},
live: {
averageDays: null,
currentFlags: 0,
},
completed: {
averageDays: null,
currentFlags: 0,
},
archived: {
currentFlags: 0,
last30Days: 0,
},
});
});

View File

@ -1,6 +1,29 @@
import type { FromSchema } from 'json-schema-to-ts';
import { projectActivitySchema } from './project-activity-schema';
const stageDataWithAverageDaysSchema = {
type: 'object',
additionalProperties: false,
description:
'Statistics on feature flags in a given stage in this project.',
required: ['averageDays', 'currentFlags'],
properties: {
averageDays: {
type: 'number',
nullable: true,
description:
"The average number of days a feature flag remains in a stage in this project. Will be null if Unleash doesn't have any data for this stage yet.",
example: 5,
},
currentFlags: {
type: 'integer',
description:
'The number of feature flags currently in a stage in this project.',
example: 10,
},
},
} as const;
export const projectStatusSchema = {
$id: '#/components/schemas/projectStatusSchema',
type: 'object',
@ -57,6 +80,39 @@ export const projectStatusSchema = {
},
},
},
lifecycleSummary: {
type: 'object',
additionalProperties: false,
description: 'Feature flag lifecycle statistics for this project.',
required: ['initial', 'preLive', 'live', 'completed', 'archived'],
properties: {
initial: stageDataWithAverageDaysSchema,
preLive: stageDataWithAverageDaysSchema,
live: stageDataWithAverageDaysSchema,
completed: stageDataWithAverageDaysSchema,
archived: {
type: 'object',
additionalProperties: false,
required: ['currentFlags', 'last30Days'],
description:
'Information on archived flags in this project.',
properties: {
currentFlags: {
type: 'integer',
description:
'The number of archived feature flags in this project. If a flag is deleted permanently, it will no longer be counted as part of this statistic.',
example: 10,
},
last30Days: {
type: 'integer',
description:
'The number of flags in this project that have been changed over the last 30 days.',
example: 5,
},
},
},
},
},
},
components: {
schemas: {