diff --git a/admin/tabs/monitor/editor/MonitorEditorDialog.ts b/admin/tabs/monitor/editor/MonitorEditorDialog.ts index 575ba42c0..bb32cad6e 100644 --- a/admin/tabs/monitor/editor/MonitorEditorDialog.ts +++ b/admin/tabs/monitor/editor/MonitorEditorDialog.ts @@ -28,7 +28,13 @@ export const monitorEditorDialog = hoistCmp.factory(({model}) = canOutsideClickClose: false, onClose: () => (model.showEditorDialog = false), item: panel({ - item: restGrid({modelConfig: {...modelSpec, readonly: AppModel.readonly}}), + item: restGrid({ + modelConfig: { + ...modelSpec, + readonly: AppModel.readonly, + showRefreshButton: true + } + }), bbar: [ filler(), button({ diff --git a/core/HoistService.ts b/core/HoistService.ts index d1800e915..88cd1ecb9 100644 --- a/core/HoistService.ts +++ b/core/HoistService.ts @@ -4,7 +4,7 @@ * * Copyright © 2024 Extremely Heavy Industries Inc. */ -import {HoistBase, managed, LoadSupport, LoadSpec, Loadable, PlainObject} from './'; +import {XH, HoistBase, managed, LoadSupport, LoadSpec, Loadable, PlainObject} from './'; /** * Core superclass for Services in Hoist. Services are special classes used in both Hoist and @@ -83,10 +83,19 @@ export class HoistService extends HoistBase implements Loadable { async autoRefreshAsync(meta?: PlainObject) { return this.loadSupport?.autoRefreshAsync(meta); } - async doLoadAsync(loadSpec: LoadSpec) {} async loadAsync(loadSpec?: LoadSpec | Partial) { return this.loadSupport?.loadAsync(loadSpec); } + + //-------------- + // For override + //-------------- + async doLoadAsync(loadSpec: LoadSpec) {} + async onLoadException(e: unknown, loadSpec: LoadSpec) { + if (!e['isRoutine']) { + XH.handleException(e, {showAlert: false}); + } + } } export interface HoistServiceClass { diff --git a/core/load/LoadSpec.ts b/core/load/LoadSpec.ts index e4e4efa02..806aa40bd 100644 --- a/core/load/LoadSpec.ts +++ b/core/load/LoadSpec.ts @@ -62,9 +62,9 @@ export class LoadSpec { return this !== this.owner.lastRequested; } - /** True if a more recent request to load this object's owner has *successfully completed*. */ + /** True if a more recent request to load this object's owner has completed*. */ get isObsolete(): boolean { - return this.owner.lastSucceeded?.loadNumber > this.loadNumber; + return this.owner.lastCompleted?.loadNumber > this.loadNumber; } /** Display type of refresh for troubleshooting and logging. */ diff --git a/core/load/LoadSupport.ts b/core/load/LoadSupport.ts index 58036a816..5f42ea966 100644 --- a/core/load/LoadSupport.ts +++ b/core/load/LoadSupport.ts @@ -14,7 +14,7 @@ import { } from '../'; import {LoadSpec, Loadable} from './'; import {makeObservable, observable, runInAction} from '@xh/hoist/mobx'; -import {logDebug, logError, throwIf} from '@xh/hoist/utils/js'; +import {logDebug, throwIf} from '@xh/hoist/utils/js'; import {isPlainObject, pull} from 'lodash'; /** @@ -29,7 +29,7 @@ import {isPlainObject, pull} from 'lodash'; */ export class LoadSupport extends HoistBase implements Loadable { lastRequested: LoadSpec = null; - lastSucceeded: LoadSpec = null; + lastCompleted: LoadSpec = null; @managed loadModel: TaskObserver = TaskObserver.trackLast(); @@ -70,6 +70,10 @@ export class LoadSupport extends HoistBase implements Loadable { return this.loadAsync({meta, isAutoRefresh: true}); } + onLoadException(exception: unknown, loadSpec: LoadSpec) { + this.target.onLoadException(exception, loadSpec); + } + async doLoadAsync(loadSpec: LoadSpec) { let {target, loadModel} = this; @@ -90,31 +94,27 @@ export class LoadSupport extends HoistBase implements Loadable { .linkTo(loadModel) .catch(e => { exception = e; + target.onLoadException(exception, loadSpec); throw e; }) .finally(() => { - runInAction(() => { - this.lastLoadCompleted = new Date(); - this.lastLoadException = exception; - }); - - if (!exception) { - this.lastSucceeded = loadSpec; + const now = new Date(); + + // If not obsolete, update state. + if (!loadSpec.isObsolete) { + runInAction(() => { + this.lastCompleted = loadSpec; + this.lastLoadCompleted = now; + this.lastLoadException = exception; + }); } - if (target instanceof RefreshContextModel) return; - - const elapsed = this.lastLoadCompleted.getTime() - this.lastLoadRequested.getTime(), - status = exception ? 'failed' : null, - msg = pull([loadSpec.typeDisplay, status, `${elapsed}ms`, exception], null); + // Basic client-side debug logging + if (!(target instanceof RefreshContextModel)) { + const elapsed = now.getTime() - loadSpec.dateCreated.getTime(), + status = exception ? 'failed' : null, + msg = pull([loadSpec.typeDisplay, status, `${elapsed}ms`, exception], null); - if (exception) { - if (exception.isRoutine) { - logDebug(msg, target); - } else { - logError(msg, target); - } - } else { logDebug(msg, target); } }); diff --git a/core/load/Loadable.ts b/core/load/Loadable.ts index 9c4ecebc3..3d82999a4 100644 --- a/core/load/Loadable.ts +++ b/core/load/Loadable.ts @@ -59,4 +59,6 @@ export interface Loadable { * `loadSupport` or when making calls to {@link FetchService} APIs. */ doLoadAsync(loadSpec: LoadSpec): Promise; + + onLoadException(e: unknown, loadSpec: LoadSpec): void; } diff --git a/core/model/HoistModel.ts b/core/model/HoistModel.ts index 1e4fdb4d5..45e70ce1c 100644 --- a/core/model/HoistModel.ts +++ b/core/model/HoistModel.ts @@ -7,7 +7,7 @@ import {action, makeObservable, observable} from '@xh/hoist/mobx'; import {warnIf} from '@xh/hoist/utils/js'; import {forOwn, has, isFunction} from 'lodash'; -import {DefaultHoistProps, HoistBase, LoadSpecConfig, managed, PlainObject} from '../'; +import {DefaultHoistProps, HoistBase, LoadSpecConfig, managed, PlainObject, XH} from '../'; import {instanceManager} from '../impl/InstanceManager'; import {Loadable, LoadSpec, LoadSupport} from '../load'; import {ModelSelector} from './'; @@ -115,11 +115,20 @@ export abstract class HoistModel extends HoistBase implements Loadable { async autoRefreshAsync(meta?: PlainObject) { return this.loadSupport?.autoRefreshAsync(meta); } - async doLoadAsync(loadSpec: LoadSpec) {} async loadAsync(loadSpec?: LoadSpecConfig) { return this.loadSupport?.loadAsync(loadSpec); } + //-------------- + // For override + //-------------- + async doLoadAsync(loadSpec: LoadSpec) {} + async onLoadException(e: unknown, loadSpec: LoadSpec) { + if (!e['isRoutine']) { + XH.handleException(e, {showAlert: false}); + } + } + //--------------------------- // Linked model support //--------------------------- diff --git a/data/UrlStore.ts b/data/UrlStore.ts index 386f9632f..ca4b6a258 100644 --- a/data/UrlStore.ts +++ b/data/UrlStore.ts @@ -62,4 +62,6 @@ export class UrlStore extends Store implements Loadable { if (dataRoot) data = data[dataRoot]; this.loadData(data); } + + onLoadException(e: unknown, loadSpec: LoadSpec) {} }