fix(grid): preserve local result filters
This commit is contained in:
1 parent
264f5c4836
commit
cb754cd0cc
4 files changed
+112
-1
No files matched your search
@@ -55,6 +55,7 @@ interface ColumnarQueryResult {
|
|||||||
execution_error?: true;
|
execution_error?: true;
|
||||||
statement_index?: number;
|
statement_index?: number;
|
||||||
column_types?: string[];
|
column_types?: string[];
|
||||||
|
local_column_filters?: QueryResult["local_column_filters"];
|
||||||
columnValues: CellValue[][];
|
columnValues: CellValue[][];
|
||||||
rowCount: number;
|
rowCount: number;
|
||||||
mongo_documents?: unknown[];
|
mongo_documents?: unknown[];
|
||||||
@@ -327,6 +328,10 @@ function clonePlain<T>(value: T): T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cloneLocalColumnFilters(filters: QueryResult["local_column_filters"]): QueryResult["local_column_filters"] {
|
||||||
|
return filters ? Object.fromEntries(Object.entries(filters).map(([columnIndex, values]) => [columnIndex, [...values]])) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function stripSessionIds(result: QueryResult | undefined): QueryResult | undefined {
|
function stripSessionIds(result: QueryResult | undefined): QueryResult | undefined {
|
||||||
if (!result) return undefined;
|
if (!result) return undefined;
|
||||||
return {
|
return {
|
||||||
@@ -334,6 +339,7 @@ function stripSessionIds(result: QueryResult | undefined): QueryResult | undefin
|
|||||||
execution_error: result.execution_error,
|
execution_error: result.execution_error,
|
||||||
statement_index: result.statement_index,
|
statement_index: result.statement_index,
|
||||||
column_types: result.column_types ? [...result.column_types] : undefined,
|
column_types: result.column_types ? [...result.column_types] : undefined,
|
||||||
|
local_column_filters: cloneLocalColumnFilters(result.local_column_filters),
|
||||||
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
|
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
|
||||||
spatial_values: result.spatial_values?.map((row) => [...row]),
|
spatial_values: result.spatial_values?.map((row) => [...row]),
|
||||||
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
|
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
|
||||||
@@ -377,6 +383,7 @@ function toColumnarResult(result: QueryResult | undefined): ColumnarQueryResult
|
|||||||
execution_error: result.execution_error,
|
execution_error: result.execution_error,
|
||||||
statement_index: result.statement_index,
|
statement_index: result.statement_index,
|
||||||
column_types: result.column_types ? [...result.column_types] : undefined,
|
column_types: result.column_types ? [...result.column_types] : undefined,
|
||||||
|
local_column_filters: cloneLocalColumnFilters(result.local_column_filters),
|
||||||
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
|
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
|
||||||
spatial_values: result.spatial_values?.map((row) => [...row]),
|
spatial_values: result.spatial_values?.map((row) => [...row]),
|
||||||
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
|
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
|
||||||
@@ -403,6 +410,7 @@ function fromColumnarResult(result: ColumnarQueryResult | undefined): QueryResul
|
|||||||
execution_error: result.execution_error,
|
execution_error: result.execution_error,
|
||||||
statement_index: result.statement_index,
|
statement_index: result.statement_index,
|
||||||
column_types: result.column_types ? [...result.column_types] : undefined,
|
column_types: result.column_types ? [...result.column_types] : undefined,
|
||||||
|
local_column_filters: cloneLocalColumnFilters(result.local_column_filters),
|
||||||
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
|
spatial_columns: result.spatial_columns?.map((entry) => ({ column_index: entry.column_index, srid: entry.srid })),
|
||||||
spatial_values: result.spatial_values?.map((row) => [...row]),
|
spatial_values: result.spatial_values?.map((row) => [...row]),
|
||||||
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
|
large_value_cells: result.large_value_cells?.map((cell) => ({ ...cell })),
|
||||||
@@ -712,6 +720,7 @@ export function decodeTabResultSnapshot(bytes: Uint8Array | ArrayBuffer): TabRes
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (!isRecord(decoded.payload)) return undefined;
|
if (!isRecord(decoded.payload)) return undefined;
|
||||||
|
// SAFETY: The validated envelope is produced by encodeTabResultSnapshot, so its record payload has the snapshot shape expected here.
|
||||||
return payloadToSnapshot(decoded.payload as unknown as TabResultSnapshotPayload);
|
return payloadToSnapshot(decoded.payload as unknown as TabResultSnapshotPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,38 @@ describe("queryStore switchTab", () => {
|
|||||||
expect(tab.result.local_column_filters).toBeUndefined();
|
expect(tab.result.local_column_filters).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps local filters isolated across result-run switches and clearing", async () => {
|
||||||
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
|
const queryStore = useQueryStore();
|
||||||
|
const tabId = queryStore.createTab("pg-1", "app", "Query", "query");
|
||||||
|
const tab = queryStore.tabs.find((item) => item.id === tabId)!;
|
||||||
|
const result = (filters?: Record<string, string[]>) => ({
|
||||||
|
columns: ["id", "status"],
|
||||||
|
rows: [[1, "active"]],
|
||||||
|
affected_rows: 0,
|
||||||
|
execution_time_ms: 1,
|
||||||
|
local_column_filters: filters,
|
||||||
|
});
|
||||||
|
tab.resultRuns = [
|
||||||
|
{ id: "run-a", title: "Run A", sequence: 1, sql: "select 1", createdAt: 1, result: result() },
|
||||||
|
{ id: "run-b", title: "Run B", sequence: 2, sql: "select 2", createdAt: 2, result: result({ "1": ["str:pending"] }) },
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(await queryStore.setActiveResultRun(tabId, "run-a")).toBe(true);
|
||||||
|
queryStore.updateDataGridLocalColumnFilters(tabId, { "1": ["str:active"] });
|
||||||
|
expect(tab.resultRuns?.[0]?.result?.local_column_filters).toEqual({ "1": ["str:active"] });
|
||||||
|
|
||||||
|
expect(await queryStore.setActiveResultRun(tabId, "run-b")).toBe(true);
|
||||||
|
expect(tab.result?.local_column_filters).toEqual({ "1": ["str:pending"] });
|
||||||
|
expect(await queryStore.setActiveResultRun(tabId, "run-a")).toBe(true);
|
||||||
|
expect(tab.result?.local_column_filters).toEqual({ "1": ["str:active"] });
|
||||||
|
|
||||||
|
queryStore.updateDataGridLocalColumnFilters(tabId, {});
|
||||||
|
expect(await queryStore.setActiveResultRun(tabId, "run-b")).toBe(true);
|
||||||
|
expect(await queryStore.setActiveResultRun(tabId, "run-a")).toBe(true);
|
||||||
|
expect(tab.result?.local_column_filters).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("stores hidden data-grid column keys on the tab result", async () => {
|
it("stores hidden data-grid column keys on the tab result", async () => {
|
||||||
const { useQueryStore } = await import("@/stores/queryStore");
|
const { useQueryStore } = await import("@/stores/queryStore");
|
||||||
const queryStore = useQueryStore();
|
const queryStore = useQueryStore();
|
||||||
|
|||||||
+2
@@ -4605,6 +4605,7 @@ test("starting a new query clears the previous result payload immediately", asyn
|
|||||||
rows: [[new Array(10_000).fill("old").join("")]],
|
rows: [[new Array(10_000).fill("old").join("")]],
|
||||||
affected_rows: 0,
|
affected_rows: 0,
|
||||||
execution_time_ms: 1,
|
execution_time_ms: 1,
|
||||||
|
local_column_filters: { "0": ["str:old"] },
|
||||||
};
|
};
|
||||||
|
|
||||||
globalThis.fetch = withConnectionHealthMock(async (input) => {
|
globalThis.fetch = withConnectionHealthMock(async (input) => {
|
||||||
@@ -4636,6 +4637,7 @@ test("starting a new query clears the previous result payload immediately", asyn
|
|||||||
assert.equal(tab.results, undefined);
|
assert.equal(tab.results, undefined);
|
||||||
await execution;
|
await execution;
|
||||||
assert.deepEqual(tab.result?.columns, ["new"]);
|
assert.deepEqual(tab.result?.columns, ["new"]);
|
||||||
|
assert.equal(tab.result?.local_column_filters, undefined);
|
||||||
} finally {
|
} finally {
|
||||||
globalThis.fetch = originalFetch;
|
globalThis.fetch = originalFetch;
|
||||||
restoreStorage();
|
restoreStorage();
|
||||||
|
|||||||
+69
-1
@@ -1,7 +1,7 @@
|
|||||||
import { strict as assert } from "node:assert";
|
import { strict as assert } from "node:assert";
|
||||||
import { test } from "vitest";
|
import { test } from "vitest";
|
||||||
import { buildTabResultSnapshot, decodeTabResultSnapshot, encodeTabResultSnapshot } from "../../apps/desktop/src/lib/tabs/tabResultCache.ts";
|
import { buildTabResultSnapshot, decodeTabResultSnapshot, encodeTabResultSnapshot } from "../../apps/desktop/src/lib/tabs/tabResultCache.ts";
|
||||||
import type { QueryTab } from "../../apps/desktop/src/types/database.ts";
|
import type { QueryResult, QueryTab } from "../../apps/desktop/src/types/database.ts";
|
||||||
|
|
||||||
function queryTab(overrides: Partial<QueryTab> = {}): QueryTab {
|
function queryTab(overrides: Partial<QueryTab> = {}): QueryTab {
|
||||||
return {
|
return {
|
||||||
@@ -102,6 +102,74 @@ test("result snapshots strip session handles from result runs", () => {
|
|||||||
assert.deepEqual(snapshot?.resultRuns?.[0]?.resultLocalSortOriginalMongoCopyDocuments, [{ _id: { $oid: "507f1f77bcf86cd799439012" } }]);
|
assert.deepEqual(snapshot?.resultRuns?.[0]?.resultLocalSortOriginalMongoCopyDocuments, [{ _id: { $oid: "507f1f77bcf86cd799439012" } }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("result snapshots preserve local column filters for all result windows", () => {
|
||||||
|
const result = (filters?: Record<string, string[]>): QueryResult => ({
|
||||||
|
columns: ["id", "status"],
|
||||||
|
rows: [[1, "active"]],
|
||||||
|
affected_rows: 0,
|
||||||
|
execution_time_ms: 1,
|
||||||
|
local_column_filters: filters,
|
||||||
|
});
|
||||||
|
const tab = queryTab({
|
||||||
|
result: result({ "1": ["str:root"] }),
|
||||||
|
results: [result({ "1": ["str:first"] }), result({ "1": ["str:second"] })],
|
||||||
|
activeResultIndex: 0,
|
||||||
|
resultRuns: [
|
||||||
|
{
|
||||||
|
id: "run-a",
|
||||||
|
title: "Run A",
|
||||||
|
sequence: 1,
|
||||||
|
sql: "select 1",
|
||||||
|
createdAt: 1,
|
||||||
|
result: result({ "1": ["str:run-a"] }),
|
||||||
|
results: [result({ "1": ["str:run-a-first"] }), result()],
|
||||||
|
activeResultIndex: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "run-b",
|
||||||
|
title: "Run B",
|
||||||
|
sequence: 2,
|
||||||
|
sql: "select 2",
|
||||||
|
createdAt: 2,
|
||||||
|
result: result({ "1": ["str:run-b"] }),
|
||||||
|
results: [result({ "1": ["str:run-b-first"] })],
|
||||||
|
activeResultIndex: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
activeResultRunId: "run-a",
|
||||||
|
});
|
||||||
|
|
||||||
|
const snapshot = buildTabResultSnapshot(tab);
|
||||||
|
assert.deepEqual(snapshot?.result?.local_column_filters, { "1": ["str:root"] });
|
||||||
|
assert.deepEqual(
|
||||||
|
snapshot?.results?.map((item) => item.local_column_filters),
|
||||||
|
[{ "1": ["str:first"] }, { "1": ["str:second"] }],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
snapshot?.resultRuns?.map((run) => run.result?.local_column_filters),
|
||||||
|
[{ "1": ["str:run-a"] }, { "1": ["str:run-b"] }],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
snapshot?.resultRuns?.[0]?.results?.map((item) => item.local_column_filters),
|
||||||
|
[{ "1": ["str:run-a-first"] }, undefined],
|
||||||
|
);
|
||||||
|
|
||||||
|
const restored = decodeTabResultSnapshot(encodeTabResultSnapshot(snapshot!));
|
||||||
|
assert.deepEqual(restored?.result?.local_column_filters, { "1": ["str:root"] });
|
||||||
|
assert.deepEqual(
|
||||||
|
restored?.results?.map((item) => item.local_column_filters),
|
||||||
|
[{ "1": ["str:first"] }, { "1": ["str:second"] }],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
restored?.resultRuns?.map((run) => run.result?.local_column_filters),
|
||||||
|
[{ "1": ["str:run-a"] }, { "1": ["str:run-b"] }],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
restored?.resultRuns?.[0]?.results?.map((item) => item.local_column_filters),
|
||||||
|
[{ "1": ["str:run-a-first"] }, undefined],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("result snapshots encode as binary columnar payloads and decode back to rows", () => {
|
test("result snapshots encode as binary columnar payloads and decode back to rows", () => {
|
||||||
const snapshot = buildTabResultSnapshot(
|
const snapshot = buildTabResultSnapshot(
|
||||||
queryTab({
|
queryTab({
|
||||||
|
|||||||
Reference in new issue
Block a user