mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-23 00:22:19 +01:00
feat: import feature strategies (#2885)
This commit is contained in:
parent
b12962e7d2
commit
5569101f30
@ -1,5 +1,10 @@
|
||||
import { IUnleashConfig } from '../types/option';
|
||||
import { FeatureToggle, IFeatureStrategy, ITag } from '../types/model';
|
||||
import {
|
||||
FeatureToggle,
|
||||
IFeatureEnvironment,
|
||||
IFeatureStrategy,
|
||||
ITag,
|
||||
} from '../types/model';
|
||||
import { Logger } from '../logger';
|
||||
import { IFeatureTagStore } from '../types/stores/feature-tag-store';
|
||||
import { IProjectStore } from '../types/stores/project-store';
|
||||
@ -26,8 +31,8 @@ export interface IExportQuery {
|
||||
|
||||
export interface IImportDTO {
|
||||
data: IExportData;
|
||||
project?: string;
|
||||
environment?: string;
|
||||
project: string;
|
||||
environment: string;
|
||||
}
|
||||
|
||||
export interface IExportData {
|
||||
@ -35,6 +40,7 @@ export interface IExportData {
|
||||
tags?: ITag[];
|
||||
contextFields?: IContextFieldDto[];
|
||||
featureStrategies: IFeatureStrategy[];
|
||||
featureEnvironments: IFeatureEnvironment[];
|
||||
}
|
||||
|
||||
export default class ExportImportService {
|
||||
@ -93,25 +99,62 @@ export default class ExportImportService {
|
||||
}
|
||||
|
||||
async export(query: ExportQuerySchema): Promise<IExportData> {
|
||||
const features = await this.toggleStore.getAllByNames(query.features);
|
||||
const featureStrategies =
|
||||
await this.featureStrategiesStore.getAllByFeatures(
|
||||
query.features,
|
||||
query.environment,
|
||||
);
|
||||
return { features, featureStrategies };
|
||||
const [features, featureEnvironments, featureStrategies] =
|
||||
await Promise.all([
|
||||
this.toggleStore.getAllByNames(query.features),
|
||||
(
|
||||
await this.featureEnvironmentStore.getAll({
|
||||
environment: query.environment,
|
||||
})
|
||||
).filter((item) => query.features.includes(item.featureName)),
|
||||
this.featureStrategiesStore.getAllByFeatures(
|
||||
query.features,
|
||||
query.environment,
|
||||
),
|
||||
]);
|
||||
return { features, featureStrategies, featureEnvironments };
|
||||
}
|
||||
|
||||
async import(dto: IImportDTO, user: User): Promise<void> {
|
||||
await Promise.all(
|
||||
dto.data.features.map((feature) =>
|
||||
this.featureToggleService.createFeatureToggle(
|
||||
dto.project || feature.project,
|
||||
dto.project,
|
||||
feature,
|
||||
user.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
await Promise.all(
|
||||
dto.data.featureStrategies.map((featureStrategy) =>
|
||||
this.featureToggleService.unprotectedCreateStrategy(
|
||||
{
|
||||
name: featureStrategy.strategyName,
|
||||
constraints: featureStrategy.constraints,
|
||||
parameters: featureStrategy.parameters,
|
||||
segments: featureStrategy.segments,
|
||||
sortOrder: featureStrategy.sortOrder,
|
||||
},
|
||||
{
|
||||
featureName: featureStrategy.featureName,
|
||||
environment: dto.environment,
|
||||
projectId: dto.project,
|
||||
},
|
||||
user.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
await Promise.all(
|
||||
dto.data.featureEnvironments.map((featureEnvironment) =>
|
||||
this.featureToggleService.unprotectedUpdateEnabled(
|
||||
dto.project,
|
||||
featureEnvironment.featureName,
|
||||
dto.environment,
|
||||
featureEnvironment.enabled,
|
||||
user.name,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,26 @@ import {
|
||||
import dbInit, { ITestDb } from '../../helpers/database-init';
|
||||
import getLogger from '../../../fixtures/no-logger';
|
||||
import { IEventStore } from 'lib/types/stores/event-store';
|
||||
import { FeatureToggle, FeatureToggleDTO, IStrategyConfig } from 'lib/types';
|
||||
import {
|
||||
FeatureToggle,
|
||||
FeatureToggleDTO,
|
||||
IEnvironmentStore,
|
||||
IFeatureStrategy,
|
||||
IFeatureToggleStore,
|
||||
IProjectStore,
|
||||
IStrategyConfig,
|
||||
} from 'lib/types';
|
||||
import { DEFAULT_ENV } from '../../../../lib/util';
|
||||
import { IImportDTO } from '../../../../lib/services/export-import-service';
|
||||
|
||||
let app: IUnleashTest;
|
||||
let db: ITestDb;
|
||||
let eventStore: IEventStore;
|
||||
let environmentStore: IEnvironmentStore;
|
||||
let projectStore: IProjectStore;
|
||||
let toggleStore: IFeatureToggleStore;
|
||||
|
||||
const defaultStrategy = {
|
||||
const defaultStrategy: IStrategyConfig = {
|
||||
name: 'default',
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
@ -38,6 +50,24 @@ const createToggle = async (
|
||||
}
|
||||
};
|
||||
|
||||
const createProject = async (project: string, environment: string) => {
|
||||
await db.stores.environmentStore.create({
|
||||
name: environment,
|
||||
type: 'production',
|
||||
});
|
||||
await db.stores.projectStore.create({
|
||||
name: project,
|
||||
description: '',
|
||||
id: project,
|
||||
});
|
||||
await app.request
|
||||
.post(`/api/admin/projects/${project}/environments`)
|
||||
.send({
|
||||
environment,
|
||||
})
|
||||
.expect(200);
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
db = await dbInit('export_import_api_serial', getLogger);
|
||||
app = await setupAppWithCustomConfig(db.stores, {
|
||||
@ -48,10 +78,16 @@ beforeAll(async () => {
|
||||
},
|
||||
});
|
||||
eventStore = db.stores.eventStore;
|
||||
environmentStore = db.stores.environmentStore;
|
||||
projectStore = db.stores.projectStore;
|
||||
toggleStore = db.stores.featureToggleStore;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await eventStore.deleteAll();
|
||||
await toggleStore.deleteAll();
|
||||
await projectStore.deleteAll();
|
||||
await environmentStore.deleteAll();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@ -59,11 +95,8 @@ afterAll(async () => {
|
||||
await db.destroy();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await db.stores.featureToggleStore.deleteAll();
|
||||
});
|
||||
|
||||
test('exports features', async () => {
|
||||
await createProject('default', 'default');
|
||||
const strategy = {
|
||||
name: 'default',
|
||||
parameters: { rollout: '100', stickiness: 'default' },
|
||||
@ -106,10 +139,19 @@ test('exports features', async () => {
|
||||
},
|
||||
],
|
||||
featureStrategies: [resultStrategy],
|
||||
featureEnvironments: [
|
||||
{
|
||||
enabled: false,
|
||||
environment: 'default',
|
||||
featureName: 'first_feature',
|
||||
variants: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('returns all features, when no feature was defined', async () => {
|
||||
await createProject('default', 'default');
|
||||
await createToggle({
|
||||
name: 'first_feature',
|
||||
description: 'the #1 feature',
|
||||
@ -130,24 +172,100 @@ test('returns all features, when no feature was defined', async () => {
|
||||
expect(body.features).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('import features', async () => {
|
||||
const feature: FeatureToggle = { project: 'ignore', name: 'first_feature' };
|
||||
test('import features to existing project and environment', async () => {
|
||||
const feature = 'first_feature';
|
||||
const project = 'new_project';
|
||||
const environment = 'staging';
|
||||
const variants = [
|
||||
{
|
||||
name: 'variantA',
|
||||
weight: 500,
|
||||
payload: {
|
||||
type: 'string',
|
||||
value: 'payloadA',
|
||||
},
|
||||
overrides: [],
|
||||
stickiness: 'default',
|
||||
weightType: 'variable',
|
||||
},
|
||||
{
|
||||
name: 'variantB',
|
||||
weight: 500,
|
||||
payload: {
|
||||
type: 'string',
|
||||
value: 'payloadB',
|
||||
},
|
||||
overrides: [],
|
||||
stickiness: 'default',
|
||||
weightType: 'variable',
|
||||
},
|
||||
];
|
||||
const exportedFeature: FeatureToggle = {
|
||||
project: 'old_project',
|
||||
name: 'first_feature',
|
||||
variants,
|
||||
};
|
||||
const exportedStrategy: IFeatureStrategy = {
|
||||
id: '798cb25a-2abd-47bd-8a95-40ec13472309',
|
||||
featureName: feature,
|
||||
projectId: 'old_project',
|
||||
environment: 'old_environment',
|
||||
strategyName: 'default',
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
};
|
||||
const importPayload: IImportDTO = {
|
||||
data: {
|
||||
features: [exportedFeature],
|
||||
featureStrategies: [exportedStrategy],
|
||||
featureEnvironments: [
|
||||
{
|
||||
enabled: true,
|
||||
featureName: 'first_feature',
|
||||
environment: 'irrelevant',
|
||||
},
|
||||
],
|
||||
},
|
||||
project: project,
|
||||
environment: environment,
|
||||
};
|
||||
await createProject(project, environment);
|
||||
|
||||
await app.request
|
||||
.post('/api/admin/features-batch/import')
|
||||
.send({
|
||||
data: { features: [feature] },
|
||||
project: 'default',
|
||||
environment: 'custom_environment',
|
||||
})
|
||||
.send(importPayload)
|
||||
.set('Content-Type', 'application/json')
|
||||
.expect(201);
|
||||
|
||||
const { body } = await app.request
|
||||
.get('/api/admin/features/first_feature')
|
||||
const { body: importedFeature } = await app.request
|
||||
.get(`/api/admin/features/${feature}`)
|
||||
.expect(200);
|
||||
expect(importedFeature).toMatchObject({
|
||||
name: 'first_feature',
|
||||
project: project,
|
||||
variants,
|
||||
});
|
||||
|
||||
const { body: importedFeatureEnvironment } = await app.request
|
||||
.get(
|
||||
`/api/admin/projects/${project}/features/${feature}/environments/${environment}`,
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
expect(body).toMatchObject({
|
||||
name: 'first_feature',
|
||||
project: 'default',
|
||||
expect(importedFeatureEnvironment).toMatchObject({
|
||||
name: feature,
|
||||
environment,
|
||||
enabled: true,
|
||||
strategies: [
|
||||
{
|
||||
featureName: feature,
|
||||
projectId: project,
|
||||
environment: environment,
|
||||
parameters: {},
|
||||
constraints: [],
|
||||
sortOrder: 9999,
|
||||
name: 'default',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user