1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-03-27 00:19:39 +01:00

feat: Persist dependent features (#4772)

This commit is contained in:
Mateusz Kwasniewski 2023-09-19 13:01:38 +02:00 committed by GitHub
parent 12d9297f68
commit be7f0d8b4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 97 additions and 3 deletions

View File

@ -37,6 +37,7 @@ import ProjectStatsStore from './project-stats-store';
import { Db } from './db';
import { ImportTogglesStore } from '../features/export-import-toggles/import-toggles-store';
import PrivateProjectStore from '../features/private-project/privateProjectStore';
import { DependentFeaturesStore } from '../features/dependent-features/dependent-features-store';
export const createStores = (
config: IUnleashConfig,
@ -130,6 +131,7 @@ export const createStores = (
projectStatsStore: new ProjectStatsStore(db, eventBus, getLogger),
importTogglesStore: new ImportTogglesStore(db),
privateProjectStore: new PrivateProjectStore(db, getLogger),
dependentFeaturesStore: new DependentFeaturesStore(db),
};
};

View File

@ -1,4 +1,5 @@
import { CreateDependentFeatureSchema } from '../../openapi';
import { IDependentFeaturesStore } from './dependent-features-store-type';
export type FeatureDependency =
| {
@ -9,6 +10,12 @@ export type FeatureDependency =
}
| { parent: string; child: string; enabled: false };
export class DependentFeaturesService {
private dependentFeaturesStore: IDependentFeaturesStore;
constructor(dependentFeaturesStore: IDependentFeaturesStore) {
this.dependentFeaturesStore = dependentFeaturesStore;
}
async upsertFeatureDependency(
parentFeature: string,
dependentFeature: CreateDependentFeatureSchema,
@ -27,6 +34,6 @@ export class DependentFeaturesService {
enabled: true,
variants,
};
console.log(featureDependency);
await this.dependentFeaturesStore.upsert(featureDependency);
}
}

View File

@ -0,0 +1,5 @@
import { FeatureDependency } from './dependent-features-service';
export interface IDependentFeaturesStore {
upsert(featureDependency: FeatureDependency): Promise<void>;
}

View File

@ -0,0 +1,31 @@
import { FeatureDependency } from './dependent-features-service';
import { Db } from '../../db/db';
import { IDependentFeaturesStore } from './dependent-features-store-type';
type SerializableFeatureDependency = Omit<FeatureDependency, 'variants'> & {
variants?: string;
};
export class DependentFeaturesStore implements IDependentFeaturesStore {
private db: Db;
constructor(db: Db) {
this.db = db;
}
async upsert(featureDependency: FeatureDependency): Promise<void> {
const serializableFeatureDependency: SerializableFeatureDependency = {
parent: featureDependency.parent,
child: featureDependency.child,
enabled: featureDependency.enabled,
};
if ('variants' in featureDependency) {
serializableFeatureDependency.variants = JSON.stringify(
featureDependency.variants,
);
}
await this.db('dependent_features')
.insert(serializableFeatureDependency)
.onConflict(['parent', 'child'])
.merge();
}
}

View File

@ -11,7 +11,7 @@ let app: IUnleashTest;
let db: ITestDb;
beforeAll(async () => {
db = await dbInit('feature_dependencies', getLogger);
db = await dbInit('dependent_features', getLogger);
app = await setupAppWithCustomConfig(
db.stores,
{
@ -50,7 +50,14 @@ test('should add feature dependency', async () => {
await app.createFeature(parent);
await app.createFeature(child);
// save explicit enabled and variants
await addFeatureDependency(parent, {
feature: child,
enabled: false,
});
// overwrite with implicit enabled: true and variants
await addFeatureDependency(parent, {
feature: child,
variants: ['variantB'],
});
});

View File

@ -0,0 +1,7 @@
import { IDependentFeaturesStore } from './dependent-features-store-type';
export class FakeDependentFeaturesStore implements IDependentFeaturesStore {
async upsert(): Promise<void> {
return Promise.resolve();
}
}

View File

@ -290,7 +290,9 @@ export const createServices = (
const eventAnnouncerService = new EventAnnouncerService(stores, config);
const dependentFeaturesService = new DependentFeaturesService();
const dependentFeaturesService = new DependentFeaturesService(
stores.dependentFeaturesStore,
);
return {
accessService,

View File

@ -34,6 +34,7 @@ import { IAccountStore } from './stores/account-store';
import { IProjectStatsStore } from './stores/project-stats-store-type';
import { IImportTogglesStore } from '../features/export-import-toggles/import-toggles-store-type';
import { IPrivateProjectStore } from '../features/private-project/privateProjectStoreType';
import { IDependentFeaturesStore } from '../features/dependent-features/dependent-features-store-type';
export interface IUnleashStores {
accessStore: IAccessStore;
@ -72,6 +73,7 @@ export interface IUnleashStores {
projectStatsStore: IProjectStatsStore;
importTogglesStore: IImportTogglesStore;
privateProjectStore: IPrivateProjectStore;
dependentFeaturesStore: IDependentFeaturesStore;
}
export {
@ -110,4 +112,5 @@ export {
IFavoriteProjectsStore,
IImportTogglesStore,
IPrivateProjectStore,
IDependentFeaturesStore,
};

View File

@ -0,0 +1,28 @@
'use strict';
exports.up = function (db, cb) {
db.runSql(
`
CREATE TABLE IF NOT EXISTS dependent_features
(
parent varchar(255) NOT NULL,
child varchar(255) NOT NULL,
enabled boolean DEFAULT true NOT NULL,
variants JSONB DEFAULT '[]'::jsonb NOT NULL,
PRIMARY KEY (parent, child),
FOREIGN KEY (parent) REFERENCES features (name) ON DELETE RESTRICT,
FOREIGN KEY (child) REFERENCES features (name) ON DELETE CASCADE
);
`,
cb(),
);
};
exports.down = function (db, cb) {
db.runSql(
`
DROP TABLE dependent_features;
`,
cb,
);
};

View File

@ -37,6 +37,7 @@ import FakeFavoriteFeaturesStore from './fake-favorite-features-store';
import FakeFavoriteProjectsStore from './fake-favorite-projects-store';
import { FakeAccountStore } from './fake-account-store';
import FakeProjectStatsStore from './fake-project-stats-store';
import { FakeDependentFeaturesStore } from '../../lib/features/dependent-features/fake-dependent-features-store';
const db = {
select: () => ({
@ -83,6 +84,7 @@ const createStores: () => IUnleashStores = () => {
projectStatsStore: new FakeProjectStatsStore(),
importTogglesStore: {} as IImportTogglesStore,
privateProjectStore: {} as IPrivateProjectStore,
dependentFeaturesStore: new FakeDependentFeaturesStore(),
};
};