mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Import export (#2865)
This commit is contained in:
		
							parent
							
								
									0c1e997f0b
								
							
						
					
					
						commit
						f3f3a59e5e
					
				
							
								
								
									
										59
									
								
								src/lib/routes/admin-api/export-import.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/lib/routes/admin-api/export-import.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| @ -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) { | ||||
| @ -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', | ||||
|  | ||||
							
								
								
									
										87
									
								
								src/lib/services/export-import-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/lib/services/export-import-service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| @ -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, | ||||
| @ -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); | ||||
| @ -176,6 +178,7 @@ export const createServices = ( | ||||
|         instanceStatsService, | ||||
|         favoritesService, | ||||
|         maintenanceService, | ||||
|         exportImportService, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| @ -218,4 +221,5 @@ export { | ||||
|     LastSeenService, | ||||
|     InstanceStatsService, | ||||
|     FavoritesService, | ||||
|     ExportImportService, | ||||
| }; | ||||
|  | ||||
| @ -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; | ||||
| @ -79,4 +80,5 @@ export interface IUnleashServices { | ||||
|     instanceStatsService: InstanceStatsService; | ||||
|     favoritesService: FavoritesService; | ||||
|     maintenanceService: MaintenanceService; | ||||
|     exportImportService: ExportImportService; | ||||
| } | ||||
|  | ||||
							
								
								
									
										72
									
								
								src/test/e2e/api/admin/export-import.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/test/e2e/api/admin/export-import.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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', | ||||
|             }, | ||||
|         ], | ||||
|     }); | ||||
| }); | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user