chore(ui): завершить миграцию Filters и удалить старый API

This commit is contained in:
Denozordec committed 2026-09-20 00:47:33 +07:00
1 parent 4fd844f977
commit 0d418e2e39
6 files changed
+37 -2025

No files matched your search

+12 -7
View File
@@ -1,8 +1,6 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { applyFilterQuery } from "./apply-filter-query"
import { migrateFilterQuery } from "./migrate-filter-query"
import { applyReuiFilters } from "./apply-reui-filters"
import { createFilterQuery, createFilterRule } from "../../components/reui/filters/filters-query"
const rows = [{ country: "RU", status: "online" }, { country: "DE", status: "offline" }, { country: "NL", status: "offline" }]
@@ -36,10 +34,17 @@ test("text prefix and suffix use case-insensitive matching and support negation"
assert.deepEqual(applyFilterQuery(rows, createFilterQuery([{ ...rule("country", "starts_with", "r"), negated: true }]), accessors), rows.slice(1))
})
test("migration preserves the existing operators and implicit AND", () => {
for (const operator of ["is", "isNot", "isAnyOf", "isNotAnyOf", "contains", "doesNotContain"]) {
const filters = [{ id: "country", field: "country", operator, values: ["RU", "DE"] },
{ id: "status", field: "status", operator: "isNot", values: ["online"] }]
assert.deepEqual(applyFilterQuery(rows, migrateFilterQuery(filters), accessors), applyReuiFilters(rows, filters, accessors))
test("scalar operators keep their expected results when combined with AND", () => {
const cases: [string, unknown, typeof rows][] = [
["is", "RU", []],
["is_not", "RU", [rows[1], rows[2]]],
["is_any_of", ["RU", "DE"], [rows[1]]],
["is_none_of", ["RU", "DE"], [rows[2]]],
["contains", "e", [rows[1]]],
["not_contains", "e", [rows[2]]],
]
for (const [operator, value, expected] of cases) {
const query = createFilterQuery([rule("country", operator, value), rule("status", "is_not", "online")])
assert.deepEqual(applyFilterQuery(rows, query, accessors), expected)
}
})
-40
View File
@@ -1,40 +0,0 @@
import type { Filter } from "@/components/reui/filters"
type FieldAccessor<T> = Record<string, (row: T) => unknown>
function matchFilter<T>(row: T, filter: Filter, accessors: FieldAccessor<T>): boolean {
const getValue = accessors[filter.field]
if (!getValue) return true
const value = getValue(row)
const vals = filter.values
const str = (v: unknown) => String(v ?? "").toLowerCase()
switch (filter.operator) {
case "is":
return vals.length > 0 && String(value) === String(vals[0])
case "isNot":
return vals.length === 0 || String(value) !== String(vals[0])
case "isAnyOf":
return vals.some((v) => String(value) === String(v))
case "isNotAnyOf":
return !vals.some((v) => String(value) === String(v))
case "contains":
return vals.some((v) => str(value).includes(str(v)))
case "doesNotContain":
return !vals.some((v) => str(value).includes(str(v)))
default:
return true
}
}
export function applyReuiFilters<T>(
rows: T[],
filters: Filter[],
accessors: FieldAccessor<T>,
): T[] {
if (filters.length === 0) return rows
return rows.filter((row) =>
filters.every((f) => matchFilter(row, f, accessors)),
)
}
-15
View File
@@ -1,15 +0,0 @@
import { createFilterQuery, createFilterRule } from "@/components/reui/filters/filters-query"
const OPERATORS: Record<string, string> = {
is: "is", isNot: "is_not", isAnyOf: "is_any_of", isNotAnyOf: "is_none_of",
contains: "contains", doesNotContain: "not_contains",
}
/** Temporary input contract, independent of the old UI component. */
export function migrateFilterQuery(filters: { id: string; field: string; operator: string; values: unknown[] }[]) {
return createFilterQuery(filters.map(filter => {
const operator = OPERATORS[filter.operator]
if (!operator) throw new Error(`Неподдерживаемое условие: ${filter.operator}`)
return createFilterRule({ id: filter.id, path: [filter.field], operator, value: filter.values })
}))
}