Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(participant): export to a playground VSCODE-574 #832

Merged
merged 16 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@
"dark": "images/dark/play.svg"
}
},
{
"command": "mdb.exportCodeToPlayground",
"title": "Export Code to Playground"
},
{
"command": "mdb.exportToPython",
"title": "MongoDB: Export To Python 3"
Expand Down Expand Up @@ -747,6 +751,17 @@
"when": "mdb.isPlayground == true"
}
],
"mdb.copilot": [
{
"command": "mdb.exportCodeToPlayground"
}
],
"editor/context": [
{
"submenu": "mdb.copilot",
"group": "1_main@2"
}
],
"commandPalette": [
{
"command": "mdb.selectDatabaseWithParticipant",
Expand Down Expand Up @@ -948,6 +963,10 @@
"command": "mdb.runPlayground",
"when": "false"
},
{
"command": "mdb.exportCodeToPlayground",
"when": "false"
},
{
"command": "mdb.createIndexFromTreeView",
"when": "false"
Expand Down Expand Up @@ -994,6 +1013,12 @@
}
]
},
"submenus": [
{
"id": "mdb.copilot",
"label": "MongoDB Copilot"
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
}
],
"keybindings": [
{
"command": "mdb.runSelectedPlaygroundBlocks",
Expand Down
1 change: 1 addition & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum EXTENSION_COMMANDS {
MDB_RUN_SELECTED_PLAYGROUND_BLOCKS = 'mdb.runSelectedPlaygroundBlocks',
MDB_RUN_ALL_PLAYGROUND_BLOCKS = 'mdb.runAllPlaygroundBlocks',
MDB_RUN_ALL_OR_SELECTED_PLAYGROUND_BLOCKS = 'mdb.runPlayground',
MDB_EXPORT_CODE_TO_PLAYGROUND = 'mdb.exportCodeToPlayground',

MDB_FIX_THIS_INVALID_INTERACTIVE_SYNTAX = 'mdb.fixThisInvalidInteractiveSyntax',
MDB_FIX_ALL_INVALID_INTERACTIVE_SYNTAX = 'mdb.fixAllInvalidInteractiveSyntax',
Expand Down
3 changes: 3 additions & 0 deletions src/mdbExtensionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ export default class MDBExtensionController implements vscode.Disposable {
EXTENSION_COMMANDS.MDB_RUN_ALL_OR_SELECTED_PLAYGROUND_BLOCKS,
() => this._playgroundController.runAllOrSelectedPlaygroundBlocks()
);
this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_CODE_TO_PLAYGROUND, () =>
this._participantController.exportCodeToPlayground()
);

this.registerCommand(
EXTENSION_COMMANDS.MDB_FIX_THIS_INVALID_INTERACTIVE_SYNTAX,
Expand Down
61 changes: 61 additions & 0 deletions src/participant/participant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import EXTENSION_COMMANDS from '../commands';
import type { StorageController } from '../storage';
import { StorageVariables } from '../storage';
import { GenericPrompt, isPromptEmpty } from './prompts/generic';
import { EportToPlaygroundPrompt } from './prompts/exportToPlayground';
import type { ChatResult } from './constants';
import {
askToConnectChatResult,
Expand Down Expand Up @@ -1201,6 +1202,66 @@ export default class ParticipantController {
});
}

async exportCodeToPlayground(): Promise<boolean> {
const activeTextEditor = vscode.window.activeTextEditor;
if (!activeTextEditor) {
void vscode.window.showErrorMessage('Active editor not found.');
return Promise.resolve(false);
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
}

const sortedSelections = Array.from(activeTextEditor.selections).sort(
(a, b) => a.start.compareTo(b.start)
);
const selectedText = sortedSelections
.map((selection) => activeTextEditor.document.getText(selection))
.join('\n');

const code =
selectedText || activeTextEditor.document.getText().trim() || '';

try {
const progressResult = await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Exporting code to a playground...',
cancellable: true,
},
async (progress, token) => {
const messages = EportToPlaygroundPrompt.buildMessages(code);
return await this.getChatResponseContent({
messages,
token,
});
}
);

if (progressResult?.includes("Sorry, I can't assist with that.")) {
void vscode.window.showErrorMessage("Sorry, I can't assist with that.");
return Promise.resolve(false);
Anemy marked this conversation as resolved.
Show resolved Hide resolved
}

if (progressResult) {
const runnableContent = getRunnableContentFromString(progressResult);
if (progressResult) {
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
await vscode.commands.executeCommand(
EXTENSION_COMMANDS.OPEN_PARTICIPANT_QUERY_IN_PLAYGROUND,
{
runnableContent,
}
);
}
}

return Promise.resolve(true);
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
} catch (error) {
log.error(
'Exporting code to a playground with cancel modal failed',
error
);
return Promise.resolve(false);
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
}
}

