diff --git a/news/1 Enhancements/19067.md b/news/1 Enhancements/19067.md new file mode 100644 index 000000000000..db37e023f7e6 --- /dev/null +++ b/news/1 Enhancements/19067.md @@ -0,0 +1 @@ +Improve information collected by the `Python: Report Issue` command. diff --git a/package.json b/package.json index 582bae0903b2..cdf527ae753d 100644 --- a/package.json +++ b/package.json @@ -922,6 +922,7 @@ "type": "string" }, "python.tensorBoard.logDirectory": { + "default": "", "description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.", "scope": "application", "type": "string" diff --git a/resources/report_issue_template.md b/resources/report_issue_template.md index f2fbc21d77ad..838a6f6aa9e7 100644 --- a/resources/report_issue_template.md +++ b/resources/report_issue_template.md @@ -26,7 +26,7 @@ XXX

``` -{3} +{3}{4} ```

diff --git a/resources/report_issue_user_settings.json b/resources/report_issue_user_settings.json index e30a8adc1736..d1c13e7b9e37 100644 --- a/resources/report_issue_user_settings.json +++ b/resources/report_issue_user_settings.json @@ -3,7 +3,7 @@ "pythonPath": "placeholder", "onDidChange": false, "defaultInterpreterPath": "placeholder", - "defaultLS": true, + "defaultLS": false, "envFile": "placeholder", "venvPath": "placeholder", "venvFolders": "placeholder", @@ -25,7 +25,7 @@ "linting": { "enabled": true, "cwd": "placeholder", - "Flake8Args": "placeholder", + "flake8Args": "placeholder", "flake8CategorySeverity": false, "flake8Enabled": true, "flake8Path": "placeholder", @@ -85,9 +85,6 @@ "testing": { "cwd": "placeholder", "debugPort": true, - "nosetestArgs": "placeholder", - "nosetestsEnabled": true, - "nosetestPath": "placeholder", "promptToConfigure": true, "pytestArgs": "placeholder", "pytestEnabled": true, diff --git a/src/client/common/application/commands/reportIssueCommand.ts b/src/client/common/application/commands/reportIssueCommand.ts index 18c95f669d78..d18299e6698e 100644 --- a/src/client/common/application/commands/reportIssueCommand.ts +++ b/src/client/common/application/commands/reportIssueCommand.ts @@ -7,8 +7,9 @@ import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; import { inject, injectable } from 'inversify'; +import { isEqual } from 'lodash'; import { IExtensionSingleActivationService } from '../../../activation/types'; -import { ICommandManager, IWorkspaceService } from '../types'; +import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from '../types'; import { EXTENSION_ROOT_DIR } from '../../../constants'; import { IInterpreterService } from '../../../interpreter/contracts'; import { Commands } from '../../constants'; @@ -16,6 +17,8 @@ import { IConfigurationService, IPythonSettings } from '../../types'; import { sendTelemetryEvent } from '../../../telemetry'; import { EventName } from '../../../telemetry/constants'; import { EnvironmentType } from '../../../pythonEnvironments/info'; +import { PythonSettings } from '../../configSettings'; +import { SystemVariables } from '../../variables/systemVariables'; /** * Allows the user to report an issue related to the Python extension using our template. @@ -24,12 +27,18 @@ import { EnvironmentType } from '../../../pythonEnvironments/info'; export class ReportIssueCommandHandler implements IExtensionSingleActivationService { public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true }; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private readonly packageJSONSettings: any; + constructor( @inject(ICommandManager) private readonly commandManager: ICommandManager, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, - ) {} + @inject(IApplicationEnvironment) appEnvironment: IApplicationEnvironment, + ) { + this.packageJSONSettings = appEnvironment.packageJson?.contributes?.configuration?.properties; + } public async activate(): Promise { this.commandManager.registerCommand(Commands.ReportIssue, this.openReportIssue, this); @@ -48,20 +57,31 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ const argSetting = argSettings[property]; if (argSetting) { if (typeof argSetting === 'object') { - userSettings = userSettings.concat(os.EOL, property, os.EOL); + let propertyHeaderAdded = false; const argSettingsDict = (settings[property] as unknown) as Record; if (typeof argSettingsDict === 'object') { Object.keys(argSetting).forEach((item) => { const prop = argSetting[item]; if (prop) { - const value = prop === true ? JSON.stringify(argSettingsDict[item]) : '""'; - userSettings = userSettings.concat('• ', item, ': ', value, os.EOL); + const defaultValue = this.getDefaultValue(`${property}.${item}`); + if (defaultValue === undefined || !isEqual(defaultValue, argSettingsDict[item])) { + if (!propertyHeaderAdded) { + userSettings = userSettings.concat(os.EOL, property, os.EOL); + propertyHeaderAdded = true; + } + const value = + prop === true ? JSON.stringify(argSettingsDict[item]) : '""'; + userSettings = userSettings.concat('• ', item, ': ', value, os.EOL); + } } }); } } else { - const value = argSetting === true ? JSON.stringify(settings[property]) : '""'; - userSettings = userSettings.concat(os.EOL, property, ': ', value, os.EOL); + const defaultValue = this.getDefaultValue(property); + if (defaultValue === undefined || !isEqual(defaultValue, settings[property])) { + const value = argSetting === true ? JSON.stringify(settings[property]) : '""'; + userSettings = userSettings.concat(os.EOL, property, ': ', value, os.EOL); + } } } }); @@ -72,10 +92,30 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ this.workspaceService.getConfiguration('python').get('languageServer') || 'Not Found'; const virtualEnvKind = interpreter?.envType || EnvironmentType.Unknown; + const hasMultipleFolders = (this.workspaceService.workspaceFolders?.length ?? 0) > 1; + const hasMultipleFoldersText = + hasMultipleFolders && userSettings !== '' + ? `Multiroot scenario, following user settings may not apply:${os.EOL}` + : ''; await this.commandManager.executeCommand('workbench.action.openIssueReporter', { extensionId: 'ms-python.python', - issueBody: template.format(pythonVersion, virtualEnvKind, languageServer, userSettings), + issueBody: template.format( + pythonVersion, + virtualEnvKind, + languageServer, + hasMultipleFoldersText, + userSettings, + ), }); sendTelemetryEvent(EventName.USE_REPORT_ISSUE_COMMAND, undefined, {}); } + + private getDefaultValue(settingKey: string) { + if (!this.packageJSONSettings) { + return undefined; + } + const resource = PythonSettings.getSettingsUriAndTarget(undefined, this.workspaceService).uri; + const systemVariables = new SystemVariables(resource, undefined, this.workspaceService); + return systemVariables.resolveAny(this.packageJSONSettings[`python.${settingKey}`]?.default); + } } diff --git a/src/test/common/application/commands/issueTemplateVenv1.md b/src/test/common/application/commands/issueTemplateVenv1.md index 5b2062628633..181049f2a965 100644 --- a/src/test/common/application/commands/issueTemplateVenv1.md +++ b/src/test/common/application/commands/issueTemplateVenv1.md @@ -28,12 +28,14 @@ XXX ``` experiments -• enabled: true +• enabled: false • optInto: [] • optOutFrom: [] venvPath: "" +pipenvPath: "" + ```

diff --git a/src/test/common/application/commands/issueTemplateVenv2.md b/src/test/common/application/commands/issueTemplateVenv2.md new file mode 100644 index 000000000000..974b0f113086 --- /dev/null +++ b/src/test/common/application/commands/issueTemplateVenv2.md @@ -0,0 +1,39 @@ + +# Behaviour +## Expected vs. Actual + +XXX + +## Steps to reproduce: + +1. XXX + + + + +# Diagnostic data + +- Python version (& distribution if applicable, e.g. Anaconda): 3.9.0 +- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Venv +- Value of the `python.languageServer` setting: Pylance + +
+ +User Settings + +

+ +``` +Multiroot scenario, following user settings may not apply: + +experiments +• enabled: false + +venvPath: "" + +``` + +

+
diff --git a/src/test/common/application/commands/reportIssueCommand.unit.test.ts b/src/test/common/application/commands/reportIssueCommand.unit.test.ts index 59b25221d7e3..92b4c0725f3f 100644 --- a/src/test/common/application/commands/reportIssueCommand.unit.test.ts +++ b/src/test/common/application/commands/reportIssueCommand.unit.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable global-require */ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. @@ -8,22 +9,27 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { anything, capture, instance, mock, verify, when } from 'ts-mockito'; import { expect } from 'chai'; +import { WorkspaceFolder } from 'vscode-languageserver-protocol'; import * as Telemetry from '../../../../client/telemetry'; import { LanguageServerType } from '../../../../client/activation/types'; import { CommandManager } from '../../../../client/common/application/commandManager'; import { ReportIssueCommandHandler } from '../../../../client/common/application/commands/reportIssueCommand'; -import { ICommandManager, IWorkspaceService } from '../../../../client/common/application/types'; +import { + IApplicationEnvironment, + ICommandManager, + IWorkspaceService, +} from '../../../../client/common/application/types'; import { WorkspaceService } from '../../../../client/common/application/workspace'; import { IInterpreterService } from '../../../../client/interpreter/contracts'; import { MockWorkspaceConfiguration } from '../../../mocks/mockWorkspaceConfig'; -import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants'; import { InterpreterService } from '../../../../client/interpreter/interpreterService'; -import { Commands } from '../../../../client/common/constants'; +import { Commands, EXTENSION_ROOT_DIR } from '../../../../client/common/constants'; import { AllCommands } from '../../../../client/common/application/commands'; import { ConfigurationService } from '../../../../client/common/configuration/service'; import { IConfigurationService } from '../../../../client/common/types'; import { EventName } from '../../../../client/telemetry/constants'; import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info'; +import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants'; suite('Report Issue Command', () => { let reportIssueCommandHandler: ReportIssueCommandHandler; @@ -31,12 +37,14 @@ suite('Report Issue Command', () => { let workspaceService: IWorkspaceService; let interpreterService: IInterpreterService; let configurationService: IConfigurationService; + let appEnvironment: IApplicationEnvironment; setup(async () => { workspaceService = mock(WorkspaceService); cmdManager = mock(CommandManager); interpreterService = mock(InterpreterService); configurationService = mock(ConfigurationService); + appEnvironment = mock(); when(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).thenResolve(); when(workspaceService.getConfiguration('python')).thenReturn( @@ -51,12 +59,13 @@ suite('Report Issue Command', () => { when(interpreterService.getActiveInterpreter()).thenResolve(interpreter); when(configurationService.getSettings()).thenReturn({ experiments: { - enabled: true, + enabled: false, optInto: [], optOutFrom: [], }, initialize: true, venvPath: 'path', + pipenvPath: 'pipenv', // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any); @@ -67,6 +76,7 @@ suite('Report Issue Command', () => { instance(workspaceService), instance(interpreterService), instance(configurationService), + instance(appEnvironment), ); await reportIssueCommandHandler.activate(); }); @@ -75,7 +85,7 @@ suite('Report Issue Command', () => { sinon.restore(); }); - test('Test if issue body is filled', async () => { + test('Test if issue body is filled correctly when including all the settings', async () => { await reportIssueCommandHandler.openReportIssue(); const templatePath = path.join( @@ -100,6 +110,45 @@ suite('Report Issue Command', () => { const actual = args[1].issueBody; expect(actual).to.be.equal(expectedIssueBody); }); + + test('Test if issue body is filled when only including settings which are explicitly set', async () => { + // eslint-disable-next-line import/no-dynamic-require + when(appEnvironment.packageJson).thenReturn(require(path.join(EXTENSION_ROOT_DIR, 'package.json'))); + when(workspaceService.workspaceFolders).thenReturn([ + instance(mock(WorkspaceFolder)), + instance(mock(WorkspaceFolder)), + ]); // Multiroot scenario + reportIssueCommandHandler = new ReportIssueCommandHandler( + instance(cmdManager), + instance(workspaceService), + instance(interpreterService), + instance(configurationService), + instance(appEnvironment), + ); + await reportIssueCommandHandler.activate(); + await reportIssueCommandHandler.openReportIssue(); + + const templatePath = path.join( + EXTENSION_ROOT_DIR_FOR_TESTS, + 'src', + 'test', + 'common', + 'application', + 'commands', + 'issueTemplateVenv2.md', + ); + const expectedIssueBody = fs.readFileSync(templatePath, 'utf8'); + + const args: [string, { extensionId: string; issueBody: string }] = capture< + AllCommands, + { extensionId: string; issueBody: string } + >(cmdManager.executeCommand).last(); + + verify(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).once(); + expect(args[0]).to.be.equal('workbench.action.openIssueReporter'); + const actual = args[1].issueBody; + expect(actual).to.be.equal(expectedIssueBody); + }); test('Should send telemetry event when run Report Issue Command', async () => { const sendTelemetryStub = sinon.stub(Telemetry, 'sendTelemetryEvent'); await reportIssueCommandHandler.openReportIssue();