-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expose API to show DS Data Viewer component from external extension #12045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
388f87d
85783ff
3a6ad6c
fc0dcef
d161502
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| 'use strict'; | ||
| import '../../common/extensions'; | ||
|
|
||
| import { inject, injectable, named } from 'inversify'; | ||
| import { inject, injectable } from 'inversify'; | ||
| import * as path from 'path'; | ||
| import { ViewColumn } from 'vscode'; | ||
|
|
||
|
|
@@ -16,18 +16,23 @@ import * as localize from '../../common/utils/localize'; | |
| import { noop } from '../../common/utils/misc'; | ||
| import { StopWatch } from '../../common/utils/stopWatch'; | ||
| import { sendTelemetryEvent } from '../../telemetry'; | ||
| import { HelpLinks, Identifiers, Telemetry } from '../constants'; | ||
| import { HelpLinks, Telemetry } from '../constants'; | ||
| import { JupyterDataRateLimitError } from '../jupyter/jupyterDataRateLimitError'; | ||
| import { ICodeCssGenerator, IDataViewer, IJupyterVariable, IJupyterVariables, INotebook, IThemeFinder } from '../types'; | ||
| import { ICodeCssGenerator, IDataViewer, IThemeFinder } from '../types'; | ||
| import { WebViewHost } from '../webViewHost'; | ||
| import { DataViewerMessageListener } from './dataViewerMessageListener'; | ||
| import { DataViewerMessages, IDataViewerMapping, IGetRowsRequest } from './types'; | ||
| import { | ||
| DataViewerMessages, | ||
| IDataFrameInfo, | ||
| IDataViewerDataProvider, | ||
| IDataViewerMapping, | ||
| IGetRowsRequest | ||
| } from './types'; | ||
|
|
||
| const dataExplorereDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'viewers'); | ||
| @injectable() | ||
| export class DataViewer extends WebViewHost<IDataViewerMapping> implements IDataViewer, IDisposable { | ||
| private notebook: INotebook | undefined; | ||
| private variable: IJupyterVariable | undefined; | ||
| private dataProvider: IDataViewerDataProvider | undefined; | ||
| private rowsTimer: StopWatch | undefined; | ||
| private pendingRowsCount: number = 0; | ||
|
|
||
|
|
@@ -37,7 +42,6 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData | |
| @inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator, | ||
| @inject(IThemeFinder) themeFinder: IThemeFinder, | ||
| @inject(IWorkspaceService) workspaceService: IWorkspaceService, | ||
| @inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableManager: IJupyterVariables, | ||
| @inject(IApplicationShell) private applicationShell: IApplicationShell, | ||
| @inject(IExperimentsManager) experimentsManager: IExperimentsManager, | ||
| @inject(UseCustomEditorApi) useCustomEditorApi: boolean | ||
|
|
@@ -57,33 +61,43 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData | |
| useCustomEditorApi, | ||
| false | ||
| ); | ||
| } | ||
|
|
||
| // Load the web panel using our current directory as we don't expect to load any other files | ||
| super.loadWebPanel(process.cwd()).catch(traceError); | ||
| private static trimTitle(title: string): string { | ||
|
||
| const TRIM_LENGTH = 40; | ||
| if (title && title.length > TRIM_LENGTH) { | ||
| title = `${title.substr(0, TRIM_LENGTH)}...`; | ||
| } | ||
| return title; | ||
| } | ||
|
|
||
| public async showVariable(variable: IJupyterVariable, notebook: INotebook): Promise<void> { | ||
| public async showData(dataProvider: IDataViewerDataProvider, title: string): Promise<void> { | ||
| if (!this.isDisposed) { | ||
| // Save notebook this is tied to | ||
| this.notebook = notebook; | ||
| // Save the data provider | ||
| this.dataProvider = dataProvider; | ||
|
|
||
| // Fill in our variable's beginning data | ||
| this.variable = await this.prepVariable(variable, notebook); | ||
| // Load the web panel using our current directory as we don't expect to load any other files | ||
| await super.loadWebPanel(process.cwd()).catch(traceError); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we swallowing errors here. Not sure this is the right thing to do.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @DonJayamanne great comments :) thanks
|
||
|
|
||
| // Create our new title with the variable name | ||
| let newTitle = `${localize.DataScience.dataExplorerTitle()} - ${variable.name}`; | ||
| const TRIM_LENGTH = 40; | ||
| if (newTitle.length > TRIM_LENGTH) { | ||
| newTitle = `${newTitle.substr(0, TRIM_LENGTH)}...`; | ||
| } | ||
|
|
||
| super.setTitle(newTitle); | ||
| super.setTitle(DataViewer.trimTitle(title)); | ||
|
|
||
| // Then show our web panel. Eventually we need to consume the data | ||
| await super.show(true); | ||
|
|
||
| const dataFrameInfo = await this.prepDataFrameInfo(dataProvider); | ||
|
|
||
| // Send a message with our data | ||
| this.postMessage(DataViewerMessages.InitializeData, this.variable).ignoreErrors(); | ||
| this.postMessage(DataViewerMessages.InitializeData, dataFrameInfo).ignoreErrors(); | ||
| } | ||
| } | ||
|
|
||
| public dispose(): void { | ||
| super.dispose(); | ||
|
|
||
| if (this.dataProvider) { | ||
| // Call dispose on the data provider | ||
| this.dataProvider.dispose(); | ||
| this.dataProvider = undefined; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -109,9 +123,9 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData | |
| super.onMessage(message, payload); | ||
| } | ||
|
|
||
| private async prepVariable(variable: IJupyterVariable, notebook: INotebook): Promise<IJupyterVariable> { | ||
| private async prepDataFrameInfo(dataProvider: IDataViewerDataProvider): Promise<IDataFrameInfo> { | ||
| this.rowsTimer = new StopWatch(); | ||
| const output = await this.variableManager.getDataFrameInfo(variable, notebook); | ||
| const output = await dataProvider.getDataFrameInfo(); | ||
|
|
||
| // Log telemetry about number of rows | ||
| try { | ||
|
|
@@ -131,13 +145,8 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData | |
|
|
||
| private async getAllRows() { | ||
| return this.wrapRequest(async () => { | ||
| if (this.variable && this.variable.rowCount && this.notebook) { | ||
| const allRows = await this.variableManager.getDataFrameRows( | ||
| this.variable, | ||
| this.notebook, | ||
| 0, | ||
| this.variable.rowCount | ||
| ); | ||
| if (this.dataProvider) { | ||
| const allRows = await this.dataProvider.getAllRows(); | ||
| this.pendingRowsCount = 0; | ||
| return this.postMessage(DataViewerMessages.GetAllRowsResponse, allRows); | ||
| } | ||
|
|
@@ -146,12 +155,10 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData | |
|
|
||
| private getRowChunk(request: IGetRowsRequest) { | ||
| return this.wrapRequest(async () => { | ||
| if (this.variable && this.variable.rowCount && this.notebook) { | ||
| const rows = await this.variableManager.getDataFrameRows( | ||
| this.variable, | ||
| this.notebook, | ||
| if (this.dataProvider) { | ||
| const rows = await this.dataProvider.getRows( | ||
| request.start, | ||
| Math.min(request.end, this.variable.rowCount) | ||
| Math.min(request.end, (await this.dataProvider.getDataFrameInfo()).rowCount) | ||
sevillal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ); | ||
| return this.postMessage(DataViewerMessages.GetRowsResponse, { | ||
| rows, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
| 'use strict'; | ||
| import '../../common/extensions'; | ||
|
|
||
| import { inject, injectable, named } from 'inversify'; | ||
|
|
||
| import { JSONObject } from '@phosphor/coreutils'; | ||
|
|
||
| import { Identifiers } from '../constants'; | ||
| import { IJupyterVariable, IJupyterVariableDataProvider, IJupyterVariables, INotebook } from '../types'; | ||
| import { DataViewerDependencyService } from './dataViewerDependencyService'; | ||
| import { ColumnType, IDataFrameInfo } from './types'; | ||
|
|
||
| @injectable() | ||
| export class JupyterVariableDataProvider implements IJupyterVariableDataProvider { | ||
| private initialized: boolean = false; | ||
|
|
||
| constructor( | ||
| @inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableManager: IJupyterVariables, | ||
| @inject(DataViewerDependencyService) private dependencyService: DataViewerDependencyService, | ||
| private variable: IJupyterVariable, | ||
| private notebook: INotebook | ||
| ) {} | ||
|
|
||
| /** | ||
| * Normalizes column types to the types the UI component understands. | ||
| * Defaults to 'string'. | ||
| * @param columns | ||
| * @returns Array of columns with normalized type | ||
| */ | ||
| private static getNormalizedColumns(columns: { key: string; type: string }[]): { key: string; type: ColumnType }[] { | ||
| return columns.map((column: { key: string; type: string }) => { | ||
| let normalizedType: ColumnType; | ||
| switch (column.type) { | ||
| case 'bool': | ||
| normalizedType = ColumnType.Bool; | ||
| break; | ||
| case 'integer': | ||
| case 'int32': | ||
| case 'int64': | ||
| case 'float': | ||
| case 'float32': | ||
| case 'float64': | ||
| case 'number': | ||
| normalizedType = ColumnType.Number; | ||
| break; | ||
| default: | ||
| normalizedType = ColumnType.String; | ||
| } | ||
| return { | ||
| key: column.key, | ||
| type: normalizedType | ||
| }; | ||
| }); | ||
| } | ||
|
|
||
| public dispose(): void { | ||
| return; | ||
| } | ||
|
|
||
| public async getDataFrameInfo(): Promise<IDataFrameInfo> { | ||
| await this.ensureInitialized(); | ||
| return { | ||
| columns: this.variable.columns | ||
| ? JupyterVariableDataProvider.getNormalizedColumns(this.variable.columns) | ||
| : this.variable.columns, | ||
| indexColumn: this.variable.indexColumn, | ||
| rowCount: this.variable.rowCount || 0 | ||
| }; | ||
| } | ||
|
|
||
| public async getAllRows() { | ||
| let allRows: JSONObject = {}; | ||
| await this.ensureInitialized(); | ||
| if (this.variable.rowCount) { | ||
| allRows = await this.variableManager.getDataFrameRows( | ||
| this.variable, | ||
| this.notebook, | ||
| 0, | ||
| this.variable.rowCount | ||
| ); | ||
| } | ||
| return allRows; | ||
| } | ||
|
|
||
| public async getRows(start: number, end: number) { | ||
| let rows: JSONObject = {}; | ||
sevillal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| await this.ensureInitialized(); | ||
| if (this.variable.rowCount) { | ||
| rows = await this.variableManager.getDataFrameRows(this.variable, this.notebook, start, end); | ||
| } | ||
| return rows; | ||
| } | ||
|
|
||
| private async ensureInitialized(): Promise<void> { | ||
| // Postpone pre-req and variable initialization until data is requested. | ||
| if (!this.initialized) { | ||
sevillal marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| await this.dependencyService.checkAndInstallMissingDependencies(this.notebook.getMatchingInterpreter()); | ||
| this.variable = await this.variableManager.getDataFrameInfo(this.variable, this.notebook); | ||
| this.initialized = true; | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.