Skip to content

Commit 7879cf9

Browse files
gagikalenakhineika
andauthored
feat(tree-explorer): add treeview telemetry and refactor to use a more standardized format VSCODE-651 (#891)
Co-authored-by: Alena Khineika <[email protected]>
1 parent fabbc8c commit 7879cf9

File tree

11 files changed

+177
-63
lines changed

11 files changed

+177
-63
lines changed

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,6 @@ enum EXTENSION_COMMANDS {
8585
SHOW_EXPORT_TO_LANGUAGE_RESULT = 'mdb.showExportToLanguageResult',
8686
}
8787

88+
export type ExtensionCommand = EXTENSION_COMMANDS;
89+
8890
export default EXTENSION_COMMANDS;

src/documentSource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ export enum DocumentSource {
22
DOCUMENT_SOURCE_TREEVIEW = 'treeview',
33
DOCUMENT_SOURCE_PLAYGROUND = 'playground',
44
DOCUMENT_SOURCE_COLLECTIONVIEW = 'collectionview',
5-
DOCUMENT_SOURCE_QUERY_WITH_COPILOT_CODELENS = 'query with copilot codelens',
5+
DOCUMENT_SOURCE_CODELENS = 'codelens',
66
}

src/editors/queryWithCopilotCodeLensProvider.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@ export class QueryWithCopilotCodeLensProvider
3131
prompt: 'Describe the query you would like to generate',
3232
placeHolder:
3333
'e.g. Find the document in sample_mflix.users with the name of Kayden Washington',
34-
messagePrefix: '/query',
34+
command: 'query',
3535
isNewChat: true,
36-
source: DocumentSource.DOCUMENT_SOURCE_QUERY_WITH_COPILOT_CODELENS,
36+
telemetry: {
37+
source: DocumentSource.DOCUMENT_SOURCE_CODELENS,
38+
},
3739
};
3840

