delegate requests from the debug variable view as well (#15681)

* delegate requests from the variable view as well

* add new file

* fix return types, rename command

* update test, get full variable for variable view request

* remove test.only
This commit is contained in:
Aaron Munger 2024-05-16 15:51:15 -07:00 коммит произвёл GitHub
Родитель ca1db4e9e7
Коммит df007bf29a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
10 изменённых файлов: 140 добавлений и 101 удалений

Просмотреть файл

@ -2033,7 +2033,7 @@
],
"jupyterVariableViewers": [
{
"command": "jupyter.showDataViewer",
"command": "jupyter.showJupyterDataViewer",
"title": "%jupyter.command.jupyter.showDataViewer.title%",
"dataTypes": [
"DataFrame",

Просмотреть файл

@ -176,6 +176,7 @@ export interface ICommandNameArgumentTypeMapping {
[DSCommands.LatestExtension]: [string];
[DSCommands.EnableLoadingWidgetsFrom3rdPartySource]: [];
[DSCommands.ShowDataViewer]: [IJupyterVariable | IShowDataViewerFromVariablePanel];
[DSCommands.ShowJupyterDataViewer]: [IJupyterVariable];
[DSCommands.RefreshDataViewer]: [];
[DSCommands.ClearSavedJupyterUris]: [];
[DSCommands.RunByLine]: [NotebookCell];

Просмотреть файл

@ -225,6 +225,7 @@ export namespace Commands {
export const LatestExtension = 'jupyter.latestExtension';
export const EnableLoadingWidgetsFrom3rdPartySource = 'jupyter.enableLoadingWidgetScriptsFromThirdPartySource';
export const ShowDataViewer = 'jupyter.showDataViewer';
export const ShowJupyterDataViewer = 'jupyter.showJupyterDataViewer';
export const RefreshDataViewer = 'jupyter.refreshDataViewer';
export const ClearSavedJupyterUris = 'jupyter.clearSavedJupyterUris';
export const OpenVariableView = 'jupyter.openVariableView';

Просмотреть файл

@ -112,6 +112,7 @@ suite('DataViewer @webview', function () {
evaluateName: 'my_list',
name: 'my_list',
value: '[1, 2, 3]',
type: 'list',
variablesReference
}
};

Просмотреть файл

@ -33,6 +33,7 @@ import { PythonEnvironment } from '../../../platform/pythonEnvironments/info';
import { IKernelProvider } from '../../../kernels/types';
import { IInteractiveWindowProvider } from '../../../interactive-window/types';
import { IShowDataViewerFromVariablePanel } from '../../../messageTypes';
import { DataViewerDelegator } from './dataViewerDelegator';
export const PromptAboutDeprecation = 'ds_prompt_about_deprecation';
@ -60,7 +61,8 @@ export class DataViewerCommandRegistry implements IExtensionSyncActivationServic
@inject(IKernelProvider) private readonly kernelProvider: IKernelProvider,
@inject(IInteractiveWindowProvider) private interactiveWindowProvider: IInteractiveWindowProvider,
@inject(IExperimentService) private readonly experimentService: IExperimentService,
@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalMemento: Memento
@inject(IMemento) @named(GLOBAL_MEMENTO) private readonly globalMemento: Memento,
@inject(DataViewerDelegator) private readonly dataViewerDelegator: DataViewerDelegator
) {
this.dataViewerChecker = new DataViewerChecker(configService);
if (!workspace.isTrusted) {
@ -74,7 +76,8 @@ export class DataViewerCommandRegistry implements IExtensionSyncActivationServic
if (!workspace.isTrusted) {
return;
}
this.registerCommand(Commands.ShowDataViewer, this.onVariablePanelShowDataViewerRequest);
this.registerCommand(Commands.ShowDataViewer, this.delegateDataViewer);
this.registerCommand(Commands.ShowJupyterDataViewer, this.showJupyterVariableView);
}
private registerCommand<
E extends keyof ICommandNameArgumentTypeMapping,
@ -84,8 +87,26 @@ export class DataViewerCommandRegistry implements IExtensionSyncActivationServic
const disposable = commands.registerCommand(command, callback, this);
this.disposables.push(disposable);
}
private async onVariablePanelShowDataViewerRequest(request: IJupyterVariable | IShowDataViewerFromVariablePanel) {
const requestVariable = 'variable' in request ? request.variable : request;
private async delegateDataViewer(request: IJupyterVariable | IShowDataViewerFromVariablePanel) {
const variable = 'variable' in request ? await this.getVariableFromRequest(request) : request;
if (!variable) {
return;
}
return this.dataViewerDelegator.showContributedDataViewer(variable);
}
// get the information needed about the request from the debug variable view
private async getVariableFromRequest(request: IShowDataViewerFromVariablePanel) {
if (this.variableProvider) {
const variable = convertDebugProtocolVariableToIJupyterVariable(
request.variable as unknown as DebugProtocol.Variable
);
return this.variableProvider.getFullVariable(variable);
}
}
private async showJupyterVariableView(requestVariable: IJupyterVariable) {
sendTelemetryEvent(EventName.OPEN_DATAVIEWER_FROM_VARIABLE_WINDOW_REQUEST);
// DataViewerDeprecation
@ -128,17 +149,13 @@ export class DataViewerCommandRegistry implements IExtensionSyncActivationServic
pythonEnv && (await this.dataViewerDependencyService.checkAndInstallMissingDependencies(pythonEnv));
}
const variable = convertDebugProtocolVariableToIJupyterVariable(
requestVariable as unknown as DebugProtocol.Variable
);
const jupyterVariable = await this.variableProvider.getFullVariable(variable);
const jupyterVariableDataProvider = await this.jupyterVariableDataProviderFactory.create(
jupyterVariable
requestVariable
);
const dataFrameInfo = await jupyterVariableDataProvider.getDataFrameInfo();
const columnSize = dataFrameInfo?.columns?.length;
if (columnSize && (await this.dataViewerChecker.isRequestedColumnSizeAllowed(columnSize))) {
const title: string = `${DataScience.dataExplorerTitle} - ${jupyterVariable.name}`;
const title: string = `${DataScience.dataExplorerTitle} - ${requestVariable.name}`;
const dv = await this.dataViewerFactory.create(jupyterVariableDataProvider, title);
sendTelemetryEvent(EventName.OPEN_DATAVIEWER_FROM_VARIABLE_WINDOW_SUCCESS);
return dv;

Просмотреть файл

@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { commands, Extension, QuickPickItem, window, extensions } from 'vscode';
import { Experiments, IExperimentService } from '../../../platform/common/types';
import { Commands, JVSC_EXTENSION_ID, Telemetry } from '../../../platform/common/constants';
import { inject, injectable } from 'inversify';
import { IJupyterVariable } from '../../../kernels/variables/types';
import { IVariableViewer } from '../variablesView/types';
import { noop } from '../../../platform/common/utils/misc';
import { sendTelemetryEvent } from '../../../platform/telemetry';
import { logger } from '../../../platform/logging';
import * as localize from '../../../platform/common/utils/localize';
@injectable()
export class DataViewerDelegator {
constructor(@inject(IExperimentService) private readonly experiments: IExperimentService) {}
public async showContributedDataViewer(variable: IJupyterVariable) {
try {
if (this.experiments.inExperiment(Experiments.DataViewerContribution)) {
// jupyterVariableViewers
const variableViewers = this.getMatchingVariableViewers(variable);
if (variableViewers.length === 0) {
// No data frame viewer extensions, show notifications
return commands.executeCommand('workbench.extensions.search', '@tag:jupyterVariableViewers');
} else if (variableViewers.length === 1) {
const command = variableViewers[0].jupyterVariableViewers.command;
return commands.executeCommand(command, variable);
} else {
const thirdPartyViewers = variableViewers.filter((d) => d.extension.id !== JVSC_EXTENSION_ID);
if (thirdPartyViewers.length === 1) {
const command = thirdPartyViewers[0].jupyterVariableViewers.command;
return commands.executeCommand(command, variable);
}
// show quick pick
const quickPick = window.createQuickPick<QuickPickItem & { command: string }>();
quickPick.title = 'Select DataFrame Viewer';
quickPick.items = variableViewers.map((d) => {
return {
label: d.jupyterVariableViewers.title,
detail: d.extension.packageJSON?.displayName ?? d.extension.id,
command: d.jupyterVariableViewers.command
};
});
quickPick.onDidAccept(async () => {
const item = quickPick.selectedItems[0];
if (item) {
quickPick.hide();
return commands.executeCommand(item.command, variable);
}
});
quickPick.show();
}
} else {
return commands.executeCommand(Commands.ShowJupyterDataViewer, variable);
}
} catch (e) {
logger.error(e);
sendTelemetryEvent(Telemetry.FailedShowDataViewer);
window.showErrorMessage(localize.DataScience.showDataViewerFail).then(noop, noop);
}
}
private getMatchingVariableViewers(
variable: IJupyterVariable
): { extension: Extension<unknown>; jupyterVariableViewers: IVariableViewer }[] {
const variableViewers = this.getVariableViewers();
return variableViewers.filter((d) => d.jupyterVariableViewers.dataTypes.includes(variable.type));
}
public getVariableViewers(): { extension: Extension<unknown>; jupyterVariableViewers: IVariableViewer }[] {
const variableViewers = extensions.all
.filter(
(e) =>
e.packageJSON?.contributes?.jupyterVariableViewers &&
e.packageJSON?.contributes?.jupyterVariableViewers.length
)
.map((e) => {
const contributes = e.packageJSON?.contributes;
if (contributes?.jupyterVariableViewers) {
return contributes.jupyterVariableViewers.map((jupyterVariableViewers: IVariableViewer) => ({
extension: e,
jupyterVariableViewers
}));
}
return [];
})
.flat();
return variableViewers;
}
}

Просмотреть файл

@ -5,6 +5,7 @@ import { IExtensionSyncActivationService } from '../../platform/activation/types
import { IServiceManager } from '../../platform/ioc/types';
import { DataViewer } from './dataviewer/dataViewer';
import { DataViewerCommandRegistry } from './dataviewer/dataViewerCommandRegistry';
import { DataViewerDelegator } from './dataviewer/dataViewerDelegator';
import { DataViewerDependencyService } from './dataviewer/dataViewerDependencyService.node';
import { DataViewerFactory } from './dataviewer/dataViewerFactory';
import { JupyterVariableDataProvider } from './dataviewer/jupyterVariableDataProvider';
@ -46,6 +47,7 @@ export function registerTypes(serviceManager: IServiceManager) {
IExtensionSyncActivationService,
DataViewerCommandRegistry
);
serviceManager.addSingleton<DataViewerDelegator>(DataViewerDelegator, DataViewerDelegator);
// Plot Viewer
serviceManager.add<IPlotViewer>(IPlotViewer, PlotViewer);

Просмотреть файл

@ -28,6 +28,7 @@ import { PlotViewHandler } from './plotView/plotViewHandler';
import { RendererCommunication } from './plotView/rendererCommunication';
import { IPlotSaveHandler } from './plotView/types';
import { IPyWidgetRendererComms } from './ipywidgets/rendererComms';
import { DataViewerDelegator } from './dataviewer/dataViewerDelegator';
export function registerTypes(serviceManager: IServiceManager) {
serviceManager.addSingleton<IExtensionSyncActivationService>(
@ -46,6 +47,7 @@ export function registerTypes(serviceManager: IServiceManager) {
IDataViewerDependencyService,
DataViewerDependencyService
);
serviceManager.addSingleton<DataViewerDelegator>(DataViewerDelegator, DataViewerDelegator);
// Plot Viewer
serviceManager.add<IPlotViewer>(IPlotViewer, PlotViewer);

Просмотреть файл

@ -1,14 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { Extension, QuickPickItem, Uri, commands, WebviewView as vscodeWebviewView, window } from 'vscode';
import { Uri, WebviewView as vscodeWebviewView, window } from 'vscode';
import { joinPath } from '../../../platform/vscode-path/resources';
import { capturePerfTelemetry, sendTelemetryEvent, Telemetry } from '../../../telemetry';
import { INotebookWatcher, IVariableViewPanelMapping, IVariableViewer } from './types';
import { INotebookWatcher, IVariableViewPanelMapping } from './types';
import { VariableViewMessageListener } from './variableViewMessageListener';
import { InteractiveWindowMessages, IShowDataViewer } from '../../../messageTypes';
import {
IJupyterVariable,
IJupyterVariables,
IJupyterVariablesRequest,
IJupyterVariablesResponse
@ -25,12 +24,10 @@ import {
IExperimentService,
Experiments
} from '../../../platform/common/types';
import * as localize from '../../../platform/common/utils/localize';
import { WebviewViewHost } from '../../../platform/webviews/webviewViewHost';
import { swallowExceptions } from '../../../platform/common/utils/decorators';
import { noop } from '../../../platform/common/utils/misc';
import { Commands, JVSC_EXTENSION_ID } from '../../../platform/common/constants';
import { extensions } from 'vscode';
import { DataViewerDelegator } from '../dataviewer/dataViewerDelegator';
// This is the client side host for the native notebook variable view webview
// It handles passing messages to and from the react view as well as the connection
@ -46,7 +43,8 @@ export class VariableView extends WebviewViewHost<IVariableViewPanelMapping> imp
private readonly variables: IJupyterVariables,
private readonly disposables: IDisposableRegistry,
private readonly notebookWatcher: INotebookWatcher,
private readonly experiments: IExperimentService
private readonly experiments: IExperimentService,
private readonly dataViewerDelegator: DataViewerDelegator
) {
const variableViewDir = joinPath(context.extensionUri, 'dist', 'webviews', 'webview-side', 'viewers');
super(configuration, (c, d) => new VariableViewMessageListener(c, d), provider, variableViewDir, [
@ -138,90 +136,11 @@ export class VariableView extends WebviewViewHost<IVariableViewPanelMapping> imp
@swallowExceptions()
public async showDataViewer(request: IShowDataViewer) {
request.variable.fileName = request.variable.fileName ?? this.notebookWatcher.activeKernel?.notebook.uri;
try {
if (this.experiments.inExperiment(Experiments.DataViewerContribution)) {
// jupyterVariableViewers
const variableViewers = this.getMatchingVariableViewers(request.variable);
if (variableViewers.length === 0) {
// No data frame viewer extensions, show notifications
await commands.executeCommand('workbench.extensions.search', '@tag:jupyterVariableViewers');
return;
} else if (variableViewers.length === 1) {
const command = variableViewers[0].jupyterVariableViewers.command;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return commands.executeCommand(command as any, request.variable);
} else {
const thirdPartyViewers = variableViewers.filter((d) => d.extension.id !== JVSC_EXTENSION_ID);
if (thirdPartyViewers.length === 1) {
const command = thirdPartyViewers[0].jupyterVariableViewers.command;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return commands.executeCommand(command as any, request.variable);
}
// show quick pick
const quickPick = window.createQuickPick<QuickPickItem & { command: string }>();
quickPick.title = 'Select DataFrame Viewer';
quickPick.items = variableViewers.map((d) => {
return {
label: d.jupyterVariableViewers.title,
detail: d.extension.packageJSON?.displayName ?? d.extension.id,
command: d.jupyterVariableViewers.command
};
});
quickPick.onDidAccept(() => {
const item = quickPick.selectedItems[0];
if (item) {
quickPick.hide();
commands
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.executeCommand(item.command as any, request.variable)
.then(noop, noop);
}
});
quickPick.show();
}
} else {
return commands.executeCommand(Commands.ShowDataViewer, request.variable);
}
} catch (e) {
logger.error(e);
sendTelemetryEvent(Telemetry.FailedShowDataViewer);
window.showErrorMessage(localize.DataScience.showDataViewerFail).then(noop, noop);
}
}
private getMatchingVariableViewers(
variable: IJupyterVariable
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): { extension: Extension<any>; jupyterVariableViewers: IVariableViewer }[] {
const variableViewers = this.getVariableViewers();
return variableViewers.filter((d) => d.jupyterVariableViewers.dataTypes.includes(variable.type));
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private getVariableViewers(): { extension: Extension<any>; jupyterVariableViewers: IVariableViewer }[] {
const variableViewers = extensions.all
.filter(
(e) =>
e.packageJSON?.contributes?.jupyterVariableViewers &&
e.packageJSON?.contributes?.jupyterVariableViewers.length
)
.map((e) => {
const contributes = e.packageJSON?.contributes;
if (contributes?.jupyterVariableViewers) {
return contributes.jupyterVariableViewers.map((jupyterVariableViewers: IVariableViewer) => ({
extension: e,
jupyterVariableViewers
}));
}
return [];
})
.flat();
return variableViewers;
return this.dataViewerDelegator.showContributedDataViewer(request.variable);
}
private postProcessSupportsDataExplorer(response: IJupyterVariablesResponse) {
const variableViewers = this.getVariableViewers();
const variableViewers = this.dataViewerDelegator.getVariableViewers();
response.pageResponse.forEach((variable) => {
if (this.experiments.inExperiment(Experiments.DataViewerContribution)) {
variable.supportsDataExplorer = variableViewers.some((d) =>

Просмотреть файл

@ -15,6 +15,7 @@ import {
import { createDeferred, Deferred } from '../../../platform/common/utils/async';
import { INotebookWatcher, IVariableViewProvider } from './types';
import { VariableView } from './variableView';
import { DataViewerDelegator } from '../dataviewer/dataViewerDelegator';
// This class creates our UI for our variable view and links it to the vs code webview view
@injectable()
@ -47,7 +48,8 @@ export class VariableViewProvider implements IVariableViewProvider {
@inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variables: IJupyterVariables,
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
@inject(INotebookWatcher) private readonly notebookWatcher: INotebookWatcher,
@inject(IExperimentService) private readonly experiments: IExperimentService
@inject(IExperimentService) private readonly experiments: IExperimentService,
@inject(DataViewerDelegator) private readonly dataViewerDelegator: DataViewerDelegator
) {}
public async resolveWebviewView(
@ -65,7 +67,8 @@ export class VariableViewProvider implements IVariableViewProvider {
this.variables,
this.disposables,
this.notebookWatcher,
this.experiments
this.experiments,
this.dataViewerDelegator
);
// If someone is waiting for the variable view resolve that here