mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
feat: map project owners to projects list (#6928)
- Combining list of projects with owners - Additional tests and checks
This commit is contained in:
parent
44521c1c74
commit
34c1da58cc
@ -4,10 +4,53 @@ import { type IUser, RoleName, type IGroup } from '../../types';
|
||||
import { randomId } from '../../util';
|
||||
import { ProjectOwnersReadModel } from './project-owners-read-model';
|
||||
|
||||
const mockProjectWithCounts = (name: string) => ({
|
||||
name,
|
||||
id: name,
|
||||
description: '',
|
||||
featureCount: 0,
|
||||
memberCount: 0,
|
||||
mode: 'open' as const,
|
||||
defaultStickiness: 'default' as const,
|
||||
staleFeatureCount: 0,
|
||||
potentiallyStaleFeatureCount: 0,
|
||||
avgTimeToProduction: 0,
|
||||
});
|
||||
|
||||
describe('unit tests', () => {
|
||||
test('maps owners to projects', () => {});
|
||||
test('maps owners to projects', () => {
|
||||
const projects = [{ name: 'project1' }, { name: 'project2' }] as any;
|
||||
|
||||
const owners = {
|
||||
project1: [{ ownerType: 'user' as const, name: 'Owner Name' }],
|
||||
project2: [{ ownerType: 'user' as const, name: 'Owner Name' }],
|
||||
};
|
||||
|
||||
const projectsWithOwners = ProjectOwnersReadModel.addOwnerData(
|
||||
projects,
|
||||
owners,
|
||||
);
|
||||
|
||||
expect(projectsWithOwners).toMatchObject([
|
||||
{ name: 'project1', owners: [{ name: 'Owner Name' }] },
|
||||
{ name: 'project2', owners: [{ name: 'Owner Name' }] },
|
||||
]);
|
||||
});
|
||||
|
||||
test('returns "system" when a project has no owners', async () => {
|
||||
// this is a mapping test; not an integration test
|
||||
const projects = [{ name: 'project1' }, { name: 'project2' }] as any;
|
||||
|
||||
const owners = {};
|
||||
|
||||
const projectsWithOwners = ProjectOwnersReadModel.addOwnerData(
|
||||
projects,
|
||||
owners,
|
||||
);
|
||||
|
||||
expect(projectsWithOwners).toMatchObject([
|
||||
{ name: 'project1', owners: [{ ownerType: 'system' }] },
|
||||
{ name: 'project2', owners: [{ ownerType: 'system' }] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -16,8 +59,10 @@ let readModel: ProjectOwnersReadModel;
|
||||
|
||||
let ownerRoleId: number;
|
||||
let owner: IUser;
|
||||
let owner2: IUser;
|
||||
let member: IUser;
|
||||
let group: IGroup;
|
||||
let group2: IGroup;
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('project_owners_read_model_serial', getLogger);
|
||||
@ -25,11 +70,17 @@ beforeAll(async () => {
|
||||
ownerRoleId = (await db.stores.roleStore.getRoleByName(RoleName.OWNER)).id;
|
||||
|
||||
const ownerData = {
|
||||
name: 'Owner User',
|
||||
name: 'Owner Name',
|
||||
username: 'owner',
|
||||
email: 'owner@email.com',
|
||||
imageUrl: 'image-url-1',
|
||||
};
|
||||
const ownerData2 = {
|
||||
name: 'Second Owner Name',
|
||||
username: 'owner2',
|
||||
email: 'owner2@email.com',
|
||||
imageUrl: 'image-url-3',
|
||||
};
|
||||
const memberData = {
|
||||
name: 'Member Name',
|
||||
username: 'member',
|
||||
@ -40,10 +91,13 @@ beforeAll(async () => {
|
||||
// create users
|
||||
owner = await db.stores.userStore.insert(ownerData);
|
||||
member = await db.stores.userStore.insert(memberData);
|
||||
owner2 = await db.stores.userStore.insert(ownerData2);
|
||||
|
||||
// create groups
|
||||
group = await db.stores.groupStore.create({ name: 'Group Name' });
|
||||
await db.stores.groupStore.addUserToGroups(owner.id, [group.id]);
|
||||
group2 = await db.stores.groupStore.create({ name: 'Second Group Name' });
|
||||
await db.stores.groupStore.addUserToGroups(member.id, [group.id]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -64,6 +118,12 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe('integration tests', () => {
|
||||
test('returns an empty object if there are no projects', async () => {
|
||||
const owners = await readModel.getAllProjectOwners();
|
||||
|
||||
expect(owners).toStrictEqual({});
|
||||
});
|
||||
|
||||
test('name takes precedence over username', async () => {
|
||||
const projectId = randomId();
|
||||
await db.stores.projectStore.create({ id: projectId, name: projectId });
|
||||
@ -77,7 +137,7 @@ describe('integration tests', () => {
|
||||
const owners = await readModel.getAllProjectOwners();
|
||||
expect(owners).toMatchObject({
|
||||
[projectId]: expect.arrayContaining([
|
||||
expect.objectContaining({ name: 'Owner User' }),
|
||||
expect.objectContaining({ name: 'Owner Name' }),
|
||||
]),
|
||||
});
|
||||
});
|
||||
@ -98,7 +158,7 @@ describe('integration tests', () => {
|
||||
[projectId]: [
|
||||
{
|
||||
ownerType: 'user',
|
||||
name: 'Owner User',
|
||||
name: 'Owner Name',
|
||||
email: 'owner@email.com',
|
||||
imageUrl: 'image-url-1',
|
||||
},
|
||||
@ -128,7 +188,7 @@ describe('integration tests', () => {
|
||||
const owners = await readModel.getAllProjectOwners();
|
||||
|
||||
expect(owners).toMatchObject({
|
||||
[projectId]: [{ name: 'Owner User' }],
|
||||
[projectId]: [{ name: 'Owner Name' }],
|
||||
});
|
||||
});
|
||||
|
||||
@ -155,21 +215,134 @@ describe('integration tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('users are listed before groups', async () => {});
|
||||
test('users are listed before groups', async () => {
|
||||
const projectId = randomId();
|
||||
await db.stores.projectStore.create({ id: projectId, name: projectId });
|
||||
|
||||
test('owners (users and groups) are sorted by when they were added; oldest first', async () => {});
|
||||
await db.stores.accessStore.addGroupToRole(
|
||||
group.id,
|
||||
ownerRoleId,
|
||||
'',
|
||||
projectId,
|
||||
);
|
||||
|
||||
test('returns the system owner for the default project', async () => {});
|
||||
await db.stores.accessStore.addUserToRole(
|
||||
owner.id,
|
||||
ownerRoleId,
|
||||
projectId,
|
||||
);
|
||||
|
||||
test('returns an empty list if there are no projects', async () => {
|
||||
const owners = await readModel.getAllProjectOwners();
|
||||
|
||||
expect(owners).toStrictEqual({});
|
||||
expect(owners).toMatchObject({
|
||||
[projectId]: [
|
||||
{
|
||||
email: 'owner@email.com',
|
||||
imageUrl: 'image-url-1',
|
||||
name: 'Owner Name',
|
||||
ownerType: 'user',
|
||||
},
|
||||
{
|
||||
name: 'Group Name',
|
||||
ownerType: 'group',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('enriches fully', async () => {
|
||||
const owners = await readModel.enrichWithOwners([]);
|
||||
test('owners (users and groups) are sorted by when they were added; oldest first', async () => {
|
||||
const projectId = randomId();
|
||||
await db.stores.projectStore.create({ id: projectId, name: projectId });
|
||||
|
||||
expect(owners).toStrictEqual([]);
|
||||
// Raw query in order to set the created_at date
|
||||
await db.rawDatabase('role_user').insert({
|
||||
user_id: owner2.id,
|
||||
role_id: ownerRoleId,
|
||||
project: projectId,
|
||||
created_at: new Date('2024-01-01T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Raw query in order to set the created_at date
|
||||
await db.rawDatabase('group_role').insert({
|
||||
group_id: group2.id,
|
||||
role_id: ownerRoleId,
|
||||
project: projectId,
|
||||
created_at: new Date('2024-01-01T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
await db.stores.accessStore.addGroupToRole(
|
||||
group.id,
|
||||
ownerRoleId,
|
||||
'',
|
||||
projectId,
|
||||
);
|
||||
|
||||
await db.stores.accessStore.addUserToRole(
|
||||
owner.id,
|
||||
ownerRoleId,
|
||||
projectId,
|
||||
);
|
||||
|
||||
const owners = await readModel.getAllProjectOwners();
|
||||
|
||||
expect(owners).toMatchObject({
|
||||
[projectId]: [
|
||||
{
|
||||
email: 'owner2@email.com',
|
||||
imageUrl: 'image-url-3',
|
||||
name: 'Second Owner Name',
|
||||
ownerType: 'user',
|
||||
},
|
||||
{
|
||||
email: 'owner@email.com',
|
||||
imageUrl: 'image-url-1',
|
||||
name: 'Owner Name',
|
||||
ownerType: 'user',
|
||||
},
|
||||
{
|
||||
name: 'Second Group Name',
|
||||
ownerType: 'group',
|
||||
},
|
||||
{
|
||||
name: 'Group Name',
|
||||
ownerType: 'group',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('does not modify an empty array', async () => {
|
||||
const projectsWithOwners = await readModel.addOwners([]);
|
||||
|
||||
expect(projectsWithOwners).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test('adds system owner when no owners are found', async () => {
|
||||
const projectIdA = randomId();
|
||||
const projectIdB = randomId();
|
||||
await db.stores.projectStore.create({
|
||||
id: projectIdA,
|
||||
name: projectIdA,
|
||||
});
|
||||
await db.stores.projectStore.create({
|
||||
id: projectIdB,
|
||||
name: projectIdB,
|
||||
});
|
||||
|
||||
await db.stores.accessStore.addUserToRole(
|
||||
owner.id,
|
||||
ownerRoleId,
|
||||
projectIdB,
|
||||
);
|
||||
|
||||
const projectsWithOwners = await readModel.addOwners([
|
||||
mockProjectWithCounts(projectIdA),
|
||||
mockProjectWithCounts(projectIdB),
|
||||
]);
|
||||
|
||||
expect(projectsWithOwners).toMatchObject([
|
||||
{ name: projectIdA, owners: [{ ownerType: 'system' }] },
|
||||
{ name: projectIdB, owners: [{ ownerType: 'user' }] },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -38,15 +38,14 @@ export class ProjectOwnersReadModel {
|
||||
this.roleStore = roleStore;
|
||||
}
|
||||
|
||||
addOwnerData(
|
||||
static addOwnerData(
|
||||
projects: IProjectWithCount[],
|
||||
owners: ProjectOwnersDictionary,
|
||||
): IProjectWithCountAndOwners[] {
|
||||
// const projectsWithOwners = projects.map((p) => ({
|
||||
// ...p,
|
||||
// owners: projectOwners[p.id] || [],
|
||||
// }));
|
||||
return [];
|
||||
return projects.map((project) => ({
|
||||
...project,
|
||||
owners: owners[project.name] || [{ ownerType: 'system' }],
|
||||
}));
|
||||
}
|
||||
|
||||
private async getAllProjectUsersByRole(
|
||||
@ -62,6 +61,7 @@ export class ProjectOwnersReadModel {
|
||||
'ru.project',
|
||||
)
|
||||
.from(`${T.ROLE_USER} as ru`)
|
||||
.orderBy('ru.created_at', 'asc')
|
||||
.join(`${T.ROLES} as r`, 'ru.role_id', 'r.id')
|
||||
.where('r.id', roleId)
|
||||
.join(`${T.USERS} as user`, 'ru.user_id', 'user.id');
|
||||
@ -93,6 +93,7 @@ export class ProjectOwnersReadModel {
|
||||
const groupsResult = await this.db
|
||||
.select('groups.name', 'gr.created_at', 'gr.project')
|
||||
.from(`${T.GROUP_ROLE} as gr`)
|
||||
.orderBy('gr.created_at', 'asc')
|
||||
.join(`${T.ROLES} as r`, 'gr.role_id', 'r.id')
|
||||
.where('r.id', roleId)
|
||||
.join('groups', 'gr.group_id', 'groups.id');
|
||||
@ -122,30 +123,27 @@ export class ProjectOwnersReadModel {
|
||||
const usersDict = await this.getAllProjectUsersByRole(ownerRole.id);
|
||||
const groupsDict = await this.getAllProjectGroupsByRole(ownerRole.id);
|
||||
|
||||
const projects = [
|
||||
...new Set([...Object.keys(usersDict), ...Object.keys(groupsDict)]),
|
||||
];
|
||||
const dict: Record<
|
||||
string,
|
||||
Array<UserProjectOwner | GroupProjectOwner>
|
||||
> = usersDict;
|
||||
|
||||
const dict = Object.fromEntries(
|
||||
projects.map((project) => {
|
||||
return [
|
||||
project,
|
||||
[
|
||||
...(usersDict[project] || []),
|
||||
...(groupsDict[project] || []),
|
||||
],
|
||||
];
|
||||
}),
|
||||
);
|
||||
Object.keys(groupsDict).forEach((project) => {
|
||||
if (project in dict) {
|
||||
dict[project] = dict[project].concat(groupsDict[project]);
|
||||
} else {
|
||||
dict[project] = groupsDict[project];
|
||||
}
|
||||
});
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
async enrichWithOwners(
|
||||
async addOwners(
|
||||
projects: IProjectWithCount[],
|
||||
): Promise<IProjectWithCountAndOwners[]> {
|
||||
const owners = await this.getAllProjectOwners();
|
||||
|
||||
return this.addOwnerData(projects, owners);
|
||||
return ProjectOwnersReadModel.addOwnerData(projects, owners);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user