diff --git a/backend/packages/Upgrade/src/api/controllers/SegmentController.ts b/backend/packages/Upgrade/src/api/controllers/SegmentController.ts index e58fbc80d6..b2409eb4be 100644 --- a/backend/packages/Upgrade/src/api/controllers/SegmentController.ts +++ b/backend/packages/Upgrade/src/api/controllers/SegmentController.ts @@ -369,18 +369,20 @@ export class SegmentController { return this.segmentService.importSegment(segment, request.logger); } - @Get('/export/:id') - public exportSegment(@Param('id') id: string, @Req() request: AppRequest): Promise { - if (!id) { + @Post('/export') + public exportSegments( @Body({ validate: false }) ids: string[], @Req() request: AppRequest): Promise { + if (!ids) { return Promise.reject(new Error(SERVER_ERROR.MISSING_PARAMS + ' : segmentId should not be null.')); } - if (!isUUID(id)) { - return Promise.reject( - new Error( - JSON.stringify({ type: SERVER_ERROR.INCORRECT_PARAM_FORMAT, message: ' : segmentId should be of type UUID.' }) - ) - ); + for (const id of ids) { + if (!isUUID(id)) { + return Promise.reject( + new Error( + JSON.stringify({ type: SERVER_ERROR.INCORRECT_PARAM_FORMAT, message: ' : segmentId should be of type UUID.' }) + ) + ); + } } - return this.segmentService.exportSegment(id, request.logger); + return this.segmentService.exportSegments(ids, request.logger); } } diff --git a/backend/packages/Upgrade/src/api/services/SegmentService.ts b/backend/packages/Upgrade/src/api/services/SegmentService.ts index cc94637388..a877b67cd4 100644 --- a/backend/packages/Upgrade/src/api/services/SegmentService.ts +++ b/backend/packages/Upgrade/src/api/services/SegmentService.ts @@ -179,16 +179,24 @@ export class SegmentService { return this.addSegmentDataInDB(segment, logger); } - public async exportSegment(segmentId: string, logger: UpgradeLogger): Promise { - logger.info({ message: `Export segment by id. segmentId: ${segmentId}` }); - const segmentDoc = await this.segmentRepository.findOne({ - where: { id: segmentId }, - relations: ['individualForSegment', 'groupForSegment', 'subSegments'], - }); - if (!segmentDoc) { - throw new Error(SERVER_ERROR.QUERY_FAILED); + public async exportSegments(segmentIds: string[], logger: UpgradeLogger): Promise { + logger.info({ message: `Export segment by id. segmentId: ${segmentIds}` }); + let segmentsDoc: Segment[] = []; + if (segmentIds.length > 1) { + segmentsDoc = await this.getSegmentByIds(segmentIds); + }else { + const segmentDoc = await this.segmentRepository.findOne({ + where: { id: segmentIds[0] }, + relations: ['individualForSegment', 'groupForSegment', 'subSegments'], + }); + if (!segmentDoc) { + throw new Error(SERVER_ERROR.QUERY_FAILED); + }else { + segmentsDoc.push(segmentDoc); + } } - return segmentDoc; + + return segmentsDoc; } private async addSegmentDataInDB(segment: SegmentInputValidator, logger: UpgradeLogger): Promise { diff --git a/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts b/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts index cf4c30cdcd..5e8cc13303 100644 --- a/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts +++ b/backend/packages/Upgrade/test/unit/services/SegmentService.test.ts @@ -330,14 +330,14 @@ describe('Segment Service Testing', () => { }); it('should export a segment', async () => { - const segments = await service.exportSegment(seg1.id, logger); - expect(segments).toEqual(seg1); + const segments = await service.exportSegments([seg1.id], logger); + expect(segments).toEqual([seg1]); }); it('should throw an error when segment not found on export', async () => { repo.findOne = jest.fn().mockResolvedValue(null); expect(async () => { - await service.exportSegment(seg1.id, logger); + await service.exportSegments([seg1.id], logger); }).rejects.toThrow(new Error(SERVER_ERROR.QUERY_FAILED)); }); }); diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.spec.ts b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.spec.ts index 9de22cc9e2..3877be0279 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.spec.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.spec.ts @@ -84,14 +84,14 @@ describe('SegmentDataService', () => { }); }); - describe('#exportSegment', () => { - it('should get the exportSegment http observable', () => { + describe('#exportSegments', () => { + it('should post the exportSegments http observable', () => { const segmentId = mockSegmentId; - const expectedUrl = `${mockEnvironment.api.exportSegment}/${segmentId}`; + const expectedUrl = `${mockEnvironment.api.exportSegments}`; - service.exportSegment(segmentId); + service.exportSegments([segmentId]); - expect(mockHttpClient.get).toHaveBeenCalledWith(expectedUrl); + expect(mockHttpClient.post).toHaveBeenCalledWith(expectedUrl, [segmentId]); }); }); diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts index b397be7586..fb338ab802 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.data.service.ts @@ -27,9 +27,9 @@ export class SegmentsDataService { return this.http.post(url, segment); } - exportSegment(segmentId: string) { - const url = `${this.environment.api.exportSegment}/${segmentId}`; - return this.http.get(url); + exportSegments(segmentIds: string[]) { + const url = this.environment.api.exportSegments; + return this.http.post(url, segmentIds); } importSegment(segment: SegmentInput) { diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.service.spec.ts b/frontend/projects/upgrade/src/app/core/segments/segments.service.spec.ts index f45172394d..f32ff9c73d 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.service.spec.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.service.spec.ts @@ -2,7 +2,7 @@ import { fakeAsync, tick } from '@angular/core/testing'; import { BehaviorSubject } from 'rxjs'; import { SEGMENT_TYPE } from 'upgrade_types'; import { SegmentsService } from './segments.service'; -import { actionDeleteSegment, actionExportSegment, actionUpsertSegment } from './store/segments.actions'; +import { actionDeleteSegment, actionExportSegments, actionUpsertSegment } from './store/segments.actions'; import { SegmentInput, UpsertSegmentType } from './store/segments.model'; import * as SegmentSelectors from './store/segments.selectors'; const MockStateStore$ = new BehaviorSubject({}); @@ -111,13 +111,13 @@ describe('SegmentService', () => { }); }); - describe('#exportSegment', () => { - it('should dispatch exportSegment with the given input', () => { - const segmentId = 'abc123'; + describe('#exportSegments', () => { + it('should dispatch exportSegments with the given input', () => { + const segmentIds = ['abc123']; - service.exportSegment(segmentId); + service.exportSegments(segmentIds); - expect(mockStore.dispatch).toHaveBeenCalledWith(actionExportSegment({ segmentId })); + expect(mockStore.dispatch).toHaveBeenCalledWith(actionExportSegments({ segmentIds })); }); }); }); diff --git a/frontend/projects/upgrade/src/app/core/segments/segments.service.ts b/frontend/projects/upgrade/src/app/core/segments/segments.service.ts index 46c11a64ca..7199b5616d 100644 --- a/frontend/projects/upgrade/src/app/core/segments/segments.service.ts +++ b/frontend/projects/upgrade/src/app/core/segments/segments.service.ts @@ -61,8 +61,8 @@ export class SegmentsService { ); } - exportSegment(segmentId: string) { - this.store$.dispatch(SegmentsActions.actionExportSegment({ segmentId })); + exportSegments(segmentIds: string[]) { + this.store$.dispatch(SegmentsActions.actionExportSegments({ segmentIds })); } importSegment(segment: SegmentInput) { diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts index ae398b1cbb..8590d77248 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.actions.ts @@ -49,7 +49,7 @@ export const actionSetIsLoadingSegments = createAction( props<{ isLoadingSegments: boolean }>() ); -export const actionExportSegment = createAction('[Segments] Export Segment', props<{ segmentId: string }>()); +export const actionExportSegments = createAction('[Segments] Export Segment', props<{ segmentIds: string[] }>()); export const actionExportSegmentSuccess = createAction('[Segments] Export Segment Success'); diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts index b8a0f20717..f319fdc464 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.spec.ts @@ -206,15 +206,15 @@ describe('SegmentsEffects', () => { })); }); - describe('exportSegment$', () => { + describe('exportSegments$', () => { it('should do nothing if Segment is id', fakeAsync(() => { let neverEmitted = true; - service.exportSegment$.subscribe(() => { + service.exportSegments$.subscribe(() => { neverEmitted = false; }); - actions$.next(SegmentsActions.actionExportSegment({ segmentId: undefined })); + actions$.next(SegmentsActions.actionExportSegments({ segmentIds: undefined })); tick(0); @@ -222,33 +222,33 @@ describe('SegmentsEffects', () => { })); it('should dispatch actionExportSegmentSuccess on success', fakeAsync(() => { - segmentsDataService.exportSegment = jest.fn().mockReturnValue(of([{ ...mockSegment }])); + segmentsDataService.exportSegments = jest.fn().mockReturnValue(of([{ ...mockSegment }])); document.body.removeChild = jest.fn(); const expectedAction = SegmentsActions.actionExportSegmentSuccess(); - service.exportSegment$.subscribe((result) => { + service.exportSegments$.subscribe((result) => { expect(result).toEqual(expectedAction); // this assertion tests the last action in the private "download" method to make sure it ran expect(document.body.removeChild).toHaveBeenCalled(); }); - actions$.next(SegmentsActions.actionExportSegment({ segmentId: { ...mockSegment }.id })); + actions$.next(SegmentsActions.actionExportSegments({ segmentIds: [{ ...mockSegment }.id] })); tick(0); })); it('should dispatch actionExportSegmentFailure on failure', fakeAsync(() => { - segmentsDataService.exportSegment = jest.fn().mockReturnValue(throwError(() => new Error('test'))); + segmentsDataService.exportSegments = jest.fn().mockReturnValue(throwError(() => new Error('test'))); const expectedAction = SegmentsActions.actionExportSegmentFailure(); - service.exportSegment$.subscribe((result) => { + service.exportSegments$.subscribe((result) => { expect(result).toEqual(expectedAction); }); - actions$.next(SegmentsActions.actionExportSegment({ segmentId: { ...mockSegment }.id })); + actions$.next(SegmentsActions.actionExportSegments({ segmentIds: [{ ...mockSegment }.id] } )); tick(0); })); diff --git a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts index e6d10a9f75..673f67bb18 100644 --- a/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts +++ b/frontend/projects/upgrade/src/app/core/segments/store/segments.effects.ts @@ -8,6 +8,7 @@ import { SegmentsDataService } from '../segments.data.service'; import * as SegmentsActions from './segments.actions'; import { Segment, UpsertSegmentType } from './segments.model'; import { selectAllSegments } from './segments.selectors'; +import JSZip from 'jszip'; @Injectable() export class SegmentsEffects { @@ -79,15 +80,25 @@ export class SegmentsEffects { ) ); - exportSegment$ = createEffect(() => + exportSegments$ = createEffect(() => this.actions$.pipe( - ofType(SegmentsActions.actionExportSegment), - map((action) => ({ segmentId: action.segmentId })), - filter(({ segmentId }) => !!segmentId), - switchMap(({ segmentId }) => - this.segmentsDataService.exportSegment(segmentId).pipe( - map((data: any) => { - this.download(data.name + '.json', data); + ofType(SegmentsActions.actionExportSegments), + map((action) => ({ segmentIds: action.segmentIds })), + filter(({ segmentIds }) => !!segmentIds), + switchMap(({ segmentIds }) => + this.segmentsDataService.exportSegments(segmentIds).pipe( + map((data: Segment[]) => { + if (data.length > 1) { + const zip = new JSZip(); + data.forEach((segment, index) => { + zip.file(segment.name + ' (File ' + (index + 1) + ').json', JSON.stringify(segment)); + }); + zip.generateAsync({ type: 'base64' }).then((content) => { + this.download('Segments.zip', content, true); + }); + } else { + this.download(data[0].name + '.json', data[0], false); + } return SegmentsActions.actionExportSegmentSuccess(); }), catchError(() => [SegmentsActions.actionExportSegmentFailure()]) @@ -96,9 +107,11 @@ export class SegmentsEffects { ) ); - private download(filename, text) { + private download(filename, text, isZip: boolean) { const element = document.createElement('a'); - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + JSON.stringify(text)); + isZip + ? element.setAttribute('href', 'data:application/zip;base64,' + text) + : element.setAttribute('href', 'data:text/plain;charset=utf-8,' + JSON.stringify(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html index 2f550b61c4..9de4dc82dc 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/modal/import-segment/import-segment.component.html @@ -5,7 +5,7 @@

{{ 'segments.import-segmemt.message.text' | translate }}

- + {{ 'segments.import-segment.error.message.text' | translate }} diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html index fa753f8ab4..691db86606 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.html @@ -24,7 +24,7 @@ mat-flat-button color="primary" class="ft-14-700 export-segments" - (click)="openExportAllSegmentDialog()" + (click)="openExportAllSegment()" *ngIf="(permissions$ | async)?.segments.create" > download diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.ts index 8bdae3e424..2f67de15de 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/components/segments-list/segments-list.component.ts @@ -82,6 +82,13 @@ export class SegmentsListComponent implements OnInit, OnDestroy, AfterViewInit { }); } + openExportAllSegment() { + const segmentIds = this.allSegments.data.map(segment => { + return segment.id; + }) + this.segmentsService.exportSegments(segmentIds); + } + ngOnDestroy() { this.allSegmentsSub.unsubscribe(); } diff --git a/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/view-segment/view-segment.component.ts b/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/view-segment/view-segment.component.ts index 03effbd564..8356489a67 100644 --- a/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/view-segment/view-segment.component.ts +++ b/frontend/projects/upgrade/src/app/features/dashboard/segments/pages/view-segment/view-segment.component.ts @@ -103,7 +103,7 @@ export class ViewSegmentComponent implements OnInit, OnDestroy { } exportSegment(segmentId: string) { - this.segmentsService.exportSegment(segmentId); + this.segmentsService.exportSegments([segmentId]); } ngOnDestroy() { diff --git a/frontend/projects/upgrade/src/environments/environment-types.ts b/frontend/projects/upgrade/src/environments/environment-types.ts index 4ee5f58f29..1db76db388 100644 --- a/frontend/projects/upgrade/src/environments/environment-types.ts +++ b/frontend/projects/upgrade/src/environments/environment-types.ts @@ -38,6 +38,7 @@ export interface APIEndpoints { segments: string; importSegment: string; exportSegment: string; + exportSegments: string; getGroupAssignmentStatus: string; } diff --git a/frontend/projects/upgrade/src/environments/environment.bsnl.ts b/frontend/projects/upgrade/src/environments/environment.bsnl.ts index 5275644957..26462275c1 100644 --- a/frontend/projects/upgrade/src/environments/environment.bsnl.ts +++ b/frontend/projects/upgrade/src/environments/environment.bsnl.ts @@ -47,6 +47,7 @@ export const environment = { segments: '/segments', importSegment: '/segments/import', exportSegment: '/segments/export', + exportSegments: '/segments/export', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts index 0025d9ba13..0f7a0a30d5 100755 --- a/frontend/projects/upgrade/src/environments/environment.demo.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.demo.prod.ts @@ -46,7 +46,7 @@ export const environment = { contextMetaData: '/experiments/contextMetaData', segments: '/segments', importSegment: '/segments/import', - exportSegment: '/segments/export', + exportSegments: '/segments/export', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.prod.ts b/frontend/projects/upgrade/src/environments/environment.prod.ts index f8a2b2d7b3..a73652c5f3 100755 --- a/frontend/projects/upgrade/src/environments/environment.prod.ts +++ b/frontend/projects/upgrade/src/environments/environment.prod.ts @@ -47,6 +47,7 @@ export const environment = { segments: '/segments', importSegment: '/segments/import', exportSegment: '/segments/export', + exportSegments: '/segments/export', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.staging.ts b/frontend/projects/upgrade/src/environments/environment.staging.ts index f92d2f8cdb..a061c41177 100644 --- a/frontend/projects/upgrade/src/environments/environment.staging.ts +++ b/frontend/projects/upgrade/src/environments/environment.staging.ts @@ -46,7 +46,7 @@ export const environment = { contextMetaData: '/experiments/contextMetaData', segments: '/segments', importSegment: '/segments/import', - exportSegment: '/segments/export', + exportSegments: '/segments/export', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, }; diff --git a/frontend/projects/upgrade/src/environments/environment.ts b/frontend/projects/upgrade/src/environments/environment.ts index b859c1b323..56f6a385b8 100755 --- a/frontend/projects/upgrade/src/environments/environment.ts +++ b/frontend/projects/upgrade/src/environments/environment.ts @@ -51,6 +51,7 @@ export const environment = { segments: '/segments', importSegment: '/segments/import', exportSegment: '/segments/export', + exportSegments: '/segments/export', getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus', }, };