Skip to content

Commit c1a7e8c

Browse files
authored
Warn when using qgrid > 1.1.1 (#11280)
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.
1 parent 33e4c99 commit c1a7e8c

File tree

15 files changed

+215
-10
lines changed

15 files changed

+215
-10
lines changed

news/2 Fixes/11245.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Warn when using a version of the widget `qgrid` greater than `1.1.1` with the recommendation to downgrade to `1.1.1`.

package.nls.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,5 +474,6 @@
474474
"DataScience.loadThirdPartyWidgetScriptsPostEnabled": "Please restart the Kernel when changing the setting 'python.dataScience.widgetScriptSources'.",
475475
"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}).",
476476
"DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork": "Unable to load a compatible version of the widget '{0}'. Expected behavior may be affected.",
477-
"DataScience.unhandledMessage": "Unhandled kernel message from a widget: {0} : {1}"
477+
"DataScience.unhandledMessage": "Unhandled kernel message from a widget: {0} : {1}",
478+
"DataScience.qgridWidgetScriptVersionCompatibilityWarning": "Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1."
478479
}

src/client/common/utils/localize.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,10 @@ export namespace DataScience {
876876
'DataScience.widgetScriptNotFoundOnCDNWidgetMightNotWork',
877877
"Unable to load a compatible version of the widget '{0}'. Expected behavior may be affected."
878878
);
879+
export const qgridWidgetScriptVersionCompatibilityWarning = localize(
880+
'DataScience.qgridWidgetScriptVersionCompatibilityWarning',
881+
"Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1."
882+
);
879883
}
880884

