fix(grid): keep filters when refreshing restored table tabs
This commit is contained in:
1 parent
cd8300c3f5
commit
aa637b2d90
5 files changed
+101
-4
No files matched your search
@@ -2,7 +2,7 @@
|
||||
import { computed, ref, defineAsyncComponent, watch, nextTick, onMounted, onUnmounted } from "vue";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import { canReloadUnavailableDataTab } from "@/lib/table/tableDataRefresh";
|
||||
import { canReloadUnavailableDataTab, restoredDataTabReloadFilters } from "@/lib/table/tableDataRefresh";
|
||||
import { defaultViewForResult } from "@/lib/query/queryResultDefaultView";
|
||||
import { isQueryExecutionErrorResult } from "@/lib/query/queryResultError";
|
||||
import { batchSqlRecoveryState, type BatchSqlRecoveryAction } from "@/lib/query/batchSqlRecovery";
|
||||
@@ -916,6 +916,11 @@ function refreshQueryEditorCompletionCache(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
function reloadUnavailableDataTab() {
|
||||
const { whereInput, orderBy } = restoredDataTabReloadFilters(props.activeTab);
|
||||
emit("reload", undefined, undefined, whereInput, orderBy);
|
||||
}
|
||||
|
||||
function refreshData(): boolean {
|
||||
// Reuse ObjectBrowser's reload path so schema reloads and stale object-response guards stay intact.
|
||||
if (props.activeTab.mode === "objects") return objectBrowserRef.value?.refresh?.() ?? false;
|
||||
@@ -927,7 +932,7 @@ function refreshData(): boolean {
|
||||
if (props.activeTab.mode === "databases") return databaseBrowserRef.value?.refresh?.() ?? false;
|
||||
// Restored data tabs intentionally omit row data, so refresh must work before DataGrid mounts.
|
||||
if (canReloadUnavailableDataTab(props.activeTab)) {
|
||||
emit("reload");
|
||||
reloadUnavailableDataTab();
|
||||
return true;
|
||||
}
|
||||
if (activeElasticsearchJsonResponse.value) {
|
||||
@@ -2220,7 +2225,7 @@ defineExpose({
|
||||
<kbd v-for="key in modRKeys" :key="key" class="min-w-5 rounded border border-border/60 bg-muted/50 px-1.5 py-0.5 text-center font-mono text-[12px] leading-none text-muted-foreground shadow-xs">{{ key }}</kbd>
|
||||
<span>{{ t("grid.dataUnavailableHintSuffix") }}</span>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" class="h-7 gap-1.5" @click="emit('reload')">
|
||||
<Button variant="outline" size="sm" class="h-7 gap-1.5" @click="reloadUnavailableDataTab()">
|
||||
<RefreshCcw class="h-3.5 w-3.5" />
|
||||
{{ t("grid.refresh") }}
|
||||
</Button>
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const contentAreaSource = readFileSync(new URL("../ContentArea.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("ContentArea restored data-tab refresh", () => {
|
||||
it("routes both no-data refresh entries through the filter-preserving helper (#7963)", () => {
|
||||
// 裸 emit("reload") 会让 onReloadData 把缺省的 whereInput/orderBy 当成
|
||||
// “用户已清空”,刷新后退回整张表的数据。
|
||||
expect(contentAreaSource).not.toContain(`@click="emit('reload')"`);
|
||||
expect(contentAreaSource).not.toMatch(/\bemit\("reload"\)\s*;/);
|
||||
expect(contentAreaSource).toContain(`@click="reloadUnavailableDataTab()"`);
|
||||
expect(contentAreaSource).toContain("reloadUnavailableDataTab();");
|
||||
});
|
||||
|
||||
it("passes the restored tab's own WHERE/ORDER BY into the reload event", () => {
|
||||
expect(contentAreaSource).toContain("const { whereInput, orderBy } = restoredDataTabReloadFilters(props.activeTab);");
|
||||
expect(contentAreaSource).toContain(`emit("reload", undefined, undefined, whereInput, orderBy);`);
|
||||
});
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { computed, reactive } from "vue";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useDataGridActions } from "@/composables/useDataGridActions";
|
||||
import { clearTableMetadataCache } from "@/lib/metadata/tableMetadataCache";
|
||||
import { restoredDataTabReloadFilters } from "@/lib/table/tableDataRefresh";
|
||||
import type { QueryTab } from "@/types/database";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
@@ -166,6 +167,50 @@ describe("useDataGridActions", () => {
|
||||
expect(mocks.executeTabSql.mock.calls[0]?.[2]).not.toHaveProperty("preserveTotalRowCountDuringExecution");
|
||||
});
|
||||
|
||||
it("keeps the restored filter and sort when the no-data placeholder refresh runs (#7963)", async () => {
|
||||
// 重启后恢复的数据标签页没有 result,刷新走的是 ContentArea 空态占位符那条
|
||||
// 不带参数的入口;它必须回带标签页自身的 whereInput/orderByInput,
|
||||
// 否则刷新会重新加载整张表。
|
||||
const tab = tableDataTab({
|
||||
result: undefined,
|
||||
whereInput: "status = 'active'",
|
||||
orderByInput: '"name" DESC',
|
||||
tableMeta: {
|
||||
schema: "public",
|
||||
tableName: "users",
|
||||
tableType: "TABLE",
|
||||
columns: [
|
||||
{ name: "id", data_type: "integer", is_nullable: false, column_default: null, is_primary_key: true, extra: null },
|
||||
{ name: "name", data_type: "text", is_nullable: true, column_default: null, is_primary_key: false, extra: null },
|
||||
{ name: "status", data_type: "text", is_nullable: true, column_default: null, is_primary_key: false, extra: null },
|
||||
],
|
||||
primaryKeys: ["id"],
|
||||
},
|
||||
});
|
||||
mocks.tabs.push(tab);
|
||||
const actions = useDataGridActions(computed(() => tab));
|
||||
|
||||
const { whereInput, orderBy } = restoredDataTabReloadFilters(tab);
|
||||
await actions.onReloadData(undefined, undefined, whereInput, orderBy);
|
||||
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ whereInput: "status = 'active'", orderBy: '"name" DESC' }));
|
||||
expect(tab.whereInput).toBe("status = 'active'");
|
||||
expect(tab.orderByInput).toBe('"name" DESC');
|
||||
});
|
||||
|
||||
it("still clears the stored filter when a mounted grid refreshes with an emptied WHERE input", async () => {
|
||||
// 对照:DataGrid 的 currentWhereInput() 在用户清空筛选时返回 undefined,
|
||||
// 所以 onReloadData 里不能无条件回退到 tab.whereInput。
|
||||
const tab = tableDataTab({ whereInput: "status = 'active'" });
|
||||
mocks.tabs.push(tab);
|
||||
const actions = useDataGridActions(computed(() => tab));
|
||||
|
||||
await actions.onReloadData(tab.sql, "", undefined, undefined, undefined, undefined, "refresh");
|
||||
|
||||
expect(mocks.buildTableSelectSql).toHaveBeenCalledWith(expect.objectContaining({ whereInput: undefined, orderBy: undefined }));
|
||||
expect(tab.whereInput).toBe("");
|
||||
});
|
||||
|
||||
it("preserves the toolbar page segment and offset for table-data refresh", async () => {
|
||||
const tab = tableDataTab({
|
||||
resultPageLimit: 25,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { canReloadUnavailableDataTab } from "@/lib/table/tableDataRefresh";
|
||||
import { canReloadUnavailableDataTab, restoredDataTabReloadFilters } from "@/lib/table/tableDataRefresh";
|
||||
|
||||
describe("canReloadUnavailableDataTab", () => {
|
||||
it("allows restored data tabs to reload before the grid mounts", () => {
|
||||
@@ -21,3 +21,17 @@ describe("canReloadUnavailableDataTab", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("restoredDataTabReloadFilters", () => {
|
||||
it("carries the restored tab's own filter and sort back into the reload (#7963)", () => {
|
||||
expect(restoredDataTabReloadFilters({ whereInput: "status = 'active'", orderByInput: "name DESC" })).toEqual({
|
||||
whereInput: "status = 'active'",
|
||||
orderBy: "name DESC",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports blank filters as absent so the reload does not emit an empty WHERE/ORDER BY", () => {
|
||||
expect(restoredDataTabReloadFilters({ whereInput: "", orderByInput: undefined })).toEqual({ whereInput: undefined, orderBy: undefined });
|
||||
expect(restoredDataTabReloadFilters({ whereInput: " ", orderByInput: " " })).toEqual({ whereInput: undefined, orderBy: undefined });
|
||||
});
|
||||
});
|
||||
@@ -5,3 +5,16 @@ type RefreshableDataTab = Pick<QueryTab, "mode" | "result" | "isExecuting">;
|
||||
export function canReloadUnavailableDataTab(tab: RefreshableDataTab): boolean {
|
||||
return tab.mode === "data" && !tab.result && !tab.isExecuting;
|
||||
}
|
||||
|
||||
type RestoredDataTabFilters = Pick<QueryTab, "whereInput" | "orderByInput">;
|
||||
|
||||
// 恢复的数据标签页没有 result,刷新走的是 DataGrid 之外那条不带参数的 reload 入口。
|
||||
// onReloadData 把缺省的 whereInput/orderBy 当成“用户已清空”(DataGrid 清空筛选时
|
||||
// 正是传 undefined),所以这里必须显式回带标签页自己持久化的过滤/排序,
|
||||
// 否则刷新会退回整张表的数据(#7963)。
|
||||
export function restoredDataTabReloadFilters(tab: RestoredDataTabFilters): { whereInput?: string; orderBy?: string } {
|
||||
return {
|
||||
whereInput: tab.whereInput?.trim() || undefined,
|
||||
orderBy: tab.orderByInput?.trim() || undefined,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user