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.
This commit is contained in:
@@ -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')`).
|
- **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).
|
- **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)
|
## Not a deviation (for reference)
|
||||||
|
|
||||||
A few other `viewName === '...'` / `objectName === '...'` checks exist in
|
A few other `viewName === '...'` / `objectName === '...'` checks exist in
|
||||||
|
|||||||
@@ -503,6 +503,8 @@ export function DynamicList({ viewName }: DynamicListProps) {
|
|||||||
// (see withAccountListColumns, account-quota-usage-column deviation)
|
// (see withAccountListColumns, account-quota-usage-column deviation)
|
||||||
// include the synthetic `quotaUsage` column gets it resolved and rendered.
|
// include the synthetic `quotaUsage` column gets it resolved and rendered.
|
||||||
const hasQuotaUsageColumn = (resolved?.list?.columns ?? []).some((c) => c.name === 'quotaUsage');
|
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 displayColumns = useMemo(() => {
|
||||||
const columns = resolved?.list?.columns ?? [];
|
const columns = resolved?.list?.columns ?? [];
|
||||||
@@ -671,6 +673,12 @@ export function DynamicList({ viewName }: DynamicListProps) {
|
|||||||
properties.splice(quotaIdx, 1, 'usedDiskQuota', 'quotas');
|
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')) {
|
if (isMailboxList && !properties.includes('parentId')) {
|
||||||
properties.push('parentId');
|
properties.push('parentId');
|
||||||
}
|
}
|
||||||
@@ -755,7 +763,18 @@ export function DynamicList({ viewName }: DynamicListProps) {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[resolved, schema, buildFilter, buildSort, isWebApplications, isAccountsList, hasQuotaUsageColumn, isMailboxList, activeClientFilters],
|
[
|
||||||
|
resolved,
|
||||||
|
schema,
|
||||||
|
buildFilter,
|
||||||
|
buildSort,
|
||||||
|
isWebApplications,
|
||||||
|
isAccountsList,
|
||||||
|
hasQuotaUsageColumn,
|
||||||
|
hasAliasCountColumn,
|
||||||
|
isMailboxList,
|
||||||
|
activeClientFilters,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1680,6 +1699,8 @@ export function DynamicList({ viewName }: DynamicListProps) {
|
|||||||
formatUserRole(item, schema!)
|
formatUserRole(item, schema!)
|
||||||
) : hasQuotaUsageColumn && col.name === 'quotaUsage' ? (
|
) : hasQuotaUsageColumn && col.name === 'quotaUsage' ? (
|
||||||
renderQuotaUsage(item, t)
|
renderQuotaUsage(item, t)
|
||||||
|
) : hasAliasCountColumn && col.name === 'aliasCount' ? (
|
||||||
|
Object.keys((item.aliases as Record<string, unknown>) ?? {}).length
|
||||||
) : isMailboxList && col.name === 'name' ? (
|
) : isMailboxList && col.name === 'name' ? (
|
||||||
(() => {
|
(() => {
|
||||||
const depth = mailboxDepths.get(item.id as string) ?? 0;
|
const depth = mailboxDepths.get(item.id as string) ?? 0;
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ import type { Schema } from '@/types/schema';
|
|||||||
* The Groups list gets the same `quotaUsage` column (groups have their own
|
* The Groups list gets the same `quotaUsage` column (groups have their own
|
||||||
* `usedDiskQuota`/`quotas`, same as users), but not `roles` — that column
|
* `usedDiskQuota`/`quotas`, same as users), but not `roles` — that column
|
||||||
* is specific to the Users list.
|
* 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 {
|
export function withAccountListColumns(schema: Schema): Schema {
|
||||||
let lists = schema.lists;
|
let lists = schema.lists;
|
||||||
@@ -29,13 +35,18 @@ export function withAccountListColumns(schema: Schema): Schema {
|
|||||||
...userList.columns.filter((c) => c.name !== 'createdAt'),
|
...userList.columns.filter((c) => c.name !== 'createdAt'),
|
||||||
{ name: 'roles', label: 'Role' },
|
{ name: 'roles', label: 'Role' },
|
||||||
{ name: 'quotaUsage', label: 'Usage / Quota' },
|
{ name: 'quotaUsage', label: 'Usage / Quota' },
|
||||||
|
{ name: 'aliasCount', label: 'Aliases' },
|
||||||
];
|
];
|
||||||
lists = { ...lists, 'x:Account/User': { ...userList, columns } };
|
lists = { ...lists, 'x:Account/User': { ...userList, columns } };
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupList = lists['x:Account/Group'];
|
const groupList = lists['x:Account/Group'];
|
||||||
if (groupList) {
|
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 } };
|
lists = { ...lists, 'x:Account/Group': { ...groupList, columns } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user