Skip to content

Warn when using qgrid > 1.1.1 #11280

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

Merged
merged 2 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/2 Fixes/11245.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Warn when using a version of the widget `qgrid` greater than `1.1.1` with the recommendation to downgrade to `1.1.1`.
3 changes: 2 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,5 +474,6 @@
"DataScience.loadThirdPartyWidgetScriptsPostEnabled": "Please restart the Kernel when changing the setting 'python.dataScience.widgetScriptSources'.",
"DataScience.enableCDNForWidgetsSetting": "Widgets require us to download supporting files from a 3rd party website. Click <a href='https://command:python.datascience.enableLoadingWidgetScriptsFromThirdPartySource'>here</a> to enable this or click <a href='https://aka.ms/PVSCIPyWidgets'>here</a> for more information. (Error loading {0}:{1}).",
"DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork": "Unable to load a compatible version of the widget '{0}'. Expected behavior may be affected.",
"DataScience.unhandledMessage": "Unhandled kernel message from a widget: {0} : {1}"
"DataScience.unhandledMessage": "Unhandled kernel message from a widget: {0} : {1}",
"DataScience.qgridWidgetScriptVersionCompatibilityWarning": "Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1."
}
4 changes: 4 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,10 @@ export namespace DataScience {
'DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork',
"Unable to load a compatible version of the widget '{0}'. Expected behavior may be affected."
);
export const qgridWidgetScriptVersionCompatibilityWarning = localize(
'DataScience.qgridWidgetScriptVersionCompatibilityWarning',
"Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1."
);
}

