Skip to content

Commit 172bc53

Browse files
authored
fix(playground): add better handling and tracking for export to playground errors VSCODE-666 (#906)
1 parent 30e1ace commit 172bc53

File tree

4 files changed

+125
-36
lines changed

4 files changed

+125
-36
lines changed

src/participant/participant.ts

Lines changed: 74 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ import { processStreamWithIdentifiers } from './streamParsing';
4545
import type { PromptIntent } from './prompts/intent';
4646
import { isPlayground, getSelectedText, getAllText } from '../utils/playground';
4747
import type { DataService } from 'mongodb-data-service';
48-
import { ParticipantErrorTypes } from './participantErrorTypes';
48+
import {
49+
ParticipantErrorTypes,
50+
type ExportToPlaygroundError,
51+
} from './participantErrorTypes';
4952
import type PlaygroundResultProvider from '../editors/playgroundResultProvider';
5053
import { isExportToLanguageResult } from '../types/playgroundType';
5154
import { PromptHistory } from './prompts/promptHistory';
@@ -345,26 +348,35 @@ export default class ParticipantController {
345348
token: vscode.CancellationToken;
346349
language?: string;
347350
}): Promise<string | null> {
348-
const chatResponse = await this._getChatResponse({
349-
modelInput,
350-
token,
351-
});
351+
try {
352+
const chatResponse = await this._getChatResponse({
353+
modelInput,
354+
token,
355+
});
352356

353-
const languageCodeBlockIdentifier = {
354-
start: `\`\`\`${language ? language : 'javascript'}`,
355-
end: '```',
356-
};
357+
const languageCodeBlockIdentifier = {
358+
start: `\`\`\`${language ? language : 'javascript'}`,
359+
end: '```',
360+
};
357361

358-
const runnableContent: string[] = [];
359-
await processStreamWithIdentifiers({
360-
processStreamFragment: () => {},
361-
onStreamIdentifier: (content: string) => {
362-
runnableContent.push(content.trim());
363-
},
364-
inputIterable: chatResponse.text,
365-
identifier: languageCodeBlockIdentifier,
366-
});
367-
return runnableContent.length ? runnableContent.join('') : null;
362+
const runnableContent: string[] = [];
363+
await processStreamWithIdentifiers({
364+
processStreamFragment: () => {},
365+
onStreamIdentifier: (content: string) => {
366+
runnableContent.push(content.trim());
367+
},
368+
inputIterable: chatResponse.text,
369+
identifier: languageCodeBlockIdentifier,
370+
});
371+
return runnableContent.length ? runnableContent.join('') : null;
372+
} catch (error) {
373+
/** If anything goes wrong with the response or the stream, return null instead of throwing. */
374+
log.error(
375+
'Error while streaming chat response with export to language',
376+
error
377+
);
378+
return null;
379+
}
368380
}
369381

