Skip to content

Commit 722d81b

Browse files
authored
Expose API to show DS Data Viewer component from external extension (#12045)
* Issue#12033 Expose API to show Data Viewer component from an external extension * Issue#12033 Add headers and comments to important updated/new code * Issue12033 Fix review comments: 1. Do not trim title in DataViewer 2. More strucuted type to represent rows responses from IDataViewerDataProvider. Created IRowsResponse type {data: any[]} 3. Change createion of JupyterVariableDataProvider through a Factory Class 4. Fix initialization lock/flag in jupyterVariableDataProvider to ensure only one execution 5. showDataViewer API return void 6. Cache dataFrameInfo in DataViewer to prevent multiple fetches 7. Fix automotter change in reactSlickGrid.txt imports 8. Move IDataViewer and IDataViewerFactory to data-viewing/types * Issue#12033 Fix hygiene * Issue#12033 Change IRowsResponse to be of type any[] Co-authored-by: Sergio Villalobos <sevillal@microsoft.com>
1 parent e42fc1e commit 722d81b

File tree

19 files changed

+460
-215
lines changed

19 files changed

+460
-215
lines changed

src/client/api.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { isTestExecution } from './common/constants';
77
import { DebugAdapterNewPtvsd } from './common/experiments/groups';
88
import { traceError } from './common/logger';
99
import { IConfigurationService, IExperimentsManager, Resource } from './common/types';
10+
import { IDataViewerDataProvider, IDataViewerFactory } from './datascience/data-viewing/types';
1011
import {
1112
getDebugpyLauncherArgs,
1213
getDebugpyPackagePath,
@@ -64,6 +65,14 @@ export interface IExtensionApi {
6465
*/
6566
getExecutionCommand(resource?: Resource): string[] | undefined;
6667
};
68+
datascience: {
69+
/**
70+
* Launches Data Viewer component.
71+
* @param {IDataViewerDataProvider} dataProvider Instance that will be used by the Data Viewer component to fetch data.
72+
* @param {string} title Data Viewer title
73+
*/
74+
showDataViewer(dataProvider: IDataViewerDataProvider, title: string): Promise<void>;
75+
};
6776
}
6877

6978
export function buildApi(
@@ -118,6 +127,12 @@ export function buildApi(
118127
// If pythonPath equals an empty string, no interpreter is set.
119128
return pythonPath === '' ? undefined : [pythonPath];
120129
}
130+
},
131+
datascience: {
132+
async showDataViewer(dataProvider: IDataViewerDataProvider, title: string): Promise<void> {
133+
const dataViewerProviderService = serviceContainer.get<IDataViewerFactory>(IDataViewerFactory);
134+
await dataViewerProviderService.create(dataProvider, title);
135+
}
121136
}
122137
};
123138

src/client/datascience/data-viewing/dataViewer.ts

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
'use strict';
44
import '../../common/extensions';
55

6-
import { inject, injectable, named } from 'inversify';
6+
import { inject, injectable } from 'inversify';
77
import * as path from 'path';
88
import { ViewColumn } from 'vscode';
99

@@ -16,28 +16,34 @@ import * as localize from '../../common/utils/localize';
1616
import { noop } from '../../common/utils/misc';
1717
import { StopWatch } from '../../common/utils/stopWatch';
1818
import { sendTelemetryEvent } from '../../telemetry';
19-
import { HelpLinks, Identifiers, Telemetry } from '../constants';
19+
import { HelpLinks, Telemetry } from '../constants';
2020
import { JupyterDataRateLimitError } from '../jupyter/jupyterDataRateLimitError';
21-
import { ICodeCssGenerator, IDataViewer, IJupyterVariable, IJupyterVariables, INotebook, IThemeFinder } from '../types';
21+
import { ICodeCssGenerator, IThemeFinder } from '../types';
2222
import { WebViewHost } from '../webViewHost';
2323
import { DataViewerMessageListener } from './dataViewerMessageListener';
24-
import { DataViewerMessages, IDataViewerMapping, IGetRowsRequest } from './types';
24+
import {
25+
DataViewerMessages,
26+
IDataFrameInfo,
27+
IDataViewer,
28+
IDataViewerDataProvider,
29+
IDataViewerMapping,
30+
IGetRowsRequest
31+
} from './types';
2532

