Skip to content

fix(tui): use the shared API wire types instead of local copies - #2258

Merged
milenkovicm merged 12 commits into
apache:mainfrom
andygrove:tui-shared-api-types
Aug 18, 2026
Merged

fix(tui): use the shared API wire types instead of local copies#2258
milenkovicm merged 12 commits into
apache:mainfrom
andygrove:tui-shared-api-types

Conversation

@andygrove

@andygrove andygrove commented Aug 8, 2026

Copy link
Copy Markdown
Member

Which issue does this PR close?

Closes #2257.

Rationale for this change

The TUI declared its own copies of the scheduler's /api/* response types. Nothing kept the two sets in step, and they have drifted: 2ed3464df (multi-partition tasks, #2038) changed TaskSummary::partition_id from u32 to Vec<u32> and updated ballista-cli/src/main.rs but not the TUI's copy, which still declared u32. The field has no #[serde(default)], so the whole JobStageResponse fails to deserialize and the stage detail popup breaks.

A second, quieter drift in the same file: StageTaskStatus::Failed { reason } against the scheduler's TaskStatus::Failed { reason, error }. Serde ignores the extra field, so the TUI simply could not show the error text.

The fix is not to re-sync the copies, it is to stop having copies. #2256 extracts the wire types into ballista-api-types, a serde-only leaf crate. This PR points the TUI at them, which turns this whole class of drift into a compile error.

What changes are included in this PR?

ballista-cli takes a dependency on ballista-api-types under both the tui and web features, and the local declarations are replaced with re-exports of the shared types under the names the TUI already used:

Was (local) Now (shared)
Job JobResponse
JobStagesResponse QueryStagesResponse
JobStageResponse QueryStageSummary
StageTaskResponse TaskSummary
StageTaskStatus TaskStatus
TaskPercentiles Percentiles

Job's four status predicates (is_queued, is_running, is_completed, is_failed) become a JobStatusExt extension trait, since the type is now defined in another crate and ballista-api-types should not carry TUI concerns.

The renames the shared names imply (stage.id to stage.stage_id, and so on) account for most of the line count. Three changes are more than mechanical:

  • partition_id renders as a list. Single-partition tasks look the same as before; multi-partition tasks now show every partition they own instead of failing to parse. This is the actual bug fix.
  • Failed tasks can show the error. The status arm destructures Failed { reason, .. } against the richer shared enum.
  • stage_plan is Option<String> rather than a #[serde(default)] String, so the plan popup renders an empty plan as empty rather than relying on the default.

start_time / end_time are u64 on the shared type where the TUI had i64; the duration calculation uses saturating_sub accordingly, which also removes an underflow if a job ever reports end_time < start_time.

Two regression tests are added, both against payloads shaped like real scheduler output: one deserializes a task with "partition_id": [0, 1, 2], and one deserializes Failed with both reason and error.

Are there any user-facing changes?

Yes, all fixes:

  • The stage detail popup works again against a current scheduler.
  • Multi-partition tasks display all their partitions instead of breaking the response.
  • Failed tasks can surface the scheduler's error text.

No API changes. ballista-cli gains an internal dependency, and ballista-api-types is serde-only so the wasm32 web build is unaffected.

Verified locally: cargo test -p ballista-cli --no-default-features --features cli,tui passes (247 tests, including the two new ones), the web feature builds, and clippy is clean for both feature sets with -D warnings.

…or out dto_build

Move the scheduler's REST response types into a new leaf crate,
`ballista-history`, and pull the graph-to-DTO construction out of the
axum handlers into a pure `api::dto_build` module.

Behavior preserving: the same DTOs are produced from the same state, so
live REST responses are byte-identical. The existing handler tests cover
this, and the helper unit tests move alongside the functions they test.

