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

feat: project details for personal dashboard (#8274)

This commit is contained in:
Mateusz Kwasniewski 2024-09-26 13:43:51 +02:00 committed by GitHub
parent d6f5280a98
commit 137b8ee260
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 99 deletions

View File

@ -1,7 +1,7 @@
import type {
BasePersonalProject,
IPersonalDashboardReadModel,
PersonalFeature,
PersonalProject,
} from './personal-dashboard-read-model-type';
export class FakePersonalDashboardReadModel
@ -11,7 +11,7 @@ export class FakePersonalDashboardReadModel
return [];
}
async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
return [];
}
}

View File

@ -103,58 +103,22 @@ test('should return personal dashboard with membered projects', async () => {
{
name: 'Default',
id: 'default',
roles: [
{
name: 'Editor',
id: 2,
type: 'root',
},
],
owners: [
{
ownerType: 'system',
},
],
health: 100,
memberCount: 0,
featureCount: 0,
},
{
name: projectA.name,
id: projectA.id,
roles: [
{
name: 'Member',
id: 5,
type: 'project',
},
],
owners: [
{
email: 'user1@test.com',
imageUrl:
'https://gravatar.com/avatar/a8cc79d8407a64b0d8982df34e3525afd298a479fe68f300651380730dbf23e9?s=42&d=retro&r=g',
name: 'user1@test.com',
ownerType: 'user',
},
],
health: 100,
memberCount: 2,
featureCount: 0,
},
{
name: projectC.name,
id: projectC.id,
roles: [
{
name: 'Owner',
id: 4,
type: 'project',
},
],
owners: [
{
email: 'user2@test.com',
imageUrl:
'https://gravatar.com/avatar/706150f3ef810ea66acb30c6d55f1a7e545338747072609e47df71c7c7ccc6a4?s=42&d=retro&r=g',
name: 'user2@test.com',
ownerType: 'user',
},
],
memberCount: 1,
featureCount: 0,
},
],
});
@ -197,47 +161,16 @@ test('should return projects where users are part of a group', async () => {
{
name: 'Default',
id: 'default',
roles: [
{
name: 'Editor',
id: 2,
type: 'root',
},
],
owners: [
{
ownerType: 'system',
},
],
health: 100,
memberCount: 0,
featureCount: 0,
},
{
name: projectA.name,
id: projectA.id,
roles: [
{
name: 'Owner',
id: 4,
type: 'project',
},
{
name: 'Member',
id: 5,
type: 'project',
},
],
owners: [
{
email: 'user1@test.com',
imageUrl:
'https://gravatar.com/avatar/a8cc79d8407a64b0d8982df34e3525afd298a479fe68f300651380730dbf23e9?s=42&d=retro&r=g',
name: 'user1@test.com',
ownerType: 'user',
},
{
name: 'groupA',
ownerType: 'group',
},
],
health: 100,
memberCount: 2,
featureCount: 0,
},
],
});

View File

