feat(sqlserver): add activity trace
This commit is contained in:
24 files changed
+2108
-10
No files matched your search
@@ -0,0 +1,43 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
disposeSqlServerActivityTrace: vi.fn(() => Promise.resolve()),
|
||||
disposeAllSqlServerActivityTraces: vi.fn(() => Promise.resolve()),
|
||||
disposeSqlServerActivityTracesForConnection: vi.fn(() => Promise.resolve()),
|
||||
cleanupStaleSqlServerTraceSessions: vi.fn(() => Promise.resolve(0)),
|
||||
hasSqlServerActivityTraceForConnection: vi.fn(() => false),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/sqlserver/sqlServerActivityTraceRuntime", () => mocks);
|
||||
|
||||
const connectionStoreSource = readFileSync(new URL("../connectionStore.ts", import.meta.url), "utf8");
|
||||
|
||||
describe("queryStore SQL Server trace lifecycle", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.stubGlobal("localStorage", {
|
||||
getItem: vi.fn(() => null),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
});
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("disposes the trace runtime only when its tab is explicitly closed", async () => {
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const store = useQueryStore();
|
||||
const tabId = store.openSqlServerActivityTrace("sqlserver-1");
|
||||
|
||||
store.switchTab(store.createTab("sqlserver-1", "app", "query_1"));
|
||||
expect(mocks.disposeSqlServerActivityTrace).not.toHaveBeenCalled();
|
||||
|
||||
store.closeTab(tabId);
|
||||
expect(mocks.disposeSqlServerActivityTrace).toHaveBeenCalledWith(tabId);
|
||||
});
|
||||
|
||||
it("does not scan trace sessions on ordinary connection success", () => {
|
||||
expect(connectionStoreSource).not.toContain("cleanupStaleSqlServerTraceSessions");
|
||||
});
|
||||
});
|
||||
@@ -75,6 +75,7 @@ import { loadTimeoutInheritanceBackup, saveTimeoutInheritanceBackup } from "@/li
|
||||
import { migrateSqlServerLegacyCompatibilityConfig, requiresSqlServerLegacyCompatibilityComponent, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility";
|
||||
import { gaussdbMTypeDisplayName } from "@/lib/table/postgresDataTypeHelp";
|
||||
import { deleteTabResultSnapshotsForOwner } from "@/lib/tabs/tabResultCache";
|
||||
import { disposeSqlServerActivityTracesForConnection, hasSqlServerActivityTraceForConnection } from "@/lib/sqlserver/sqlServerActivityTraceRuntime";
|
||||
import { connectionUsesVisibleSchemaFilter, filterDatabaseNamesForConnection, filterSchemaNamesForConnection, filterVisibleDatabaseNames, normalizeVisibleDatabaseSelection } from "@/lib/database/visibleDatabases";
|
||||
import {
|
||||
buildObjectGroupPlaceholderNodes,
|
||||
@@ -2931,9 +2932,10 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
async function disconnect(connectionId: string) {
|
||||
const stateRevision = bumpConnectionStateRevision(connectionId);
|
||||
const shouldRemoveOneTimeConnection = getConfig(connectionId)?.one_time === true;
|
||||
if (hasSqlServerActivityTraceForConnection(connectionId)) await disposeSqlServerActivityTracesForConnection(connectionId);
|
||||
const disconnectRequest = startDisconnectRequest(connectionId);
|
||||
cancelLocalConnectionAttempt(connectionId);
|
||||
const shouldRemoveOneTimeConnection = getConfig(connectionId)?.one_time === true;
|
||||
|
||||
connectedIds.value.delete(connectionId);
|
||||
clearPrimaryVisibleObjectNames(connectionId);
|
||||
@@ -2978,6 +2980,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
async function closeDatabaseConnection(connectionId: string, database: string) {
|
||||
if (hasSqlServerActivityTraceForConnection(connectionId, database)) await disposeSqlServerActivityTracesForConnection(connectionId, database);
|
||||
await api.closeDatabaseConnection(connectionId, database);
|
||||
const { useQueryStore } = await import("@/stores/queryStore");
|
||||
const queryStore = useQueryStore();
|
||||
|
||||
@@ -71,6 +71,7 @@ import { ensureSqlExtension } from "@/lib/savedSql/savedSqlFileName";
|
||||
import { resolveSavedSqlExecutionTarget, savedSqlExecutionTargetFromTab, type SavedSqlExecutionTarget, type SavedSqlOpenTargetMode } from "@/lib/savedSql/savedSqlExecutionTarget";
|
||||
import { safeLocalStorageGet, safeLocalStorageRemove } from "@/lib/backend/safeStorage";
|
||||
import { sqlTextFingerprint } from "@/lib/sql/sqlTextFingerprint";
|
||||
import { disposeAllSqlServerActivityTraces, disposeSqlServerActivityTrace } from "@/lib/sqlserver/sqlServerActivityTraceRuntime";
|
||||
import type { SavedSqlFile } from "@/types/database";
|
||||
import i18n from "@/i18n";
|
||||
import { translateBackendError } from "@/i18n/backend-errors";
|
||||
@@ -1586,6 +1587,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
onScopeDispose(() => {
|
||||
if (persistTimer) clearTimeout(persistTimer);
|
||||
persistTimer = null;
|
||||
void disposeAllSqlServerActivityTraces();
|
||||
});
|
||||
|
||||
// Immediately flush any pending debounced persist so the on-disk content
|
||||
@@ -1834,6 +1836,31 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return id;
|
||||
}
|
||||
|
||||
function openSqlServerActivityTrace(connectionId: string) {
|
||||
const existing = tabs.value.find((tab) => tab.mode === "sqlserver-trace" && tab.connectionId === connectionId);
|
||||
if (existing) {
|
||||
switchTab(existing.id);
|
||||
return existing.id;
|
||||
}
|
||||
|
||||
const conn = useConnectionStore().getConfig(connectionId);
|
||||
const id = uuid();
|
||||
const tab: QueryTab = {
|
||||
id,
|
||||
title: conn?.name ? `${conn.name} - ${t("sqlServerTrace.title")}` : t("sqlServerTrace.title"),
|
||||
connectionId,
|
||||
database: conn?.database || "",
|
||||
sql: "",
|
||||
isExecuting: false,
|
||||
isCancelling: false,
|
||||
isExplaining: false,
|
||||
mode: "sqlserver-trace",
|
||||
};
|
||||
tabs.value.push(tab);
|
||||
activeTabId.value = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
function openMysqlDashboard(connectionId: string) {
|
||||
const existing = tabs.value.find((tab) => tab.mode === "mysql-dashboard" && tab.connectionId === connectionId);
|
||||
if (existing) {
|
||||
@@ -2376,6 +2403,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
const idx = tabs.value.findIndex((t) => t.id === id);
|
||||
if (idx < 0) return;
|
||||
persistSavedSqlEditorPosition(tabs.value[idx]);
|
||||
if (tab.mode === "sqlserver-trace") void disposeSqlServerActivityTrace(tab.id);
|
||||
clearDataGridPendingSnapshotsForTab(id);
|
||||
if (tabs.value[idx].txnSessionId) void rollbackTransaction(id);
|
||||
if (tabs.value[idx].isExecuting) void cancelTabExecution(id);
|
||||
@@ -2657,6 +2685,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
tabs.value
|
||||
.filter((tab) => closingIds.has(tab.id))
|
||||
.forEach((tab) => {
|
||||
if (tab.mode === "sqlserver-trace") void disposeSqlServerActivityTrace(tab.id);
|
||||
clearDataGridPendingSnapshotsForTab(tab.id);
|
||||
if (tab.txnSessionId) void rollbackTransaction(tab.id);
|
||||
if (tab.isExecuting) void cancelTabExecution(tab.id);
|
||||
@@ -5931,6 +5960,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
openMongoBucket,
|
||||
openUserAdmin,
|
||||
openProcessList,
|
||||
openSqlServerActivityTrace,
|
||||
openMysqlDashboard,
|
||||
openPostgresDashboard,
|
||||
openNacosDashboard,
|
||||
|
||||
Reference in new issue
Block a user