Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
22 changes: 21 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@
"command": "mysql.selectTop1000",
"title": "Select Top 1000",
"category": "MySQL"
},
{
"command": "mysql.editConnection",
"title": "Edit Connection",
"category": "MySQL"
},
{
"command": "mysql.dropTable",
"title": "Drop table",
"category": "MySQL"
}
],
"menus": {
Expand All @@ -100,7 +110,7 @@
{
"command": "mysql.deleteConnection",
"when": "view == mysql && viewItem == connection",
"group": "mysql@2"
"group": "mysql@3"
},
{
"command": "mysql.newQuery",
Expand All @@ -121,6 +131,16 @@
"command": "mysql.refresh",
"when": "view == mysql && viewItem == table",
"group": "mysql@1"
},
{
"command": "mysql.editConnection",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this before mysql.deleteConnection

"when": "view == mysql && viewItem == connection",
"group": "mysql@2"
},
{
"command": "mysql.dropTable",
"when": "view == mysql && viewItem == table",
"group": "mysql@2"
}
]
},
Expand Down
108 changes: 106 additions & 2 deletions src/common/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
import * as asciitable from "asciitable";
import * as fs from "fs";
import * as mysql from "mysql";
import * as uuidv1 from "uuid/v1";
import * as vscode from "vscode";
import { IConnection } from "../model/connection";
import { ConnectionNode } from "../model/connectionNode";
import { MySQLTreeDataProvider } from "../mysqlTreeDataProvider";
import { AppInsightsClient } from "./appInsightsClient";
import { Constants } from "./constants";
import { Global } from "./global";
import { OutputChannel } from "./outputChannel";

