diff --git a/ui/src/app/member/member-import-dialog.component.html b/ui/src/app/member/member-import-dialog.component.html new file mode 100644 index 000000000..4dc579a45 --- /dev/null +++ b/ui/src/app/member/member-import-dialog.component.html @@ -0,0 +1,43 @@ +
+ + +
+
+
+ +
\ No newline at end of file diff --git a/ui/src/app/member/member-import-dialog.component.scss b/ui/src/app/member/member-import-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/member/member-import-dialog.component.spec.ts b/ui/src/app/member/member-import-dialog.component.spec.ts new file mode 100644 index 000000000..3b3a7772c --- /dev/null +++ b/ui/src/app/member/member-import-dialog.component.spec.ts @@ -0,0 +1,64 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { MemberImportDialogComponent } from './member-import-dialog.component' +import { EventService } from '../shared/service/event.service' +import { FileUploadService } from '../shared/service/file-upload.service' +import { FormBuilder } from '@angular/forms' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { EMPTY, of } from 'rxjs' +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/compiler' + +describe('MemberImportDialogComponent', () => { + let component: MemberImportDialogComponent + let fixture: ComponentFixture + + let uploadServiceSpy: jasmine.SpyObj + + beforeEach(() => { + uploadServiceSpy = jasmine.createSpyObj('FileUploadService', ['uploadFile']) + + TestBed.configureTestingModule({ + declarations: [MemberImportDialogComponent], + imports: [HttpClientTestingModule], + providers: [FormBuilder, NgbModal, NgbActiveModal, { provide: FileUploadService, useValue: uploadServiceSpy }], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }) + fixture = TestBed.createComponent(MemberImportDialogComponent) + component = fixture.componentInstance + fixture.detectChanges() + + uploadServiceSpy = TestBed.inject(FileUploadService) as jasmine.SpyObj + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + + it('should call upload service', () => { + component.currentFile = getFileList() + uploadServiceSpy.uploadFile.and.returnValue(EMPTY) + component.upload() + expect(uploadServiceSpy.uploadFile).toHaveBeenCalled() + }) + + it('errors should be parsed', () => { + component.currentFile = getFileList() + uploadServiceSpy.uploadFile.and.returnValue(of('[{"index":1,"message":"error"}]')) + component.upload() + expect(uploadServiceSpy.uploadFile).toHaveBeenCalled() + expect(component.csvErrors.length).toEqual(1) + }) + + const getFileList = () => { + const blob = new Blob([''], { type: 'text/html' }) + const file = blob + const fileList: FileList = { + 0: file, + 1: file, + length: 2, + item: (index: number) => file, + } + return fileList + } +}) diff --git a/ui/src/app/member/member-import-dialog.component.ts b/ui/src/app/member/member-import-dialog.component.ts new file mode 100644 index 000000000..a60ac72b6 --- /dev/null +++ b/ui/src/app/member/member-import-dialog.component.ts @@ -0,0 +1,104 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { MemberService } from './service/member.service' +import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' +import { EventService } from '../shared/service/event.service' +import { FileUploadService } from '../shared/service/file-upload.service' +import { ActivatedRoute, Router } from '@angular/router' +import { Event } from '../shared/model/event.model' +import { EventType } from '../app.constants' +import { faBan, faSave } from '@fortawesome/free-solid-svg-icons' + +@Component({ + selector: 'app-member-import-dialog', + templateUrl: './member-import-dialog.component.html', + styleUrls: ['./member-import-dialog.component.scss'], +}) +export class MemberImportDialogComponent { + public resourceUrl + isSaving: boolean + currentFile: FileList | null + csvErrors: any + loading = false + faBan = faBan + faSave = faSave + + constructor( + protected memberService: MemberService, + public activeModal: NgbActiveModal, + protected eventService: EventService, + private uploadService: FileUploadService + ) { + this.currentFile = null + this.isSaving = false + this.resourceUrl = this.memberService.resourceUrl + '/members/upload' + } + + clear() { + this.activeModal.dismiss('cancel') + } + + selectFile(event: any) { + this.currentFile = event.target.files + } + + upload() { + if (this.currentFile) { + this.loading = true + const f = this.currentFile.item(0) + this.uploadService.uploadFile(this.resourceUrl, f!, 'text').subscribe((res: any) => { + if (res) { + this.csvErrors = JSON.parse(res) + this.loading = false + if (this.csvErrors.length === 0) { + this.eventService.broadcast(new Event(EventType.MEMBER_LIST_MODIFICATION, 'New member uploaded')) + this.activeModal.dismiss(true) + } + } + }) + } else { + alert( + $localize`:gatewayApp.msUserServiceMSUser.import.emptyFile.string:There is no file to upload. Please select one.` + ) + } + } +} + +@Component({ + selector: 'app-member-import-popup', + template: '', +}) +export class MemberImportPopupComponent implements OnInit, OnDestroy { + protected ngbModalRef: NgbModalRef | undefined | null + + constructor( + protected activatedRoute: ActivatedRoute, + protected router: Router, + protected modalService: NgbModal + ) {} + + ngOnInit() { + this.activatedRoute.data.subscribe(({ msMember }) => { + setTimeout(() => { + this.ngbModalRef = this.modalService.open(MemberImportDialogComponent as Component, { + size: 'lg', + backdrop: 'static', + }) + this.ngbModalRef.componentInstance.msMember = msMember + this.ngbModalRef.result.then( + (result) => { + this.router.navigate(['/members', { outlets: { popup: null } }]) + this.ngbModalRef = null + }, + (reason) => { + this.router.navigate(['/members', { outlets: { popup: null } }]) + this.ngbModalRef = null + } + ) + }, 0) + }) + } + + ngOnDestroy() { + this.ngbModalRef = null + } +} diff --git a/ui/src/app/member/member.module.ts b/ui/src/app/member/member.module.ts index 71bdee116..52159ceee 100644 --- a/ui/src/app/member/member.module.ts +++ b/ui/src/app/member/member.module.ts @@ -8,6 +8,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms' import { MembersComponent } from './members.component' import { MemberUpdateComponent } from './member-update.component' import { MemberDetailComponent } from './member-detail.component' +import { MemberImportDialogComponent } from './member-import-dialog.component' @NgModule({ imports: [ @@ -18,6 +19,6 @@ import { MemberDetailComponent } from './member-detail.component' FormsModule, ReactiveFormsModule, ], - declarations: [MembersComponent, MemberUpdateComponent, MemberDetailComponent], + declarations: [MembersComponent, MemberUpdateComponent, MemberDetailComponent, MemberImportDialogComponent], }) export class MemberModule {} diff --git a/ui/src/app/member/member.route.ts b/ui/src/app/member/member.route.ts index 8c5ac7160..b288c8086 100644 --- a/ui/src/app/member/member.route.ts +++ b/ui/src/app/member/member.route.ts @@ -7,6 +7,7 @@ import { MemberService } from './service/member.service' import { MembersComponent } from './members.component' import { MemberUpdateComponent } from './member-update.component' import { MemberDetailComponent } from './member-detail.component' +import { MemberImportPopupComponent } from './member-import-dialog.component' export const MemberResolver: ResolveFn = ( route: ActivatedRouteSnapshot, @@ -33,6 +34,18 @@ export const memberRoutes: Routes = [ pageTitle: 'gatewayApp.msUserServiceMSMember.home.title.string', }, canActivate: [AuthGuard], + children: [ + { + path: 'import', + component: MemberImportPopupComponent, + data: { + authorities: ['ROLE_ADMIN'], + pageTitle: 'gatewayApp.msUserServiceMSUser.home.title.strings', + }, + canActivate: [AuthGuard], + outlet: 'popup', + }, + ], }, { path: 'new', diff --git a/ui/src/app/member/members.component.html b/ui/src/app/member/members.component.html index abfdec74e..93f68d952 100644 --- a/ui/src/app/member/members.component.html +++ b/ui/src/app/member/members.component.html @@ -13,7 +13,7 @@

Import members from CSV @@ -173,3 +173,4 @@