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:
parent
12d9297f68
commit
be7f0d8b4e
@ -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),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import { FeatureDependency } from './dependent-features-service';
|
||||||
|
|
||||||
|
export interface IDependentFeaturesStore {
|
||||||
|
upsert(featureDependency: FeatureDependency): Promise<void>;
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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'],
|
||||||
});
|
});
|
||||||
});
|
});
|
@ -0,0 +1,7 @@
|
|||||||
|
import { IDependentFeaturesStore } from './dependent-features-store-type';
|
||||||
|
|
||||||
|
export class FakeDependentFeaturesStore implements IDependentFeaturesStore {
|
||||||
|
async upsert(): Promise<void> {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
|
28
src/migrations/20230919104006-dependent-features.js
Normal file
28
src/migrations/20230919104006-dependent-features.js
Normal 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,
|
||||||
|
);
|
||||||
|
};
|
2
src/test/fixtures/store.ts
vendored
2
src/test/fixtures/store.ts
vendored
@ -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(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user