mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
feat: project details for personal dashboard (#8274)
This commit is contained in:
parent
d6f5280a98
commit
137b8ee260
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
|
BasePersonalProject,
|
||||||
IPersonalDashboardReadModel,
|
IPersonalDashboardReadModel,
|
||||||
PersonalFeature,
|
PersonalFeature,
|
||||||
PersonalProject,
|
|
||||||
} from './personal-dashboard-read-model-type';
|
} from './personal-dashboard-read-model-type';
|
||||||
|
|
||||||
export class FakePersonalDashboardReadModel
|
export class FakePersonalDashboardReadModel
|
||||||
@ -11,7 +11,7 @@ export class FakePersonalDashboardReadModel
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
|
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,58 +103,22 @@ test('should return personal dashboard with membered projects', async () => {
|
|||||||
{
|
{
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
id: 'default',
|
id: 'default',
|
||||||
roles: [
|
health: 100,
|
||||||
{
|
memberCount: 0,
|
||||||
name: 'Editor',
|
featureCount: 0,
|
||||||
id: 2,
|
|
||||||
type: 'root',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
owners: [
|
|
||||||
{
|
|
||||||
ownerType: 'system',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: projectA.name,
|
name: projectA.name,
|
||||||
id: projectA.id,
|
id: projectA.id,
|
||||||
roles: [
|
health: 100,
|
||||||
{
|
memberCount: 2,
|
||||||
name: 'Member',
|
featureCount: 0,
|
||||||
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: projectC.name,
|
name: projectC.name,
|
||||||
id: projectC.id,
|
id: projectC.id,
|
||||||
roles: [
|
memberCount: 1,
|
||||||
{
|
featureCount: 0,
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -197,47 +161,16 @@ test('should return projects where users are part of a group', async () => {
|
|||||||
{
|
{
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
id: 'default',
|
id: 'default',
|
||||||
roles: [
|
health: 100,
|
||||||
{
|
memberCount: 0,
|
||||||
name: 'Editor',
|
featureCount: 0,
|
||||||
id: 2,
|
|
||||||
type: 'root',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
owners: [
|
|
||||||
{
|
|
||||||
ownerType: 'system',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: projectA.name,
|
name: projectA.name,
|
||||||
id: projectA.id,
|
id: projectA.id,
|
||||||
roles: [
|
health: 100,
|
||||||
{
|
memberCount: 2,
|
||||||
name: 'Owner',
|
featureCount: 0,
|
||||||
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',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import type { ProjectOwners } from '../project/project-owners-read-model.type';
|
import type { ProjectOwners } from '../project/project-owners-read-model.type';
|
||||||
|
|
||||||
export type PersonalFeature = { name: string; type: string; project: string };
|
export type PersonalFeature = { name: string; type: string; project: string };
|
||||||
export type PersonalProject = {
|
export type BasePersonalProject = {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
roles: {
|
roles?: {
|
||||||
name: string;
|
name: string;
|
||||||
id: number;
|
id: number;
|
||||||
type: 'custom' | 'project' | 'root' | 'custom-root';
|
type: 'custom' | 'project' | 'root' | 'custom-root';
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
export type PersonalProjectWithOwners = PersonalProject & {
|
export type PersonalProject = BasePersonalProject & {
|
||||||
owners: ProjectOwners;
|
owners?: ProjectOwners;
|
||||||
|
} & {
|
||||||
|
health: number;
|
||||||
|
memberCount: number;
|
||||||
|
featureCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IPersonalDashboardReadModel {
|
export interface IPersonalDashboardReadModel {
|
||||||
getPersonalFeatures(userId: number): Promise<PersonalFeature[]>;
|
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 { Db } from '../../db/db';
|
||||||
import type {
|
import type {
|
||||||
|
BasePersonalProject,
|
||||||
IPersonalDashboardReadModel,
|
IPersonalDashboardReadModel,
|
||||||
PersonalFeature,
|
PersonalFeature,
|
||||||
PersonalProject,
|
PersonalProject,
|
||||||
@ -18,7 +19,7 @@ export class PersonalDashboardReadModel implements IPersonalDashboardReadModel {
|
|||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
|
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
|
||||||
const result = await this.db<{
|
const result = await this.db<{
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -2,35 +2,48 @@ import type { IProjectOwnersReadModel } from '../project/project-owners-read-mod
|
|||||||
import type {
|
import type {
|
||||||
IPersonalDashboardReadModel,
|
IPersonalDashboardReadModel,
|
||||||
PersonalFeature,
|
PersonalFeature,
|
||||||
PersonalProjectWithOwners,
|
PersonalProject,
|
||||||
} from './personal-dashboard-read-model-type';
|
} from './personal-dashboard-read-model-type';
|
||||||
|
import type { IProjectReadModel } from '../project/project-read-model-type';
|
||||||
|
|
||||||
export class PersonalDashboardService {
|
export class PersonalDashboardService {
|
||||||
private personalDashboardReadModel: IPersonalDashboardReadModel;
|
private personalDashboardReadModel: IPersonalDashboardReadModel;
|
||||||
|
|
||||||
private projectOwnersReadModel: IProjectOwnersReadModel;
|
private projectOwnersReadModel: IProjectOwnersReadModel;
|
||||||
|
|
||||||
|
private projectReadModel: IProjectReadModel;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
personalDashboardReadModel: IPersonalDashboardReadModel,
|
personalDashboardReadModel: IPersonalDashboardReadModel,
|
||||||
projectOwnersReadModel: IProjectOwnersReadModel,
|
projectOwnersReadModel: IProjectOwnersReadModel,
|
||||||
|
projectReadModel: IProjectReadModel,
|
||||||
) {
|
) {
|
||||||
this.personalDashboardReadModel = personalDashboardReadModel;
|
this.personalDashboardReadModel = personalDashboardReadModel;
|
||||||
this.projectOwnersReadModel = projectOwnersReadModel;
|
this.projectOwnersReadModel = projectOwnersReadModel;
|
||||||
|
this.projectReadModel = projectReadModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
|
getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
|
||||||
return this.personalDashboardReadModel.getPersonalFeatures(userId);
|
return this.personalDashboardReadModel.getPersonalFeatures(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPersonalProjects(
|
async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
|
||||||
userId: number,
|
// TODO: add favorite projects in addition to membership projects
|
||||||
): Promise<PersonalProjectWithOwners[]> {
|
const userProjectIds =
|
||||||
const projects =
|
await this.projectReadModel.getProjectsByUser(userId);
|
||||||
await this.personalDashboardReadModel.getPersonalProjects(userId);
|
|
||||||
|
|
||||||
const withOwners =
|
const projects = await this.projectReadModel.getProjectsForAdminUi({
|
||||||
await this.projectOwnersReadModel.addOwners(projects);
|
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: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: ['id', 'name', 'roles'],
|
required: [
|
||||||
|
'id',
|
||||||
|
'name',
|
||||||
|
'health',
|
||||||
|
'memberCount',
|
||||||
|
'featureCount',
|
||||||
|
],
|
||||||
properties: {
|
properties: {
|
||||||
id: {
|
id: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -25,6 +31,22 @@ export const personalDashboardSchema = {
|
|||||||
example: 'My Project',
|
example: 'My Project',
|
||||||
description: 'The name of the 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,
|
owners: projectSchema.properties.owners,
|
||||||
roles: {
|
roles: {
|
||||||
type: 'array',
|
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 { FakePersonalDashboardReadModel } from '../features/personal-dashboard/fake-personal-dashboard-read-model';
|
||||||
import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
|
import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
|
||||||
import { FakeProjectOwnersReadModel } from '../features/project/fake-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 = (
|
export const createServices = (
|
||||||
stores: IUnleashStores,
|
stores: IUnleashStores,
|
||||||
@ -406,12 +408,16 @@ export const createServices = (
|
|||||||
: createFakeOnboardingService(config).onboardingService;
|
: createFakeOnboardingService(config).onboardingService;
|
||||||
onboardingService.listen();
|
onboardingService.listen();
|
||||||
|
|
||||||
|
// TODO: move to composition root
|
||||||
const personalDashboardService = new PersonalDashboardService(
|
const personalDashboardService = new PersonalDashboardService(
|
||||||
db
|
db
|
||||||
? new PersonalDashboardReadModel(db)
|
? new PersonalDashboardReadModel(db)
|
||||||
: new FakePersonalDashboardReadModel(),
|
: new FakePersonalDashboardReadModel(),
|
||||||
|
|
||||||
db ? new ProjectOwnersReadModel(db) : new FakeProjectOwnersReadModel(),
|
db ? new ProjectOwnersReadModel(db) : new FakeProjectOwnersReadModel(),
|
||||||
|
db
|
||||||
|
? new ProjectReadModel(db, config.eventBus, config.flagResolver)
|
||||||
|
: new FakeProjectReadModel(),
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
Loading…
Reference in New Issue
Block a user