Skip to content

feat(matrix): make the business-role row view a matrix option (#370 on top of #937) - #1184

Closed
WimvandenHeijkant wants to merge 18 commits into
dor/issue-937from
feature/matrix-business-roles-optional
Closed

WimvandenHeijkant wants to merge 18 commits into
dor/issue-937from
feature/matrix-business-roles-optional

Conversation

@WimvandenHeijkant

Copy link
Copy Markdown
Contributor

Stacked on #1183 (dor/issue-937). Retarget to main once that merges.

What this is

The business-role row view built for #370 — roles as parent rows, their resources drawn and folded underneath, the BR chips, the deviation markers — becomes the opt-in half of #937's wizard checkbox, now called "Show business roles as foldable rows".

This replaces PR #933, which was written against a main that has since extracted accessPackageModel.js, MatrixCell.helpers.js and the row-cell components. The fold, the staircase promotion and the marker strip land in those instead of in the monolith they were written against. #933's implementation stays in the history of dor/issue-370 as the reference.

How the option is enforced

The gate is the data, not a pile of UI conditionals. With no business-role rows on the resource axis, analyseRoleRows finds nothing foldable, no row carries a roleParentId, and every affordance disappears on its own — fold chevrons, toolbar buttons, chips, "held outside" counts, the under-role export layout.

Two things could have been drawn from the SOLL columns alone, so they take an explicit showBusinessRoles:

  • the red + "more than the role assigns" mark, and
  • the legend entries for markers a default grid can never draw.

That makes "the default matrix is the old matrix" a property tests pin rather than an emergent one — see the new describe blocks in MatrixView.mount.test.jsx, MatrixGroupRow.mount.test.jsx, MatrixLegend.test.js and matrix-business-role-rows.spec.js.

Reconciling migration 061 with #937's decision 5

Per the scope amendment on #370. No flag of its own was needed: the coverage view's self arm stays unconditional (holding a business role is governed access), and the scope/governed counts follow the flag because the resource axis does — #937's choke point already excludes business-role resources from the pair counts. Pinned in scopeHistory.test.js from both directions.

Fix at the source, not the surface

Two things stayed unconditional on purpose, because they are not about business roles:

  • the resizable grid (useViewportFitHeight / useResizableGridHeight / GridResizeHandle) — it also fixes a page-overflow the current measurement still has;
  • the cell marker strip geometry, which stops markers overlapping their neighbours' badges.

One behaviour change reaches the default matrix: the Gaps row filter now uses the same comparison as the amber ! marker, so the Gaps view and the marker can no longer disagree. Previously the filter flagged rows the marker did not.

Tests

  • UI 1498 passed, API 2735 passed; both coverage suites green against their thresholds.
  • New mutation scope app/ui/stryker.roles.config.json for coverageDeviation.js + useBusinessRoleFold.js — measured 93.54% (333 killed / 21 survived / 2 unkillable-by-construction), floor 90, registered in the weekly workflow. The five presentational/measurement files that landed with the feature are excluded with written reasons.
  • The two ratchets written in Python (file length, complexity) could not be run locally — no working interpreter on this box — so CI is the first check on those.

🤖 Generated with Claude Code

IdentityAtlas DoR agent and others added 18 commits August 4, 2026 12:24
Brings 61 commits of main (up to ac657ec) onto the #370 fold branch so the
PR stops sitting on a 3-day-old base and picks up the bug fixes landed since.

Conflict resolution, by tier:

* .github/** — taken from main wholesale. The DoR agent had edited its own
  scripts during this build, but every one of those edits has since landed on
  main independently (#932, #936, #967), in several cases verbatim. Verified
  line-by-line that nothing unique was dropped: the only four lines absent from
  main were changed_summary's two-dot git range (main uses the better
  origin/main...HEAD) and the hardcoded sk:[sk3,sk5,…] fan-out matrix, which
  main deliberately replaced with derived runner labels + direct dispatch to
  the reservation holder (#950, #961).

* MatrixGroupRow.jsx / MatrixView.jsx — real reconciliation. Main's Contexts
  column (#924) and its extraction of getRoleBadge into the shared
  getApRoleBadge (#949) now coexist with the fold affordance: the local badge
  copy is dropped in favour of the shared helper, and the row renders the fold
  toggle in the name cell followed by main's MatrixContextsCell. Confirmed the
  fold's synthetic role-child rows inherit `contexts` for free — they spread
  existing row objects rather than building new ones — so the new column is
  populated on the rows this feature adds.

* matrix.spec.js / the mount tests — add/add of independent blocks; both sides
  kept. The e2e file was reassembled from the merge stages because both new
  describes opened with the same filter literal; verified main left the trailing
  scrollbar describe untouched, so the branch's edits to it survive intact.

* Demo dataset — parts list is the union (role-drift + shared-grants + volume).
  Main's #928 test pinned the default company at 39 resources; #370 grew it to
  46 (Verify-DemoDataset.ps1 already expected 46/7, and the doc's count tables
  already said so). Updated that pin and the three prose references main added
  that still said 39, so the dataset's documented and asserted counts agree.

Verified: API 2123/2123, UI 1086/1087, demo-dataset Pester 44/46, lint clean
(0 errors) on both packages. The one UI failure is a pre-existing en-NL locale
artifact on this machine (toLocaleString gives 1.500, the test pins 1,500); the
two Pester failures pre-date this merge and are fixed in the follow-up commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three pre-date the main merge (verified by running the suites against the
pre-merge branch tip) and none change product behaviour.

* DemoDataset: `$pid` is a read-only PowerShell automatic variable, so
  assigning to it threw before the assertion ever ran and the "one resource,
  two business roles" case was never actually checked. Renamed to
  `$principalId`. PSScriptAnalyzer flags this as PSAvoidAssignmentToAutomaticVariable.

* DemoDataset: the business-role count still pinned 5 after #370 added the
  role-drift and shared-grant roles. Verify-DemoDataset.ps1 and the doc's count
  table already said 7; this brings the last pin into line.

* MatrixScopePanel: the tile-naming test hardcoded '1,500' while the panel
  formats with toLocaleString() and no explicit locale, so it passed only on an
  en-US runner and failed on e.g. en-NL. Asserts against the same formatter now.

Verified: demo-dataset Pester 46/46, UI 1087/1087, API 2123/2123.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The branch added 061_business_role_covers_itself.sql but never ran the
record step every migration-adding PR needs, so the gate reported "new
migration not recorded". It went unnoticed because the workflow had been
sitting at action_required on every prior push and had never actually run.

Additive-only, as the ratchet requires: one line appended, no existing hash
touched. Generated with a Node stand-in for tools/migrations/ratchet.py
--update (no Python on this machine) that mirrors its hashing and output
byte-for-byte; verified the gate's own check logic now passes over all 63.

Note this makes 061 immutable: any further schema change coming out of
functional testing needs a new 062 rather than an edit to 061.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two CI failures the main merge finally let run. Neither was caused by
the merge; both are the collision of #370's new row model with definitions
that lived somewhere else.

Excel export (main's #949 e2e, "exported cells disagree with the grid"):
the export still read `orderedGroups`, the row list from before business
roles adopt their resources, so it had one row per resource while the grid
now draws a resource under every role that grants it. The export now comes
from the same buildRoleLayout() the grid uses — no second row model to
drift — but is built with an empty fold set, so it mirrors the grid's
structure without inheriting its fold state. Folding is a reading aid; an
access-review artifact must never quietly lose a resource because someone
collapsed a role before hitting Export. That is what the hook's own header
comment already claimed ("never what is fetched, counted or exported") and
is now actually true.

Governed history (integration, ts G=75 vs live G=124): migration 061 gave
vw_UserPermissionAssignmentViaBusinessRole a second arm — holding a
business role is itself governed access — but scopeHistory.js rebuilds that
definition in SQL rather than reading the view, so 061 never reached it.
Live counted the role-membership cells as governed, history did not, and
the latest timeseries point disagreed with live scope-stats for the same
instant. Added the matching arm, mirroring 061 exactly. Historical governed
percentages rise: they were understating governance, not misreporting data.

Both paths still restate one definition in two places, which is what let
this drift. Worth collapsing later; noted rather than done here so the fix
stays reviewable.

Tests: hook-level (export ignores folds, row identity stays usable for cell
lookups), mount-level (Export after "Fold roles" still receives the folded
resources), and SQL-shape parity for 061's arm. UI 1090/1090, API 2124/2124,
lint 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e cell

The export e2e (#949) scraped the row name out of the name cell's innerText,
stripping only a leading expander glyph. #370 put more in that cell — the
fold toggle, the nesting elbow, and the "BR"/"BR+3" overlap chips — so the
scrape started returning "SG-Monitoring-Tools\nBR" and every row failed to
pair with its export row. The export itself was already correct; the reader
was reading the affordances as part of the name. (The multi-line names are
also why the failure listed phantom "BR (Group)" rows: one wrapped string.)

Gives the display name an element of its own (`data-row-name`) and reads
that, so anything else the name cell grows later can't be mistaken for the
resource name again. A mount test locks the contract the e2e depends on.

UI 1091/1091, lint 0 errors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…937)

The business-role row view built for #370 — roles as parent rows, their
resources folded underneath, the BR chips and the deviation markers — is
now the opt-in half of #937's "Show business roles as foldable rows".
Ticked, a matrix gets the whole layer; left alone (the default) it is the
matrix that was there before, minus the business-role rows #937 removed.

The gate is the data, not a pile of UI conditionals: with no business-role
rows on the resource axis, analyseRoleRows finds nothing foldable, no row
carries a roleParentId, and every affordance disappears on its own. Only
the two things that could be drawn from the SOLL columns alone — the
over-grant mark and the legend entries for markers the grid cannot draw —
take an explicit showBusinessRoles, so "the default matrix is the old
matrix" is a property tests pin rather than an emergent one.

Migration 061 is reconciled with #937's decision 5 without a flag of its
own: the coverage view's self arm stays unconditional, and the scope
counts follow the flag because the resource axis does.

Rebuilt on top of #937 rather than merged as-is: main had since extracted
accessPackageModel.js, MatrixCell.helpers.js and the row-cell components,
so the fold, the staircase promotion and the marker strip land in those
instead of in the monolith they were written against.

Adds app/ui/stryker.roles.config.json for coverageDeviation.js and
useBusinessRoleFold.js — measured 93.54%, floor 90.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@WimvandenHeijkant

Copy link
Copy Markdown
Contributor Author

Folded into #933, which is now the single PR for this work — #937 and #370 were merged into one item at the requestor's request. Same tree, rebased onto current main; this branch is no longer needed.

@WimvandenHeijkant
WimvandenHeijkant deleted the feature/matrix-business-roles-optional branch September 12, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy-to-sidekick Deploy this PR's branch to a dor-build sidekick for functional testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant