From da3f5a43934f956f50bc93041b5130f0c4996876 Mon Sep 17 00:00:00 2001 From: Bobby Iliev Date: Fri, 27 Oct 2023 15:28:35 +0300 Subject: [PATCH] Add SQL activity log (#117) * Add query activity log * Revert the log to show newest first * Remove copy notifications and add data storage * Change emojies to svg icons * Remove the latency emoji --- package.json | 15 ++++++++ resources/error_icon.svg | 3 ++ resources/success_icon.svg | 4 +++ src/extension.ts | 26 ++++++++++++-- src/providers/activity.ts | 72 ++++++++++++++++++++++++++++++++++++++ src/providers/index.ts | 4 ++- 6 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 resources/error_icon.svg create mode 100644 resources/success_icon.svg create mode 100644 src/providers/activity.ts diff --git a/package.json b/package.json index 1e01bda..293c9e6 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,12 @@ "name": "Explorer", "icon": "", "contextualTitle": "Explorer" + }, + { + "id": "activityLog", + "name": "Activity Log", + "icon": "", + "contextualTitle": "Activity Log" } ], "materializePanelContainer": [ @@ -154,6 +160,15 @@ "light": "resources/clip_light.svg", "dark": "resources/clip_dark.svg" } + }, + { + "command": "extension.copySQL", + "title": "Copy SQL", + "category": "materialize", + "icon": { + "light": "resources/clip_light.svg", + "dark": "resources/clip_dark.svg" + } } ], "keybindings": [ diff --git a/resources/error_icon.svg b/resources/error_icon.svg new file mode 100644 index 0000000..1a4de5e --- /dev/null +++ b/resources/error_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/resources/success_icon.svg b/resources/success_icon.svg new file mode 100644 index 0000000..2984ae3 --- /dev/null +++ b/resources/success_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/extension.ts b/src/extension.ts index 7f9f129..e4324cf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { AuthProvider, ResultsProvider, DatabaseTreeProvider } from './providers'; +import { AuthProvider, ResultsProvider, DatabaseTreeProvider, ActivityLogTreeProvider } from './providers'; import { Context, EventType } from './context'; import { randomUUID } from 'crypto'; @@ -10,6 +10,10 @@ export function activate(vsContext: vscode.ExtensionContext) { console.log("[Extension]", "Activating Materialize extension."); context = new Context(); + // Register the activity log + const activityLogProvider = new ActivityLogTreeProvider(vsContext); + vscode.window.registerTreeDataProvider('activityLog', activityLogProvider); + // Register the database explorer const databaseTreeProvider = new DatabaseTreeProvider(context); vscode.window.createTreeView('explorer', { treeDataProvider: databaseTreeProvider }); @@ -76,12 +80,23 @@ export function activate(vsContext: vscode.ExtensionContext) { } else { context.emit("event", { type: EventType.queryResults, data: { ...results, elapsedTime, id } }); } + activityLogProvider.addLog({ + status: "success", + latency: elapsedTime, // assuming elapsedTime holds the time taken for the query to execute + sql: query + }); } catch (error: any) { console.log("[RunSQLCommand]", error.toString()); console.log("[RunSQLCommand]", JSON.stringify(error)); const endTime = Date.now(); const elapsedTime = endTime - startTime; + activityLogProvider.addLog({ + status: "failure", + latency: elapsedTime, // assuming elapsedTime holds the time taken before the error was caught + sql: query + }); + context.emit("event", { type: EventType.queryResults, data: { id, rows: [], fields: [], error: { message: error.toString(), position: error.position, @@ -106,10 +121,17 @@ export function activate(vsContext: vscode.ExtensionContext) { vsContext.subscriptions.push(runDisposable); vsContext.subscriptions.push(copyDisposable); + + let copySQLDisposable = vscode.commands.registerCommand('extension.copySQL', (sql: string) => { + vscode.env.clipboard.writeText(sql); + }); + + vsContext.subscriptions.push(copySQLDisposable); + return context; } export function deactivate() { console.log("[Extension]", "Deactivating Materialize extension."); context.stop(); -} \ No newline at end of file +} diff --git a/src/providers/activity.ts b/src/providers/activity.ts new file mode 100644 index 0000000..e95d857 --- /dev/null +++ b/src/providers/activity.ts @@ -0,0 +1,72 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; + +export default class ActivityLogTreeProvider implements vscode.TreeDataProvider { + + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + private logs: ActivityLog[] = []; + + constructor(private context: vscode.ExtensionContext) { + this.logs = this.context.globalState.get('activityLogs') || []; + } + + addLog(log: ActivityLog) { + this.logs.push(log); + + // Remove the oldest log if we have more than 100 + if (this.logs.length > 100) { + this.logs.shift(); + } + + this.context.globalState.update('activityLogs', this.logs); + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: ActivityLogNode): vscode.TreeItem { + return element; + } + + getChildren(element?: ActivityLogNode): Thenable { + if (!element) { + // Revert the logs to show the newest ones at the top + return Promise.resolve(this.logs.slice().reverse().map(log => new ActivityLogNode(log))); + } + return Promise.resolve([]); + } +} + +interface ActivityLog { + status: "success" | "failure"; + latency: number; + sql: string; +} + +class ActivityLogItem extends vscode.TreeItem { + constructor(public readonly log: ActivityLog) { + // Shorten the displayed query if it's too long + const shortSQL = log.sql.length > 50 ? log.sql.substring(0, 50) + "..." : log.sql; + + super(shortSQL, vscode.TreeItemCollapsibleState.None); + + // Set iconPath based on the status + const iconName = log.status === "success" ? "success_icon.svg" : "error_icon.svg"; + this.iconPath = vscode.Uri.file(path.join(__dirname, '..', 'resources', iconName)); + + // Set the description to the query latency + this.description = `${log.latency}ms`; + + this.tooltip = `${log.sql} | Latency: ${log.latency}ms`; + this.contextValue = 'activityLogItem'; + + // Copy SQL command to clipboard + this.command = { + command: 'extension.copySQL', + title: 'Copy SQL', + arguments: [log.sql] + }; + } +} + +class ActivityLogNode extends ActivityLogItem {} diff --git a/src/providers/index.ts b/src/providers/index.ts index 8906064..485008e 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -1,9 +1,11 @@ import AuthProvider from "./auth"; import ResultsProvider from "./results"; import DatabaseTreeProvider from "./schema"; +import ActivityLogTreeProvider from "./activity"; export { AuthProvider, ResultsProvider, DatabaseTreeProvider, -}; \ No newline at end of file + ActivityLogTreeProvider, +};