diff --git a/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx b/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx index b70b6cf7e9..00269456d0 100644 --- a/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx +++ b/frontend/src/component/project/Project/Import/configure/ImportExplanation.tsx @@ -11,7 +11,7 @@ const ImportExplanationHeader = styled(Typography)(({ theme }) => ({ marginBottom: theme.spacing(0.5), })); -const ImportExplanationDescription = styled(Typography)(({ theme }) => ({ +const ImportExplanationDescription = styled(Box)(({ theme }) => ({ marginBottom: theme.spacing(3), color: theme.palette.text.secondary, fontSize: theme.fontSizes.smallBody, diff --git a/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx b/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx index 56901012d1..c528d2c415 100644 --- a/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx +++ b/frontend/src/component/project/Project/Import/validate/ValidationStage.tsx @@ -132,11 +132,13 @@ export const ValidationStage: FC<{ this configuration {validationResult.errors.map(error => ( - + {error.message} {error.affectedItems.map(item => ( - {item} + + {item} + ))} diff --git a/src/lib/db/feature-environment-store.ts b/src/lib/db/feature-environment-store.ts index fb4f816d79..da9aa7a8d0 100644 --- a/src/lib/db/feature-environment-store.ts +++ b/src/lib/db/feature-environment-store.ts @@ -120,7 +120,6 @@ export class FeatureEnvironmentStore implements IFeatureEnvironmentStore { enabled: r.enabled, featureName: r.feature_name, environment: r.environment, - variants: r.variants, })); } diff --git a/src/lib/routes/admin-api/export-import.ts b/src/lib/routes/admin-api/export-import.ts index 826400d3cb..bc83272c40 100644 --- a/src/lib/routes/admin-api/export-import.ts +++ b/src/lib/routes/admin-api/export-import.ts @@ -5,9 +5,7 @@ import { IUnleashConfig } from '../../types/option'; import { IUnleashServices } from '../../types/services'; import { Logger } from '../../logger'; import { OpenApiService } from '../../services/openapi-service'; -import ExportImportService, { - IImportDTO, -} from 'lib/services/export-import-service'; +import ExportImportService from 'lib/services/export-import-service'; import { InvalidOperationError } from '../../error'; import { createRequestSchema, createResponseSchema } from '../../openapi'; import { exportResultSchema } from '../../openapi/spec/export-result-schema'; @@ -51,12 +49,6 @@ class ExportImportController extends Controller { }), ], }); - this.route({ - method: 'post', - path: '/import', - permission: NONE, - handler: this.importData, - }); } async export( @@ -94,16 +86,5 @@ class ExportImportController extends Controller { ); } } - - async importData( - req: IAuthRequest, - res: Response, - ): Promise { - const dto = req.body; - const user = req.user; - await this.exportImportService.import(dto, user); - - res.status(201).end(); - } } export default ExportImportController; diff --git a/src/lib/services/export-import-service.ts b/src/lib/services/export-import-service.ts index c595e485da..849b20d4b8 100644 --- a/src/lib/services/export-import-service.ts +++ b/src/lib/services/export-import-service.ts @@ -1,12 +1,5 @@ import { IUnleashConfig } from '../types/option'; -import { - FeatureToggle, - IFeatureEnvironment, - IFeatureStrategy, - IFeatureStrategySegment, - ISegment, - ITag, -} from '../types/model'; +import { IFeatureStrategy, IFeatureStrategySegment } from '../types/model'; import { Logger } from '../logger'; import { IFeatureTagStore } from '../types/stores/feature-tag-store'; import { IProjectStore } from '../types/stores/project-store'; @@ -20,27 +13,17 @@ import { IEnvironmentStore } from '../types/stores/environment-store'; import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store'; import { IContextFieldStore, IUnleashStores } from '../types/stores'; import { ISegmentStore } from '../types/stores/segment-store'; -import { IContextFieldDto } from '../types/stores/context-field-store'; import FeatureToggleService from './feature-toggle-service'; -import User from 'lib/types/user'; import { ExportQuerySchema } from '../openapi/spec/export-query-schema'; import { FEATURES_EXPORTED, IFlagResolver, IUnleashServices } from '../types'; +import { ExportResultSchema } from '../openapi'; export interface IImportDTO { - data: IExportData; + data: ExportResultSchema; project: string; environment: string; } -export interface IExportData { - features: FeatureToggle[]; - tags?: ITag[]; - contextFields: IContextFieldDto[]; - featureStrategies: IFeatureStrategy[]; - featureEnvironments: IFeatureEnvironment[]; - segments: ISegment[]; -} - export default class ExportImportService { private logger: Logger; @@ -102,7 +85,7 @@ export default class ExportImportService { async export( query: ExportQuerySchema, userName: string, - ): Promise { + ): Promise { const [ features, featureEnvironments, @@ -140,10 +123,22 @@ export default class ExportImportService { ), ); const result = { - features, - featureStrategies, - featureEnvironments, - contextFields: filteredContextFields, + features: features.map((item) => { + const { createdAt, archivedAt, lastSeenAt, ...rest } = item; + return rest; + }), + featureStrategies: featureStrategies.map((item) => ({ + name: item.strategyName, + ...item, + })), + featureEnvironments: featureEnvironments.map((item) => ({ + ...item, + name: item.featureName, + })), + contextFields: filteredContextFields.map((item) => { + const { createdAt, ...rest } = item; + return rest; + }), featureTags, segments: filteredSegments, }; @@ -169,48 +164,6 @@ export default class ExportImportService { .map((segment) => segment.segmentId); }); } - - async import(dto: IImportDTO, user: User): Promise { - await Promise.all( - dto.data.features.map((feature) => - this.featureToggleService.createFeatureToggle( - 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, - ), - ), - ); - } } module.exports = ExportImportService; diff --git a/src/test/e2e/api/admin/export-import.e2e.test.ts b/src/test/e2e/api/admin/export-import.e2e.test.ts index 86b848587f..cfb87ff2d7 100644 --- a/src/test/e2e/api/admin/export-import.e2e.test.ts +++ b/src/test/e2e/api/admin/export-import.e2e.test.ts @@ -6,17 +6,14 @@ import dbInit, { ITestDb } from '../../helpers/database-init'; import getLogger from '../../../fixtures/no-logger'; import { IEventStore } from 'lib/types/stores/event-store'; import { - FeatureToggle, FeatureToggleDTO, IEnvironmentStore, - IFeatureStrategy, IFeatureToggleStore, IProjectStore, ISegment, IStrategyConfig, } from 'lib/types'; import { DEFAULT_ENV } from '../../../../lib/util'; -import { IImportDTO } from '../../../../lib/services/export-import-service'; import { ContextFieldSchema } from '../../../../lib/openapi'; let app: IUnleashTest; @@ -185,7 +182,6 @@ test('exports features', async () => { enabled: false, environment: 'default', featureName: 'first_feature', - variants: [], }, ], segments: [ @@ -248,7 +244,6 @@ test('should export custom context fields', async () => { enabled: false, environment: 'default', featureName: 'first_feature', - variants: [], }, ], contextFields: [context], @@ -289,7 +284,6 @@ test('should export tags', async () => { enabled: false, environment: 'default', featureName: 'first_feature', - variants: [], }, ], featureTags: [{ featureName, tagValue: 'tag1' }], @@ -317,103 +311,3 @@ test('returns all features, when no feature was defined', async () => { expect(body.features).toHaveLength(2); }); - -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', - }, - ], - contextFields: [], - segments: [], - }, - project: project, - environment: environment, - }; - await createProject(project, environment); - - await app.request - .post('/api/admin/features-batch/import') - .send(importPayload) - .set('Content-Type', 'application/json') - .expect(201); - - 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(importedFeatureEnvironment).toMatchObject({ - name: feature, - environment, - enabled: true, - strategies: [ - { - featureName: feature, - projectId: project, - environment: environment, - parameters: {}, - constraints: [], - sortOrder: 9999, - name: 'default', - }, - ], - }); -});