Skip to content

Commit f18430f

Browse files
gagikalenakhineika
andauthored
feat(playground): add Generate Query with Copilot code lens in playgrounds VSCODE-650 (#881)
Co-authored-by: Alena Khineika <[email protected]>
1 parent e93006a commit f18430f

13 files changed

+314
-31
lines changed

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ enum EXTENSION_COMMANDS {
7373

7474
// Chat participant.
7575
OPEN_PARTICIPANT_CODE_IN_PLAYGROUND = 'mdb.openParticipantCodeInPlayground',
76+
SEND_MESSAGE_TO_PARTICIPANT = 'mdb.sendMessageToParticipant',
77+
SEND_MESSAGE_TO_PARTICIPANT_FROM_INPUT = 'mdb.sendMessageToParticipantFromInput',
7678
RUN_PARTICIPANT_CODE = 'mdb.runParticipantCode',
7779
CONNECT_WITH_PARTICIPANT = 'mdb.connectWithParticipant',
7880
SELECT_DATABASE_WITH_PARTICIPANT = 'mdb.selectDatabaseWithParticipant',

src/connectionController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export default class ConnectionController {
201201
ignoreFocusOut: true,
202202
placeHolder:
203203
'e.g. mongodb+srv://username:[email protected]/admin',
204-
prompt: 'Enter your connection string (SRV or standard)',
204+
prompt: 'Enter your SRV or standard connection string',
205205
validateInput: (uri: string) => {
206206
if (
207207
!uri.startsWith('mongodb://') &&

src/documentSource.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +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',
56
}

src/editors/activeConnectionCodeLensProvider.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default class ActiveConnectionCodeLensProvider
2424
this._onDidChangeCodeLenses.fire();
2525
});
2626

27-
this._activeConnectionChangedHandler = () => {
27+
this._activeConnectionChangedHandler = (): void => {
2828
this._onDidChangeCodeLenses.fire();
2929
};
3030
this._connectionController.addEventListener(
@@ -50,10 +50,10 @@ export default class ActiveConnectionCodeLensProvider
5050
? getDBFromConnectionString(connectionString)
5151
: null;
5252
message = defaultDB
53-
? `Currently connected to ${this._connectionController.getActiveConnectionName()} with default database ${defaultDB}. Click here to change connection.`
54-
: `Currently connected to ${this._connectionController.getActiveConnectionName()}. Click here to change connection.`;
53+
? `Connected to ${this._connectionController.getActiveConnectionName()} with default database ${defaultDB}`
54+
: `Connected to ${this._connectionController.getActiveConnectionName()}`;
5555
} else {
56-
message = 'Disconnected. Click here to connect.';
56+
message = 'Connect';
5757
}
5858

5959
codeLens.command = {
@@ -65,7 +65,7 @@ export default class ActiveConnectionCodeLensProvider
6565
return [codeLens];
6666
}
6767

68-
deactivate() {
68+
deactivate(): void {
6969
this._connectionController.removeEventListener(
7070
DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED,
7171
this._activeConnectionChangedHandler

src/editors/editorsController.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type PlaygroundResultProvider from './playgroundResultProvider';
3232
import { PLAYGROUND_RESULT_SCHEME } from './playgroundResultProvider';
3333
import { StatusView } from '../views';
3434
import type TelemetryService from '../telemetry/telemetryService';
35+
import type { QueryWithCopilotCodeLensProvider } from './queryWithCopilotCodeLensProvider';
3536

3637
const log = createLogger('editors controller');
3738

@@ -102,6 +103,7 @@ export default class EditorsController {
102103
_exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider;
103104
_editDocumentCodeLensProvider: EditDocumentCodeLensProvider;
104105
_collectionDocumentsCodeLensProvider: CollectionDocumentsCodeLensProvider;
106+
_queryWithCopilotCodeLensProvider: QueryWithCopilotCodeLensProvider;
105107

106108
constructor({
107109
context,
@@ -115,6 +117,7 @@ export default class EditorsController {
115117
playgroundSelectionCodeActionProvider,
116118
playgroundDiagnosticsCodeActionProvider,
117119
editDocumentCodeLensProvider,
120+
queryWithCopilotCodeLensProvider,
118121
}: {
119122
context: vscode.ExtensionContext;
120123
connectionController: ConnectionController;
@@ -127,6 +130,7 @@ export default class EditorsController {
127130
playgroundSelectionCodeActionProvider: PlaygroundSelectionCodeActionProvider;
128131
playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider;
129132
editDocumentCodeLensProvider: EditDocumentCodeLensProvider;
133+
queryWithCopilotCodeLensProvider: QueryWithCopilotCodeLensProvider;
130134
}) {
131135
this._connectionController = connectionController;
132136
this._playgroundController = playgroundController;
@@ -160,6 +164,7 @@ export default class EditorsController {
160164
playgroundSelectionCodeActionProvider;
161165
this._playgroundDiagnosticsCodeActionProvider =
162166
playgroundDiagnosticsCodeActionProvider;
167+
this._queryWithCopilotCodeLensProvider = queryWithCopilotCodeLensProvider;
163168

164169
vscode.workspace.onDidCloseTextDocument((e) => {
165170
const uriParams = new URLSearchParams(e.uri.query);
@@ -410,6 +415,10 @@ export default class EditorsController {
410415
)
411416
);
412417
this._context.subscriptions.push(
418+
vscode.languages.registerCodeLensProvider(
419+
{ language: 'javascript' },
420+
this._queryWithCopilotCodeLensProvider
421+
),
413422
vscode.languages.registerCodeLensProvider(
414423
{ language: 'javascript' },
415424
this._activeConnectionCodeLensProvider
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as vscode from 'vscode';
2+
import EXTENSION_COMMANDS from '../commands';
3+
import type { SendMessageToParticipantFromInputOptions } from '../participant/participantTypes';
4+
import { isPlayground } from '../utils/playground';
5+
import { COPILOT_EXTENSION_ID } from '../participant/constants';
6+
import { DocumentSource } from '../documentSource';
7+
8+
export class QueryWithCopilotCodeLensProvider
9+
implements vscode.CodeLensProvider
10+
{
11+
constructor() {}
12+
13+
readonly onDidChangeCodeLenses: vscode.Event<void> =
14+
vscode.extensions.onDidChange;
15+
16+
provideCodeLenses(document: vscode.TextDocument): vscode.CodeLens[] {
17+
if (!isPlayground(document.uri)) {
18+
return [];
19+
}
20+
21+
// We can only detect whether a user has the Copilot extension active
22+
// but not whether it has an active subscription.
23+
const hasCopilotChatActive =
24+
vscode.extensions.getExtension(COPILOT_EXTENSION_ID)?.isActive === true;
25+
26+
if (!hasCopilotChatActive) {
27+
return [];
28+
}
29+
30+
const options: SendMessageToParticipantFromInputOptions = {
31+
prompt: 'Describe the query you would like to generate',
32+
placeHolder:
33+
'e.g. Find the document in sample_mflix.users with the name of Kayden Washington',
34+
messagePrefix: '/query',
35+
isNewChat: true,
36+
source: DocumentSource.DOCUMENT_SOURCE_QUERY_WITH_COPILOT_CODELENS,
37+
};
38+
39+
return [
40+
new vscode.CodeLens(new vscode.Range(0, 0, 0, 0), {
41+
title: '✨ Generate query with MongoDB Copilot',
42+
command: EXTENSION_COMMANDS.SEND_MESSAGE_TO_PARTICIPANT_FROM_INPUT,
43+
arguments: [options],
44+
}),
45+
];
46+
}
47+
}

src/mdbExtensionController.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ import type {
4646
} from './participant/participant';
4747
import ParticipantController from './participant/participant';
4848
import type { OpenSchemaCommandArgs } from './participant/prompts/schema';
49+
import { QueryWithCopilotCodeLensProvider } from './editors/queryWithCopilotCodeLensProvider';
50+
import type {
51+
SendMessageToParticipantOptions,
52+
SendMessageToParticipantFromInputOptions,
53+
} from './participant/participantTypes';
4954

5055
// This class is the top-level controller for our extension.
5156
// Commands which the extensions handles are defined in the function `activate`.
@@ -65,6 +70,7 @@ export default class MDBExtensionController implements vscode.Disposable {
6570
_telemetryService: TelemetryService;
6671
_languageServerController: LanguageServerController;
6772
_webviewController: WebviewController;
73+
_queryWithCopilotCodeLensProvider: QueryWithCopilotCodeLensProvider;
6874
_playgroundResultProvider: PlaygroundResultProvider;
6975
_activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider;
7076
_editDocumentCodeLensProvider: EditDocumentCodeLensProvider;
@@ -105,6 +111,8 @@ export default class MDBExtensionController implements vscode.Disposable {
105111
this._connectionController,
106112
this._editDocumentCodeLensProvider
107113
);
114+
this._queryWithCopilotCodeLensProvider =
115+
new QueryWithCopilotCodeLensProvider();
108116
this._activeConnectionCodeLensProvider =
109117
new ActiveConnectionCodeLensProvider(this._connectionController);
110118
this._exportToLanguageCodeLensProvider =
@@ -143,6 +151,7 @@ export default class MDBExtensionController implements vscode.Disposable {
143151
playgroundDiagnosticsCodeActionProvider:
144152
this._playgroundDiagnosticsCodeActionProvider,
145153
editDocumentCodeLensProvider: this._editDocumentCodeLensProvider,
154+
queryWithCopilotCodeLensProvider: this._queryWithCopilotCodeLensProvider,
146155
});
147156
this._webviewController = new WebviewController({
148157
connectionController: this._connectionController,
@@ -305,6 +314,22 @@ export default class MDBExtensionController implements vscode.Disposable {
305314
});
306315
}
307316
);
317+
this.registerParticipantCommand(
318+
EXTENSION_COMMANDS.SEND_MESSAGE_TO_PARTICIPANT,
319+
async (options: SendMessageToParticipantOptions) => {
320+
await this._participantController.sendMessageToParticipant(options);
321+
return true;
322+
}
323+
);
324+
this.registerParticipantCommand(
325+
EXTENSION_COMMANDS.SEND_MESSAGE_TO_PARTICIPANT_FROM_INPUT,
326+
async (options: SendMessageToParticipantFromInputOptions) => {
327+
await this._participantController.sendMessageToParticipantFromInput(
328+
options
329+
);
330+
return true;
331+
}
332+
);
308333
this.registerParticipantCommand(
309334
EXTENSION_COMMANDS.RUN_PARTICIPANT_CODE,
310335
({ runnableContent }: RunParticipantCodeCommandArgs) => {
@@ -949,12 +974,9 @@ export default class MDBExtensionController implements vscode.Disposable {
949974

950975
const copilot = vscode.extensions.getExtension('github.copilot-chat');
951976
if (result?.title === action) {
952-
await vscode.commands.executeCommand('workbench.action.chat.newChat');
953-
await vscode.commands.executeCommand(
954-
'workbench.action.chat.clearHistory'
955-
);
956-
await vscode.commands.executeCommand('workbench.action.chat.open', {
957-
query: '@MongoDB ',
977+
await this._participantController.sendMessageToParticipant({
978+
message: '',
979+
isNewChat: true,
958980
isPartialQuery: true,
959981
});
960982
this._telemetryService.trackCopilotIntroductionClicked({

src/participant/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ChatMetadataStore } from './chatMetadata';
33

44
export const CHAT_PARTICIPANT_ID = 'mongodb.participant';
55
export const CHAT_PARTICIPANT_MODEL = 'gpt-4o';
6+
export const COPILOT_EXTENSION_ID = 'GitHub.copilot';
67

78
export type ParticipantResponseType =
89
| 'query'

src/participant/participant.ts

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ import { ParticipantErrorTypes } from './participantErrorTypes';
4949
import type PlaygroundResultProvider from '../editors/playgroundResultProvider';
5050
import { isExportToLanguageResult } from '../types/playgroundType';
5151
import { PromptHistory } from './prompts/promptHistory';
52+
import type {
53+
SendMessageToParticipantOptions,
54+
SendMessageToParticipantFromInputOptions,
55+
} from './participantTypes';
5256
import { DEFAULT_EXPORT_TO_LANGUAGE_DRIVER_SYNTAX } from '../editors/exportToLanguageCodeLensProvider';
5357
import { EXPORT_TO_LANGUAGE_ALIASES } from '../editors/playgroundSelectionCodeActionProvider';
5458

@@ -130,9 +134,51 @@ export default class ParticipantController {
130134
* in the chat. To work around this, we can write a message as the user, which will
131135
* trigger the chat handler and give us access to the model.
132136
*/
133-
writeChatMessageAsUser(message: string): Thenable<unknown> {
137+
async sendMessageToParticipant(
138+
options: SendMessageToParticipantOptions
139+
): Promise<unknown> {
140+
const { message, isNewChat = false, isPartialQuery = false } = options;
141+
142+
if (isNewChat) {
143+
await vscode.commands.executeCommand('workbench.action.chat.newChat');
144+
await vscode.commands.executeCommand(
145+
'workbench.action.chat.clearHistory'
146+
);
147+
}
148+
134149
return vscode.commands.executeCommand('workbench.action.chat.open', {
135150
query: `@MongoDB ${message}`,
151+
isPartialQuery,
152+
});
153+
}
154+
155+
async sendMessageToParticipantFromInput(
156+
options: SendMessageToParticipantFromInputOptions
157+
): Promise<unknown> {
158+
const {
159+
messagePrefix = '',
160+
isNewChat = false,
161+
isPartialQuery = false,
162+
source,
163+
...inputBoxOptions
164+
} = options;
165+
166+
this._telemetryService.trackCopilotParticipantSubmittedFromInputBox({
167+
source,
168+
});
169+
170+
const message = await vscode.window.showInputBox({
171+
...inputBoxOptions,
172+
});
173+
174+
if (message === undefined || message.trim() === '') {
175+
return Promise.resolve();
176+
}
177+
178+
return this.sendMessageToParticipant({
179+
message: `${messagePrefix ? `${messagePrefix} ` : ''}${message}`,
180+
isNewChat,
181+
isPartialQuery,
136182
});
137183
}
138184

@@ -460,9 +506,9 @@ export default class ParticipantController {
460506

461507
const connectionName = this._connectionController.getActiveConnectionName();
462508

463-
return this.writeChatMessageAsUser(
464-
`${command ? `${command} ` : ''}${connectionName}`
465-
) as Promise<boolean>;
509+
return this.sendMessageToParticipant({
510+
message: `${command ? `${command} ` : ''}${connectionName}`,
511+
}) as Promise<boolean>;
466512
}
467513

468514
getConnectionsTree(command: ParticipantCommand): vscode.MarkdownString[] {
@@ -501,7 +547,7 @@ export default class ParticipantController {
501547
const dataService = this._connectionController.getActiveDataService();
502548
if (!dataService) {
503549
// Run a blank command to get the user to connect first.
504-
void this.writeChatMessageAsUser(command);
550+
void this.sendMessageToParticipant({ message: command });
505551
return [];
506552
}
507553

@@ -549,9 +595,9 @@ export default class ParticipantController {
549595
databaseName: databaseName,
550596
});
551597

552-
return this.writeChatMessageAsUser(
553-
`${command} ${databaseName}`
554-
) as Promise<boolean>;
598+
return this.sendMessageToParticipant({
599+
message: `${command} ${databaseName}`,
600+
}) as Promise<boolean>;
555601
}
556602

557603
async getCollectionQuickPicks({
@@ -564,7 +610,7 @@ export default class ParticipantController {
564610
const dataService = this._connectionController.getActiveDataService();
565611
if (!dataService) {
566612
// Run a blank command to get the user to connect first.
567-
void this.writeChatMessageAsUser(command);
613+
void this.sendMessageToParticipant({ message: command });
568614
return [];
569615
}
570616

@@ -625,9 +671,9 @@ export default class ParticipantController {
625671
databaseName: databaseName,
626672
collectionName: collectionName,
627673
});
628-
return this.writeChatMessageAsUser(
629-
`${command} ${collectionName}`
630-
) as Promise<boolean>;
674+
return this.sendMessageToParticipant({
675+
message: `${command} ${collectionName}`,
676+
}) as Promise<boolean>;
631677
}
632678

633679
renderDatabasesTree({
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type * as vscode from 'vscode';
2+
import type { DocumentSource } from '../documentSource';
3+
4+
export type SendMessageToParticipantOptions = {
5+
message: string;
6+
isNewChat?: boolean;
7+
isPartialQuery?: boolean;
8+
};
9+
10+
export type SendMessageToParticipantFromInputOptions = {
11+
messagePrefix?: string;
12+
source?: DocumentSource;
13+
} & Omit<SendMessageToParticipantOptions, 'message'> &
14+
vscode.InputBoxOptions;

0 commit comments

Comments
 (0)