mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
refactor: switch projectStore.getProjects
with projectReadModel.getProjectsForAdminUi
in project service (#7904)
Hooks up the new project read model and updates the existing project service to use it instead when the flag is on. In doing: - creates a composition root for the read model - includes it in IUnleashStores - updates some existing methods to accept either the old or the new model - updates the OpenAPI schema to deprecate the old properties
This commit is contained in:
parent
044da6866c
commit
79c3f8e975
@ -51,6 +51,7 @@ import { FeatureLifecycleReadModel } from '../features/feature-lifecycle/feature
|
|||||||
import { LargestResourcesReadModel } from '../features/metrics/sizes/largest-resources-read-model';
|
import { LargestResourcesReadModel } from '../features/metrics/sizes/largest-resources-read-model';
|
||||||
import { IntegrationEventsStore } from '../features/integration-events/integration-events-store';
|
import { IntegrationEventsStore } from '../features/integration-events/integration-events-store';
|
||||||
import { FeatureCollaboratorsReadModel } from '../features/feature-toggle/feature-collaborators-read-model';
|
import { FeatureCollaboratorsReadModel } from '../features/feature-toggle/feature-collaborators-read-model';
|
||||||
|
import { createProjectReadModel } from '../features/project/createProjectReadModel';
|
||||||
|
|
||||||
export const createStores = (
|
export const createStores = (
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
@ -177,6 +178,11 @@ export const createStores = (
|
|||||||
largestResourcesReadModel: new LargestResourcesReadModel(db),
|
largestResourcesReadModel: new LargestResourcesReadModel(db),
|
||||||
integrationEventsStore: new IntegrationEventsStore(db, { eventBus }),
|
integrationEventsStore: new IntegrationEventsStore(db, { eventBus }),
|
||||||
featureCollaboratorsReadModel: new FeatureCollaboratorsReadModel(db),
|
featureCollaboratorsReadModel: new FeatureCollaboratorsReadModel(db),
|
||||||
|
projectReadModel: createProjectReadModel(
|
||||||
|
db,
|
||||||
|
eventBus,
|
||||||
|
config.flagResolver,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
18
src/lib/features/project/createProjectReadModel.ts
Normal file
18
src/lib/features/project/createProjectReadModel.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type EventEmitter from 'events';
|
||||||
|
import type { Db } from '../../server-impl';
|
||||||
|
import type { IProjectReadModel } from './project-read-model-type';
|
||||||
|
import type { IFlagResolver } from '../../types';
|
||||||
|
import { ProjectReadModel } from './project-read-model';
|
||||||
|
import { FakeProjectReadModel } from './fake-project-read-model';
|
||||||
|
|
||||||
|
export const createProjectReadModel = (
|
||||||
|
db: Db,
|
||||||
|
eventBus: EventEmitter,
|
||||||
|
flagResolver: IFlagResolver,
|
||||||
|
): IProjectReadModel => {
|
||||||
|
return new ProjectReadModel(db, eventBus, flagResolver);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFakeProjectReadModel = (): IProjectReadModel => {
|
||||||
|
return new FakeProjectReadModel();
|
||||||
|
};
|
@ -48,6 +48,10 @@ import {
|
|||||||
createEventsService,
|
createEventsService,
|
||||||
createFakeEventsService,
|
createFakeEventsService,
|
||||||
} from '../events/createEventsService';
|
} from '../events/createEventsService';
|
||||||
|
import {
|
||||||
|
createFakeProjectReadModel,
|
||||||
|
createProjectReadModel,
|
||||||
|
} from './createProjectReadModel';
|
||||||
|
|
||||||
export const createProjectService = (
|
export const createProjectService = (
|
||||||
db: Db,
|
db: Db,
|
||||||
@ -120,6 +124,12 @@ export const createProjectService = (
|
|||||||
eventService,
|
eventService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const projectReadModel = createProjectReadModel(
|
||||||
|
db,
|
||||||
|
eventBus,
|
||||||
|
config.flagResolver,
|
||||||
|
);
|
||||||
|
|
||||||
return new ProjectService(
|
return new ProjectService(
|
||||||
{
|
{
|
||||||
projectStore,
|
projectStore,
|
||||||
@ -131,6 +141,7 @@ export const createProjectService = (
|
|||||||
projectStatsStore,
|
projectStatsStore,
|
||||||
projectOwnersReadModel,
|
projectOwnersReadModel,
|
||||||
projectFlagCreatorsReadModel,
|
projectFlagCreatorsReadModel,
|
||||||
|
projectReadModel,
|
||||||
},
|
},
|
||||||
config,
|
config,
|
||||||
accessService,
|
accessService,
|
||||||
@ -184,6 +195,8 @@ export const createFakeProjectService = (
|
|||||||
eventService,
|
eventService,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const projectReadModel = createFakeProjectReadModel();
|
||||||
|
|
||||||
return new ProjectService(
|
return new ProjectService(
|
||||||
{
|
{
|
||||||
projectStore,
|
projectStore,
|
||||||
@ -195,6 +208,7 @@ export const createFakeProjectService = (
|
|||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
accountStore,
|
accountStore,
|
||||||
projectStatsStore,
|
projectStatsStore,
|
||||||
|
projectReadModel,
|
||||||
},
|
},
|
||||||
config,
|
config,
|
||||||
accessService,
|
accessService,
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import type { IProjectWithCount } from '../../types';
|
|
||||||
import type {
|
import type {
|
||||||
IProjectOwnersReadModel,
|
IProjectOwnersReadModel,
|
||||||
IProjectWithCountAndOwners,
|
IProjectForUiWithOwners,
|
||||||
} from './project-owners-read-model.type';
|
} from './project-owners-read-model.type';
|
||||||
|
import type { ProjectForUi } from './project-read-model-type';
|
||||||
|
|
||||||
export class FakeProjectOwnersReadModel implements IProjectOwnersReadModel {
|
export class FakeProjectOwnersReadModel implements IProjectOwnersReadModel {
|
||||||
async addOwners(
|
async addOwners(
|
||||||
projects: IProjectWithCount[],
|
projects: ProjectForUi[],
|
||||||
): Promise<IProjectWithCountAndOwners[]> {
|
): Promise<IProjectForUiWithOwners[]> {
|
||||||
return projects.map((project) => ({
|
return projects.map((project) => ({
|
||||||
...project,
|
...project,
|
||||||
owners: [{ ownerType: 'system' }],
|
owners: [{ ownerType: 'system' }],
|
||||||
|
14
src/lib/features/project/fake-project-read-model.ts
Normal file
14
src/lib/features/project/fake-project-read-model.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { IProjectReadModel } from '../../types';
|
||||||
|
import type {
|
||||||
|
ProjectForUi,
|
||||||
|
ProjectForInsights,
|
||||||
|
} from './project-read-model-type';
|
||||||
|
|
||||||
|
export class FakeProjectReadModel implements IProjectReadModel {
|
||||||
|
getProjectsForAdminUi(): Promise<ProjectForUi[]> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
getProjectsForInsights(): Promise<ProjectForInsights[]> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
@ -3,23 +3,24 @@ import getLogger from '../../../test/fixtures/no-logger';
|
|||||||
import { type IUser, RoleName, type IGroup } from '../../types';
|
import { type IUser, RoleName, type IGroup } from '../../types';
|
||||||
import { randomId } from '../../util';
|
import { randomId } from '../../util';
|
||||||
import { ProjectOwnersReadModel } from './project-owners-read-model';
|
import { ProjectOwnersReadModel } from './project-owners-read-model';
|
||||||
|
import type { ProjectForUi } from './project-read-model-type';
|
||||||
|
|
||||||
jest.mock('../../util', () => ({
|
jest.mock('../../util', () => ({
|
||||||
...jest.requireActual('../../util'),
|
...jest.requireActual('../../util'),
|
||||||
generateImageUrl: jest.fn((input) => `https://${input.image_url}`),
|
generateImageUrl: jest.fn((input) => `https://${input.image_url}`),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockProjectWithCounts = (name: string) => ({
|
const mockProjectData = (name: string): ProjectForUi => ({
|
||||||
name,
|
name,
|
||||||
id: name,
|
id: name,
|
||||||
description: '',
|
|
||||||
featureCount: 0,
|
featureCount: 0,
|
||||||
memberCount: 0,
|
memberCount: 0,
|
||||||
mode: 'open' as const,
|
mode: 'open' as const,
|
||||||
defaultStickiness: 'default' as const,
|
health: 100,
|
||||||
staleFeatureCount: 0,
|
createdAt: new Date(),
|
||||||
potentiallyStaleFeatureCount: 0,
|
favorite: false,
|
||||||
avgTimeToProduction: 0,
|
lastReportedFlagUsage: null,
|
||||||
|
lastFlagUpdate: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('unit tests', () => {
|
describe('unit tests', () => {
|
||||||
@ -351,8 +352,8 @@ describe('integration tests', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const projectsWithOwners = await readModel.addOwners([
|
const projectsWithOwners = await readModel.addOwners([
|
||||||
mockProjectWithCounts(projectIdA),
|
mockProjectData(projectIdA),
|
||||||
mockProjectWithCounts(projectIdB),
|
mockProjectData(projectIdB),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(projectsWithOwners).toMatchObject([
|
expect(projectsWithOwners).toMatchObject([
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import type { Db } from '../../db/db';
|
import type { Db } from '../../db/db';
|
||||||
import { RoleName, type IProjectWithCount } from '../../types';
|
import { RoleName } from '../../types';
|
||||||
import { anonymise, generateImageUrl } from '../../util';
|
import { anonymise, generateImageUrl } from '../../util';
|
||||||
import type {
|
import type {
|
||||||
GroupProjectOwner,
|
GroupProjectOwner,
|
||||||
IProjectOwnersReadModel,
|
IProjectOwnersReadModel,
|
||||||
IProjectWithCountAndOwners,
|
IProjectForUiWithOwners,
|
||||||
ProjectOwnersDictionary,
|
ProjectOwnersDictionary,
|
||||||
UserProjectOwner,
|
UserProjectOwner,
|
||||||
} from './project-owners-read-model.type';
|
} from './project-owners-read-model.type';
|
||||||
|
import type { ProjectForUi } from './project-read-model-type';
|
||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
ROLE_USER: 'role_user',
|
ROLE_USER: 'role_user',
|
||||||
@ -24,9 +25,9 @@ export class ProjectOwnersReadModel implements IProjectOwnersReadModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static addOwnerData(
|
static addOwnerData(
|
||||||
projects: IProjectWithCount[],
|
projects: ProjectForUi[],
|
||||||
owners: ProjectOwnersDictionary,
|
owners: ProjectOwnersDictionary,
|
||||||
): IProjectWithCountAndOwners[] {
|
): IProjectForUiWithOwners[] {
|
||||||
return projects.map((project) => ({
|
return projects.map((project) => ({
|
||||||
...project,
|
...project,
|
||||||
owners: owners[project.id] || [{ ownerType: 'system' }],
|
owners: owners[project.id] || [{ ownerType: 'system' }],
|
||||||
@ -138,9 +139,9 @@ export class ProjectOwnersReadModel implements IProjectOwnersReadModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addOwners(
|
async addOwners(
|
||||||
projects: IProjectWithCount[],
|
projects: ProjectForUi[],
|
||||||
anonymizeProjectOwners: boolean = false,
|
anonymizeProjectOwners: boolean = false,
|
||||||
): Promise<IProjectWithCountAndOwners[]> {
|
): Promise<IProjectForUiWithOwners[]> {
|
||||||
const owners = await this.getAllProjectOwners(anonymizeProjectOwners);
|
const owners = await this.getAllProjectOwners(anonymizeProjectOwners);
|
||||||
|
|
||||||
return ProjectOwnersReadModel.addOwnerData(projects, owners);
|
return ProjectOwnersReadModel.addOwnerData(projects, owners);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { IProjectWithCount } from '../../types';
|
import type { TransitionalProjectData } from './project-read-model-type';
|
||||||
|
|
||||||
export type SystemOwner = { ownerType: 'system' };
|
export type SystemOwner = { ownerType: 'system' };
|
||||||
export type UserProjectOwner = {
|
export type UserProjectOwner = {
|
||||||
@ -17,13 +17,13 @@ type ProjectOwners =
|
|||||||
|
|
||||||
export type ProjectOwnersDictionary = Record<string, ProjectOwners>;
|
export type ProjectOwnersDictionary = Record<string, ProjectOwners>;
|
||||||
|
|
||||||
export type IProjectWithCountAndOwners = IProjectWithCount & {
|
export type IProjectForUiWithOwners = TransitionalProjectData & {
|
||||||
owners: ProjectOwners;
|
owners: ProjectOwners;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IProjectOwnersReadModel {
|
export interface IProjectOwnersReadModel {
|
||||||
addOwners(
|
addOwners(
|
||||||
projects: IProjectWithCount[],
|
projects: TransitionalProjectData[],
|
||||||
anonymizeProjectOwners?: boolean,
|
anonymizeProjectOwners?: boolean,
|
||||||
): Promise<IProjectWithCountAndOwners[]>;
|
): Promise<IProjectForUiWithOwners[]>;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { ProjectMode } from '../../types';
|
import type { IProjectWithCount, ProjectMode } from '../../types';
|
||||||
import type { IProjectQuery } from './project-store-type';
|
import type { IProjectQuery } from './project-store-type';
|
||||||
|
|
||||||
export type ProjectForUi = {
|
export type ProjectForUi = {
|
||||||
@ -16,6 +16,9 @@ export type ProjectForUi = {
|
|||||||
lastFlagUpdate: Date | null;
|
lastFlagUpdate: Date | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @todo remove with flag useProjectReadModel
|
||||||
|
export type TransitionalProjectData = ProjectForUi | IProjectWithCount;
|
||||||
|
|
||||||
export type ProjectForInsights = {
|
export type ProjectForInsights = {
|
||||||
id: string;
|
id: string;
|
||||||
health: number;
|
health: number;
|
||||||
|
@ -49,13 +49,10 @@ export class ProjectReadModel implements IProjectReadModel {
|
|||||||
private db: Db;
|
private db: Db;
|
||||||
|
|
||||||
private timer: Function;
|
private timer: Function;
|
||||||
private flagResolver: IFlagResolver;
|
|
||||||
constructor(
|
|
||||||
db: Db,
|
|
||||||
|
|
||||||
eventBus: EventEmitter,
|
private flagResolver: IFlagResolver;
|
||||||
flagResolver: IFlagResolver,
|
|
||||||
) {
|
constructor(db: Db, eventBus: EventEmitter, flagResolver: IFlagResolver) {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.timer = (action) =>
|
this.timer = (action) =>
|
||||||
metricsHelper.wrapTimer(eventBus, DB_TIME, {
|
metricsHelper.wrapTimer(eventBus, DB_TIME, {
|
||||||
|
@ -82,7 +82,9 @@ beforeAll(async () => {
|
|||||||
await stores.accessStore.addUserToRole(opsUser.id, 1, '');
|
await stores.accessStore.addUserToRole(opsUser.id, 1, '');
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
getLogger,
|
getLogger,
|
||||||
experimental: { flags: { archiveProjects: true } },
|
experimental: {
|
||||||
|
flags: { archiveProjects: true, useProjectReadModel: true },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
eventService = createEventsService(db.rawDatabase, config);
|
eventService = createEventsService(db.rawDatabase, config);
|
||||||
accessService = createAccessService(db.rawDatabase, config);
|
accessService = createAccessService(db.rawDatabase, config);
|
||||||
@ -323,9 +325,7 @@ test('should revive project', async () => {
|
|||||||
const project = {
|
const project = {
|
||||||
id: 'test-revive',
|
id: 'test-revive',
|
||||||
name: 'New project',
|
name: 'New project',
|
||||||
description: 'Blah',
|
|
||||||
mode: 'open' as const,
|
mode: 'open' as const,
|
||||||
defaultStickiness: 'default',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await projectService.createProject(project, user, TEST_AUDIT_USER);
|
await projectService.createProject(project, user, TEST_AUDIT_USER);
|
||||||
@ -347,9 +347,7 @@ test('should not be able to archive project with flags', async () => {
|
|||||||
const project = {
|
const project = {
|
||||||
id: 'test-archive-with-flags',
|
id: 'test-archive-with-flags',
|
||||||
name: 'New project',
|
name: 'New project',
|
||||||
description: 'Blah',
|
|
||||||
mode: 'open' as const,
|
mode: 'open' as const,
|
||||||
defaultStickiness: 'default',
|
|
||||||
};
|
};
|
||||||
await projectService.createProject(project, user, auditUser);
|
await projectService.createProject(project, user, auditUser);
|
||||||
await stores.featureToggleStore.create(project.id, {
|
await stores.featureToggleStore.create(project.id, {
|
||||||
@ -2748,9 +2746,7 @@ test('should get project settings with mode', async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(foundProjectOne!.mode).toBe('private');
|
expect(foundProjectOne!.mode).toBe('private');
|
||||||
expect(foundProjectOne!.defaultStickiness).toBe('clientId');
|
|
||||||
expect(foundProjectTwo!.mode).toBe('open');
|
expect(foundProjectTwo!.mode).toBe('open');
|
||||||
expect(foundProjectTwo!.defaultStickiness).toBe('default');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create project with environments', () => {
|
describe('create project with environments', () => {
|
||||||
|
@ -32,7 +32,6 @@ import {
|
|||||||
type IProjectRoleUsage,
|
type IProjectRoleUsage,
|
||||||
type IProjectStore,
|
type IProjectStore,
|
||||||
type IProjectUpdate,
|
type IProjectUpdate,
|
||||||
type IProjectWithCount,
|
|
||||||
type IUnleashConfig,
|
type IUnleashConfig,
|
||||||
type IUnleashStores,
|
type IUnleashStores,
|
||||||
MOVE_FEATURE_TOGGLE,
|
MOVE_FEATURE_TOGGLE,
|
||||||
@ -54,6 +53,7 @@ import {
|
|||||||
ProjectUserUpdateRoleEvent,
|
ProjectUserUpdateRoleEvent,
|
||||||
RoleName,
|
RoleName,
|
||||||
SYSTEM_USER_ID,
|
SYSTEM_USER_ID,
|
||||||
|
type IProjectReadModel,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import type {
|
import type {
|
||||||
IProjectAccessModel,
|
IProjectAccessModel,
|
||||||
@ -87,6 +87,7 @@ import type { IProjectFlagCreatorsReadModel } from './project-flag-creators-read
|
|||||||
import { throwExceedsLimitError } from '../../error/exceeds-limit-error';
|
import { throwExceedsLimitError } from '../../error/exceeds-limit-error';
|
||||||
import type EventEmitter from 'events';
|
import type EventEmitter from 'events';
|
||||||
import type { ApiTokenService } from '../../services/api-token-service';
|
import type { ApiTokenService } from '../../services/api-token-service';
|
||||||
|
import type { TransitionalProjectData } from './project-read-model-type';
|
||||||
|
|
||||||
type Days = number;
|
type Days = number;
|
||||||
type Count = number;
|
type Count = number;
|
||||||
@ -161,6 +162,8 @@ export default class ProjectService {
|
|||||||
|
|
||||||
private eventBus: EventEmitter;
|
private eventBus: EventEmitter;
|
||||||
|
|
||||||
|
private projectReadModel: IProjectReadModel;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{
|
{
|
||||||
projectStore,
|
projectStore,
|
||||||
@ -172,6 +175,7 @@ export default class ProjectService {
|
|||||||
featureEnvironmentStore,
|
featureEnvironmentStore,
|
||||||
accountStore,
|
accountStore,
|
||||||
projectStatsStore,
|
projectStatsStore,
|
||||||
|
projectReadModel,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
IUnleashStores,
|
IUnleashStores,
|
||||||
| 'projectStore'
|
| 'projectStore'
|
||||||
@ -183,6 +187,7 @@ export default class ProjectService {
|
|||||||
| 'featureEnvironmentStore'
|
| 'featureEnvironmentStore'
|
||||||
| 'accountStore'
|
| 'accountStore'
|
||||||
| 'projectStatsStore'
|
| 'projectStatsStore'
|
||||||
|
| 'projectReadModel'
|
||||||
>,
|
>,
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
accessService: AccessService,
|
accessService: AccessService,
|
||||||
@ -214,16 +219,18 @@ export default class ProjectService {
|
|||||||
this.isEnterprise = config.isEnterprise;
|
this.isEnterprise = config.isEnterprise;
|
||||||
this.resourceLimits = config.resourceLimits;
|
this.resourceLimits = config.resourceLimits;
|
||||||
this.eventBus = config.eventBus;
|
this.eventBus = config.eventBus;
|
||||||
|
this.projectReadModel = projectReadModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjects(
|
async getProjects(
|
||||||
query?: IProjectQuery,
|
query?: IProjectQuery,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
): Promise<IProjectWithCount[]> {
|
): Promise<TransitionalProjectData[]> {
|
||||||
const projects = await this.projectStore.getProjectsWithCounts(
|
const getProjects = this.flagResolver.isEnabled('useProjectReadModel')
|
||||||
query,
|
? () => this.projectReadModel.getProjectsForAdminUi(query, userId)
|
||||||
userId,
|
: () => this.projectStore.getProjectsWithCounts(query, userId);
|
||||||
);
|
|
||||||
|
const projects = await getProjects();
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const projectAccess =
|
const projectAccess =
|
||||||
@ -243,8 +250,8 @@ export default class ProjectService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async addOwnersToProjects(
|
async addOwnersToProjects(
|
||||||
projects: IProjectWithCount[],
|
projects: TransitionalProjectData[],
|
||||||
): Promise<IProjectWithCount[]> {
|
): Promise<TransitionalProjectData[]> {
|
||||||
const anonymizeProjectOwners = this.flagResolver.isEnabled(
|
const anonymizeProjectOwners = this.flagResolver.isEnabled(
|
||||||
'anonymizeProjectOwners',
|
'anonymizeProjectOwners',
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import type { FromSchema } from 'json-schema-to-ts';
|
|||||||
export const projectSchema = {
|
export const projectSchema = {
|
||||||
$id: '#/components/schemas/projectSchema',
|
$id: '#/components/schemas/projectSchema',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
// additionalProperties: false, // todo: re-enable when flag projectListImprovements is removed
|
||||||
required: ['id', 'name'],
|
required: ['id', 'name'],
|
||||||
description:
|
description:
|
||||||
'A definition of the project used for projects listing purposes',
|
'A definition of the project used for projects listing purposes',
|
||||||
@ -19,6 +19,7 @@ export const projectSchema = {
|
|||||||
description: 'The name of this project',
|
description: 'The name of this project',
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
deprecated: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
example: 'DX squad feature release',
|
example: 'DX squad feature release',
|
||||||
@ -36,11 +37,13 @@ export const projectSchema = {
|
|||||||
description: 'The number of features this project has',
|
description: 'The number of features this project has',
|
||||||
},
|
},
|
||||||
staleFeatureCount: {
|
staleFeatureCount: {
|
||||||
|
deprecated: true,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
example: 10,
|
example: 10,
|
||||||
description: 'The number of stale features this project has',
|
description: 'The number of stale features this project has',
|
||||||
},
|
},
|
||||||
potentiallyStaleFeatureCount: {
|
potentiallyStaleFeatureCount: {
|
||||||
|
deprecated: true,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
example: 10,
|
example: 10,
|
||||||
description:
|
description:
|
||||||
@ -58,6 +61,7 @@ export const projectSchema = {
|
|||||||
format: 'date-time',
|
format: 'date-time',
|
||||||
},
|
},
|
||||||
updatedAt: {
|
updatedAt: {
|
||||||
|
deprecated: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
format: 'date-time',
|
format: 'date-time',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
@ -85,12 +89,14 @@ export const projectSchema = {
|
|||||||
"The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not.",
|
"The project's [collaboration mode](https://docs.getunleash.io/reference/project-collaboration-mode). Determines whether non-project members can submit change requests or not.",
|
||||||
},
|
},
|
||||||
defaultStickiness: {
|
defaultStickiness: {
|
||||||
|
deprecated: true,
|
||||||
type: 'string',
|
type: 'string',
|
||||||
example: 'userId',
|
example: 'userId',
|
||||||
description:
|
description:
|
||||||
'A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy',
|
'A default stickiness for the project affecting the default stickiness value for variants and Gradual Rollout strategy',
|
||||||
},
|
},
|
||||||
avgTimeToProduction: {
|
avgTimeToProduction: {
|
||||||
|
deprecated: true,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
example: 10,
|
example: 10,
|
||||||
description:
|
description:
|
||||||
|
@ -568,6 +568,7 @@ export interface ICustomRole extends IRole {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @deprecated Remove with flag useProjectReadModel
|
||||||
export interface IProjectWithCount extends IProject {
|
export interface IProjectWithCount extends IProject {
|
||||||
featureCount: number;
|
featureCount: number;
|
||||||
staleFeatureCount: number;
|
staleFeatureCount: number;
|
||||||
|
@ -48,6 +48,7 @@ import { IFeatureLifecycleReadModel } from '../features/feature-lifecycle/featur
|
|||||||
import { ILargestResourcesReadModel } from '../features/metrics/sizes/largest-resources-read-model-type';
|
import { ILargestResourcesReadModel } from '../features/metrics/sizes/largest-resources-read-model-type';
|
||||||
import type { IntegrationEventsStore } from '../features/integration-events/integration-events-store';
|
import type { IntegrationEventsStore } from '../features/integration-events/integration-events-store';
|
||||||
import { IFeatureCollaboratorsReadModel } from '../features/feature-toggle/types/feature-collaborators-read-model-type';
|
import { IFeatureCollaboratorsReadModel } from '../features/feature-toggle/types/feature-collaborators-read-model-type';
|
||||||
|
import type { IProjectReadModel } from '../features/project/project-read-model-type';
|
||||||
|
|
||||||
export interface IUnleashStores {
|
export interface IUnleashStores {
|
||||||
accessStore: IAccessStore;
|
accessStore: IAccessStore;
|
||||||
@ -100,6 +101,7 @@ export interface IUnleashStores {
|
|||||||
largestResourcesReadModel: ILargestResourcesReadModel;
|
largestResourcesReadModel: ILargestResourcesReadModel;
|
||||||
integrationEventsStore: IntegrationEventsStore;
|
integrationEventsStore: IntegrationEventsStore;
|
||||||
featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel;
|
featureCollaboratorsReadModel: IFeatureCollaboratorsReadModel;
|
||||||
|
projectReadModel: IProjectReadModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -151,4 +153,5 @@ export {
|
|||||||
ILargestResourcesReadModel,
|
ILargestResourcesReadModel,
|
||||||
IFeatureCollaboratorsReadModel,
|
IFeatureCollaboratorsReadModel,
|
||||||
type IntegrationEventsStore,
|
type IntegrationEventsStore,
|
||||||
|
type IProjectReadModel,
|
||||||
};
|
};
|
||||||
|
2
src/test/fixtures/store.ts
vendored
2
src/test/fixtures/store.ts
vendored
@ -51,6 +51,7 @@ import { FakeFeatureStrategiesReadModel } from '../../lib/features/feature-toggl
|
|||||||
import { FakeFeatureLifecycleReadModel } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-read-model';
|
import { FakeFeatureLifecycleReadModel } from '../../lib/features/feature-lifecycle/fake-feature-lifecycle-read-model';
|
||||||
import { FakeLargestResourcesReadModel } from '../../lib/features/metrics/sizes/fake-largest-resources-read-model';
|
import { FakeLargestResourcesReadModel } from '../../lib/features/metrics/sizes/fake-largest-resources-read-model';
|
||||||
import { FakeFeatureCollaboratorsReadModel } from '../../lib/features/feature-toggle/fake-feature-collaborators-read-model';
|
import { FakeFeatureCollaboratorsReadModel } from '../../lib/features/feature-toggle/fake-feature-collaborators-read-model';
|
||||||
|
import { createFakeProjectReadModel } from '../../lib/features/project/createProjectReadModel';
|
||||||
|
|
||||||
const db = {
|
const db = {
|
||||||
select: () => ({
|
select: () => ({
|
||||||
@ -111,6 +112,7 @@ const createStores: () => IUnleashStores = () => {
|
|||||||
largestResourcesReadModel: new FakeLargestResourcesReadModel(),
|
largestResourcesReadModel: new FakeLargestResourcesReadModel(),
|
||||||
integrationEventsStore: {} as IntegrationEventsStore,
|
integrationEventsStore: {} as IntegrationEventsStore,
|
||||||
featureCollaboratorsReadModel: new FakeFeatureCollaboratorsReadModel(),
|
featureCollaboratorsReadModel: new FakeFeatureCollaboratorsReadModel(),
|
||||||
|
projectReadModel: createFakeProjectReadModel(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user