fix(docs-infra): notify `ErrorHandler` of `UnrecoverableState` errors (#42941)

With this commit, the `ErrorHandler` is notified of ServiceWorker
`UnrecoverableState` errors. The main purpose of this change is
gathering info about the occurrence (and frequency) of such errors in
Google analytics.

PR Close #42941
This commit is contained in:
George Kalpakas 2021-07-22 18:42:19 +03:00 committed by Dylan Hunn
parent b1082b7ad8
commit 85e93c3833
2 changed files with 35 additions and 6 deletions

View File

@ -1,4 +1,4 @@
import { ApplicationRef, Injector } from '@angular/core'; import { ApplicationRef, ErrorHandler, Injector } from '@angular/core';
import { discardPeriodicTasks, fakeAsync, tick } from '@angular/core/testing'; import { discardPeriodicTasks, fakeAsync, tick } from '@angular/core/testing';
import { SwUpdate } from '@angular/service-worker'; import { SwUpdate } from '@angular/service-worker';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -13,6 +13,7 @@ import { SwUpdatesService } from './sw-updates.service';
describe('SwUpdatesService', () => { describe('SwUpdatesService', () => {
let injector: Injector; let injector: Injector;
let appRef: MockApplicationRef; let appRef: MockApplicationRef;
let errorHandler: ErrorHandler;
let location: MockLocationService; let location: MockLocationService;
let service: SwUpdatesService; let service: SwUpdatesService;
let swu: MockSwUpdate; let swu: MockSwUpdate;
@ -27,13 +28,15 @@ describe('SwUpdatesService', () => {
const setup = (isSwUpdateEnabled: boolean) => { const setup = (isSwUpdateEnabled: boolean) => {
injector = Injector.create({providers: [ injector = Injector.create({providers: [
{ provide: ApplicationRef, useClass: MockApplicationRef, deps: [] }, { provide: ApplicationRef, useClass: MockApplicationRef, deps: [] },
{ provide: ErrorHandler, useValue: {handleError: jasmine.createSpy('handlerError')} },
{ provide: LocationService, useFactory: () => new MockLocationService(''), deps: [] }, { provide: LocationService, useFactory: () => new MockLocationService(''), deps: [] },
{ provide: Logger, useClass: MockLogger, deps: [] }, { provide: Logger, useClass: MockLogger, deps: [] },
{ provide: SwUpdate, useFactory: () => new MockSwUpdate(isSwUpdateEnabled), deps: [] }, { provide: SwUpdate, useFactory: () => new MockSwUpdate(isSwUpdateEnabled), deps: [] },
{ provide: SwUpdatesService, deps: [ApplicationRef, LocationService, Logger, SwUpdate] } { provide: SwUpdatesService, deps: [ApplicationRef, ErrorHandler, LocationService, Logger, SwUpdate] }
]}); ]});
appRef = injector.get(ApplicationRef) as unknown as MockApplicationRef; appRef = injector.get(ApplicationRef) as unknown as MockApplicationRef;
errorHandler = injector.get(ErrorHandler);
location = injector.get(LocationService) as unknown as MockLocationService; location = injector.get(LocationService) as unknown as MockLocationService;
service = injector.get(SwUpdatesService); service = injector.get(SwUpdatesService);
swu = injector.get(SwUpdate) as unknown as MockSwUpdate; swu = injector.get(SwUpdate) as unknown as MockSwUpdate;
@ -129,6 +132,24 @@ describe('SwUpdatesService', () => {
expect(location.reloadPage).toHaveBeenCalledTimes(2); expect(location.reloadPage).toHaveBeenCalledTimes(2);
})); }));
it('should notify the `ErrorHandler` when an unrecoverable state has been detected', run(() => {
expect(errorHandler.handleError).not.toHaveBeenCalled();
swu.$$unrecoverableSubj.next({reason: 'Something bad happened'});
expect(errorHandler.handleError).toHaveBeenCalledBefore(location.reloadPage);
expect(errorHandler.handleError)
.toHaveBeenCalledWith('Unrecoverable state: Something bad happened');
(errorHandler.handleError as jasmine.Spy).calls.reset();
location.reloadPage.calls.reset();
swu.$$unrecoverableSubj.next({reason: 'Something worse happened'});
expect(errorHandler.handleError).toHaveBeenCalledBefore(location.reloadPage);
expect(errorHandler.handleError)
.toHaveBeenCalledWith('Unrecoverable state: Something worse happened');
}));
describe('when `SwUpdate` is not enabled', () => { describe('when `SwUpdate` is not enabled', () => {
const runDeactivated = (specFn: VoidFunction) => run(specFn, false); const runDeactivated = (specFn: VoidFunction) => run(specFn, false);
@ -165,6 +186,7 @@ describe('SwUpdatesService', () => {
swu.$$unrecoverableSubj.next({reason: 'Something bad happened'}); swu.$$unrecoverableSubj.next({reason: 'Something bad happened'});
swu.$$unrecoverableSubj.next({reason: 'Something worse happened'}); swu.$$unrecoverableSubj.next({reason: 'Something worse happened'});
expect(errorHandler.handleError).not.toHaveBeenCalled();
expect(location.reloadPage).not.toHaveBeenCalled(); expect(location.reloadPage).not.toHaveBeenCalled();
})); }));
}); });
@ -221,12 +243,15 @@ describe('SwUpdatesService', () => {
it('should stop requesting page reloads when unrecoverable states are detected', run(() => { it('should stop requesting page reloads when unrecoverable states are detected', run(() => {
swu.$$unrecoverableSubj.next({reason: 'Something bad happened'}); swu.$$unrecoverableSubj.next({reason: 'Something bad happened'});
expect(errorHandler.handleError).toHaveBeenCalledTimes(1);
expect(location.reloadPage).toHaveBeenCalledTimes(1); expect(location.reloadPage).toHaveBeenCalledTimes(1);
service.ngOnDestroy(); service.ngOnDestroy();
(errorHandler.handleError as jasmine.Spy).calls.reset();
location.reloadPage.calls.reset(); location.reloadPage.calls.reset();
swu.$$unrecoverableSubj.next({reason: 'Something worse happened'}); swu.$$unrecoverableSubj.next({reason: 'Something worse happened'});
expect(errorHandler.handleError).not.toHaveBeenCalled();
expect(location.reloadPage).not.toHaveBeenCalled(); expect(location.reloadPage).not.toHaveBeenCalled();
})); }));
}); });

