Skip to content

Commit

Permalink
feat: add project owners to personal dashboard project payload (#8248)
Browse files Browse the repository at this point in the history
This PR adds project owner information to the personal dashboard's
project payload.

To do so, it uses the existing project owners read model.

I've had to make a few changes to the project owners read model to
accomodate this:
- make the input type to `addOwners` more lenient. We only need the
project ids, so we can make that the only required property
- fall back to using email as the name if the user has no name or
username (such as if you sign up with the demo auth)
  • Loading branch information
thomasheartman authored Sep 25, 2024
1 parent e680921 commit 44bf661
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ test('should return personal dashboard with membered projects', async () => {
type: 'root',
},
],
owners: [
{
ownerType: 'system',
},
],
},
{
name: projectA.name,
Expand All @@ -121,6 +126,15 @@ test('should return personal dashboard with membered projects', async () => {
type: 'project',
},
],
owners: [
{
email: '[email protected]',
imageUrl:
'https://gravatar.com/avatar/a8cc79d8407a64b0d8982df34e3525afd298a479fe68f300651380730dbf23e9?s=42&d=retro&r=g',
name: '[email protected]',
ownerType: 'user',
},
],
},
{
name: projectC.name,
Expand All @@ -132,6 +146,15 @@ test('should return personal dashboard with membered projects', async () => {
type: 'project',
},
],
owners: [
{
email: '[email protected]',
imageUrl:
'https://gravatar.com/avatar/706150f3ef810ea66acb30c6d55f1a7e545338747072609e47df71c7c7ccc6a4?s=42&d=retro&r=g',
name: '[email protected]',
ownerType: 'user',
},
],
},
],
});
Expand Down Expand Up @@ -181,6 +204,11 @@ test('should return projects where users are part of a group', async () => {
type: 'root',
},
],
owners: [
{
ownerType: 'system',
},
],
},
{
name: projectA.name,
Expand All @@ -197,6 +225,19 @@ test('should return projects where users are part of a group', async () => {
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',
},
],
},
],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { ProjectOwners } from '../project/project-owners-read-model.type';

export type PersonalFeature = { name: string; type: string; project: string };
export type PersonalProject = {
name: string;
Expand All @@ -8,6 +10,9 @@ export type PersonalProject = {
type: 'custom' | 'project' | 'root' | 'custom-root';
}[];
};
export type PersonalProjectWithOwners = PersonalProject & {
owners: ProjectOwners;
};

