Skip to content

Commit

Permalink
Warn when using qgrid > 1.1.1 (#11280)
Browse files Browse the repository at this point in the history
For #11245
* Warn when using qgrid version > 1.1.1
* For now hardcoded to check only qgrid version 1.1.1.
* Should be easy enough to add others, didn't want to make it too generic.
  • Loading branch information
DonJayamanne committed Apr 24, 2020
1 parent dbc444c commit 88e141a
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 10 deletions.
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 @@ -470,5 +470,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 @@ -860,6 +860,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 @@ -292,6 +292,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 { WidgetScriptSource } from '../ipywidgets/types';
Expand Down Expand Up @@ -115,7 +116,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 @@ -607,6 +609,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 @@ -87,6 +87,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 @@ -117,6 +118,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 @@ -1992,6 +1992,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 @@ -41,6 +41,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 @@ -58,6 +58,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 @@ -134,6 +135,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 @@ -232,5 +234,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 @@ -38,6 +40,7 @@ export class WidgetManagerComponent extends React.Component<Props> {
string,
{ deferred: Deferred<void>; timer: NodeJS.Timeout | number | undefined }
>();
private readonly registeredWidgetSources = new Map<string, WidgetScriptSource>();
private timedoutWaitingForWidgetsToGetLoaded?: boolean;
private widgetsCanLoadFromCDN: boolean = false;
private readonly loaderSettings = {
Expand Down Expand Up @@ -78,6 +81,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 @@ -108,6 +112,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 request = this.widgetSourceRequests.get(source.moduleName);
Expand Down Expand Up @@ -167,6 +172,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 @@ -233,10 +253,25 @@ export class WidgetManagerComponent extends React.Component<Props> {
{ moduleName, moduleVersion }
);

return request.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 request.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 @@ -60,6 +60,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

0 comments on commit 88e141a

Please sign in to comment.