Skip to content

feat(activity): Activity journal on the redesign layout (#905) - #933

Merged
alex-dembele merged 6 commits into
masterfrom
feat/905-activity
Oct 12, 2026
Merged

alex-dembele merged 6 commits into
masterfrom
feat/905-activity

Conversation

@alex-dembele

Copy link
Copy Markdown
Member

Sixth screen of the redesign epic (#899). Stacked on #932 (financial): the base is feat/904-financial, so the diff shows only this issue.

What changes for the user

Activity is now the organisation's journal from the mockup:

  • Domain pills: Tout, Risques, Incidents, Vulnérabilités, Mitigations, Preuves, Conformité, Rapports.
  • Days grouped as Aujourd'hui, Hier, then the date, on a vertical rail.
  • One sentence per entry, e.g. "System Administrator a changé le statut de la vulnérabilité Contournement d'authentification — passerelle API", with the time on the right. System entries show the OpenRisk mark.
  • Objects with a drawer open it in place; others link to their page.
  • Older entries load on scroll or with the button.

The dashboard's "Activité récente" card reads the same sentences.

Backend

  • /timeline events carry domain and object, and ?domain= filters inside the query. The object comes from the record's title or name when the audit row carries one, otherwise from the name the summary quotes. Ids and bare numbers are never shown as names.
  • Visibility change (tenant scoping untouched): mitigations, reports and frameworks have no drawer, so before this only audit-permission holders saw their events. They now follow the feed's own rule, "you see an event if you may read what it is about". The gate is the permission that already guards each page: mitigations:read, compliance:controls:read, reports:board:read. Governance records stay behind the audit permission. Flagging it here because it touches who sees what.
  • Technical rows are left out of the journal for everyone and stay in the audit trail: onboarding celebrations, notification reads, unnamed routes.
  • One mutation is recorded twice, by the HTTP middleware and by the model hook. The journal shows it once: it keeps the model row (which carries the record) and moves the actor over from the HTTP twin.
  • Actors read by display name (NamesByIDs), falling back to the email.
  • Fix found in the live pass: GormAuditChainRepository, the one production reads, ignored AuditEventFilter.EntityTypes. The domain filter came back unfiltered. The per-entity timeline asks for two names of one type (compliance_control + control), so it also matched any other type sharing the id. That matters for incidents, whose ids are sequential. Unit tests used a fake that honoured the list; a repository test now covers the real one.

Verified

  • go test ./...: all packages pass. New tests:
    • TestJournal_Success / _NotFound (unknown domain) / _Unauthorized (per-page permission).
    • Domain pushed into the query, twin merge keeps the actor, TestObjectLabel, names lookup.
    • TestAuditChainList_FiltersByEntityTypes: it failed before the fix.
  • npx tsc -b --noEmit clean, eslint clean on the touched files, npx vitest run 115 files / 1041 tests. One run had a flake in shared/ds Menu, untouched here; it passed 3 times alone.
  • Live on Postgres with the parity dataset, at 1440×900 in dark and light, side by side with the design:
    • ?domain=incident returned 5 incidents (10 audit rows before the merge), each with its title and the person who created it.
    • The button loaded 30 → 60 entries.
    • The Incidents pill filtered.
    • Clicking an incident opened its drawer at /activity?drawer=incident&entity=5.

Deliberate differences from the mockup

  • Objects are names, not references (INC-2026-033, R-0115). Those references don't exist yet; the same gap applies on the risk register.
  • Updates read "a modifié" or "a changé le statut de". The mockup's "a relevé la probabilité de 2 à 3" needs before/after values, which the feed deliberately leaves to the audit trail, behind its own permission.
  • The subtitle avoids the mockup's gendered "autorisée".

Not done here

  • /timeline is not in docs/openapi.yaml at all. Documenting it is a separate docs task.
  • The live check used the admin account only. The per-permission visibility is covered by tests.

Closes #905

)

The redesigned Activity page reads "Fatou Ndiaye a créé la mitigation
Bastion d'administration (PAM)" and filters by Risques, Incidents,
Vulnérabilités, Mitigations, Preuves, Conformité and Rapports. The feed
now gives it what that takes:

- Each event names its domain and its object. The object is the record's
  title or name when the audit row carries it, else the name the summary
  quotes; ids and bare numbers are not names.
- GET /timeline?domain=… filters by domain inside the query, so a
  filtered page is full rather than over-read and thinned.
- Mitigations, reports and frameworks have no drawer and were shown only
  to audit-permission holders. They now follow the feed's own rule (you
  see an event if you may read what it is about), gated by the permission
  that guards their pages: mitigations:read, compliance:controls:read,
  reports:board:read. Governance records stay behind the audit permission.
