TSK-1675: fix import function to go through http interceptor (#1737)
This commit is contained in:
parent
d922d8a178
commit
6124db01ad
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="import-export">
|
<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
|
Import
|
||||||
<mat-icon>cloud_upload</mat-icon>
|
<mat-icon>cloud_upload</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<input #selectedFile type="file" accept=".json" (change)="uploadFile()" class="hidden" />
|
<input #selectedFile type="file" accept=".json" (change)="uploadFile()" class="hidden" />
|
||||||
</form>
|
</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
|
Export
|
||||||
<mat-icon>cloud_download</mat-icon>
|
<mat-icon>cloud_download</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<button mat-menu-item href="javascript:void(0)" (click)="export()">
|
<button mat-menu-item href="javascript:void(0)" (click)="export()">
|
||||||
All Domains
|
All Domains
|
||||||
</button>
|
</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}}
|
{{domain === '' ? 'Master' : domain}}
|
||||||
</button>
|
</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { WindowRefService } from '../../../shared/services/window/window.service
|
||||||
import { DomainService } from '../../../shared/services/domain/domain.service';
|
import { DomainService } from '../../../shared/services/domain/domain.service';
|
||||||
import { WorkbasketDefinitionService } from '../../services/workbasket-definition.service';
|
import { WorkbasketDefinitionService } from '../../services/workbasket-definition.service';
|
||||||
import { NotificationService } from '../../../shared/services/notifications/notification.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 { ImportExportService } from '../../services/import-export.service';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
|
|
@ -56,7 +55,6 @@ xdescribe('ImportExportComponent', () => {
|
||||||
WindowRefService,
|
WindowRefService,
|
||||||
WorkbasketDefinitionService,
|
WorkbasketDefinitionService,
|
||||||
ClassificationDefinitionService,
|
ClassificationDefinitionService,
|
||||||
UploadService,
|
|
||||||
ImportExportService,
|
ImportExportService,
|
||||||
{ provide: DomainService, useClass: domainServiceSpy },
|
{ provide: DomainService, useClass: domainServiceSpy },
|
||||||
{ provide: NotificationService, useClass: notificationServiceSpy },
|
{ provide: NotificationService, useClass: notificationServiceSpy },
|
||||||
|
|
|
||||||
|
|
@ -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 { ClassificationDefinitionService } from 'app/administration/services/classification-definition.service';
|
||||||
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition.service';
|
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition.service';
|
||||||
import { DomainService } from 'app/shared/services/domain/domain.service';
|
import { DomainService } from 'app/shared/services/domain/domain.service';
|
||||||
import { TaskanaType } from 'app/shared/models/taskana-type';
|
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 { ImportExportService } from 'app/administration/services/import-export.service';
|
||||||
import { NotificationService } from '../../../shared/services/notifications/notification.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({
|
@Component({
|
||||||
selector: 'taskana-administration-import-export',
|
selector: 'taskana-administration-import-export',
|
||||||
templateUrl: './import-export.component.html',
|
templateUrl: './import-export.component.html',
|
||||||
styleUrls: ['./import-export.component.scss']
|
styleUrls: ['./import-export.component.scss']
|
||||||
})
|
})
|
||||||
export class ImportExportComponent implements OnInit {
|
export class ImportExportComponent implements OnInit, OnDestroy {
|
||||||
@Input() currentSelection: TaskanaType;
|
@Input() currentSelection: TaskanaType;
|
||||||
@Input() parentComponent: string;
|
@Input() parentComponent: string;
|
||||||
|
|
||||||
@ViewChild('selectedFile', { static: true })
|
@ViewChild('selectedFile', { static: true })
|
||||||
selectedFileInput;
|
selectedFileInput;
|
||||||
|
|
||||||
domains: string[] = [];
|
domains$: Observable<string[]>;
|
||||||
|
destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private domainService: DomainService,
|
private domainService: DomainService,
|
||||||
private workbasketDefinitionService: WorkbasketDefinitionService,
|
private workbasketDefinitionService: WorkbasketDefinitionService,
|
||||||
private classificationDefinitionService: ClassificationDefinitionService,
|
private classificationDefinitionService: ClassificationDefinitionService,
|
||||||
public uploadService: UploadService,
|
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private importExportService: ImportExportService
|
private importExportService: ImportExportService,
|
||||||
|
private hotToastService: HotToastService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.domainService.getDomains().subscribe((data) => {
|
this.domains$ = this.domainService.getDomains();
|
||||||
this.domains = data;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export(domain = '') {
|
export(domain = '') {
|
||||||
|
|
@ -47,31 +52,42 @@ export class ImportExportComponent implements OnInit {
|
||||||
|
|
||||||
uploadFile() {
|
uploadFile() {
|
||||||
const file = this.selectedFileInput.nativeElement.files[0];
|
const file = this.selectedFileInput.nativeElement.files[0];
|
||||||
const formdata = new FormData();
|
|
||||||
const ajax = new XMLHttpRequest();
|
|
||||||
if (this.checkFormatFile(file)) {
|
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) {
|
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 {
|
} 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);
|
|
||||||
}
|
}
|
||||||
}
|
this.resetProgress();
|
||||||
|
|
||||||
progressHandler(event) {
|
|
||||||
const percent = (event.loaded / event.total) * 100;
|
|
||||||
this.uploadService.setCurrentProgressValue(Math.round(percent));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkFormatFile(file): boolean {
|
private checkFormatFile(file): boolean {
|
||||||
|
|
@ -87,40 +103,11 @@ export class ImportExportComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetProgress() {
|
private resetProgress() {
|
||||||
this.uploadService.setCurrentProgressValue(0);
|
|
||||||
this.uploadService.isInUse = false;
|
|
||||||
this.selectedFileInput.nativeElement.value = '';
|
this.selectedFileInput.nativeElement.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private onReadyStateChangeHandler(event) {
|
ngOnDestroy() {
|
||||||
if (event.readyState === 4 && event.status >= 400) {
|
this.destroy$.next();
|
||||||
let key = 'FALLBACK';
|
this.destroy$.complete();
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Injectable } from '@angular/core';
|
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 { TaskanaDate } from 'app/shared/util/taskana.date';
|
||||||
import { BlobGenerator } from 'app/shared/util/blob-generator';
|
import { BlobGenerator } from 'app/shared/util/blob-generator';
|
||||||
import { Classification } from '../../shared/models/classification';
|
import { Classification } from '../../shared/models/classification';
|
||||||
|
|
@ -24,4 +24,11 @@ export class ClassificationDefinitionService {
|
||||||
);
|
);
|
||||||
return classificationDefObservable;
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { environment } from 'environments/environment';
|
|
||||||
import { WorkbasketDefinition } from 'app/shared/models/workbasket-definition';
|
import { WorkbasketDefinition } from 'app/shared/models/workbasket-definition';
|
||||||
import { TaskanaDate } from 'app/shared/util/taskana.date';
|
import { TaskanaDate } from 'app/shared/util/taskana.date';
|
||||||
import { BlobGenerator } from 'app/shared/util/blob-generator';
|
import { BlobGenerator } from 'app/shared/util/blob-generator';
|
||||||
import { take } from 'rxjs/operators';
|
import { take } from 'rxjs/operators';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
import { StartupService } from '../../shared/services/startup/startup.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkbasketDefinitionService {
|
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
|
// GET
|
||||||
exportWorkbaskets(domain: string): Observable<WorkbasketDefinition[]> {
|
exportWorkbaskets(domain: string): Observable<WorkbasketDefinition[]> {
|
||||||
|
|
@ -22,4 +24,11 @@ export class WorkbasketDefinitionService {
|
||||||
);
|
);
|
||||||
return workbasketDefObservable;
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,6 @@
|
||||||
<mat-progress-bar *ngIf="requestInProgress" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="requestInProgress" mode="indeterminate"></mat-progress-bar>
|
||||||
</div>
|
</div>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
<taskana-shared-progress-spinner
|
|
||||||
[hidden]="currentProgressValue === 0" currentValue={{currentProgressValue}}>
|
|
||||||
</taskana-shared-progress-spinner>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mat-sidenav-content>
|
</mat-sidenav-content>
|
||||||
|
|
|
||||||
|
|
@ -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 { RequestInProgressService } from './shared/services/request-in-progress/request-in-progress.service';
|
||||||
import { OrientationService } from './shared/services/orientation/orientation.service';
|
import { OrientationService } from './shared/services/orientation/orientation.service';
|
||||||
import { SelectedRouteService } from './shared/services/selected-route/selected-route';
|
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 { TaskanaEngineService } from './shared/services/taskana-engine/taskana-engine.service';
|
||||||
import { WindowRefService } from 'app/shared/services/window/window.service';
|
import { WindowRefService } from 'app/shared/services/window/window.service';
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
|
|
@ -23,7 +22,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
selectedRoute = '';
|
selectedRoute = '';
|
||||||
|
|
||||||
requestInProgress = false;
|
requestInProgress = false;
|
||||||
currentProgressValue = 0;
|
|
||||||
|
|
||||||
version: string;
|
version: string;
|
||||||
toggle: boolean = false;
|
toggle: boolean = false;
|
||||||
|
|
@ -36,7 +34,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
private orientationService: OrientationService,
|
private orientationService: OrientationService,
|
||||||
private selectedRouteService: SelectedRouteService,
|
private selectedRouteService: SelectedRouteService,
|
||||||
private formsValidatorService: FormsValidatorService,
|
private formsValidatorService: FormsValidatorService,
|
||||||
public uploadService: UploadService,
|
|
||||||
private sidenavService: SidenavService,
|
private sidenavService: SidenavService,
|
||||||
private taskanaEngineService: TaskanaEngineService,
|
private taskanaEngineService: TaskanaEngineService,
|
||||||
private window: WindowRefService
|
private window: WindowRefService
|
||||||
|
|
@ -76,13 +73,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
this.selectedRoute = value;
|
this.selectedRoute = value;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.uploadService
|
|
||||||
.getCurrentProgressObservable()
|
|
||||||
.pipe(takeUntil(this.destroy$))
|
|
||||||
.subscribe((value) => {
|
|
||||||
this.currentProgressValue = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.taskanaEngineService
|
this.taskanaEngineService
|
||||||
.getVersion()
|
.getVersion()
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
|
|
||||||
|
|
@ -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 { UserInformationComponent } from 'app/shared/components/user-information/user-information.component';
|
||||||
import { NoAccessComponent } from 'app/shared/components/no-access/no-access.component';
|
import { NoAccessComponent } from 'app/shared/components/no-access/no-access.component';
|
||||||
import { FormsValidatorService } from './shared/services/forms-validator/forms-validator.service';
|
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 { NotificationService } from './shared/services/notifications/notification.service';
|
||||||
import { SidenavService } from './shared/services/sidenav/sidenav.service';
|
import { SidenavService } from './shared/services/sidenav/sidenav.service';
|
||||||
import { SidenavListComponent } from 'app/shared/components/sidenav-list/sidenav-list.component';
|
import { SidenavListComponent } from 'app/shared/components/sidenav-list/sidenav-list.component';
|
||||||
|
|
@ -105,7 +104,6 @@ const PROVIDERS = [
|
||||||
MasterAndDetailService,
|
MasterAndDetailService,
|
||||||
TaskanaEngineService,
|
TaskanaEngineService,
|
||||||
FormsValidatorService,
|
FormsValidatorService,
|
||||||
UploadService,
|
|
||||||
NotificationService,
|
NotificationService,
|
||||||
ClassificationCategoriesService,
|
ClassificationCategoriesService,
|
||||||
SidenavService,
|
SidenavService,
|
||||||
|
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue