1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-10 01:16:39 +02:00

feat: copy feature with parent (#4918)

This commit is contained in:
Mateusz Kwasniewski 2023-10-04 12:20:27 +02:00 committed by GitHub
parent 5141d9db67
commit 257414424d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 140 additions and 346 deletions

View File

@ -84,7 +84,7 @@ export const AddDependencyDialogue = ({
secondaryButtonText='Cancel'
>
<Box>
You feature will be evaluated only when the selected parent
Your feature will be evaluated only when the selected parent
feature is enabled in the same environment.
<br />
<br />

View File

@ -22,6 +22,32 @@ export class DependentFeaturesService {
this.eventService = eventService;
}
async cloneDependencies(
{
featureName,
newFeatureName,
projectId,
}: { featureName: string; newFeatureName: string; projectId: string },
user: string,
) {
const parents = await this.dependentFeaturesReadModel.getParents(
featureName,
);
await Promise.all(
parents.map((parent) =>
this.upsertFeatureDependency(
{ child: newFeatureName, projectId },
{
feature: parent.feature,
enabled: parent.enabled,
variants: parent.variants,
},
user,
),
),
);
}
async upsertFeatureDependency(
{ child, projectId }: { child: string; projectId: string },
dependentFeature: CreateDependentFeatureSchema,

View File

@ -48,6 +48,10 @@ import { DependentFeaturesReadModel } from '../dependent-features/dependent-feat
import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
import FeatureTagStore from '../../db/feature-tag-store';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
import {
createDependentFeaturesService,
createFakeDependentFeaturesService,
} from '../dependent-features/createDependentFeaturesService';
export const createFeatureToggleService = (
db: Db,
@ -115,6 +119,8 @@ export const createFeatureToggleService = (
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
const dependentFeaturesService = createDependentFeaturesService(db, config);
const featureToggleService = new FeatureToggleService(
{
featureStrategiesStore,
@ -133,6 +139,7 @@ export const createFeatureToggleService = (
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
dependentFeaturesService,
);
return featureToggleService;
};
@ -173,6 +180,7 @@ export const createFakeFeatureToggleService = (
const changeRequestAccessReadModel = createFakeChangeRequestAccessService();
const fakePrivateProjectChecker = createFakePrivateProjectChecker();
const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
const dependentFeaturesService = createFakeDependentFeaturesService(config);
const featureToggleService = new FeatureToggleService(
{
featureStrategiesStore,
@ -191,6 +199,7 @@ export const createFakeFeatureToggleService = (
changeRequestAccessReadModel,
fakePrivateProjectChecker,
dependentFeaturesReadModel,
dependentFeaturesService,
);
return featureToggleService;
};

View File

@ -13,6 +13,7 @@ import { IPrivateProjectChecker } from '../features/private-project/privateProje
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
import EventService from './event-service';
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
import { DependentFeaturesService } from '../features/dependent-features/dependent-features-service';
test('Should only store events for potentially stale on', async () => {
expect.assertions(2);
@ -64,6 +65,7 @@ test('Should only store events for potentially stale on', async () => {
{} as IChangeRequestAccessReadModel,
{} as IPrivateProjectChecker,
{} as IDependentFeaturesReadModel,
{} as DependentFeaturesService,
);
await featureToggleService.updatePotentiallyStaleFeatures();

View File

@ -99,6 +99,7 @@ import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
import EventService from './event-service';
import { DependentFeaturesService } from '../features/dependent-features/dependent-features-service';
interface IFeatureContext {
featureName: string;
@ -162,6 +163,8 @@ class FeatureToggleService {
private dependentFeaturesReadModel: IDependentFeaturesReadModel;
private dependentFeaturesService: DependentFeaturesService;
constructor(
{
featureStrategiesStore,
@ -193,6 +196,7 @@ class FeatureToggleService {
changeRequestAccessReadModel: IChangeRequestAccessReadModel,
privateProjectChecker: IPrivateProjectChecker,
dependentFeaturesReadModel: IDependentFeaturesReadModel,
dependentFeaturesService: DependentFeaturesService,
) {
this.logger = getLogger('services/feature-toggle-service.ts');
this.featureStrategiesStore = featureStrategiesStore;
@ -210,6 +214,7 @@ class FeatureToggleService {
this.changeRequestAccessReadModel = changeRequestAccessReadModel;
this.privateProjectChecker = privateProjectChecker;
this.dependentFeaturesReadModel = dependentFeaturesReadModel;
this.dependentFeaturesService = dependentFeaturesService;
}
async validateFeaturesContext(
@ -1255,7 +1260,17 @@ class FeatureToggleService {
}),
);
await Promise.all([...strategyTasks, ...variantTasks]);
const cloneDependencies =
this.dependentFeaturesService.cloneDependencies(
{ featureName, newFeatureName, projectId },
userName,
);
await Promise.all([
...strategyTasks,
...variantTasks,
cloneDependencies,
]);
return created;
}

View File

@ -233,6 +233,13 @@ export const createServices = (
config,
privateProjectChecker,
);
const dependentFeaturesService = db
? createDependentFeaturesService(db, config)
: createFakeDependentFeaturesService(config);
const transactionalDependentFeaturesService = (txDb: Knex.Transaction) =>
createDependentFeaturesService(txDb, config);
const featureToggleServiceV2 = new FeatureToggleService(
stores,
config,
@ -242,6 +249,7 @@ export const createServices = (
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
dependentFeaturesService,
);
const environmentService = new EnvironmentService(stores, config);
const featureTagService = new FeatureTagService(
@ -327,12 +335,6 @@ export const createServices = (
const eventAnnouncerService = new EventAnnouncerService(stores, config);
const dependentFeaturesService = db
? createDependentFeaturesService(db, config)
: createFakeDependentFeaturesService(config);
const transactionalDependentFeaturesService = (txDb: Knex.Transaction) =>
createDependentFeaturesService(txDb, config);
return {
accessService,
accountService,

View File

@ -295,6 +295,29 @@ test('Should not allow to archive/delete feature with children', async () => {
);
});
test('should clone feature with parent dependencies', async () => {
const parent = uuidv4();
const child = uuidv4();
const childClone = uuidv4();
await app.createFeature(parent, 'default');
await app.createFeature(child, 'default');
await app.addDependency(child, parent);
await app.request
.post(`/api/admin/projects/default/features/${child}/clone`)
.send({ name: childClone, replaceGroupId: false })
.expect(201);
const { body: clonedFeature } = await app.getProjectFeatures(
'default',
child,
);
expect(clonedFeature).toMatchObject({
children: [],
dependencies: [{ feature: parent, enabled: true, variants: [] }],
});
});
test('Can get features for project', async () => {
await app.request
.post('/api/admin/projects/default/features')

View File

@ -13,25 +13,19 @@ import {
IUnleashStores,
IUserAccessOverview,
} from '../../../lib/types';
import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import ProjectService from '../../../lib/services/project-service';
import { createTestConfig } from '../../config/test-config';
import { DEFAULT_PROJECT } from '../../../lib/types/project';
import { ALL_PROJECTS } from '../../../lib/util/constants';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { EventService, FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import {
createAccessService,
createFeatureToggleService,
createProjectService,
} from '../../../lib/features';
let db: ITestDb;
let stores: IUnleashStores;
let accessService: AccessService;
let eventService: EventService;
let groupService: GroupService;
let featureToggleService;
let favoritesService;
let projectService;
let editorUser;
let editorRole;
@ -238,51 +232,14 @@ beforeAll(async () => {
// @ts-ignore
experimental: { environments: { enabled: true } },
});
eventService = new EventService(stores, config);
groupService = new GroupService(stores, { getLogger }, eventService);
accessService = new AccessService(stores, config, groupService);
accessService = createAccessService(db.rawDatabase, config);
const roles = await accessService.getRootRoles();
editorRole = roles.find((r) => r.name === RoleName.EDITOR);
adminRole = roles.find((r) => r.name === RoleName.ADMIN);
readRole = roles.find((r) => r.name === RoleName.VIEWER);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
config,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
featureToggleService = new FeatureToggleService(
stores,
config,
new SegmentService(
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
),
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
eventService,
privateProjectChecker,
);
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
projectService = createProjectService(db.rawDatabase, config);
editorUser = await createUser(editorRole.id);

View File

@ -6,20 +6,13 @@ import { ApiTokenType, IApiToken } from '../../../lib/types/models/api-token';
import { DEFAULT_ENV } from '../../../lib/util/constants';
import { addDays, subDays } from 'date-fns';
import ProjectService from '../../../lib/services/project-service';
import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { AccessService } from '../../../lib/services/access-service';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { EventService, FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import { EventService } from '../../../lib/services';
import { createProjectService } from '../../../lib/features';
let db;
let stores;
let apiTokenService: ApiTokenService;
let projectService: ProjectService;
let favoritesService: FavoritesService;
beforeAll(async () => {
const config = createTestConfig({
@ -28,35 +21,6 @@ beforeAll(async () => {
db = await dbInit('api_token_service_serial', getLogger);
stores = db.stores;
const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
config,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
const featureToggleService = new FeatureToggleService(
stores,
config,
new SegmentService(
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
),
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
const project = {
id: 'test-project',
name: 'Test Project',
@ -68,17 +32,7 @@ beforeAll(async () => {
name: 'Some Name',
email: 'test@getunleash.io',
});
favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
eventService,
privateProjectChecker,
);
projectService = createProjectService(db.rawDatabase, config);
await projectService.createProject(project, user);

View File

@ -2,12 +2,6 @@ import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { createTestConfig } from '../../config/test-config';
import dbInit from '../helpers/database-init';
import { DEFAULT_ENV } from '../../../lib/util';
import {
AccessService,
EventService,
GroupService,
SegmentService,
} from '../../../lib/services';
import { FeatureStrategySchema } from '../../../lib/openapi';
import User from '../../../lib/types/user';
import {
@ -23,9 +17,10 @@ import {
PermissionError,
} from '../../../lib/error';
import { ISegmentService } from '../../../lib/segments/segment-service-interface';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import {
createFeatureToggleService,
createSegmentService,
} from '../../../lib/features';
let stores: IUnleashStores;
let db;
@ -54,38 +49,10 @@ beforeAll(async () => {
);
unleashConfig = config;
stores = db.stores;
const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
config,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
segmentService = new SegmentService(
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
);
service = new FeatureToggleService(
stores,
config,
segmentService,
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
segmentService = createSegmentService(db.rawDatabase, config);
service = createFeatureToggleService(db.rawDatabase, config);
});
afterAll(async () => {
@ -462,40 +429,14 @@ test('If change requests are enabled, cannot change variants without going via C
{ name: featureName },
'test-user',
);
const eventService = new EventService(stores, unleashConfig);
const groupService = new GroupService(stores, unleashConfig, eventService);
const accessService = new AccessService(
stores,
unleashConfig,
groupService,
);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
unleashConfig,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
// Force all feature flags on to make sure we have Change requests on
const customFeatureService = new FeatureToggleService(
stores,
{
...unleashConfig,
flagResolver: {
isEnabled: () => true,
},
const customFeatureService = createFeatureToggleService(db.rawDatabase, {
...unleashConfig,
flagResolver: {
isEnabled: () => true,
},
segmentService,
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
});
const newVariant: IVariant = {
name: 'cr-enabled',
@ -556,40 +497,14 @@ test('If CRs are protected for any environment in the project stops bulk update
project.id,
disabledEnv.name,
);
const eventService = new EventService(stores, unleashConfig);
const groupService = new GroupService(stores, unleashConfig, eventService);
const accessService = new AccessService(
stores,
unleashConfig,
groupService,
);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
unleashConfig,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
// Force all feature flags on to make sure we have Change requests on
const customFeatureService = new FeatureToggleService(
stores,
{
...unleashConfig,
flagResolver: {
isEnabled: () => true,
},
const customFeatureService = createFeatureToggleService(db.rawDatabase, {
...unleashConfig,
flagResolver: {
isEnabled: () => true,
},
segmentService,
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
});
const toggle = await service.createFeatureToggle(
project.id,

View File

@ -12,7 +12,6 @@ import { createTestConfig } from '../../config/test-config';
import dbInit, { ITestDb } from '../helpers/database-init';
import { IUnleashStores } from '../../../lib/types/stores';
import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { SegmentService } from '../../../lib/services/segment-service';
import { FeatureToggle, ISegment, WeightType } from '../../../lib/types/model';
import { PlaygroundFeatureSchema } from '../../../lib/openapi/spec/playground-feature-schema';
import { offlineUnleashClientNode } from '../../../lib/features/playground/offline-unleash-client.test';
@ -21,13 +20,12 @@ import { SdkContextSchema } from 'lib/openapi/spec/sdk-context-schema';
import { SegmentSchema } from 'lib/openapi/spec/segment-schema';
import { playgroundStrategyEvaluation } from '../../../lib/openapi/spec/playground-strategy-schema';
import { PlaygroundSegmentSchema } from 'lib/openapi/spec/playground-segment-schema';
import { GroupService } from '../../../lib/services/group-service';
import { AccessService } from '../../../lib/services/access-service';
import { ISegmentService } from '../../../lib/segments/segment-service-interface';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import { EventService } from '../../../lib/services';
import {
createFeatureToggleService,
createSegmentService,
} from '../../../lib/features';
let stores: IUnleashStores;
let db: ITestDb;
@ -39,38 +37,13 @@ beforeAll(async () => {
const config = createTestConfig();
db = await dbInit('playground_service_serial', config.getLogger);
stores = db.stores;
const eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
config,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
segmentService = new SegmentService(
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
);
segmentService = createSegmentService(db.rawDatabase, config);
featureToggleService = new FeatureToggleService(
stores,
config,
segmentService,
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
service = new PlaygroundService(config, {
featureToggleServiceV2: featureToggleService,
segmentService,

View File

@ -1,28 +1,21 @@
import dbInit, { ITestDb } from '../helpers/database-init';
import getLogger from '../../fixtures/no-logger';
import FeatureToggleService from '../../../lib/services/feature-toggle-service';
import { AccessService } from '../../../lib/services/access-service';
import ProjectService from '../../../lib/services/project-service';
import ProjectHealthService from '../../../lib/services/project-health-service';
import { createTestConfig } from '../../config/test-config';
import { IUnleashStores } from '../../../lib/types';
import { IUser } from '../../../lib/server-impl';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { EventService, FavoritesService } from '../../../lib/services';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import { EventService } from '../../../lib/services';
import {
createFeatureToggleService,
createProjectService,
} from '../../../lib/features';
let stores: IUnleashStores;
let db: ITestDb;
let projectService;
let groupService;
let accessService;
let eventService: EventService;
let projectHealthService;
let featureToggleService;
let favoritesService;
let user: IUser;
beforeAll(async () => {
@ -34,47 +27,10 @@ beforeAll(async () => {
email: 'test@getunleash.io',
});
eventService = new EventService(stores, config);
groupService = new GroupService(stores, config, eventService);
accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
config,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
featureToggleService = new FeatureToggleService(
stores,
config,
new SegmentService(
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
),
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
favoritesService = new FavoritesService(stores, config, eventService);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
eventService,
privateProjectChecker,
);
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
projectService = createProjectService(db.rawDatabase, config);
projectHealthService = new ProjectHealthService(
stores,
config,

View File

@ -9,25 +9,23 @@ import { RoleName } from '../../../lib/types/model';
import { randomId } from '../../../lib/util/random-id';
import EnvironmentService from '../../../lib/services/environment-service';
import IncompatibleProjectError from '../../../lib/error/incompatible-project-error';
import { SegmentService } from '../../../lib/services/segment-service';
import { GroupService } from '../../../lib/services/group-service';
import { EventService, FavoritesService } from '../../../lib/services';
import { EventService } from '../../../lib/services';
import { FeatureEnvironmentEvent } from '../../../lib/types/events';
import { addDays, subDays } from 'date-fns';
import { ChangeRequestAccessReadModel } from '../../../lib/features/change-request-access-service/sql-change-request-access-read-model';
import { createPrivateProjectChecker } from '../../../lib/features/private-project/createPrivateProjectChecker';
import { DependentFeaturesReadModel } from '../../../lib/features/dependent-features/dependent-features-read-model';
import {
createAccessService,
createFeatureToggleService,
createProjectService,
} from '../../../lib/features';
let stores;
let db: ITestDb;
let projectService: ProjectService;
let groupService: GroupService;
let accessService: AccessService;
let eventService: EventService;
let environmentService: EnvironmentService;
let featureToggleService: FeatureToggleService;
let favoritesService: FavoritesService;
let user;
const isProjectUser = async (
@ -55,48 +53,12 @@ beforeAll(async () => {
},
});
eventService = new EventService(stores, config);
groupService = new GroupService(stores, config, eventService);
accessService = new AccessService(stores, config, groupService);
const changeRequestAccessReadModel = new ChangeRequestAccessReadModel(
db.rawDatabase,
accessService,
);
const privateProjectChecker = createPrivateProjectChecker(
db.rawDatabase,
config,
);
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
db.rawDatabase,
);
featureToggleService = new FeatureToggleService(
stores,
config,
new SegmentService(
stores,
changeRequestAccessReadModel,
config,
eventService,
privateProjectChecker,
),
accessService,
eventService,
changeRequestAccessReadModel,
privateProjectChecker,
dependentFeaturesReadModel,
);
accessService = createAccessService(db.rawDatabase, config);
featureToggleService = createFeatureToggleService(db.rawDatabase, config);
favoritesService = new FavoritesService(stores, config, eventService);
environmentService = new EnvironmentService(stores, config);
projectService = new ProjectService(
stores,
config,
accessService,
featureToggleService,
groupService,
favoritesService,
eventService,
privateProjectChecker,
);
projectService = createProjectService(db.rawDatabase, config);
});
afterAll(async () => {