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:
parent
d6f5280a98
commit
137b8ee260
@ -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 [];
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -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[]>;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user