@ -1,20 +1,24 @@
import type { ProjectOwners } from '../project/project-owners-read-model.type';
export type PersonalFeature = { name: string; type: string; project: string };
export type PersonalProject = {
export type BasePersonalProject = {
name: string;
id: string;
roles: {
roles?: {
name: string;
id: number;
type: 'custom' | 'project' | 'root' | 'custom-root';
}[];
};
export type PersonalProjectWithOwners = PersonalProject & {
owners: ProjectOwners;
export type PersonalProject = BasePersonalProject & {
owners?: ProjectOwners;
} & {
health: number;
memberCount: number;
featureCount: number;
};
export interface IPersonalDashboardReadModel {
getPersonalFeatures(userId: number): Promise<PersonalFeature[]>;
getPersonalProjects(userId: number): Promise<PersonalProject[]>;
getPersonalProjects(userId: number): Promise<BasePersonalProject[]>;
}

View File

@ -1,5 +1,6 @@
import type { Db } from '../../db/db';
import type {
BasePersonalProject,
IPersonalDashboardReadModel,
PersonalFeature,
PersonalProject,
@ -18,7 +19,7 @@ export class PersonalDashboardReadModel implements IPersonalDashboardReadModel {
this.db = db;
}
async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
const result = await this.db<{
name: string;
id: string;

View File

@ -2,35 +2,48 @@ import type { IProjectOwnersReadModel } from '../project/project-owners-read-mod
import type {
IPersonalDashboardReadModel,
PersonalFeature,
PersonalProjectWithOwners,
PersonalProject,
} from './personal-dashboard-read-model-type';
import type { IProjectReadModel } from '../project/project-read-model-type';
export class PersonalDashboardService {
private personalDashboardReadModel: IPersonalDashboardReadModel;
private projectOwnersReadModel: IProjectOwnersReadModel;
private projectReadModel: IProjectReadModel;
constructor(
personalDashboardReadModel: IPersonalDashboardReadModel,
projectOwnersReadModel: IProjectOwnersReadModel,
projectReadModel: IProjectReadModel,
) {
this.personalDashboardReadModel = personalDashboardReadModel;
this.projectOwnersReadModel = projectOwnersReadModel;
this.projectReadModel = projectReadModel;
}
getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
return this.personalDashboardReadModel.getPersonalFeatures(userId);
}
async getPersonalProjects(
userId: number,
): Promise<PersonalProjectWithOwners[]> {
const projects =
await this.personalDashboardReadModel.getPersonalProjects(userId);
async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
// TODO: add favorite projects in addition to membership projects
const userProjectIds =
await this.projectReadModel.getProjectsByUser(userId);
const withOwners =
await this.projectOwnersReadModel.addOwners(projects);
const projects = await this.projectReadModel.getProjectsForAdminUi({
ids: userProjectIds,
});
return withOwners;
const normalizedProjects = projects.map((project) => ({
id: project.id,
name: project.name,
health: project.health,
memberCount: project.memberCount,
featureCount: project.featureCount,
}));
return normalizedProjects;
}
}

View File

@ -13,7 +13,13 @@ export const personalDashboardSchema = {
items: {
type: 'object',
additionalProperties: false,
required: ['id', 'name', 'roles'],
required: [
'id',
'name',
'health',
'memberCount',
'featureCount',
],
properties: {
id: {
type: 'string',
@ -25,6 +31,22 @@ export const personalDashboardSchema = {
example: 'My Project',
description: 'The name of the project',
},
health: {
type: 'number',
example: 50,
description:
"An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100",
},
memberCount: {
type: 'number',
example: 4,
description: 'The number of members this project has',
},
featureCount: {
type: 'number',
example: 10,
description: 'The number of features this project has',
},
owners: projectSchema.properties.owners,
roles: {
type: 'array',

View File

@ -151,6 +151,8 @@ import { PersonalDashboardReadModel } from '../features/personal-dashboard/perso
import { FakePersonalDashboardReadModel } from '../features/personal-dashboard/fake-personal-dashboard-read-model';
import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
import { FakeProjectOwnersReadModel } from '../features/project/fake-project-owners-read-model';
import { FakeProjectReadModel } from '../features/project/fake-project-read-model';
import { ProjectReadModel } from '../features/project/project-read-model';
export const createServices = (
stores: IUnleashStores,
@ -406,12 +408,16 @@ export const createServices = (
: createFakeOnboardingService(config).onboardingService;
onboardingService.listen();
// TODO: move to composition root
const personalDashboardService = new PersonalDashboardService(
db
? new PersonalDashboardReadModel(db)
: new FakePersonalDashboardReadModel(),
db ? new ProjectOwnersReadModel(db) : new FakeProjectOwnersReadModel(),
db
? new ProjectReadModel(db, config.eventBus, config.flagResolver)
: new FakeProjectReadModel(),
);
return {