add server error interceptor and update full page error
This commit is contained in:
parent
6c0f123bd9
commit
90287baf62
1
src/assets/icons/offline.svg
Normal file
1
src/assets/icons/offline.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M21 11l2-2c-3.73-3.73-8.87-5.15-13.7-4.31l2.58 2.58c3.3-.02 6.61 1.22 9.12 3.73zm-2 2c-1.08-1.08-2.36-1.85-3.72-2.33l3.02 3.02.7-.69zM9 17l3 3 3-3c-1.65-1.66-4.34-1.66-6 0zM3.41 1.64L2 3.05 5.05 6.1C3.59 6.83 2.22 7.79 1 9l2 2c1.23-1.23 2.65-2.16 4.17-2.78l2.24 2.24C7.79 10.89 6.27 11.74 5 13l2 2c1.35-1.35 3.11-2.04 4.89-2.06l7.08 7.08 1.41-1.41L3.41 1.64z"/></svg>
|
||||||
|
After Width: | Height: | Size: 517 B |
@ -1,17 +1,14 @@
|
|||||||
@import 'variables';
|
@use 'variables';
|
||||||
|
|
||||||
.full-page-section,
|
.full-page-section,
|
||||||
.full-page-content {
|
.full-page-content {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
inset: 0;
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-page-section {
|
.full-page-section {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
background: $white;
|
background: variables.$white;
|
||||||
z-index: 900;
|
z-index: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,29 @@
|
|||||||
<ng-container *ngIf="errorService.error$ | async as error">
|
<ng-container *ngIf="errorService.error$ | async as error">
|
||||||
|
<ng-container *ngIf="error.status === 0; else serverError">
|
||||||
|
<div class="offline-box flex-align-items-center" [@animateOpenClose]="'open'">
|
||||||
|
<mat-icon svgIcon="iqser:offline"></mat-icon>
|
||||||
|
|
||||||
|
<div class="heading-l ml-14" [translate]="'error.offline'"></div>
|
||||||
|
|
||||||
|
<iqser-circle-button
|
||||||
|
(action)="errorService.set(undefined)"
|
||||||
|
[tooltip]="'error.close' | translate"
|
||||||
|
icon="iqser:close"
|
||||||
|
tooltipPosition="below"
|
||||||
|
></iqser-circle-button>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #serverError>
|
||||||
<section class="full-page-section"></section>
|
<section class="full-page-section"></section>
|
||||||
<section class="full-page-content">
|
|
||||||
|
<section class="full-page-content flex-align-items-center">
|
||||||
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
<mat-icon svgIcon="iqser:failure"></mat-icon>
|
||||||
<div class="heading-l mt-24" translate="error.title"></div>
|
|
||||||
|
<div class="heading-l mt-24" [translate]="'error.title'"></div>
|
||||||
|
|
||||||
<div class="mt-16 error">{{ error.message }}</div>
|
<div class="mt-16 error">{{ error.message }}</div>
|
||||||
|
|
||||||
<iqser-icon-button
|
<iqser-icon-button
|
||||||
(action)="reload()"
|
(action)="reload()"
|
||||||
[label]="'error.reload' | translate"
|
[label]="'error.reload' | translate"
|
||||||
@ -12,4 +32,5 @@
|
|||||||
icon="iqser:refresh"
|
icon="iqser:refresh"
|
||||||
></iqser-icon-button>
|
></iqser-icon-button>
|
||||||
</section>
|
</section>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -1,13 +1,32 @@
|
|||||||
@import '../../../assets/styles/variables';
|
@use '../../../assets/styles/variables';
|
||||||
|
|
||||||
|
.offline-box {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
height: 40px;
|
||||||
|
width: 300px;
|
||||||
|
background: variables.$white;
|
||||||
|
border: 1px solid variables.$separator;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
|
||||||
|
> mat-icon {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
> iqser-circle-button {
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.full-page-section {
|
.full-page-section {
|
||||||
opacity: 0.95;
|
opacity: 0.95;
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-page-content {
|
.full-page-content {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
> mat-icon {
|
> mat-icon {
|
||||||
@ -16,12 +35,12 @@
|
|||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading-l {
|
.error {
|
||||||
color: $grey-7;
|
color: variables.$primary;
|
||||||
font-weight: initial;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error {
|
:is(.offline-box, .full-page-content) .heading-l {
|
||||||
color: $red-1;
|
color: #9398a0;
|
||||||
}
|
font-weight: initial;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,19 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { IconButtonTypes } from '../../buttons';
|
import { IconButtonTypes } from '../../buttons';
|
||||||
import { ErrorService } from '../error.service';
|
import { ErrorService } from '../error.service';
|
||||||
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-full-page-error',
|
selector: 'iqser-full-page-error',
|
||||||
templateUrl: './full-page-error.component.html',
|
templateUrl: './full-page-error.component.html',
|
||||||
styleUrls: ['./full-page-error.component.scss'],
|
styleUrls: ['./full-page-error.component.scss'],
|
||||||
|
animations: [
|
||||||
|
trigger('animateOpenClose', [
|
||||||
|
state('open', style({ bottom: '20px' })),
|
||||||
|
state('void', style({ bottom: '-70px' })),
|
||||||
|
transition('* => open, open => void', animate('1s ease-in-out'))
|
||||||
|
])
|
||||||
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class FullPageErrorComponent {
|
export class FullPageErrorComponent {
|
||||||
|
|||||||
@ -1,2 +1,4 @@
|
|||||||
export * from './error.service';
|
export * from './error.service';
|
||||||
|
export * from './max-retries.token';
|
||||||
|
export * from './server-error-interceptor';
|
||||||
export * from './full-page-error/full-page-error.component';
|
export * from './full-page-error/full-page-error.component';
|
||||||
|
|||||||
3
src/lib/error/max-retries.token.ts
Normal file
3
src/lib/error/max-retries.token.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const MAX_RETRIES_ON_SERVER_ERROR = new InjectionToken<number>('Number of retries before giving up');
|
||||||
52
src/lib/error/server-error-interceptor.ts
Normal file
52
src/lib/error/server-error-interceptor.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||||
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
|
import { ErrorService } from '@iqser/common-ui';
|
||||||
|
import { MonoTypeOperatorFunction, Observable, throwError, timer } from 'rxjs';
|
||||||
|
import { catchError, mergeMap, retryWhen, tap } from 'rxjs/operators';
|
||||||
|
import { MAX_RETRIES_ON_SERVER_ERROR } from './max-retries.token';
|
||||||
|
|
||||||
|
function updateSeconds(seconds: number) {
|
||||||
|
if (seconds === 0 || seconds === 1) {
|
||||||
|
return seconds + 1;
|
||||||
|
} else {
|
||||||
|
return seconds * seconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function backoffOnServerError(maxRetries = 3): MonoTypeOperatorFunction<HttpEvent<unknown>> {
|
||||||
|
let seconds = 0;
|
||||||
|
return retryWhen(attempts =>
|
||||||
|
attempts.pipe(
|
||||||
|
tap(() => (seconds = updateSeconds(seconds))),
|
||||||
|
mergeMap((error: HttpErrorResponse, index) => {
|
||||||
|
if ((error.status <= 500 && error.status !== 0) || index === maxRetries) {
|
||||||
|
return throwError(error);
|
||||||
|
} else {
|
||||||
|
console.error('An error occurred: ', error);
|
||||||
|
console.error(`Retrying in ${seconds} seconds...`);
|
||||||
|
return timer(seconds * 1000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ServerErrorInterceptor implements HttpInterceptor {
|
||||||
|
constructor(
|
||||||
|
private readonly _errorService: ErrorService,
|
||||||
|
@Optional() @Inject(MAX_RETRIES_ON_SERVER_ERROR) private readonly _maxRetries: number
|
||||||
|
) {}
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
|
return next.handle(req).pipe(
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
if (error.status >= 500 || error.status === 0) {
|
||||||
|
this._errorService.set(error);
|
||||||
|
}
|
||||||
|
return throwError(error);
|
||||||
|
}),
|
||||||
|
backoffOnServerError(this._maxRetries || 3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ export class IqserIconsModule {
|
|||||||
'edit',
|
'edit',
|
||||||
'failure',
|
'failure',
|
||||||
'help-outline',
|
'help-outline',
|
||||||
|
'offline',
|
||||||
'refresh',
|
'refresh',
|
||||||
'search',
|
'search',
|
||||||
'sort-asc',
|
'sort-asc',
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export class SyncWidthDirective implements OnDestroy {
|
|||||||
private readonly _interval: number;
|
private readonly _interval: number;
|
||||||
|
|
||||||
constructor(private readonly _elementRef: ElementRef) {
|
constructor(private readonly _elementRef: ElementRef) {
|
||||||
this._interval = setInterval(() => {
|
this._interval = window.setInterval(() => {
|
||||||
this._matchWidth();
|
this._matchWidth();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export function Debounce(delay = 300): MethodDecorator {
|
|||||||
|
|
||||||
descriptorCopy.value = function _new(...args: unknown[]) {
|
descriptorCopy.value = function _new(...args: unknown[]) {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
|
timeout = window.setTimeout(() => (descriptor.value as (...params: unknown[]) => unknown).apply(this, args), delay);
|
||||||
};
|
};
|
||||||
|
|
||||||
return descriptorCopy;
|
return descriptorCopy;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user