mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: personal dashboard project avg health scores (#8328)
This commit is contained in:
parent
a874ac085d
commit
f47ae12263
@ -8,6 +8,13 @@ import type {
|
||||
export class FakePersonalDashboardReadModel
|
||||
implements IPersonalDashboardReadModel
|
||||
{
|
||||
async getLatestHealthScores(
|
||||
project: string,
|
||||
count: number,
|
||||
): Promise<number[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
|
||||
return [];
|
||||
}
|
||||
|
@ -227,6 +227,10 @@ test('should return personal dashboard project details', async () => {
|
||||
);
|
||||
|
||||
expect(body).toMatchObject({
|
||||
insights: {
|
||||
avgHealthCurrentWindow: null,
|
||||
avgHealthPastWindow: null,
|
||||
},
|
||||
owners: [{}],
|
||||
roles: [{}],
|
||||
onboardingStatus: {
|
||||
@ -260,6 +264,41 @@ test('should return personal dashboard project details', async () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const insertHealthScore = (id: string, health: number) => {
|
||||
const irrelevantFlagTrendDetails = {
|
||||
total_flags: 10,
|
||||
stale_flags: 10,
|
||||
potentially_stale_flags: 10,
|
||||
};
|
||||
return db.rawDatabase('flag_trends').insert({
|
||||
...irrelevantFlagTrendDetails,
|
||||
id,
|
||||
project: project.id,
|
||||
health,
|
||||
});
|
||||
};
|
||||
|
||||
await insertHealthScore('2024-01', 80);
|
||||
await insertHealthScore('2024-02', 80);
|
||||
await insertHealthScore('2024-03', 80);
|
||||
await insertHealthScore('2024-04', 81);
|
||||
|
||||
await insertHealthScore('2024-05', 90);
|
||||
await insertHealthScore('2024-06', 91);
|
||||
await insertHealthScore('2024-07', 91);
|
||||
await insertHealthScore('2024-08', 91);
|
||||
|
||||
const { body: bodyWithHealthScores } = await app.request.get(
|
||||
`/api/admin/personal-dashboard/${project.id}`,
|
||||
);
|
||||
|
||||
expect(bodyWithHealthScores).toMatchObject({
|
||||
insights: {
|
||||
avgHealthPastWindow: 80,
|
||||
avgHealthCurrentWindow: 91,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should return Unleash admins', async () => {
|
||||
|
@ -21,4 +21,5 @@ export type PersonalProject = BasePersonalProject & {
|
||||
export interface IPersonalDashboardReadModel {
|
||||
getPersonalFeatures(userId: number): Promise<PersonalFeature[]>;
|
||||
getPersonalProjects(userId: number): Promise<BasePersonalProject[]>;
|
||||
getLatestHealthScores(project: string, count: number): Promise<number[]>;
|
||||
}
|
||||
|
@ -19,6 +19,19 @@ export class PersonalDashboardReadModel implements IPersonalDashboardReadModel {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
async getLatestHealthScores(
|
||||
project: string,
|
||||
count: number,
|
||||
): Promise<number[]> {
|
||||
const results = await this.db<{ health: number }>('flag_trends')
|
||||
.select('health')
|
||||
.orderBy('created_at', 'desc')
|
||||
.where('project', project)
|
||||
.limit(count);
|
||||
|
||||
return results.map((row) => Number(row.health));
|
||||
}
|
||||
|
||||
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
|
||||
const result = await this.db<{
|
||||
name: string;
|
||||
|
@ -136,11 +136,39 @@ export class PersonalDashboardService {
|
||||
type: role.type as PersonalDashboardProjectDetailsSchema['roles'][number]['type'],
|
||||
}));
|
||||
|
||||
const healthScores =
|
||||
await this.personalDashboardReadModel.getLatestHealthScores(
|
||||
projectId,
|
||||
8,
|
||||
);
|
||||
let avgHealthCurrentWindow: number | null = null;
|
||||
let avgHealthPastWindow: number | null = null;
|
||||
|
||||
if (healthScores.length >= 4) {
|
||||
avgHealthCurrentWindow = Math.round(
|
||||
healthScores
|
||||
.slice(0, 4)
|
||||
.reduce((acc, score) => acc + score, 0) / 4,
|
||||
);
|
||||
}
|
||||
|
||||
if (healthScores.length >= 8) {
|
||||
avgHealthPastWindow = Math.round(
|
||||
healthScores
|
||||
.slice(4, 8)
|
||||
.reduce((acc, score) => acc + score, 0) / 4,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
latestEvents: formattedEvents,
|
||||
onboardingStatus,
|
||||
owners,
|
||||
roles: projectRoles,
|
||||
insights: {
|
||||
avgHealthCurrentWindow,
|
||||
avgHealthPastWindow,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,36 @@ export const personalDashboardProjectDetailsSchema = {
|
||||
type: 'object',
|
||||
description: 'Project details in personal dashboard',
|
||||
additionalProperties: false,
|
||||
required: ['owners', 'roles', 'latestEvents', 'onboardingStatus'],
|
||||
required: [
|
||||
'owners',
|
||||
'roles',
|
||||
'latestEvents',
|
||||
'onboardingStatus',
|
||||
'insights',
|
||||
],
|
||||
properties: {
|
||||
insights: {
|
||||
type: 'object',
|
||||
description: 'Insights for the project',
|
||||
additionalProperties: false,
|
||||
required: ['avgHealthCurrentWindow', 'avgHealthPastWindow'],
|
||||
properties: {
|
||||
avgHealthCurrentWindow: {
|
||||
type: 'number',
|
||||
description:
|
||||
'The average health score in the current window of the last 4 weeks',
|
||||
example: 80,
|
||||
nullable: true,
|
||||
},
|
||||
avgHealthPastWindow: {
|
||||
type: 'number',
|
||||
description:
|
||||
'The average health score in the previous 4 weeks before the current window',
|
||||
example: 70,
|
||||
nullable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
onboardingStatus: projectOverviewSchema.properties.onboardingStatus,
|
||||
latestEvents: {
|
||||
type: 'array',
|
||||
|
Loading…
Reference in New Issue
Block a user