Skip to content

feat(versioning): role-aware family collapse, family-latest resolver, EntityVersionPicker (PRD #442 phase 3)#467

Merged
larsgeorge-db merged 2 commits into
mainfrom
feat-version-family-picker
May 28, 2026
Merged

feat(versioning): role-aware family collapse, family-latest resolver, EntityVersionPicker (PRD #442 phase 3)#467
larsgeorge-db merged 2 commits into
mainfrom
feat-version-family-picker

Conversation

@larsgeorge-db
Copy link
Copy Markdown
Collaborator

Replaces #448, which was auto-closed when its original stacked base branch was deleted during the #447 admin merge. Identical content — same two commits rebased onto current main.

Summary

Phase 3 of PRD #442. Closes the gap between "which row collapses to the latest in a family" and "who's allowed to see what", and ships the unified EntityVersionPicker that the link-contract-to-port dialog (and future selectors per #452/#453/#454) already consume.

Backend

  • common/version_visibility.py (new) — role-aware status rank tables + collapse_by_family helper. Consumer rank treats draft as invisible; elevated rank (admin / owner / subscriber / team-member) treats draft as a real version.
  • DataContractsManager / DataProductsManager: integrate the helper into list_* paths, threading caller_email + caller_team_ids through so the manager can compute _elevated_*_families once per request.
  • GET /api/data-contracts/families/{family_id}/latest and GET /api/data-products/families/{family_id}/latest — visibility-aware resolvers for family-follow-latest references.
  • Phase 3 follow-up (squashed into PR): serialization_alias for versionFamilyId, versionCount, parentContractId, baseName, changeSummary, draftOwnerId so the API actually emits camelCase to match the frontend types.

Frontend

  • components/common/entity-version-picker.tsx (new) — unified searchable combobox supporting entity-pinned and family-follow-latest scopes. When a statusFilter is set, falls back to ?include_history=true and collapses client-side so published versions inside an otherwise-draft-latest family stay selectable.
  • link-contract-to-port-dialog.tsx: migrated from bespoke Select to EntityVersionPicker (closes [Feature]: UI support for contract versions #69).
  • version-selector.tsx shared-component touch-up: passes the full row to onVersionChange, adds triggerClassName for layout reuse.

Tests

  • test_version_visibility.py — status visibility, rank ordering, tie-breaks.
  • test_version_family.py — manager-level collapse with elevated/consumer modes.
  • test_data_contracts_api_models.py — camelCase emission for the aliased fields.

33 unit tests across test_version_family.py + test_version_visibility.py pass on the rebased-to-main branch.

Stack note

Has one dependent PR (#457, clone-serialization fix for #455) — that PR's base will be retargeted to main immediately after this one merges.

Test plan

  • Backend unit tests (pytest src/tests/unit/test_version_family.py src/tests/unit/test_version_visibility.py) — 33/33 pass.
  • Manual Playwright smoke: link-contract dialog opens, scope toggle works, published versions inside draft-latest families remain selectable.
  • Manual: subscriber and team-owner views show draft rows; anonymous consumer does not.

… EntityVersionPicker (PRD #442 phase 3)

Phase 3 of the unified version-family work. Three vertically-aligned slices:

1. **Role-aware visibility ranking** — adds
   `src/common/version_visibility.py` with elevated vs consumer status
   rank tables and a `collapse_by_family` helper. Plumbed into the
   contracts and products list managers so the family representative is
   picked according to the caller's role:
     * Consumer: ACTIVE > DEPRECATED (drafts hidden entirely)
     * Owner / team member / subscriber / admin: DRAFT > PROPOSED >
       UNDER_REVIEW > APPROVED > ACTIVE > DEPRECATED
   Subscription-based elevation is wired in for products via the
   existing `DataProductSubscriptionDb` table; contracts use ownership
   only (no subscription table today).

2. **Family-latest resolver endpoints** —
   `GET /api/data-contracts/families/{family_id}/latest` and the
   products counterpart. These resolve a family-follow-latest reference
   to a concrete row using the same role-aware rank, so any caller
   storing only a family id can read back the right version.

3. **EntityVersionPicker** — new shared component in
   `components/common/entity-version-picker.tsx`. Combobox over the
   collapsed list endpoint (one row per family with inline version
   badges), optional Entity-pinned / Family-follow-latest scope toggle,
   and an inline sub-picker for refining the version when pinning. The
   `link-contract-to-port-dialog` (closes #69) is migrated as the first
   call site, with `allowedScopes=['entity']` until the output-port
   reference-storage slice lands.

Tests added:
* `test_version_visibility.py` — 12 cases covering status visibility,
  rank ordering, tie-breaks, and the consumer-only / admin-only edges.
* `test_version_family.py` — two new manager-level cases proving
  consumers see the active rep while team members of the same family
  see the in-flight draft.

Note: the dev backend reloader has a pre-existing misconfiguration
(noted in PR #447). Logic is covered by unit tests; live verification
requires a manual `uvicorn` restart.
…ck (#442 phase 3 follow-up)

Two issues uncovered during Playwright smoke testing of #448:

1. **camelCase serialization on list endpoints.** The
   ``DataContractSummary`` and ``DataProduct`` Pydantic models declared
   their PRD-#442 fields (``versionFamilyId``, ``versionCount``,
   ``parentContractId/parentProductId``, ``baseName``, ``changeSummary``,
   ``draftOwnerId``) with snake_case ``alias``-es. FastAPI's default
   ``response_model_by_alias=True`` then emitted snake_case, but the FE
   TS types use camelCase — so ``row.versionCount`` was silently
   ``undefined`` and the count badge never rendered. Switch each field
   to use Pydantic v2's ``serialization_alias`` so the wire format is
   camelCase regardless of the by-alias toggle, while the existing
   snake_case ``alias`` keeps ORM ``from_attributes=True`` reading
   working for both the SQLAlchemy attribute and snake_case JSON input.

2. **Picker hid valid published versions inside elevated families.**
   With role-aware ranking landed in #448, a family whose newest row is
   a draft now collapses to that draft as its representative. The
   EntityVersionPicker only ever saw collapsed reps, so a port-link
   dialog scoped to ``statusFilter=['active','approved','certified']``
   would silently drop families that had a perfectly valid published
   version available — e.g. Customer Data Contract disappeared from the
   POS Transaction Stream port picker after a 2.0.0 draft was created.
   When ``statusFilter`` is supplied, fetch with
   ``?include_history=true`` and collapse client-side after status
   filtering, preferring the newest matching version per family.
   ``versionCount`` is computed BEFORE collapse so the badge still
   reflects the full family size.

Smoke-tested end-to-end via Playwright MCP against a multi-version
family. Original v1.0.0 (active) is now selectable, picker write
persists the entity-pinned contract id correctly.
@larsgeorge-db larsgeorge-db requested a review from a team as a code owner May 28, 2026 15:12
@larsgeorge-db larsgeorge-db merged commit eb709ac into main May 28, 2026
6 of 7 checks passed
@larsgeorge-db larsgeorge-db deleted the feat-version-family-picker branch May 28, 2026 15:13
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.

[Feature]: UI support for contract versions

1 participant