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,
+};