mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: project owners in project service (#6935)
Schema and integrating into service and controller for project owners --------- Co-authored-by: Thomas Heartman <thomas@getunleash.io>
This commit is contained in:
		
							parent
							
								
									7d01dbb748
								
							
						
					
					
						commit
						66ec9a2f2f
					
				@ -43,6 +43,7 @@ import FeatureSearchStore from '../features/feature-search/feature-search-store'
 | 
				
			|||||||
import { InactiveUsersStore } from '../users/inactive/inactive-users-store';
 | 
					import { InactiveUsersStore } from '../users/inactive/inactive-users-store';
 | 
				
			||||||
import { TrafficDataUsageStore } from '../features/traffic-data-usage/traffic-data-usage-store';
 | 
					import { TrafficDataUsageStore } from '../features/traffic-data-usage/traffic-data-usage-store';
 | 
				
			||||||
import { SegmentReadModel } from '../features/segment/segment-read-model';
 | 
					import { SegmentReadModel } from '../features/segment/segment-read-model';
 | 
				
			||||||
 | 
					import { ProjectOwnersReadModel } from '../features/project/project-owners-read-model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createStores = (
 | 
					export const createStores = (
 | 
				
			||||||
    config: IUnleashConfig,
 | 
					    config: IUnleashConfig,
 | 
				
			||||||
@ -148,6 +149,7 @@ export const createStores = (
 | 
				
			|||||||
        inactiveUsersStore: new InactiveUsersStore(db, eventBus, getLogger),
 | 
					        inactiveUsersStore: new InactiveUsersStore(db, eventBus, getLogger),
 | 
				
			||||||
        trafficDataUsageStore: new TrafficDataUsageStore(db, getLogger),
 | 
					        trafficDataUsageStore: new TrafficDataUsageStore(db, getLogger),
 | 
				
			||||||
        segmentReadModel: new SegmentReadModel(db),
 | 
					        segmentReadModel: new SegmentReadModel(db),
 | 
				
			||||||
 | 
					        projectOwnersReadModel: new ProjectOwnersReadModel(db),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -41,6 +41,8 @@ import {
 | 
				
			|||||||
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
 | 
					import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
 | 
				
			||||||
import FeatureTypeStore from '../../db/feature-type-store';
 | 
					import FeatureTypeStore from '../../db/feature-type-store';
 | 
				
			||||||
import FakeFeatureTypeStore from '../../../test/fixtures/fake-feature-type-store';
 | 
					import FakeFeatureTypeStore from '../../../test/fixtures/fake-feature-type-store';
 | 
				
			||||||
 | 
					import { ProjectOwnersReadModel } from './project-owners-read-model';
 | 
				
			||||||
 | 
					import { FakeProjectOwnersReadModel } from './fake-project-owners-read-model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createProjectService = (
 | 
					export const createProjectService = (
 | 
				
			||||||
    db: Db,
 | 
					    db: Db,
 | 
				
			||||||
@ -54,6 +56,7 @@ export const createProjectService = (
 | 
				
			|||||||
        getLogger,
 | 
					        getLogger,
 | 
				
			||||||
        flagResolver,
 | 
					        flagResolver,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    const projectOwnersReadModel = new ProjectOwnersReadModel(db);
 | 
				
			||||||
    const groupStore = new GroupStore(db);
 | 
					    const groupStore = new GroupStore(db);
 | 
				
			||||||
    const featureToggleStore = new FeatureToggleStore(
 | 
					    const featureToggleStore = new FeatureToggleStore(
 | 
				
			||||||
        db,
 | 
					        db,
 | 
				
			||||||
@ -115,6 +118,7 @@ export const createProjectService = (
 | 
				
			|||||||
            featureTypeStore,
 | 
					            featureTypeStore,
 | 
				
			||||||
            accountStore,
 | 
					            accountStore,
 | 
				
			||||||
            projectStatsStore,
 | 
					            projectStatsStore,
 | 
				
			||||||
 | 
					            projectOwnersReadModel,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        config,
 | 
					        config,
 | 
				
			||||||
        accessService,
 | 
					        accessService,
 | 
				
			||||||
@ -131,6 +135,7 @@ export const createFakeProjectService = (
 | 
				
			|||||||
): ProjectService => {
 | 
					): ProjectService => {
 | 
				
			||||||
    const { getLogger } = config;
 | 
					    const { getLogger } = config;
 | 
				
			||||||
    const eventStore = new FakeEventStore();
 | 
					    const eventStore = new FakeEventStore();
 | 
				
			||||||
 | 
					    const projectOwnersReadModel = new FakeProjectOwnersReadModel();
 | 
				
			||||||
    const projectStore = new FakeProjectStore();
 | 
					    const projectStore = new FakeProjectStore();
 | 
				
			||||||
    const groupStore = new FakeGroupStore();
 | 
					    const groupStore = new FakeGroupStore();
 | 
				
			||||||
    const featureToggleStore = new FakeFeatureToggleStore();
 | 
					    const featureToggleStore = new FakeFeatureToggleStore();
 | 
				
			||||||
@ -169,6 +174,7 @@ export const createFakeProjectService = (
 | 
				
			|||||||
    return new ProjectService(
 | 
					    return new ProjectService(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            projectStore,
 | 
					            projectStore,
 | 
				
			||||||
 | 
					            projectOwnersReadModel,
 | 
				
			||||||
            eventStore,
 | 
					            eventStore,
 | 
				
			||||||
            featureToggleStore,
 | 
					            featureToggleStore,
 | 
				
			||||||
            environmentStore,
 | 
					            environmentStore,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								src/lib/features/project/fake-project-owners-read-model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/lib/features/project/fake-project-owners-read-model.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					import type { IProjectWithCount } from '../../types';
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					    IProjectOwnersReadModel,
 | 
				
			||||||
 | 
					    IProjectWithCountAndOwners,
 | 
				
			||||||
 | 
					} from './project-owners-read-model.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class FakeProjectOwnersReadModel implements IProjectOwnersReadModel {
 | 
				
			||||||
 | 
					    async addOwners(
 | 
				
			||||||
 | 
					        projects: IProjectWithCount[],
 | 
				
			||||||
 | 
					    ): Promise<IProjectWithCountAndOwners[]> {
 | 
				
			||||||
 | 
					        return projects.map((project) => ({
 | 
				
			||||||
 | 
					            ...project,
 | 
				
			||||||
 | 
					            owners: [{ ownerType: 'system' }],
 | 
				
			||||||
 | 
					        }));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -197,9 +197,19 @@ export default class ProjectController extends Controller {
 | 
				
			|||||||
            user.id,
 | 
					            user.id,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if (this.flagResolver.isEnabled('projectsListNewCards')) {
 | 
					        if (this.flagResolver.isEnabled('projectsListNewCards')) {
 | 
				
			||||||
        //   TODO: get project owners and add to response
 | 
					            const projectsWithOwners =
 | 
				
			||||||
        // }
 | 
					                await this.projectService.addOwnersToProjects(projects);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.openApiService.respondWithValidation(
 | 
				
			||||||
 | 
					                200,
 | 
				
			||||||
 | 
					                res,
 | 
				
			||||||
 | 
					                projectsSchema.$id,
 | 
				
			||||||
 | 
					                { version: 1, projects: serializeDates(projectsWithOwners) },
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.openApiService.respondWithValidation(
 | 
					        this.openApiService.respondWithValidation(
 | 
				
			||||||
            200,
 | 
					            200,
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,10 @@ const mockProjectWithCounts = (name: string) => ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
describe('unit tests', () => {
 | 
					describe('unit tests', () => {
 | 
				
			||||||
    test('maps owners to projects', () => {
 | 
					    test('maps owners to projects', () => {
 | 
				
			||||||
        const projects = [{ name: 'project1' }, { name: 'project2' }] as any;
 | 
					        const projects = [
 | 
				
			||||||
 | 
					            { id: 'project1', name: 'Project one' },
 | 
				
			||||||
 | 
					            { id: 'project2', name: 'Project two' },
 | 
				
			||||||
 | 
					        ] as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const owners = {
 | 
					        const owners = {
 | 
				
			||||||
            project1: [{ ownerType: 'user' as const, name: 'Owner Name' }],
 | 
					            project1: [{ ownerType: 'user' as const, name: 'Owner Name' }],
 | 
				
			||||||
@ -32,13 +35,21 @@ describe('unit tests', () => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(projectsWithOwners).toMatchObject([
 | 
					        expect(projectsWithOwners).toMatchObject([
 | 
				
			||||||
            { name: 'project1', owners: [{ name: 'Owner Name' }] },
 | 
					            {
 | 
				
			||||||
            { name: 'project2', owners: [{ name: 'Owner Name' }] },
 | 
					                id: 'project1',
 | 
				
			||||||
 | 
					                name: 'Project one',
 | 
				
			||||||
 | 
					                owners: [{ name: 'Owner Name' }],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                id: 'project2',
 | 
				
			||||||
 | 
					                name: 'Project two',
 | 
				
			||||||
 | 
					                owners: [{ name: 'Owner Name' }],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    test('returns "system" when a project has no owners', async () => {
 | 
					    test('returns "system" when a project has no owners', async () => {
 | 
				
			||||||
        const projects = [{ name: 'project1' }, { name: 'project2' }] as any;
 | 
					        const projects = [{ id: 'project1' }, { id: 'project2' }] as any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const owners = {};
 | 
					        const owners = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,8 +59,14 @@ describe('unit tests', () => {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(projectsWithOwners).toMatchObject([
 | 
					        expect(projectsWithOwners).toMatchObject([
 | 
				
			||||||
            { name: 'project1', owners: [{ ownerType: 'system' }] },
 | 
					            {
 | 
				
			||||||
            { name: 'project2', owners: [{ ownerType: 'system' }] },
 | 
					                id: 'project1',
 | 
				
			||||||
 | 
					                owners: [{ ownerType: 'system' }],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                id: 'project2',
 | 
				
			||||||
 | 
					                owners: [{ ownerType: 'system' }],
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
@ -66,7 +83,7 @@ let group2: IGroup;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
beforeAll(async () => {
 | 
					beforeAll(async () => {
 | 
				
			||||||
    db = await dbInit('project_owners_read_model_serial', getLogger);
 | 
					    db = await dbInit('project_owners_read_model_serial', getLogger);
 | 
				
			||||||
    readModel = new ProjectOwnersReadModel(db.rawDatabase, db.stores.roleStore);
 | 
					    readModel = new ProjectOwnersReadModel(db.rawDatabase);
 | 
				
			||||||
    ownerRoleId = (await db.stores.roleStore.getRoleByName(RoleName.OWNER)).id;
 | 
					    ownerRoleId = (await db.stores.roleStore.getRoleByName(RoleName.OWNER)).id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ownerData = {
 | 
					    const ownerData = {
 | 
				
			||||||
@ -107,14 +124,7 @@ afterAll(async () => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
afterEach(async () => {
 | 
					afterEach(async () => {
 | 
				
			||||||
    if (db) {
 | 
					    db.stores.roleStore;
 | 
				
			||||||
        const projects = await db.stores.projectStore.getAll();
 | 
					 | 
				
			||||||
        for (const project of projects) {
 | 
					 | 
				
			||||||
            // Clean only project roles, not all roles
 | 
					 | 
				
			||||||
            await db.stores.roleStore.removeRolesForProject(project.id);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        await db.stores.projectStore.deleteAll();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('integration tests', () => {
 | 
					describe('integration tests', () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,12 @@
 | 
				
			|||||||
import type { Db } from '../../db/db';
 | 
					import type { Db } from '../../db/db';
 | 
				
			||||||
import { RoleName, type IProjectWithCount, type IRoleStore } from '../../types';
 | 
					import { RoleName, type IProjectWithCount } from '../../types';
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
					    GroupProjectOwner,
 | 
				
			||||||
 | 
					    IProjectOwnersReadModel,
 | 
				
			||||||
 | 
					    IProjectWithCountAndOwners,
 | 
				
			||||||
 | 
					    ProjectOwnersDictionary,
 | 
				
			||||||
 | 
					    UserProjectOwner,
 | 
				
			||||||
 | 
					} from './project-owners-read-model.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const T = {
 | 
					const T = {
 | 
				
			||||||
    ROLE_USER: 'role_user',
 | 
					    ROLE_USER: 'role_user',
 | 
				
			||||||
@ -8,34 +15,11 @@ const T = {
 | 
				
			|||||||
    USERS: 'users',
 | 
					    USERS: 'users',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type SystemOwner = { ownerType: 'system' };
 | 
					export class ProjectOwnersReadModel implements IProjectOwnersReadModel {
 | 
				
			||||||
type UserProjectOwner = {
 | 
					 | 
				
			||||||
    ownerType: 'user';
 | 
					 | 
				
			||||||
    name: string;
 | 
					 | 
				
			||||||
    email?: string;
 | 
					 | 
				
			||||||
    imageUrl?: string;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
type GroupProjectOwner = {
 | 
					 | 
				
			||||||
    ownerType: 'group';
 | 
					 | 
				
			||||||
    name: string;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
type ProjectOwners =
 | 
					 | 
				
			||||||
    | [SystemOwner]
 | 
					 | 
				
			||||||
    | Array<UserProjectOwner | GroupProjectOwner>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export type ProjectOwnersDictionary = Record<string, ProjectOwners>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type IProjectWithCountAndOwners = IProjectWithCount & {
 | 
					 | 
				
			||||||
    owners: ProjectOwners;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ProjectOwnersReadModel {
 | 
					 | 
				
			||||||
    private db: Db;
 | 
					    private db: Db;
 | 
				
			||||||
    roleStore: IRoleStore;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(db: Db, roleStore: IRoleStore) {
 | 
					    constructor(db: Db) {
 | 
				
			||||||
        this.db = db;
 | 
					        this.db = db;
 | 
				
			||||||
        this.roleStore = roleStore;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static addOwnerData(
 | 
					    static addOwnerData(
 | 
				
			||||||
@ -44,7 +28,7 @@ export class ProjectOwnersReadModel {
 | 
				
			|||||||
    ): IProjectWithCountAndOwners[] {
 | 
					    ): IProjectWithCountAndOwners[] {
 | 
				
			||||||
        return projects.map((project) => ({
 | 
					        return projects.map((project) => ({
 | 
				
			||||||
            ...project,
 | 
					            ...project,
 | 
				
			||||||
            owners: owners[project.name] || [{ ownerType: 'system' }],
 | 
					            owners: owners[project.id] || [{ ownerType: 'system' }],
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,7 +103,9 @@ export class ProjectOwnersReadModel {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAllProjectOwners(): Promise<ProjectOwnersDictionary> {
 | 
					    async getAllProjectOwners(): Promise<ProjectOwnersDictionary> {
 | 
				
			||||||
        const ownerRole = await this.roleStore.getRoleByName(RoleName.OWNER);
 | 
					        const ownerRole = await this.db(T.ROLES)
 | 
				
			||||||
 | 
					            .where({ name: RoleName.OWNER })
 | 
				
			||||||
 | 
					            .first();
 | 
				
			||||||
        const usersDict = await this.getAllProjectUsersByRole(ownerRole.id);
 | 
					        const usersDict = await this.getAllProjectUsersByRole(ownerRole.id);
 | 
				
			||||||
        const groupsDict = await this.getAllProjectGroupsByRole(ownerRole.id);
 | 
					        const groupsDict = await this.getAllProjectGroupsByRole(ownerRole.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								src/lib/features/project/project-owners-read-model.type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/lib/features/project/project-owners-read-model.type.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import type { IProjectWithCount } from '../../types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type SystemOwner = { ownerType: 'system' };
 | 
				
			||||||
 | 
					export type UserProjectOwner = {
 | 
				
			||||||
 | 
					    ownerType: 'user';
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					    email?: string;
 | 
				
			||||||
 | 
					    imageUrl?: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export type GroupProjectOwner = {
 | 
				
			||||||
 | 
					    ownerType: 'group';
 | 
				
			||||||
 | 
					    name: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					type ProjectOwners =
 | 
				
			||||||
 | 
					    | [SystemOwner]
 | 
				
			||||||
 | 
					    | Array<UserProjectOwner | GroupProjectOwner>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ProjectOwnersDictionary = Record<string, ProjectOwners>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type IProjectWithCountAndOwners = IProjectWithCount & {
 | 
				
			||||||
 | 
					    owners: ProjectOwners;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IProjectOwnersReadModel {
 | 
				
			||||||
 | 
					    addOwners(
 | 
				
			||||||
 | 
					        projects: IProjectWithCount[],
 | 
				
			||||||
 | 
					    ): Promise<IProjectWithCountAndOwners[]>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -50,6 +50,7 @@ import {
 | 
				
			|||||||
    RoleName,
 | 
					    RoleName,
 | 
				
			||||||
    SYSTEM_USER_ID,
 | 
					    SYSTEM_USER_ID,
 | 
				
			||||||
    type ProjectCreated,
 | 
					    type ProjectCreated,
 | 
				
			||||||
 | 
					    type IProjectOwnersReadModel,
 | 
				
			||||||
} from '../../types';
 | 
					} from '../../types';
 | 
				
			||||||
import type {
 | 
					import type {
 | 
				
			||||||
    IProjectAccessModel,
 | 
					    IProjectAccessModel,
 | 
				
			||||||
@ -77,8 +78,6 @@ import type {
 | 
				
			|||||||
    IProjectQuery,
 | 
					    IProjectQuery,
 | 
				
			||||||
} from './project-store-type';
 | 
					} from './project-store-type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Days = number;
 | 
					type Days = number;
 | 
				
			||||||
type Count = number;
 | 
					type Count = number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -112,6 +111,8 @@ function includes(
 | 
				
			|||||||
export default class ProjectService {
 | 
					export default class ProjectService {
 | 
				
			||||||
    private projectStore: IProjectStore;
 | 
					    private projectStore: IProjectStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private projectOwnersReadModel: IProjectOwnersReadModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private accessService: AccessService;
 | 
					    private accessService: AccessService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private eventStore: IEventStore;
 | 
					    private eventStore: IEventStore;
 | 
				
			||||||
@ -147,6 +148,7 @@ export default class ProjectService {
 | 
				
			|||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            projectStore,
 | 
					            projectStore,
 | 
				
			||||||
 | 
					            projectOwnersReadModel,
 | 
				
			||||||
            eventStore,
 | 
					            eventStore,
 | 
				
			||||||
            featureToggleStore,
 | 
					            featureToggleStore,
 | 
				
			||||||
            environmentStore,
 | 
					            environmentStore,
 | 
				
			||||||
@ -157,6 +159,7 @@ export default class ProjectService {
 | 
				
			|||||||
        }: Pick<
 | 
					        }: Pick<
 | 
				
			||||||
            IUnleashStores,
 | 
					            IUnleashStores,
 | 
				
			||||||
            | 'projectStore'
 | 
					            | 'projectStore'
 | 
				
			||||||
 | 
					            | 'projectOwnersReadModel'
 | 
				
			||||||
            | 'eventStore'
 | 
					            | 'eventStore'
 | 
				
			||||||
            | 'featureToggleStore'
 | 
					            | 'featureToggleStore'
 | 
				
			||||||
            | 'environmentStore'
 | 
					            | 'environmentStore'
 | 
				
			||||||
@ -174,6 +177,7 @@ export default class ProjectService {
 | 
				
			|||||||
        privateProjectChecker: IPrivateProjectChecker,
 | 
					        privateProjectChecker: IPrivateProjectChecker,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        this.projectStore = projectStore;
 | 
					        this.projectStore = projectStore;
 | 
				
			||||||
 | 
					        this.projectOwnersReadModel = projectOwnersReadModel;
 | 
				
			||||||
        this.environmentStore = environmentStore;
 | 
					        this.environmentStore = environmentStore;
 | 
				
			||||||
        this.featureEnvironmentStore = featureEnvironmentStore;
 | 
					        this.featureEnvironmentStore = featureEnvironmentStore;
 | 
				
			||||||
        this.accessService = accessService;
 | 
					        this.accessService = accessService;
 | 
				
			||||||
@ -218,6 +222,12 @@ export default class ProjectService {
 | 
				
			|||||||
        return projects;
 | 
					        return projects;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async addOwnersToProjects(
 | 
				
			||||||
 | 
					        projects: IProjectWithCount[],
 | 
				
			||||||
 | 
					    ): Promise<IProjectWithCount[]> {
 | 
				
			||||||
 | 
					        return this.projectOwnersReadModel.addOwners(projects);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getProject(id: string): Promise<IProject> {
 | 
					    async getProject(id: string): Promise<IProject> {
 | 
				
			||||||
        return this.projectStore.get(id);
 | 
					        return this.projectStore.get(id);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -89,6 +89,74 @@ export const projectSchema = {
 | 
				
			|||||||
            description:
 | 
					            description:
 | 
				
			||||||
                'The average time from when a feature was created to when it was enabled in the "production" environment during the current window',
 | 
					                'The average time from when a feature was created to when it was enabled in the "production" environment during the current window',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        owners: {
 | 
				
			||||||
 | 
					            description:
 | 
				
			||||||
 | 
					                'The users and/or groups that have the "owner" role in this project. If no such users or groups exist, the list will contain the "system" owner instead.',
 | 
				
			||||||
 | 
					            oneOf: [
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: 'array',
 | 
				
			||||||
 | 
					                    minItems: 1,
 | 
				
			||||||
 | 
					                    items: {
 | 
				
			||||||
 | 
					                        anyOf: [
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                type: 'object',
 | 
				
			||||||
 | 
					                                required: ['ownerType', 'name'],
 | 
				
			||||||
 | 
					                                properties: {
 | 
				
			||||||
 | 
					                                    ownerType: {
 | 
				
			||||||
 | 
					                                        type: 'string',
 | 
				
			||||||
 | 
					                                        enum: ['user'],
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                    name: {
 | 
				
			||||||
 | 
					                                        type: 'string',
 | 
				
			||||||
 | 
					                                        example: 'User Name',
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                    imageUrl: {
 | 
				
			||||||
 | 
					                                        type: 'string',
 | 
				
			||||||
 | 
					                                        nullable: true,
 | 
				
			||||||
 | 
					                                        example:
 | 
				
			||||||
 | 
					                                            'https://example.com/image.jpg',
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                    email: {
 | 
				
			||||||
 | 
					                                        type: 'string',
 | 
				
			||||||
 | 
					                                        nullable: true,
 | 
				
			||||||
 | 
					                                        example: 'user@example.com',
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                type: 'object',
 | 
				
			||||||
 | 
					                                required: ['ownerType', 'name'],
 | 
				
			||||||
 | 
					                                properties: {
 | 
				
			||||||
 | 
					                                    ownerType: {
 | 
				
			||||||
 | 
					                                        type: 'string',
 | 
				
			||||||
 | 
					                                        enum: ['group'],
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                    name: {
 | 
				
			||||||
 | 
					                                        type: 'string',
 | 
				
			||||||
 | 
					                                        example: 'Group Name',
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        ],
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    type: 'array',
 | 
				
			||||||
 | 
					                    minItems: 1,
 | 
				
			||||||
 | 
					                    maxItems: 1,
 | 
				
			||||||
 | 
					                    items: {
 | 
				
			||||||
 | 
					                        type: 'object',
 | 
				
			||||||
 | 
					                        required: ['ownerType'],
 | 
				
			||||||
 | 
					                        properties: {
 | 
				
			||||||
 | 
					                            ownerType: {
 | 
				
			||||||
 | 
					                                type: 'string',
 | 
				
			||||||
 | 
					                                enum: ['system'],
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    components: {},
 | 
					    components: {},
 | 
				
			||||||
} as const;
 | 
					} as const;
 | 
				
			||||||
 | 
				
			|||||||
@ -40,6 +40,7 @@ import { IFeatureSearchStore } from '../features/feature-search/feature-search-s
 | 
				
			|||||||
import type { IInactiveUsersStore } from '../users/inactive/types/inactive-users-store-type';
 | 
					import type { IInactiveUsersStore } from '../users/inactive/types/inactive-users-store-type';
 | 
				
			||||||
import { ITrafficDataUsageStore } from '../features/traffic-data-usage/traffic-data-usage-store-type';
 | 
					import { ITrafficDataUsageStore } from '../features/traffic-data-usage/traffic-data-usage-store-type';
 | 
				
			||||||
import { ISegmentReadModel } from '../features/segment/segment-read-model-type';
 | 
					import { ISegmentReadModel } from '../features/segment/segment-read-model-type';
 | 
				
			||||||
 | 
					import { IProjectOwnersReadModel } from '../features/project/project-owners-read-model.type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IUnleashStores {
 | 
					export interface IUnleashStores {
 | 
				
			||||||
    accessStore: IAccessStore;
 | 
					    accessStore: IAccessStore;
 | 
				
			||||||
@ -84,6 +85,7 @@ export interface IUnleashStores {
 | 
				
			|||||||
    inactiveUsersStore: IInactiveUsersStore;
 | 
					    inactiveUsersStore: IInactiveUsersStore;
 | 
				
			||||||
    trafficDataUsageStore: ITrafficDataUsageStore;
 | 
					    trafficDataUsageStore: ITrafficDataUsageStore;
 | 
				
			||||||
    segmentReadModel: ISegmentReadModel;
 | 
					    segmentReadModel: ISegmentReadModel;
 | 
				
			||||||
 | 
					    projectOwnersReadModel: IProjectOwnersReadModel;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
@ -127,4 +129,5 @@ export {
 | 
				
			|||||||
    IFeatureSearchStore,
 | 
					    IFeatureSearchStore,
 | 
				
			||||||
    ITrafficDataUsageStore,
 | 
					    ITrafficDataUsageStore,
 | 
				
			||||||
    ISegmentReadModel,
 | 
					    ISegmentReadModel,
 | 
				
			||||||
 | 
					    IProjectOwnersReadModel,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/test/fixtures/store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/test/fixtures/store.ts
									
									
									
									
										vendored
									
									
								
							@ -43,6 +43,7 @@ import FakeFeatureSearchStore from '../../lib/features/feature-search/fake-featu
 | 
				
			|||||||
import { FakeInactiveUsersStore } from '../../lib/users/inactive/fakes/fake-inactive-users-store';
 | 
					import { FakeInactiveUsersStore } from '../../lib/users/inactive/fakes/fake-inactive-users-store';
 | 
				
			||||||
import { FakeTrafficDataUsageStore } from '../../lib/features/traffic-data-usage/fake-traffic-data-usage-store';
 | 
					import { FakeTrafficDataUsageStore } from '../../lib/features/traffic-data-usage/fake-traffic-data-usage-store';
 | 
				
			||||||
import { FakeSegmentReadModel } from '../../lib/features/segment/fake-segment-read-model';
 | 
					import { FakeSegmentReadModel } from '../../lib/features/segment/fake-segment-read-model';
 | 
				
			||||||
 | 
					import { FakeProjectOwnersReadModel } from '../../lib/features/project/fake-project-owners-read-model';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const db = {
 | 
					const db = {
 | 
				
			||||||
    select: () => ({
 | 
					    select: () => ({
 | 
				
			||||||
@ -95,6 +96,7 @@ const createStores: () => IUnleashStores = () => {
 | 
				
			|||||||
        inactiveUsersStore: new FakeInactiveUsersStore(),
 | 
					        inactiveUsersStore: new FakeInactiveUsersStore(),
 | 
				
			||||||
        trafficDataUsageStore: new FakeTrafficDataUsageStore(),
 | 
					        trafficDataUsageStore: new FakeTrafficDataUsageStore(),
 | 
				
			||||||
        segmentReadModel: new FakeSegmentReadModel(),
 | 
					        segmentReadModel: new FakeSegmentReadModel(),
 | 
				
			||||||
 | 
					        projectOwnersReadModel: new FakeProjectOwnersReadModel(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user