Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add stale flag count to project status payload #8751

Merged
merged 10 commits into from
Nov 14, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ const placeholderData: ProjectStatusSchema = {
last30Days: 0,
},
},
staleFlags: {
total: 0,
},
};

export const useProjectStatus = (projectId: string) => {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/openapi/models/projectStatusSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ export interface ProjectStatusSchema {
lifecycleSummary: ProjectStatusSchemaLifecycleSummary;
/** Key resources within the project */
resources: ProjectStatusSchemaResources;
/** Information on stale and potentially stale flags in this project. */
staleFlags: {
/** The total number of flags in this project that are stale or potentially stale. */
total: number;
};
}
5 changes: 5 additions & 0 deletions src/lib/features/project-status/createProjectStatusService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
createFakeProjectLifecycleSummaryReadModel,
createProjectLifecycleSummaryReadModel,
} from './project-lifecycle-read-model/createProjectLifecycleSummaryReadModel';
import { ProjectStaleFlagsReadModel } from './project-stale-flags-read-model/project-stale-flags-read-model';
import { FakeProjectStaleFlagsReadModel } from './project-stale-flags-read-model/fake-project-stale-flags-read-model';

export const createProjectStatusService = (
db: Db,
Expand All @@ -40,6 +42,7 @@ export const createProjectStatusService = (
);
const projectLifecycleSummaryReadModel =
createProjectLifecycleSummaryReadModel(db, config);
const projectStaleFlagsReadModel = new ProjectStaleFlagsReadModel(db);

return new ProjectStatusService(
{
Expand All @@ -50,6 +53,7 @@ export const createProjectStatusService = (
},
new PersonalDashboardReadModel(db),
projectLifecycleSummaryReadModel,
projectStaleFlagsReadModel,
);
};

Expand All @@ -67,6 +71,7 @@ export const createFakeProjectStatusService = () => {
},
new FakePersonalDashboardReadModel(),
createFakeProjectLifecycleSummaryReadModel(),
new FakeProjectStaleFlagsReadModel(),
);

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { IProjectStaleFlagsReadModel } from './project-stale-flags-read-model-type';

