UI: Entities version create/restore progress and error handling

This commit is contained in:
Igor Kulikov 2022-06-21 13:10:20 +03:00
parent 6ea1ac0c8e
commit 9a893dd330
16 changed files with 296 additions and 156 deletions

View File

@ -53,12 +53,14 @@ import { forkJoin, Observable, of, ReplaySubject, Subject, throwError, timer } f
import { CancelAnimationFrame } from '@core/services/raf.service';
import { EntityType } from '@shared/models/entity-type.models';
import {
createLabelFromDatasource, createLabelFromPattern,
deepClone, flatFormattedData,
createLabelFromPattern,
deepClone,
flatFormattedData,
formattedDataFormDatasourceData,
isDefined,
isDefinedAndNotNull,
isEqual
isEqual,
parseHttpErrorMessage
} from '@core/utils';
import { EntityId } from '@app/shared/models/id/entity-id';
import * as moment_ from 'moment';
@ -801,10 +803,10 @@ export class WidgetSubscription implements IWidgetSubscription {
this.rpcErrorText = 'Request Timeout.';
} else {
this.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
const error = this.extractRejectionErrorText(rejection);
const error = parseHttpErrorMessage(rejection, this.ctx.translate);
if (error) {
this.rpcErrorText += '</br>';
this.rpcErrorText += error;
this.rpcErrorText += error.message;
}
}
this.callbacks.onRpcFailed(this);
@ -816,40 +818,6 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
private extractRejectionErrorText(rejection: HttpErrorResponse) {
let error = null;
if (rejection.error) {
error = rejection.error;
try {
error = rejection.error ? JSON.parse(rejection.error) : null;
} catch (e) {}
}
if (error && !error.message) {
error = this.prepareMessageFromData(error);
} else if (error && error.message) {
error = error.message;
}
return error;
}
private prepareMessageFromData(data) {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}
update(isTimewindowTypeChanged = false) {
if (this.type !== widgetType.rpc) {
this.widgetTimewindowChangedSubject.next(this.timeWindowConfig);

View File

@ -38,10 +38,11 @@ import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.m
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { selectIsUserLoaded } from '@core/auth/auth.selectors';
import { catchError, finalize, switchMap, takeWhile, tap } from 'rxjs/operators';
import { catchError, finalize, map, switchMap, takeWhile, tap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActionLoadFinish, ActionLoadStart } from '@core/interceptors/load.actions';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@Injectable({
providedIn: 'root'
@ -135,7 +136,24 @@ export class EntitiesVersionControlService {
}
public loadEntitiesVersion(request: VersionLoadRequest, config?: RequestConfig): Observable<VersionLoadResult> {
return this.http.post<VersionLoadResult>('/api/entities/vc/entity', request, defaultHttpOptionsFromConfig(config));
this.store.dispatch(new ActionLoadStart());
return this.http.post<string>('/api/entities/vc/entity', request,
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}})).pipe(
switchMap((requestId) => {
return timer(0, 2000).pipe(
switchMap(() => this.getVersionLoadRequestStatus(requestId, config)),
takeWhile((res) => !res.done, true),
);
}),
finalize(() => {
this.store.dispatch(new ActionLoadFinish());
}),
);
}
private getVersionLoadRequestStatus(requestId: string, config?: RequestConfig): Observable<VersionLoadResult> {
return this.http.get<VersionLoadResult>(`/api/entities/vc/entity/${requestId}/status`,
defaultHttpOptionsFromConfig({...config, ...{ignoreLoading: true}}));
}
public compareEntityDataToVersion(branch: string,

View File

@ -29,6 +29,7 @@ import { ActionLoadFinish, ActionLoadStart } from './load.actions';
import { ActionNotificationShow } from '@app/core/notification/notification.actions';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { parseHttpErrorMessage } from '@core/utils';
let tmpHeaders = {};
@ -131,43 +132,12 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
}
if (unhandled && !ignoreErrors) {
let error = null;
if (req.responseType === 'text') {
try {
error = errorResponse.error ? JSON.parse(errorResponse.error) : null;
} catch (e) {}
} else {
error = errorResponse.error;
}
if (error && !error.message) {
this.showError(this.prepareMessageFromData(error));
} else if (error && error.message) {
this.showError(error.message, error.timeout ? error.timeout : 0);
} else {
this.showError('Unhandled error code ' + (error ? error.status : '\'Unknown\''));
}
const errorMessageWithTimeout = parseHttpErrorMessage(errorResponse, this.translate, req.responseType);
this.showError(errorMessageWithTimeout.message, errorMessageWithTimeout.timeout);
}
return throwError(errorResponse);
}
private prepareMessageFromData(data) {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}
private retryRequest(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const thisTimeout = 1000 + Math.random() * 3000;
return of(null).pipe(

View File

@ -21,6 +21,10 @@ import { Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/sha
import { EntityId } from '@shared/models/id/entity-id';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { EntityType, baseDetailsPageByEntityType } from '@shared/models/entity-type.models';
import { HttpErrorResponse } from '@angular/common/http';
import { letterSpacing } from 'html2canvas/dist/types/css/property-descriptors/letter-spacing';
import { TranslateService } from '@ngx-translate/core';
import { serverErrorCodesTranslations } from '@shared/models/constants';
const varsRegex = /\${([^}]*)}/g;
@ -664,3 +668,53 @@ export function randomAlphanumeric(length: number): string {
export function getEntityDetailsPageURL(id: string, entityType: EntityType): string {
return `${baseDetailsPageByEntityType.get(entityType)}/${id}`;
}
export function parseHttpErrorMessage(errorResponse: HttpErrorResponse,
translate: TranslateService, responseType?: string): {message: string, timeout: number} {
let error = null;
let errorMessage: string;
let timeout = 0;
if (responseType === 'text') {
try {
error = errorResponse.error ? JSON.parse(errorResponse.error) : null;
} catch (e) {}
} else {
error = errorResponse.error;
}
if (error && !error.message) {
errorMessage = prepareMessageFromData(error);
} else if (error && error.message) {
errorMessage = error.message;
timeout = error.timeout ? error.timeout : 0;
} else {
errorMessage = `Unhandled error code ${error ? error.status : '\'Unknown\''}`;
}
if (isObject(errorMessage)) {
let errorText = `${errorResponse.status}: `;
let errorKey = null;
if ((errorMessage as any).errorCode) {
errorKey = serverErrorCodesTranslations.get((errorMessage as any).errorCode);
}
errorText += errorKey ? translate.instant(errorKey) : errorResponse.statusText;
errorMessage = errorText;
}
return {message: errorMessage, timeout};
}
function prepareMessageFromData(data): string {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}

View File

@ -16,7 +16,7 @@
-->
<section style="min-width: 800px;">
<section *ngIf="!resultMessage">
<section *ngIf="!versionCreateResult$">
<mat-toolbar>
<h2>{{ 'version-control.create-entities-version' | translate }}</h2>
<span fxFlex></span>
@ -69,9 +69,11 @@
</button>
</div>
</section>
<section *ngIf="resultMessage">
<div class="mat-title vc-result-message" [innerHtml]="resultMessage"></div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<section *ngIf="versionCreateResult$">
<section *ngIf="resultMessage">
<div class="mat-title vc-result-message" [innerHtml]="resultMessage"></div>
</section>
<div *ngIf="(versionCreateResult$ | async)?.done || hasError; else progress" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
@ -79,5 +81,13 @@
{{ 'action.close' | translate }}
</button>
</div>
<ng-template #progress>
<section fxLayout="column" fxLayoutAlign="center center">
<div class="mat-title vc-result-message progress">
<span translate>version-control.creating-version</span>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</section>
</ng-template>
</section>
</section>

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import {
@ -30,13 +30,16 @@ import { EntitiesVersionControlService } from '@core/http/entities-version-contr
import { TranslateService } from '@ngx-translate/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Observable, Subscription } from 'rxjs';
import { share } from 'rxjs/operators';
import { parseHttpErrorMessage } from '@core/utils';
@Component({
selector: 'tb-complex-version-create',
templateUrl: './complex-version-create.component.html',
styleUrls: ['./version-control.scss']
})
export class ComplexVersionCreateComponent extends PageComponent implements OnInit {
export class ComplexVersionCreateComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
branch: string;
@ -57,10 +60,16 @@ export class ComplexVersionCreateComponent extends PageComponent implements OnIn
resultMessage: SafeHtml;
hasError = false;
versionCreateResult: VersionCreationResult = null;
versionCreateBranch: string = null;
versionCreateResult$: Observable<VersionCreationResult>;
private versionCreateResultSubscription: Subscription;
constructor(protected store: Store<AppState>,
private entitiesVersionControlService: EntitiesVersionControlService,
private cd: ChangeDetectorRef,
@ -79,6 +88,13 @@ export class ComplexVersionCreateComponent extends PageComponent implements OnIn
});
}
ngOnDestroy() {
super.ngOnDestroy();
if (this.versionCreateResultSubscription) {
this.versionCreateResultSubscription.unsubscribe();
}
}
cancel(): void {
if (this.onClose) {
this.onClose(this.versionCreateResult, this.versionCreateBranch);
@ -93,11 +109,20 @@ export class ComplexVersionCreateComponent extends PageComponent implements OnIn
entityTypes: this.createVersionFormGroup.get('entityTypes').value,
type: VersionCreateRequestType.COMPLEX
};
this.entitiesVersionControlService.saveEntitiesVersion(request).subscribe((result) => {
if (!result.added && !result.modified && !result.removed) {
this.versionCreateResult$ = this.entitiesVersionControlService.saveEntitiesVersion(request, {ignoreErrors: true}).pipe(
share()
);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
this.versionCreateResultSubscription = this.versionCreateResult$.subscribe((result) => {
if (result.done && !result.added && !result.modified && !result.removed) {
this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(this.translate.instant('version-control.nothing-to-commit'));
} else {
this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(this.translate.instant('version-control.version-create-result',
this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(result.error ? result.error : this.translate.instant('version-control.version-create-result',
{added: result.added, modified: result.modified, removed: result.removed}));
}
this.versionCreateResult = result;
@ -106,6 +131,14 @@ export class ComplexVersionCreateComponent extends PageComponent implements OnIn
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
},
(error) => {
this.hasError = true;
this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(parseHttpErrorMessage(error, this.translate).message);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
});
}
}

View File

@ -15,8 +15,8 @@
limitations under the License.
-->
<section [ngStyle]="versionLoadResult ? {minWidth: '500px'} : {minWidth: '800px'}">
<section *ngIf="!versionLoadResult">
<section [ngStyle]="versionLoadResult$ ? {minWidth: '500px'} : {minWidth: '800px'}">
<section *ngIf="!versionLoadResult$">
<mat-toolbar>
<h2>{{ 'version-control.restore-entities-from-version' | translate: {versionName} }}</h2>
<span fxFlex></span>
@ -48,13 +48,13 @@
</button>
</div>
</section>
<section *ngIf="versionLoadResult">
<div *ngIf="!entityTypeLoadResults.length && !errorMessage" class="mat-title vc-result-message">
<section *ngIf="versionLoadResult$">
<div *ngIf="(versionLoadResult$ | async)?.done && !entityTypeLoadResults?.length && !errorMessage" class="mat-title vc-result-message">
{{ 'version-control.no-entities-restored' | translate }}
</div>
<div *ngIf="errorMessage" class="mat-title vc-result-message error" [innerHTML]="errorMessage"></div>
<div *ngFor="let entityTypeLoadResult of entityTypeLoadResults" class="mat-title vc-result-message">{{ entityTypeLoadResultMessage(entityTypeLoadResult) }}</div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<div *ngIf="(versionLoadResult$ | async)?.done || hasError; else progress" fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
@ -62,5 +62,13 @@
{{ 'action.close' | translate }}
</button>
</div>
<ng-template #progress>
<section fxLayout="column" fxLayoutAlign="center center">
<div class="mat-title vc-result-message progress">
<span translate>version-control.restoring-entities-from-version</span>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</section>
</ng-template>
</section>
</section>

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
@ -28,15 +28,18 @@ import { AppState } from '@core/core.state';
import { EntitiesVersionControlService } from '@core/http/entities-version-control.service';
import { TranslateService } from '@ngx-translate/core';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { SafeHtml } from '@angular/platform-browser';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { Observable, Subscription } from 'rxjs';
import { share } from 'rxjs/operators';
import { parseHttpErrorMessage } from '@core/utils';
@Component({
selector: 'tb-complex-version-load',
templateUrl: './complex-version-load.component.html',
styleUrls: ['./version-control.scss']
})
export class ComplexVersionLoadComponent extends PageComponent implements OnInit {
export class ComplexVersionLoadComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
branch: string;
@ -61,10 +64,17 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
errorMessage: SafeHtml;
hasError = false;
versionLoadResult$: Observable<VersionLoadResult>;
private versionLoadResultSubscription: Subscription;
constructor(protected store: Store<AppState>,
private entitiesVersionControlService: EntitiesVersionControlService,
private cd: ChangeDetectorRef,
private translate: TranslateService,
private sanitizer: DomSanitizer,
private fb: FormBuilder) {
super(store);
}
@ -75,6 +85,13 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
});
}
ngOnDestroy() {
super.ngOnDestroy();
if (this.versionLoadResultSubscription) {
this.versionLoadResultSubscription.unsubscribe();
}
}
entityTypeLoadResultMessage(result: EntityTypeLoadResult): string {
const entityType = result.entityType;
let message = this.translate.instant(entityTypeTranslations.get(entityType).typePlural) + ': ';
@ -105,7 +122,14 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
entityTypes: this.loadVersionFormGroup.get('entityTypes').value,
type: VersionLoadRequestType.ENTITY_TYPE
};
this.entitiesVersionControlService.loadEntitiesVersion(request).subscribe((result) => {
this.versionLoadResult$ = this.entitiesVersionControlService.loadEntitiesVersion(request, {ignoreErrors: true}).pipe(
share()
);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
this.versionLoadResultSubscription = this.versionLoadResult$.subscribe((result) => {
this.versionLoadResult = result;
this.entityTypeLoadResults = (result.result || []).filter(res => res.created || res.updated || res.deleted);
if (result.error) {
@ -115,6 +139,14 @@ export class ComplexVersionLoadComponent extends PageComponent implements OnInit
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
},
(error) => {
this.hasError = true;
this.errorMessage = this.sanitizer.bypassSecurityTrustHtml(parseHttpErrorMessage(error, this.translate).message);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
});
}
}

View File

@ -66,7 +66,7 @@
</div>
</section>
<section *ngIf="versionCreateResult$">
<section *ngIf="(versionCreateResult$ | async)?.done; else progress">
<section *ngIf="(versionCreateResult$ | async)?.done || resultMessage; else progress">
<section *ngIf="resultMessage">
<div class="mat-title vc-result-message">{{ resultMessage }}</div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">

View File

@ -31,6 +31,7 @@ import { Observable, of, Subscription } from 'rxjs';
import { EntityType } from '@shared/models/entity-type.models';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { share } from 'rxjs/operators';
import { parseHttpErrorMessage } from '@core/utils';
@Component({
selector: 'tb-entity-version-create',
@ -113,7 +114,7 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
},
type: VersionCreateRequestType.SINGLE_ENTITY
};
this.versionCreateResult$ = this.entitiesVersionControlService.saveEntitiesVersion(request).pipe(
this.versionCreateResult$ = this.entitiesVersionControlService.saveEntitiesVersion(request, {ignoreErrors: true}).pipe(
share()
);
this.cd.detectChanges();
@ -133,6 +134,13 @@ export class EntityVersionCreateComponent extends PageComponent implements OnIni
this.onClose(result, request.branch);
}
}
},
(error) => {
this.resultMessage = parseHttpErrorMessage(error, this.translate).message;
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
});
});
}

