TSK-1675: fix import function to go through http interceptor (#1737)

This commit is contained in:
Chi Nguyen 2021-11-01 09:56:30 +01:00 committed by GitHub
parent d922d8a178
commit 6124db01ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 105 deletions

View File

@ -1,5 +1,5 @@
<div class="import-export">
<button mat-stroked-button class="mr-1" matTooltip="Import {{parentComponent}}" [ngClass]="{disabled: uploadService?.isInUse}" (click)="selectedFile.click()" title="Import">
<button mat-stroked-button class="mr-1" matTooltip="Import {{parentComponent}}" (click)="selectedFile.click()" title="Import">
Import
<mat-icon>cloud_upload</mat-icon>
</button>
@ -8,7 +8,7 @@
<input #selectedFile type="file" accept=".json" (change)="uploadFile()" class="hidden" />
</form>
<button mat-stroked-button class="mr-1" matTooltip="Export {{parentComponent}}" [matMenuTriggerFor]="menu" [ngClass]="{disabled: uploadService?.isInUse}" title="Export">
<button mat-stroked-button class="mr-1" matTooltip="Export {{parentComponent}}" [matMenuTriggerFor]="menu" title="Export">
Export
<mat-icon>cloud_download</mat-icon>
</button>
@ -16,7 +16,7 @@
<button mat-menu-item href="javascript:void(0)" (click)="export()">
All Domains
</button>
<button mat-menu-item *ngFor="let domain of domains" href="javascript:void(0)" (click)="export(domain)">
<button mat-menu-item *ngFor="let domain of (domains$ | async)" href="javascript:void(0)" (click)="export(domain)">
{{domain === '' ? 'Master' : domain}}
</button>
</mat-menu>

View File

@ -7,7 +7,6 @@ import { WindowRefService } from '../../../shared/services/window/window.service
import { DomainService } from '../../../shared/services/domain/domain.service';
import { WorkbasketDefinitionService } from '../../services/workbasket-definition.service';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { UploadService } from '../../../shared/services/upload/upload.service';
import { ImportExportService } from '../../services/import-export.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';
@ -56,7 +55,6 @@ xdescribe('ImportExportComponent', () => {
WindowRefService,
WorkbasketDefinitionService,
ClassificationDefinitionService,
UploadService,
ImportExportService,
{ provide: DomainService, useClass: domainServiceSpy },
{ provide: NotificationService, useClass: notificationServiceSpy },

View File

@ -1,40 +1,45 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition.service';
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition.service';
import { DomainService } from 'app/shared/services/domain/domain.service';
import { TaskanaType } from 'app/shared/models/taskana-type';
import { environment } from 'environments/environment';
import { UploadService } from 'app/shared/services/upload/upload.service';
import { ImportExportService } from 'app/administration/services/import-export.service';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { Observable, Subject } from 'rxjs';
import { HotToastService } from '@ngneat/hot-toast';
import { takeUntil } from 'rxjs/operators';
/**
* Recommendation: Turn this component into presentational component - no logic, instead events are
* fired back to parent components with @Output(). This way the logic of exporting/importing workbasket
* or classification is stored in their respective container component.
*/
@Component({
selector: 'taskana-administration-import-export',
templateUrl: './import-export.component.html',
styleUrls: ['./import-export.component.scss']
})
export class ImportExportComponent implements OnInit {
export class ImportExportComponent implements OnInit, OnDestroy {
@Input() currentSelection: TaskanaType;
@Input() parentComponent: string;
@ViewChild('selectedFile', { static: true })
selectedFileInput;
domains: string[] = [];
domains$: Observable<string[]>;
destroy$ = new Subject<void>();
constructor(
private domainService: DomainService,
private workbasketDefinitionService: WorkbasketDefinitionService,
private classificationDefinitionService: ClassificationDefinitionService,
public uploadService: UploadService,
private notificationService: NotificationService,
private importExportService: ImportExportService
private importExportService: ImportExportService,
private hotToastService: HotToastService
) {}
ngOnInit() {
this.domainService.getDomains().subscribe((data) => {
this.domains = data;
});
this.domains$ = this.domainService.getDomains();
}
export(domain = '') {
@ -47,31 +52,42 @@ export class ImportExportComponent implements OnInit {
uploadFile() {
const file = this.selectedFileInput.nativeElement.files[0];
const formdata = new FormData();
const ajax = new XMLHttpRequest();
if (this.checkFormatFile(file)) {
formdata.append('file', file);
ajax.upload.addEventListener('progress', this.progressHandler.bind(this), false);
ajax.addEventListener('load', this.resetProgress.bind(this), false);
ajax.addEventListener('error', this.onFailedResponse.bind(this, ajax), false);
ajax.onreadystatechange = this.onReadyStateChangeHandler.bind(this, ajax);
if (this.currentSelection === TaskanaType.WORKBASKETS) {
ajax.open('POST', `${environment.taskanaRestUrl}/v1/workbasket-definitions`);
this.workbasketDefinitionService
.importWorkbasket(file)
.pipe(
takeUntil(this.destroy$),
this.hotToastService.observe({
loading: 'Uploading...',
success: 'File successfully uploaded',
error: 'Upload failed'
})
)
.subscribe({
next: () => {
this.importExportService.setImportingFinished(true);
}
});
} else {
ajax.open('POST', `${environment.taskanaRestUrl}/v1/classification-definitions`);
this.classificationDefinitionService
.importClassification(file)
.pipe(
takeUntil(this.destroy$),
this.hotToastService.observe({
loading: 'Uploading...',
success: 'File successfully uploaded',
error: 'Upload failed'
})
)
.subscribe({
next: () => {
this.importExportService.setImportingFinished(true);
}
});
}
if (!environment.production) {
ajax.setRequestHeader('Authorization', 'Basic YWRtaW46YWRtaW4=');
}
ajax.send(formdata);
this.uploadService.isInUse = true;
this.uploadService.setCurrentProgressValue(1);
}
}
progressHandler(event) {
const percent = (event.loaded / event.total) * 100;
this.uploadService.setCurrentProgressValue(Math.round(percent));
this.resetProgress();
}
private checkFormatFile(file): boolean {
@ -87,40 +103,11 @@ export class ImportExportComponent implements OnInit {
}
private resetProgress() {
this.uploadService.setCurrentProgressValue(0);
this.uploadService.isInUse = false;
this.selectedFileInput.nativeElement.value = '';
}
private onReadyStateChangeHandler(event) {
if (event.readyState === 4 && event.status >= 400) {
let key = 'FALLBACK';
if (event.status === 401) {
key = 'IMPORT_EXPORT_UPLOAD_FAILED_AUTH';
} else if (event.status === 404) {
key = 'IMPORT_EXPORT_UPLOAD_FAILED_NOT_FOUND';
} else if (event.status === 409) {
key = 'IMPORT_EXPORT_UPLOAD_FAILED_CONFLICTS';
} else if (event.status === 413) {
key = 'IMPORT_EXPORT_UPLOAD_FAILED_SIZE';
}
this.errorHandler(key);
} else if (event.readyState === 4 && event.status === 204) {
const message = this.currentSelection === TaskanaType.WORKBASKETS ? 'WORKBASKET_IMPORT' : 'CLASSIFICATION_IMPORT';
this.notificationService.showSuccess(message);
this.importExportService.setImportingFinished(true);
this.resetProgress();
}
}
private onFailedResponse() {
this.errorHandler('IMPORT_EXPORT_UPLOAD_FAILED');
}
private errorHandler(key: string) {
this.notificationService.showError(key);
delete this.selectedFileInput.files;
this.resetProgress();
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { BlobGenerator } from 'app/shared/util/blob-generator';
import { Classification } from '../../shared/models/classification';
@ -24,4 +24,11 @@ export class ClassificationDefinitionService {
);
return classificationDefObservable;
}
importClassification(file: File) {
const formData = new FormData();
formData.append('file', file);
const headers = new HttpHeaders().set('Content-Type', 'multipart/form-data');
return this.httpClient.post(this.url, formData, { headers });
}
}

View File

@ -1,17 +1,19 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { WorkbasketDefinition } from 'app/shared/models/workbasket-definition';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { BlobGenerator } from 'app/shared/util/blob-generator';
import { take } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { StartupService } from '../../shared/services/startup/startup.service';
@Injectable()
export class WorkbasketDefinitionService {
url: string = `${environment.taskanaRestUrl}/v1/workbasket-definitions`;
constructor(private httpClient: HttpClient, private startupService: StartupService) {}
constructor(private httpClient: HttpClient) {}
get url(): string {
return this.startupService.getTaskanaRestUrl() + '/v1/workbasket-definitions';
}
// GET
exportWorkbaskets(domain: string): Observable<WorkbasketDefinition[]> {
@ -22,4 +24,11 @@ export class WorkbasketDefinitionService {
);
return workbasketDefObservable;
}
importWorkbasket(file: File) {
const formData = new FormData();
formData.append('file', file);
const headers = new HttpHeaders().set('Content-Type', 'multipart/form-data');
return this.httpClient.post(this.url, formData, { headers });
}
}

View File

@ -27,9 +27,6 @@
<mat-progress-bar *ngIf="requestInProgress" mode="indeterminate"></mat-progress-bar>
</div>
<router-outlet></router-outlet>
<taskana-shared-progress-spinner
[hidden]="currentProgressValue === 0" currentValue={{currentProgressValue}}>
</taskana-shared-progress-spinner>
</div>
</div>
</mat-sidenav-content>

View File

@ -6,7 +6,6 @@ import { SidenavService } from './shared/services/sidenav/sidenav.service';
import { RequestInProgressService } from './shared/services/request-in-progress/request-in-progress.service';
import { OrientationService } from './shared/services/orientation/orientation.service';
import { SelectedRouteService } from './shared/services/selected-route/selected-route';
import { UploadService } from './shared/services/upload/upload.service';
import { TaskanaEngineService } from './shared/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from 'app/shared/services/window/window.service';
import { environment } from 'environments/environment';
@ -23,7 +22,6 @@ export class AppComponent implements OnInit, OnDestroy {
selectedRoute = '';
requestInProgress = false;
currentProgressValue = 0;
version: string;
toggle: boolean = false;
@ -36,7 +34,6 @@ export class AppComponent implements OnInit, OnDestroy {
private orientationService: OrientationService,
private selectedRouteService: SelectedRouteService,
private formsValidatorService: FormsValidatorService,
public uploadService: UploadService,
private sidenavService: SidenavService,
private taskanaEngineService: TaskanaEngineService,
private window: WindowRefService
@ -76,13 +73,6 @@ export class AppComponent implements OnInit, OnDestroy {
this.selectedRoute = value;
});
this.uploadService
.getCurrentProgressObservable()
.pipe(takeUntil(this.destroy$))
.subscribe((value) => {
this.currentProgressValue = value;
});
this.taskanaEngineService
.getVersion()
.pipe(takeUntil(this.destroy$))

View File

@ -39,7 +39,6 @@ import { NavBarComponent } from 'app/shared/components/nav-bar/nav-bar.component
import { UserInformationComponent } from 'app/shared/components/user-information/user-information.component';
import { NoAccessComponent } from 'app/shared/components/no-access/no-access.component';
import { FormsValidatorService } from './shared/services/forms-validator/forms-validator.service';
import { UploadService } from './shared/services/upload/upload.service';
import { NotificationService } from './shared/services/notifications/notification.service';
import { SidenavService } from './shared/services/sidenav/sidenav.service';
import { SidenavListComponent } from 'app/shared/components/sidenav-list/sidenav-list.component';
@ -105,7 +104,6 @@ const PROVIDERS = [
MasterAndDetailService,
TaskanaEngineService,
FormsValidatorService,
UploadService,
NotificationService,
ClassificationCategoriesService,
SidenavService,

View File

@ -1,18 +0,0 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UploadService {
private currentProgressValue = new Subject<number>();
public isInUse = false;
setCurrentProgressValue(value: number) {
this.currentProgressValue.next(value);
}
getCurrentProgressObservable(): Observable<number> {
return this.currentProgressValue.asObservable();
}
}