diff --git a/CHANGELOG.md b/CHANGELOG.md index 985698eee9fa..4793f0e3141b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,65 @@ # Changelog +## 2020.8.3 (31 August 2020) + +### Enhancements + +1. Add telemetry about the install source for the extension. + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: + +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [bandit](https://pypi.org/project/bandit/), + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + ## 2020.8.2 (27 August 2020) ### Enhancements diff --git a/package-lock.json b/package-lock.json index 3220d91b2397..7331e602d84b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2020.8.2", + "version": "2020.8.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 32bcfced9d25..dd8ea5b841be 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, Jupyter Notebooks, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2020.8.2", + "version": "2020.8.3", "featureFlags": { "usingNewInterpreterStorage": true }, diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index b842440db208..b2c7704812c5 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -17,6 +17,7 @@ import { Commands, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL, UseProposed import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; import { traceError } from './common/logger'; import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; +import { IFileSystem } from './common/platform/types'; import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { @@ -57,6 +58,7 @@ import { registerTypes as providersRegisterTypes } from './providers/serviceRegi import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; import { TerminalProvider } from './providers/terminalProvider'; import { ISortImportsEditingProvider } from './providers/types'; +import { setExtensionInstallTelemetryProperties } from './telemetry/extensionInstallTelemetry'; import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; import { ICodeExecutionManager, ITerminalAutoActivation } from './terminals/types'; import { TEST_OUTPUT_CHANNEL } from './testing/common/constants'; @@ -104,6 +106,10 @@ async function activateLegacy( platformRegisterTypes(serviceManager); processRegisterTypes(serviceManager); + // We need to setup this property before any telemetry is sent + const fs = serviceManager.get(IFileSystem); + await setExtensionInstallTelemetryProperties(fs); + const applicationEnv = serviceManager.get(IApplicationEnvironment); const enableProposedApi = applicationEnv.packageJson.enableProposedApi; serviceManager.addSingletonInstance(UseProposedApi, enableProposedApi); diff --git a/src/client/telemetry/extensionInstallTelemetry.ts b/src/client/telemetry/extensionInstallTelemetry.ts new file mode 100644 index 000000000000..87e6ec50e3ea --- /dev/null +++ b/src/client/telemetry/extensionInstallTelemetry.ts @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { setSharedProperty } from '.'; +import { IFileSystem } from '../common/platform/types'; +import { EXTENSION_ROOT_DIR } from '../constants'; + +/** + * Sets shared telemetry property about where the extension was installed from + * currently we only detect installations from the Python coding pack installer. + * Those installations get the 'pythonCodingPack'. Otherwise assume the default + * case as 'MarketPlace'. + * + */ +export async function setExtensionInstallTelemetryProperties(fs: IFileSystem) { + // Look for PythonCodingPack file under `%USERPROFILE%/.vscode/extensions` + // folder. If that file exists treat this extension as installed from coding + // pack. + // + // Use parent of EXTENSION_ROOT_DIR to access %USERPROFILE%/.vscode/extensions + // this is because the installer will add PythonCodingPack to %USERPROFILE%/.vscode/extensions + // or %USERPROFILE%/.vscode-insiders/extensions depending on what was installed + // previously by the user. If we always join (, .vscode, extensions), we will + // end up looking at the wrong place, with respect to the extension that was launched. + const fileToCheck = path.join(path.dirname(EXTENSION_ROOT_DIR), 'PythonCodingPack'); + if (await fs.fileExists(fileToCheck)) { + setSharedProperty('installSource', 'pythonCodingPack'); + } else { + // We did not file the `PythonCodingPack` file, assume market place install. + setSharedProperty('installSource', 'marketPlace'); + } +} diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 27dd96bba0c1..2f805e82ad0c 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -332,6 +332,12 @@ export interface ISharedPropertyMapping { * For every DS telemetry we would like to know the type of Notebook Editor used when doing something. */ ['ds_notebookeditor']: undefined | 'old' | 'custom' | 'native'; + + /** + * For every telemetry event from the extension we want to make sure we can associate it with install + * source. We took this approach to work around very limiting query performance issues. + */ + ['installSource']: undefined | 'marketPlace' | 'pythonCodingPack'; } // Map all events to their properties diff --git a/src/test/telemetry/extensionInstallTelemetery.unit.test.ts b/src/test/telemetry/extensionInstallTelemetery.unit.test.ts new file mode 100644 index 000000000000..47e25eca05fa --- /dev/null +++ b/src/test/telemetry/extensionInstallTelemetery.unit.test.ts @@ -0,0 +1,29 @@ +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import { anyString, instance, mock, when } from 'ts-mockito'; +import { FileSystem } from '../../client/common/platform/fileSystem'; +import { IFileSystem } from '../../client/common/platform/types'; +import * as Telemetry from '../../client/telemetry'; +import { setExtensionInstallTelemetryProperties } from '../../client/telemetry/extensionInstallTelemetry'; + +suite('Extension Install Telemetry', () => { + let fs: IFileSystem; + let telemetryPropertyStub: sinon.SinonStub; + setup(() => { + fs = mock(FileSystem); + telemetryPropertyStub = sinon.stub(Telemetry, 'setSharedProperty'); + }); + teardown(() => { + telemetryPropertyStub.restore(); + }); + test('PythonCodingPack exists', async () => { + when(fs.fileExists(anyString())).thenResolve(true); + await setExtensionInstallTelemetryProperties(instance(fs)); + assert.ok(telemetryPropertyStub.calledOnceWithExactly('installSource', 'pythonCodingPack')); + }); + test('PythonCodingPack does not exists', async () => { + when(fs.fileExists(anyString())).thenResolve(false); + await setExtensionInstallTelemetryProperties(instance(fs)); + assert.ok(telemetryPropertyStub.calledOnceWithExactly('installSource', 'marketPlace')); + }); +});