1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-04-19 01:17:18 +02: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 { Db } from './db';
import { ImportTogglesStore } from '../features/export-import-toggles/import-toggles-store'; import { ImportTogglesStore } from '../features/export-import-toggles/import-toggles-store';
import PrivateProjectStore from '../features/private-project/privateProjectStore'; import PrivateProjectStore from '../features/private-project/privateProjectStore';
import { DependentFeaturesStore } from '../features/dependent-features/dependent-features-store';
export const createStores = ( export const createStores = (
config: IUnleashConfig, config: IUnleashConfig,
@ -130,6 +131,7 @@ export const createStores = (
projectStatsStore: new ProjectStatsStore(db, eventBus, getLogger), projectStatsStore: new ProjectStatsStore(db, eventBus, getLogger),
importTogglesStore: new ImportTogglesStore(db), importTogglesStore: new ImportTogglesStore(db),
privateProjectStore: new PrivateProjectStore(db, getLogger), privateProjectStore: new PrivateProjectStore(db, getLogger),
dependentFeaturesStore: new DependentFeaturesStore(db),
}; };
}; };

View File

@ -1,4 +1,5 @@
import { CreateDependentFeatureSchema } from '../../openapi'; import { CreateDependentFeatureSchema } from '../../openapi';
import { IDependentFeaturesStore } from './dependent-features-store-type';
export type FeatureDependency = export type FeatureDependency =
| { | {
@ -9,6 +10,12 @@ export type FeatureDependency =
} }
| { parent: string; child: string; enabled: false }; | { parent: string; child: string; enabled: false };
export class DependentFeaturesService { export class DependentFeaturesService {
private dependentFeaturesStore: IDependentFeaturesStore;
constructor(dependentFeaturesStore: IDependentFeaturesStore) {
this.dependentFeaturesStore = dependentFeaturesStore;
}
async upsertFeatureDependency( async upsertFeatureDependency(
parentFeature: string, parentFeature: string,
dependentFeature: CreateDependentFeatureSchema, dependentFeature: CreateDependentFeatureSchema,
@ -27,6 +34,6 @@ export class DependentFeaturesService {
enabled: true, enabled: true,
variants, 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; let db: ITestDb;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('feature_dependencies', getLogger); db = await dbInit('dependent_features', getLogger);
app = await setupAppWithCustomConfig( app = await setupAppWithCustomConfig(
db.stores, db.stores,
{ {
@ -50,7 +50,14 @@ test('should add feature dependency', async () => {
await app.createFeature(parent); await app.createFeature(parent);
await app.createFeature(child); await app.createFeature(child);
// save explicit enabled and variants
await addFeatureDependency(parent, { await addFeatureDependency(parent, {
feature: child, 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 eventAnnouncerService = new EventAnnouncerService(stores, config);
const dependentFeaturesService = new DependentFeaturesService(); const dependentFeaturesService = new DependentFeaturesService(
stores.dependentFeaturesStore,
);
return { return {
accessService, accessService,

View File

@ -34,6 +34,7 @@ import { IAccountStore } from './stores/account-store';
import { IProjectStatsStore } from './stores/project-stats-store-type'; import { IProjectStatsStore } from './stores/project-stats-store-type';
import { IImportTogglesStore } from '../features/export-import-toggles/import-toggles-store-type'; import { IImportTogglesStore } from '../features/export-import-toggles/import-toggles-store-type';
import { IPrivateProjectStore } from '../features/private-project/privateProjectStoreType'; import { IPrivateProjectStore } from '../features/private-project/privateProjectStoreType';
import { IDependentFeaturesStore } from '../features/dependent-features/dependent-features-store-type';
export interface IUnleashStores { export interface IUnleashStores {
accessStore: IAccessStore; accessStore: IAccessStore;
@ -72,6 +73,7 @@ export interface IUnleashStores {
projectStatsStore: IProjectStatsStore; projectStatsStore: IProjectStatsStore;
importTogglesStore: IImportTogglesStore; importTogglesStore: IImportTogglesStore;
privateProjectStore: IPrivateProjectStore; privateProjectStore: IPrivateProjectStore;
dependentFeaturesStore: IDependentFeaturesStore;
} }
export { export {
@ -110,4 +112,5 @@ export {
IFavoriteProjectsStore, IFavoriteProjectsStore,
IImportTogglesStore, IImportTogglesStore,
IPrivateProjectStore, 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 FakeFavoriteProjectsStore from './fake-favorite-projects-store';
import { FakeAccountStore } from './fake-account-store'; import { FakeAccountStore } from './fake-account-store';
import FakeProjectStatsStore from './fake-project-stats-store'; import FakeProjectStatsStore from './fake-project-stats-store';
import { FakeDependentFeaturesStore } from '../../lib/features/dependent-features/fake-dependent-features-store';
const db = { const db = {
select: () => ({ select: () => ({
@ -83,6 +84,7 @@ const createStores: () => IUnleashStores = () => {
projectStatsStore: new FakeProjectStatsStore(), projectStatsStore: new FakeProjectStatsStore(),
importTogglesStore: {} as IImportTogglesStore, importTogglesStore: {} as IImportTogglesStore,
privateProjectStore: {} as IPrivateProjectStore, privateProjectStore: {} as IPrivateProjectStore,
dependentFeaturesStore: new FakeDependentFeaturesStore(),
}; };
}; };