feat(grid): add persistent SQL filter views
This commit is contained in:
27 files changed
+1428
-107
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildDataGridStructuredWhere, useDataGridFilterBuilder, type DataGridStructuredFilterRule } from "@/composables/useDataGridFilterBuilder";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { buildDataGridStructuredWhere, createDataGridFilterConditionCache, moveDataGridStructuredFilterRule, useDataGridFilterBuilder, type DataGridStructuredFilterRule } from "@/composables/useDataGridFilterBuilder";
|
||||
|
||||
describe("useDataGridFilterBuilder", () => {
|
||||
it("searches columns by camel-case initials and any-position text", () => {
|
||||
@@ -52,4 +52,52 @@ describe("useDataGridFilterBuilder", () => {
|
||||
]),
|
||||
).toBe("((a) AND (b)) OR (c)");
|
||||
});
|
||||
|
||||
it("moves complete and disabled rules while preserving their condition data", async () => {
|
||||
let nextId = 0;
|
||||
const builder = useDataGridFilterBuilder({
|
||||
columns: ["a", "b", "c"],
|
||||
createId: () => `rule-${++nextId}`,
|
||||
isComplete: () => true,
|
||||
buildCondition: async (rule) => `${rule.columnName} = ${rule.rawValue}`,
|
||||
});
|
||||
builder.ensureRule();
|
||||
builder.updateRule("rule-1", { columnName: "a", rawValue: "1" });
|
||||
builder.addRule();
|
||||
builder.updateRule("rule-2", { columnName: "b", rawValue: "2", disabled: true });
|
||||
builder.addRule();
|
||||
builder.updateRule("rule-3", { columnName: "c", rawValue: "3" });
|
||||
|
||||
builder.moveRule("rule-3", 0);
|
||||
|
||||
expect(builder.rules.value.map((rule) => rule.id)).toEqual(["rule-3", "rule-1", "rule-2"]);
|
||||
expect(builder.rules.value[2]).toMatchObject({ columnName: "b", rawValue: "2", disabled: true });
|
||||
expect(await builder.buildWhere()).toBe("(c = 3) AND (a = 1)");
|
||||
});
|
||||
|
||||
it("clamps rule moves and ignores unknown rule ids", () => {
|
||||
const rule = (id: string): DataGridStructuredFilterRule => ({ id, columnName: id, mode: "equals", rawValue: id, rawEndValue: "", conjunction: "AND" });
|
||||
const rules = [rule("a"), rule("b"), rule("c")];
|
||||
|
||||
expect(moveDataGridStructuredFilterRule(rules, "a", 99).map((item) => item.id)).toEqual(["b", "c", "a"]);
|
||||
expect(moveDataGridStructuredFilterRule(rules, "c", -2).map((item) => item.id)).toEqual(["c", "a", "b"]);
|
||||
expect(moveDataGridStructuredFilterRule(rules, "missing", 1)).toEqual(rules);
|
||||
});
|
||||
|
||||
it("reuses unchanged rule conditions and drops removed rules", async () => {
|
||||
const cache = createDataGridFilterConditionCache();
|
||||
const buildFirst = vi.fn(async () => "id = 1");
|
||||
const buildChanged = vi.fn(async () => "id = 2");
|
||||
|
||||
await expect(cache.resolve("rule-1", "id:1", buildFirst)).resolves.toBe("id = 1");
|
||||
await expect(cache.resolve("rule-1", "id:1", buildFirst)).resolves.toBe("id = 1");
|
||||
expect(buildFirst).toHaveBeenCalledOnce();
|
||||
|
||||
await expect(cache.resolve("rule-1", "id:2", buildChanged)).resolves.toBe("id = 2");
|
||||
expect(buildChanged).toHaveBeenCalledOnce();
|
||||
|
||||
cache.retain([]);
|
||||
await expect(cache.resolve("rule-1", "id:2", buildChanged)).resolves.toBe("id = 2");
|
||||
expect(buildChanged).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,45 @@ export function buildDataGridStructuredWhere(items: Array<{ rule: DataGridStruct
|
||||
return result;
|
||||
}
|
||||
|
||||
export function moveDataGridStructuredFilterRule(rules: readonly DataGridStructuredFilterRule[], ruleId: string, targetIndex: number): DataGridStructuredFilterRule[] {
|
||||
const sourceIndex = rules.findIndex((rule) => rule.id === ruleId);
|
||||
if (sourceIndex < 0 || rules.length < 2) return [...rules];
|
||||
const nextIndex = Math.min(rules.length - 1, Math.max(0, Math.trunc(targetIndex)));
|
||||
if (sourceIndex === nextIndex) return [...rules];
|
||||
const nextRules = [...rules];
|
||||
const [rule] = nextRules.splice(sourceIndex, 1);
|
||||
nextRules.splice(nextIndex, 0, rule);
|
||||
return nextRules;
|
||||
}
|
||||
|
||||
export function createDataGridFilterConditionCache() {
|
||||
const entries = new Map<string, { signature: string; condition: Promise<string | null> }>();
|
||||
|
||||
function resolve(ruleId: string, signature: string, build: () => Promise<string | null>): Promise<string | null> {
|
||||
const cached = entries.get(ruleId);
|
||||
if (cached?.signature === signature) return cached.condition;
|
||||
|
||||
let condition: Promise<string | null>;
|
||||
condition = Promise.resolve()
|
||||
.then(build)
|
||||
.catch((error) => {
|
||||
if (entries.get(ruleId)?.condition === condition) entries.delete(ruleId);
|
||||
throw error;
|
||||
});
|
||||
entries.set(ruleId, { signature, condition });
|
||||
return condition;
|
||||
}
|
||||
|
||||
function retain(ruleIds: readonly string[]) {
|
||||
const retained = new Set(ruleIds);
|
||||
for (const ruleId of entries.keys()) {
|
||||
if (!retained.has(ruleId)) entries.delete(ruleId);
|
||||
}
|
||||
}
|
||||
|
||||
return { resolve, retain };
|
||||
}
|
||||
|
||||
export function useDataGridFilterBuilder(options: UseDataGridFilterBuilderOptions) {
|
||||
const rules = ref<DataGridStructuredFilterRule[]>([]);
|
||||
const open = ref(false);
|
||||
@@ -63,6 +102,9 @@ export function useDataGridFilterBuilder(options: UseDataGridFilterBuilderOption
|
||||
return next;
|
||||
});
|
||||
}
|
||||
function moveRule(id: string, targetIndex: number) {
|
||||
rules.value = moveDataGridStructuredFilterRule(rules.value, id, targetIndex);
|
||||
}
|
||||
function reset() {
|
||||
appliedWhereInput.value = "";
|
||||
rules.value = toValue(options.columns).length ? [defaultRule()] : [];
|
||||
@@ -90,5 +132,5 @@ export function useDataGridFilterBuilder(options: UseDataGridFilterBuilderOption
|
||||
},
|
||||
);
|
||||
|
||||
return { rules, open, columnSearch, appliedWhereInput, filteredColumns, activeCount, defaultRule, ensureRule, addRule, removeRule, updateRule, reset, buildWhere, apply };
|
||||
return { rules, open, columnSearch, appliedWhereInput, filteredColumns, activeCount, defaultRule, ensureRule, addRule, removeRule, updateRule, moveRule, reset, buildWhere, apply };
|
||||
}
|
||||
Reference in new issue
Block a user