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

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

This PR adds stale flag count to the project status payload. This is
useful for the project status page to show the number of stale flags in
the project.
This commit is contained in:
Thomas Heartman 2024-11-14 15:10:10 +01:00 committed by GitHub
parent b4e2d5d270
commit 5d32d149cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 132 additions and 12 deletions

View File

@ -35,6 +35,9 @@ const placeholderData: ProjectStatusSchema = {
last30Days: 0,
},
},
staleFlags: {
total: 0,
},
};
export const useProjectStatus = (projectId: string) => {

View File

@ -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;
};
}

View File

@ -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,
@ -40,6 +42,7 @@ export const createProjectStatusService = (
);
const projectLifecycleSummaryReadModel =
createProjectLifecycleSummaryReadModel(db, config);
const projectStaleFlagsReadModel = new ProjectStaleFlagsReadModel(db);
return new ProjectStatusService(
{
@ -50,6 +53,7 @@ export const createProjectStatusService = (
},
new PersonalDashboardReadModel(db),
projectLifecycleSummaryReadModel,
projectStaleFlagsReadModel,
);
};
@ -67,6 +71,7 @@ export const createFakeProjectStatusService = () => {
},
new FakePersonalDashboardReadModel(),
createFakeProjectLifecycleSummaryReadModel(),
new FakeProjectStaleFlagsReadModel(),
);
return {

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
export interface IProjectStaleFlagsReadModel {
getStaleFlagCountForProject: (projectId: string) => Promise<number>;
}

View File

@ -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);
}
}

View File

@ -8,6 +8,7 @@ import type {
} 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';
import type { IProjectStaleFlagsReadModel } from './project-stale-flags-read-model/project-stale-flags-read-model-type';
export class ProjectStatusService {
private eventStore: IEventStore;
@ -16,6 +17,7 @@ export class ProjectStatusService {
private segmentStore: ISegmentStore;
private personalDashboardReadModel: IPersonalDashboardReadModel;
private projectLifecycleSummaryReadModel: IProjectLifecycleSummaryReadModel;
private projectStaleFlagsReadModel: IProjectStaleFlagsReadModel;
constructor(
{
@ -29,6 +31,7 @@ export class ProjectStatusService {
>,
personalDashboardReadModel: IPersonalDashboardReadModel,
projectLifecycleReadModel: IProjectLifecycleSummaryReadModel,
projectStaleFlagsReadModel: IProjectStaleFlagsReadModel,
) {
this.eventStore = eventStore;
this.projectStore = projectStore;
@ -36,6 +39,7 @@ export class ProjectStatusService {
this.segmentStore = segmentStore;
this.personalDashboardReadModel = personalDashboardReadModel;
this.projectLifecycleSummaryReadModel = projectLifecycleReadModel;
this.projectStaleFlagsReadModel = projectStaleFlagsReadModel;
}
async getProjectStatus(projectId: string): Promise<ProjectStatusSchema> {
@ -47,6 +51,7 @@ export class ProjectStatusService {
activityCountByDate,
healthScores,
lifecycleSummary,
staleFlagCount,
] = await Promise.all([
this.projectStore.getConnectedEnvironmentCountForProject(projectId),
this.projectStore.getMembersCountByProject(projectId),
@ -57,6 +62,9 @@ export class ProjectStatusService {
this.projectLifecycleSummaryReadModel.getProjectLifecycleSummary(
projectId,
),
this.projectStaleFlagsReadModel.getStaleFlagCountForProject(
projectId,
),
]);
const averageHealth = healthScores.length
@ -74,6 +82,9 @@ export class ProjectStatusService {
activityCountByDate,
averageHealth: Math.round(averageHealth),
lifecycleSummary,
staleFlags: {
total: staleFlagCount,
},
};
}
}

View File

@ -6,6 +6,7 @@ import {
import getLogger from '../../../test/fixtures/no-logger';
import {
FEATURE_CREATED,
type IUser,
RoleName,
type IAuditUser,
type IUnleashConfig,
@ -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,
});
});

View File

@ -36,6 +36,9 @@ test('projectStatusSchema', () => {
members: 1,
segments: 0,
},
staleFlags: {
total: 0,
},
};
expect(

View File

@ -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 projects activity level over time.',
@ -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,

View File

@ -29,24 +29,20 @@ if (!response.ok) {
const data = await response.json();
data.servers = [{
data.servers = [
{
url: '<your-unleash-url>',
}];
},
];
const outputDir = './docs/generated/'
const outputDir = './docs/generated/';
// Write the JSON to file
const outputPath = path.join(outputDir, 'openapi.json')
const outputPath = path.join(outputDir, 'openapi.json');
// Ensure directory exists
await fs.mkdir(outputDir, { recursive: true });
await fs.writeFile(
outputPath,
JSON.stringify(data, null, 2),
'utf8'
);
await fs.writeFile(outputPath, JSON.stringify(data, null, 2), 'utf8');
console.log(`OpenAPI spec saved to ${outputPath}`);