diff --git a/frontend/packages/swarm-ui/src/ui/table/Table.tsx b/frontend/packages/swarm-ui/src/ui/table/Table.tsx index 30b3f8f0..643a073a 100644 --- a/frontend/packages/swarm-ui/src/ui/table/Table.tsx +++ b/frontend/packages/swarm-ui/src/ui/table/Table.tsx @@ -24,6 +24,7 @@ import { useLocalSetting } from "@hive/shared/settings-storage.js"; import { MultiselectFilterOptions } from "../multiselect-filter/MultiselectFilter.js"; import { focusInto, restoreFocus } from "../popover/focus.js"; import { clampLeft } from "./popover-position.js"; +import { forExistingColumns } from "./table-filters.js"; import "./Table.css"; export interface TableColumn { @@ -157,9 +158,15 @@ export function useTableFilters( rows: T[], storageKey: string, ) { - const [filters, setFilters] = useLocalSetting< + const [saved, setFilters] = useLocalSetting< Record >(storageKey, {}); + // A saved filter on a column that no longer exists would hide every row; + // work from the live ones, so the next save drops the rest. + const filters = forExistingColumns( + saved, + columns.map((c) => c.key), + ); const activeFilters = Object.entries(filters).filter(([, f]) => isFilterActive(f), diff --git a/frontend/packages/swarm-ui/src/ui/table/table-filters.ts b/frontend/packages/swarm-ui/src/ui/table/table-filters.ts new file mode 100644 index 00000000..34c70f1a --- /dev/null +++ b/frontend/packages/swarm-ui/src/ui/table/table-filters.ts @@ -0,0 +1,16 @@ +// What a table does with the filters it remembered, without a DOM. + +/** + * The saved filters whose column still exists. Filters are kept by column + * key, so one saved for a column a later deploy renamed or removed would + * match no row and hide every one; it is ignored instead, and dropped the + * next time the filters are saved. + */ +export function forExistingColumns( + filters: Record, + columnKeys: string[], +): Record { + return Object.fromEntries( + Object.entries(filters).filter(([key]) => columnKeys.includes(key)), + ); +} diff --git a/frontend/packages/swarm-ui/test/table-filters.test.mjs b/frontend/packages/swarm-ui/test/table-filters.test.mjs new file mode 100644 index 00000000..28119d59 --- /dev/null +++ b/frontend/packages/swarm-ui/test/table-filters.test.mjs @@ -0,0 +1,19 @@ +// Which remembered table filters still apply. Run by `npm test`, which Node +// runs with its built-in test runner and type stripping, so the `.ts` module +// is imported as it is. +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { forExistingColumns } from "../src/ui/table/table-filters.ts"; + +test("a filter on a column that is gone is ignored", () => { + const saved = { status: { value: "up" }, oldName: { value: "x" } }; + assert.deepEqual(forExistingColumns(saved, ["status", "name"]), { + status: { value: "up" }, + }); +}); + +test("filters on columns that all exist are kept, and none is fine", () => { + const saved = { a: 1, b: 2 }; + assert.deepEqual(forExistingColumns(saved, ["a", "b"]), saved); + assert.deepEqual(forExistingColumns({}, ["a"]), {}); +});