Skip to content

feat(activity): enrich activities with iam User display names#193

Open
mattdjenkinson wants to merge 1 commit intomainfrom
feat/activity-display-names-backend
Open

feat(activity): enrich activities with iam User display names#193
mattdjenkinson wants to merge 1 commit intomainfrom
feat/activity-display-names-backend

Conversation

@mattdjenkinson
Copy link
Copy Markdown
Contributor

Summary

Enriches activity records with display names looked up from iam User CRs, so the UI can render human names with email/UID available on hover. This is the backend half of the work previously bundled in #192; the matching UI changes ship in a separate PR.

What changes

  • New UserResolver interface in internal/processor with TTL + single-flight CachedUserResolver and a NoopUserResolver for tests
  • New IAMUserResolver in internal/activityprocessor that fetches iam.miloapis.com/v1alpha1 Users via controller-runtime + Unstructured
  • ActivityActor gains DisplayName (omitempty); ActivityLink gains DisplayName and Email (both omitempty)
  • ResolveActorWithResolver(ctx, user, resolver) populates the actor's DisplayName for human users (falls back to email when no User record found)
  • After CEL link() evaluation, User-typed links are hydrated with display name + email
  • Summary post-processing replaces the actor's email with their display name when available
  • EvaluateAuditRulesWithResolver / EvaluateEventRulesWithResolver overloads thread the resolver through; the existing non-resolver entrypoints remain backwards-compatible
  • processor.go wires NewCachedUserResolver(NewIAMUserResolver(k8sClient), 0, 0) (defaults: 5m positive, 30s negative TTL)

Tests

  • internal/processor/userlookup_test.go covers cache hit, negative cache, TTL expiry, single-flight collapse, error not cached, empty-key short-circuit
  • internal/processor/enrichment_test.go covers actor email replacement, link upgrade, and User-typed link hydration

Notes

  • TS types in ui/ already declare displayName/email as optional, so the UI PR can ship independently — fields stay undefined until this lands
  • Resolver errors are silently dropped: activities still emit without enrichment

Test plan

  • CI green
  • Deploy to staging and verify a fresh activity (e.g. machine account create) has spec.actor.displayName populated and User-typed links carry displayName + email
  • Verify a user with no givenName/familyName falls back to email cleanly

Resolve actors and User-typed link targets to human-readable
display names so the UI can show names instead of raw emails / UIDs.

- ActivityActor gets a new omitempty DisplayName field; ActivityLink
  gets DisplayName + Email (populated only when the link's resource
  is an iam User).
- New UserResolver interface in internal/processor/userlookup.go
  with NoopUserResolver and a CachedUserResolver wrapper (TTL,
  single-flight, negative caching).
- ResolveActorWithResolver populates the actor's DisplayName from
  the iam User's spec.givenName + spec.familyName via the resolver.
- ActivityBuilder gains a UserResolver and rewrites the summary at
  build time: substitutes actor.Name (typically email) with
  actor.DisplayName, upgrades existing actor link() entries in
  place (or appends a synthetic actor link), and hydrates User-typed
  link targets via the resolver.
- *WithResolver overloads on Evaluate{Audit,Event}Rules and
  EvaluateCompiledAuditRules let callers opt into enrichment without
  changing the existing public API.
- IAMUserResolver in internal/activityprocessor uses a
  controller-runtime client to fetch iam.miloapis.com/v1alpha1/User
  CRs as Unstructured (no milo Go-types dependency).
- The processor wires NewCachedUserResolver(NewIAMUserResolver(...))
  at startup; resolver errors are silently ignored, so the activity
  is still emitted with raw values when lookup fails.

All new optional fields are backwards compatible: consumers that
don't know about DisplayName continue to work, and once they
recognise it they get richer data automatically.

Tests:
- internal/processor/userlookup_test.go: cache hit, neg-cache,
  TTL expiry, single-flight collapse, error-not-cached, empty-key
  short-circuit.
- internal/processor/enrichment_test.go: actor replacement, link
  upgrade in place, actor-not-in-summary, user link hydration,
  non-user link untouched, ResolveActorWithResolver paths.
@mattdjenkinson mattdjenkinson requested a review from scotwells May 1, 2026 16:15
mattdjenkinson added a commit that referenced this pull request May 1, 2026
## Summary

Rebuilds the activity UI to consume `@datum-cloud/datum-ui` as a peer
dependency and converts the activity feed, events, and audit views to
table + timeline layouts with manual pagination. This is the **UI half**
of the work previously bundled in #192; the matching backend
display-name enrichment ships in #193.

## What changes

**Design system migration**
- Promote `@datum-cloud/datum-ui` to a peer dependency
- Replace local shadcn primitives with datum-ui exports where possible
- Drop Tailwind/PostCSS configs and unused local components
- Externalize peer deps in `rollup.config.mjs`
- Update `Dockerfile` to drop postcss/tailwind copies

**Layout refactor**
- Convert `ActivityFeed`, `EventsFeed`, `AuditLogQueryComponent` to
table/timeline views
- Manual "Load more" pagination (replaces auto-scroll observer that was
misfiring on tab switches)
- Add `Timestamp` component using `Intl.DateTimeFormat` for
timezone-aware rendering with a single tooltip
- Add reusable details-panel primitives in `components/details.tsx`;
apply across activity/audit/event expanded views
- Truncation with tooltips on summary/notes columns
- Tooltip shim renders Radix Tooltip directly (mirroring datum-ui
styles) so flex truncation actually engages
- Normalize toolbar styling, filter chips, and details-panel padding
across feeds
- Remove "unsaved changes" badge

**Display name support**
- Mirror `displayName` / `email` (both optional) on `Actor` and
`ActivityLink` TS types
- Render `actor.displayName ?? actor.name` with email/UID hover;
user-typed link markers swap in `displayName` as visible text
- Falls back cleanly when backend hasn't populated the new fields, so
this PR can land independently of #193

## Tooltip / truncation notes

datum-ui's Tooltip wraps the trigger in a `relative inline-flex` span
which broke parent flex truncation. To keep datum-ui's visual styling,
the local Tooltip in `ui/tooltip.tsx` re-implements Radix Tooltip
directly with the same class tokens — same look, no flex-chain breakage.

<img width="1502" height="829" alt="Screenshot 2026-05-01 at 16 27 29"
src="https://github.com/user-attachments/assets/57a564c5-dfd3-4d8c-91f8-3e5bc46367ba"
/>
<img width="1624" height="1061" alt="Screenshot 2026-05-01 at 13 55 32"
src="https://github.com/user-attachments/assets/716e691e-3b80-4928-a139-54aafd2839fe"
/>
<img width="1624" height="1061" alt="Screenshot 2026-05-01 at 13 55 40"
src="https://github.com/user-attachments/assets/53e7ff60-8132-4645-bb13-d4d989190e4f"
/>
<img width="1624" height="1061" alt="Screenshot 2026-05-01 at 13 55 54"
src="https://github.com/user-attachments/assets/9c013a99-3e0a-4120-aa14-b69126510632"
/>
<img width="1624" height="1061" alt="Screenshot 2026-05-01 at 13 56 07"
src="https://github.com/user-attachments/assets/38bed23d-40a1-4a05-949d-d9871cc39c1f"
/>
<img width="1624" height="1061" alt="Screenshot 2026-05-01 at 14 09 47"
src="https://github.com/user-attachments/assets/a8f94d88-5038-4cfe-83b9-19ed75a14ab7"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant