Skip to content

Commit

Permalink
feat: project details for personal dashboard (#8274)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Sep 26, 2024
1 parent d6f5280 commit 137b8ee
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 99 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {
BasePersonalProject,
IPersonalDashboardReadModel,
PersonalFeature,
PersonalProject,
} from './personal-dashboard-read-model-type';

export class FakePersonalDashboardReadModel
Expand All @@ -11,7 +11,7 @@ export class FakePersonalDashboardReadModel
return [];
}

async getPersonalProjects(userId: number): Promise<PersonalProject[]> {
async getPersonalProjects(userId: number): Promise<BasePersonalProject[]> {
return [];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]',
imageUrl:
'https://gravatar.com/avatar/a8cc79d8407a64b0d8982df34e3525afd298a479fe68f300651380730dbf23e9?s=42&d=retro&r=g',
name: '[email protected]',
ownerType: 'user',
},
],
health: 100,
memberCount: 2,
featureCount: 0,
},
{
name: projectC.name,
id: projectC.id,
roles: [
{
name: 'Owner',
id: 4,
type: 'project',
},
],
owners: [
{
email: '[email protected]',
imageUrl:
'https://gravatar.com/avatar/706150f3ef810ea66acb30c6d55f1a7e545338747072609e47df71c7c7ccc6a4?s=42&d=retro&r=g',
name: '[email protected]',
ownerType: 'user',
},
],
memberCount: 1,
featureCount: 0,
},
],
});
Expand Down Expand Up @@ -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: '[email protected]',
imageUrl:
'https://gravatar.com/avatar/a8cc79d8407a64b0d8982df34e3525afd298a479fe68f300651380730dbf23e9?s=42&d=retro&r=g',
name: '[email protected]',
ownerType: 'user',
},
{
name: 'groupA',
ownerType: 'group',
},
],
health: 100,
memberCount: 2,
featureCount: 0,
},
],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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[]>;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Db } from '../../db/db';
import type {
BasePersonalProject,
IPersonalDashboardReadModel,
PersonalFeature,
PersonalProject,
Expand All @@ -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;
Expand Down
31 changes: 22 additions & 9 deletions src/lib/features/personal-dashboard/personal-dashboard-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 projects = await this.projectReadModel.getProjectsForAdminUi({
ids: userProjectIds,
});

const withOwners =
await this.projectOwnersReadModel.addOwners(projects);
const normalizedProjects = projects.map((project) => ({
id: project.id,
name: project.name,
health: project.health,
memberCount: project.memberCount,
featureCount: project.featureCount,
}));

return withOwners;
return normalizedProjects;
}
}
24 changes: 23 additions & 1 deletion src/lib/openapi/spec/personal-dashboard-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand Down
6 changes: 6 additions & 0 deletions src/lib/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 137b8ee

Please sign in to comment.