Expand Down Expand Up @@ -47,7 +51,7 @@ export class Utility {
sql = sql ? sql : vscode.window.activeTextEditor.document.getText();
connectionOptions = connectionOptions ? connectionOptions : Global.activeConnection;
connectionOptions.multipleStatements = true;
const connection = Utility.createConnection(connectionOptions);
const connection = Utility.createMySQLConnection(connectionOptions);

OutputChannel.appendLine("[Start] Executing MySQL query...");
connection.query(sql, (err, rows) => {
Expand Down Expand Up @@ -82,7 +86,7 @@ export class Utility {
return vscode.window.showTextDocument(textDocument);
}

public static createConnection(connectionOptions: IConnection): any {
public static createMySQLConnection(connectionOptions: IConnection): any {
const newConnectionOptions: any = Object.assign({}, connectionOptions);
if (connectionOptions.certPath && fs.existsSync(connectionOptions.certPath)) {
newConnectionOptions.ssl = {
Expand All @@ -92,6 +96,78 @@ export class Utility {
return mysql.createConnection(newConnectionOptions);
}

public static async editConnection(connectionNode: ConnectionNode, context: vscode.ExtensionContext, mysqlTreeDataProvider: MySQLTreeDataProvider) {
if (connectionNode) {
connectionNode.editConnection(context, mysqlTreeDataProvider);
} else {
const selectedConnection = await Utility.pickConnection(context);
if (selectedConnection !== undefined) {
const dummyNode = new ConnectionNode(selectedConnection.id, selectedConnection.host, selectedConnection.user, selectedConnection.password,
selectedConnection.port, selectedConnection.certPath);

dummyNode.editConnection(context, mysqlTreeDataProvider);
}
}
}

public static async deleteConnection(connectionNode: ConnectionNode, context: vscode.ExtensionContext, mysqlTreeDataProvider: MySQLTreeDataProvider) {
if (connectionNode) {
connectionNode.deleteConnection(context, mysqlTreeDataProvider);
} else {
const selectedConnection = await Utility.pickConnection(context);
if (selectedConnection !== undefined) {
const dummyNode = new ConnectionNode(selectedConnection.id, selectedConnection.host, selectedConnection.user, selectedConnection.password,
selectedConnection.port, selectedConnection.certPath);

dummyNode.deleteConnection(context, mysqlTreeDataProvider);
}
}
}

public static async createConnectionFromInput(context: vscode.ExtensionContext, copyFrom?: IConnection): Promise<IConnection> {
const host = await vscode.window.showInputBox({ prompt: "The hostname of the database", placeHolder: "host", ignoreFocusOut: true, value: copyFrom !== undefined ? copyFrom.host : ""});
if (!host) {
return;
}

const user = await vscode.window.showInputBox({ prompt: "The MySQL user to authenticate as", placeHolder: "user", ignoreFocusOut: true, value: copyFrom !== undefined ? copyFrom.user : "" });
if (!user) {
return;
}

const pw = copyFrom !== undefined ? await Global.keytar.getPassword(Constants.ExtensionId, copyFrom.id) : "";
const password = await vscode.window.showInputBox({ prompt: "The password of the MySQL user", placeHolder: "password",
ignoreFocusOut: true, password: true,
value: pw });
if (password === undefined) {
return;
}

const port = await vscode.window.showInputBox({ prompt: "The port number to connect to", placeHolder: "port", ignoreFocusOut: true, value: copyFrom !== undefined ? copyFrom.port : "" });
if (!port) {
return;
}

const certPath = await vscode.window.showInputBox({ prompt: "[Optional] SSL certificate path. Leave empty to ignore", placeHolder: "certificate file path", ignoreFocusOut: true,
value: copyFrom !== undefined ? copyFrom.certPath : "" });
if (certPath === undefined) {
return;
}

const connection: IConnection = {
host,
user,
password,
port,
certPath,
};

const id = copyFrom ? copyFrom.id : uuidv1();
connection.id = id;

return connection;
}

private static async hasActiveConnection(): Promise<boolean> {
let count = 5;
while (!Global.activeConnection && count > 0) {
Expand All @@ -106,4 +182,32 @@ export class Utility {
setTimeout(resolve, ms);
});
}

private static async pickConnection(context: vscode.ExtensionContext): Promise<IConnection> {
let selectedConnection;

const connections = context.globalState.get<{ [key: string]: IConnection }>(Constants.GlobalStateMySQLConectionsKey);
const items: vscode.QuickPickItem[] = [];

if (connections) {
for (const id of Object.keys(connections)) {
const item = connections[id];
items.push({
label: item.host,
description: item.user});
}
}

await vscode.window.showQuickPick(items).then(async (selection) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use: const selection = await vscode.window.showQuickPick(items)

// the user canceled the selection
if (!selection) {
return selectedConnection;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be just return

}

const selectedConnectionId = Object.keys(connections)[items.indexOf(selection)];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there are two connections have the same host and user?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would still get the correct selection from the pick menu.. but you wont be able to differ from the two when the menu is shown.. how would you enable anyone to see the difference between two connections with the same host and user? showing the internal id of the connection wont be to much use for the user.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we could still get the correct selection from the pick menu, then it will be OK. No extra change needed.

selectedConnection = connections[selectedConnectionId];
selectedConnection.id = selectedConnectionId;
});
return selectedConnection;
}
}
10 changes: 9 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function activate(context: vscode.ExtensionContext) {
}));

context.subscriptions.push(vscode.commands.registerCommand("mysql.deleteConnection", (connectionNode: ConnectionNode) => {
connectionNode.deleteConnection(context, mysqlTreeDataProvider);
Utility.deleteConnection(connectionNode, context, mysqlTreeDataProvider);
}));

context.subscriptions.push(vscode.commands.registerCommand("mysql.runQuery", () => {
Expand All @@ -38,6 +38,14 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.commands.registerCommand("mysql.selectTop1000", (tableNode: TableNode) => {
tableNode.selectTop1000();
}));

context.subscriptions.push(vscode.commands.registerCommand("mysql.editConnection", (connectionNode: ConnectionNode) => {
Utility.editConnection(connectionNode, context, mysqlTreeDataProvider);
}));

context.subscriptions.push(vscode.commands.registerCommand("mysql.dropTable", (tableNode: TableNode) => {
tableNode.dropTable();
}));
}

export function deactivate() {
Expand Down
1 change: 1 addition & 0 deletions src/model/connection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface IConnection {
id?: string;
readonly host: string;
readonly user: string;
readonly password?: string;
Expand Down
44 changes: 43 additions & 1 deletion src/model/connectionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export class ConnectionNode implements INode {
}

public async getChildren(): Promise<INode[]> {
const connection = Utility.createConnection({
const connection = Utility.createMySQLConnection({
host: this.host,
user: this.user,
password: this.password,
Expand Down Expand Up @@ -61,6 +61,15 @@ export class ConnectionNode implements INode {
}

public async deleteConnection(context: vscode.ExtensionContext, mysqlTreeDataProvider: MySQLTreeDataProvider) {
const options: vscode.MessageOptions = {
modal: true,
};
const answer = await vscode.window.showWarningMessage(`Are you sure you want to delete ${this.host}?`, options, "Delete connection");

if (answer === undefined) {
return;
}

AppInsightsClient.sendEvent("deleteConnection");
const connections = context.globalState.get<{ [key: string]: IConnection }>(Constants.GlobalStateMySQLConectionsKey);
delete connections[this.id];
Expand All @@ -70,4 +79,37 @@ export class ConnectionNode implements INode {

mysqlTreeDataProvider.refresh();
}

public async editConnection(context: vscode.ExtensionContext, mysqlTreeDataProvider: MySQLTreeDataProvider) {
AppInsightsClient.sendEvent("editConncetion.start");

const copyFrom: IConnection = {
id: this.id,
host: this.host,
user: this.user,
password: this.password,
port: this.port,
certPath: this.certPath,
};
const editConnection = await Utility.createConnectionFromInput(context, copyFrom);

if (editConnection !== undefined) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic is similar to addConnection. Please help merge them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge them into a addOrEditConnection method?
Should the metod(s) be placed on the connectionNode or the mysqlTreeDataProvider class?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could wrap these similar code snippet into a method into https://github.com/formulahendry/vscode-mysql/blob/master/src/common/utility.ts

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(We still keep editConnection in connectionNode and addConnection in mysqlTreeDataProvider, and call shared method in utility)

let connections = await context.globalState.get<{ [key: string]: IConnection }>(Constants.GlobalStateMySQLConectionsKey);

if (!connections) {
connections = {};
}

connections[editConnection.id] = editConnection;

if (editConnection.password) {
await Global.keytar.setPassword(Constants.ExtensionId, editConnection.id, editConnection.password);
}

await context.globalState.update(Constants.GlobalStateMySQLConectionsKey, connections);
mysqlTreeDataProvider.refresh();
}

AppInsightsClient.sendEvent("editConncetion.end");
}
}
2 changes: 1 addition & 1 deletion src/model/databaseNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class DatabaseNode implements INode {
}

public async getChildren(): Promise<INode[]> {
const connection = Utility.createConnection({
const connection = Utility.createMySQLConnection({
host: this.host,
user: this.user,
password: this.password,
Expand Down
29 changes: 28 additions & 1 deletion src/model/tableNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class TableNode implements INode {
}

public async getChildren(): Promise<INode[]> {
const connection = Utility.createConnection({
const connection = Utility.createMySQLConnection({
host: this.host,
user: this.user,
password: this.password,
Expand Down Expand Up @@ -62,4 +62,31 @@ export class TableNode implements INode {

Utility.runQuery(sql, connection);
}

public async dropTable() {
const options: vscode.MessageOptions = {
modal: true,
};
const answer = await vscode.window.showWarningMessage(`Are you sure you want to drop table ${this.table}?`, options, "Drop table");

if (answer === undefined) {
return;
}

AppInsightsClient.sendEvent("drop table");
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropTable

const sql = `DROP TABLE ${this.database}.${this.table}`;
Utility.createSQLTextDocument(sql);

const connection = {
host: this.host,
user: this.user,
password: this.password,
port: this.port,
database: this.database,
certPath: this.certPath,
};
Global.activeConnection = connection;

Utility.runQuery(sql, connection);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about refreshing the table list after deletion is successful?

Copy link
Contributor Author

@jbpoulsen jbpoulsen Dec 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do i refresh the databaseNode from inside the tableNode class? I want to call:

mysqlTreeDataProvider.refresh(databaseNode);

How can i get the "parent" databaseNode for a tableNode?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could pass databaseNode to constructor of tableNode

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright ill look into that

}
}
Loading