2633
const dataExplorereDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'viewers');
2734
@injectable()
2835
export class DataViewer extends WebViewHost<IDataViewerMapping> implements IDataViewer, IDisposable {
29-
private notebook: INotebook | undefined;
30-
private variable: IJupyterVariable | undefined;
36+
private dataProvider: IDataViewerDataProvider | undefined;
3137
private rowsTimer: StopWatch | undefined;
3238
private pendingRowsCount: number = 0;
39+
private dataFrameInfoPromise: Promise<IDataFrameInfo> | undefined;
3340

3441
constructor(
3542
@inject(IWebPanelProvider) provider: IWebPanelProvider,
3643
@inject(IConfigurationService) configuration: IConfigurationService,
3744
@inject(ICodeCssGenerator) cssGenerator: ICodeCssGenerator,
3845
@inject(IThemeFinder) themeFinder: IThemeFinder,
3946
@inject(IWorkspaceService) workspaceService: IWorkspaceService,
40-
@inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableManager: IJupyterVariables,
4147
@inject(IApplicationShell) private applicationShell: IApplicationShell,
4248
@inject(IExperimentsManager) experimentsManager: IExperimentsManager,
4349
@inject(UseCustomEditorApi) useCustomEditorApi: boolean
@@ -57,33 +63,35 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData
5763
useCustomEditorApi,
5864
false
5965
);
60-
61-
// Load the web panel using our current directory as we don't expect to load any other files
62-
super.loadWebPanel(process.cwd()).catch(traceError);
6366
}
6467

65-
public async showVariable(variable: IJupyterVariable, notebook: INotebook): Promise<void> {
68+
public async showData(dataProvider: IDataViewerDataProvider, title: string): Promise<void> {
6669
if (!this.isDisposed) {
67-
// Save notebook this is tied to
68-
this.notebook = notebook;
69-
70-
// Fill in our variable's beginning data
71-
this.variable = await this.prepVariable(variable, notebook);
70+
// Save the data provider
71+
this.dataProvider = dataProvider;
7272

73-
// Create our new title with the variable name
74-
let newTitle = `${localize.DataScience.dataExplorerTitle()} - ${variable.name}`;
75-
const TRIM_LENGTH = 40;
76-
if (newTitle.length > TRIM_LENGTH) {
77-
newTitle = `${newTitle.substr(0, TRIM_LENGTH)}...`;
78-
}
73+
// Load the web panel using our current directory as we don't expect to load any other files
74+
await super.loadWebPanel(process.cwd()).catch(traceError);
7975

80-
super.setTitle(newTitle);
76+
super.setTitle(title);
8177

8278
// Then show our web panel. Eventually we need to consume the data
8379
await super.show(true);
8480

81+
const dataFrameInfo = await this.prepDataFrameInfo();
82+
8583
// Send a message with our data
86-
this.postMessage(DataViewerMessages.InitializeData, this.variable).ignoreErrors();
84+
this.postMessage(DataViewerMessages.InitializeData, dataFrameInfo).ignoreErrors();
85+
}
86+
}
87+
88+
public dispose(): void {
89+
super.dispose();
90+
91+
if (this.dataProvider) {
92+
// Call dispose on the data provider
93+
this.dataProvider.dispose();
94+
this.dataProvider = undefined;
8795
}
8896
}
8997

@@ -109,9 +117,16 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData
109117
super.onMessage(message, payload);
110118
}
111119