This is the first step toward a history server that replays completed
jobs and serves the same `/api/*` responses without a live scheduler.
Splitting the DTOs into a serde-only crate lets that server build the
identical wire types without depending on the scheduler's live
execution graph, and moving construction out of the handlers means it
can run against state that did not come from a handler request.

`JobResponse::job_id` becomes a `String` rather than `ballista_core::JobId`
so the new crate stays serde-only. `JobId` is `#[serde(transparent)]`
over `String`, so the JSON is unchanged.
Follow-up cleanups on the extraction:

- Collapse the three near-identical ExecutionStage arms in
  graph_to_query_stages into one destructuring match, dropping the
  mutable placeholder-zero summary.
- Take PlanFormat by value instead of &JobQueryParams, and move
  PlanFormat into ballista-history. It is part of the wire contract, and
  the pure builder no longer imports an axum query-param type back out of
  the handler module.
- Inject `now` into graph_to_query_stages rather than reading the clock,
  so replaying a stored log renders stable elapsed times.
- Share percent_complete and min_start_time; use displayable() instead of
  the longhand DisplayableExecutionPlan::new().
- Drop the dead JobConfig alias and the unused serde_json dev-dependency,
  make task_status_to_dto private, and remove a duplicated test.
- Enable #![warn(missing_docs)] on ballista-history and document the
  types, matching the other Ballista crates.
- Register ballista-history with the release tooling: version bump
  script, publish order, and crate dependency graph.
The previous wording left it ambiguous whether the history server
re-derives responses from stored execution state or replays stored DTOs.
It replays them: the scheduler builds each response once against the live
graph and writes it to the event log, so byte-identical output is a
structural property rather than two implementations agreeing.

Also records the consequence, that anything not captured at write time
cannot be recovered at replay time.
The crate holds the /api/* wire types, and it has three parties, not one:
the scheduler serves them, the web TUI deserializes them, and a future
history server will serve replayed copies. Naming it after the history
server made it awkward for the TUI, which parses live scheduler responses
and today keeps its own duplicate declarations.

Renaming it after the contract it defines removes that friction. The
event-log schema, writer, and reader can then land as a separate
ballista-history crate that depends on this one.
The TUI declared its own structs for the scheduler's /api/* responses.
Nothing kept them in step, so 2ed3464 changed TaskSummary::partition_id
from u32 to Vec<u32> without touching the TUI, and the stages popup has
been unable to parse a stage response since.

Replace the local declarations with the shared ballista-api-types
definitions, so the next scheduler-side field change is a compile error
here rather than a runtime parse failure:

  Job                -> JobResponse
  JobStagesResponse  -> QueryStagesResponse
  JobStageResponse   -> QueryStageSummary
  StageTaskResponse  -> TaskSummary
  StageTaskStatus    -> TaskStatus
  TaskPercentiles    -> Percentiles

Job's four status predicates move to a JobStatusExt extension trait,
since the type now lives in another crate.

Two display sites change as a result. Multi-partition tasks render the
whole partition list rather than a single id, and a failed task can now
show the scheduler's error text alongside the reason.

Closes apache#2257.
Comment thread ballista-cli/src/tui/ui/main/jobs/mod.rs Outdated
Comment thread ballista-cli/src/tui/ui/main/jobs/stage_tasks_popup.rs Outdated
Comment thread ballista-cli/Cargo.toml Outdated
Comment thread ballista-cli/Cargo.toml
Co-authored-by: Martin Grigorov <martin-g@users.noreply.github.com>
@martin-g

Copy link
Copy Markdown
Member

@andygrove Let me fix the build since I suggested the improvement that broke it!

@martin-g

Copy link
Copy Markdown
Member

@andygrove May I take over here ?

@milenkovicm

Copy link
Copy Markdown
Contributor

thanks @andygrove & @martin-g

@milenkovicm
milenkovicm merged commit a12fc9d into apache:main Aug 18, 2026
26 checks passed
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.

TUI's duplicated API response types have drifted from the scheduler, breaking the stages popup

3 participants