881885
export namespace DebugConfigStrings {

src/client/datascience/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ export enum Telemetry {
290290
ZMQNotSupported = 'DATASCIENCE.ZMQ_NATIVE_BINARIES_NOT_LOADING',
291291
IPyWidgetLoadSuccess = 'DS_INTERNAL.IPYWIDGET_LOAD_SUCCESS',
292292
IPyWidgetLoadFailure = 'DS_INTERNAL.IPYWIDGET_LOAD_FAILURE',
293+
IPyWidgetWidgetVersionNotSupportedLoadFailure = 'DS_INTERNAL.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED_LOAD_FAILURE',
293294
IPyWidgetLoadDisabled = 'DS_INTERNAL.IPYWIDGET_LOAD_DISABLED',
294295
HashedIPyWidgetNameUsed = 'DS_INTERNAL.IPYWIDGET_USED_BY_USER',
295296
HashedIPyWidgetNameDiscovered = 'DS_INTERNAL.IPYWIDGET_DISCOVERED',

src/client/datascience/interactive-common/interactiveWindowTypes.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
CommonActionType,
1111
IAddCellAction,
1212
ILoadIPyWidgetClassFailureAction,
13-
LoadIPyWidgetClassLoadAction
13+
LoadIPyWidgetClassLoadAction,
14+
NotifyIPyWidgeWidgetVersionNotSupportedAction
1415
} from '../../../datascience-ui/interactive-common/redux/reducers/types';
1516
import { PythonInterpreter } from '../../interpreter/contracts';
1617
import { NativeKeyboardCommandTelemetry, NativeMouseCommandTelemetry } from '../constants';
@@ -117,7 +118,8 @@ export enum InteractiveWindowMessages {
117118
IPyWidgetLoadSuccess = 'ipywidget_load_success',
118119
IPyWidgetLoadFailure = 'ipywidget_load_failure',
119120
IPyWidgetRenderFailure = 'ipywidget_render_failure',
120-
IPyWidgetUnhandledKernelMessage = 'ipywidget_unhandled_kernel_message'
121+
IPyWidgetUnhandledKernelMessage = 'ipywidget_unhandled_kernel_message',
122+
IPyWidgetWidgetVersionNotSupported = 'ipywidget_widget_version_not_supported'
121123
}
122124

123125
export enum IPyWidgetMessages {
@@ -580,6 +582,7 @@ export class IInteractiveWindowMapping {
580582
public [InteractiveWindowMessages.UpdateDisplayData]: KernelMessage.IUpdateDisplayDataMsg;
581583
public [InteractiveWindowMessages.IPyWidgetLoadSuccess]: LoadIPyWidgetClassLoadAction;
582584
public [InteractiveWindowMessages.IPyWidgetLoadFailure]: ILoadIPyWidgetClassFailureAction;
585+
public [InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported]: NotifyIPyWidgeWidgetVersionNotSupportedAction;
583586
public [InteractiveWindowMessages.ConvertUriForUseInWebViewRequest]: Uri;
584587
public [InteractiveWindowMessages.ConvertUriForUseInWebViewResponse]: { request: Uri; response: Uri };
585588
public [InteractiveWindowMessages.IPyWidgetRenderFailure]: Error;

src/client/datascience/interactive-common/synchronization.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const messageWithMessageTypes: MessageMapping<IInteractiveWindowMapping> & Messa
8888
[CommonActionType.FOCUS_INPUT]: MessageType.other,
8989
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: MessageType.other,
9090
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: MessageType.other,
91+
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: MessageType.other,
9192
[CommonActionType.IPYWIDGET_RENDER_FAILURE]: MessageType.other,
9293

9394
// Types from InteractiveWindowMessages
@@ -119,6 +120,7 @@ const messageWithMessageTypes: MessageMapping<IInteractiveWindowMapping> & Messa
119120
[InteractiveWindowMessages.IPyWidgetLoadFailure]: MessageType.other,
120121
[InteractiveWindowMessages.IPyWidgetRenderFailure]: MessageType.other,
121122
[InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage]: MessageType.other,
123+
[InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported]: MessageType.other,
122124
[InteractiveWindowMessages.LoadAllCells]: MessageType.other,
123125
[InteractiveWindowMessages.LoadAllCellsComplete]: MessageType.other,
124126
[InteractiveWindowMessages.LoadOnigasmAssemblyRequest]: MessageType.other,

src/client/datascience/ipywidgets/ipywidgetHandler.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import stripAnsi from 'strip-ansi';
99
import { Event, EventEmitter, Uri } from 'vscode';
1010
import {
1111
ILoadIPyWidgetClassFailureAction,
12-
LoadIPyWidgetClassLoadAction
12+
LoadIPyWidgetClassLoadAction,
13+
NotifyIPyWidgeWidgetVersionNotSupportedAction
1314
} from '../../../datascience-ui/interactive-common/redux/reducers/types';
1415
import { EnableIPyWidgets } from '../../common/experimentGroups';
1516
import { traceError, traceInfo } from '../../common/logger';
@@ -75,6 +76,8 @@ export class IPyWidgetHandler implements IInteractiveWindowListener {
7576
this.sendLoadSucceededTelemetry(payload);
7677
} else if (message === InteractiveWindowMessages.IPyWidgetLoadFailure) {
7778
this.sendLoadFailureTelemetry(payload);
79+
} else if (message === InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported) {
80+
this.sendUnsupportedWidgetVersionFailureTelemetry(payload);
7881
} else if (message === InteractiveWindowMessages.IPyWidgetRenderFailure) {
7982
this.sendRenderFailureTelemetry(payload);
8083
} else if (message === InteractiveWindowMessages.IPyWidgetUnhandledKernelMessage) {
@@ -111,6 +114,16 @@ export class IPyWidgetHandler implements IInteractiveWindowListener {
111114
// do nothing on failure
112115
}
113116
}
117+
private sendUnsupportedWidgetVersionFailureTelemetry(payload: NotifyIPyWidgeWidgetVersionNotSupportedAction) {
118+
try {
119+
sendTelemetryEvent(Telemetry.IPyWidgetWidgetVersionNotSupportedLoadFailure, 0, {
120+
moduleHash: this.hash(payload.moduleName),
121+
moduleVersion: payload.moduleVersion
122+
});
123+
} catch {
124+
// do nothing on failure
125+
}
126+
}
114127
private sendRenderFailureTelemetry(payload: Error) {
115128
try {
116129
traceError('Error rendering a widget: ', payload);

src/client/telemetry/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1974,6 +1974,10 @@ export interface IEventNamePropertyMapping {
19741974
// Whether we timedout getting the source of the script (fetching script source in extension code).
19751975
timedout: boolean;
19761976
};
1977+
/**
1978+
* Telemetry event sent when an ipywidget version that is not supported is used & we have trapped this and warned the user abou it.
1979+
*/
1980+
[Telemetry.IPyWidgetWidgetVersionNotSupportedLoadFailure]: { moduleHash: string; moduleVersion: string };
19771981
/**
19781982
* Telemetry event sent when an loading of 3rd party ipywidget JS scripts from 3rd party source has been disabled.
19791983
*/

src/datascience-ui/history-react/redux/reducers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const reducerMap: Partial<IInteractiveActionMapping> = {
4343
[CommonActionType.FOCUS_INPUT]: CommonEffects.focusInput,
4444
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: CommonEffects.handleLoadIPyWidgetClassSuccess,
4545
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: CommonEffects.handleLoadIPyWidgetClassFailure,
46+
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: CommonEffects.notifyAboutUnsupportedWidgetVersions,
4647
[CommonActionType.IPYWIDGET_RENDER_FAILURE]: CommonEffects.handleIPyWidgetRenderFailure,
4748

4849
// Messages from the webview (some are ignored)

src/datascience-ui/interactive-common/redux/reducers/commonEffects.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import {
1818
CommonReducerArg,
1919
ILoadIPyWidgetClassFailureAction,
2020
IOpenSettingsAction,
21-
LoadIPyWidgetClassLoadAction
21+
LoadIPyWidgetClassLoadAction,
22+
NotifyIPyWidgeWidgetVersionNotSupportedAction
2223
} from './types';
2324

2425
export namespace CommonEffects {
@@ -258,6 +259,42 @@ export namespace CommonEffects {
258259
return arg.prevState;
259260
}
260261
}
262+
export function notifyAboutUnsupportedWidgetVersions(
263+
arg: CommonReducerArg<CommonActionType, NotifyIPyWidgeWidgetVersionNotSupportedAction>
264+
): IMainState {
265+
// Find the first currently executing cell and add an error to its output
266+
let index = arg.prevState.cellVMs.findIndex((c) => c.cell.state === CellState.executing);
267+
268+
// If there isn't one, then find the latest that matches the current execution count.
269+
if (index < 0) {
270+
index = arg.prevState.cellVMs.findIndex(
271+
(c) => c.cell.data.execution_count === arg.prevState.currentExecutionCount
272+
);
273+
}
274+
if (index >= 0 && arg.prevState.cellVMs[index].cell.data.cell_type === 'code') {
275+
const newVMs = [...arg.prevState.cellVMs];
276+
const current = arg.prevState.cellVMs[index];
277+
278+
const errorMessage = getLocString(
279+
'DataScience.qgridWidgetScriptVersionCompatibilityWarning',
280+
"Unable to load a compatible version of the widget 'qgrid'. Consider downgrading to version 1.1.1."
281+
);
282+
newVMs[index] = Helpers.asCellViewModel({
283+
...current,
284+
uiSideError: errorMessage
285+
});
286+
287+
// Make sure to tell the extension so it can log telemetry.
288+
postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetWidgetVersionNotSupported, arg.payload.data);
289+
290+
return {
291+
...arg.prevState,
292+
cellVMs: newVMs
293+
};
294+
} else {
295+
return arg.prevState;
296+
}
297+
}
261298
export function handleIPyWidgetRenderFailure(arg: CommonReducerArg<CommonActionType, Error>): IMainState {
262299
// Make sure to tell the extension so it can log telemetry.
263300
postActionToExtension(arg, InteractiveWindowMessages.IPyWidgetRenderFailure, arg.payload.data);

src/datascience-ui/interactive-common/redux/reducers/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export enum CommonActionType {
5959
IPYWIDGET_RENDER_FAILURE = 'action.ipywidget_render_failure',
6060
LOAD_IPYWIDGET_CLASS_SUCCESS = 'action.load_ipywidget_class_success',
6161
LOAD_IPYWIDGET_CLASS_FAILURE = 'action.load_ipywidget_class_failure',
62+
IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED = 'action.ipywidget_widget_version_not_supported',
6263
LOADED_ALL_CELLS = 'action.loaded_all_cells',
6364
LINK_CLICK = 'action.link_click',
6465
MOVE_CELL_DOWN = 'action.move_cell_down',
@@ -136,6 +137,7 @@ export type CommonActionTypeMapping = {
136137
[CommonActionType.FOCUS_INPUT]: never | undefined;
137138
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: LoadIPyWidgetClassLoadAction;
138139
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: ILoadIPyWidgetClassFailureAction;
140+
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: NotifyIPyWidgeWidgetVersionNotSupportedAction;
139141
[CommonActionType.IPYWIDGET_RENDER_FAILURE]: Error;
140142
};
141143

@@ -233,5 +235,9 @@ export type LoadIPyWidgetClassLoadAction = {
233235
moduleName: string;
234236
moduleVersion: string;
235237
};
238+
export type NotifyIPyWidgeWidgetVersionNotSupportedAction = {
239+
moduleName: 'qgrid';
240+
moduleVersion: string;
241+
};
236242

237243
export type CommonAction<T = never | undefined> = ActionWithPayload<T, CommonActionType | InteractiveWindowMessages>;

src/datascience-ui/ipywidgets/container.tsx

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ import {
1919
CommonAction,
2020
CommonActionType,
2121
ILoadIPyWidgetClassFailureAction,
22-
LoadIPyWidgetClassLoadAction
22+
LoadIPyWidgetClassLoadAction,
23+
NotifyIPyWidgeWidgetVersionNotSupportedAction
2324
} from '../interactive-common/redux/reducers/types';
2425
import { IStore } from '../interactive-common/redux/store';
2526
import { PostOffice } from '../react-common/postOffice';
27+
import { warnAboutWidgetVersionsThatAreNotSupported } from './incompatibleWidgetHandler';
2628
import { WidgetManager } from './manager';
2729
import { registerScripts } from './requirejsRegistry';
2830

@@ -35,6 +37,7 @@ type Props = {
3537
export class WidgetManagerComponent extends React.Component<Props> {
3638
private readonly widgetManager: WidgetManager;
3739
private readonly widgetSourceRequests = new Map<string, Deferred<void>>();
40+
private readonly registeredWidgetSources = new Map<string, WidgetScriptSource>();
3841
private timedoutWaitingForWidgetsToGetLoaded?: boolean;
3942
private widgetsCanLoadFromCDN: boolean = false;
4043
private readonly loaderSettings = {
@@ -75,6 +78,7 @@ export class WidgetManagerComponent extends React.Component<Props> {
7578
// This happens when we have restarted a kernel.
7679
// If user changed the kernel, then some widgets might exist now and some might now.
7780
this.widgetSourceRequests.clear();
81+
this.registeredWidgetSources.clear();
7882
}
7983
return true;
8084
}
@@ -105,6 +109,7 @@ export class WidgetManagerComponent extends React.Component<Props> {
105109

106110
// Now resolve promises (anything that was waiting for modules to get registered can carry on).
107111
sources.forEach((source) => {
112+
this.registeredWidgetSources.set(source.moduleName, source);
108113
// We have fetched the script sources for all of these modules.
109114
// In some cases we might not have the source, meaning we don't have it or couldn't find it.
110115
let deferred = this.widgetSourceRequests.get(source.moduleName);
@@ -157,6 +162,21 @@ export class WidgetManagerComponent extends React.Component<Props> {
157162
}
158163
};
159164
}
165+
private createWidgetVersionNotSupportedErrorAction(
166+
moduleName: 'qgrid',
167+
moduleVersion: string
168+
): CommonAction<NotifyIPyWidgeWidgetVersionNotSupportedAction> {
169+
return {
170+
type: CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED,
171+
payload: {
172+
messageDirection: 'incoming',
173+
data: {
174+
moduleName,
175+
moduleVersion
176+
}
177+
}
178+
};
179+
}
160180
private async handleLoadError(
161181
className: string,
162182
moduleName: string,
@@ -213,10 +233,25 @@ export class WidgetManagerComponent extends React.Component<Props> {
213233
{ moduleName, moduleVersion }
214234
);
215235

216-
return deferred.promise.catch((ex) =>
217-
// tslint:disable-next-line: no-console
218-
console.error(`Failed to load Widget Script from Extension for for ${moduleName}, ${moduleVersion}`, ex)
219-
);
236+
return deferred.promise
237+
.then(() => {
238+
const widgetSource = this.registeredWidgetSources.get(moduleName);
239+
if (widgetSource) {
240+
warnAboutWidgetVersionsThatAreNotSupported(
241+
widgetSource,
242+
moduleVersion,
243+
this.widgetsCanLoadFromCDN,
244+
(info) =>
245+
this.props.store.dispatch(
246+
this.createWidgetVersionNotSupportedErrorAction(info.moduleName, info.moduleVersion)
247+
)
248+
);
249+
}
250+
})
251+
.catch((ex) =>
252+
// tslint:disable-next-line: no-console
253+
console.error(`Failed to load Widget Script from Extension for for ${moduleName}, ${moduleVersion}`, ex)
254+
);
220255
}
221256

222257
private handleLoadSuccess(className: string, moduleName: string, moduleVersion: string) {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import * as semver from 'semver';
7+
import { WidgetScriptSource } from '../../client/datascience/ipywidgets/types';
8+
const supportedVersionOfQgrid = '1.1.1';
9+
const qgridModuleName = 'qgrid';
10+
11+
/**
12+
* For now only warns about qgrid.
13+
* Warn user about qgrid versions > 1.1.1 (we know CDN isn't available for newer versions and local widget source will not work).
14+
* Recommend to downgrade to 1.1.1.
15+
* Returns `true` if a warning has been displayed.
16+
*/
17+
export function warnAboutWidgetVersionsThatAreNotSupported(
18+
widgetSource: WidgetScriptSource,
19+
moduleVersion: string,
20+
cdnSupported: boolean,
21+
errorDispatcher: (info: { moduleName: typeof qgridModuleName; moduleVersion: string }) => void
22+
) {
23+
// if widget exists on CDN or CDN is disabled, get out.
24+
if (widgetSource.source === 'cdn' || !cdnSupported) {
25+
return false;
26+
}
27+
// Warn about qrid.
28+
if (widgetSource.moduleName !== qgridModuleName) {
29+
return false;
30+
}
31+
// We're only interested in versions > 1.1.1.
32+
try {
33+
// If we have an exact version, & if that is <= 1.1.1, then no warning needs to be displayed.
34+
if (!moduleVersion.startsWith('^') && semver.compare(moduleVersion, supportedVersionOfQgrid) <= 0) {
35+
return false;
36+
}
37+
// If we have a version range, then check the range.
38+
// Basically if our minimum version 1.1.1 is met, then nothing to do.
39+
// Eg. requesting script source for version `^1.3.0`.
40+
if (moduleVersion.startsWith('^') && semver.satisfies(supportedVersionOfQgrid, moduleVersion)) {
41+
return false;
42+
}
43+
} catch {
44+
return false;
45+
}
46+
errorDispatcher({ moduleName: widgetSource.moduleName, moduleVersion });
47+
}

src/datascience-ui/native-editor/redux/reducers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const reducerMap: Partial<INativeEditorActionMapping> = {
6161
[CommonActionType.UNMOUNT]: Creation.unmount,
6262
[CommonActionType.LOAD_IPYWIDGET_CLASS_SUCCESS]: CommonEffects.handleLoadIPyWidgetClassSuccess,
6363
[CommonActionType.LOAD_IPYWIDGET_CLASS_FAILURE]: CommonEffects.handleLoadIPyWidgetClassFailure,
64+
[CommonActionType.IPYWIDGET_WIDGET_VERSION_NOT_SUPPORTED]: CommonEffects.notifyAboutUnsupportedWidgetVersions,
6465

6566
// Messages from the webview (some are ignored)
6667
[InteractiveWindowMessages.StartCell]: Creation.startCell,

0 commit comments

Comments
 (0)