From 3241dea5e45c1d6488cb85f78e1ef1c7bacd4bc9 Mon Sep 17 00:00:00 2001 From: Steven RYDELL Date: Sat, 1 Aug 2026 20:18:01 +0200 Subject: [PATCH] feat(accounts): add Aliases count column to Accounts and Groups Same pattern as the existing quotaUsage deviation: neither list's schema exposes an alias count as a column, only the full `aliases` objectList on the detail view. Added a synthetic `aliasCount` column (SCHEMA-DEVIATION: account-alias-count-column, documented in SCHEMA_DEVIATIONS.md) that DynamicList resolves by fetching the real `aliases` property and counting its entries. Verified against a live test server: adding a real alias to an account correctly bumps its Aliases count from 0 to 1 in the list. --- SCHEMA_DEVIATIONS.md | 7 +++++++ src/components/lists/DynamicList.tsx | 23 ++++++++++++++++++++++- src/lib/accountColumns.ts | 13 ++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/SCHEMA_DEVIATIONS.md b/SCHEMA_DEVIATIONS.md index 3235780..8dbcf9e 100644 --- a/SCHEMA_DEVIATIONS.md +++ b/SCHEMA_DEVIATIONS.md @@ -67,6 +67,13 @@ itself stays byte-for-byte alignable with upstream's version of the file. - **Why**: the `x:Application` list schema is not guaranteed to expose `enabled` as a list column, even though it's a real object property (fetched separately via `properties.push('enabled')`). - **Ideal fix**: the server's `x:Application` list schema always includes `enabled` as a real column; the fallback branch is deleted (only the reordering logic remains, which is not a deviation). +### `account-alias-count-column` 🟡 + +- **Where**: [`src/lib/accountColumns.ts`](src/lib/accountColumns.ts), rendering in [`src/components/lists/DynamicList.tsx`](src/components/lists/DynamicList.tsx) +- **What**: adds a synthetic `aliasCount` column to the `x:Account/User` and `x:Account/Group` lists — not a real schema property; `DynamicList` resolves it from the real `aliases` objectList property and renders its entry count. +- **Why**: neither list's schema exposes alias count as a column, only the full `aliases` list on the detail view. +- **Ideal fix**: the server's `x:Account/User` and `x:Account/Group` list schemas include a computed alias-count column natively; this column definition is deleted. + ## Not a deviation (for reference) A few other `viewName === '...'` / `objectName === '...'` checks exist in diff --git a/src/components/lists/DynamicList.tsx b/src/components/lists/DynamicList.tsx index dcfe558..96bcc53 100644 --- a/src/components/lists/DynamicList.tsx +++ b/src/components/lists/DynamicList.tsx @@ -503,6 +503,8 @@ export function DynamicList({ viewName }: DynamicListProps) { // (see withAccountListColumns, account-quota-usage-column deviation) // include the synthetic `quotaUsage` column gets it resolved and rendered. const hasQuotaUsageColumn = (resolved?.list?.columns ?? []).some((c) => c.name === 'quotaUsage'); + // SCHEMA-DEVIATION: account-alias-count-column (see SCHEMA_DEVIATIONS.md) + const hasAliasCountColumn = (resolved?.list?.columns ?? []).some((c) => c.name === 'aliasCount'); const displayColumns = useMemo(() => { const columns = resolved?.list?.columns ?? []; @@ -671,6 +673,12 @@ export function DynamicList({ viewName }: DynamicListProps) { properties.splice(quotaIdx, 1, 'usedDiskQuota', 'quotas'); } } + if (hasAliasCountColumn) { + const aliasIdx = properties.indexOf('aliasCount'); + if (aliasIdx !== -1) { + properties.splice(aliasIdx, 1, 'aliases'); + } + } if (isMailboxList && !properties.includes('parentId')) { properties.push('parentId'); } @@ -755,7 +763,18 @@ export function DynamicList({ viewName }: DynamicListProps) { setLoading(false); } }, - [resolved, schema, buildFilter, buildSort, isWebApplications, isAccountsList, hasQuotaUsageColumn, isMailboxList, activeClientFilters], + [ + resolved, + schema, + buildFilter, + buildSort, + isWebApplications, + isAccountsList, + hasQuotaUsageColumn, + hasAliasCountColumn, + isMailboxList, + activeClientFilters, + ], ); useEffect(() => { @@ -1680,6 +1699,8 @@ export function DynamicList({ viewName }: DynamicListProps) { formatUserRole(item, schema!) ) : hasQuotaUsageColumn && col.name === 'quotaUsage' ? ( renderQuotaUsage(item, t) + ) : hasAliasCountColumn && col.name === 'aliasCount' ? ( + Object.keys((item.aliases as Record) ?? {}).length ) : isMailboxList && col.name === 'name' ? ( (() => { const depth = mailboxDepths.get(item.id as string) ?? 0; diff --git a/src/lib/accountColumns.ts b/src/lib/accountColumns.ts index e3d801c..5933e85 100644 --- a/src/lib/accountColumns.ts +++ b/src/lib/accountColumns.ts @@ -19,6 +19,12 @@ import type { Schema } from '@/types/schema'; * The Groups list gets the same `quotaUsage` column (groups have their own * `usedDiskQuota`/`quotas`, same as users), but not `roles` — that column * is specific to the Users list. + * + * SCHEMA-DEVIATION: account-alias-count-column (see SCHEMA_DEVIATIONS.md) + * + * Both lists also get a synthetic `aliasCount` column — DynamicList + * resolves it from the real `aliases` objectList property (an id-keyed + * map, per JMAP's objectList wire format) and renders its entry count. */ export function withAccountListColumns(schema: Schema): Schema { let lists = schema.lists; @@ -29,13 +35,18 @@ export function withAccountListColumns(schema: Schema): Schema { ...userList.columns.filter((c) => c.name !== 'createdAt'), { name: 'roles', label: 'Role' }, { name: 'quotaUsage', label: 'Usage / Quota' }, + { name: 'aliasCount', label: 'Aliases' }, ]; lists = { ...lists, 'x:Account/User': { ...userList, columns } }; } const groupList = lists['x:Account/Group']; if (groupList) { - const columns = [...groupList.columns, { name: 'quotaUsage', label: 'Usage / Quota' }]; + const columns = [ + ...groupList.columns, + { name: 'quotaUsage', label: 'Usage / Quota' }, + { name: 'aliasCount', label: 'Aliases' }, + ]; lists = { ...lists, 'x:Account/Group': { ...groupList, columns } }; }