1
0
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:
Mateusz Kwasniewski 2023-01-12 15:24:34 +01:00 committed by GitHub
parent b12962e7d2
commit 5569101f30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 190 additions and 29 deletions

View File

@ -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,
),
),
);
}
}

View File

@ -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',
},
],
});
});