Ajout d'un système de gestion des erreurs et alertes centralisé.
This commit is contained in:
parent
93929718d6
commit
67089013af
|
@ -1,4 +1,5 @@
|
|||
<cre-header></cre-header>
|
||||
<cre-global-alert-handler></cre-global-alert-handler>
|
||||
<div>
|
||||
<div class="dark-background"></div>
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<ng-container *ngFor="let alert of alertBuffer">
|
||||
<div
|
||||
class="alert"
|
||||
[class.alert-success]="alert.alert.type === SUCCESS_ALERT_TYPE"
|
||||
[class.alert-warning]="alert.alert.type === WARNING_ALERT_TYPE"
|
||||
[class.alert-danger]="alert.alert.type === ERROR_ALERT_TYPE">
|
||||
{{alert.alert.message}}
|
||||
</div>
|
||||
</ng-container>
|
|
@ -0,0 +1,17 @@
|
|||
import {Component} from '@angular/core'
|
||||
import {AlertHandlerComponent, AlertService, AlertType} from '../../service/alert.service'
|
||||
|
||||
@Component({
|
||||
selector: 'cre-global-alert-handler',
|
||||
templateUrl: './global-alert-handler.component.html',
|
||||
styleUrls: ['./global-alert-handler.component.sass']
|
||||
})
|
||||
export class GlobalAlertHandlerComponent extends AlertHandlerComponent {
|
||||
readonly SUCCESS_ALERT_TYPE = AlertType.Success
|
||||
readonly WARNING_ALERT_TYPE = AlertType.Warning
|
||||
readonly ERROR_ALERT_TYPE = AlertType.Error
|
||||
|
||||
constructor(alertService: AlertService) {
|
||||
super(alertService)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
import {Injectable} from '@angular/core'
|
||||
import {interval} from 'rxjs'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AlertService {
|
||||
private alertQueue: Alert[] = []
|
||||
private activeHandler: AlertHandlerComponent
|
||||
|
||||
constructor() {
|
||||
// interval(500).subscribe({next: i => this.pushError('Test error ' + i)})
|
||||
}
|
||||
|
||||
pushSuccess(message: String) {
|
||||
this.enqueue(AlertType.Success, message)
|
||||
}
|
||||
|
||||
pushWarning(message: String) {
|
||||
this.enqueue(AlertType.Warning, message)
|
||||
}
|
||||
|
||||
pushError(message: String) {
|
||||
this.enqueue(AlertType.Error, message)
|
||||
}
|
||||
|
||||
set activeAlertHandlerComponent(handler: AlertHandlerComponent) {
|
||||
this.activeHandler = handler
|
||||
}
|
||||
|
||||
private enqueue(type: AlertType, message: String) {
|
||||
const alert = {type, message}
|
||||
if (this.activeHandler && !this.activeHandler.isBufferFull) {
|
||||
if (this.alertQueue.length == 0) {
|
||||
this.activeHandler.pushAlert(alert)
|
||||
} else {
|
||||
this.activeHandler.pushAlert(this.dequeue())
|
||||
this.alertQueue.unshift(alert)
|
||||
}
|
||||
} else {
|
||||
this.alertQueue.unshift(alert)
|
||||
}
|
||||
}
|
||||
|
||||
private dequeue(): Alert {
|
||||
return this.alertQueue.pop()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An alert handler component is a component that will show the alerts pushed by the alert system to the user.
|
||||
*/
|
||||
export abstract class AlertHandlerComponent {
|
||||
protected static readonly DEFAULT_ALERT_BUFFER_SIZE = 3
|
||||
protected static readonly DEFAULT_ALERT_DURATION = 5
|
||||
|
||||
alertBuffer = new Array<{ alert: Alert, time: number }>()
|
||||
protected alertBufferSize: number = AlertHandlerComponent.DEFAULT_ALERT_BUFFER_SIZE
|
||||
protected alertDuration: number = AlertHandlerComponent.DEFAULT_ALERT_DURATION
|
||||
protected alertDurationCounter = 0
|
||||
|
||||
protected constructor(
|
||||
protected alertService: AlertService
|
||||
) {
|
||||
alertService.activeAlertHandlerComponent = this
|
||||
|
||||
interval(1000).subscribe({
|
||||
next: () => {
|
||||
this.alertDurationCounter++
|
||||
if (this.alertBuffer.length > 1) {
|
||||
this.alertBuffer
|
||||
.filter(a => a.time + this.alertDuration < this.alertDurationCounter)
|
||||
.map(a => this.alertBuffer.indexOf(a))
|
||||
.forEach(i => this.alertBuffer.splice(i, 1))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pushAlert(alert: Alert) {
|
||||
this.alertBuffer.unshift({alert: alert, time: this.alertDurationCounter})
|
||||
}
|
||||
|
||||
get isBufferFull(): boolean {
|
||||
return this.alertBuffer.length >= this.alertBufferSize
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An alert is a message that will be shown to the user.
|
||||
*
|
||||
* An alert can be a success alert, a warning alert of an error alert.
|
||||
*/
|
||||
class Alert {
|
||||
constructor(
|
||||
public type: AlertType,
|
||||
public message: String
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
export enum AlertType {
|
||||
Success,
|
||||
Warning,
|
||||
Error
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
import {Injectable} from '@angular/core'
|
||||
import {AlertService} from './alert.service'
|
||||
import {AppState} from '../app-state'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ErrorService {
|
||||
private static readonly UNKNOWN_ERROR_MESSAGE = 'Une erreur inconnue est survenue'
|
||||
private defaultHandledErrorModels: ErrorModel[] = [{
|
||||
filter: error => error.status === 0 && error.statusText === 'Unknown Error' || error.status === 502,
|
||||
consumer: () => this.appState.isServerOnline = false
|
||||
}, {
|
||||
filter: error => error.status === 400,
|
||||
consumer: error => console.error(error),
|
||||
messageProducer: () => 'Certaines informations dans la requête étaient invalides'
|
||||
}, {
|
||||
filter: error => error.status === 401,
|
||||
messageProducer: () => 'Vous devez être connecté pour effectuer cette action'
|
||||
}, {
|
||||
filter: error => error.status === 403,
|
||||
messageProducer: () => 'Vous n\'avez pas la permission d\'effectuer cette action'
|
||||
}, {
|
||||
filter: error => error.status === 404,
|
||||
messageProducer: () => 'La resource demandée n\'a pas été trouvée'
|
||||
}, {
|
||||
filter: error => error.status === 500,
|
||||
messageProducer: () => ErrorService.UNKNOWN_ERROR_MESSAGE
|
||||
}]
|
||||
|
||||
private activeHandler: ErrorHandler
|
||||
|
||||
constructor(
|
||||
private alertService: AlertService,
|
||||
private appState: AppState
|
||||
) {
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
if (!this.activeHandler) {
|
||||
console.error('An error occurred but no handler was set')
|
||||
}
|
||||
|
||||
let matchingModels = this.activeHandler.handledErrorModels.filter(m => m.filter(error)) // Find error models whose filter matches the current error
|
||||
|
||||
if (!matchingModels || matchingModels.length == 0) { // If none are found, search in defaults handlers
|
||||
matchingModels = this.defaultHandledErrorModels.filter(m => m.filter(error))
|
||||
}
|
||||
|
||||
if (!matchingModels || matchingModels.length == 0) { // If still none are found, handle as an unknown error
|
||||
this.consumeUnknownError(error)
|
||||
return
|
||||
}
|
||||
|
||||
matchingModels.forEach(m => {
|
||||
if (m.consumer || m.messageProducer) {
|
||||
if (m.consumer) {
|
||||
m.consumer(error)
|
||||
}
|
||||
if (m.messageProducer) {
|
||||
this.alertService.pushError(m.messageProducer(error))
|
||||
}
|
||||
} else {
|
||||
console.error('An error model has no consumer or message')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
consumeUnknownError(error: any) {
|
||||
console.error(error)
|
||||
this.alertService.pushError(ErrorService.UNKNOWN_ERROR_MESSAGE)
|
||||
}
|
||||
|
||||
set activeErrorHandler(handler: ErrorHandler) {
|
||||
this.activeHandler = handler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error handler, defining models of errors that this type will handle
|
||||
*/
|
||||
export abstract class ErrorHandler {
|
||||
abstract handledErrorModels: ErrorModel[]
|
||||
|
||||
protected constructor(
|
||||
protected errorService: ErrorService
|
||||
) {
|
||||
this.errorService.activeErrorHandler = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An error model define how errors matching its filter will be handled.
|
||||
*
|
||||
* The consumer will consume matching errors when they occurs.
|
||||
* The message producer returns a string that will be pushed to the alert system.
|
||||
*
|
||||
* To work correctly a model must define at least one handler (consumer or message producer).
|
||||
*/
|
||||
export class ErrorModel {
|
||||
constructor(
|
||||
public filter: (error: any) => Boolean,
|
||||
public consumer?: (error: any) => void,
|
||||
public messageProducer?: (error: any) => String
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -27,10 +27,11 @@ import {MatSelectModule} from "@angular/material/select";
|
|||
import {MatOptionModule} from "@angular/material/core";
|
||||
import {MaterialFileInputModule} from "ngx-material-file-input";
|
||||
import { FileButtonComponent } from './file-button/file-button.component';
|
||||
import { GlobalAlertHandlerComponent } from './components/global-alert-handler/global-alert-handler.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent],
|
||||
declarations: [HeaderComponent, EmployeeInfoComponent, LabeledIconComponent, ConfirmBoxComponent, PermissionsListComponent, PermissionsFieldComponent, NavComponent, EntityListComponent, EntityAddComponent, EntityEditComponent, FileButtonComponent, GlobalAlertHandlerComponent],
|
||||
exports: [
|
||||
CommonModule,
|
||||
HttpClientModule,
|
||||
|
@ -55,7 +56,8 @@ import { FileButtonComponent } from './file-button/file-button.component';
|
|||
EntityListComponent,
|
||||
EntityAddComponent,
|
||||
EntityEditComponent,
|
||||
FileButtonComponent
|
||||
FileButtonComponent,
|
||||
GlobalAlertHandlerComponent
|
||||
],
|
||||
imports: [
|
||||
MatTabsModule,
|
||||
|
|
Loading…
Reference in New Issue