export class FakeProjectStaleFlagsReadModel
implements IProjectStaleFlagsReadModel
{
async getStaleFlagCountForProject(): Promise<number> {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IProjectStaleFlagsReadModel {
getStaleFlagCountForProject: (projectId: string) => Promise<number>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Db } from '../../../server-impl';
import type { IProjectStaleFlagsReadModel } from './project-stale-flags-read-model-type';

export class ProjectStaleFlagsReadModel implements IProjectStaleFlagsReadModel {
constructor(private db: Db) {}

async getStaleFlagCountForProject(projectId: string): Promise<number> {
const result = await this.db('features')
.count()
.where({ project: projectId, archived: false })
.where((builder) =>
builder
.orWhere({ stale: true })
.orWhere({ potentially_stale: true }),
);

return Number(result[0].count);
}
}
11 changes: 11 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,6 +7,7 @@ import type {
IUnleashStores,
} from '../../types';
import type { IPersonalDashboardReadModel } from '../personal-dashboard/personal-dashboard-read-model-type';
import type { IProjectStaleFlagsReadModel } from './IProjectStaleFlagsReadModel';
import type { IProjectLifecycleSummaryReadModel } from './project-lifecycle-read-model/project-lifecycle-read-model-type';

export class ProjectStatusService {
Expand All @@ -16,6 +17,7 @@ export class ProjectStatusService {
private segmentStore: ISegmentStore;
private personalDashboardReadModel: IPersonalDashboardReadModel;
private projectLifecycleSummaryReadModel: IProjectLifecycleSummaryReadModel;
private projectStaleFlagsReadModel: IProjectStaleFlagsReadModel;

constructor(
{
Expand All @@ -29,13 +31,15 @@ export class ProjectStatusService {
>,
personalDashboardReadModel: IPersonalDashboardReadModel,
projectLifecycleReadModel: IProjectLifecycleSummaryReadModel,
projectStaleFlagsReadModel: IProjectStaleFlagsReadModel,
) {
this.eventStore = eventStore;
this.projectStore = projectStore;
this.apiTokenStore = apiTokenStore;
this.segmentStore = segmentStore;
this.personalDashboardReadModel = personalDashboardReadModel;
this.projectLifecycleSummaryReadModel = projectLifecycleReadModel;
this.projectStaleFlagsReadModel = projectStaleFlagsReadModel;
}

async getProjectStatus(projectId: string): Promise<ProjectStatusSchema> {
Expand All @@ -47,6 +51,7 @@ export class ProjectStatusService {
activityCountByDate,
healthScores,
lifecycleSummary,
staleFlagCount,
] = await Promise.all([
this.projectStore.getConnectedEnvironmentCountForProject(projectId),
this.projectStore.getMembersCountByProject(projectId),
Expand All @@ -57,6 +62,9 @@ export class ProjectStatusService {
this.projectLifecycleSummaryReadModel.getProjectLifecycleSummary(
projectId,
),
this.projectStaleFlagsReadModel.getStaleFlagCountForProject(
projectId,
),
]);

const averageHealth = healthScores.length
Expand All @@ -74,6 +82,9 @@ export class ProjectStatusService {
activityCountByDate,
averageHealth: Math.round(averageHealth),
lifecycleSummary,
staleFlags: {
total: staleFlagCount,
},
};
}
}
50 changes: 50 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 @@ -6,6 +6,7 @@ import {
import getLogger from '../../../test/fixtures/no-logger';
import {
FEATURE_CREATED,
type IUser,
RoleName,
type IAuditUser,
type IUnleashConfig,
Expand Down Expand Up @@ -300,3 +301,52 @@ test('project status contains lifecycle data', async () => {
},
});
});

test('project status includes stale flags', async () => {
const otherProject = await app.services.projectService.createProject(
{
name: 'otherProject',
id: randomId(),
},
{} as IUser,
{} as IAuditUser,
);
const createFlagInState = async (
name: string,
state?: Object,
projectId?: string,
) => {
await app.createFeature(name, projectId);
if (state) {
await db.rawDatabase('features').update(state).where({ name });
}
};

await createFlagInState('stale-flag', { stale: true });
await createFlagInState('potentially-stale-flag', {
potentially_stale: true,
});
await createFlagInState('potentially-stale-and-stale-flag', {
potentially_stale: true,
stale: true,
});
await createFlagInState('non-stale-flag');
await createFlagInState('archived-stale-flag', {
archived: true,
stale: true,
});
await createFlagInState(
'stale-other-project',
{ stale: true },
otherProject.id,
);

const { body } = await app.request
.get('/api/admin/projects/default/status')
.expect('Content-Type', /json/)
.expect(200);

expect(body.staleFlags).toMatchObject({
total: 3,
});
});
3 changes: 3 additions & 0 deletions src/lib/openapi/spec/project-status-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ test('projectStatusSchema', () => {
members: 1,
segments: 0,
},
staleFlags: {
total: 0,
},
};

expect(
Expand Down
16 changes: 16 additions & 0 deletions src/lib/openapi/spec/project-status-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const projectStatusSchema = {
'resources',
'averageHealth',
'lifecycleSummary',
'staleFlags',
],
description:
'Schema representing the overall status of a project, including an array of activity records. Each record in the activity array contains a date and a count, providing a snapshot of the project’s activity level over time.',
Expand Down Expand Up @@ -85,6 +86,21 @@ export const projectStatusSchema = {
},
},
},
staleFlags: {
type: 'object',
additionalProperties: false,
description:
'Information on stale and potentially stale flags in this project.',
required: ['total'],
properties: {
total: {
type: 'integer',
minimum: 0,
description:
'The total number of flags in this project that are stale or potentially stale.',
},
},
},
lifecycleSummary: {
type: 'object',
additionalProperties: false,
Expand Down
Loading