Repository navigation
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
Closed
WimvandenHeijkant wants to merge 18 commits into
WimvandenHeijkant wants to merge 18 commits into
Conversation
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>
Contributor
Author
WimvandenHeijkant
deleted the
feature/matrix-business-roles-optional
branch
September 12, 2026 12:46
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1183 (
dor/issue-937). Retarget tomainonce that merges.What this is
The business-role row view built for #370 — roles as parent rows, their resources drawn and folded underneath, the
BRchips, 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
mainthat has since extractedaccessPackageModel.js,MatrixCell.helpers.jsand 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 ofdor/issue-370as 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,
analyseRoleRowsfinds nothing foldable, no row carries aroleParentId, 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:+"more than the role assigns" mark, andThat makes "the default matrix is the old matrix" a property tests pin rather than an emergent one — see the new
describeblocks inMatrixView.mount.test.jsx,MatrixGroupRow.mount.test.jsx,MatrixLegend.test.jsandmatrix-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.jsfrom both directions.Fix at the source, not the surface
Two things stayed unconditional on purpose, because they are not about business roles:
useViewportFitHeight/useResizableGridHeight/GridResizeHandle) — it also fixes a page-overflow the current measurement still has;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
app/ui/stryker.roles.config.jsonforcoverageDeviation.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.🤖 Generated with Claude Code