View File

@ -16,7 +16,7 @@
-->
<section [ngStyle]="entityDataInfo ? {minWidth: '400px'} : {}">
<section *ngIf="!errorMessage">
<section *ngIf="!versionLoadResult$">
<mat-toolbar *ngIf="entityDataInfo">
<h2>{{ 'version-control.restore-entity-from-version' | translate: {versionName} }}</h2>
<span fxFlex></span>
@ -55,15 +55,27 @@
</button>
</div>
</section>
<section *ngIf="errorMessage">
<div class="mat-title vc-result-message error" [innerHTML]="errorMessage"></div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.close' | translate }}
</button>
</div>
<section *ngIf="versionLoadResult$">
<section *ngIf="(versionLoadResult$ | async)?.done || errorMessage; else progress">
<section *ngIf="errorMessage">
<div class="mat-title vc-result-message error" [innerHTML]="errorMessage"></div>
<div fxLayoutAlign="end center" fxLayoutGap="8px">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.close' | translate }}
</button>
</div>
</section>
</section>
<ng-template #progress>
<section fxLayout="column" fxLayoutAlign="center center">
<div class="mat-title vc-result-message progress">
<span translate>version-control.restoring-entity-version</span>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
</div>
</section>
</ng-template>
</section>
</section>

View File