async chatHandler(
...args: [
vscode.ChatRequest,
Expand Down
32 changes: 32 additions & 0 deletions src/participant/prompts/exportToPlayground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as vscode from 'vscode';

export class EportToPlaygroundPrompt {
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
static getAssistantPrompt(): vscode.LanguageModelChatMessage {
const prompt = `You are a MongoDB expert.
Your task is to help the user build MongoDB queries and aggregation pipelines that perform their task.
You achieve this by converting user's code written in any proggramming language to the MongoDB Shell syntax.
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
Take a user promt as an input string and translate it to the MongoDB Shell language.
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
Keep your response concise.
You should suggest queries that are performant and correct.
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
Respond with markdown, suggest code in a Markdown code block that begins with \`\`\`javascript and ends with \`\`\`.
Anemy marked this conversation as resolved.
Show resolved Hide resolved
You can imagine the schema, collection, and database name.
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
Respond in MongoDB shell syntax using the \`\`\`javascript code block syntax.`;

// eslint-disable-next-line new-cap
return vscode.LanguageModelChatMessage.Assistant(prompt);
}

static getUserPrompt(prompt: string): vscode.LanguageModelChatMessage {
// eslint-disable-next-line new-cap
return vscode.LanguageModelChatMessage.User(prompt);
Anemy marked this conversation as resolved.
Show resolved Hide resolved
}

static buildMessages(prompt: string): vscode.LanguageModelChatMessage[] {
const messages = [
EportToPlaygroundPrompt.getAssistantPrompt(),
EportToPlaygroundPrompt.getUserPrompt(prompt),
];

return messages;
}
}
101 changes: 101 additions & 0 deletions src/test/suite/participant/participant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
} from '../../../storage/storageController';
import type { LoadedConnection } from '../../../storage/connectionStorage';
import { ChatMetadataStore } from '../../../participant/chatMetadata';
import { getFullRange } from '../suggestTestHelpers';
import { isPlayground } from '../../../utils/playground';

// The Copilot's model in not available in tests,
// therefore we need to mock its methods and returning values.
Expand Down Expand Up @@ -1279,6 +1281,105 @@ Schema:
expect(properties.error_name).to.equal('Docs Chatbot API Issue');
});
});

suite('export to playground', function () {
beforeEach(async function () {
await vscode.commands.executeCommand(
'workbench.action.files.newUntitledFile'
);
});

afterEach(async function () {
await vscode.commands.executeCommand(
'workbench.action.closeActiveEditor'
);
});

test('exports all code to a playground', async function () {
this.timeout(20000);
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
const editor = vscode.window.activeTextEditor;
if (!editor) {
throw new Error('Window active text editor is undefined');
}

const testDocumentUri = editor.document.uri;
const edit = new vscode.WorkspaceEdit();
const code = `
InsertOneResult result = collection.insertOne(new Document()
.append("_id", new ObjectId())
.append("title", "Ski Bloopers")
.append("genres", Arrays.asList("Documentary", "Comedy")));
System.out.println("Success! Inserted document id: " + result.getInsertedId());
`;
edit.replace(testDocumentUri, getFullRange(editor.document), code);
await vscode.workspace.applyEdit(edit);
sendRequestStub.onCall(0).resolves({
text: [
'```javascript\n' +
'db.collection.insertOne({\n' +
'_id: new ObjectId(),\n' +
'title: "Ski Bloopers",' +
'genres: ["Documentary", "Comedy"]' +
'});\n' +
'print("Success! Inserted document id: " + result.insertedId);' +
'```',
],
});
await testParticipantController.exportCodeToPlayground();
const messages = sendRequestStub.firstCall.args[0];
expect(messages[1].content).to.include('System.out.println');
expect(
isPlayground(vscode.window.activeTextEditor?.document.uri)
).to.be.eql(true);
expect(vscode.window.activeTextEditor?.document.getText()).to.include(
'Inserted document id'
);
});

test('exports selected lines of code to a playground', async function () {
const editor = vscode.window.activeTextEditor;
if (!editor) {
throw new Error('Window active text editor is undefined');
}

const testDocumentUri = editor.document.uri;
const edit = new vscode.WorkspaceEdit();
const code = `
InsertOneResult result = collection.insertOne(new Document()
.append("_id", new ObjectId())
.append("title", "Ski Bloopers")
.append("genres", Arrays.asList("Documentary", "Comedy")));
System.out.println("Success! Inserted document id: " + result.getInsertedId());
`;
edit.replace(testDocumentUri, getFullRange(editor.document), code);
await vscode.workspace.applyEdit(edit);
const position = editor.selection.active;
const startPosition = position.with(0, 0);
const endPosition = position.with(3, 63);
const newSelection = new vscode.Selection(startPosition, endPosition);
editor.selection = newSelection;
sendRequestStub.onCall(0).resolves({
text: [
'```javascript\n' +
'db.collection.insertOne({\n' +
'_id: new ObjectId(),\n' +
'title: "Ski Bloopers",' +
'genres: ["Documentary", "Comedy"]' +
'});\n' +
'```',
],
});
await testParticipantController.exportCodeToPlayground();
const messages = sendRequestStub.firstCall.args[0];
expect(messages[1].content).to.not.include('System.out.println');
expect(
isPlayground(vscode.window.activeTextEditor?.document.uri)
).to.be.eql(true);
expect(
vscode.window.activeTextEditor?.document.getText()
).to.not.include('Inserted document id');
});
});
});
});

Expand Down
Loading