Skip to content

Commit f841dbe

Browse files
authored
Merge pull request #85 from microsoft/don/issueReporter
Support for issue reporter
2 parents 2e498bd + 3f20ff1 commit f841dbe

File tree

6 files changed

+137
-15
lines changed

6 files changed

+137
-15
lines changed

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,22 @@
5656
"icon": "$(copilot)",
5757
"title": "%commands.dachat.analyzeCsv.title%",
5858
"shortTitle": "%commands.dachat.analyzeCsv.shortTitle%"
59+
},
60+
{
61+
"category": "Data Analysis",
62+
"command": "dachat.reportIssue",
63+
"title": "Report Issue..."
5964
}
6065
],
6166
"menus": {
6267
"commandPalette": [
6368
{
6469
"command": "dachat.analyzeCsv",
6570
"when": "false"
71+
},
72+
{
73+
"command": "dachat.reportIssue",
74+
"when": "true"
6675
}
6776
],
6877
"editor/title": [

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import * as vscode from 'vscode';
66
import { registerCsvCommand } from './csvCommand';
77
import { DataAgent } from './dataAgent';
8+
import { registerIssueReporter } from './issueReporter';
89
import { initializeLogger } from './logger';
910
import { FindFilesTool, InstallPythonPackageTool, RunPythonTool } from './tools';
1011

@@ -14,6 +15,7 @@ export function activate(context: vscode.ExtensionContext) {
1415
context.subscriptions.push(logger);
1516
context.subscriptions.push(dataAgent);
1617
context.subscriptions.push(registerCsvCommand());
18+
context.subscriptions.push(registerIssueReporter(context));
1719
context.subscriptions.push(vscode.lm.registerTool(FindFilesTool.Id, new FindFilesTool(context)));
1820
const pythonTool = new RunPythonTool(context);
1921
context.subscriptions.push(vscode.lm.registerTool(RunPythonTool.Id, pythonTool));

src/issueReporter.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation and GitHub. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
import { commands, ExtensionContext } from 'vscode';
6+
import { getLastErrors } from './logger';
7+
8+
export function registerIssueReporter(context: ExtensionContext) {
9+
return commands.registerCommand('dachat.reportIssue', () => {
10+
commands.executeCommand('workbench.action.openIssueReporter', {
11+
extensionId: context.extension.id,
12+
issueBody: issueBody,
13+
data: getIssueData()
14+
});
15+
});
16+
}
17+
18+
const issueBody = `
19+
<!-- Please fill in all XXX markers -->
20+
# Behaviour
21+
22+
XXX
23+
24+
## Steps to reproduce:
25+
26+
1. XXX
27+
28+
<!--
29+
**After** creating the issue on GitHub, you can add screenshots and GIFs of what is happening.
30+
Consider tools like https://gifcap.dev, https://www.screentogif.com/ for GIF creation.
31+
-->
32+
33+
<!-- **NOTE**: Please do provide logs from Data Analysis Output panel. -->
34+
<!-- Use the command \`Output: Focus on Output View\`, select \`Data Analysis\` from the dropdown -->
35+
<!-- Copy the output and past it in the XXX region -->
36+
37+
# Outputs
38+
39+
<details>
40+
41+
<summary>Output from Data Analysis Output Panel</summary>
42+
43+
<p>
44+
45+
\`\`\`
46+
XXX
47+
\`\`\`
48+
49+
</p>
50+
</details>
51+
`;
52+
53+
54+
function getIssueData() {
55+
const error = getLastErrors().trim();
56+
if (!error) {
57+
return '';
58+
}
59+
return `
60+
<details>
61+
<summary>Last few Errors</summary>
62+
<p>
63+
64+
\`\`\`
65+
${error}
66+
\`\`\`
67+
</p>
68+
</details>
69+
`;
70+
};

src/logger.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
*--------------------------------------------------------------------------------------------*/
44

55
import { ExtensionContext, ExtensionMode, LogOutputChannel, window } from "vscode";
6+
import { StopWatch } from "./platform/common/stopwatch";
67

78
let logger: LogOutputChannel;
89

10+
const lastSeenError = {
11+
timer: new StopWatch(),
12+
error: ''
13+
}
14+
915
export function initializeLogger(extensionContext: ExtensionContext) {
1016
if (!logger) {
1117
logger = window.createOutputChannel('Data Analysis', { log: true });
@@ -17,9 +23,32 @@ export function initializeLogger(extensionContext: ExtensionContext) {
1723

1824
debug.bind(logger)(message, ...args);
1925
};
26+
const error = logger.error;
27+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
28+
logger.error = (errorMsg: string | Error, ...args: any[]) => {
29+
// Get track of the last known error for issue reporting purposes.
30+
lastSeenError.timer.reset();
31+
lastSeenError.error = [`${getTime()} ${errorMsg.toString()}`].concat(args.map(arg => `${arg}`)).join('\n');
32+
error.bind(logger)(errorMsg, ...args);
33+
}
2034
}
2135

2236
return logger;
2337
}
2438

25-
export { logger };
39+
40+
function getTime() {
41+
const now = new Date();
42+
return now.toTimeString().split(' ')[0];
43+
}
44+
45+
function getLastErrors() {
46+
// If we haven't see any errors in the past 20 minutes, no point reporting any old errors.
47+
if (!lastSeenError.error || lastSeenError.timer.elapsedTime > 20 * 60 * 1000) {
48+
return '';
49+
}
50+
return lastSeenError.error;
51+
}
52+
53+
export { getLastErrors, logger };
54+

src/platform/common/stopwatch.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation and GitHub. All rights reserved.
3+
*--------------------------------------------------------------------------------------------*/
4+
5+
6+
/**
7+
* Tracks wall clock time. Start time is set at contruction.
8+
*/
9+
export class StopWatch {
10+
private started = Date.now();
11+
public get elapsedTime() {
12+
return Date.now() - this.started;
13+
}
14+
public reset() {
15+
this.started = Date.now();
16+
}
17+
}

src/tools.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class RunPythonTool implements vscode.LanguageModelTool<IRunPythonParamet
7878
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7979
error: (message: string, ...args: any[]) => logger.error(`Pyodide => ${message}`, ...args),
8080
// eslint-disable-next-line @typescript-eslint/no-explicit-any
81-
info: (message: string, ...args: any[]) => logger.info(`Pyodide => ${message}`, ...args)
81+
info: (message: string, ...args: any[]) => logger.debug(`Pyodide => ${message}`, ...args)
8282
}
8383
});
8484
}
@@ -88,19 +88,13 @@ export class RunPythonTool implements vscode.LanguageModelTool<IRunPythonParamet
8888
_token: vscode.CancellationToken
8989
) {
9090
const code = sanitizePythonCode(options.input.code);
91-
logger.debug(`Executing Python Code for "${options.input.reason}"`);
92-
logger.debug(`Code => `);
93-
logger.debug(code);
91+
logger.info(`Executing Python Code for "${options.input.reason || ''}"`);
92+
logger.info(`Code => `, code);
9493

9594
this.pendingRequests = this.pendingRequests.finally().then(() => this._kernel.execute(code));
9695
const result = await this.pendingRequests as Awaited<ReturnType<typeof Kernel.prototype.execute>>;
9796

98-
logger.debug(`Result => `);
99-
Object.keys(result || {}).forEach(key => {
100-
logger.debug(`${key} :`);
101-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
102-
logger.debug((result as any)[key]);
103-
});
97+
logger.debug(`Result => `, JSON.stringify(result));
10498

10599
const content: (vscode.LanguageModelPromptTsxPart | vscode.LanguageModelTextPart)[] = []
106100
if (result && result['text/plain']) {
@@ -112,7 +106,9 @@ export class RunPythonTool implements vscode.LanguageModelTool<IRunPythonParamet
112106
}
113107

114108
if (result && result['application/vnd.code.notebook.error']) {
115-
throw result['application/vnd.code.notebook.error'] as Error;
109+
const error = result['application/vnd.code.notebook.error'] as Error;
110+
logger.error(`Toolcall failed, Error ${error.name}, ${error.message}`);
111+
throw error;
116112
}
117113
return new vscode.LanguageModelToolResult(content);
118114
}
@@ -188,7 +184,7 @@ export class InstallPythonPackageTool implements vscode.LanguageModelTool<IInsta
188184
options: vscode.LanguageModelToolInvocationOptions<IInstallPythonPackage>,
189185
token: vscode.CancellationToken
190186
) {
191-
logger.debug(`Installing Package "${options.input.package}"`);
187+
logger.info(`Installing Package "${options.input.package}"`);
192188
const result = await this.pythonTool.invoke({
193189
input: {
194190
code: `import ${options.input.package}`,
@@ -197,8 +193,7 @@ export class InstallPythonPackageTool implements vscode.LanguageModelTool<IInsta
197193
tokenizationOptions: options.tokenizationOptions
198194
}, token);
199195

200-
logger.debug(`Result after installing package ${options.input.package} => `);
201-
logger.debug(JSON.stringify(result));
196+
logger.debug(`Result after installing package ${options.input.package} => `, JSON.stringify(result));
202197

203198
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart('Installation successful')]);
204199
}

0 commit comments

Comments
 (0)