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:
parent
b4e2d5d270
commit
5d32d149cd
@ -35,6 +35,9 @@ const placeholderData: ProjectStatusSchema = {
|
||||
last30Days: 0,
|
||||
},
|
||||
},
|
||||
staleFlags: {
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export const useProjectStatus = (projectId: string) => {
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export interface IProjectStaleFlagsReadModel {
|
||||
getStaleFlagCountForProject: (projectId: string) => Promise<number>;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
@ -36,6 +36,9 @@ test('projectStatusSchema', () => {
|
||||
members: 1,
|
||||
segments: 0,
|
||||
},
|
||||
staleFlags: {
|
||||
total: 0,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
|
@ -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.',
|
||||
@ -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,
|
||||
|
@ -29,24 +29,20 @@ if (!response.ok) {
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
data.servers = [{
|
||||
url: '<your-unleash-url>',
|
||||
}];
|
||||
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}`);
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user