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 { OnboardingReadModel } from '../onboarding/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 = (
db: Db,
@ -34,6 +36,7 @@ export const createPersonalDashboardService = (
}),
new PrivateProjectChecker(stores, config),
new AccountStore(db, config.getLogger),
new AccessStore(db, config.eventBus, config.getLogger),
);
};
@ -50,5 +53,6 @@ export const createFakePersonalDashboardService = (config: IUnleashConfig) => {
}),
new FakePrivateProjectChecker(),
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 () => {
await loginUser('new_user@test.com');
const { body: user } = await loginUser('new_user@test.com');
await app.createFeature('log_feature_a');
await app.createFeature('log_feature_b');
await app.createFeature('log_feature_c');
const project = await createProject(`x${randomId()}`, user);
await app.createFeature('log_feature_a', project.id);
await app.createFeature('log_feature_b', project.id);
await app.createFeature('log_feature_c', project.id);
const { body } = await app.request.get(
`/api/admin/personal-dashboard/default`,
`/api/admin/personal-dashboard/${project.id}`,
);
expect(body).toMatchObject({
@ -251,6 +252,12 @@ test('should return personal dashboard project details', async () => {
'**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 =
await this.personalDashboardService.getPersonalProjectDetails(
user.id,
req.params.projectId,
);
@ -115,8 +116,6 @@ export default class PersonalDashboardController extends Controller {
personalDashboardProjectDetailsSchema.$id,
{
...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 { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType';
import type {
IAccessStore,
IAccountStore,
IEventStore,
IOnboardingReadModel,
@ -36,6 +37,8 @@ export class PersonalDashboardService {
private onboardingReadModel: IOnboardingReadModel;
private accessStore: IAccessStore;
constructor(
personalDashboardReadModel: IPersonalDashboardReadModel,
projectOwnersReadModel: IProjectOwnersReadModel,
@ -45,6 +48,7 @@ export class PersonalDashboardService {
featureEventFormatter: FeatureEventFormatter,
privateProjectChecker: IPrivateProjectChecker,
accountStore: IAccountStore,
accessStore: IAccessStore,
) {
this.personalDashboardReadModel = personalDashboardReadModel;
this.projectOwnersReadModel = projectOwnersReadModel;
@ -54,6 +58,7 @@ export class PersonalDashboardService {
this.featureEventFormatter = featureEventFormatter;
this.privateProjectChecker = privateProjectChecker;
this.accountStore = accountStore;
this.accessStore = accessStore;
}
getPersonalFeatures(userId: number): Promise<PersonalFeature[]> {
@ -95,6 +100,7 @@ export class PersonalDashboardService {
}
async getPersonalProjectDetails(
userId: number,
projectId: string,
): Promise<PersonalDashboardProjectDetailsSchema> {
const recentEvents = await this.eventStore.searchEvents(
@ -117,11 +123,24 @@ export class PersonalDashboardService {
const owners =
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 {
latestEvents: formattedEvents,
onboardingStatus,
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 FakeRoleStore from '../../test/fixtures/fake-role-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 type { IRole } from '../../lib/types/stores/access-store';
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 roleStore = new FakeRoleStore();
const environmentStore = new FakeEnvironmentStore();
const accessStore = new AccessStoreMock();
const accessStore = new FakeAccessStore();
accessStore.getGroupIdsForRole = groupIdResultOverride;
accessStore.getUserIdsForRole = async (): Promise<number[]> => {
return [];

View File

@ -19,7 +19,7 @@ import {
import FakeRoleStore from './fake-role-store';
import type { PermissionRef } from '../../lib/services/access-service';
class AccessStoreMock implements IAccessStore {
export class FakeAccessStore implements IAccessStore {
fakeRolesStore: IRoleStore;
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;