From f749718478517f40c63dc6751d5f07cd543b02f5 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Dec 2025 11:23:14 +0100 Subject: [PATCH 1/7] feat(client): add error handling --- src/errors.ts | 5 +++++ src/index.ts | 45 +++++++++++++++++++++++++++------------------ src/model.ts | 49 ++++++++++++++++++++++++++++++++----------------- src/widget.ts | 9 +++++++-- 4 files changed, 71 insertions(+), 37 deletions(-) create mode 100644 src/errors.ts diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..8beeb43 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,5 @@ +import { showErrorMessage } from "@jupyterlab/apputils"; + +export const handleError = async (err: Error | string, title: string): Promise => { + await showErrorMessage(title, err); +}; diff --git a/src/index.ts b/src/index.ts index 492139b..2dc751a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import { addAvroFileType, addIpcFileType, addOrcFileType, addParquetFileType } f import { getArrowIPCIcon, getAvroIcon, getORCIcon, getParquetIcon } from "./labicons"; import { ArrowGridViewerFactory } from "./widget"; import type { ArrowGridViewer, ITextRenderConfig } from "./widget"; +import { handleError } from "./errors"; export namespace NoOpContentProvider { export interface IOptions { @@ -141,24 +142,28 @@ function activateArrowGrid( app.docRegistry.addWidgetFactory(factory); factory.widgetCreated.connect(async (_sender, widget) => { - // Track the widget. - void tracker.add(widget); - // Notify the widget tracker if restore data needs to update. - widget.context.pathChanged.connect(() => { - void tracker.save(widget); - }); + try { + // Track the widget. + void tracker.add(widget); + // Notify the widget tracker if restore data needs to update. + widget.context.pathChanged.connect(() => { + void tracker.save(widget); + }); + + if (csv_ft) { + widget.title.icon = csv_ft.icon; + widget.title.iconClass = csv_ft.iconClass!; + widget.title.iconLabel = csv_ft.iconLabel!; + } + await widget.content.ready; + widget.content.style = style; + widget.content.rendererConfig = rendererConfig; + updateThemes(); - if (csv_ft) { - widget.title.icon = csv_ft.icon; - widget.title.iconClass = csv_ft.iconClass!; - widget.title.iconLabel = csv_ft.iconLabel!; + console.log("JupyterLab extension arbalister is activated!"); + } catch (error: any) { + await handleError(error, "ArrowGridViewer widget initialization failed"); } - await widget.content.ready; - widget.content.style = style; - widget.content.rendererConfig = rendererConfig; - updateThemes(); - - console.log("JupyterLab extension arbalister is activated!"); }); const updateThemes = (newTheme?: string | null) => { @@ -178,8 +183,12 @@ function activateArrowGrid( }; if (themeManager) { themeManager.themeChanged.connect((_, args) => { - const newTheme = args.newValue; - updateThemes(newTheme); + try { + const newTheme = args.newValue; + updateThemes(newTheme); + } catch (error: any) { + void handleError(error, "Failed to the viewer according to updated theme"); + } }); } } diff --git a/src/model.ts b/src/model.ts index 1630c7d..e763340 100644 --- a/src/model.ts +++ b/src/model.ts @@ -4,6 +4,7 @@ import type * as Arrow from "apache-arrow"; import { PairMap } from "./collection"; import { fetchStats, fetchTable } from "./requests"; +import { handleError } from "./errors"; export namespace ArrowModel { export interface IOptions { @@ -27,16 +28,20 @@ export class ArrowModel extends DataModel { } protected async initialize(): Promise { - const [schema, stats, chunk00] = await Promise.all([ - this.fetchSchema(), - fetchStats({ path: this._path }), - this.fetchChunk([0, 0]), - ]); - - this._schema = schema; - this._chunks.set([0, 0], chunk00); - this._numCols = stats.num_cols; - this._numRows = stats.num_rows; + try { + const [schema, stats, chunk00] = await Promise.all([ + this.fetchSchema(), + fetchStats({ path: this._path }), + this.fetchChunk([0, 0]), + ]); + + this._schema = schema; + this._chunks.set([0, 0], chunk00); + this._numCols = stats.num_cols; + this._numRows = stats.num_rows; + } catch (error: any) { + await handleError(error, "Initial data could not be loaded"); + } } get ready(): Promise { @@ -104,10 +109,15 @@ export class ArrowModel extends DataModel { // Fetch data, however we cannot await it due to the interface required by the DataGrid. // Instead, we fire the request, and notify of change upon completion. - const promise = this.fetchChunk(chunk_idx).then((table) => { - this._chunks.set(chunk_idx, table); - this.emitChangedChunk(chunk_idx); - }); + const promise = this.fetchChunk(chunk_idx) + .then((table) => { + this._chunks.set(chunk_idx, table); + this.emitChangedChunk(chunk_idx); + }) + .catch((error: any) => { + this._chunks.delete(chunk_idx); + void handleError(error, "Chunck load failed"); + }); this._chunks.set(chunk_idx, promise); return this._loadingRepr; @@ -141,9 +151,14 @@ export class ArrowModel extends DataModel { return; } - const promise = this.fetchChunk(chunk_idx).then((table) => { - this._chunks.set(chunk_idx, table); - }); + const promise = this.fetchChunk(chunk_idx) + .then((table) => { + this._chunks.set(chunk_idx, table); + }) + .catch((error: any) => { + this._chunks.delete(chunk_idx); + void handleError(error, "Prefetch failed"); + }); this._chunks.set(chunk_idx, promise); } diff --git a/src/widget.ts b/src/widget.ts index 6ed9669..dfaa469 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -6,6 +6,7 @@ import type { DocumentRegistry, IDocumentWidget } from "@jupyterlab/docregistry" import type * as DataGridModule from "@lumino/datagrid"; import { ArrowModel } from "./model"; +import { handleError } from "./errors"; export namespace ArrowGridViewer { export interface IOptions { @@ -68,8 +69,12 @@ export class ArrowGridViewer extends Panel { protected async initialize(): Promise { this._defaultStyle = DataGrid.defaultStyle; - await this._updateGrid(); - this._revealed.resolve(undefined); + try { + await this._updateGrid(); + this._revealed.resolve(undefined); + } catch (error: any) { + await handleError(error, "Failed to initialized ArrowGridViewer"); + } } private async _updateGrid() { From 8afca11b73df478ef3479f78b575bae94b08d071 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Dec 2025 13:31:12 +0100 Subject: [PATCH 2/7] fix error types --- src/errors.ts | 4 ++-- src/index.ts | 5 ++--- src/model.ts | 6 +++--- src/widget.ts | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index 8beeb43..9d98987 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,5 +1,5 @@ import { showErrorMessage } from "@jupyterlab/apputils"; -export const handleError = async (err: Error | string, title: string): Promise => { - await showErrorMessage(title, err); +export const handleError = async (err: Error | string | unknown, title: string): Promise => { + await showErrorMessage(title, err as Error | string); }; diff --git a/src/index.ts b/src/index.ts index 2dc751a..9ef850d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -159,9 +159,8 @@ function activateArrowGrid( widget.content.style = style; widget.content.rendererConfig = rendererConfig; updateThemes(); - console.log("JupyterLab extension arbalister is activated!"); - } catch (error: any) { + } catch (error) { await handleError(error, "ArrowGridViewer widget initialization failed"); } }); @@ -186,7 +185,7 @@ function activateArrowGrid( try { const newTheme = args.newValue; updateThemes(newTheme); - } catch (error: any) { + } catch (error) { void handleError(error, "Failed to the viewer according to updated theme"); } }); diff --git a/src/model.ts b/src/model.ts index e763340..81ef99d 100644 --- a/src/model.ts +++ b/src/model.ts @@ -39,7 +39,7 @@ export class ArrowModel extends DataModel { this._chunks.set([0, 0], chunk00); this._numCols = stats.num_cols; this._numRows = stats.num_rows; - } catch (error: any) { + } catch (error) { await handleError(error, "Initial data could not be loaded"); } } @@ -114,7 +114,7 @@ export class ArrowModel extends DataModel { this._chunks.set(chunk_idx, table); this.emitChangedChunk(chunk_idx); }) - .catch((error: any) => { + .catch((error) => { this._chunks.delete(chunk_idx); void handleError(error, "Chunck load failed"); }); @@ -155,7 +155,7 @@ export class ArrowModel extends DataModel { .then((table) => { this._chunks.set(chunk_idx, table); }) - .catch((error: any) => { + .catch((error) => { this._chunks.delete(chunk_idx); void handleError(error, "Prefetch failed"); }); diff --git a/src/widget.ts b/src/widget.ts index dfaa469..054e8d3 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -72,7 +72,7 @@ export class ArrowGridViewer extends Panel { try { await this._updateGrid(); this._revealed.resolve(undefined); - } catch (error: any) { + } catch (error) { await handleError(error, "Failed to initialized ArrowGridViewer"); } } From 7070bc755897cb663e98bfabac463a248d2f3cd7 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Dec 2025 13:35:51 +0100 Subject: [PATCH 3/7] fix by formatting --- src/index.ts | 2 +- src/model.ts | 4 ++-- src/widget.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 9ef850d..b0cf10c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,11 +8,11 @@ import type * as services from "@jupyterlab/services"; import type { Contents } from "@jupyterlab/services"; import type { DataGrid } from "@lumino/datagrid"; +import { handleError } from "./errors"; import { addAvroFileType, addIpcFileType, addOrcFileType, addParquetFileType } from "./filetypes"; import { getArrowIPCIcon, getAvroIcon, getORCIcon, getParquetIcon } from "./labicons"; import { ArrowGridViewerFactory } from "./widget"; import type { ArrowGridViewer, ITextRenderConfig } from "./widget"; -import { handleError } from "./errors"; export namespace NoOpContentProvider { export interface IOptions { diff --git a/src/model.ts b/src/model.ts index 81ef99d..a064f13 100644 --- a/src/model.ts +++ b/src/model.ts @@ -3,8 +3,8 @@ import { DataModel } from "@lumino/datagrid"; import type * as Arrow from "apache-arrow"; import { PairMap } from "./collection"; -import { fetchStats, fetchTable } from "./requests"; import { handleError } from "./errors"; +import { fetchStats, fetchTable } from "./requests"; export namespace ArrowModel { export interface IOptions { @@ -116,7 +116,7 @@ export class ArrowModel extends DataModel { }) .catch((error) => { this._chunks.delete(chunk_idx); - void handleError(error, "Chunck load failed"); + void handleError(error, "Chunk load failed"); }); this._chunks.set(chunk_idx, promise); diff --git a/src/widget.ts b/src/widget.ts index 054e8d3..67cb94f 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -5,8 +5,8 @@ import { Panel } from "@lumino/widgets"; import type { DocumentRegistry, IDocumentWidget } from "@jupyterlab/docregistry"; import type * as DataGridModule from "@lumino/datagrid"; -import { ArrowModel } from "./model"; import { handleError } from "./errors"; +import { ArrowModel } from "./model"; export namespace ArrowGridViewer { export interface IOptions { From 5e718c897a2ab808a0229cc37104f44bd5a3f6de Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Dec 2025 14:16:48 +0100 Subject: [PATCH 4/7] fix showing the error dialog --- src/errors.ts | 5 ----- src/index.ts | 7 +++---- src/model.ts | 49 +++++++++++++++++-------------------------------- src/widget.ts | 4 ++-- 4 files changed, 22 insertions(+), 43 deletions(-) delete mode 100644 src/errors.ts diff --git a/src/errors.ts b/src/errors.ts deleted file mode 100644 index 9d98987..0000000 --- a/src/errors.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { showErrorMessage } from "@jupyterlab/apputils"; - -export const handleError = async (err: Error | string | unknown, title: string): Promise => { - await showErrorMessage(title, err as Error | string); -}; diff --git a/src/index.ts b/src/index.ts index b0cf10c..899e5c1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { ILayoutRestorer } from "@jupyterlab/application"; -import { IThemeManager, WidgetTracker } from "@jupyterlab/apputils"; +import { IThemeManager, showErrorMessage, WidgetTracker } from "@jupyterlab/apputils"; import { IDefaultDrive } from "@jupyterlab/services"; import { ITranslator } from "@jupyterlab/translation"; import type { JupyterFrontEnd, JupyterFrontEndPlugin } from "@jupyterlab/application"; @@ -8,7 +8,6 @@ import type * as services from "@jupyterlab/services"; import type { Contents } from "@jupyterlab/services"; import type { DataGrid } from "@lumino/datagrid"; -import { handleError } from "./errors"; import { addAvroFileType, addIpcFileType, addOrcFileType, addParquetFileType } from "./filetypes"; import { getArrowIPCIcon, getAvroIcon, getORCIcon, getParquetIcon } from "./labicons"; import { ArrowGridViewerFactory } from "./widget"; @@ -161,7 +160,7 @@ function activateArrowGrid( updateThemes(); console.log("JupyterLab extension arbalister is activated!"); } catch (error) { - await handleError(error, "ArrowGridViewer widget initialization failed"); + await showErrorMessage("ArrowGridViewer widget initialization failed", error as Error); } }); @@ -186,7 +185,7 @@ function activateArrowGrid( const newTheme = args.newValue; updateThemes(newTheme); } catch (error) { - void handleError(error, "Failed to the viewer according to updated theme"); + void showErrorMessage("Failed to the viewer according to updated theme", error as Error); } }); } diff --git a/src/model.ts b/src/model.ts index a064f13..1630c7d 100644 --- a/src/model.ts +++ b/src/model.ts @@ -3,7 +3,6 @@ import { DataModel } from "@lumino/datagrid"; import type * as Arrow from "apache-arrow"; import { PairMap } from "./collection"; -import { handleError } from "./errors"; import { fetchStats, fetchTable } from "./requests"; export namespace ArrowModel { @@ -28,20 +27,16 @@ export class ArrowModel extends DataModel { } protected async initialize(): Promise { - try { - const [schema, stats, chunk00] = await Promise.all([ - this.fetchSchema(), - fetchStats({ path: this._path }), - this.fetchChunk([0, 0]), - ]); - - this._schema = schema; - this._chunks.set([0, 0], chunk00); - this._numCols = stats.num_cols; - this._numRows = stats.num_rows; - } catch (error) { - await handleError(error, "Initial data could not be loaded"); - } + const [schema, stats, chunk00] = await Promise.all([ + this.fetchSchema(), + fetchStats({ path: this._path }), + this.fetchChunk([0, 0]), + ]); + + this._schema = schema; + this._chunks.set([0, 0], chunk00); + this._numCols = stats.num_cols; + this._numRows = stats.num_rows; } get ready(): Promise { @@ -109,15 +104,10 @@ export class ArrowModel extends DataModel { // Fetch data, however we cannot await it due to the interface required by the DataGrid. // Instead, we fire the request, and notify of change upon completion. - const promise = this.fetchChunk(chunk_idx) - .then((table) => { - this._chunks.set(chunk_idx, table); - this.emitChangedChunk(chunk_idx); - }) - .catch((error) => { - this._chunks.delete(chunk_idx); - void handleError(error, "Chunk load failed"); - }); + const promise = this.fetchChunk(chunk_idx).then((table) => { + this._chunks.set(chunk_idx, table); + this.emitChangedChunk(chunk_idx); + }); this._chunks.set(chunk_idx, promise); return this._loadingRepr; @@ -151,14 +141,9 @@ export class ArrowModel extends DataModel { return; } - const promise = this.fetchChunk(chunk_idx) - .then((table) => { - this._chunks.set(chunk_idx, table); - }) - .catch((error) => { - this._chunks.delete(chunk_idx); - void handleError(error, "Prefetch failed"); - }); + const promise = this.fetchChunk(chunk_idx).then((table) => { + this._chunks.set(chunk_idx, table); + }); this._chunks.set(chunk_idx, promise); } diff --git a/src/widget.ts b/src/widget.ts index 67cb94f..0520695 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -1,3 +1,4 @@ +import { showErrorMessage } from "@jupyterlab/apputils"; import { ABCWidgetFactory, DocumentWidget } from "@jupyterlab/docregistry"; import { PromiseDelegate } from "@lumino/coreutils"; import { BasicKeyHandler, BasicMouseHandler, DataGrid, TextRenderer } from "@lumino/datagrid"; @@ -5,7 +6,6 @@ import { Panel } from "@lumino/widgets"; import type { DocumentRegistry, IDocumentWidget } from "@jupyterlab/docregistry"; import type * as DataGridModule from "@lumino/datagrid"; -import { handleError } from "./errors"; import { ArrowModel } from "./model"; export namespace ArrowGridViewer { @@ -73,7 +73,7 @@ export class ArrowGridViewer extends Panel { await this._updateGrid(); this._revealed.resolve(undefined); } catch (error) { - await handleError(error, "Failed to initialized ArrowGridViewer"); + await showErrorMessage("Failed to initialized ArrowGridViewer", error as Error); } } From fc9426853c9dce3816d53a555da79d074df5f4b8 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Dec 2025 15:42:51 +0100 Subject: [PATCH 5/7] add retry functionality --- src/index.ts | 10 ++++++++-- src/widget.ts | 20 ++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 899e5c1..038a61a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -160,7 +160,10 @@ function activateArrowGrid( updateThemes(); console.log("JupyterLab extension arbalister is activated!"); } catch (error) { - await showErrorMessage("ArrowGridViewer widget initialization failed", error as Error); + await showErrorMessage( + trans.__("ArrowGridViewer widget initialization failed"), + error as Error, + ); } }); @@ -185,7 +188,10 @@ function activateArrowGrid( const newTheme = args.newValue; updateThemes(newTheme); } catch (error) { - void showErrorMessage("Failed to the viewer according to updated theme", error as Error); + void showErrorMessage( + trans.__("Failed to the viewer according to updated theme"), + error as Error, + ); } }); } diff --git a/src/widget.ts b/src/widget.ts index 0520695..4318e43 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -1,4 +1,4 @@ -import { showErrorMessage } from "@jupyterlab/apputils"; +import { Dialog, showDialog } from "@jupyterlab/apputils"; import { ABCWidgetFactory, DocumentWidget } from "@jupyterlab/docregistry"; import { PromiseDelegate } from "@lumino/coreutils"; import { BasicKeyHandler, BasicMouseHandler, DataGrid, TextRenderer } from "@lumino/datagrid"; @@ -69,11 +69,27 @@ export class ArrowGridViewer extends Panel { protected async initialize(): Promise { this._defaultStyle = DataGrid.defaultStyle; + try { await this._updateGrid(); this._revealed.resolve(undefined); + throw new Error("test"); } catch (error) { - await showErrorMessage("Failed to initialized ArrowGridViewer", error as Error); + const trans = Dialog.translator.load("jupyterlab"); + const buttons = [ + Dialog.cancelButton({ label: trans.__("Close") }), + Dialog.okButton({ label: trans.__("Retry") }), + ]; + const confirm = await showDialog({ + title: "Failed to initialized ArrowGridViewer", + body: typeof error === "string" ? error : (error as Error).message, + buttons, + }); + const shouldRetry = confirm.button.accept; + + if (shouldRetry) { + await this.initialize(); + } } } From 82052c5efef576bd515e07b3502ca428e16655e4 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Tue, 16 Dec 2025 15:53:05 +0100 Subject: [PATCH 6/7] clean code --- src/widget.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widget.ts b/src/widget.ts index 4318e43..f878b16 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -73,7 +73,6 @@ export class ArrowGridViewer extends Panel { try { await this._updateGrid(); this._revealed.resolve(undefined); - throw new Error("test"); } catch (error) { const trans = Dialog.translator.load("jupyterlab"); const buttons = [ From afe40178ebf3b7e583d4bfbe782c911b5d1b9b41 Mon Sep 17 00:00:00 2001 From: Anastasiia Sliusar Date: Wed, 17 Dec 2025 18:09:56 +0100 Subject: [PATCH 7/7] refactoring --- src/widget.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/widget.ts b/src/widget.ts index f878b16..9f76e33 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -69,10 +69,15 @@ export class ArrowGridViewer extends Panel { protected async initialize(): Promise { this._defaultStyle = DataGrid.defaultStyle; + await this._updateGrid(); + this._revealed.resolve(undefined); + } + private async _updateGrid() { try { - await this._updateGrid(); - this._revealed.resolve(undefined); + const model = new ArrowModel({ path: this.path }); + await model.ready; + this._grid.dataModel = model; } catch (error) { const trans = Dialog.translator.load("jupyterlab"); const buttons = [ @@ -87,17 +92,11 @@ export class ArrowGridViewer extends Panel { const shouldRetry = confirm.button.accept; if (shouldRetry) { - await this.initialize(); + await this._updateGrid(); } } } - private async _updateGrid() { - const model = new ArrowModel({ path: this.path }); - await model.ready; - this._grid.dataModel = model; - } - private async _updateRenderer(): Promise { if (this._baseRenderer === null) { return;