Skip to content

Commit

Permalink
Import export (#2865)
Browse files Browse the repository at this point in the history
  • Loading branch information
sjaanus committed Jan 10, 2023
1 parent 0c1e997 commit f3f3a59
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 0 deletions.
59 changes: 59 additions & 0 deletions src/lib/routes/admin-api/export-import.ts
@@ -0,0 +1,59 @@
import { Request, Response } from 'express';
import Controller from '../controller';
import { NONE } from '../../types/permissions';
import { IUnleashConfig } from '../../types/option';
import { IUnleashServices } from '../../types/services';
import { Logger } from '../../logger';
import { OpenApiService } from '../../services/openapi-service';
import ExportImportService, {
IExportQuery,
} from 'lib/services/export-import-service';

class ExportImportController extends Controller {
private logger: Logger;

private exportImportService: ExportImportService;

private openApiService: OpenApiService;

constructor(
config: IUnleashConfig,
{
exportImportService,
openApiService,
}: Pick<IUnleashServices, 'exportImportService' | 'openApiService'>,
) {
super(config);
this.logger = config.getLogger('/admin-api/export-import.ts');
this.exportImportService = exportImportService;
this.openApiService = openApiService;
this.route({
method: 'post',
path: '/export',
permission: NONE,
handler: this.export,
// middleware: [
// this.openApiService.validPath({
// tags: ['Import/Export'],
// operationId: 'export',
// responses: {
// 200: createResponseSchema('stateSchema'),
// },
// parameters:
// exportQueryParameters as unknown as OpenAPIV3.ParameterObject[],
// }),
// ],
});
}

async export(
req: Request<unknown, unknown, IExportQuery, unknown>,
res: Response,
): Promise<void> {
const query = req.body;
const data = await this.exportImportService.export(query);

res.json(data);
}
}
export default ExportImportController;
5 changes: 5 additions & 0 deletions src/lib/routes/admin-api/index.ts
Expand Up @@ -28,6 +28,7 @@ import { PublicSignupController } from './public-signup';
import InstanceAdminController from './instance-admin';
import FavoritesController from './favorites';
import MaintenanceController from './maintenance';
import ExportImportController from './export-import';

class AdminApi extends Controller {
constructor(config: IUnleashConfig, services: IUnleashServices) {
Expand Down Expand Up @@ -77,6 +78,10 @@ class AdminApi extends Controller {
new ContextController(config, services).router,
);
this.app.use('/state', new StateController(config, services).router);
this.app.use(
'/features-batch',
new ExportImportController(config, services).router,
);
this.app.use('/tags', new TagController(config, services).router);
this.app.use(
'/tag-types',
Expand Down
87 changes: 87 additions & 0 deletions src/lib/services/export-import-service.ts
@@ -0,0 +1,87 @@
import { IUnleashConfig } from '../types/option';
import { FeatureToggle, ITag } from '../types/model';
import { Logger } from '../logger';
import { IFeatureTagStore } from '../types/stores/feature-tag-store';
import { IProjectStore } from '../types/stores/project-store';
import { ITagTypeStore } from '../types/stores/tag-type-store';
import { ITagStore } from '../types/stores/tag-store';
import { IEventStore } from '../types/stores/event-store';
import { IStrategyStore } from '../types/stores/strategy-store';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { IFeatureStrategiesStore } from '../types/stores/feature-strategies-store';
import { IEnvironmentStore } from '../types/stores/environment-store';
import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store';
import { IUnleashStores } from '../types/stores';
import { ISegmentStore } from '../types/stores/segment-store';
import { IFlagResolver } from 'lib/types';
import { IContextFieldDto } from '../types/stores/context-field-store';

export interface IExportQuery {
features: string[];
environment: string;
}

export interface IExportData {
features: FeatureToggle[];
tags?: ITag[];
contextFields?: IContextFieldDto[];
}

export default class ExportImportService {
private logger: Logger;

private toggleStore: IFeatureToggleStore;

private featureStrategiesStore: IFeatureStrategiesStore;

private strategyStore: IStrategyStore;

private eventStore: IEventStore;

private tagStore: ITagStore;

private tagTypeStore: ITagTypeStore;

private projectStore: IProjectStore;

private featureEnvironmentStore: IFeatureEnvironmentStore;

private featureTagStore: IFeatureTagStore;

private environmentStore: IEnvironmentStore;

private segmentStore: ISegmentStore;

private flagResolver: IFlagResolver;

constructor(
stores: IUnleashStores,
{
getLogger,
flagResolver,
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
) {
this.eventStore = stores.eventStore;
this.toggleStore = stores.featureToggleStore;
this.strategyStore = stores.strategyStore;
this.tagStore = stores.tagStore;
this.featureStrategiesStore = stores.featureStrategiesStore;
this.featureEnvironmentStore = stores.featureEnvironmentStore;
this.tagTypeStore = stores.tagTypeStore;
this.projectStore = stores.projectStore;
this.featureTagStore = stores.featureTagStore;
this.environmentStore = stores.environmentStore;
this.segmentStore = stores.segmentStore;
this.flagResolver = flagResolver;
this.logger = getLogger('services/state-service.js');
}

async export(query: IExportQuery): Promise<IExportData> {
const features = (
await this.toggleStore.getAll({ archived: false })
).filter((toggle) => query.features.includes(toggle.name));
return { features: features };
}
}

module.exports = ExportImportService;
4 changes: 4 additions & 0 deletions src/lib/services/index.ts
Expand Up @@ -39,6 +39,7 @@ import { LastSeenService } from './client-metrics/last-seen-service';
import { InstanceStatsService } from './instance-stats-service';
import { FavoritesService } from './favorites-service';
import MaintenanceService from './maintenance-service';
import ExportImportService from './export-import-service';

export const createServices = (
stores: IUnleashStores,
Expand All @@ -60,6 +61,7 @@ export const createServices = (
const featureTypeService = new FeatureTypeService(stores, config);
const resetTokenService = new ResetTokenService(stores, config);
const stateService = new StateService(stores, config);
const exportImportService = new ExportImportService(stores, config);
const strategyService = new StrategyService(stores, config);
const tagService = new TagService(stores, config);
const tagTypeService = new TagTypeService(stores, config);
Expand Down Expand Up @@ -176,6 +178,7 @@ export const createServices = (
instanceStatsService,
favoritesService,
maintenanceService,
exportImportService,
};
};

Expand Down Expand Up @@ -218,4 +221,5 @@ export {
LastSeenService,
InstanceStatsService,
FavoritesService,
ExportImportService,
};
2 changes: 2 additions & 0 deletions src/lib/types/services.ts
Expand Up @@ -37,6 +37,7 @@ import { LastSeenService } from '../services/client-metrics/last-seen-service';
import { InstanceStatsService } from '../services/instance-stats-service';
import { FavoritesService } from '../services';
import MaintenanceService from '../services/maintenance-service';
import ExportImportService from 'lib/services/export-import-service';

export interface IUnleashServices {
accessService: AccessService;
Expand Down Expand Up @@ -79,4 +80,5 @@ export interface IUnleashServices {
instanceStatsService: InstanceStatsService;
favoritesService: FavoritesService;
maintenanceService: MaintenanceService;
exportImportService: ExportImportService;
}
72 changes: 72 additions & 0 deletions src/test/e2e/api/admin/export-import.e2e.test.ts
@@ -0,0 +1,72 @@
import { IUnleashTest, setupApp } from '../../helpers/test-helper';
import dbInit, { ITestDb } from '../../helpers/database-init';
import getLogger from '../../../fixtures/no-logger';
import { IEventStore } from 'lib/types/stores/event-store';
import { FeatureToggleDTO, IStrategyConfig } from 'lib/types';
import { DEFAULT_ENV } from '../../../../lib/util';

let app: IUnleashTest;
let db: ITestDb;
let eventStore: IEventStore;

const createToggle = async (
toggle: FeatureToggleDTO,
strategy?: Omit<IStrategyConfig, 'id'>,
projectId: string = 'default',
username: string = 'test',
) => {
await app.services.featureToggleServiceV2.createFeatureToggle(
projectId,
toggle,
username,
);
if (strategy) {
await app.services.featureToggleServiceV2.createStrategy(
strategy,
{ projectId, featureName: toggle.name, environment: DEFAULT_ENV },
username,
);
}
};

beforeAll(async () => {
db = await dbInit('export_import_api_serial', getLogger);
app = await setupApp(db.stores);
eventStore = db.stores.eventStore;
});

beforeEach(async () => {
await eventStore.deleteAll();
});

afterAll(async () => {
await app.destroy();
await db.destroy();
});

afterEach(() => {
db.stores.featureToggleStore.deleteAll();
});

test('exports features', async () => {
await createToggle({
name: 'first_feature',
description: 'the #1 feature',
});
const { body } = await app.request
.post('/api/admin/features-batch/export')
.send({
features: ['first_feature'],
environment: 'default',
})
.set('Content-Type', 'application/json')
.expect(200);

expect(body).toMatchObject({
features: [
{
name: 'first_feature',
},
],
});
});

0 comments on commit f3f3a59

Please sign in to comment.