112-
private async prepVariable(variable: IJupyterVariable, notebook: INotebook): Promise<IJupyterVariable> {
120+
private getDataFrameInfo(): Promise<IDataFrameInfo> {
121+
if (!this.dataFrameInfoPromise) {
122+
this.dataFrameInfoPromise = this.dataProvider ? this.dataProvider.getDataFrameInfo() : Promise.resolve({});
123+
}
124+
return this.dataFrameInfoPromise;
125+
}
126+
127+
private async prepDataFrameInfo(): Promise<IDataFrameInfo> {
113128
this.rowsTimer = new StopWatch();
114-
const output = await this.variableManager.getDataFrameInfo(variable, notebook);
129+
const output = await this.getDataFrameInfo();
115130

116131
// Log telemetry about number of rows
117132
try {
@@ -131,13 +146,8 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData
131146

132147
private async getAllRows() {
133148
return this.wrapRequest(async () => {
134-
if (this.variable && this.variable.rowCount && this.notebook) {
135-
const allRows = await this.variableManager.getDataFrameRows(
136-
this.variable,
137-
this.notebook,
138-
0,
139-
this.variable.rowCount
140-
);
149+
if (this.dataProvider) {
150+
const allRows = await this.dataProvider.getAllRows();
141151
this.pendingRowsCount = 0;
142152
return this.postMessage(DataViewerMessages.GetAllRowsResponse, allRows);
143153
}
@@ -146,12 +156,11 @@ export class DataViewer extends WebViewHost<IDataViewerMapping> implements IData
146156

147157
private getRowChunk(request: IGetRowsRequest) {
148158
return this.wrapRequest(async () => {
149-
if (this.variable && this.variable.rowCount && this.notebook) {
150-
const rows = await this.variableManager.getDataFrameRows(
151-
this.variable,
152-
this.notebook,
159+
if (this.dataProvider) {
160+
const dataFrameInfo = await this.getDataFrameInfo();
161+
const rows = await this.dataProvider.getRows(
153162
request.start,
154-
Math.min(request.end, this.variable.rowCount)
163+
Math.min(request.end, dataFrameInfo.rowCount ? dataFrameInfo.rowCount : 0)
155164
);
156165
return this.postMessage(DataViewerMessages.GetRowsResponse, {
157166
rows,

src/client/datascience/data-viewing/dataViewerProvider.ts renamed to src/client/datascience/data-viewing/dataViewerFactory.ts

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,14 @@ import { inject, injectable } from 'inversify';
77

88
import { IAsyncDisposable, IAsyncDisposableRegistry } from '../../common/types';
99
import { IServiceContainer } from '../../ioc/types';
10-
import { IDataViewer, IDataViewerProvider, IJupyterVariable, INotebook } from '../types';
11-
import { DataViewerDependencyService } from './dataViewerDependencyService';
10+
import { IDataViewer, IDataViewerDataProvider, IDataViewerFactory } from './types';
1211

1312
@injectable()
14-
export class DataViewerProvider implements IDataViewerProvider, IAsyncDisposable {
13+
export class DataViewerFactory implements IDataViewerFactory, IAsyncDisposable {
1514
private activeExplorers: IDataViewer[] = [];
1615
constructor(
1716
@inject(IServiceContainer) private serviceContainer: IServiceContainer,
18-
@inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry,
19-
@inject(DataViewerDependencyService) private dependencyService: DataViewerDependencyService
17+
@inject(IAsyncDisposableRegistry) asyncRegistry: IAsyncDisposableRegistry
2018
) {
2119
asyncRegistry.push(this);
2220
}
@@ -25,18 +23,17 @@ export class DataViewerProvider implements IDataViewerProvider, IAsyncDisposable
2523
await Promise.all(this.activeExplorers.map((d) => d.dispose()));
2624
}
2725

28-
public async create(variable: IJupyterVariable, notebook: INotebook): Promise<IDataViewer> {
26+
public async create(dataProvider: IDataViewerDataProvider, title: string): Promise<IDataViewer> {
2927
let result: IDataViewer | undefined;
3028

31-
// Create the data explorer (this should show the window)
29+
// Create the data explorer
3230
const dataExplorer = this.serviceContainer.get<IDataViewer>(IDataViewer);
3331
try {
34-
// Verify this is allowed.
35-
await this.dependencyService.checkAndInstallMissingDependencies(notebook.getMatchingInterpreter());
36-
3732
// Then load the data.
3833
this.activeExplorers.push(dataExplorer);
39-
await dataExplorer.showVariable(variable, notebook);
34+
35+
// Show the window and the data
36+
await dataExplorer.showData(dataProvider, title);
4037
result = dataExplorer;
4138
} finally {
4239
if (!result) {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import '../../common/extensions';
5+
6+
import { inject, injectable, named } from 'inversify';
7+
8+
import { Identifiers } from '../constants';
9+
import { IJupyterVariable, IJupyterVariableDataProvider, IJupyterVariables, INotebook } from '../types';
10+
import { DataViewerDependencyService } from './dataViewerDependencyService';
11+
import { ColumnType, IDataFrameInfo, IRowsResponse } from './types';
12+
13+
@injectable()
14+
export class JupyterVariableDataProvider implements IJupyterVariableDataProvider {
15+
private initialized: boolean = false;
16+
private notebook: INotebook | undefined;
17+
private variable: IJupyterVariable | undefined;
18+
19+
constructor(
20+
@inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableManager: IJupyterVariables,
21+
@inject(DataViewerDependencyService) private dependencyService: DataViewerDependencyService
22+
) {}
23+
24+
/**
25+
* Normalizes column types to the types the UI component understands.
26+
* Defaults to 'string'.
27+
* @param columns
28+
* @returns Array of columns with normalized type
29+
*/
30+
private static getNormalizedColumns(columns: { key: string; type: string }[]): { key: string; type: ColumnType }[] {
31+
return columns.map((column: { key: string; type: string }) => {
32+
let normalizedType: ColumnType;
33+
switch (column.type) {
34+
case 'bool':
35+
normalizedType = ColumnType.Bool;
36+
break;
37+
case 'integer':
38+
case 'int32':
39+
case 'int64':
40+
case 'float':
41+
case 'float32':
42+
case 'float64':
43+
case 'number':
44+
normalizedType = ColumnType.Number;
45+
break;
46+
default:
47+
normalizedType = ColumnType.String;
48+
}
49+
return {
50+
key: column.key,
51+
type: normalizedType
52+
};
53+
});
54+
}
55+
56+
public dispose(): void {
57+
return;
58+
}
59+
60+
public setDependencies(variable: IJupyterVariable, notebook: INotebook): void {
61+
this.notebook = notebook;
62+
this.variable = variable;
63+
}
64+
65+
public async getDataFrameInfo(): Promise<IDataFrameInfo> {
66+
let dataFrameInfo: IDataFrameInfo = {};
67+
await this.ensureInitialized();
68+
if (this.variable && this.notebook) {
69+
dataFrameInfo = {
70+
columns: this.variable.columns
71+
? JupyterVariableDataProvider.getNormalizedColumns(this.variable.columns)
72+
: this.variable.columns,
73+
indexColumn: this.variable.indexColumn,
74+
rowCount: this.variable.rowCount
75+
};
76+
}
77+
return dataFrameInfo;
78+
}
79+
80+
public async getAllRows() {
81+
let allRows: IRowsResponse = [];
82+
await this.ensureInitialized();
83+
if (this.variable && this.variable.rowCount && this.notebook) {
84+
const dataFrameRows = await this.variableManager.getDataFrameRows(
85+
this.variable,
86+
this.notebook,
87+
0,
88+
this.variable.rowCount
89+
);
90+
allRows = dataFrameRows && dataFrameRows.data ? (dataFrameRows.data as IRowsResponse) : [];
91+
}
92+
return allRows;
93+
}
94+
95+
public async getRows(start: number, end: number) {
96+
let rows: IRowsResponse = [];
97+
await this.ensureInitialized();
98+
if (this.variable && this.variable.rowCount && this.notebook) {
99+
const dataFrameRows = await this.variableManager.getDataFrameRows(this.variable, this.notebook, start, end);
100+
rows = dataFrameRows && dataFrameRows.data ? (dataFrameRows.data as IRowsResponse) : [];
101+
}
102+
return rows;
103+
}
104+
105+
private async ensureInitialized(): Promise<void> {
106+
// Postpone pre-req and variable initialization until data is requested.
107+
if (!this.initialized && this.variable && this.notebook) {
108+
this.initialized = true;
109+
await this.dependencyService.checkAndInstallMissingDependencies(this.notebook.getMatchingInterpreter());
110+
this.variable = await this.variableManager.getDataFrameInfo(this.variable, this.notebook);
111+
}
112+
}
113+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
'use strict';
4+
import '../../common/extensions';
5+
6+
import { inject, injectable } from 'inversify';
7+
8+
import { IServiceContainer } from '../../ioc/types';
9+
import {
10+
IJupyterVariable,
11+
IJupyterVariableDataProvider,
12+
IJupyterVariableDataProviderFactory,
13+
INotebook
14+
} from '../types';
15+
16+
@injectable()
17+
export class JupyterVariableDataProviderFactory implements IJupyterVariableDataProviderFactory {
18+
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) {}
19+
20+
public async create(variable: IJupyterVariable, notebook: INotebook): Promise<IJupyterVariableDataProvider> {
21+
const jupyterVariableDataProvider = this.serviceContainer.get<IJupyterVariableDataProvider>(
22+
IJupyterVariableDataProvider
23+
);
24+
jupyterVariableDataProvider.setDependencies(variable, notebook);
25+
return jupyterVariableDataProvider;
26+
}
27+
}

0 commit comments

Comments
 (0)