Skip to content

Commit

Permalink
Merge pull request #1154 from ORCID/memberImport
Browse files Browse the repository at this point in the history
member import
  • Loading branch information
auumgn authored Apr 10, 2024
2 parents d21f18d + cdc1a7c commit 33928ff
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 2 deletions.
43 changes: 43 additions & 0 deletions ui/src/app/member/member-import-dialog.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<form name="uploadForm" (ngSubmit)="upload()">
<div class="modal-header">
<h4 class="modal-title" i18n="@@gatewayApp.msUserServiceMSMember.import.title.string">Import members</h4>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"
(click)="clear()">&times;</button>
</div>
<div class="modal-body">
<app-error-alert></app-error-alert>
<div class="alerts top right" role="alert" *ngIf="csvErrors && csvErrors.length > 0">
<ngb-alert class="alert alert-danger alert-dismissible" role="alert" ng-reflect-type="danger" (close)="csvErrors = null" [dismissible]="false">
<p i18n="@@gatewayApp.msUserServiceMSMember.import.errors.label.string">Oops! There was a problem processing your data. Pleases fix the errors below and try again</p>
<table>
<thead>
<th>Errors</th>
</thead>
<tbody>
<tr *ngFor="let error of csvErrors">
<td>Row {{error['index']}}</td>
<td>{{error['message']}}</td>
</tr>
</tbody>
</table>
</ngb-alert>
</div>
<p id="jhi-delete-msMember-heading" i18n="@@gatewayApp.msUserServiceMSMember.import.label.string">Please select a CSV file to upload</p>
<div class="form-group">
<label class="form-control-label sr-only" i18n="@@gatewayApp.msUserServiceMSMember.import.filePath" for="field_filePath.string">File Path</label>
<input type="file" class="form-control" name="filePath" id="field_filePath" accept=".csv"
onclick="this.value=null" (change)="selectFile($event)"/>
</div>
</div>
<div *ngIf="loading" class="progress progress-striped">
<div class="progress-bar indeterminate" role="progressbar"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" data-dismiss="modal" (click)="clear()">
<fa-icon [icon]="faBan"></fa-icon>&nbsp;<span i18n="@@entity.action.cancel.string">Cancel</span>
</button>
<button id="jhi-confirm-delete-msUser" type="submit" class="btn btn-primary">
<fa-icon [icon]="faSave"></fa-icon>&nbsp;<span i18n="@@entity.action.upload.string">Upload</span>
</button>
</div>
</form>
Empty file.
64 changes: 64 additions & 0 deletions ui/src/app/member/member-import-dialog.component.spec.ts
Original file line number Diff line number Diff line change
@@ -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<MemberImportDialogComponent>

let uploadServiceSpy: jasmine.SpyObj<FileUploadService>

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<FileUploadService>
})

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 = <File>blob
const fileList: FileList = {
0: file,
1: file,
length: 2,
item: (index: number) => file,
}
return fileList
}
})
104 changes: 104 additions & 0 deletions ui/src/app/member/member-import-dialog.component.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
3 changes: 2 additions & 1 deletion ui/src/app/member/member.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
Expand All @@ -18,6 +19,6 @@ import { MemberDetailComponent } from './member-detail.component'
FormsModule,
ReactiveFormsModule,
],
declarations: [MembersComponent, MemberUpdateComponent, MemberDetailComponent],
declarations: [MembersComponent, MemberUpdateComponent, MemberDetailComponent, MemberImportDialogComponent],
})
export class MemberModule {}
13 changes: 13 additions & 0 deletions ui/src/app/member/member.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Member | null> = (
route: ActivatedRouteSnapshot,
Expand All @@ -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',
Expand Down
3 changes: 2 additions & 1 deletion ui/src/app/member/members.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h1 id="page-heading" class="mt-3" i18n="@@gatewayApp.msUserServiceMSMember.home
<button
id="jh-upload-ms-member"
class="btn btn-primary float-right jh-create-entity create-ms-member ml-1"
[routerLink]="['/', 'members', { outlets: { popup: 'import' } }]"
[routerLink]="['/' + 'members', { outlets: { popup: 'import' } }]"
>
<fa-icon [icon]="faPlus"></fa-icon>
<span i18n="@@gatewayApp.msUserServiceMSMember.home.uploadLabel.string"> Import members from CSV </span>
Expand Down Expand Up @@ -173,3 +173,4 @@ <h1 id="page-heading" class="mt-3" i18n="@@gatewayApp.msUserServiceMSMember.home
</div>
</div>
</div>
<router-outlet name="popup"></router-outlet>

0 comments on commit 33928ff

Please sign in to comment.