export interface IPersonalDashboardReadModel {
getPersonalFeatures(userId: number): Promise<PersonalFeature[]>;
Expand Down
23 changes: 19 additions & 4 deletions src/lib/features/personal-dashboard/personal-dashboard-service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import type { IProjectOwnersReadModel } from '../project/project-owners-read-model.type';
import type {
IPersonalDashboardReadModel,
PersonalFeature,
PersonalProject,
PersonalProjectWithOwners,
} from './personal-dashboard-read-model-type';

export class PersonalDashboardService {
private personalDashboardReadModel: IPersonalDashboardReadModel;

constructor(personalDashboardReadModel: IPersonalDashboardReadModel) {
private projectOwnersReadModel: IProjectOwnersReadModel;

constructor(
personalDashboardReadModel: IPersonalDashboardReadModel,
projectOwnersReadModel: IProjectOwnersReadModel,
) {
this.personalDashboardReadModel = personalDashboardReadModel;
this.projectOwnersReadModel = projectOwnersReadModel;
}

getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
return this.personalDashboardReadModel.getPersonalFeatures(userId);
}

getPersonalProjects(userId: number): Promise<PersonalProject[]> {
return this.personalDashboardReadModel.getPersonalProjects(userId);
async getPersonalProjects(
userId: number,
): Promise<PersonalProjectWithOwners[]> {
const projects =
await this.personalDashboardReadModel.getPersonalProjects(userId);

const withOwners =
await this.projectOwnersReadModel.addOwners(projects);

return withOwners;
}
}
9 changes: 4 additions & 5 deletions src/lib/features/project/fake-project-owners-read-model.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type {
IProjectOwnersReadModel,
IProjectForUiWithOwners,
WithProjectOwners,
} from './project-owners-read-model.type';
import type { ProjectForUi } from './project-read-model-type';

export class FakeProjectOwnersReadModel implements IProjectOwnersReadModel {
async addOwners(
projects: ProjectForUi[],
): Promise<IProjectForUiWithOwners[]> {
async addOwners<T extends { id: string }>(
projects: T[],
): Promise<WithProjectOwners<T>> {
return projects.map((project) => ({
...project,
owners: [{ ownerType: 'system' }],
Expand Down
20 changes: 11 additions & 9 deletions src/lib/features/project/project-owners-read-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { anonymise, generateImageUrl } from '../../util';
import type {
GroupProjectOwner,
IProjectOwnersReadModel,
IProjectForUiWithOwners,
ProjectOwnersDictionary,
UserProjectOwner,
WithProjectOwners,
} from './project-owners-read-model.type';
import type { ProjectForUi } from './project-read-model-type';

const T = {
ROLE_USER: 'role_user',
Expand All @@ -24,10 +23,10 @@ export class ProjectOwnersReadModel implements IProjectOwnersReadModel {
this.db = db;
}

static addOwnerData(
projects: ProjectForUi[],
static addOwnerData<T extends { id: string }>(
projects: T[],
owners: ProjectOwnersDictionary,
): IProjectForUiWithOwners[] {
): WithProjectOwners<T> {
return projects.map((project) => ({
...project,
owners: owners[project.id] || [{ ownerType: 'system' }],
Expand Down Expand Up @@ -64,7 +63,10 @@ export class ProjectOwnersReadModel implements IProjectOwnersReadModel {

const data: UserProjectOwner = {
ownerType: 'user',
name: user?.name || user?.username,
name:
user?.name ||
user?.username ||
processSensitiveData(user?.email),
email: processSensitiveData(user?.email),
imageUrl: generateImageUrl(user),
};
Expand Down Expand Up @@ -138,10 +140,10 @@ export class ProjectOwnersReadModel implements IProjectOwnersReadModel {
return dict;
}

async addOwners(
projects: ProjectForUi[],
async addOwners<T extends { id: string }>(
projects: T[],
anonymizeProjectOwners: boolean = false,
): Promise<IProjectForUiWithOwners[]> {
): Promise<WithProjectOwners<T>> {
const owners = await this.getAllProjectOwners(anonymizeProjectOwners);

return ProjectOwnersReadModel.addOwnerData(projects, owners);
Expand Down
12 changes: 8 additions & 4 deletions src/lib/features/project/project-owners-read-model.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type GroupProjectOwner = {
ownerType: 'group';
name: string;
};
type ProjectOwners =
export type ProjectOwners =
| [SystemOwner]
| Array<UserProjectOwner | GroupProjectOwner>;

Expand All @@ -21,9 +21,13 @@ export type IProjectForUiWithOwners = ProjectForUi & {
owners: ProjectOwners;
};

export type WithProjectOwners<T extends { id: string }> = (T & {
owners: ProjectOwners;
})[];

export interface IProjectOwnersReadModel {
addOwners(
projects: ProjectForUi[],
addOwners<T extends { id: string }>(
projects: T[],
anonymizeProjectOwners?: boolean,
): Promise<IProjectForUiWithOwners[]>;
): Promise<WithProjectOwners<T>>;
}
2 changes: 2 additions & 0 deletions src/lib/openapi/spec/personal-dashboard-schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FromSchema } from 'json-schema-to-ts';
import { projectSchema } from './project-schema';

export const personalDashboardSchema = {
$id: '#/components/schemas/personalDashboardSchema',
Expand All @@ -24,6 +25,7 @@ export const personalDashboardSchema = {
example: 'My Project',
description: 'The name of the project',
},
owners: projectSchema.properties.owners,
roles: {
type: 'array',
description:
Expand Down
4 changes: 4 additions & 0 deletions src/lib/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ import { OnboardingService } from '../features/onboarding/onboarding-service';
import { PersonalDashboardService } from '../features/personal-dashboard/personal-dashboard-service';
import { PersonalDashboardReadModel } from '../features/personal-dashboard/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 { FakeProjectOwnersReadModel } from '../features/project/fake-project-owners-read-model';

export const createServices = (
stores: IUnleashStores,
Expand Down Expand Up @@ -408,6 +410,8 @@ export const createServices = (
db
? new PersonalDashboardReadModel(db)
: new FakePersonalDashboardReadModel(),

db ? new ProjectOwnersReadModel(db) : new FakeProjectOwnersReadModel(),
);

return {
Expand Down

0 comments on commit 44bf661

Please sign in to comment.