Matrix report: show what a relation means, and filter by it - #885
Merged
Conversation
The relation attribute editor only rendered single_select pickers, so any boolean dimension declared in a relation type's attributes_schema was invisible and could never be set — the seeded CRUD flags on Application/Business Process to Data Object among them. Add a boolean branch driven off the schema (nothing is keyed to a particular attribute), surface those values as relation badges, and widen the editor gate accordingly: gating on single_select alone is what made flag-only relation types uneditable. Also fixes three Matrix report labels that rendered raw translation keys, and lets a report that supplies its own buildExportData offer Excel export even without a table view — the Matrix has real tabular data but no table toggle, so XLSX was unreachable for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
The matrix endpoint collapsed every link into a deduplicated set of card-id pairs, discarding the relation type, its orientation and its attributes. A cell could therefore only ever say "something connects these two" — never what, nor how many, since the set also meant a pair with three relations counted as one. Each intersection now carries its edges: relation type, orientation relative to the row axis, and the attribute values declared by that relation type's schema. Distinct attribute bags are interned and referenced by index, so the payload grows with the variety of the values rather than the volume of the relations. Relation-type, attribute-value and direction filters are pushed into SQL. Only edges are filtered; the card lists always ship complete, because pruning cards server-side would break the parent chains the client needs to rebuild the hierarchy. Cards and relations are fetched with column-level selects and a join on the endpoint types, replacing two IN lists that grew with the landscape. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
The matrix could say a link existed but never what it meant. Cells now have two more display modes — compact codes in the dense grid, or readable labels in a widened one — and a filter bar narrows the grid to relations of a given type, direction or attribute value, which also hides the cards that no longer match. Everything shown, filtered and exported is derived from the relation type's own attributes_schema at runtime. No attribute is named in code, so a dimension an admin adds today gets a glyph, a colour, a legend entry, a filter and an export column with no code change. flowDirection gets no special case either: it is a select like any other, and structural direction — which end of the relation the row card sits on — is carried per edge instead. Also adds coverage-gap metrics and a gaps-only view, row/column search, axis transpose, a two-sheet Excel export (the grid, and a row per relation with its values), and column-header tooltips. The four full-grid passes behind the totals and the heat scale collapse into one pass over the edges, which the per-cell value work made necessary; the now-unused aggregateCount goes with them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
Adds the guide sections for the value cell displays, the relation filter, gap analysis, search and transpose, and the two-sheet Excel export, in all ten locales. The demo interface relations carried no flow direction, so the Application x Interface provider/consumer story had nothing to show: the first application listed against an interface now publishes it and the rest consume it. The CRUD and usage-type demo data already existed. Uncoloured relation values are now assigned a colour by their position within their relation type rather than by hashing the key. Values of one relation type are read side by side, and the hash gave two of the four CRUD flags near-identical reds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
A card with sub-cards was only a heading spanning them, with no row or column of its own, so a relation attached to the card itself had nowhere to land. The grid dropped it, while the totals, the coverage figure and the hide-unrelated toggle — which read raw card ids — still counted it: the card appeared on the axis, the intersection stayed blank, and the numbers did not add up. In the demo dataset that hid 17 of the 25 Organization-Application relations. Such a card now gets a line of its own labelled "(itself)" under its heading, and a collapsed group counts its own relations along with its children's. Hiding unrelated cards no longer drops a card whose only relations are its own, either — that test looked at descendants and never at the card itself. Also restores relation coverage the endpoint lost in 2.33.0: it had begun matching on the relation types the metamodel declares for the axis pair, which silently discarded relations left behind by a renamed type, brought in by an import, or declared for another pair, and emptied the grid entirely for a pair the metamodel says nothing about. What belongs in the grid is decided by the cards a relation connects. buildCellMatrix now counts the edges it cannot place, so the invariant that every relation lands in a cell is asserted rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY
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.
Summary
Implements discussion #883: the Matrix report can now show a relation's values in the cell and be filtered by relation type, direction and attribute value, instead of only saying that a link exists. Along the way it fixes three bugs that made relation data invisible — boolean relation attributes could never be set, relations attached to a card that has children never reached a cell, and several relations between the same pair counted as one.
Type
Changes
Everything the grid shows, filters and exports is derived from each relation type's
attributes_schemaat runtime. No attribute key appears anywhere in the code, so a dimension an admin adds gets a glyph, colour, legend entry, filter and export column with no code change.flowDirectiongets no special case either — it is a select like any other, and structural direction (which end of the relation the row card sits on) is carried per edge instead.Backend —
app/api/v1/reports.py[relationTypeIndex, orientation, attributeSetIndex]per underlying relation. Distinct attribute bags are interned and referenced by index, so the payload grows with the variety of the values rather than the volume of the relations (22 demo relations → 8 bags).relation_types,attranddirectionquery params, pushed into SQL as JSONB predicates. Only edges are filtered — the card lists always ship complete, because pruning cards server-side would break theparent_idchains the client rebuilds the hierarchy from.SELECTand a join on the endpoint card types replace twoINlists that grew with the landscape.MATRIX_MAX_EDGES(200k) caps the payload and flags truncation.Frontend
matrixDimensions.ts(new) — classifies each schema field as a flag (boolean), an enum (single_select) or a scalar, and derives the glyph, colour and label for every value. Codes come from the localized labels, so a French user readsC R M S.matrixCells.ts(new) — builds the grid in one pass over the edges. This replaced four full-grid walks whose per-cell aggregation was itself quadratic in the leaf counts; cost grew with the area of the grid even though the data is sparse.matrixHierarchy.aggregateCountis deleted with them.MatrixFilterBar.tsx(new) — relation type, direction and one filter per attribute, following the existing PortfolioReport filter-bar pattern. Hidden entirely when the axis pair has no relation types.existsandcountunchanged; codes adds colour-coded glyphs in the dense 32px grid, labels widens the data columns to 120px and shows the value names. Only the data columns change width — the row-header columns stay pinned toROW_HEADER_COL_WIDTH, which their sticky offsets are computed from (the Fix Matrix report hierarchical-row cell misalignment #846 invariant, now covered by a test).buildExportData: the grid as shown, and one row per relation with its values spread across columns.ReportShell's XLSX gate now allows a report that supplies its own export data even without a table view;ReportLegendgains an optional glyph.Fixes
single_select, so any flag a relation type declared — the built-in Create/Read/Update/Delete pairs among them — was invisible and unusable. They now render as checkboxes and appear as relation badges.Docs, i18n, demo
en de fr es it pt zh ru da ar.matrix.*keys across the same 10 locales.relAppToInterfacerelations gain flow directions so the provider/consumer story has content; CRUD and usage-type demo data already existed.VERSION→ 2.33.1,CHANGELOG.mdentries,docs/api/openapi.jsonregenerated.Test Plan
Verified against a live instance seeded with
SEED_DEMO=true, driving the real UI:CRUD ● Create ● Read ● Update ● Delete, cells carry the glyphs, filters reach the server with the expectedattr=params.Automated:
TestMatrixRelationSemantics,TestMatrixFilters,TestMatrixRelationTypeCoveragecover orientation, interning, per-type filter scoping,__empty__, malformed filters, a relation type not declared for the pair, and a pair the metamodel declares nothing for. Full backend suite green.matrixCells.test.ts(including the invariant that every payload edge lands in a cell —droppedEdges === 0— which is the check that would have caught the parent-card bug),matrixDimensions.test.ts,MatrixReport.test.tsx, plusmatrixHierarchyandRelationAttributesEditoradditions.npm run lint,npx tsc -b --noEmit,npm run build,mkdocs build --strictall clean.Not done: the new UI is not re-screenshotted. The sandbox can't reach Google Fonts, so every Material Symbol captures as its ligature name.
35_report_matrixkeeps a longer settle time and will pick up the new UI on the nextnpm run capturefrom an environment with normal font access.Checklist
CLAUDE.md/reports/matrixkeeps itsreports.ea_dashboardgateasync deffor all new route handlers and DB operations/VERSIONand added aCHANGELOG.mdentry (if user-facing change)docs/(if UI or feature change)https://claude.ai/code/session_01BHRoag5Y9bW8xnm3W7LeqY