diff --git a/package.json b/package.json index 3f64489147ed..ca605ff667b9 100644 --- a/package.json +++ b/package.json @@ -596,6 +596,11 @@ "title": "%DataScience.selectKernel%", "category": "Python", "enablement": "python.datascience.isnativeactive" + }, + { + "command": "python.datascience.gatherquality", + "title": "%DataScience.gatherGood%", + "category": "Python" } ], "menus": { diff --git a/package.nls.json b/package.nls.json index 36a153817396..e7529070ee4d 100644 --- a/package.nls.json +++ b/package.nls.json @@ -418,7 +418,7 @@ "DataScience.findJupyterCommandProgressCheckInterpreter": "Checking {0}.", "DataScience.findJupyterCommandProgressSearchCurrentPath": "Searching current path.", "DataScience.gatheredScriptDescription":"# This file was generated by an experimental feature called 'Gather'.\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 an experimental feature called \"Gather\". 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://aka.ms/gathersurvey?succeed_value=1) [No](https://aka.ms/gathersurvey?succeed_value=0)", + "DataScience.gatheredNotebookDescriptionInMarkdown": "## Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by an experimental feature called \"Gather\". 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", diff --git a/src/client/common/application/commands.ts b/src/client/common/application/commands.ts index f75a1760ffd3..8da4a3d858b4 100644 --- a/src/client/common/application/commands.ts +++ b/src/client/common/application/commands.ts @@ -161,4 +161,5 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu [DSCommands.SaveNotebookNonCustomEditor]: [Uri]; [DSCommands.SaveAsNotebookNonCustomEditor]: [Uri, Uri]; [DSCommands.OpenNotebookNonCustomEditor]: [Uri]; + [DSCommands.GatherQuality]: [string]; } diff --git a/src/client/common/utils/localize.ts b/src/client/common/utils/localize.ts index b7e322b0029f..c17fbfc7672c 100644 --- a/src/client/common/utils/localize.ts +++ b/src/client/common/utils/localize.ts @@ -726,7 +726,7 @@ export namespace DataScience { ); export const gatheredNotebookDescriptionInMarkdown = localize( 'DataScience.gatheredNotebookDescriptionInMarkdown', - '# Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by an experimental feature called "Gather". 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://aka.ms/gathersurvey?succeed_value=1) [No](https://aka.ms/gathersurvey?succeed_value=0)' + '# Gathered Notebook\nGathered from ```{0}```\n\n| | |\n|---|---|\n|   |This notebook was generated by an experimental feature called "Gather". 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 a3e3ab123348..280a8d731d1c 100644 --- a/src/client/datascience/commands/commandRegistry.ts +++ b/src/client/datascience/commands/commandRegistry.ts @@ -4,12 +4,12 @@ 'use strict'; import { inject, injectable, multiInject, named, optional } from 'inversify'; -import { CodeLens, Range } from 'vscode'; +import { CodeLens, env, Range, Uri } from 'vscode'; import { ICommandNameArgumentTypeMapping } from '../../common/application/commands'; import { ICommandManager, IDebugService, IDocumentManager } from '../../common/application/types'; import { IDisposable, IOutputChannel } from '../../common/types'; import { DataScience } from '../../common/utils/localize'; -import { captureTelemetry } from '../../telemetry'; +import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { Commands, JUPYTER_OUTPUT_CHANNEL, Telemetry } from '../constants'; import { ICodeWatcher, @@ -68,6 +68,7 @@ export class CommandRegistry implements IDisposable { this.registerCommand(Commands.DebugCurrentCellPalette, this.debugCurrentCellFromCursor); this.registerCommand(Commands.CreateNewNotebook, this.createNewNotebook); this.registerCommand(Commands.ViewJupyterOutput, this.viewJupyterOutput); + this.registerCommand(Commands.GatherQuality, this.reportGatherQuality); if (this.commandListeners) { this.commandListeners.forEach((listener: IDataScienceCommandListener) => { listener.register(this.commandManager); @@ -344,4 +345,9 @@ export class CommandRegistry implements IDisposable { // Ask our code lens provider to find the matching code watcher for the current document return this.dataScienceCodeLensProvider.getCodeWatcher(activeEditor.document); } + + private reportGatherQuality(val: string) { + sendTelemetryEvent(Telemetry.GatherQualityReport, undefined, { result: val === 'no' ? 'no' : 'yes' }); + env.openExternal(Uri.parse(`https://aka.ms/gathersurvey?succeed=${val}`)); + } } diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index b87e48b63f0b..f5db0f19d32a 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -81,6 +81,7 @@ export namespace Commands { export const SaveNotebookNonCustomEditor = 'python.datascience.notebookeditor.save'; export const SaveAsNotebookNonCustomEditor = 'python.datascience.notebookeditor.saveAs'; export const OpenNotebookNonCustomEditor = 'python.datascience.notebookeditor.open'; + export const GatherQuality = 'python.datascience.gatherquality'; } export namespace CodeLensCommands { @@ -285,6 +286,7 @@ export enum Telemetry { KernelInvalid = 'DS_INTERNAL.INVALID_KERNEL_USED', GatherCompleted = 'DATASCIENCE.GATHER_COMPLETED', GatheredNotebookSaved = 'DATASCIENCE.GATHERED_NOTEBOOK_SAVED', + GatherQualityReport = 'DS_INTERNAL.GATHER_QUALITY_REPORT', ZMQNotSupported = 'DATASCIENCE.ZMQ_NATIVE_BINARIES_NOT_LOADING' } diff --git a/src/client/datascience/interactive-common/linkProvider.ts b/src/client/datascience/interactive-common/linkProvider.ts index 636f940ea02e..a6ca7818b869 100644 --- a/src/client/datascience/interactive-common/linkProvider.ts +++ b/src/client/datascience/interactive-common/linkProvider.ts @@ -4,7 +4,7 @@ import '../../common/extensions'; import { inject, injectable } from 'inversify'; -import { Event, EventEmitter, Position, Range, Selection, TextEditorRevealType, Uri } from 'vscode'; +import { commands, Event, EventEmitter, Position, Range, Selection, TextEditorRevealType, Uri } from 'vscode'; import { IApplicationShell, ICommandManager, IDocumentManager } from '../../common/application/types'; import { IFileSystem } from '../../common/platform/types'; @@ -15,6 +15,10 @@ import { InteractiveWindowMessages } from './interactiveWindowTypes'; const LineQueryRegex = /line=(\d+)/; +// The following list of commands represent those that can be executed +// in a markdown cell using the syntax: https://command:[my.vscode.command]. +const linkCommandWhitelist = ['python.datascience.gatherquality']; + // tslint:disable: no-any @injectable() export class LinkProvider implements IInteractiveWindowListener { @@ -40,9 +44,16 @@ export class LinkProvider implements IInteractiveWindowListener { case InteractiveWindowMessages.OpenLink: if (payload) { // Special case file URIs - const href = payload.toString(); + const href: string = payload.toString(); if (href.startsWith('file')) { this.openFile(href); + } else if (href.startsWith('https://command:')) { + const temp: string = href.split(':')[2]; + const command = temp.split('/?')[0]; + const params: string[] = temp.split('/?')[1].split(','); + if (linkCommandWhitelist.includes(command)) { + commands.executeCommand(command, params); + } } else { this.applicationShell.openUrl(href); } diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 3a279a109a79..2077192e2aa4 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1900,6 +1900,10 @@ export interface IEventNamePropertyMapping { * Telemetry event sent when a gathered notebook has been saved by the user. */ [Telemetry.GatheredNotebookSaved]: undefined | never; + /** + * Telemetry event sent when the user reports whether Gathered notebook was good or not + */ + [Telemetry.GatherQualityReport]: { result: 'yes' | 'no' }; /** * Telemetry event sent when the ZMQ native binaries do not work. */