Skip to content

Commit 259dbd5

Browse files
Kartik Rajkarthiknadig
authored andcommitted
Improve information collected by the Python: Report Issue command (#19068)
* Improve information collected by the `Python: Report Issue` command * Fix package.json * Add test * Add test for multiroot
1 parent 7a94dfd commit 259dbd5

File tree

8 files changed

+149
-20
lines changed

8 files changed

+149
-20
lines changed

news/1 Enhancements/19067.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve information collected by the `Python: Report Issue` command.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,7 @@
922922
"type": "string"
923923
},
924924
"python.tensorBoard.logDirectory": {
925+
"default": "",
925926
"description": "Set this setting to your preferred TensorBoard log directory to skip log directory prompt when starting TensorBoard.",
926927
"scope": "application",
927928
"type": "string"

resources/report_issue_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ XXX
2626
<p>
2727

2828
```
29-
{3}
29+
{3}{4}
3030
```
3131

3232
</p>

resources/report_issue_user_settings.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"pythonPath": "placeholder",
44
"onDidChange": false,
55
"defaultInterpreterPath": "placeholder",
6-
"defaultLS": true,
6+
"defaultLS": false,
77
"envFile": "placeholder",
88
"venvPath": "placeholder",
99
"venvFolders": "placeholder",
@@ -25,7 +25,7 @@
2525
"linting": {
2626
"enabled": true,
2727
"cwd": "placeholder",
28-
"Flake8Args": "placeholder",
28+
"flake8Args": "placeholder",
2929
"flake8CategorySeverity": false,
3030
"flake8Enabled": true,
3131
"flake8Path": "placeholder",
@@ -85,9 +85,6 @@
8585
"testing": {
8686
"cwd": "placeholder",
8787
"debugPort": true,
88-
"nosetestArgs": "placeholder",
89-
"nosetestsEnabled": true,
90-
"nosetestPath": "placeholder",
9188
"promptToConfigure": true,
9289
"pytestArgs": "placeholder",
9390
"pytestEnabled": true,

src/client/common/application/commands/reportIssueCommand.ts

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ import * as fs from 'fs-extra';
77
import * as os from 'os';
88
import * as path from 'path';
99
import { inject, injectable } from 'inversify';
10+
import { isEqual } from 'lodash';
1011
import { IExtensionSingleActivationService } from '../../../activation/types';
11-
import { ICommandManager, IWorkspaceService } from '../types';
12+
import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from '../types';
1213
import { EXTENSION_ROOT_DIR } from '../../../constants';
1314
import { IInterpreterService } from '../../../interpreter/contracts';
1415
import { Commands } from '../../constants';
1516
import { IConfigurationService, IPythonSettings } from '../../types';
1617
import { sendTelemetryEvent } from '../../../telemetry';
1718
import { EventName } from '../../../telemetry/constants';
1819
import { EnvironmentType } from '../../../pythonEnvironments/info';
20+
import { PythonSettings } from '../../configSettings';
21+
import { SystemVariables } from '../../variables/systemVariables';
1922

2023
/**
2124
* Allows the user to report an issue related to the Python extension using our template.
@@ -24,12 +27,18 @@ import { EnvironmentType } from '../../../pythonEnvironments/info';
2427
export class ReportIssueCommandHandler implements IExtensionSingleActivationService {
2528
public readonly supportedWorkspaceTypes = { untrustedWorkspace: false, virtualWorkspace: true };
2629

30+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31+
private readonly packageJSONSettings: any;
32+
2733
constructor(
2834
@inject(ICommandManager) private readonly commandManager: ICommandManager,
2935
@inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService,
3036
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
3137
@inject(IConfigurationService) protected readonly configurationService: IConfigurationService,
32-
) {}
38+
@inject(IApplicationEnvironment) appEnvironment: IApplicationEnvironment,
39+
) {
40+
this.packageJSONSettings = appEnvironment.packageJson?.contributes?.configuration?.properties;
41+
}
3342

3443
public async activate(): Promise<void> {
3544
this.commandManager.registerCommand(Commands.ReportIssue, this.openReportIssue, this);
@@ -48,20 +57,31 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ
4857
const argSetting = argSettings[property];
4958
if (argSetting) {
5059
if (typeof argSetting === 'object') {
51-
userSettings = userSettings.concat(os.EOL, property, os.EOL);
60+
let propertyHeaderAdded = false;
5261
const argSettingsDict = (settings[property] as unknown) as Record<string, unknown>;
5362
if (typeof argSettingsDict === 'object') {
5463
Object.keys(argSetting).forEach((item) => {
5564
const prop = argSetting[item];
5665
if (prop) {
57-
const value = prop === true ? JSON.stringify(argSettingsDict[item]) : '"<placeholder>"';
58-
userSettings = userSettings.concat('• ', item, ': ', value, os.EOL);
66+
const defaultValue = this.getDefaultValue(`${property}.${item}`);
67+
if (defaultValue === undefined || !isEqual(defaultValue, argSettingsDict[item])) {
68+
if (!propertyHeaderAdded) {
69+
userSettings = userSettings.concat(os.EOL, property, os.EOL);
70+
propertyHeaderAdded = true;
71+
}
72+
const value =
73+
prop === true ? JSON.stringify(argSettingsDict[item]) : '"<placeholder>"';
74+
userSettings = userSettings.concat('• ', item, ': ', value, os.EOL);
75+
}
5976
}
6077
});
6178
}
6279
} else {
63-
const value = argSetting === true ? JSON.stringify(settings[property]) : '"<placeholder>"';
64-
userSettings = userSettings.concat(os.EOL, property, ': ', value, os.EOL);
80+
const defaultValue = this.getDefaultValue(property);
81+
if (defaultValue === undefined || !isEqual(defaultValue, settings[property])) {
82+
const value = argSetting === true ? JSON.stringify(settings[property]) : '"<placeholder>"';
83+
userSettings = userSettings.concat(os.EOL, property, ': ', value, os.EOL);
84+
}
6585
}
6686
}
6787
});
@@ -72,10 +92,30 @@ export class ReportIssueCommandHandler implements IExtensionSingleActivationServ
7292
this.workspaceService.getConfiguration('python').get<string>('languageServer') || 'Not Found';
7393
const virtualEnvKind = interpreter?.envType || EnvironmentType.Unknown;
7494

95+
const hasMultipleFolders = (this.workspaceService.workspaceFolders?.length ?? 0) > 1;
96+
const hasMultipleFoldersText =
97+
hasMultipleFolders && userSettings !== ''
98+
? `Multiroot scenario, following user settings may not apply:${os.EOL}`
99+
: '';
75100
await this.commandManager.executeCommand('workbench.action.openIssueReporter', {
76101
extensionId: 'ms-python.python',
77-
issueBody: template.format(pythonVersion, virtualEnvKind, languageServer, userSettings),
102+
issueBody: template.format(
103+
pythonVersion,
104+
virtualEnvKind,
105+
languageServer,
106+
hasMultipleFoldersText,
107+
userSettings,
108+
),
78109
});
79110
sendTelemetryEvent(EventName.USE_REPORT_ISSUE_COMMAND, undefined, {});
80111
}
112+
113+
private getDefaultValue(settingKey: string) {
114+
if (!this.packageJSONSettings) {
115+
return undefined;
116+
}
117+
const resource = PythonSettings.getSettingsUriAndTarget(undefined, this.workspaceService).uri;
118+
const systemVariables = new SystemVariables(resource, undefined, this.workspaceService);
119+
return systemVariables.resolveAny(this.packageJSONSettings[`python.${settingKey}`]?.default);
120+
}
81121
}

src/test/common/application/commands/issueTemplateVenv1.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ XXX
2828
```
2929
3030
experiments
31-
• enabled: true
31+
• enabled: false
3232
• optInto: []
3333
• optOutFrom: []
3434
3535
venvPath: "<placeholder>"
3636
37+
pipenvPath: "<placeholder>"
38+
3739
```
3840

3941
</p>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!-- Please fill in all XXX markers -->
2+
# Behaviour
3+
## Expected vs. Actual
4+
5+
XXX
6+
7+
## Steps to reproduce:
8+
9+
1. XXX
10+
11+
<!--
12+
**After** creating the issue on GitHub, you can add screenshots and GIFs of what is happening. Consider tools like https://www.cockos.com/licecap/, https://github.com/phw/peek or https://www.screentogif.com/ for GIF creation.
13+
-->
14+
15+
<!-- **NOTE**: Everything below is auto-generated; no editing required. -->
16+
# Diagnostic data
17+
18+
- Python version (& distribution if applicable, e.g. Anaconda): 3.9.0
19+
- Type of virtual environment used (e.g. conda, venv, virtualenv, etc.): Venv
20+
- Value of the `python.languageServer` setting: Pylance
21+
22+
<details>
23+
24+
<summary>User Settings</summary>
25+
26+
<p>
27+
28+
```
29+
Multiroot scenario, following user settings may not apply:
30+
31+
experiments
32+
• enabled: false
33+
34+
venvPath: "<placeholder>"
35+
36+
```
37+
38+
</p>
39+
</details>

src/test/common/application/commands/reportIssueCommand.unit.test.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable global-require */
12
// Copyright (c) Microsoft Corporation. All rights reserved.
23
// Licensed under the MIT License.
34

@@ -8,35 +9,42 @@ import * as fs from 'fs-extra';
89
import * as path from 'path';
910
import { anything, capture, instance, mock, verify, when } from 'ts-mockito';
1011
import { expect } from 'chai';
12+
import { WorkspaceFolder } from 'vscode-languageserver-protocol';
1113
import * as Telemetry from '../../../../client/telemetry';
1214
import { LanguageServerType } from '../../../../client/activation/types';
1315
import { CommandManager } from '../../../../client/common/application/commandManager';
1416
import { ReportIssueCommandHandler } from '../../../../client/common/application/commands/reportIssueCommand';
15-
import { ICommandManager, IWorkspaceService } from '../../../../client/common/application/types';
17+
import {
18+
IApplicationEnvironment,
19+
ICommandManager,
20+
IWorkspaceService,
21+
} from '../../../../client/common/application/types';
1622
import { WorkspaceService } from '../../../../client/common/application/workspace';
1723
import { IInterpreterService } from '../../../../client/interpreter/contracts';
1824
import { MockWorkspaceConfiguration } from '../../../mocks/mockWorkspaceConfig';
19-
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants';
2025
import { InterpreterService } from '../../../../client/interpreter/interpreterService';
21-
import { Commands } from '../../../../client/common/constants';
26+
import { Commands, EXTENSION_ROOT_DIR } from '../../../../client/common/constants';
2227
import { AllCommands } from '../../../../client/common/application/commands';
2328
import { ConfigurationService } from '../../../../client/common/configuration/service';
2429
import { IConfigurationService } from '../../../../client/common/types';
2530
import { EventName } from '../../../../client/telemetry/constants';
2631
import { EnvironmentType, PythonEnvironment } from '../../../../client/pythonEnvironments/info';
32+
import { EXTENSION_ROOT_DIR_FOR_TESTS } from '../../../constants';
2733

2834
suite('Report Issue Command', () => {
2935
let reportIssueCommandHandler: ReportIssueCommandHandler;
3036
let cmdManager: ICommandManager;
3137
let workspaceService: IWorkspaceService;
3238
let interpreterService: IInterpreterService;
3339
let configurationService: IConfigurationService;
40+
let appEnvironment: IApplicationEnvironment;
3441

3542
setup(async () => {
3643
workspaceService = mock(WorkspaceService);
3744
cmdManager = mock(CommandManager);
3845
interpreterService = mock(InterpreterService);
3946
configurationService = mock(ConfigurationService);
47+
appEnvironment = mock<IApplicationEnvironment>();
4048

4149
when(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).thenResolve();
4250
when(workspaceService.getConfiguration('python')).thenReturn(
@@ -51,12 +59,13 @@ suite('Report Issue Command', () => {
5159
when(interpreterService.getActiveInterpreter()).thenResolve(interpreter);
5260
when(configurationService.getSettings()).thenReturn({
5361
experiments: {
54-
enabled: true,
62+
enabled: false,
5563
optInto: [],
5664
optOutFrom: [],
5765
},
5866
initialize: true,
5967
venvPath: 'path',
68+
pipenvPath: 'pipenv',
6069
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6170
} as any);
6271

@@ -67,6 +76,7 @@ suite('Report Issue Command', () => {
6776
instance(workspaceService),
6877
instance(interpreterService),
6978
instance(configurationService),
79+
instance(appEnvironment),
7080
);
7181
await reportIssueCommandHandler.activate();
7282
});
@@ -75,7 +85,7 @@ suite('Report Issue Command', () => {
7585
sinon.restore();
7686
});
7787

78-
test('Test if issue body is filled', async () => {
88+
test('Test if issue body is filled correctly when including all the settings', async () => {
7989
await reportIssueCommandHandler.openReportIssue();
8090

8191
const templatePath = path.join(
@@ -100,6 +110,45 @@ suite('Report Issue Command', () => {
100110
const actual = args[1].issueBody;
101111
expect(actual).to.be.equal(expectedIssueBody);
102112
});
113+
114+
test('Test if issue body is filled when only including settings which are explicitly set', async () => {
115+
// eslint-disable-next-line import/no-dynamic-require
116+
when(appEnvironment.packageJson).thenReturn(require(path.join(EXTENSION_ROOT_DIR, 'package.json')));
117+
when(workspaceService.workspaceFolders).thenReturn([
118+
instance(mock(WorkspaceFolder)),
119+
instance(mock(WorkspaceFolder)),
120+
]); // Multiroot scenario
121+
reportIssueCommandHandler = new ReportIssueCommandHandler(
122+
instance(cmdManager),
123+
instance(workspaceService),
124+
instance(interpreterService),
125+
instance(configurationService),
126+
instance(appEnvironment),
127+
);
128+
await reportIssueCommandHandler.activate();
129+
await reportIssueCommandHandler.openReportIssue();
130+
131+
const templatePath = path.join(
132+
EXTENSION_ROOT_DIR_FOR_TESTS,
133+
'src',
134+
'test',
135+
'common',
136+
'application',
137+
'commands',
138+
'issueTemplateVenv2.md',
139+
);
140+
const expectedIssueBody = fs.readFileSync(templatePath, 'utf8');
141+
142+
const args: [string, { extensionId: string; issueBody: string }] = capture<
143+
AllCommands,
144+
{ extensionId: string; issueBody: string }
145+
>(cmdManager.executeCommand).last();
146+
147+
verify(cmdManager.executeCommand('workbench.action.openIssueReporter', anything())).once();
148+
expect(args[0]).to.be.equal('workbench.action.openIssueReporter');
149+
const actual = args[1].issueBody;
150+
expect(actual).to.be.equal(expectedIssueBody);
151+
});
103152
test('Should send telemetry event when run Report Issue Command', async () => {
104153
const sendTelemetryStub = sinon.stub(Telemetry, 'sendTelemetryEvent');
105154
await reportIssueCommandHandler.openReportIssue();

0 commit comments

Comments
 (0)