Skip to content

Commit

Permalink
Merge pull request #952 from CarnegieLearningWeb/bug-fix/ExportAllSeg…
Browse files Browse the repository at this point in the history
…ments

Export All segments features added and export segment api modified
  • Loading branch information
jreddig authored Aug 8, 2023
2 parents aae3023 + 9662435 commit 95c2f9e
Show file tree
Hide file tree
Showing 20 changed files with 97 additions and 63 deletions.
22 changes: 12 additions & 10 deletions backend/packages/Upgrade/src/api/controllers/SegmentController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Segment> {
if (!id) {
@Post('/export')
public exportSegments( @Body({ validate: false }) ids: string[], @Req() request: AppRequest): Promise<Segment[]> {
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);
}
}
26 changes: 17 additions & 9 deletions backend/packages/Upgrade/src/api/services/SegmentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,24 @@ export class SegmentService {
return this.addSegmentDataInDB(segment, logger);
}

public async exportSegment(segmentId: string, logger: UpgradeLogger): Promise<Segment> {
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<Segment[]> {
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<Segment> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({});
Expand Down Expand Up @@ -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 }));
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,49 +206,49 @@ 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);

expect(neverEmitted).toEqual(true);
}));

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);
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()])
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<p class="ft-16-400">
{{ 'segments.import-segmemt.message.text' | translate }}
</p>
<input accept=".json" type="file" class="ft-14-400 file-input" (change)="uploadFile($event)" />
<input accept=".json" type="file" multiple class="ft-14-400 file-input" (change)="uploadFile($event)" />
<span class="error-msg" *ngIf="!isSegmentJSONValid">
{{ 'segments.import-segment.error.message.text' | translate }}
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
mat-flat-button
color="primary"
class="ft-14-700 export-segments"
(click)="openExportAllSegmentDialog()"
(click)="openExportAllSegment()"
*ngIf="(permissions$ | async)?.segments.create"
>
<mat-icon>download</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class ViewSegmentComponent implements OnInit, OnDestroy {
}

exportSegment(segmentId: string) {
this.segmentsService.exportSegment(segmentId);
this.segmentsService.exportSegments([segmentId]);
}

ngOnDestroy() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface APIEndpoints {
segments: string;
importSegment: string;
exportSegment: string;
exportSegments: string;
getGroupAssignmentStatus: string;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const environment = {
segments: '/segments',
importSegment: '/segments/import',
exportSegment: '/segments/export',
exportSegments: '/segments/export',
getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const environment = {
contextMetaData: '/experiments/contextMetaData',
segments: '/segments',
importSegment: '/segments/import',
exportSegment: '/segments/export',
exportSegments: '/segments/export',
getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const environment = {
segments: '/segments',
importSegment: '/segments/import',
exportSegment: '/segments/export',
exportSegments: '/segments/export',
getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const environment = {
contextMetaData: '/experiments/contextMetaData',
segments: '/segments',
importSegment: '/segments/import',
exportSegment: '/segments/export',
exportSegments: '/segments/export',
getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus',
},
};
1 change: 1 addition & 0 deletions frontend/projects/upgrade/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const environment = {
segments: '/segments',
importSegment: '/segments/import',
exportSegment: '/segments/export',
exportSegments: '/segments/export',
getGroupAssignmentStatus: '/experiments/getGroupAssignmentStatus',
},
};

0 comments on commit 95c2f9e

Please sign in to comment.