Skip to content

Vehicle details: hydrate from NeTEx XML endpoint, not projection GQL #73

@dynnamitt

Description

@dynnamitt

Problem

VehicleDetails.tsx currently hydrates its edit form from VehicleGQLShaped — the projection row used by the list — via hydrateFromRow() (src/data/vehicles/vehicleFormDefaults.ts). That row only carries the few columns the list needs (registrationNumber, operationalNumber, transportType.{id,name,transportMode}, version). It is missing the bulk of the NeTEx Vehicle shape — Name, Description, ShortName, BuildDate, ChassisNumber, RegistrationDate, keyList, ValidBetween, VehicleModelRef, etc. — so the form can never round-trip a real Vehicle.

The Sobek single-vehicle export endpoint is in place:

  • GET /services/vehicles/netex/vehicles/{id}PublicationDelivery XML (CompositeFrame → ResourceFrame → vehicles → <Vehicle>), with Accept: application/xml. Unknown id returns an empty ResourceFrame.

This issue switches details hydration to that endpoint. Save path already round-trips via useVehiclePairSavebuildVehiclePairXml → import — this aligns the read side with it.

Scope (Hathor only)

  1. Add fetchVehicleNetexXml(applicationImportBaseUrl, id, token) under src/data/vehicles/xml/GET ${base}/vehicles/${encodeURIComponent(id)}, Accept: application/xml, returns raw string.
  2. Add parseVehicleXml(xml): Partial<Vehicle> — uses fast-xml-parser's XMLParser (already a dep, see src/data/vehicle-imports/xmlUtils.ts:106) to drill PublicationDelivery → dataObjects → (CompositeFrame.frames?.ResourceFrame ?? ResourceFrame) → vehicles → Vehicle[0]. Reuse findResourceFrame + toArray from xmlUtils.ts. Map the parsed shape into the Vehicle interface declared in src/data/vehicles/xml/Vehicle.ts.
  3. Add useVehicle(id) hook — mirrors useVehicles() lifecycle (fetch on id/baseUrl change, loading/error/refetch), returns { data: Partial<Vehicle> | null, loading, error, refetch }.
  4. VehicleDetails.tsx: replace the hydrateFromRow(vehicle) path with the new hook. Keep the GQL row only for the header context strip (transport-type chip + mode chip) until issue Vehicle + VehicleModel combined details form (phase 2) #69 reshapes that too. The edit form (VehicleEditForm) gets fed the XML-shaped data directly.
  5. Delete vehicleFormDefaults.ts:hydrateFromRow once nothing else references it. BLANK_FORM stays — "New Vehicle" flow still needs it.

Open design question (handled in PR)

fast-xml-parser's default shape does not match xml/Vehicle.ts:

concern xml/Vehicle.ts fast-xml-parser default
attributes \$xxx (e.g. \$id) @_xxx (e.g. @_id)
text node value #text
ref shape string (flattened, e.g. TransportTypeRef: 'NMR:VT:1') { '@_ref': 'NMR:VT:1' }
arrays always T[] (e.g. Name: TextType[]) object when 1, array when N (or use isArray)

Two acceptable approaches:

  • (a) Parser adapter — keep xml/Vehicle.ts as the truth; write a thin xmlToVehicle() that renames keys (@_\$), unwraps { '@_ref' } to string, and forces arrays via XMLParser's isArray callback on the known list fields. This is the inverse of the existing vehicleToXmlShape() write mapper.
  • (b) Adopt the parser shape — change xml/Vehicle.ts (and VehicleEditForm's readers like firstText) to consume @_ / #text / { '@_ref' } directly, then update Vehicle-mapping.ts to be identity-shaped on the parsed half. Bigger blast radius but eliminates two transforms.

Decision happens during PR: build a small fixture from the export-seed (vehicle-export-seed.xml in Sobek), parse with XMLParser, diff against Vehicle interface, then pick. If the gap turns out to be wide enough that neither option is clean, retrofit Vehicle.ts / EditForm to the XML shape per the user's plan ("the details form will be retrofitted to align with the xml shaped vehicle").

Out of scope

Verification

  • npm run local against Sobek :37999. Click a vehicle in the list → details slider opens → form fields populated from the NeTEx fetch (not just registration number + op number). Edit a field → save → re-open → field persists.
  • Network tab shows the GET /netex/vehicles/{id} round-trip.
  • Unknown-id route still renders the existing "Vehicle not found" empty state (empty vehicles array from the parser).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions