From 9187a37f929f10c99e31b7ccfff0ab89e99645e1 Mon Sep 17 00:00:00 2001 From: "Maarten A. Breddels" Date: Mon, 3 Aug 2020 15:38:06 +0200 Subject: [PATCH] feat: when a widget fails, show a fallback error widget --- js/src/errorwidget.js | 32 ++++++++++++++++++++++++++++++++ js/src/manager.js | 43 +++++++++++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 js/src/errorwidget.js diff --git a/js/src/errorwidget.js b/js/src/errorwidget.js new file mode 100644 index 000000000..7e7bf2141 --- /dev/null +++ b/js/src/errorwidget.js @@ -0,0 +1,32 @@ +import * as widgets from "@jupyter-widgets/base"; + +// create a Widget Model that captures an error object +export +function createErrorWidget(error) { + class ErrorWidget extends widgets.DOMWidgetModel { + constructor(attributes, options) { + attributes = {...attributes, + _view_name: 'ErrorWidgetView', + _view_module: 'voila-errorwidget', + _model_module_version: '1.0.0', + _view_module_version: '1.0.0', + failed_module: attributes._view_module, + failed_model_name: attributes._model_name, + error: error} + super(attributes, options); + console.log(attributes); + } + } + return ErrorWidget; +} + +export +class ErrorWidgetView extends widgets.DOMWidgetView { + render() { + console.log('render'); + const module = this.model.get('failed_module'); + const name = this.model.get('failed_model_name'); + const error = String(this.model.get('error').stack); + this.el.innerHTML = `Failed to load widget '${name}' from module '${module}', error:
${error}
`; + } +} \ No newline at end of file diff --git a/js/src/manager.js b/js/src/manager.js index 3f927d4dc..d5b66b532 100644 --- a/js/src/manager.js +++ b/js/src/manager.js @@ -31,6 +31,7 @@ import { MessageLoop } from '@phosphor/messaging'; import { requireLoader } from './loader'; import { batchRateMap } from './utils'; +import * as errorwidget from './errorwidget'; if (typeof window !== "undefined" && typeof window.define !== "undefined") { window.define("@jupyter-widgets/base", base); @@ -49,6 +50,8 @@ if (typeof window !== "undefined" && typeof window.define !== "undefined") { window.define("@phosphor/algorithm", PhosphorAlgorithm); window.define("@phosphor/commands", PhosphorCommands); window.define("@phosphor/domutils", PhosphorDomutils); + + window.define("voila-errorwidget", errorwidget); } const WIDGET_MIMETYPE = 'application/vnd.jupyter.widget-view+json'; @@ -109,20 +112,35 @@ export class WidgetManager extends JupyterLabManager { if ( moduleName === '@jupyter-widgets/base' || moduleName === '@jupyter-widgets/controls' || - moduleName === '@jupyter-widgets/output' + moduleName === '@jupyter-widgets/output' || + moduleName === 'voila-errorwidget' ) { - return super.loadClass(className, moduleName, moduleVersion); + return new Promise((resolve) => { + super.loadClass(className, moduleName, moduleVersion).then((cls) => { + resolve(cls) + }, (err) => { + // If we failed, fallback to the buildin error widget + resolve(errorwidget.createErrorWidget(err)); + }); + }); } else { // TODO: code duplicate from HTMLWidgetManager, consider a refactor - return this.loader(moduleName, moduleVersion).then((module) => { - if (module[className]) { - return module[className]; - } - else { - return Promise.reject("Class " + className + " not found in module " + moduleName + "@" + moduleVersion); - } - }) + return new Promise((resolve) => { + this.loader(moduleName, moduleVersion).then((module) => { + if (module[className]) { + resolve(module[className]); + } + else { + // If we failed to find the widget, fallback to the buildin error widget + const error = new Error("Class " + className + " not found in module " + moduleName + "@" + moduleVersion); + resolve(errorwidget.createErrorWidget(error)); + } + }, (error) => { + // If we failed to load the module, fallback to the buildin error widget + resolve(errorwidget.createErrorWidget(error)); + }) + }); } } @@ -145,6 +163,11 @@ export class WidgetManager extends JupyterLabManager { version: output.OUTPUT_WIDGET_VERSION, exports: output }); + this.register({ + name: 'voila-errorwidget', + version: '1.0.0', + exports: errorwidget + }); } async _build_models() {