View File

@ -1,4 +1,4 @@
import { ApplicationRef, Injectable, OnDestroy } from '@angular/core'; import { ApplicationRef, ErrorHandler, Injectable, OnDestroy } from '@angular/core';
import { SwUpdate } from '@angular/service-worker'; import { SwUpdate } from '@angular/service-worker';
import { concat, interval, Subject } from 'rxjs'; import { concat, interval, Subject } from 'rxjs';
import { first, takeUntil, tap } from 'rxjs/operators'; import { first, takeUntil, tap } from 'rxjs/operators';
@ -21,8 +21,8 @@ export class SwUpdatesService implements OnDestroy {
private onDestroy = new Subject<void>(); private onDestroy = new Subject<void>();
constructor( constructor(
appRef: ApplicationRef, location: LocationService, private logger: Logger, appRef: ApplicationRef, errorHandler: ErrorHandler, location: LocationService,
private swu: SwUpdate) { private logger: Logger, private swu: SwUpdate) {
if (!swu.isEnabled) { if (!swu.isEnabled) {
return; return;
} }
@ -55,7 +55,11 @@ export class SwUpdatesService implements OnDestroy {
// Request an immediate page reload once an unrecoverable state has been detected. // Request an immediate page reload once an unrecoverable state has been detected.
this.swu.unrecoverable this.swu.unrecoverable
.pipe( .pipe(
tap(evt => this.log(`Unrecoverable state: ${evt.reason}\nReloading...`)), tap(evt => {
const errorMsg = `Unrecoverable state: ${evt.reason}`;
errorHandler.handleError(errorMsg);
this.log(`${errorMsg}\nReloading...`);
}),
takeUntil(this.onDestroy), takeUntil(this.onDestroy),
) )
.subscribe(() => location.reloadPage()); .subscribe(() => location.reloadPage());