1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-17 01:17:29 +02:00

feat: return project roles (#8314)

This PR updates the personal dashboard project endpoint to return owners
and roles. It also adds the impl for getting roles (via the access
store).

I'm filtering the roles for a project to only include project roles for
now, but we might wanna change this later.

Tests and UI update will follow.
This commit is contained in:
Thomas Heartman 2024-10-01 11:13:59 +02:00 committed by GitHub
parent 050e53e564
commit 96d8dc353a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 42 additions and 13 deletions

View File

@ -16,6 +16,8 @@ import { AccountStore } from '../../db/account-store';
import { FakeAccountStore } from '../../../test/fixtures/fake-account-store'; import { FakeAccountStore } from '../../../test/fixtures/fake-account-store';
import { OnboardingReadModel } from '../onboarding/onboarding-read-model'; import { OnboardingReadModel } from '../onboarding/onboarding-read-model';
import { FakeOnboardingReadModel } from '../onboarding/fake-onboarding-read-model'; import { FakeOnboardingReadModel } from '../onboarding/fake-onboarding-read-model';
import { AccessStore } from '../../db/access-store';
import FakeAccessStore from '../../../test/fixtures/fake-access-store';
export const createPersonalDashboardService = ( export const createPersonalDashboardService = (
db: Db, db: Db,
@ -34,6 +36,7 @@ export const createPersonalDashboardService = (
}), }),
new PrivateProjectChecker(stores, config), new PrivateProjectChecker(stores, config),
new AccountStore(db, config.getLogger), new AccountStore(db, config.getLogger),
new AccessStore(db, config.eventBus, config.getLogger),
); );
}; };
@ -50,5 +53,6 @@ export const createFakePersonalDashboardService = (config: IUnleashConfig) => {
}), }),
new FakePrivateProjectChecker(), new FakePrivateProjectChecker(),
new FakeAccountStore(), new FakeAccountStore(),
new FakeAccessStore(),
); );
}; };

View File

@ -215,14 +215,15 @@ test('should return projects where users are part of a group', async () => {
}); });
test('should return personal dashboard project details', async () => { test('should return personal dashboard project details', async () => {
await loginUser('new_user@test.com'); const { body: user } = await loginUser('new_user@test.com');
await app.createFeature('log_feature_a'); const project = await createProject(`x${randomId()}`, user);
await app.createFeature('log_feature_b'); await app.createFeature('log_feature_a', project.id);
await app.createFeature('log_feature_c'); await app.createFeature('log_feature_b', project.id);
await app.createFeature('log_feature_c', project.id);
const { body } = await app.request.get( const { body } = await app.request.get(
`/api/admin/personal-dashboard/default`, `/api/admin/personal-dashboard/${project.id}`,
); );
expect(body).toMatchObject({ expect(body).toMatchObject({
@ -251,6 +252,12 @@ test('should return personal dashboard project details', async () => {
'**new_user@test.com** created **[log_feature_a]', '**new_user@test.com** created **[log_feature_a]',
), ),
}, },
{
createdBy: 'audit user',
summary: expect.stringContaining(
`**audit user** created project **[${project.id}]`,
),
},
], ],
}); });
}); });

View File

@ -106,6 +106,7 @@ export default class PersonalDashboardController extends Controller {
const projectDetails = const projectDetails =
await this.personalDashboardService.getPersonalProjectDetails( await this.personalDashboardService.getPersonalProjectDetails(
user.id,
req.params.projectId, req.params.projectId,
); );
@ -115,8 +116,6 @@ export default class PersonalDashboardController extends Controller {
personalDashboardProjectDetailsSchema.$id, personalDashboardProjectDetailsSchema.$id,
{ {
...projectDetails, ...projectDetails,
owners: [{ ownerType: 'user', name: 'placeholder' }],
roles: [{ name: 'placeholder', id: 0, type: 'project' }],
}, },
); );
} }

View File