export namespace DebugConfigStrings {
Expand Down
1 change: 1 addition & 0 deletions src/client/datascience/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ export enum Telemetry {
ZMQNotSupported = 'DATASCIENCE.ZMQ_NATIVE_BINARIES_NOT_LOADING',
IPyWidgetLoadSuccess = 'DS_INTERNAL.IPYWIDGET_LOAD_SUCCESS',
IPyWidgetLoadFailure = 'DS_INTERNAL.IPYWIDGET_LOAD_FAILURE',
IPyWidgetWidgetVersionNotSupportedLoadFailure = 'DS_INTERNAL.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED_LOAD_FAILURE',
IPyWidgetLoadDisabled = 'DS_INTERNAL.IPYWIDGET_LOAD_DISABLED',
HashedIPyWidgetNameUsed = 'DS_INTERNAL.IPYWIDGET_USED_BY_USER',
HashedIPyWidgetNameDiscovered = 'DS_INTERNAL.IPYWIDGET_DISCOVERED',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
CommonActionType,
IAddCellAction,
ILoadIPyWidgetClassFailureAction,
LoadIPyWidgetClassLoadAction
LoadIPyWidgetClassLoadAction,
NotifyIPyWidgeWidgetVersionNotSupportedAction
} from '../../../datascience-ui/interactive-common/redux/reducers/types';
import { PythonInterpreter } from '../../interpreter/contracts';
import { NativeKeyboardCommandTelemetry, NativeMouseCommandTelemetry } from '../constants';
Expand Down Expand Up @@ -117,7 +118,8 @@ export enum InteractiveWindowMessages {
IPyWidgetLoadSuccess = 'ipywidget_load_success',
IPyWidgetLoadFailure = 'ipywidget_load_failure',
IPyWidgetRenderFailure = 'ipywidget_render_failure',
IPyWidgetUnhandledKernelMessage = 'ipywidget_unhandled_kernel_message'
IPyWidgetUnhandledKernelMessage = 'ipywidget_unhandled_kernel_message',
IPyWidgetWidgetVersionNotSupported = 'ipywidget_widget_version_not_supported'
}

export enum IPyWidgetMessages {
Expand Down Expand Up @@ -580,6 +582,7 @@ export class IInteractiveWindowMapping {
public [InteractiveWindowMessages.UpdateDisplayData]: KernelMessage.IUpdateDisplayDataMsg;
public [InteractiveWindowMessages.IPyWidgetLoadSuccess]: LoadIPyWidgetClassLoadAction;
public [InteractiveWindowMessages.IPyWidgetLoadFailure]: ILoadIPyWidgetClassFailureAction;
public [InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported]: NotifyIPyWidgeWidgetVersionNotSupportedAction;
public [InteractiveWindowMessages.ConvertUriForUseInWebViewRequest]: Uri;
public [InteractiveWindowMessages.ConvertUriForUseInWebViewResponse]: { request: Uri; response: Uri };
public [InteractiveWindowMessages.IPyWidgetRenderFailure]: Error;
Expand Down
2 changes: 2 additions & 0 deletions src/client/datascience/interactive-common/synchronization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const messageWithMessageTypes: MessageMapping<IInteractiveWindowMapping> & Messa
[CommonActionType.FOCUS_INPUT]: MessageType.other,
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: MessageType.other,
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: MessageType.other,
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: MessageType.other,
[CommonActionType.IPYWIDGET_RENDER_FAILURE]: MessageType.other,

// Types from InteractiveWindowMessages
Expand Down Expand Up @@ -119,6 +120,7 @@ const messageWithMessageTypes: MessageMapping<IInteractiveWindowMapping> & Messa
[InteractiveWindowMessages.IPyWidgetLoadFailure]: MessageType.other,
[InteractiveWindowMessages.IPyWidgetRenderFailure]: MessageType.other,
[InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage]: MessageType.other,
[InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported]: MessageType.other,
[InteractiveWindowMessages.LoadAllCells]: MessageType.other,
[InteractiveWindowMessages.LoadAllCellsComplete]: MessageType.other,
[InteractiveWindowMessages.LoadOnigasmAssemblyRequest]: MessageType.other,
Expand Down
15 changes: 14 additions & 1 deletion src/client/datascience/ipywidgets/ipywidgetHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import stripAnsi from 'strip-ansi';
import { Event, EventEmitter, Uri } from 'vscode';
import {
ILoadIPyWidgetClassFailureAction,
LoadIPyWidgetClassLoadAction
LoadIPyWidgetClassLoadAction,
NotifyIPyWidgeWidgetVersionNotSupportedAction
} from '../../../datascience-ui/interactive-common/redux/reducers/types';
import { EnableIPyWidgets } from '../../common/experimentGroups';
import { traceError, traceInfo } from '../../common/logger';
Expand Down Expand Up @@ -75,6 +76,8 @@ export class IPyWidgetHandler implements IInteractiveWindowListener {
this.sendLoadSucceededTelemetry(payload);
} else if (message === InteractiveWindowMessages.IPyWidgetLoadFailure) {
this.sendLoadFailureTelemetry(payload);
} else if (message === InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported) {
this.sendUnsupportedWidgetVersionFailureTelemetry(payload);
} else if (message === InteractiveWindowMessages.IPyWidgetRenderFailure) {
this.sendRenderFailureTelemetry(payload);
} else if (message === InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage) {
Expand Down Expand Up @@ -111,6 +114,16 @@ export class IPyWidgetHandler implements IInteractiveWindowListener {
// do nothing on failure
}
}
private sendUnsupportedWidgetVersionFailureTelemetry(payload: NotifyIPyWidgeWidgetVersionNotSupportedAction) {
try {
sendTelemetryEvent(Telemetry.IPyWidgetWidgetVersionNotSupportedLoadFailure, 0, {
moduleHash: this.hash(payload.moduleName),
moduleVersion: payload.moduleVersion
});
} catch {
// do nothing on failure
}
}
private sendRenderFailureTelemetry(payload: Error) {
try {
traceError('Error rendering a widget: ', payload);
Expand Down
4 changes: 4 additions & 0 deletions src/client/telemetry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1974,6 +1974,10 @@ export interface IEventNamePropertyMapping {
// Whether we timedout getting the source of the script (fetching script source in extension code).
timedout: boolean;
};
/**
* Telemetry event sent when an ipywidget version that is not supported is used & we have trapped this and warned the user abou it.
*/
[Telemetry.IPyWidgetWidgetVersionNotSupportedLoadFailure]: { moduleHash: string; moduleVersion: string };
/**
* Telemetry event sent when an loading of 3rd party ipywidget JS scripts from 3rd party source has been disabled.
*/
Expand Down
1 change: 1 addition & 0 deletions src/datascience-ui/history-react/redux/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const reducerMap: Partial<IInteractiveActionMapping> = {
[CommonActionType.FOCUS_INPUT]: CommonEffects.focusInput,
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: CommonEffects.handleLoadIPyWidgetClassSuccess,
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: CommonEffects.handleLoadIPyWidgetClassFailure,
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: CommonEffects.notifyAboutUnsupportedWidgetVersions,
[CommonActionType.IPYWIDGET_RENDER_FAILURE]: CommonEffects.handleIPyWidgetRenderFailure,

// Messages from the webview (some are ignored)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
CommonReducerArg,
ILoadIPyWidgetClassFailureAction,
IOpenSettingsAction,
LoadIPyWidgetClassLoadAction
LoadIPyWidgetClassLoadAction,
NotifyIPyWidgeWidgetVersionNotSupportedAction
} from './types';

export namespace CommonEffects {
Expand Down Expand Up @@ -258,6 +259,42 @@ export namespace CommonEffects {
return arg.prevState;
}
}
export function notifyAboutUnsupportedWidgetVersions(
arg: CommonReducerArg<CommonActionType, NotifyIPyWidgeWidgetVersionNotSupportedAction>
): IMainState {
// Find the first currently executing cell and add an error to its output
let index = arg.prevState.cellVMs.findIndex((c) => c.cell.state === CellState.executing);

// If there isn't one, then find the latest that matches the current execution count.
if (index < 0) {
index = arg.prevState.cellVMs.findIndex(
(c) => c.cell.data.execution_count === arg.prevState.currentExecutionCount
);
}
if (index >= 0 && arg.prevState.cellVMs[index].cell.data.cell_type === 'code') {
const newVMs = [...arg.prevState.cellVMs];
const current = arg.prevState.cellVMs[index];

const errorMessage = getLocString(
'DataScience.qgridWidgetScriptVersionCompatibilityWarning',
"Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1."
);
newVMs[index] = Helpers.asCellViewModel({
...current,
uiSideError: errorMessage
});

// Make sure to tell the extension so it can log telemetry.
postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported, arg.payload.data);

return {
...arg.prevState,
cellVMs: newVMs
};
} else {
return arg.prevState;
}
}
export function handleIPyWidgetRenderFailure(arg: CommonReducerArg<CommonActionType, Error>): IMainState {
// Make sure to tell the extension so it can log telemetry.
postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetRenderFailure, arg.payload.data);
Expand Down
6 changes: 6 additions & 0 deletions src/datascience-ui/interactive-common/redux/reducers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export enum CommonActionType {
IPYWIDGET_RENDER_FAILURE = 'action.ipywidget_render_failure',
LOAD_IPYWIDGET_CLASS_SUCCESS = 'action.load_ipywidget_class_success',
LOAD_IPYWIDGET_CLASS_FAILURE = 'action.load_ipywidget_class_failure',
IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED = 'action.ipywidget_widget_version_not_supported',
LOADED_ALL_CELLS = 'action.loaded_all_cells',
LINK_CLICK = 'action.link_click',
MOVE_CELL_DOWN = 'action.move_cell_down',
Expand Down Expand Up @@ -136,6 +137,7 @@ export type CommonActionTypeMapping = {
[CommonActionType.FOCUS_INPUT]: never | undefined;
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: LoadIPyWidgetClassLoadAction;
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: ILoadIPyWidgetClassFailureAction;
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: NotifyIPyWidgeWidgetVersionNotSupportedAction;
[CommonActionType.IPYWIDGET_RENDER_FAILURE]: Error;
};

Expand Down Expand Up @@ -233,5 +235,9 @@ export type LoadIPyWidgetClassLoadAction = {
moduleName: string;
moduleVersion: string;
};
export type NotifyIPyWidgeWidgetVersionNotSupportedAction = {
moduleName: 'qgrid';
moduleVersion: string;
};

export type CommonAction<T = never | undefined> = ActionWithPayload<T, CommonActionType | InteractiveWindowMessages>;
45 changes: 40 additions & 5 deletions src/datascience-ui/ipywidgets/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import {
CommonAction,
CommonActionType,
ILoadIPyWidgetClassFailureAction,
LoadIPyWidgetClassLoadAction
LoadIPyWidgetClassLoadAction,
NotifyIPyWidgeWidgetVersionNotSupportedAction
} from '../interactive-common/redux/reducers/types';
import { IStore } from '../interactive-common/redux/store';
import { PostOffice } from '../react-common/postOffice';
import { warnAboutWidgetVersionsThatAreNotSupported } from './incompatibleWidgetHandler';
import { WidgetManager } from './manager';
import { registerScripts } from './requirejsRegistry';

Expand All @@ -35,6 +37,7 @@ type Props = {
export class WidgetManagerComponent extends React.Component<Props> {
private readonly widgetManager: WidgetManager;
private readonly widgetSourceRequests = new Map<string, Deferred<void>>();
private readonly registeredWidgetSources = new Map<string, WidgetScriptSource>();
private timedoutWaitingForWidgetsToGetLoaded?: boolean;
private widgetsCanLoadFromCDN: boolean = false;
private readonly loaderSettings = {
Expand Down Expand Up @@ -75,6 +78,7 @@ export class WidgetManagerComponent extends React.Component<Props> {
// This happens when we have restarted a kernel.
// If user changed the kernel, then some widgets might exist now and some might now.
this.widgetSourceRequests.clear();
this.registeredWidgetSources.clear();
}
return true;
}
Expand Down Expand Up @@ -105,6 +109,7 @@ export class WidgetManagerComponent extends React.Component<Props> {

// Now resolve promises (anything that was waiting for modules to get registered can carry on).
sources.forEach((source) => {
this.registeredWidgetSources.set(source.moduleName, source);
// We have fetched the script sources for all of these modules.
// In some cases we might not have the source, meaning we don't have it or couldn't find it.
let deferred = this.widgetSourceRequests.get(source.moduleName);
Expand Down Expand Up @@ -157,6 +162,21 @@ export class WidgetManagerComponent extends React.Component<Props> {
}
};
}
private createWidgetVersionNotSupportedErrorAction(
moduleName: 'qgrid',
moduleVersion: string
): CommonAction<NotifyIPyWidgeWidgetVersionNotSupportedAction> {
return {
type: CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED,
payload: {
messageDirection: 'incoming',
data: {
moduleName,
moduleVersion
}
}
};
}
private async handleLoadError(
className: string,
moduleName: string,
Expand Down Expand Up @@ -213,10 +233,25 @@ export class WidgetManagerComponent extends React.Component<Props> {
{ moduleName, moduleVersion }
);

return deferred.promise.catch((ex) =>
// tslint:disable-next-line: no-console
console.error(`Failed to load Widget Script from Extension for for ${moduleName}, ${moduleVersion}`, ex)
);
return deferred.promise
.then(() => {
const widgetSource = this.registeredWidgetSources.get(moduleName);
if (widgetSource) {
warnAboutWidgetVersionsThatAreNotSupported(
widgetSource,
moduleVersion,
this.widgetsCanLoadFromCDN,
(info) =>
this.props.store.dispatch(
this.createWidgetVersionNotSupportedErrorAction(info.moduleName, info.moduleVersion)
)
);
}
})
.catch((ex) =>
// tslint:disable-next-line: no-console
console.error(`Failed to load Widget Script from Extension for for ${moduleName}, ${moduleVersion}`, ex)
);
}

private handleLoadSuccess(className: string, moduleName: string, moduleVersion: string) {
Expand Down
47 changes: 47 additions & 0 deletions src/datascience-ui/ipywidgets/incompatibleWidgetHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

'use strict';

import * as semver from 'semver';
import { WidgetScriptSource } from '../../client/datascience/ipywidgets/types';
const supportedVersionOfQgrid = '1.1.1';
const qgridModuleName = 'qgrid';

/**
* For now only warns about qgrid.
* Warn user about qgrid versions > 1.1.1 (we know CDN isn't available for newer versions and local widget source will not work).
* Recommend to downgrade to 1.1.1.
* Returns `true` if a warning has been displayed.
*/
export function warnAboutWidgetVersionsThatAreNotSupported(
widgetSource: WidgetScriptSource,
moduleVersion: string,
cdnSupported: boolean,
errorDispatcher: (info: { moduleName: typeof qgridModuleName; moduleVersion: string }) => void
) {
// if widget exists on CDN or CDN is disabled, get out.
if (widgetSource.source === 'cdn' || !cdnSupported) {
return false;
}
// Warn about qrid.
if (widgetSource.moduleName !== qgridModuleName) {
return false;
}
// We're only interested in versions > 1.1.1.
try {
// If we have an exact version, & if that is <= 1.1.1, then no warning needs to be displayed.
if (!moduleVersion.startsWith('^') && semver.compare(moduleVersion, supportedVersionOfQgrid) <= 0) {
return false;
}
// If we have a version range, then check the range.
// Basically if our minimum version 1.1.1 is met, then nothing to do.
// Eg. requesting script source for version `^1.3.0`.
if (moduleVersion.startsWith('^') && semver.satisfies(supportedVersionOfQgrid, moduleVersion)) {
return false;
}
} catch {
return false;
}
errorDispatcher({ moduleName: widgetSource.moduleName, moduleVersion });
}
1 change: 1 addition & 0 deletions src/datascience-ui/native-editor/redux/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const reducerMap: Partial<INativeEditorActionMapping> = {
[CommonActionType.UNMOUNT]: Creation.unmount,
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: CommonEffects.handleLoadIPyWidgetClassSuccess,
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: CommonEffects.handleLoadIPyWidgetClassFailure,
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: CommonEffects.notifyAboutUnsupportedWidgetVersions,

// Messages from the webview (some are ignored)
[InteractiveWindowMessages.StartCell]: Creation.startCell,
Expand Down
Loading