370382
async streamChatResponseContentWithCodeActions({
@@ -1784,49 +1796,75 @@ export default class ParticipantController {
17841796
}
17851797

17861798
async exportCodeToPlayground(): Promise<boolean> {
1787-
const selectedText = getSelectedText();
1788-
const codeToExport = selectedText || getAllText();
1799+
const codeToExport = getSelectedText() || getAllText();
17891800

17901801
try {
1791-
const content = await vscode.window.withProgress(
1802+
const contentOrError = await vscode.window.withProgress<
1803+
{ value: string } | { error: ExportToPlaygroundError }
1804+
>(
17921805
{
17931806
location: vscode.ProgressLocation.Notification,
17941807
title: 'Exporting code to a playground...',
17951808
cancellable: true,
17961809
},
1797-
async (progress, token): Promise<string | null> => {
1798-
const modelInput = await Prompts.exportToPlayground.buildMessages({
1799-
request: { prompt: codeToExport },
1800-
});
1810+
async (
1811+
progress,
1812+
token
1813+
): Promise<{ value: string } | { error: ExportToPlaygroundError }> => {
1814+
let modelInput: ModelInput | undefined;
1815+
try {
1816+
modelInput = await Prompts.exportToPlayground.buildMessages({
1817+
request: { prompt: codeToExport },
1818+
});
1819+
} catch (error) {
1820+
return { error: 'modelInput' };
1821+
}
18011822

18021823
const result = await Promise.race([
18031824
this.streamChatResponseWithExportToLanguage({
18041825
modelInput,
18051826
token,
18061827
}),
1807-
new Promise<null>((resolve) =>
1828+
new Promise<ExportToPlaygroundError>((resolve) =>
18081829
token.onCancellationRequested(() => {
18091830
log.info('The export to a playground operation was canceled.');
1810-
resolve(null);
1831+
resolve('cancelled');
18111832
})
18121833
),
18131834
]);
18141835

1815-
if (result?.includes("Sorry, I can't assist with that.")) {
1816-
void vscode.window.showErrorMessage(
1817-
'Sorry, we were unable to generate the playground, please try again. If the error persists, try changing your selected code.'
1818-
);
1819-
return null;
1836+
if (result === 'cancelled') {
1837+
return { error: 'cancelled' };
18201838
}
18211839

1822-
return result;
1840+
if (!result || result?.includes("Sorry, I can't assist with that.")) {
1841+
return { error: 'streamChatResponseWithExportToLanguage' };
1842+
}
1843+
1844+
return { value: result };
18231845
}
18241846
);
18251847

1826-
if (!content) {
1827-
return true;
1848+
if ('error' in contentOrError) {
1849+
const { error } = contentOrError;
1850+
if (error === 'cancelled') {
1851+
return true;
1852+
}
1853+
1854+
void vscode.window.showErrorMessage(
1855+
'Failed to generate a MongoDB Playground. Please ensure your code block contains a MongoDB query.'
1856+
);
1857+
1858+
// Content in this case is already equal to the failureType; this is just to make it explicit
1859+
// and avoid accidentally sending actual contents of the message.
1860+
this._telemetryService.trackExportToPlaygroundFailed({
1861+
input_length: codeToExport?.length,
1862+
error_name: error,
1863+
});
1864+
return false;
18281865
}
18291866

1867+
const content = contentOrError.value;
18301868
await vscode.commands.executeCommand(
18311869
EXTENSION_COMMANDS.OPEN_PARTICIPANT_CODE_IN_PLAYGROUND,
18321870
{

src/participant/participantErrorTypes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ export enum ParticipantErrorTypes {
55
OTHER = 'Other',
66
DOCS_CHATBOT_API = 'Docs Chatbot API Issue',
77
}
8+
9+
export type ExportToPlaygroundError =
10+
| 'cancelled'
11+
| 'modelInput'
12+
| 'streamChatResponseWithExportToLanguage';

src/telemetry/telemetryService.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getConnectionTelemetryProperties } from './connectionTelemetry';
1212
import type { NewConnectionTelemetryEventProperties } from './connectionTelemetry';
1313
import type { ShellEvaluateResult } from '../types/playgroundType';
1414
import type { StorageController } from '../storage';
15+
import type { ExportToPlaygroundError } from '../participant/participantErrorTypes';
1516
import { ParticipantErrorTypes } from '../participant/participantErrorTypes';
1617
import type { ExtensionCommand } from '../commands';
1718
import type {
@@ -54,6 +55,11 @@ type DocumentEditedTelemetryEventProperties = {
5455
source: DocumentSource;
5556
};
5657

58+
type ExportToPlaygroundFailedEventProperties = {
59+
input_length: number | undefined;
60+
error_name?: ExportToPlaygroundError;
61+
};
62+
5763
type PlaygroundExportedToLanguageTelemetryEventProperties = {
5864
language?: string;
5965
exported_code_length: number;
@@ -106,6 +112,7 @@ type ParticipantResponseFailedProperties = {
106112
command: ParticipantResponseType;
107113
error_code?: string;
108114
error_name: ParticipantErrorTypes;
115+
error_details?: string;
109116
};
110117

111118
export type InternalPromptPurpose = 'intent' | 'namespace' | undefined;
@@ -171,6 +178,7 @@ type TelemetryEventProperties =
171178
| PlaygroundSavedTelemetryEventProperties
172179
| PlaygroundLoadedTelemetryEventProperties
173180
| KeytarSecretsMigrationFailedProperties
181+
| ExportToPlaygroundFailedEventProperties
174182
| SavedConnectionsLoadedProperties
175183
| ParticipantFeedbackProperties
176184
| ParticipantResponseFailedProperties
@@ -193,6 +201,7 @@ export enum TelemetryEventTypes {
193201
PLAYGROUND_EXPORTED_TO_LANGUAGE = 'Playground Exported To Language',
194202
PLAYGROUND_CREATED = 'Playground Created',
195203
KEYTAR_SECRETS_MIGRATION_FAILED = 'Keytar Secrets Migration Failed',
204+
EXPORT_TO_PLAYGROUND_FAILED = 'Export To Playground Failed',
196205
SAVED_CONNECTIONS_LOADED = 'Saved Connections Loaded',
197206
PARTICIPANT_FEEDBACK = 'Participant Feedback',
198207
PARTICIPANT_WELCOME_SHOWN = 'Participant Welcome Shown',
@@ -346,6 +355,12 @@ export default class TelemetryService {
346355
);
347356
}
348357

358+
trackExportToPlaygroundFailed(
359+
props: ExportToPlaygroundFailedEventProperties
360+
): void {
361+
this.track(TelemetryEventTypes.EXPORT_TO_PLAYGROUND_FAILED, props);
362+
}
363+
349364
trackCommandRun(command: ExtensionCommand): void {
350365
this.track(TelemetryEventTypes.EXTENSION_COMMAND_RUN, { command });
351366
}

src/test/suite/participant/participant.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,6 +1818,37 @@ Schema:
18181818
);
18191819
});
18201820

1821+
test('tracks failures with export to playground and not as a failed prompt', async function () {
1822+
const editor = vscode.window.activeTextEditor;
1823+
if (!editor) {
1824+
throw new Error('Window active text editor is undefined');
1825+
}
1826+
1827+
const testDocumentUri = editor.document.uri;
1828+
const edit = new vscode.WorkspaceEdit();
1829+
const code = `
1830+
THIS IS SOME ERROR CAUSING CODE.
1831+
`;
1832+
edit.replace(testDocumentUri, getFullRange(editor.document), code);
1833+
await vscode.workspace.applyEdit(edit);
1834+
1835+
await testParticipantController.exportCodeToPlayground();
1836+
sendRequestStub.rejects();
1837+
const messages = sendRequestStub.firstCall.args[0];
1838+
expect(getMessageContent(messages[1])).to.equal(code.trim());
1839+
expect(telemetryTrackStub).calledWith(
1840+
TelemetryEventTypes.EXPORT_TO_PLAYGROUND_FAILED,
1841+
{
1842+
input_length: code.trim().length,
1843+
error_name: 'streamChatResponseWithExportToLanguage',
1844+
}
1845+
);
1846+
1847+
expect(telemetryTrackStub).not.calledWith(
1848+
TelemetryEventTypes.PARTICIPANT_RESPONSE_FAILED
1849+
);
1850+
});
1851+
18211852
test('exports selected lines of code to a playground', async function () {
18221853
const editor = vscode.window.activeTextEditor;
18231854
if (!editor) {

0 commit comments

Comments
 (0)