diff --git a/package.json b/package.json index d02aab2c8da7..0ddbfde0187a 100644 --- a/package.json +++ b/package.json @@ -770,6 +770,11 @@ "command": "python.datascience.gatherquality", "title": "DataScience.gatherQuality", "category": "Python" + }, + { + "command": "python.datascience.latestExtension", + "title": "DataScience.latestExtension", + "category": "Python" } ], "menus": { @@ -1281,6 +1286,12 @@ "category": "Python", "when": "false" }, + { + "command": "python.datascience.latestExtension", + "title": "%DataScience.latestExtension%", + "category": "Python", + "when": "false" + }, { "command": "python.datascience.export", "title": "%DataScience.notebookExportAs%", diff --git a/package.nls.json b/package.nls.json index 3de3b7a252e3..e5b4f3afe8e3 100644 --- a/package.nls.json +++ b/package.nls.json @@ -474,8 +474,8 @@ "DataScience.findJupyterCommandProgressCheckInterpreter": "Checking {0}.", "DataScience.findJupyterCommandProgressSearchCurrentPath": "Searching current path.", "DataScience.gatherError": "Gather internal error", - "DataScience.gatheredScriptDescription": "# This file was generated by the Gather Extension.\n#\n# The intent is that it contains only the code required to produce\n# the same results as the cell originally selected for gathering.\n# Please note that the Python analysis is quite conservative, so if\n# it is unsure whether a line of code is necessary for execution, it\n# will err on the side of including it.\n#\n# Please let us know if you are satisfied with what was gathered here:\n# https://aka.ms/gathersurvey\n\n", - "DataScience.gatheredNotebookDescriptionInMarkdown": "## Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by the Gather Extension. The intent is that it contains only the code and cells required to produce the same results as the cell originally selected for gathering. Please note that the Python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.|\n\n**Are you satisfied with the code that was gathered?**\n\n[Yes](https://command:python.datascience.gatherquality?yes) [No](https://command:python.datascience.gatherquality?no)", + "DataScience.gatheredScriptDescription": "# This file was generated by the Gather Extension.\n# It requires version 2020.7.94776 (or newer) of the Python Extension.\n#\n# The intent is that it contains only the code required to produce\n# the same results as the cell originally selected for gathering.\n# Please note that the Python analysis is quite conservative, so if\n# it is unsure whether a line of code is necessary for execution, it\n# will err on the side of including it.\n#\n# Please let us know if you are satisfied with what was gathered here:\n# https://aka.ms/gathersurvey\n\n", + "DataScience.gatheredNotebookDescriptionInMarkdown": "## Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by the Gather Extension. It requires version 2020.7.94776 (or newer) of the Python Extension, please update [here](https://command:python.datascience.latestExtension). The intent is that it contains only the code and cells required to produce the same results as the cell originally selected for gathering. Please note that the Python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.|\n\n**Are you satisfied with the code that was gathered?**\n\n[Yes](https://command:python.datascience.gatherquality?yes) [No](https://command:python.datascience.gatherquality?no)", "DataScience.savePngTitle": "Save Image", "DataScience.jupyterSelectURIQuickPickTitle": "Pick how to connect to Jupyter", "DataScience.jupyterSelectURIQuickPickPlaceholder": "Choose an option", @@ -514,6 +514,7 @@ "DataScience.jupyterSelectURIQuickPickTitleRemoteOnly": "Pick an already running jupyter server", "DataScience.jupyterSelectURIRemoteDetail": "Specify the URI of an existing server", "DataScience.gatherQuality": "Did gather work as desired?", + "DataScience.latestExtension": "Download the latest version of the Python Extension", "DataScience.loadClassFailedWithNoInternet": "Error loading {0}:{1}. Internet connection required for loading 3rd party widgets.", "DataScience.useCDNForWidgets": "Widgets require us to download supporting files from a 3rd party website. Click [here](https://aka.ms/PVSCIPyWidgets) for more information.", "DataScience.loadThirdPartyWidgetScriptsPostEnabled": "Please restart the Kernel when changing the setting 'python.dataScience.widgetScriptSources'.", @@ -557,9 +558,9 @@ "DataScienceRendererExtension.downloadCompletedOutputMessage": "Notebook Renderers extension download complete.", "DataScience.uriProviderDescriptionFormat": "{0} (From {1} extension)", "DataScience.unknownPackage": "unknown", - "DataScience.interactiveWindowTitleFormat" : "Python Interactive - {0}", - "DataScience.interactiveWindowModeBannerTitle" : "Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).", - "DataScience.interactiveWindowModeBannerSwitchYes" : "Yes", - "DataScience.interactiveWindowModeBannerSwitchAlways" : "Always", - "DataScience.interactiveWindowModeBannerSwitchNo" : "No" + "DataScience.interactiveWindowTitleFormat": "Python Interactive - {0}", + "DataScience.interactiveWindowModeBannerTitle": "Do you want to open a new Python Interactive window for this file? [More Information](command:workbench.action.openSettings?%5B%22python.dataScience.interactiveWindowMode%22%5D).", + "DataScience.interactiveWindowModeBannerSwitchYes": "Yes", + "DataScience.interactiveWindowModeBannerSwitchAlways": "Always", + "DataScience.interactiveWindowModeBannerSwitchNo": "No" } diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index 103e28a59fce..b4d127760c2c 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -182,6 +182,7 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu [DSCommands.SaveAsNotebookNonCustomEditor]: [Uri, Uri]; [DSCommands.OpenNotebookNonCustomEditor]: [Uri]; [DSCommands.GatherQuality]: [string]; + [DSCommands.LatestExtension]: [string]; [DSCommands.EnableLoadingWidgetsFrom3rdPartySource]: [undefined | never]; [DSCommands.TrustNotebook]: [undefined | never | Uri]; } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index 7d94f06707ab..6ddf5112b3b3 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -886,11 +886,11 @@ export namespace DataScience { export const gatherError = localize('DataScience.gatherError', 'Gather internal error'); export const gatheredScriptDescription = localize( 'DataScience.gatheredScriptDescription', - '# This file was generated by the Gather Extension.\n#\n# The intent is that it contains only the code required to produce\n# the same results as the cell originally selected for gathering.\n# Please note that the Python analysis is quite conservative, so if\n# it is unsure whether a line of code is necessary for execution, it\n# will err on the side of including it.\n#\n# Please let us know if you are satisfied with what was gathered here:\n# https://aka.ms/gathersurvey\n\n' + '# This file was generated by the Gather Extension.\n# It requires version 2020.7.94776 (or newer) of the Python Extension.\n#\n# The intent is that it contains only the code required to produce\n# the same results as the cell originally selected for gathering.\n# Please note that the Python analysis is quite conservative, so if\n# it is unsure whether a line of code is necessary for execution, it\n# will err on the side of including it.\n#\n# Please let us know if you are satisfied with what was gathered here:\n# https://aka.ms/gathersurvey\n\n' ); export const gatheredNotebookDescriptionInMarkdown = localize( 'DataScience.gatheredNotebookDescriptionInMarkdown', - '# Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by the Gather Extension. The intent is that it contains only the code and cells required to produce the same results as the cell originally selected for gathering. Please note that the Python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.|\n\n**Are you satisfied with the code that was gathered?**\n\n[Yes](https://command:python.datascience.gatherquality?yes) [No](https://command:python.datascience.gatherquality?no)' + '# Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by the Gather Extension. It requires version 2020.7.94776 (or newer) of the Python Extension, please update [here](https://command:python.datascience.latestExtension). The intent is that it contains only the code and cells required to produce the same results as the cell originally selected for gathering. Please note that the Python analysis is quite conservative, so if it is unsure whether a line of code is necessary for execution, it will err on the side of including it.|\n\n**Are you satisfied with the code that was gathered?**\n\n[Yes](https://command:python.datascience.gatherquality?yes) [No](https://command:python.datascience.gatherquality?no)' ); export const savePngTitle = localize('DataScience.savePngTitle', 'Save Image'); export const fallbackToUseActiveInterpeterAsKernel = localize( diff --git a/src/client/datascience/commands/commandRegistry.ts b/src/client/datascience/commands/commandRegistry.ts index aa279097fb4b..2d1eb1a79650 100644 --- a/src/client/datascience/commands/commandRegistry.ts +++ b/src/client/datascience/commands/commandRegistry.ts @@ -80,6 +80,7 @@ export class CommandRegistry implements IDisposable { this.registerCommand(Commands.CreateNewNotebook, this.createNewNotebook); this.registerCommand(Commands.ViewJupyterOutput, this.viewJupyterOutput); this.registerCommand(Commands.GatherQuality, this.reportGatherQuality); + this.registerCommand(Commands.LatestExtension, this.openPythonExtensionPage); this.registerCommand( Commands.EnableLoadingWidgetsFrom3rdPartySource, this.enableLoadingWidgetScriptsFromThirdParty @@ -394,7 +395,11 @@ export class CommandRegistry implements IDisposable { } private reportGatherQuality(val: string) { - sendTelemetryEvent(Telemetry.GatherQualityReport, undefined, { result: val === 'no' ? 'no' : 'yes' }); - env.openExternal(Uri.parse(`https://aka.ms/gathersurvey?succeed=${val}`)); + sendTelemetryEvent(Telemetry.GatherQualityReport, undefined, { result: val[0] === 'no' ? 'no' : 'yes' }); + env.openExternal(Uri.parse(`https://aka.ms/gathersurvey?succeed=${val[0]}`)); + } + + private openPythonExtensionPage() { + env.openExternal(Uri.parse(`https://marketplace.visualstudio.com/items?itemName=ms-python.python`)); } } diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index 76b014f2a6d8..fb94d0eac9ee 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -90,6 +90,7 @@ export namespace Commands { export const SaveAsNotebookNonCustomEditor = 'python.datascience.notebookeditor.saveAs'; export const OpenNotebookNonCustomEditor = 'python.datascience.notebookeditor.open'; export const GatherQuality = 'python.datascience.gatherquality'; + export const LatestExtension = 'python.datascience.latestExtension'; export const TrustNotebook = 'python.datascience.notebookeditor.trust'; export const EnableLoadingWidgetsFrom3rdPartySource = 'python.datascience.enableLoadingWidgetScriptsFromThirdPartySource'; @@ -320,6 +321,7 @@ export enum Telemetry { KernelInvalid = 'DS_INTERNAL.INVALID_KERNEL_USED', GatherIsInstalled = 'DS_INTERNAL.GATHER_IS_INSTALLED', GatherCompleted = 'DATASCIENCE.GATHER_COMPLETED', + GatherStats = 'DS_INTERNAL.GATHER_STATS', GatheredNotebookSaved = 'DATASCIENCE.GATHERED_NOTEBOOK_SAVED', GatherQualityReport = 'DS_INTERNAL.GATHER_QUALITY_REPORT', ZMQSupported = 'DS_INTERNAL.ZMQ_NATIVE_BINARIES_LOADING', diff --git a/src/client/datascience/gather/gatherListener.ts b/src/client/datascience/gather/gatherListener.ts index 470e7ef477a3..fc97d0f6097f 100644 --- a/src/client/datascience/gather/gatherListener.ts +++ b/src/client/datascience/gather/gatherListener.ts @@ -42,6 +42,8 @@ export class GatherListener implements IInteractiveWindowListener { private notebookUri: Uri | undefined; private gatherProvider: IGatherProvider | undefined; private gatherTimer: StopWatch | undefined; + private linesSubmitted: number = 0; + private cellsSubmitted: number = 0; constructor( @inject(IApplicationShell) private applicationShell: IApplicationShell, @@ -78,11 +80,19 @@ export class GatherListener implements IInteractiveWindowListener { break; case InteractiveWindowMessages.RestartKernel: + this.linesSubmitted = 0; + this.cellsSubmitted = 0; if (this.gatherProvider) { this.gatherProvider.resetLog(); } break; + case InteractiveWindowMessages.FinishCell: + const lineCount: number = payload.cell.data.source.length as number; + this.linesSubmitted += lineCount; + this.cellsSubmitted += 1; + break; + default: break; } @@ -155,6 +165,13 @@ export class GatherListener implements IInteractiveWindowListener { await this.showNotebook(slicedProgram, cell); sendTelemetryEvent(Telemetry.GatherCompleted, this.gatherTimer?.elapsedTime, { result: 'notebook' }); } + + sendTelemetryEvent(Telemetry.GatherStats, undefined, { + linesSubmitted: this.linesSubmitted, + cellsSubmitted: this.cellsSubmitted, + linesGathered: slicedProgram.splitLines().length, + cellsGathered: generateCellsFromString(slicedProgram).length + }); } }; diff --git a/src/client/datascience/interactive-common/linkProvider.ts b/src/client/datascience/interactive-common/linkProvider.ts index ea6461b66114..b8096ad616a6 100644 --- a/src/client/datascience/interactive-common/linkProvider.ts +++ b/src/client/datascience/interactive-common/linkProvider.ts @@ -19,6 +19,7 @@ const LineQueryRegex = /line=(\d+)/; // in a markdown cell using the syntax: https://command:[my.vscode.command]. const linkCommandWhitelist = [ 'python.datascience.gatherquality', + 'python.datascience.latestExtension', 'python.datascience.enableLoadingWidgetScriptsFromThirdPartySource' ]; @@ -52,8 +53,7 @@ export class LinkProvider implements IInteractiveWindowListener { this.openFile(href); } else if (href.startsWith('https://command:')) { const temp: string = href.split(':')[2]; - const params: string[] = - temp.includes('/?') && temp.includes(',') ? temp.split('/?')[1].split(',') : []; + const params: string[] = temp.includes('/?') ? temp.split('/?')[1].split(',') : []; let command = temp.split('/?')[0]; if (command.endsWith('/')) { command = command.substring(0, command.length - 1); diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 9b0071c21894..516146d6eeb9 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -2041,6 +2041,12 @@ export interface IEventNamePropertyMapping { */ result: 'err' | 'script' | 'notebook' | 'unavailable'; }; + [Telemetry.GatherStats]: { + linesSubmitted: number; + cellsSubmitted: number; + linesGathered: number; + cellsGathered: number; + }; /** * Telemetry event sent when a gathered notebook has been saved by the user. */