Skip to content

Commit

Permalink
feat(1-3085): hook up lifecycle read model data to endpoint (#8709)
Browse files Browse the repository at this point in the history
This PR hooks up the project lifecycle summary read model to the service
and exposes the lifecycle summary data in the controller.
  • Loading branch information
thomasheartman authored Nov 11, 2024
1 parent 92f7acb commit 8493bee
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/lib/features/project-status/createProjectStatusService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -34,6 +38,8 @@ export const createProjectStatusService = (
config.getLogger,
config.flagResolver,
);
const projectLifecycleSummaryReadModel =
createProjectLifecycleSummaryReadModel(db, config);

return new ProjectStatusService(
{
Expand All @@ -43,6 +49,7 @@ export const createProjectStatusService = (
segmentStore,
},
new PersonalDashboardReadModel(db),
projectLifecycleSummaryReadModel,
);
};

Expand All @@ -59,6 +66,7 @@ export const createFakeProjectStatusService = () => {
segmentStore,
},
new FakePersonalDashboardReadModel(),
createFakeProjectLifecycleSummaryReadModel(),
);

return {
Expand Down
9 changes: 9 additions & 0 deletions src/lib/features/project-status/project-status-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ 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;
private projectStore: IProjectStore;
private apiTokenStore: IApiTokenStore;
private segmentStore: ISegmentStore;
private personalDashboardReadModel: IPersonalDashboardReadModel;
private projectLifecycleSummaryReadModel: IProjectLifecycleSummaryReadModel;

constructor(
{
Expand All @@ -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> {
Expand All @@ -42,13 +46,17 @@ export class ProjectStatusService {
segments,
activityCountByDate,
healthScores,
lifecycleSummary,
] = await Promise.all([
this.projectStore.getConnectedEnvironmentCountForProject(projectId),
this.projectStore.getMembersCountByProject(projectId),
this.apiTokenStore.countProjectTokens(projectId),
this.segmentStore.getProjectSegmentCount(projectId),
this.eventStore.getProjectRecentEventActivity(projectId),
this.personalDashboardReadModel.getLatestHealthScores(projectId, 4),
this.projectLifecycleSummaryReadModel.getProjectLifecycleSummary(
projectId,
),
]);

const averageHealth = healthScores.length
Expand All @@ -65,6 +73,7 @@ export class ProjectStatusService {
},
activityCountByDate,
averageHealth,
lifecycleSummary,
};
}
}
30 changes: 30 additions & 0 deletions src/lib/features/project-status/projects-status.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
});
});
56 changes: 56 additions & 0 deletions src/lib/openapi/spec/project-status-schema.ts
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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: {
Expand Down

0 comments on commit 8493bee

Please sign in to comment.