3941
return [

src/mdbExtensionController.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from './explorer';
2626
import ExportToLanguageCodeLensProvider from './editors/exportToLanguageCodeLensProvider';
2727
import { type ExportToLanguageResult } from './types/playgroundType';
28-
import EXTENSION_COMMANDS from './commands';
28+
import type { ExtensionCommand } from './commands';
2929
import type FieldTreeItem from './explorer/fieldTreeItem';
3030
import type IndexListTreeItem from './explorer/indexListTreeItem';
3131
import { LanguageServerController } from './language';
@@ -40,21 +40,20 @@ import WebviewController from './views/webviewController';
4040
import { createIdFactory, generateId } from './utils/objectIdHelper';
4141
import { ConnectionStorage } from './storage/connectionStorage';
4242
import type StreamProcessorTreeItem from './explorer/streamProcessorTreeItem';
43-
import type {
44-
ParticipantCommand,
45-
RunParticipantCodeCommandArgs,
46-
} from './participant/participant';
43+
import type { RunParticipantCodeCommandArgs } from './participant/participant';
4744
import ParticipantController from './participant/participant';
4845
import type { OpenSchemaCommandArgs } from './participant/prompts/schema';
4946
import { QueryWithCopilotCodeLensProvider } from './editors/queryWithCopilotCodeLensProvider';
5047
import type {
5148
SendMessageToParticipantOptions,
5249
SendMessageToParticipantFromInputOptions,
50+
ParticipantCommand,
5351
} from './participant/participantTypes';
5452
import {
5553
COPILOT_CHAT_EXTENSION_ID,
5654
COPILOT_EXTENSION_ID,
5755
} from './participant/constants';
56+
import EXTENSION_COMMANDS from './commands';
5857

5958
// This class is the top-level controller for our extension.
6059
// Commands which the extensions handles are defined in the function `activate`.
@@ -402,7 +401,7 @@ export default class MDBExtensionController implements vscode.Disposable {
402401
};
403402

404403
registerParticipantCommand = (
405-
command: string,
404+
command: ExtensionCommand,
406405
commandHandler: (...args: any[]) => Promise<boolean>
407406
): void => {
408407
const commandHandlerWithTelemetry = (args: any[]): Promise<boolean> => {
@@ -420,7 +419,7 @@ export default class MDBExtensionController implements vscode.Disposable {
420419
};
421420

422421
registerCommand = (
423-
command: string,
422+
command: ExtensionCommand,
424423
commandHandler: (...args: any[]) => Promise<boolean>
425424
): void => {
426425
const commandHandlerWithTelemetry = (args: any[]): Promise<boolean> => {

src/participant/participant.ts

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ import { PromptHistory } from './prompts/promptHistory';
5252
import type {
5353
SendMessageToParticipantOptions,
5454
SendMessageToParticipantFromInputOptions,
55+
ParticipantCommand,
56+
ParticipantCommandType,
5557
} from './participantTypes';
5658
import { DEFAULT_EXPORT_TO_LANGUAGE_DRIVER_SYNTAX } from '../editors/exportToLanguageCodeLensProvider';
5759
import { EXPORT_TO_LANGUAGE_ALIASES } from '../editors/playgroundSelectionCodeActionProvider';
5860
import { CollectionTreeItem, DatabaseTreeItem } from '../explorer';
61+
import { DocumentSource } from '../documentSource';
5962

6063
const log = createLogger('participant');
6164

@@ -72,8 +75,6 @@ export type RunParticipantCodeCommandArgs = {
7275
runnableContent: string;
7376
};
7477

75-
export type ParticipantCommand = '/query' | '/schema' | '/docs';
76-
7778
const MAX_MARKDOWN_LIST_LENGTH = 10;
7879

7980
export default class ParticipantController {
@@ -121,6 +122,7 @@ export default class ParticipantController {
121122
participantId: this._participant?.id,
122123
});
123124
this._participant.onDidReceiveFeedback(this.handleUserFeedback.bind(this));
125+
124126
return this._participant;
125127
}
126128

@@ -142,6 +144,8 @@ export default class ParticipantController {
142144
message,
143145
isNewChat = false,
144146
isPartialQuery = false,
147+
telemetry,
148+
command,
145149
...otherOptions
146150
} = options;
147151

@@ -151,10 +155,28 @@ export default class ParticipantController {
151155
'workbench.action.chat.clearHistory'
152156
);
153157
}
158+
const commandPrefix = command ? `/${command} ` : '';
159+
const query = `@MongoDB ${commandPrefix}${message}`;
160+
161+
if (telemetry) {
162+
if (isNewChat) {
163+
this._telemetryService.trackParticipantChatOpenedFromAction({
164+
...telemetry,
165+
command,
166+
});
167+
}
168+
if (!isPartialQuery) {
169+
this._telemetryService.trackParticipantPromptSubmittedFromAction({
170+
...telemetry,
171+
command: command ?? 'generic',
172+
input_length: query.length,
173+
});
174+
}
175+
}
154176

155177
return await vscode.commands.executeCommand('workbench.action.chat.open', {
156178
...otherOptions,
157-
query: `@MongoDB ${message}`,
179+
query,
158180
isPartialQuery,
159181
});
160182
}
@@ -163,27 +185,34 @@ export default class ParticipantController {
163185
options: SendMessageToParticipantFromInputOptions
164186
): Promise<unknown> {
165187
const {
166-
messagePrefix = '',
167-
isNewChat = false,
168-
isPartialQuery = false,
169-
source,
188+
isNewChat,
189+
isPartialQuery,
190+
telemetry,
191+
command,
170192
...inputBoxOptions
171193
} = options;
172194

173-
this._telemetryService.trackCopilotParticipantSubmittedFromInputBox({
174-
source,
175-
});
176-
177195
const message = await vscode.window.showInputBox({
178196
...inputBoxOptions,
179197
});
180198

199+
if (telemetry) {
200+
this._telemetryService.trackParticipantInputBoxSubmitted({
201+
...telemetry,
202+
input_length: message?.length,
203+
dismissed: message === undefined,
204+
command,
205+
});
206+
}
207+
181208
if (message === undefined || message.trim() === '') {
182209
return Promise.resolve();
183210
}
184211

185212
return this.sendMessageToParticipant({
186-
message: `${messagePrefix ? `${messagePrefix} ` : ''}${message}`,
213+
message,
214+
telemetry,
215+
command,
187216
isNewChat,
188217
isPartialQuery,
189218
});
@@ -198,13 +227,21 @@ export default class ParticipantController {
198227
await this.sendMessageToParticipant({
199228
message: `I want to ask questions about the \`${databaseName}\` database.`,
200229
isNewChat: true,
230+
telemetry: {
231+
source: DocumentSource.DOCUMENT_SOURCE_TREEVIEW,
232+
source_details: 'database',
233+
},
201234
});
202235
} else if (treeItem instanceof CollectionTreeItem) {
203236
const { databaseName, collectionName } = treeItem;
204237

205238
await this.sendMessageToParticipant({
206239
message: `I want to ask questions about the \`${databaseName}\` database's \`${collectionName}\` collection.`,
207240
isNewChat: true,
241+
telemetry: {
242+
source: DocumentSource.DOCUMENT_SOURCE_TREEVIEW,
243+
source_details: 'collection',
244+
},
208245
});
209246
} else {
210247
throw new Error('Unsupported tree item type');
@@ -233,7 +270,7 @@ export default class ParticipantController {
233270
})
234271
),
235272
});
236-
this._telemetryService.trackCopilotParticipantPrompt(modelInput.stats);
273+
this._telemetryService.trackParticipantPrompt(modelInput.stats);
237274