@ -10,6 +10,7 @@ import type {
import type { IProjectReadModel } from '../project/project-read-model-type'; import type { IProjectReadModel } from '../project/project-read-model-type';
import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType'; import type { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
import type { import type {
IAccessStore,
IAccountStore, IAccountStore,
IEventStore, IEventStore,
IOnboardingReadModel, IOnboardingReadModel,
@ -36,6 +37,8 @@ export class PersonalDashboardService {
private onboardingReadModel: IOnboardingReadModel; private onboardingReadModel: IOnboardingReadModel;
private accessStore: IAccessStore;
constructor( constructor(
personalDashboardReadModel: IPersonalDashboardReadModel, personalDashboardReadModel: IPersonalDashboardReadModel,
projectOwnersReadModel: IProjectOwnersReadModel, projectOwnersReadModel: IProjectOwnersReadModel,
@ -45,6 +48,7 @@ export class PersonalDashboardService {
featureEventFormatter: FeatureEventFormatter, featureEventFormatter: FeatureEventFormatter,
privateProjectChecker: IPrivateProjectChecker, privateProjectChecker: IPrivateProjectChecker,
accountStore: IAccountStore, accountStore: IAccountStore,
accessStore: IAccessStore,
) { ) {
this.personalDashboardReadModel = personalDashboardReadModel; this.personalDashboardReadModel = personalDashboardReadModel;
this.projectOwnersReadModel = projectOwnersReadModel; this.projectOwnersReadModel = projectOwnersReadModel;
@ -54,6 +58,7 @@ export class PersonalDashboardService {
this.featureEventFormatter = featureEventFormatter; this.featureEventFormatter = featureEventFormatter;
this.privateProjectChecker = privateProjectChecker; this.privateProjectChecker = privateProjectChecker;
this.accountStore = accountStore; this.accountStore = accountStore;
this.accessStore = accessStore;
} }
getPersonalFeatures(userId: number): Promise<PersonalFeature[]> { getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
@ -95,6 +100,7 @@ export class PersonalDashboardService {
} }
async getPersonalProjectDetails( async getPersonalProjectDetails(
userId: number,
projectId: string, projectId: string,
): Promise<PersonalDashboardProjectDetailsSchema> { ): Promise<PersonalDashboardProjectDetailsSchema> {
const recentEvents = await this.eventStore.searchEvents( const recentEvents = await this.eventStore.searchEvents(
@ -117,11 +123,24 @@ export class PersonalDashboardService {
const owners = const owners =
await this.projectOwnersReadModel.getProjectOwners(projectId); await this.projectOwnersReadModel.getProjectOwners(projectId);
const allRoles = await this.accessStore.getAllProjectRolesForUser(
userId,
projectId,
);
const projectRoles = allRoles
.filter((role) => ['project', 'custom'].includes(role.type))
.map((role) => ({
id: role.id,
name: role.name,
type: role.type as PersonalDashboardProjectDetailsSchema['roles'][number]['type'],
}));
return { return {
latestEvents: formattedEvents, latestEvents: formattedEvents,
onboardingStatus, onboardingStatus,
owners, owners,
roles: [], roles: projectRoles,
}; };
} }

View File

@ -12,7 +12,7 @@ import FakeGroupStore from '../../test/fixtures/fake-group-store';
import { FakeAccountStore } from '../../test/fixtures/fake-account-store'; import { FakeAccountStore } from '../../test/fixtures/fake-account-store';
import FakeRoleStore from '../../test/fixtures/fake-role-store'; import FakeRoleStore from '../../test/fixtures/fake-role-store';
import FakeEnvironmentStore from '../features/project-environments/fake-environment-store'; import FakeEnvironmentStore from '../features/project-environments/fake-environment-store';
import AccessStoreMock from '../../test/fixtures/fake-access-store'; import FakeAccessStore from '../../test/fixtures/fake-access-store';
import { GroupService } from '../services/group-service'; import { GroupService } from '../services/group-service';
import type { IRole } from '../../lib/types/stores/access-store'; import type { IRole } from '../../lib/types/stores/access-store';
import { import {
@ -271,7 +271,7 @@ test('throws error when trying to delete a project role in use by group', async
const accountStore = new FakeAccountStore(); const accountStore = new FakeAccountStore();
const roleStore = new FakeRoleStore(); const roleStore = new FakeRoleStore();
const environmentStore = new FakeEnvironmentStore(); const environmentStore = new FakeEnvironmentStore();
const accessStore = new AccessStoreMock(); const accessStore = new FakeAccessStore();
accessStore.getGroupIdsForRole = groupIdResultOverride; accessStore.getGroupIdsForRole = groupIdResultOverride;
accessStore.getUserIdsForRole = async (): Promise<number[]> => { accessStore.getUserIdsForRole = async (): Promise<number[]> => {
return []; return [];

View File

@ -19,7 +19,7 @@ import {
import FakeRoleStore from './fake-role-store'; import FakeRoleStore from './fake-role-store';
import type { PermissionRef } from '../../lib/services/access-service'; import type { PermissionRef } from '../../lib/services/access-service';
class AccessStoreMock implements IAccessStore { export class FakeAccessStore implements IAccessStore {
fakeRolesStore: IRoleStore; fakeRolesStore: IRoleStore;
userToRoleMap: Map<number, number> = new Map(); userToRoleMap: Map<number, number> = new Map();
@ -327,6 +327,6 @@ class AccessStoreMock implements IAccessStore {
} }
} }
module.exports = AccessStoreMock; module.exports = FakeAccessStore;
export default AccessStoreMock; export default FakeAccessStore;