diff --git a/news/2 Fixes/6273.md b/news/2 Fixes/6273.md new file mode 100644 index 000000000000..96a19e69b7d6 --- /dev/null +++ b/news/2 Fixes/6273.md @@ -0,0 +1 @@ +Add error messages if data_rate_limit is exceeded on remote (or local) connection. diff --git a/news/announce.py b/news/announce.py index d4d7dd1cfd66..71b2b5f48d7f 100644 --- a/news/announce.py +++ b/news/announce.py @@ -152,8 +152,7 @@ def complete_news(version, entry, previous_news): title, _, previous_news = previous_news.partition("\n") title = title.strip() previous_news = previous_news.strip() - section_title = (f"## {version} ({datetime.date.today().strftime('%d %B %Y')})" - ).replace("(0", "(") + section_title = f"## {version} ({datetime.date.today().strftime('%d %B %Y')})" # TODO: Insert the "Thank you!" section (in monthly releases)? return f"{title}\n\n{section_title}\n\n{entry.strip()}\n\n\n{previous_news}" diff --git a/package.nls.json b/package.nls.json index 6cbf1fcba1f2..e54345bdfcd8 100644 --- a/package.nls.json +++ b/package.nls.json @@ -277,5 +277,6 @@ "DataScience.imageListLabel": "Image", "DataScience.exportImageFailed": "Error exporting image: {0}", "downloading.file": "Downloading {0}...", - "downloading.file.progress": "{0}{1} of {2} KB ({3})" + "downloading.file.progress": "{0}{1} of {2} KB ({3})", + "DataScience.jupyterDataRateExceeded": "Cannot view variable because your data rate is set too low. Please restart your server with a data rate limit like so `--NotebookApp.iopub_data_rate_limit=10000000000.0`" } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 730bf565fc3e..62f6c5f67443 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -207,6 +207,7 @@ export namespace DataScience { export const selectedImageListLabel = localize('DataScience.selectedImageListLabel', 'Selected Image'); export const imageListLabel = localize('DataScience.imageListLabel', 'Image'); export const exportImageFailed = localize('DataScience.exportImageFailed', 'Error exporting image: {0}'); + export const jupyterDataRateExceeded = localize('DataScience.jupyterDataRateExceeded', 'Cannot view variable because your data rate is set too low. Please restart your server with a data rate limit like so `--NotebookApp.iopub_data_rate_limit=10000000000.0`'); } export namespace DebugConfigStrings { diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index 5bd1085c2e52..f27507b9fe47 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -148,6 +148,7 @@ export enum Telemetry { export namespace HelpLinks { export const PythonInteractiveHelpLink = 'https://aka.ms/pyaiinstall'; + export const JupyterDataRateHelpLink = 'https://aka.ms/AA5ggm0'; // This redirects here: https://jupyter-notebook.readthedocs.io/en/stable/config.html } export namespace Settings { diff --git a/src/client/datascience/data-viewing/dataViewer.ts b/src/client/datascience/data-viewing/dataViewer.ts index 3fa7597aba3d..3dcee41e4856 100644 --- a/src/client/datascience/data-viewing/dataViewer.ts +++ b/src/client/datascience/data-viewing/dataViewer.ts @@ -15,7 +15,8 @@ import * as localize from '../../common/utils/localize'; import { noop } from '../../common/utils/misc'; import { StopWatch } from '../../common/utils/stopWatch'; import { sendTelemetryEvent } from '../../telemetry'; -import { Telemetry } from '../constants'; +import { HelpLinks, Telemetry } from '../constants'; +import { JupyterDataRateLimitError } from '../jupyter/jupyterDataRateLimitError'; import { ICodeCssGenerator, IDataViewer, IJupyterVariable, IJupyterVariables, IThemeFinder } from '../types'; import { WebViewHost } from '../webViewHost'; import { DataViewerMessageListener } from './dataViewerMessageListener'; @@ -107,31 +108,42 @@ export class DataViewer extends WebViewHost implements IData } private async getAllRows() { - try { + return this.wrapRequest(async () => { if (this.variable && this.variable.rowCount) { const allRows = await this.variableManager.getDataFrameRows(this.variable, 0, this.variable.rowCount); this.pendingRowsCount = 0; return this.postMessage(DataViewerMessages.GetAllRowsResponse, allRows); } - } catch (e) { - traceError(e); - this.applicationShell.showErrorMessage(e); - } finally { - this.sendElapsedTimeTelemetry(); - } + }); } - private async getRowChunk(request: IGetRowsRequest) { - try { + private getRowChunk(request: IGetRowsRequest) { + return this.wrapRequest(async () => { if (this.variable && this.variable.rowCount) { const rows = await this.variableManager.getDataFrameRows(this.variable, request.start, Math.min(request.end, this.variable.rowCount)); return this.postMessage(DataViewerMessages.GetRowsResponse, { rows, start: request.start, end: request.end }); } + }); + } + + private async wrapRequest(func: () => Promise) { + try { + return await func(); } catch (e) { + if (e instanceof JupyterDataRateLimitError) { + traceError(e); + const actionTitle = localize.DataScience.pythonInteractiveHelpLink(); + this.applicationShell.showErrorMessage(e.toString(), actionTitle).then(v => { + // User clicked on the link, open it. + if (v === actionTitle) { + this.applicationShell.openUrl(HelpLinks.JupyterDataRateHelpLink); + } + }); + this.dispose(); + } traceError(e); this.applicationShell.showErrorMessage(e); } finally { - this.pendingRowsCount = Math.min(0, this.pendingRowsCount - request.end); this.sendElapsedTimeTelemetry(); } } diff --git a/src/client/datascience/jupyter/jupyterDataRateLimitError.ts b/src/client/datascience/jupyter/jupyterDataRateLimitError.ts new file mode 100644 index 000000000000..7e08688651dd --- /dev/null +++ b/src/client/datascience/jupyter/jupyterDataRateLimitError.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import * as localize from '../../common/utils/localize'; + +export class JupyterDataRateLimitError extends Error { + constructor() { + super(localize.DataScience.jupyterDataRateExceeded()); + } +} diff --git a/src/client/datascience/jupyter/jupyterVariables.ts b/src/client/datascience/jupyter/jupyterVariables.ts index 036d73719c4b..2ed29c63966b 100644 --- a/src/client/datascience/jupyter/jupyterVariables.ts +++ b/src/client/datascience/jupyter/jupyterVariables.ts @@ -14,6 +14,7 @@ import * as localize from '../../common/utils/localize'; import { EXTENSION_ROOT_DIR } from '../../constants'; import { Identifiers } from '../constants'; import { ICell, IInteractiveWindowProvider, IJupyterExecution, IJupyterVariable, IJupyterVariables } from '../types'; +import { JupyterDataRateLimitError } from './jupyterDataRateLimitError'; @injectable() export class JupyterVariables implements IJupyterVariables { @@ -142,6 +143,17 @@ export class JupyterVariables implements IJupyterVariables { const codeCell = cells[0].data as nbformat.ICodeCell; if (codeCell.outputs.length > 0) { const codeCellOutput = codeCell.outputs[0] as nbformat.IOutput; + if (codeCellOutput && codeCellOutput.output_type === 'stream' && codeCellOutput.name === 'stderr' && codeCellOutput.hasOwnProperty('text')) { + const resultString = codeCellOutput.text as string; + // See if this the IOPUB data rate limit problem + if (resultString.includes('iopub_data_rate_limit')) { + throw new JupyterDataRateLimitError(); + } else { + const error = localize.DataScience.jupyterGetVariablesExecutionError().format(resultString); + traceError(error); + throw new Error(error); + } + } if (codeCellOutput && codeCellOutput.output_type === 'stream' && codeCellOutput.hasOwnProperty('text')) { const resultString = codeCellOutput.text as string; return JSON.parse(resultString) as T; diff --git a/src/datascience-ui/react-common/settingsReactSide.ts b/src/datascience-ui/react-common/settingsReactSide.ts index 4b5649f775b7..fcf6ca1b8679 100644 --- a/src/datascience-ui/react-common/settingsReactSide.ts +++ b/src/datascience-ui/react-common/settingsReactSide.ts @@ -52,6 +52,7 @@ function load() { codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', showJupyterVariableExplorer: true, variableExplorerExclude: 'module;builtin_function_or_method', + enablePlotViewer: true, extraSettings: { editorCursor: 'line', editorCursorBlink: 'blink', diff --git a/src/test/datascience/color.test.ts b/src/test/datascience/color.test.ts index 092caf24a964..7e78e84df854 100644 --- a/src/test/datascience/color.test.ts +++ b/src/test/datascience/color.test.ts @@ -68,7 +68,8 @@ suite('Theme colors', () => { showJupyterVariableExplorer: true, variableExplorerExclude: 'module;builtin_function_or_method', codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', - markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)' + markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)', + enablePlotViewer: true }; configService = TypeMoq.Mock.ofType(); configService.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings); diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 6c5b1f8d3600..d45863b22156 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -376,7 +376,8 @@ export class DataScienceIocContainer extends UnitTestIocContainer { showJupyterVariableExplorer: true, variableExplorerExclude: 'module;builtin_function_or_method', liveShareConnectionTimeout: 100, - autoPreviewNotebooksInInteractivePane: true + autoPreviewNotebooksInInteractivePane: true, + enablePlotViewer: true }; const workspaceConfig: TypeMoq.IMock = TypeMoq.Mock.ofType(); diff --git a/src/test/datascience/editor-integration/codewatcher.unit.test.ts b/src/test/datascience/editor-integration/codewatcher.unit.test.ts index 543e61a5146b..c7c89d3b76e2 100644 --- a/src/test/datascience/editor-integration/codewatcher.unit.test.ts +++ b/src/test/datascience/editor-integration/codewatcher.unit.test.ts @@ -79,7 +79,8 @@ suite('DataScience Code Watcher Unit Tests', () => { variableExplorerExclude: 'module;builtin_function_or_method', codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)', - enableCellCodeLens: true + enableCellCodeLens: true, + enablePlotViewer: true }; // Setup the service container to return code watchers diff --git a/src/test/datascience/execution.unit.test.ts b/src/test/datascience/execution.unit.test.ts index 9fa58c18d8ae..47d7a5fe8737 100644 --- a/src/test/datascience/execution.unit.test.ts +++ b/src/test/datascience/execution.unit.test.ts @@ -551,7 +551,8 @@ suite('Jupyter Execution', async () => { variableExplorerExclude: 'module;builtin_function_or_method', codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)', - allowLiveShare: false + allowLiveShare: false, + enablePlotViewer: true }; // Service container also needs to generate jupyter servers. However we can't use a mock as that messes up returning diff --git a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts index ce484e242668..0f9eef3ab9b4 100644 --- a/src/test/datascience/interactiveWindowCommandListener.unit.test.ts +++ b/src/test/datascience/interactiveWindowCommandListener.unit.test.ts @@ -147,7 +147,8 @@ suite('Interactive window command listener', async () => { variableExplorerExclude: 'module;builtin_function_or_method', codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)', - autoPreviewNotebooksInInteractivePane: true + autoPreviewNotebooksInInteractivePane: true, + enablePlotViewer: true }; when(knownSearchPaths.getSearchPaths()).thenReturn(['/foo/bar']); diff --git a/src/test/datascience/interactiveWindowTestHelpers.tsx b/src/test/datascience/interactiveWindowTestHelpers.tsx index 057ebd9cf629..d37758162726 100644 --- a/src/test/datascience/interactiveWindowTestHelpers.tsx +++ b/src/test/datascience/interactiveWindowTestHelpers.tsx @@ -319,7 +319,8 @@ export function defaultDataScienceSettings(): IDataScienceSettings { showJupyterVariableExplorer: true, variableExplorerExclude: 'module;builtin_function_or_method', codeRegularExpression: '^(#\\s*%%|#\\s*\\|#\\s*In\\[\\d*?\\]|#\\s*In\\[ \\])', - markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)' + markdownRegularExpression: '^(#\\s*%%\\s*\\[markdown\\]|#\\s*\\)', + enablePlotViewer: true }; } diff --git a/src/test/datascience/mockJupyterManager.ts b/src/test/datascience/mockJupyterManager.ts index 56e6a2b1f44d..9646a1539599 100644 --- a/src/test/datascience/mockJupyterManager.ts +++ b/src/test/datascience/mockJupyterManager.ts @@ -100,6 +100,7 @@ export class MockJupyterManager implements IJupyterSessionManager { this.kernelSpecs.push({name: '0e8519db-0895-416c-96df-fa80131ecea0', dir: 'C:\\Users\\rchiodo\\AppData\\Roaming\\jupyter\\kernels\\0e8519db-0895-416c-96df-fa80131ecea0'}); // Setup our default cells that happen for everything + this.addCell(CodeSnippits.MatplotLibInitSvg); this.addCell(CodeSnippits.MatplotLibInitPng); this.addCell('matplotlib.style.use(\'dark_background\')'); this.addCell(`matplotlib.rcParams.update(${Identifiers.MatplotLibDefaultParams})`);