238275
const modelResponse = await model.sendRequest(
239276
modelInput.messages,
@@ -413,7 +450,7 @@ export default class ParticipantController {
413450
stream,
414451
});
415452

416-
this._telemetryService.trackCopilotParticipantResponse({
453+
this._telemetryService.trackParticipantResponse({
417454
command: 'generic',
418455
has_cta: false,
419456
found_namespace: false,
@@ -1380,7 +1417,7 @@ export default class ParticipantController {
13801417
],
13811418
});
13821419

1383-
this._telemetryService.trackCopilotParticipantResponse({
1420+
this._telemetryService.trackParticipantResponse({
13841421
command: 'schema',
13851422
has_cta: true,
13861423
found_namespace: true,
@@ -1491,7 +1528,7 @@ export default class ParticipantController {
14911528
token,
14921529
});
14931530

1494-
this._telemetryService.trackCopilotParticipantResponse({
1531+
this._telemetryService.trackParticipantResponse({
14951532
command: 'query',
14961533
has_cta: false,
14971534
found_namespace: true,
@@ -1597,7 +1634,7 @@ export default class ParticipantController {
15971634

15981635
this._streamGenericDocsLink(stream);
15991636

1600-
this._telemetryService.trackCopilotParticipantResponse({
1637+
this._telemetryService.trackParticipantResponse({
16011638
command: 'docs/copilot',
16021639
has_cta: true,
16031640
found_namespace: false,
@@ -1677,7 +1714,7 @@ export default class ParticipantController {
16771714
}
16781715
}
16791716

1680-
this._telemetryService.trackCopilotParticipantResponse({
1717+
this._telemetryService.trackParticipantResponse({
16811718
command: 'docs/chatbot',
16821719
has_cta: !!docsResult.responseReferences,
16831720
found_namespace: false,
@@ -1795,10 +1832,7 @@ export default class ParticipantController {
17951832
return true;
17961833
} catch (error) {
17971834
const message = formatError(error).message;
1798-
this._telemetryService.trackCopilotParticipantError(
1799-
error,
1800-
'exportToPlayground'
1801-
);
1835+
this._telemetryService.trackParticipantError(error, 'exportToPlayground');
18021836
void vscode.window.showErrorMessage(
18031837
`An error occurred exporting to a playground: ${message}`
18041838
);
@@ -1905,9 +1939,9 @@ Please see our [FAQ](https://www.mongodb.com/docs/generative-ai-faq/) for more i
19051939
return await this.handleGenericRequest(...args);
19061940
}
19071941
} catch (error) {
1908-
this._telemetryService.trackCopilotParticipantError(
1942+
this._telemetryService.trackParticipantError(
19091943
error,
1910-
request.command || 'generic'
1944+
(request.command as ParticipantCommandType) || 'generic'
19111945
);
19121946
// Re-throw other errors so they show up in the UI.
19131947
throw error;
@@ -1956,7 +1990,7 @@ Please see our [FAQ](https://www.mongodb.com/docs/generative-ai-faq/) for more i
19561990
'unhelpfulReason' in feedback
19571991
? (feedback.unhelpfulReason as string)
19581992
: undefined;
1959-
this._telemetryService.trackCopilotParticipantFeedback({
1993+
this._telemetryService.trackParticipantFeedback({
19601994
feedback: chatResultFeedbackKindToTelemetryValue(feedback.kind),
19611995
reason: unhelpfulReason,
19621996
response_type: (feedback.result as ChatResult)?.metadata.intent,
Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
11
import type * as vscode from 'vscode';
22
import type { DocumentSource } from '../documentSource';
33

4+
export type ParticipantCommandType = 'query' | 'schema' | 'docs';
5+
export type ParticipantCommand = `/${ParticipantCommandType}`;
6+
7+
export type ParticipantRequestType = ParticipantCommandType | 'generic';
8+
9+
export type ParticipantResponseType =
10+
| 'query'
11+
| 'schema'
12+
| 'docs'
13+
| 'docs/chatbot'
14+
| 'docs/copilot'
15+
| 'exportToPlayground'
16+
| 'generic'
17+
| 'emptyRequest'
18+
| 'cancelledRequest'
19+
| 'askToConnect'
20+
| 'askForNamespace';
21+
22+
type TelemetryMetadata = {
23+
source: DocumentSource;
24+
source_details?: 'database' | 'collection';
25+
};
26+
27+
/** Based on options from Copilot's chat open command IChatViewOpenOptions */
428
export type SendMessageToParticipantOptions = {
529
message: string;
30+
command?: ParticipantCommandType;
631
isNewChat?: boolean;
732
isPartialQuery?: boolean;
33+
telemetry?: TelemetryMetadata;
834
};
935

10-
export type SendMessageToParticipantFromInputOptions = {
11-
messagePrefix?: string;
12-
source?: DocumentSource;
13-
} & Omit<SendMessageToParticipantOptions, 'message'> &
36+
export type SendMessageToParticipantFromInputOptions = Pick<
37+
SendMessageToParticipantOptions,
38+
'isNewChat' | 'isPartialQuery' | 'command' | 'telemetry'
39+
> &
1440
vscode.InputBoxOptions;

src/participant/prompts/promptBase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
ParticipantPromptProperties,
66
} from '../../telemetry/telemetryService';
77
import { PromptHistory } from './promptHistory';
8+
import type { ParticipantCommandType } from '../participantTypes';
89

910
export interface PromptArgsBase {
1011
request: {
@@ -175,7 +176,7 @@ export abstract class PromptBase<TArgs extends PromptArgsBase> {
175176
),
176177
user_input_length: request.prompt.length,
177178
has_sample_documents: hasSampleDocs,
178-
command: request.command || 'generic',
179+
command: (request.command as ParticipantCommandType) || 'generic',
179180
history_size: context?.history.length || 0,
180181
internal_purpose: this.internalPurposeForTelemetry,
181182
};

src/participant/prompts/promptHistory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as vscode from 'vscode';
22
import { ParticipantErrorTypes } from '../participantErrorTypes';
3-
import type { ChatResult, ParticipantResponseType } from '../constants';
3+
import type { ChatResult } from '../constants';
4+
import type { ParticipantResponseType } from '../participantTypes';
45

56
export class PromptHistory {
67
private static _handleChatResponseTurn({

0 commit comments

Comments
 (0)