- Technical rows (onboarding celebrations, notification reads, unnamed
  routes) are left out of the journal for everyone; the audit trail keeps
  them.
- One mutation recorded by both the HTTP middleware and the model hook
  shows once, from the model row that carries the record.
- Actors read by display name when the user has one, email otherwise.

Tests: journal Success / NotFound (unknown domain) / Unauthorized (per
page permission), domain pushed into the query, twin merge, object
labels, names. Existing timeline tests unchanged and green.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
The audit-chain repository, which every timeline reads in production,
ignored AuditEventFilter.EntityTypes. The Activity page's domain filter
came back unfiltered, and an entity timeline that asks for two names of
one type (compliance_control and control) also matched any other type
sharing the id. Incidents use sequential ids, so "incident 5" could pick
up another type's row numbered 5. The older audit repository applied
the list; the chain one now does too. The timeline unit tests use a fake
that honoured the list, which is why they never failed; a repository
test now covers it.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
The journal keeps the model hook's row of a mutation and drops the HTTP
middleware's twin. The model row can carry no actor, so incidents read
"OpenRisk a créé l'incident …" when a person had created them. The
actor now moves from the dropped twin onto the kept row. Approval
entries also name the request rather than the circuit step ("approve
step "Comité" of "Prolongation…""), and "submitted … for approval: X"
yields X.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
…sign (#905)

The organisation's journal as the mockup lays it out: title and
subtitle, the Tout / Risques / Incidents / Vulnérabilités / Mitigations
/ Preuves / Conformité / Rapports pills (a server-side filter), entries
grouped by day (Aujourd'hui, Hier, then the date) on a vertical rail,
each with its avatar, the actor's name, the verb, the object in mono and
the time on the right. System entries carry the OpenRisk mark. Older
entries load when the end comes into view, or with the button.

Each entry is one sentence ("Fatou Ndiaye a créé la mitigation
Bastion d'administration (PAM)", "a changé le statut de la
vulnérabilité …"), built from the event's domain, verb and object in
fr/en. An object with a drawer opens it in place; others link to their
page. The dashboard's recent-activity card now reads the same sentence.

The previous page (W1-02 GlobalTimelinePage) and its create/update/
delete filter are replaced. The subtitle avoids the mockup's gendered
"autorisée".

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
GET /timeline and GET /entities/{type}/{id}/timeline were missing from
the contract. Both are now described with their shared paging and
filter parameters (cursor, limit, kind, actor_id, since, until), the
tenant feed's domain filter, and the TimelinePage / TimelineEvent
schemas, including domain and object. The description states who sees
what: drawer permission, page permission for mitigations, reports and
frameworks, audit permission for governance records. TS types
regenerated. Redocly reports the same 60 errors and 28 warnings as
before, none on these paths.

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
#899)

Signed-off-by: alex-dembele <alexandredembele16@gmail.com>
Base automatically changed from feat/904-financial to master October 12, 2026 07:34
@alex-dembele alex-dembele reopened this Oct 12, 2026
@alex-dembele
alex-dembele merged commit 3e34dcb into master Oct 12, 2026
14 of 26 checks passed
@alex-dembele alex-dembele self-assigned this Oct 12, 2026
@alex-dembele
alex-dembele deleted the feat/905-activity branch October 12, 2026 07:42
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.

feat(frontend): activity feed on the redesign layout

1 participant