diff --git a/src/notebooks/deepnote/deepnoteNotebookCommandListener.ts b/src/notebooks/deepnote/deepnoteNotebookCommandListener.ts index da121db36a..dcd086691f 100644 --- a/src/notebooks/deepnote/deepnoteNotebookCommandListener.ts +++ b/src/notebooks/deepnote/deepnoteNotebookCommandListener.ts @@ -30,6 +30,7 @@ import { DeepnoteButtonMetadataSchema, DeepnoteSqlMetadata } from './deepnoteSchemas'; +import { DATAFRAME_SQL_INTEGRATION_ID } from '../../platform/notebooks/deepnote/integrationTypes'; export type InputBlockType = | 'input-text' @@ -189,7 +190,7 @@ export class DeepnoteNotebookCommandListener implements IExtensionSyncActivation const defaultMetadata: DeepnoteSqlMetadata = { deepnote_variable_name: deepnoteVariableName, deepnote_return_variable_type: 'dataframe', - sql_integration_id: 'deepnote-dataframe-sql' + sql_integration_id: DATAFRAME_SQL_INTEGRATION_ID }; // Determine the index where to insert the new cell (below current selection or at the end) diff --git a/src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts b/src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts index 49404b0739..a282518292 100644 --- a/src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts +++ b/src/notebooks/deepnote/deepnoteNotebookCommandListener.unit.test.ts @@ -21,6 +21,7 @@ import { import { IDisposable } from '../../platform/common/types'; import * as notebookUpdater from '../../kernels/execution/notebookUpdater'; import { createMockedNotebookDocument } from '../../test/datascience/editor-integration/helpers'; +import { DATAFRAME_SQL_INTEGRATION_ID } from '../../platform/notebooks/deepnote/integrationTypes'; suite('DeepnoteNotebookCommandListener', () => { let commandListener: DeepnoteNotebookCommandListener; @@ -734,7 +735,7 @@ suite('DeepnoteNotebookCommandListener', () => { ); assert.equal( newCell.metadata.sql_integration_id, - 'deepnote-dataframe-sql', + DATAFRAME_SQL_INTEGRATION_ID, 'Should have correct sql integration id' ); diff --git a/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts b/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts index 5a15e4f30b..c6acf936e1 100644 --- a/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts +++ b/src/platform/notebooks/deepnote/sqlIntegrationEnvironmentVariablesProvider.unit.test.ts @@ -9,6 +9,7 @@ import { IntegrationType, PostgresIntegrationConfig, BigQueryIntegrationConfig, + DATAFRAME_SQL_INTEGRATION_ID, SnowflakeIntegrationConfig, SnowflakeAuthMethods } from './integrationTypes'; @@ -74,7 +75,7 @@ suite('SqlIntegrationEnvironmentVariablesProvider', () => { const uri = Uri.file('/test/notebook.deepnote'); const notebook = createMockNotebook(uri, [ createMockCell(0, NotebookCellKind.Code, 'sql', 'SELECT * FROM df', { - sql_integration_id: 'deepnote-dataframe-sql' + sql_integration_id: DATAFRAME_SQL_INTEGRATION_ID }) ]); diff --git a/src/webviews/webview-side/dataframe-renderer/DataframeRendererContainer.tsx b/src/webviews/webview-side/dataframe-renderer/DataframeRendererContainer.tsx new file mode 100644 index 0000000000..bb69c5e27a --- /dev/null +++ b/src/webviews/webview-side/dataframe-renderer/DataframeRendererContainer.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; + +import { DataframeMetadata, DataframeRenderer } from './DataframeRenderer'; +import { RendererContext } from 'vscode-notebook-renderer'; + +export interface Metadata { + cellId?: string; + cellIndex?: number; + executionCount: number; + metadata?: DataframeMetadata; + outputType: string; +} + +export function DataframeRendererContainer({ + context, + outputJson, + outputMetadata +}: { + context: RendererContext; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + outputJson: any; + outputMetadata: Metadata; +}) { + console.log(`Dataframe renderer - received data with ${Object.keys(outputJson).length} keys`); + + console.log('[DataframeRenderer] Full metadata', outputMetadata); + + const dataFrameMetadata = outputMetadata?.metadata as DataframeMetadata | undefined; + const cellId = outputMetadata?.cellId; + + console.log(`[DataframeRenderer] Extracted cellId: ${cellId}`); + + return ; +} diff --git a/src/webviews/webview-side/dataframe-renderer/ErrorBoundary.tsx b/src/webviews/webview-side/dataframe-renderer/ErrorBoundary.tsx new file mode 100644 index 0000000000..e1bab31461 --- /dev/null +++ b/src/webviews/webview-side/dataframe-renderer/ErrorBoundary.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import type { FallbackProps } from 'react-error-boundary'; + +export function ErrorFallback({ error }: FallbackProps) { + return ( +
+
Error rendering dataframe
+
{error.message}
+
+ Stack trace +
+                    {error.stack}
+                
+
+
+ ); +} diff --git a/src/webviews/webview-side/dataframe-renderer/index.ts b/src/webviews/webview-side/dataframe-renderer/index.ts index 96f21015ca..a5423d19a2 100644 --- a/src/webviews/webview-side/dataframe-renderer/index.ts +++ b/src/webviews/webview-side/dataframe-renderer/index.ts @@ -5,61 +5,50 @@ import * as ReactDOM from 'react-dom'; import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer'; -import { DataframeMetadata, DataframeRenderer } from './DataframeRenderer'; - -interface Metadata { - cellId?: string; - cellIndex?: number; - executionCount: number; - metadata?: DataframeMetadata; - outputType: string; -} +import { ErrorBoundary } from 'react-error-boundary'; +import { ErrorFallback } from './ErrorBoundary'; +import { DataframeRendererContainer, Metadata } from './DataframeRendererContainer'; export const activate: ActivationFunction = (context: RendererContext) => { return { renderOutputItem(outputItem: OutputItem, element: HTMLElement) { - console.log(`Dataframe renderer - rendering output item: ${outputItem.id}`); - try { - const data = outputItem.json(); - - console.log(`Dataframe renderer - received data with ${Object.keys(data).length} keys`); - - const metadata = outputItem.metadata as Metadata | undefined; - - console.log('[DataframeRenderer] Full metadata', metadata); - - const dataFrameMetadata = metadata?.metadata as DataframeMetadata | undefined; - const cellId = metadata?.cellId; - const cellIndex = metadata?.cellIndex; - - console.log(`[DataframeRenderer] Extracted cellId: ${cellId}, cellIndex: ${cellIndex}`); - - const root = document.createElement('div'); - - element.appendChild(root); - - ReactDOM.render( - React.createElement(DataframeRenderer, { + ReactDOM.render( + React.createElement( + ErrorBoundary, + { + FallbackComponent: ErrorFallback, + onError: (error, info) => { + console.error('Vega renderer error:', error, info); + } + }, + React.createElement(DataframeRendererContainer, { context, - data, - metadata: dataFrameMetadata, - cellId, - cellIndex - }), - root - ); - } catch (error) { - console.error(`Error rendering dataframe: ${error}`); - const errorDiv = document.createElement('div'); - errorDiv.style.padding = '10px'; - errorDiv.style.color = 'var(--vscode-errorForeground)'; - errorDiv.textContent = `Error rendering dataframe: ${error}`; - element.appendChild(errorDiv); - } + outputJson: outputItem.json(), + outputMetadata: outputItem.metadata as Metadata + }) + ), + element + ); }, - disposeOutputItem(_id?: string) { - // Cleanup if needed + disposeOutputItem(id?: string) { + // If undefined, all cells are being removed. + if (id == null) { + for (let i = 0; i < document.children.length; i++) { + const child = document.children.item(i); + if (child == null) { + continue; + } + ReactDOM.unmountComponentAtNode(child); + } + return; + } + + const element = document.getElementById(id); + if (element == null) { + return; + } + ReactDOM.unmountComponentAtNode(element); } }; };