diff --git a/news/3 Code Health/10049.md b/news/3 Code Health/10049.md new file mode 100644 index 000000000000..8db350b7860d --- /dev/null +++ b/news/3 Code Health/10049.md @@ -0,0 +1 @@ +Change select kernel telemetry to track duration till quick pick appears. \ No newline at end of file diff --git a/news/3 Code Health/9819.md b/news/3 Code Health/9819.md new file mode 100644 index 000000000000..76445586b312 --- /dev/null +++ b/news/3 Code Health/9819.md @@ -0,0 +1 @@ +Add telemetry to track notebook languages \ No newline at end of file diff --git a/news/3 Code Health/9883.md b/news/3 Code Health/9883.md new file mode 100644 index 000000000000..bf4fc2168a21 --- /dev/null +++ b/news/3 Code Health/9883.md @@ -0,0 +1 @@ +Telemetry around kernels not working and installs not working. \ No newline at end of file diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 851a411913c2..639ad4a96951 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -5,6 +5,7 @@ import * as os from 'os'; import { CancellationToken, OutputChannel, Uri } from 'vscode'; import '../../common/extensions'; import * as localize from '../../common/utils/localize'; +import { Telemetry } from '../../datascience/constants'; import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { LinterId } from '../../linters/types'; @@ -27,6 +28,7 @@ import { ProductType } from '../types'; import { isResource } from '../utils/misc'; +import { StopWatch } from '../utils/stopWatch'; import { ProductNames } from './productNames'; import { IInstallationChannelManager, InterpreterUri, IProductPathService, IProductService } from './types'; @@ -355,7 +357,22 @@ export class DataScienceInstaller extends BaseInstaller { 'Yes', 'No' ); - return item === 'Yes' ? this.install(product, resource, cancel) : InstallerResponse.Ignore; + if (item === 'Yes') { + const stopWatch = new StopWatch(); + try { + const response = await this.install(product, resource, cancel); + const event = + product === Product.jupyter ? Telemetry.UserInstalledJupyter : Telemetry.UserInstalledModule; + sendTelemetryEvent(event, stopWatch.elapsedTime, { product: productName }); + return response; + } catch (e) { + if (product === Product.jupyter) { + sendTelemetryEvent(Telemetry.JupyterInstallFailed); + } + throw e; + } + } + return InstallerResponse.Ignore; } } diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index 8cc47cd2f780..4ae92a96395e 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -14,6 +14,21 @@ export const JUPYTER_OUTPUT_CHANNEL = 'JUPYTER_OUTPUT_CHANNEL'; // Python Module to be used when instantiating the Python Daemon. export const PythonDaemonModule = 'datascience.jupyter_daemon'; +// List of 'language' names that we know about. All should be lower case as that's how we compare. +export const KnownNotebookLanguages: string[] = [ + 'python', + 'r', + 'julia', + 'c++', + 'c#', + 'f#', + 'scala', + 'haskell', + 'bash', + 'cling', + 'sas' +]; + export namespace Commands { export const RunAllCells = 'python.datascience.runallcells'; export const RunAllCellsAbove = 'python.datascience.runallcellsabove'; @@ -153,7 +168,7 @@ export enum Telemetry { CollapseAll = 'DATASCIENCE.COLLAPSE_ALL', SelectJupyterURI = 'DATASCIENCE.SELECT_JUPYTER_URI', SelectLocalJupyterKernel = 'DATASCIENCE.SELECT_LOCAL_JUPYTER_KERNEL', - SelectRemoteJupyuterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL', + SelectRemoteJupyterKernel = 'DATASCIENCE.SELECT_REMOTE_JUPYTER_KERNEL', SetJupyterURIToLocal = 'DATASCIENCE.SET_JUPYTER_URI_LOCAL', SetJupyterURIToUserSpecified = 'DATASCIENCE.SET_JUPYTER_URI_USER_SPECIFIED', Interrupt = 'DATASCIENCE.INTERRUPT', @@ -246,6 +261,12 @@ export enum Telemetry { FindKernelForLocalConnection = 'DS_INTERNAL.FIND_KERNEL_FOR_LOCAL_CONNECTION', CompletionTimeFromLS = 'DS_INTERNAL.COMPLETION_TIME_FROM_LS', CompletionTimeFromJupyter = 'DS_INTERNAL.COMPLETION_TIME_FROM_JUPYTER', + NotebookLanguage = 'DATASCIENCE.NOTEBOOK_LANGUAGE', + KernelSpecNotFound = 'DS_INTERNAL.KERNEL_SPEC_NOT_FOUND', + KernelRegisterFailed = 'DS_INTERNAL.KERNEL_REGISTER_FAILED', + KernelEnumeration = 'DS_INTERNAL.KERNEL_ENUMERATION', + JupyterInstallFailed = 'DS_INTERNAL.JUPYTER_INSTALL_FAILED', + UserInstalledModule = 'DATASCIENCE.USER_INSTALLED_MODULE', JupyterCommandLineNonDefault = 'DS_INTERNAL.JUPYTER_CUSTOM_COMMAND_LINE' } diff --git a/src/client/datascience/interactive-ipynb/nativeEditor.ts b/src/client/datascience/interactive-ipynb/nativeEditor.ts index a879a576d42f..9e9d4ef4d7d0 100644 --- a/src/client/datascience/interactive-ipynb/nativeEditor.ts +++ b/src/client/datascience/interactive-ipynb/nativeEditor.ts @@ -43,6 +43,7 @@ import { captureTelemetry, sendTelemetryEvent } from '../../telemetry'; import { EditorContexts, Identifiers, + KnownNotebookLanguages, NativeKeyboardCommandTelemetryLookup, NativeMouseCommandTelemetryLookup, Telemetry @@ -432,7 +433,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { } } - @captureTelemetry(Telemetry.ExecuteNativeCell, undefined, false) + @captureTelemetry(Telemetry.ExecuteNativeCell, undefined, true) // tslint:disable-next-line:no-any protected async reexecuteCell(info: ISubmitNewCell): Promise { try { @@ -627,6 +628,9 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { // Then save the contents. We'll stick our cells back into this format when we save if (json) { this.notebookJson = json; + + // Log language or kernel telemetry + this.sendLanguageTelemetry(this.notebookJson); } this.contentsLoadedPromise.resolve(); @@ -650,6 +654,27 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor { ); } + private sendLanguageTelemetry(notebookJson: Partial) { + try { + // See if we have a language + let language = ''; + if (notebookJson.metadata?.language_info?.name) { + language = notebookJson.metadata?.language_info?.name; + } else if (notebookJson.metadata?.kernelspec?.language) { + language = notebookJson.metadata?.kernelspec?.language.toString(); + } + if (language && !KnownNotebookLanguages.includes(language.toLowerCase())) { + language = 'unknown'; + } + if (language) { + sendTelemetryEvent(Telemetry.NotebookLanguage, undefined, { language }); + } + } catch { + // If this fails, doesn't really matter + noop(); + } + } + private async loadCells(cells: ICell[], forceDirty: boolean): Promise { // Make sure cells have at least 1 if (cells.length === 0) { diff --git a/src/client/datascience/jupyter/interpreter/jupyterCommandInterpreterDependencyService.ts b/src/client/datascience/jupyter/interpreter/jupyterCommandInterpreterDependencyService.ts index cf7267414313..d7b0cb24beaf 100644 --- a/src/client/datascience/jupyter/interpreter/jupyterCommandInterpreterDependencyService.ts +++ b/src/client/datascience/jupyter/interpreter/jupyterCommandInterpreterDependencyService.ts @@ -9,6 +9,7 @@ import { ProductNames } from '../../../common/installer/productNames'; import { IInstallationChannelManager } from '../../../common/installer/types'; import { Product } from '../../../common/types'; import { DataScience } from '../../../common/utils/localize'; +import { StopWatch } from '../../../common/utils/stopWatch'; import { sendTelemetryEvent } from '../../../telemetry'; import { Telemetry } from '../../constants'; import { IJupyterInterpreterDependencyManager } from '../../types'; @@ -37,20 +38,28 @@ export class JupyterCommandInterpreterDependencyService implements IJupyterInter // If Conda is available, always pick it as the user must have a Conda Environment const installer = installers.find(ins => ins.name === 'Conda'); const product = ProductNames.get(Product.jupyter); + const stopWatch = new StopWatch(); if (installer && product) { - sendTelemetryEvent(Telemetry.UserInstalledJupyter); installer .installModule(product) - .catch(e => - this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink()) - ); + .then(() => { + sendTelemetryEvent(Telemetry.UserInstalledJupyter, stopWatch.elapsedTime); + }) + .catch(e => { + sendTelemetryEvent(Telemetry.JupyterInstallFailed, undefined, { product }); + this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink()); + }); } else if (installers[0] && product) { installers[0] .installModule(product) - .catch(e => - this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink()) - ); + .then(() => { + sendTelemetryEvent(Telemetry.UserInstalledJupyter, stopWatch.elapsedTime); + }) + .catch(e => { + sendTelemetryEvent(Telemetry.JupyterInstallFailed, undefined, { product }); + this.applicationShell.showErrorMessage(e.message, DataScience.pythonInteractiveHelpLink()); + }); } } } else if (response === DataScience.notebookCheckForImportNo()) { diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts index 43555442abe8..d06fb1f0669e 100644 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts +++ b/src/client/datascience/jupyter/interpreter/jupyterInterpreterDependencyService.ts @@ -303,7 +303,10 @@ export class JupyterInterpreterDependencyService { return execService .execModule('jupyter', ['kernelspec', '--version'], { throwOnStdErr: true }) .then(() => true) - .catch(() => false); + .catch(() => { + sendTelemetryEvent(Telemetry.KernelSpecNotFound); + return false; + }); } /** diff --git a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts index 5cc9f6b39300..221fd632fc8a 100644 --- a/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts +++ b/src/client/datascience/jupyter/interpreter/jupyterInterpreterSubCommandExecutionService.ts @@ -15,7 +15,8 @@ import { DataScience } from '../../../common/utils/localize'; import { noop } from '../../../common/utils/misc'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts'; -import { JUPYTER_OUTPUT_CHANNEL, PythonDaemonModule } from '../../constants'; +import { sendTelemetryEvent } from '../../../telemetry'; +import { JUPYTER_OUTPUT_CHANNEL, PythonDaemonModule, Telemetry } from '../../constants'; import { IJupyterInterpreterDependencyManager, IJupyterSubCommandExecutionService } from '../../types'; import { JupyterServerInfo } from '../jupyterConnection'; import { JupyterInstallError } from '../jupyterInstallError'; @@ -192,6 +193,7 @@ export class JupyterInterpreterSubCommandExecutionService .execModule('jupyter', ['kernelspec', 'list', '--json'], spawnOptions) .then(output => output.stdout) .catch(daemonEx => { + sendTelemetryEvent(Telemetry.KernelSpecNotFound); traceError('Failed to list kernels from daemon', daemonEx); return ''; }); diff --git a/src/client/datascience/jupyter/jupyterExecution.ts b/src/client/datascience/jupyter/jupyterExecution.ts index 25cd60dfe59e..7ddbcb773e1f 100644 --- a/src/client/datascience/jupyter/jupyterExecution.ts +++ b/src/client/datascience/jupyter/jupyterExecution.ts @@ -221,6 +221,7 @@ export class JupyterExecutionBase implements IJupyterExecution { >(IJupyterSessionManagerFactory); const sessionManager = await sessionManagerFactory.create(connection); const kernelInterpreter = await this.kernelSelector.selectLocalKernel( + new StopWatch(), sessionManager, cancelToken, launchInfo.kernelSpec diff --git a/src/client/datascience/jupyter/kernels/kernelSelector.ts b/src/client/datascience/jupyter/kernels/kernelSelector.ts index f55f0bdecc9b..33cbaa3bf258 100644 --- a/src/client/datascience/jupyter/kernels/kernelSelector.ts +++ b/src/client/datascience/jupyter/kernels/kernelSelector.ts @@ -15,7 +15,7 @@ import { noop } from '../../../common/utils/misc'; import { StopWatch } from '../../../common/utils/stopWatch'; import { IInterpreterService, PythonInterpreter } from '../../../interpreter/contracts'; import { IEventNamePropertyMapping, sendTelemetryEvent } from '../../../telemetry'; -import { Telemetry } from '../../constants'; +import { KnownNotebookLanguages, Telemetry } from '../../constants'; import { reportAction } from '../../progress/decorator'; import { ReportableAction } from '../../progress/types'; import { IJupyterKernelSpec, IJupyterSessionManager } from '../../types'; @@ -90,13 +90,21 @@ export class KernelSelector { * @memberof KernelSelector */ public async selectRemoteKernel( + stopWatch: StopWatch, session: IJupyterSessionManager, cancelToken?: CancellationToken, currentKernel?: IJupyterKernelSpec | LiveKernelModel ): Promise { let suggestions = await this.selectionProvider.getKernelSelectionsForRemoteSession(session, cancelToken); suggestions = suggestions.filter(item => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || '')); - return this.selectKernel(suggestions, session, cancelToken, currentKernel); + return this.selectKernel( + stopWatch, + Telemetry.SelectRemoteJupyterKernel, + suggestions, + session, + cancelToken, + currentKernel + ); } /** * Select a kernel from a local session. @@ -107,13 +115,21 @@ export class KernelSelector { * @memberof KernelSelector */ public async selectLocalKernel( + stopWatch: StopWatch, session?: IJupyterSessionManager, cancelToken?: CancellationToken, currentKernel?: IJupyterKernelSpec | LiveKernelModel ): Promise { let suggestions = await this.selectionProvider.getKernelSelectionsForLocalSession(session, cancelToken); suggestions = suggestions.filter(item => !this.kernelIdsToHide.has(item.selection.kernelModel?.id || '')); - return this.selectKernel(suggestions, session, cancelToken, currentKernel); + return this.selectKernel( + stopWatch, + Telemetry.SelectLocalJupyterKernel, + suggestions, + session, + cancelToken, + currentKernel + ); } /** * Gets a kernel that needs to be used with a local session. @@ -168,7 +184,7 @@ export class KernelSelector { ); } else { telemetryProps.promptedToSelect = true; - selection = await this.selectLocalKernel(sessionManager, cancelToken); + selection = await this.selectLocalKernel(stopWatch, sessionManager, cancelToken); } } } else { @@ -263,6 +279,8 @@ export class KernelSelector { }; } private async selectKernel( + stopWatch: StopWatch, + telemetryEvent: Telemetry, suggestions: IKernelSpecQuickPickItem[], session?: IJupyterSessionManager, cancelToken?: CancellationToken, @@ -271,6 +289,7 @@ export class KernelSelector { const placeHolder = localize.DataScience.selectKernel() + (currentKernel ? ` (current: ${currentKernel.display_name || currentKernel.name})` : ''); + sendTelemetryEvent(telemetryEvent, stopWatch.elapsedTime); const selection = await this.applicationShell.showQuickPick(suggestions, { placeHolder }, cancelToken); if (!selection?.selection) { return {}; @@ -280,7 +299,9 @@ export class KernelSelector { sendTelemetryEvent(Telemetry.SwitchToInterpreterAsKernel); return this.useInterpreterAsKernel(selection.selection.interpreter, undefined, session, false, cancelToken); } else if (selection.selection.kernelModel) { - sendTelemetryEvent(Telemetry.SwitchToExistingKernel); + sendTelemetryEvent(Telemetry.SwitchToExistingKernel, undefined, { + language: this.computeLanguage(selection.selection.kernelModel.language) + }); // tslint:disable-next-line: no-any const interpreter = selection.selection.kernelModel ? await this.kernelService.findMatchingInterpreter(selection.selection.kernelModel, cancelToken) @@ -291,7 +312,9 @@ export class KernelSelector { kernelModel: selection.selection.kernelModel }; } else if (selection.selection.kernelSpec) { - sendTelemetryEvent(Telemetry.SwitchToExistingKernel); + sendTelemetryEvent(Telemetry.SwitchToExistingKernel, undefined, { + language: this.computeLanguage(selection.selection.kernelSpec.language) + }); const interpreter = selection.selection.kernelSpec ? await this.kernelService.findMatchingInterpreter(selection.selection.kernelSpec, cancelToken) : undefined; @@ -348,7 +371,12 @@ export class KernelSelector { } // Try an install this interpreter as a kernel. - kernelSpec = await this.kernelService.registerKernel(interpreter, disableUI, cancelToken); + try { + kernelSpec = await this.kernelService.registerKernel(interpreter, disableUI, cancelToken); + } catch (e) { + sendTelemetryEvent(Telemetry.KernelRegisterFailed); + throw e; + } // If we have a display name of a kernel that could not be found, // then notify user that we're using current interpreter instead. @@ -368,4 +396,11 @@ export class KernelSelector { return { kernelSpec, interpreter }; } + + private computeLanguage(language: string | undefined): string { + if (language && KnownNotebookLanguages.includes(language.toLowerCase())) { + return language; + } + return 'unknown'; + } } diff --git a/src/client/datascience/jupyter/kernels/kernelService.ts b/src/client/datascience/jupyter/kernels/kernelService.ts index 66fbce7ac8c7..dd376a420f11 100644 --- a/src/client/datascience/jupyter/kernels/kernelService.ts +++ b/src/client/datascience/jupyter/kernels/kernelService.ts @@ -424,7 +424,17 @@ export class KernelService { return []; } const specs: IJupyterKernelSpec[] = await enumerator; - return specs.filter(item => !!item); + const result = specs.filter(item => !!item); + + // Send telemetry on this enumeration. + const anyPython = result.find(k => k.language === 'python') !== undefined; + sendTelemetryEvent(Telemetry.KernelEnumeration, undefined, { + count: result.length, + isPython: anyPython, + source: sessionManager ? 'connection' : 'cli' + }); + + return result; } /** * Not all characters are allowed in a kernel name. diff --git a/src/client/datascience/jupyter/kernels/kernelSwitcher.ts b/src/client/datascience/jupyter/kernels/kernelSwitcher.ts index 4826b2ac14a2..586442e61c17 100644 --- a/src/client/datascience/jupyter/kernels/kernelSwitcher.ts +++ b/src/client/datascience/jupyter/kernels/kernelSwitcher.ts @@ -8,8 +8,8 @@ import { ProgressLocation, ProgressOptions } from 'vscode'; import { IApplicationShell } from '../../../common/application/types'; import { IConfigurationService } from '../../../common/types'; import { Common, DataScience } from '../../../common/utils/localize'; -import { captureTelemetry } from '../../../telemetry'; -import { Commands, Settings, Telemetry } from '../../constants'; +import { StopWatch } from '../../../common/utils/stopWatch'; +import { Commands, Settings } from '../../constants'; import { IConnection, IJupyterKernelSpec, IJupyterSessionManagerFactory, INotebook } from '../../types'; import { JupyterSessionStartError } from '../jupyterSession'; import { KernelSelector, KernelSpecInterpreter } from './kernelSelector'; @@ -51,20 +51,19 @@ export class KernelSwitcher { return kernel; } - @captureTelemetry(Telemetry.SelectLocalJupyterKernel) private async selectLocalJupyterKernel( currentKernel?: IJupyterKernelSpec | LiveKernelModel ): Promise { - return this.kernelSelector.selectLocalKernel(undefined, undefined, currentKernel); + return this.kernelSelector.selectLocalKernel(new StopWatch(), undefined, undefined, currentKernel); } - @captureTelemetry(Telemetry.SelectRemoteJupyuterKernel) private async selectRemoteJupyterKernel( connInfo: IConnection, currentKernel?: IJupyterKernelSpec | LiveKernelModel ): Promise { + const stopWatch = new StopWatch(); const session = await this.jupyterSessionManagerFactory.create(connInfo); - return this.kernelSelector.selectRemoteKernel(session, undefined, currentKernel); + return this.kernelSelector.selectRemoteKernel(stopWatch, session, undefined, currentKernel); } private async switchKernelWithRetry(notebook: INotebook, kernel: KernelSpecInterpreter): Promise { const settings = this.configService.getSettings(); diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 80d66db47f48..ae4c6a774d23 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1465,7 +1465,7 @@ export interface IEventNamePropertyMapping { [Telemetry.RegisterAndUseInterpreterAsKernel]: never | undefined; [Telemetry.UseInterpreterAsKernel]: never | undefined; [Telemetry.UseExistingKernel]: never | undefined; - [Telemetry.SwitchToExistingKernel]: never | undefined; + [Telemetry.SwitchToExistingKernel]: { language: string }; [Telemetry.SwitchToInterpreterAsKernel]: never | undefined; [Telemetry.ConvertToPythonFile]: never | undefined; [Telemetry.CopySourceCode]: never | undefined; @@ -1534,7 +1534,7 @@ export interface IEventNamePropertyMapping { [Telemetry.SelfCertsMessageEnabled]: never | undefined; [Telemetry.SelectJupyterURI]: never | undefined; [Telemetry.SelectLocalJupyterKernel]: never | undefined; - [Telemetry.SelectRemoteJupyuterKernel]: never | undefined; + [Telemetry.SelectRemoteJupyterKernel]: never | undefined; [Telemetry.SessionIdleTimeout]: never | undefined; [Telemetry.JupyterNotInstalledErrorShown]: never | undefined; [Telemetry.JupyterCommandSearch]: { @@ -1750,8 +1750,72 @@ export interface IEventNamePropertyMapping { */ [Telemetry.CompletionTimeFromJupyter]: undefined | never; /** - * Telemetry event sent to when user customizes the jupyter command line + * Telemetry event sent to indicate the language used in a notebook + * + * @type { language: string } + * @memberof IEventNamePropertyMapping + */ + [Telemetry.NotebookLanguage]: { + /** + * Language found in the notebook if a known language. Otherwise 'unknown' + */ + language: string; + }; + /** + * Telemetry event sent to indicate 'jupyter kernelspec' is not possible. + * + * @type {(undefined | never)} + * @memberof IEventNamePropertyMapping + */ + [Telemetry.KernelSpecNotFound]: undefined | never; + /** + * Telemetry event sent to indicate registering a kernel with jupyter failed. + * + * @type {(undefined | never)} + * @memberof IEventNamePropertyMapping + */ + [Telemetry.KernelRegisterFailed]: undefined | never; + /** + * Telemetry event sent to every time a kernel enumeration is done + * + * @type {...} + * @memberof IEventNamePropertyMapping + */ + [Telemetry.KernelEnumeration]: { + /** + * Count of the number of kernels found + */ + count: number; + /** + * Boolean indicating if any are python or not + */ + isPython: boolean; + /** + * Indicates how the enumeration was acquired. + */ + source: 'cli' | 'connection'; + }; + /** + * Telemetry event sent if there's an error installing a jupyter required dependency + * + * @type { product: string } + * @memberof IEventNamePropertyMapping + */ + [Telemetry.JupyterInstallFailed]: { + /** + * Product being installed (jupyter or ipykernel or other) + */ + product: string; + }; + /** + * Telemetry event sent when installing a jupyter dependency * + * @type {product: string} + * @memberof IEventNamePropertyMapping + */ + [Telemetry.UserInstalledModule]: { product: string }; + /** + * Telemetry event sent to when user customizes the jupyter command line * @type {(undefined | never)} * @memberof IEventNamePropertyMapping */ diff --git a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts index 2962b6b88085..6ec0510af9e2 100644 --- a/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts +++ b/src/test/datascience/jupyter/kernels/kernelSelector.unit.test.ts @@ -14,6 +14,7 @@ import { IInstaller, Product } from '../../../../client/common/types'; import * as localize from '../../../../client/common/utils/localize'; import { noop } from '../../../../client/common/utils/misc'; import { Architecture } from '../../../../client/common/utils/platform'; +import { StopWatch } from '../../../../client/common/utils/stopWatch'; import { JupyterSessionManager } from '../../../../client/datascience/jupyter/jupyterSessionManager'; import { KernelSelectionProvider } from '../../../../client/datascience/jupyter/kernels/kernelSelections'; import { KernelSelector } from '../../../../client/datascience/jupyter/kernels/kernelSelector'; @@ -73,7 +74,7 @@ suite('Data Science - KernelSelector', () => { ).thenResolve([]); when(appShell.showQuickPick(anything(), anything(), anything())).thenResolve(); - const kernel = await kernelSelector.selectRemoteKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectRemoteKernel(new StopWatch(), instance(sessionManager)); assert.isEmpty(kernel); verify( @@ -87,7 +88,7 @@ suite('Data Science - KernelSelector', () => { ).thenResolve([]); when(appShell.showQuickPick(anything(), anything(), anything())).thenResolve(); - const kernel = await kernelSelector.selectLocalKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectLocalKernel(new StopWatch(), instance(sessionManager)); assert.isEmpty(kernel); verify( @@ -105,7 +106,7 @@ suite('Data Science - KernelSelector', () => { // tslint:disable-next-line: no-any } as any); - const kernel = await kernelSelector.selectRemoteKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectRemoteKernel(new StopWatch(), instance(sessionManager)); assert.isOk(kernel.kernelSpec === kernelSpec); assert.isOk(kernel.interpreter === interpreter); @@ -172,7 +173,7 @@ suite('Data Science - KernelSelector', () => { kernelSelector.addKernelToIgnoreList({ id: 'id2' } as any); // tslint:disable-next-line: no-any kernelSelector.addKernelToIgnoreList({ clientId: 'id4' } as any); - const kernel = await kernelSelector.selectRemoteKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectRemoteKernel(new StopWatch(), instance(sessionManager)); assert.isEmpty(kernel); verify( @@ -240,7 +241,7 @@ suite('Data Science - KernelSelector', () => { kernelSelector.addKernelToIgnoreList({ id: 'id2' } as any); // tslint:disable-next-line: no-any kernelSelector.addKernelToIgnoreList({ clientId: 'id4' } as any); - const kernel = await kernelSelector.selectLocalKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectLocalKernel(new StopWatch(), instance(sessionManager)); assert.isEmpty(kernel); verify( @@ -265,7 +266,7 @@ suite('Data Science - KernelSelector', () => { // tslint:disable-next-line: no-any } as any); - const kernel = await kernelSelector.selectLocalKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectLocalKernel(new StopWatch(), instance(sessionManager)); assert.isOk(kernel.kernelSpec === kernelSpec); assert.isOk(kernel.interpreter === interpreter); @@ -291,7 +292,7 @@ suite('Data Science - KernelSelector', () => { // tslint:disable-next-line: no-any } as any); - const kernel = await kernelSelector.selectLocalKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectLocalKernel(new StopWatch(), instance(sessionManager)); assert.isOk(kernel.kernelSpec === kernelSpec); verify(installer.isInstalled(Product.ipykernel, interpreter)).once(); @@ -323,7 +324,7 @@ suite('Data Science - KernelSelector', () => { // tslint:disable-next-line: no-any } as any); - const kernel = await kernelSelector.selectLocalKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectLocalKernel(new StopWatch(), instance(sessionManager)); assert.isOk(kernel.kernelSpec === kernelSpec); assert.isOk(kernel.interpreter === interpreter); @@ -354,7 +355,7 @@ suite('Data Science - KernelSelector', () => { // tslint:disable-next-line: no-any } as any); - const kernel = await kernelSelector.selectLocalKernel(instance(sessionManager)); + const kernel = await kernelSelector.selectLocalKernel(new StopWatch(), instance(sessionManager)); assert.isOk(kernel.kernelSpec === kernelSpec); verify(installer.isInstalled(Product.ipykernel, interpreter)).once(); @@ -381,6 +382,7 @@ suite('Data Science - KernelSelector', () => { let nbMetadata: nbformat.INotebookMetadata = {} as any; let selectLocalKernelStub: sinon.SinonStub< [ + StopWatch, (IJupyterSessionManager | undefined)?, (CancellationToken | undefined)?, (IJupyterKernelSpec | LiveKernelModel)? diff --git a/src/test/datascience/jupyter/kernels/kernelSwitcher.unit.test.ts b/src/test/datascience/jupyter/kernels/kernelSwitcher.unit.test.ts index 2af6a3697872..c446b5100e0a 100644 --- a/src/test/datascience/jupyter/kernels/kernelSwitcher.unit.test.ts +++ b/src/test/datascience/jupyter/kernels/kernelSwitcher.unit.test.ts @@ -129,16 +129,28 @@ suite('Data Science - Kernel Switcher', () => { if (isLocalConnection) { verify( - kernelSelector.selectLocalKernel(undefined, undefined, currentKernelInfo.currentKernel) + kernelSelector.selectLocalKernel( + anything(), + undefined, + undefined, + currentKernelInfo.currentKernel + ) ).once(); } else { - verify(kernelSelector.selectRemoteKernel(anything(), anything(), anything())).once(); + verify( + kernelSelector.selectRemoteKernel(anything(), anything(), anything(), anything()) + ).once(); } }); test('Prompt to select local kernel', async () => { when( - kernelSelector.selectLocalKernel(undefined, undefined, currentKernelInfo.currentKernel) + kernelSelector.selectLocalKernel( + anything(), + undefined, + undefined, + currentKernelInfo.currentKernel + ) ).thenResolve({}); const selection = await kernelSwitcher.switchKernel(instance(notebook)); @@ -151,6 +163,7 @@ suite('Data Science - Kernel Switcher', () => { if (isLocalConnection) { when( kernelSelector.selectLocalKernel( + anything(), undefined, undefined, currentKernelInfo.currentKernel @@ -161,13 +174,13 @@ suite('Data Science - Kernel Switcher', () => { interpreter: selectedInterpreter }); } else { - when(kernelSelector.selectRemoteKernel(anything(), anything(), anything())).thenResolve( - { - kernelModel: selectedKernel, - kernelSpec: undefined, - interpreter: selectedInterpreter - } - ); + when( + kernelSelector.selectRemoteKernel(anything(), anything(), anything(), anything()) + ).thenResolve({ + kernelModel: selectedKernel, + kernelSpec: undefined, + interpreter: selectedInterpreter + }); } }); teardown(() => { @@ -284,19 +297,19 @@ suite('Data Science - Kernel Switcher', () => { return; } }); - when(kernelSelector.selectLocalKernel(undefined, anything(), anything())).thenCall( - () => { - // When selecting a kernel the second time, then return a different selection. - firstTimeSelectingAKernel = false; - return { - kernelModel: firstTimeSelectingAKernel - ? selectedKernel - : selectedKernelSecondTime, - kernelSpec: undefined, - interpreter: selectedInterpreter - }; - } - ); + when( + kernelSelector.selectLocalKernel(anything(), undefined, anything(), anything()) + ).thenCall(() => { + // When selecting a kernel the second time, then return a different selection. + firstTimeSelectingAKernel = false; + return { + kernelModel: firstTimeSelectingAKernel + ? selectedKernel + : selectedKernelSecondTime, + kernelSpec: undefined, + interpreter: selectedInterpreter + }; + }); when(appShell.showErrorMessage(anything(), anything(), anything())).thenResolve( // tslint:disable-next-line: no-any DataScience.selectDifferentKernel() as any @@ -318,7 +331,9 @@ suite('Data Science - Kernel Switcher', () => { ) ).once(); // first time when user select a kernel, second time is when user selects after failing to switch to the first kernel. - verify(kernelSelector.selectLocalKernel(anything(), anything(), anything())).twice(); + verify( + kernelSelector.selectLocalKernel(anything(), anything(), anything(), anything()) + ).twice(); }); }); });