mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
feat: feature admin API returns dependencies and children (#4848)
This commit is contained in:
parent
fd8775f13d
commit
87a81120d2
@ -1,5 +1,7 @@
|
||||
import { IDependency } from '../../types';
|
||||
|
||||
export interface IDependentFeaturesReadModel {
|
||||
getChildren(parent: string): Promise<string[]>;
|
||||
getParents(child: string): Promise<string[]>;
|
||||
getParents(child: string): Promise<IDependency[]>;
|
||||
getParentOptions(child: string): Promise<string[]>;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Db } from '../../db/db';
|
||||
import { IDependentFeaturesReadModel } from './dependent-features-read-model-type';
|
||||
import { IDependency } from '../../types';
|
||||
|
||||
export class DependentFeaturesReadModel implements IDependentFeaturesReadModel {
|
||||
private db: Db;
|
||||
@ -17,10 +18,14 @@ export class DependentFeaturesReadModel implements IDependentFeaturesReadModel {
|
||||
return rows.map((row) => row.child);
|
||||
}
|
||||
|
||||
async getParents(child: string): Promise<string[]> {
|
||||
async getParents(child: string): Promise<IDependency[]> {
|
||||
const rows = await this.db('dependent_features').where('child', child);
|
||||
|
||||
return rows.map((row) => row.parent);
|
||||
return rows.map((row) => ({
|
||||
feature: row.parent,
|
||||
enabled: row.enabled,
|
||||
variants: row.variants,
|
||||
}));
|
||||
}
|
||||
|
||||
async getParentOptions(child: string): Promise<string[]> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { IDependentFeaturesReadModel } from './dependent-features-read-model-type';
|
||||
import { IDependency } from '../../types';
|
||||
|
||||
export class FakeDependentFeaturesReadModel
|
||||
implements IDependentFeaturesReadModel
|
||||
@ -7,7 +8,7 @@ export class FakeDependentFeaturesReadModel
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
getParents(): Promise<string[]> {
|
||||
getParents(): Promise<IDependency[]> {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,8 @@ import {
|
||||
createFakePrivateProjectChecker,
|
||||
createPrivateProjectChecker,
|
||||
} from '../private-project/createPrivateProjectChecker';
|
||||
import { DependentFeaturesReadModel } from '../dependent-features/dependent-features-read-model';
|
||||
import { FakeDependentFeaturesReadModel } from '../dependent-features/fake-dependent-features-read-model';
|
||||
|
||||
export const createFeatureToggleService = (
|
||||
db: Db,
|
||||
@ -105,6 +107,8 @@ export const createFeatureToggleService = (
|
||||
|
||||
const privateProjectChecker = createPrivateProjectChecker(db, config);
|
||||
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(db);
|
||||
|
||||
const featureToggleService = new FeatureToggleService(
|
||||
{
|
||||
featureStrategiesStore,
|
||||
@ -122,6 +126,7 @@ export const createFeatureToggleService = (
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
return featureToggleService;
|
||||
};
|
||||
@ -155,7 +160,8 @@ export const createFakeFeatureToggleService = (
|
||||
);
|
||||
const segmentService = createFakeSegmentService(config);
|
||||
const changeRequestAccessReadModel = createFakeChangeRequestAccessService();
|
||||
const fakeprivateProjectChecker = createFakePrivateProjectChecker();
|
||||
const fakePrivateProjectChecker = createFakePrivateProjectChecker();
|
||||
const dependentFeaturesReadModel = new FakeDependentFeaturesReadModel();
|
||||
const featureToggleService = new FeatureToggleService(
|
||||
{
|
||||
featureStrategiesStore,
|
||||
@ -172,7 +178,8 @@ export const createFakeFeatureToggleService = (
|
||||
segmentService,
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
fakeprivateProjectChecker,
|
||||
fakePrivateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
return featureToggleService;
|
||||
};
|
||||
|
@ -121,6 +121,47 @@ export const featureSchema = {
|
||||
nullable: true,
|
||||
description: 'The list of feature tags',
|
||||
},
|
||||
children: {
|
||||
type: 'array',
|
||||
description:
|
||||
'The list of child feature names. This is an experimental field and may change.',
|
||||
items: {
|
||||
type: 'string',
|
||||
example: 'some-feature',
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['feature'],
|
||||
properties: {
|
||||
feature: {
|
||||
description: 'The name of the parent feature',
|
||||
type: 'string',
|
||||
example: 'some-feature',
|
||||
},
|
||||
enabled: {
|
||||
description:
|
||||
'Whether the parent feature is enabled or not',
|
||||
type: 'boolean',
|
||||
example: true,
|
||||
},
|
||||
variants: {
|
||||
description:
|
||||
'The list of variants the parent feature should resolve to. Only valid when feature is enabled.',
|
||||
type: 'array',
|
||||
items: {
|
||||
example: 'some-feature-blue-variant',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
description:
|
||||
'The list of parent dependencies. This is an experimental field and may change.',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
|
@ -10,6 +10,7 @@ import { AccessService } from './access-service';
|
||||
import { IChangeRequestAccessReadModel } from 'lib/features/change-request-access-service/change-request-access-read-model';
|
||||
import { ISegmentService } from 'lib/segments/segment-service-interface';
|
||||
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
||||
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
|
||||
|
||||
test('Should only store events for potentially stale on', async () => {
|
||||
expect.assertions(2);
|
||||
@ -51,6 +52,7 @@ test('Should only store events for potentially stale on', async () => {
|
||||
{} as AccessService,
|
||||
{} as IChangeRequestAccessReadModel,
|
||||
{} as IPrivateProjectChecker,
|
||||
{} as IDependentFeaturesReadModel,
|
||||
);
|
||||
|
||||
await featureToggleService.updatePotentiallyStaleFeatures();
|
||||
|
@ -16,9 +16,11 @@ import {
|
||||
FeatureToggle,
|
||||
FeatureToggleDTO,
|
||||
FeatureToggleLegacy,
|
||||
FeatureToggleWithDependencies,
|
||||
FeatureToggleWithEnvironment,
|
||||
FeatureVariantEvent,
|
||||
IConstraint,
|
||||
IDependency,
|
||||
IEventStore,
|
||||
IFeatureEnvironmentInfo,
|
||||
IFeatureEnvironmentStore,
|
||||
@ -96,6 +98,7 @@ import { ISegmentService } from 'lib/segments/segment-service-interface';
|
||||
import { IChangeRequestAccessReadModel } from '../features/change-request-access-service/change-request-access-read-model';
|
||||
import { checkFeatureFlagNamesAgainstPattern } from '../features/feature-naming-pattern/feature-naming-validation';
|
||||
import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType';
|
||||
import { IDependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model-type';
|
||||
|
||||
interface IFeatureContext {
|
||||
featureName: string;
|
||||
@ -157,6 +160,8 @@ class FeatureToggleService {
|
||||
|
||||
private privateProjectChecker: IPrivateProjectChecker;
|
||||
|
||||
private dependentFeaturesReadModel: IDependentFeaturesReadModel;
|
||||
|
||||
constructor(
|
||||
{
|
||||
featureStrategiesStore,
|
||||
@ -188,6 +193,7 @@ class FeatureToggleService {
|
||||
accessService: AccessService,
|
||||
changeRequestAccessReadModel: IChangeRequestAccessReadModel,
|
||||
privateProjectChecker: IPrivateProjectChecker,
|
||||
dependentFeaturesReadModel: IDependentFeaturesReadModel,
|
||||
) {
|
||||
this.logger = getLogger('services/feature-toggle-service.ts');
|
||||
this.featureStrategiesStore = featureStrategiesStore;
|
||||
@ -204,6 +210,7 @@ class FeatureToggleService {
|
||||
this.flagResolver = flagResolver;
|
||||
this.changeRequestAccessReadModel = changeRequestAccessReadModel;
|
||||
this.privateProjectChecker = privateProjectChecker;
|
||||
this.dependentFeaturesReadModel = dependentFeaturesReadModel;
|
||||
}
|
||||
|
||||
async validateFeaturesContext(
|
||||
@ -921,7 +928,7 @@ class FeatureToggleService {
|
||||
projectId,
|
||||
environmentVariants,
|
||||
userId,
|
||||
}: IGetFeatureParams): Promise<FeatureToggleWithEnvironment> {
|
||||
}: IGetFeatureParams): Promise<FeatureToggleWithDependencies> {
|
||||
if (projectId) {
|
||||
await this.validateFeatureBelongsToProject({
|
||||
featureName,
|
||||
@ -929,18 +936,31 @@ class FeatureToggleService {
|
||||
});
|
||||
}
|
||||
|
||||
let dependencies: IDependency[] = [];
|
||||
let children: string[] = [];
|
||||
if (this.flagResolver.isEnabled('dependentFeatures')) {
|
||||
[dependencies, children] = await Promise.all([
|
||||
this.dependentFeaturesReadModel.getParents(featureName),
|
||||
this.dependentFeaturesReadModel.getChildren(featureName),
|
||||
]);
|
||||
}
|
||||
|
||||
if (environmentVariants) {
|
||||
return this.featureStrategiesStore.getFeatureToggleWithVariantEnvs(
|
||||
featureName,
|
||||
userId,
|
||||
archived,
|
||||
);
|
||||
const result =
|
||||
await this.featureStrategiesStore.getFeatureToggleWithVariantEnvs(
|
||||
featureName,
|
||||
userId,
|
||||
archived,
|
||||
);
|
||||
return { ...result, dependencies, children };
|
||||
} else {
|
||||
return this.featureStrategiesStore.getFeatureToggleWithEnvs(
|
||||
featureName,
|
||||
userId,
|
||||
archived,
|
||||
);
|
||||
const result =
|
||||
await this.featureStrategiesStore.getFeatureToggleWithEnvs(
|
||||
featureName,
|
||||
userId,
|
||||
archived,
|
||||
);
|
||||
return { ...result, dependencies, children };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,6 +73,8 @@ import {
|
||||
createDependentFeaturesService,
|
||||
createFakeDependentFeaturesService,
|
||||
} from '../features/dependent-features/createDependentFeaturesService';
|
||||
import { DependentFeaturesReadModel } from '../features/dependent-features/dependent-features-read-model';
|
||||
import { FakeDependentFeaturesReadModel } from '../features/dependent-features/fake-dependent-features-read-model';
|
||||
|
||||
// TODO: will be moved to scheduler feature directory
|
||||
export const scheduleServices = async (
|
||||
@ -175,6 +177,9 @@ export const createServices = (
|
||||
const privateProjectChecker = db
|
||||
? createPrivateProjectChecker(db, config)
|
||||
: createFakePrivateProjectChecker();
|
||||
const dependentFeaturesReadModel = db
|
||||
? new DependentFeaturesReadModel(db)
|
||||
: new FakeDependentFeaturesReadModel();
|
||||
|
||||
const contextService = new ContextService(
|
||||
stores,
|
||||
@ -227,6 +232,7 @@ export const createServices = (
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
const environmentService = new EnvironmentService(stores, config);
|
||||
const featureTagService = new FeatureTagService(stores, config);
|
||||
|
@ -96,6 +96,12 @@ export interface FeatureToggleWithEnvironment extends FeatureToggle {
|
||||
environments: IEnvironmentDetail[];
|
||||
}
|
||||
|
||||
export interface FeatureToggleWithDependencies
|
||||
extends FeatureToggleWithEnvironment {
|
||||
dependencies: IDependency[];
|
||||
children: string[];
|
||||
}
|
||||
|
||||
// @deprecated
|
||||
export interface FeatureToggleLegacy extends FeatureToggle {
|
||||
strategies: IStrategyConfig[];
|
||||
|
@ -91,6 +91,7 @@ beforeAll(async () => {
|
||||
experimental: {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -214,6 +215,32 @@ test('Can get project overview', async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('should list dependencies and children', async () => {
|
||||
const parent = uuidv4();
|
||||
const child = uuidv4();
|
||||
await app.createFeature(parent, 'default');
|
||||
await app.createFeature(child, 'default');
|
||||
await app.addDependency(child, parent);
|
||||
|
||||
const { body: childFeature } = await app.getProjectFeatures(
|
||||
'default',
|
||||
child,
|
||||
);
|
||||
const { body: parentFeature } = await app.getProjectFeatures(
|
||||
'default',
|
||||
parent,
|
||||
);
|
||||
|
||||
expect(childFeature).toMatchObject({
|
||||
children: [],
|
||||
dependencies: [{ feature: parent, enabled: true, variants: [] }],
|
||||
});
|
||||
expect(parentFeature).toMatchObject({
|
||||
children: [child],
|
||||
dependencies: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('Can get features for project', async () => {
|
||||
await app.request
|
||||
.post('/api/admin/projects/default/features')
|
||||
|
@ -20,6 +20,7 @@ beforeAll(async () => {
|
||||
flags: {
|
||||
strictSchemaValidation: true,
|
||||
featureNamingPattern: true,
|
||||
dependentFeatures: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -63,6 +63,8 @@ export interface IUnleashHttpAPI {
|
||||
importPayload: ImportTogglesSchema,
|
||||
expectedResponseCode?: number,
|
||||
): supertest.Test;
|
||||
|
||||
addDependency(child: string, parent: string): supertest.Test;
|
||||
}
|
||||
|
||||
function httpApis(
|
||||
@ -161,6 +163,21 @@ function httpApis(
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(expectedResponseCode);
|
||||
},
|
||||
|
||||
addDependency(
|
||||
child: string,
|
||||
parent: string,
|
||||
project = DEFAULT_PROJECT,
|
||||
expectedResponseCode: number = 200,
|
||||
): supertest.Test {
|
||||
return request
|
||||
.post(
|
||||
`/api/admin/projects/${project}/features/${child}/dependencies`,
|
||||
)
|
||||
.send({ feature: parent })
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(expectedResponseCode);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import { GroupService } from '../../../lib/services/group-service';
|
||||
import { 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';
|
||||
|
||||
let db: ITestDb;
|
||||
let stores: IUnleashStores;
|
||||
@ -250,6 +251,9 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
|
||||
db.rawDatabase,
|
||||
);
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
@ -262,6 +266,7 @@ beforeAll(async () => {
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
favoritesService = new FavoritesService(stores, config);
|
||||
projectService = new ProjectService(
|
||||
|
@ -13,6 +13,7 @@ import { GroupService } from '../../../lib/services/group-service';
|
||||
import { 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';
|
||||
|
||||
let db;
|
||||
let stores;
|
||||
@ -36,6 +37,9 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
|
||||
db.rawDatabase,
|
||||
);
|
||||
const featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
@ -48,6 +52,7 @@ beforeAll(async () => {
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
const project = {
|
||||
id: 'test-project',
|
||||
|
@ -24,6 +24,7 @@ import {
|
||||
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';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
let db;
|
||||
@ -63,6 +64,9 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
|
||||
db.rawDatabase,
|
||||
);
|
||||
segmentService = new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
@ -77,6 +81,7 @@ beforeAll(async () => {
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
});
|
||||
|
||||
@ -466,6 +471,9 @@ test('If change requests are enabled, cannot change variants without going via C
|
||||
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,
|
||||
@ -479,6 +487,7 @@ test('If change requests are enabled, cannot change variants without going via C
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
|
||||
const newVariant: IVariant = {
|
||||
@ -554,6 +563,9 @@ test('If CRs are protected for any environment in the project stops bulk update
|
||||
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,
|
||||
@ -567,6 +579,7 @@ test('If CRs are protected for any environment in the project stops bulk update
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
|
||||
const toggle = await service.createFeatureToggle(
|
||||
|
@ -26,6 +26,7 @@ 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';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
let db: ITestDb;
|
||||
@ -47,6 +48,9 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
|
||||
db.rawDatabase,
|
||||
);
|
||||
segmentService = new SegmentService(
|
||||
stores,
|
||||
changeRequestAccessReadModel,
|
||||
@ -61,6 +65,7 @@ beforeAll(async () => {
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
service = new PlaygroundService(config, {
|
||||
featureToggleServiceV2: featureToggleService,
|
||||
|
@ -12,6 +12,7 @@ import { GroupService } from '../../../lib/services/group-service';
|
||||
import { 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';
|
||||
|
||||
let stores: IUnleashStores;
|
||||
let db: ITestDb;
|
||||
@ -41,6 +42,9 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
|
||||
db.rawDatabase,
|
||||
);
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
@ -53,6 +57,7 @@ beforeAll(async () => {
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
favoritesService = new FavoritesService(stores, config);
|
||||
|
||||
|
@ -16,6 +16,7 @@ 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';
|
||||
|
||||
let stores;
|
||||
let db: ITestDb;
|
||||
@ -62,6 +63,9 @@ beforeAll(async () => {
|
||||
db.rawDatabase,
|
||||
config,
|
||||
);
|
||||
const dependentFeaturesReadModel = new DependentFeaturesReadModel(
|
||||
db.rawDatabase,
|
||||
);
|
||||
featureToggleService = new FeatureToggleService(
|
||||
stores,
|
||||
config,
|
||||
@ -74,6 +78,7 @@ beforeAll(async () => {
|
||||
accessService,
|
||||
changeRequestAccessReadModel,
|
||||
privateProjectChecker,
|
||||
dependentFeaturesReadModel,
|
||||
);
|
||||
|
||||
favoritesService = new FavoritesService(stores, config);
|
||||
|
Loading…
Reference in New Issue
Block a user