[Design Discussion] User Display Attribute for User Types #1510
Replies: 4 comments 8 replies
-
|
Are we planning to move the USER_SCHEMAS table into the user DB from the config DB? The JOIN operation doesn't support cross-DB unless they are in the DB connection/instance which may bring more complexity and sqlite cannot support this AFAIK. Can we also consider onboarding a batch fetch query support for user/group services ( e.g. userService.GetUsersByIDs) . If we do the N+1 issue of roles assignment endpoint can be fixed to fetch the display names. Also having a cache-backed store may not make much sense if we fix this N+1 issue. WDYT? |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
On a side note, isn't it better to include the profile picture/ logo image too with this to improve UX? We may not support this for the moment, but wanted to know our stance on this |
Beta Was this translation helpful? Give feedback.
-
|
Had a discussion with @darshanasbg regarding this matter. The solution we came up with as follows, the schemas will have a For display purposes, we will be using the attribute defined in the |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Related Feature Issue
#1511
Problem Summary
Thunder allows creating user types (schemas) that define the structure of user attributes. However, when listing users across any endpoint (user list, OU users, group members, role assignments), users are identified only by their UUID. This makes it impossible to meaningfully identify users in the UI or API responses without making additional per-user requests.
For example, a typical user list response currently looks like:
{ "users": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, { "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210" } ] }There is no way to show a human-readable identifier (like a name or email) without fetching each user individually — an N+1 problem that degrades both UX and performance.
High-Level Approach
1. Display Attribute on User Schema
Each user schema gains a
displayAttributefield — the name of one top-level string attribute designated as the human-readable identifier for users of that type.Constraints:
stringattribute in the schemaAPI surface:
{ "id": "schema-123", "name": "employee", "displayAttribute": "email", "schema": { "email": { "type": "string", "required": true, "unique": true }, "department": { "type": "string" }, "password": { "type": "string" } } }2. Opt-in Display Resolution via
include=displayAll user listing endpoints gain an optional
include=displayquery parameter. When present, the response includes adisplayfield alongside each user ID:{ "users": [ { "id": "a1b2c3d4-...", "display": "[email protected]" }, { "id": "f9e8d7c6-...", "display": "[email protected]" } ] }Without the parameter, responses remain unchanged (fully backward compatible).
3. SQL JOIN for Display Resolution (No N+1)
Display values are resolved via a single SQL JOIN at the store layer — the
USERtable is JOINed withUSER_SCHEMASto get theDISPLAY_ATTRIBUTEcolumn name, and the actual value is extracted from the user's JSON attributes in Go. This eliminates the N+1 problem entirely.4. Cache-Backed User Schema Store
Following the existing
cache_backed_storepattern (used byapplication,flow/mgt,certpackages), a cache layer is added around the user schema store. Schema lookups use TTL-based caching since schemas rarely change. This is especially beneficial for the role assignment display resolution path, which resolves display per-user via cached schema lookups.5. Fix Role Assignment Display
The existing role assignment
include=displayfeature currently returns the user ID for user-type assignments (seegetDisplayNameForAssignmentinrole/service.go). With this change, it will resolve the actual display attribute value using the cached schema service.Architecture Overview
Affected Endpoints
GET /users?include=displaysupportGET /users/tree/{path}?include=displaysupportGET /organization-units/{id}/users?include=displaysupportGET /organization-units/tree/{path}/users?include=displaysupportGET /groups/{id}/members?include=displaysupportGET /roles/{id}/assignmentsinclude=display)POST /user-schemasdisplayAttributefieldPUT /user-schemas/{id}displayAttributefieldGET /user-schemas,GET /user-schemas/{id}displayAttributefieldDatabase Change
Add
DISPLAY_ATTRIBUTE VARCHAR(100)column to theUSER_SCHEMAStable (nullable for backward compatibility with existing schemas).Model Changes
Add optional
Display stringfield (withjson:"display,omitempty") to existing structs:user.Userou.Usergroup.MemberThe
omitemptytag ensures the field is omitted from JSON wheninclude=displayis not used, maintaining full backward compatibility.Display Resolution Strategies by Endpoint
Cache Architecture
GetUserSchemaByNameandGetUserSchemaByIDCreate,Update,Deletecache.GetCache[T]()framework with TTLValidation Rules
password,pin,secret,passkey)displayAttributeis empty; validate if provideddisplayAttributeis required; must be in eligible setFrontend Changes
?include=displayto API, show display value as primary columndisplayAttributeto schema types,displayto user typesSecurity Considerations
displayAttributefield is sanitized viasysutils.SanitizeString()in the handler layerImpacted Areas
Backend
backend/internal/userschema/— Model, store, service, handler, cache-backed store, declarative resource, error constantsbackend/internal/user/— Model, store, service, handler, store constantsbackend/internal/ou/— Model, store, service, handler, store constantsbackend/internal/group/— Model, store, service, handler, store constantsbackend/internal/role/— Service (display fix), init (new dependency)backend/internal/system/servicemanager/— Wiring for new dependencybackend/dbscripts/thunderdb/— SQLite and PostgreSQL schema scriptsAPI Specifications
api/user.yaml,api/ou.yaml,api/group.yaml,api/role.yamlFrontend
Tests
include=displayacross all listing endpointsAlternatives Considered
1. N+1 Schema Lookup (Per-User Service Call)
Approach: For each user in a list, call
userSchemaService.GetDisplayAttributeByName(user.Type)then extract the value.Rejected because: Even with caching, this requires N JSON unmarshal operations at the service layer and doesn't leverage the database's ability to resolve the attribute name in the same query. The JOIN approach is more efficient and keeps display resolution contained in the store layer.
2. Denormalized Display Column on USER Table
Approach: Store the resolved display value directly in the
USERtable as a separate column, updated on every user create/update.Rejected because: Requires triggers or application-level logic to keep the denormalized value in sync. If the display attribute is changed on the schema, all users of that type would need to be updated. The JOIN approach is simpler and always reflects the current schema configuration.
3. Always Include Display (No Opt-in)
Approach: Always return the display value in list responses without requiring
include=display.Rejected because: This would break backward compatibility for API consumers that parse responses strictly. The opt-in pattern is already established with role assignments and is the safer approach.
4. Separate Display Endpoint
Approach: Create a new
/users/displayendpoint that returns display mappings.Rejected because: Requires an extra API call from the frontend. The
include=displayquery parameter pattern is more ergonomic and consistent with the existing role assignment pattern.Questions for Community Input
Fallback behavior: When a user's display attribute value is empty/null, should we fall back to the user ID, or show an empty string? Current proposal: fall back to user ID.
Existing schemas migration: For existing schemas without a
displayAttribute, should we auto-infer it at read time (if exactly one eligible string attribute exists), or require explicit migration?Display in single-user GET: Should
GET /users/{id}also supportinclude=display, or is it only needed for list endpoints? (The full user response already contains all attributes.)Multiple
includevalues: Should theincludeparameter support comma-separated values for future extensibility (e.g.,include=display,attributes)?Beta Was this translation helpful? Give feedback.
All reactions