@ -14,12 +14,12 @@
/// limitations under the License.
///
import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, Sanitizer } from '@angular/core';
import { PageComponent } from '@shared/components/page.component';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
EntityDataInfo,
SingleEntityVersionLoadRequest,
SingleEntityVersionLoadRequest, VersionCreationResult,
VersionLoadRequestType,
VersionLoadResult
} from '@shared/models/vc.models';
@ -29,15 +29,17 @@ import { EntitiesVersionControlService } from '@core/http/entities-version-contr
import { EntityId } from '@shared/models/id/entity-id';
import { TranslateService } from '@ngx-translate/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { delay } from 'rxjs/operators';
import { SafeHtml } from '@angular/platform-browser';
import { delay, share } from 'rxjs/operators';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Observable, Subscription } from 'rxjs';
import { parseHttpErrorMessage } from '@core/utils';
@Component({
selector: 'tb-entity-version-restore',
templateUrl: './entity-version-restore.component.html',
styleUrls: ['./version-control.scss']
})
export class EntityVersionRestoreComponent extends PageComponent implements OnInit {
export class EntityVersionRestoreComponent extends PageComponent implements OnInit, OnDestroy {
@Input()
branch: string;
@ -63,10 +65,15 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
errorMessage: SafeHtml;
versionLoadResult$: Observable<VersionLoadResult>;
private versionLoadResultSubscription: Subscription;
constructor(protected store: Store<AppState>,
private entitiesVersionControlService: EntitiesVersionControlService,
private cd: ChangeDetectorRef,
private translate: TranslateService,
private sanitizer: DomSanitizer,
private fb: FormBuilder) {
super(store);
}
@ -86,6 +93,13 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
});
}
ngOnDestroy() {
super.ngOnDestroy();
if (this.versionLoadResultSubscription) {
this.versionLoadResultSubscription.unsubscribe();
}
}
cancel(): void {
if (this.onClose) {
this.onClose(null);
@ -104,18 +118,34 @@ export class EntityVersionRestoreComponent extends PageComponent implements OnIn
},
type: VersionLoadRequestType.SINGLE_ENTITY
};
this.entitiesVersionControlService.loadEntitiesVersion(request).subscribe((result) => {
if (result.error) {
this.errorMessage = this.entitiesVersionControlService.entityLoadErrorToMessage(result.error);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
} else {
if (this.onClose) {
this.onClose(result);
this.versionLoadResult$ = this.entitiesVersionControlService.loadEntitiesVersion(request, {ignoreErrors: true}).pipe(
share()
);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
this.versionLoadResultSubscription = this.versionLoadResult$.subscribe((result) => {
if (result.done) {
if (result.error) {
this.errorMessage = this.entitiesVersionControlService.entityLoadErrorToMessage(result.error);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
} else {
if (this.onClose) {
this.onClose(result);
}
}
}
},
(error) => {
this.errorMessage = this.sanitizer.bypassSecurityTrustHtml(parseHttpErrorMessage(error, this.translate).message);
this.cd.detectChanges();
if (this.popoverComponent) {
this.popoverComponent.updatePosition();
}
});
}
}

View File

@ -42,7 +42,7 @@ import {
import cssjs from '@core/css/css';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { hashCode, isDefined, isNumber } from '@core/utils';
import { hashCode, isDefined, isNumber, parseHttpErrorMessage } from '@core/utils';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import {
@ -520,7 +520,7 @@ class PersistentDatasource implements DataSource<PersistentRpcData> {
this.subscription.rpcErrorText = 'Request Timeout.';
} else {
this.subscription.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
const error = this.extractRejectionErrorText(rejection);
const error = parseHttpErrorMessage(rejection, this.translate);
if (error) {
this.subscription.rpcErrorText += '</br>';
this.subscription.rpcErrorText += error.message || '';
@ -534,40 +534,6 @@ class PersistentDatasource implements DataSource<PersistentRpcData> {
return rpcSubject.asObservable();
}
extractRejectionErrorText(rejection: HttpErrorResponse) {
let error = null;
if (rejection.error) {
error = rejection.error;
try {
error = rejection.error ? JSON.parse(rejection.error) : null;
} catch (e) {}
}
if (error && !error.message) {
error = this.prepareMessageFromData(error);
} else if (error && error.message) {
error = error.message;
}
return error;
}
prepareMessageFromData(data) {
if (typeof data === 'object' && data.constructor === ArrayBuffer) {
const msg = String.fromCharCode.apply(null, new Uint8Array(data));
try {
const msgObj = JSON.parse(msg);
if (msgObj.message) {
return msgObj.message;
} else {
return msg;
}
} catch (e) {
return msg;
}
} else {
return data;
}
}
isEmpty(): Observable<boolean> {
return this.persistentSubject.pipe(
map((requests) => !requests.length)

View File

@ -16,6 +16,7 @@
import { InjectionToken } from '@angular/core';
import { IModulesMap } from '@modules/common/modules-map.models';
import { EntityType } from '@shared/models/entity-type.models';
export const Constants = {
serverErrorCode: {
@ -38,6 +39,20 @@ export const Constants = {
}
};
export const serverErrorCodesTranslations = new Map<number, string>([
[Constants.serverErrorCode.general, 'server-error.general'],
[Constants.serverErrorCode.authentication, 'server-error.authentication'],
[Constants.serverErrorCode.jwtTokenExpired, 'server-error.jwt-token-expired'],
[Constants.serverErrorCode.tenantTrialExpired, 'server-error.tenant-trial-expired'],
[Constants.serverErrorCode.credentialsExpired, 'server-error.credentials-expired'],
[Constants.serverErrorCode.permissionDenied, 'server-error.permission-denied'],
[Constants.serverErrorCode.invalidArguments, 'server-error.invalid-arguments'],
[Constants.serverErrorCode.badRequestParams, 'server-error.bad-request-params'],
[Constants.serverErrorCode.itemNotFound, 'server-error.item-not-found'],
[Constants.serverErrorCode.tooManyRequests, 'server-error.too-many-requests'],
[Constants.serverErrorCode.tooManyUpdates, 'server-error.too-many-updates'],
]);
export const MediaBreakpoints = {
xs: 'screen and (max-width: 599px)',
sm: 'screen and (min-width: 600px) and (max-width: 959px)',

View File

@ -197,6 +197,7 @@ export interface EntityLoadError {
export interface VersionLoadResult {
result: Array<EntityTypeLoadResult>;
error: EntityLoadError;
done: boolean;
}
export interface AttributeExportData {

View File

@ -3000,6 +3000,19 @@
"retry-failed-and-timeout-hint": "Retry all failed and timed-out messages from processing pack"
}
},
"server-error": {
"general": "General server error",
"authentication": "Authentication error",
"jwt-token-expired": "JWT token expired",
"tenant-trial-expired": "Tenant trial expired",
"credentials-expired": "Credentials expired",
"permission-denied": "Permission denied",
"invalid-arguments": "Invalid arguments",
"bad-request-params": "Bad request params",
"item-not-found": "Item not found",
"too-many-requests": "Too many requests",
"too-many-updates": "Too many updates"
},
"tenant": {
"tenant": "Tenant",
"tenants": "Tenants",
@ -3310,6 +3323,7 @@
"nothing-to-commit": "No changes to commit",
"restore-version": "Restore version",
"restore-entity-from-version": "Restore entity from version '{{versionName}}'",
"restoring-entity-version": "Restoring entity version... Please wait",
"load-relations": "Load relations",
"load-attributes": "Load attributes",
"load-credentials": "Load credentials",
@ -3335,6 +3349,7 @@
"remove-other-entities": "Remove other entities",
"find-existing-entity-by-name": "Find existing entity by name",
"restore-entities-from-version": "Restore entities from version '{{versionName}}'",
"restoring-entities-from-version": "Restoring entities... Please wait",
"no-entities-restored": "No entities restored",
"created": "{{created}} created",
"updated": "{{updated}} updated",