feat(lists): make the most relevant columns sortable on the new tables
Mailing Lists, Roles, and Domains got new/existing columns in prior commits but no way to sort them — the account-client-sort mechanism was only wired up in accountColumns.ts. Extracted its one-line clientSortable() column tagger into schemaDeviationTypes.ts (shared, same intersection-type pattern) and used it in all four column-patch files: - Mailing Lists: Email Address, Description, Aliases - Roles: Description, Enabled Permissions, Disabled Permissions - Domains: Domain Name, Enabled, Aliases Re-verified server sort support live, per list this time rather than just Accounts: every property tried returns unsupportedSort except one surprise — x:Domain/query actually accepts sort by "name", despite the schema not declaring it. Documented in SCHEMA_DEVIATIONS.md and deliberately routed through the same client-sort path as everything else instead of adding a one-off "trust an undeclared sort" mechanism for that single case. Verified in the running dev server: sort indicators appear on the intended columns for all three lists, clicking reorders rows correctly (alphabetical on Mailing Lists' Email Address, numeric on Roles' Enabled Permissions: 1 -> 3 -> 50 -> 229 -> 244 -> 452), Domains sorts without error. No regression on Accounts/Groups.
This commit is contained in:
@@ -76,10 +76,10 @@ itself stays byte-for-byte alignable with upstream's version of the file.
|
|||||||
|
|
||||||
### `account-client-sort` 🟡
|
### `account-client-sort` 🟡
|
||||||
|
|
||||||
- **Where**: table-level mechanism in [`src/components/lists/DynamicList.tsx`](src/components/lists/DynamicList.tsx) (`clientSortableColumns`, `getClientSortValue`, the `fetchData` branch triggered by `clientSortField`) reading a `clientSortable` flag set per-column in [`src/lib/accountColumns.ts`](src/lib/accountColumns.ts) (type: [`ClientSortableColumn`](src/lib/schemaDeviationTypes.ts))
|
- **Where**: table-level mechanism in [`src/components/lists/DynamicList.tsx`](src/components/lists/DynamicList.tsx) (`clientSortableColumns`, `getClientSortValue`, the `fetchData` branch triggered by `clientSortField`) reading a `clientSortable` flag set per-column via the shared `clientSortable()` helper in [`src/lib/schemaDeviationTypes.ts`](src/lib/schemaDeviationTypes.ts) (type: `ClientSortableColumn`), used by [`accountColumns.ts`](src/lib/accountColumns.ts), [`mailingListColumns.ts`](src/lib/mailingListColumns.ts), [`roleColumns.ts`](src/lib/roleColumns.ts), and [`domainColumns.ts`](src/lib/domainColumns.ts)
|
||||||
- **What**: any column tagged `clientSortable` in the schema gets fetch-all-then-sort-in-memory on click (bypassing server pagination, same mechanism as `mailbox-client-hierarchy-sort`), instead of sending a JMAP `sort` to the server. The mechanism itself is generic and not tied to any specific list — currently only `withAccountListColumns` tags columns with it, for Email Address, Full Name, Usage, and Aliases on `x:Account/User` and `x:Account/Group`.
|
- **What**: any column tagged `clientSortable` in the schema gets fetch-all-then-sort-in-memory on click (bypassing server pagination, same mechanism as `mailbox-client-hierarchy-sort`), instead of sending a JMAP `sort` to the server. The mechanism itself is generic and not tied to any specific list. Currently tagged: Email Address/Full Name/Usage/Aliases on `x:Account/User` and `x:Account/Group`; Email Address/Description/Aliases on `x:MailingList`; Description/Enabled Permissions/Disabled Permissions on `x:Role`; Domain Name/Enabled/Aliases on `x:Domain`.
|
||||||
- **Why**: neither list's schema declares any sortable property at all (`list.sort` is absent) — confirmed against a live server: `x:Account/query` with `sort: [{"property":"emailAddress",...}]` returns `unsupportedSort` for every property tried, including the real ones. This is a systemic gap in the current Stalwart server, not specific to this fork's synthetic columns.
|
- **Why**: none of these lists' schemas declare any sortable property at all (`list.sort` is absent on all four) — confirmed against a live server by trying `sort` on every displayed real column: all return `unsupportedSort`, **except** `x:Domain/query` with `sort: [{"property":"name",...}]`, which the server actually accepts despite the schema not declaring it. Rather than add a second "trust an undeclared sort" pathway for that one case, Domain Name is routed through the same client-sort mechanism as everything else, for consistency; it's marginally less efficient (fetch-all instead of a paginated server sort) but domain lists are typically small.
|
||||||
- **Ideal fix**: the server's `x:Account/User`/`x:Account/Group` query methods accept `sort` on at least `emailAddress`, `description`, `usedDiskQuota`, and the schema declares them in `list.sort`; `withAccountListColumns` stops tagging those columns `clientSortable` and they fall through to the normal server-paginated `sortableFields` path already used elsewhere. The generic mechanism itself only goes away once nothing tags any column `clientSortable` anymore.
|
- **Ideal fix**: the server's query methods accept `sort` on these properties and the schema declares them in each list's `list.sort`; each `with*Columns` helper stops tagging its columns `clientSortable` and they fall through to the normal server-paginated `sortableFields` path already used elsewhere. The generic mechanism itself only goes away once nothing tags any column `clientSortable` anymore.
|
||||||
|
|
||||||
### `role-permission-count-columns` 🟡
|
### `role-permission-count-columns` 🟡
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Schema } from '@/types/schema';
|
import type { Schema } from '@/types/schema';
|
||||||
import type { ClientSortableColumn } from './schemaDeviationTypes';
|
import { clientSortable as sortable } from './schemaDeviationTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SCHEMA-DEVIATION: account-quota-usage-column (see SCHEMA_DEVIATIONS.md)
|
* SCHEMA-DEVIATION: account-quota-usage-column (see SCHEMA_DEVIATIONS.md)
|
||||||
@@ -35,10 +35,6 @@ import type { ClientSortableColumn } from './schemaDeviationTypes';
|
|||||||
* itself, the same way it already resolves `quotaUsage`/`aliasCount`,
|
* itself, the same way it already resolves `quotaUsage`/`aliasCount`,
|
||||||
* instead of hardcoding which lists/columns support it.
|
* instead of hardcoding which lists/columns support it.
|
||||||
*/
|
*/
|
||||||
function sortable(column: { name: string; label: string }): ClientSortableColumn {
|
|
||||||
return { ...column, clientSortable: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function withAccountListColumns(schema: Schema): Schema {
|
export function withAccountListColumns(schema: Schema): Schema {
|
||||||
let lists = schema.lists;
|
let lists = schema.lists;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Schema } from '@/types/schema';
|
import type { Schema } from '@/types/schema';
|
||||||
|
import { clientSortable } from './schemaDeviationTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SCHEMA-DEVIATION: account-alias-count-column (see SCHEMA_DEVIATIONS.md)
|
* SCHEMA-DEVIATION: account-alias-count-column (see SCHEMA_DEVIATIONS.md)
|
||||||
@@ -14,16 +15,31 @@ import type { Schema } from '@/types/schema';
|
|||||||
* `aliasCount` column already used on Accounts/Groups/Mailing Lists —
|
* `aliasCount` column already used on Accounts/Groups/Mailing Lists —
|
||||||
* DynamicList's generic count-column handling picks it up with no
|
* DynamicList's generic count-column handling picks it up with no
|
||||||
* further wiring.
|
* further wiring.
|
||||||
|
*
|
||||||
|
* SCHEMA-DEVIATION: account-client-sort (see SCHEMA_DEVIATIONS.md)
|
||||||
|
*
|
||||||
|
* Domain Name, Enabled, and `aliasCount` are tagged `clientSortable`.
|
||||||
|
* `name` is actually accepted by the live server's `sort` (unlike every
|
||||||
|
* other property tried on every other list — see SCHEMA_DEVIATIONS.md),
|
||||||
|
* but the schema still doesn't declare it in `list.sort`, so it's routed
|
||||||
|
* through the same client-sort mechanism as the rest for consistency
|
||||||
|
* rather than adding a second, one-off "trust an undeclared sort"
|
||||||
|
* pathway for a single column.
|
||||||
*/
|
*/
|
||||||
export function withDomainColumns(schema: Schema): Schema {
|
export function withDomainColumns(schema: Schema): Schema {
|
||||||
const list = schema.lists['x:Domain'];
|
const list = schema.lists['x:Domain'];
|
||||||
if (!list) return schema;
|
if (!list) return schema;
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
...list.columns.map((c) => (c.name === 'name' || c.name === 'isEnabled' ? clientSortable(c) : c)),
|
||||||
|
clientSortable({ name: 'aliasCount', label: 'Aliases' }),
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...schema,
|
...schema,
|
||||||
lists: {
|
lists: {
|
||||||
...schema.lists,
|
...schema.lists,
|
||||||
'x:Domain': { ...list, columns: [...list.columns, { name: 'aliasCount', label: 'Aliases' }] },
|
'x:Domain': { ...list, columns },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Schema } from '@/types/schema';
|
import type { Schema } from '@/types/schema';
|
||||||
|
import { clientSortable } from './schemaDeviationTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SCHEMA-DEVIATION: account-alias-count-column (see SCHEMA_DEVIATIONS.md)
|
* SCHEMA-DEVIATION: account-alias-count-column (see SCHEMA_DEVIATIONS.md)
|
||||||
@@ -13,16 +14,27 @@ import type { Schema } from '@/types/schema';
|
|||||||
* full `aliases` objectList on the detail view. Adds the same synthetic
|
* full `aliases` objectList on the detail view. Adds the same synthetic
|
||||||
* `aliasCount` column already used on Accounts/Groups — DynamicList's
|
* `aliasCount` column already used on Accounts/Groups — DynamicList's
|
||||||
* generic count-column handling picks it up with no further wiring.
|
* generic count-column handling picks it up with no further wiring.
|
||||||
|
*
|
||||||
|
* SCHEMA-DEVIATION: account-client-sort (see SCHEMA_DEVIATIONS.md)
|
||||||
|
*
|
||||||
|
* Email Address, Description, and `aliasCount` are tagged
|
||||||
|
* `clientSortable` — same reasoning as Accounts/Groups: the server
|
||||||
|
* doesn't support sorting on any `x:MailingList` property either.
|
||||||
*/
|
*/
|
||||||
export function withMailingListColumns(schema: Schema): Schema {
|
export function withMailingListColumns(schema: Schema): Schema {
|
||||||
const list = schema.lists['x:MailingList'];
|
const list = schema.lists['x:MailingList'];
|
||||||
if (!list) return schema;
|
if (!list) return schema;
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
...list.columns.map((c) => (c.name === 'emailAddress' || c.name === 'description' ? clientSortable(c) : c)),
|
||||||
|
clientSortable({ name: 'aliasCount', label: 'Aliases' }),
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...schema,
|
...schema,
|
||||||
lists: {
|
lists: {
|
||||||
...schema.lists,
|
...schema.lists,
|
||||||
'x:MailingList': { ...list, columns: [...list.columns, { name: 'aliasCount', label: 'Aliases' }] },
|
'x:MailingList': { ...list, columns },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Schema } from '@/types/schema';
|
import type { Schema } from '@/types/schema';
|
||||||
|
import { clientSortable } from './schemaDeviationTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SCHEMA-DEVIATION: role-permission-count-columns (see SCHEMA_DEVIATIONS.md)
|
* SCHEMA-DEVIATION: role-permission-count-columns (see SCHEMA_DEVIATIONS.md)
|
||||||
@@ -14,6 +15,12 @@ import type { Schema } from '@/types/schema';
|
|||||||
* synthetic columns — DynamicList's generic count-column handling
|
* synthetic columns — DynamicList's generic count-column handling
|
||||||
* resolves them from the real `enabledPermissions`/`disabledPermissions`
|
* resolves them from the real `enabledPermissions`/`disabledPermissions`
|
||||||
* set properties and renders their entry counts.
|
* set properties and renders their entry counts.
|
||||||
|
*
|
||||||
|
* SCHEMA-DEVIATION: account-client-sort (see SCHEMA_DEVIATIONS.md)
|
||||||
|
*
|
||||||
|
* Description and both permission-count columns are tagged
|
||||||
|
* `clientSortable` — same reasoning as Accounts/Groups: the server
|
||||||
|
* doesn't support sorting on any `x:Role` property either.
|
||||||
*/
|
*/
|
||||||
export function withRoleListColumns(schema: Schema): Schema {
|
export function withRoleListColumns(schema: Schema): Schema {
|
||||||
const list = schema.lists['x:Role'];
|
const list = schema.lists['x:Role'];
|
||||||
@@ -26,9 +33,9 @@ export function withRoleListColumns(schema: Schema): Schema {
|
|||||||
'x:Role': {
|
'x:Role': {
|
||||||
...list,
|
...list,
|
||||||
columns: [
|
columns: [
|
||||||
...list.columns,
|
...list.columns.map((c) => (c.name === 'description' ? clientSortable(c) : c)),
|
||||||
{ name: 'enabledPermissionCount', label: 'Enabled Permissions' },
|
clientSortable({ name: 'enabledPermissionCount', label: 'Enabled Permissions' }),
|
||||||
{ name: 'disabledPermissionCount', label: 'Disabled Permissions' },
|
clientSortable({ name: 'disabledPermissionCount', label: 'Disabled Permissions' }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,3 +30,8 @@ export type ClientSortableColumn = Column & { clientSortable?: boolean };
|
|||||||
export function isClientSortableColumn(c: Column): boolean {
|
export function isClientSortableColumn(c: Column): boolean {
|
||||||
return (c as ClientSortableColumn).clientSortable === true;
|
return (c as ClientSortableColumn).clientSortable === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Tags a column `clientSortable`, for schema patches that opt a column into account-client-sort. */
|
||||||
|
export function clientSortable(column: Column): ClientSortableColumn {
|
||||||
|
return { ...column, clientSortable: true };
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user