Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .cursor/rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The AI agents should never link to any issue or a pull request
in any GitHub repository in the code reviews!

The AI agents should not review AI agents' config files like CLAUDE.md or AGENTS.md!

5 changes: 5 additions & 0 deletions .gemini/rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The AI agents should never link to any issue or a pull request
in any GitHub repository in the code reviews!

The AI agents should not review AI agents' config files like CLAUDE.md or AGENTS.md!

5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The AI agents should never link to any issue or a pull request
in any GitHub repository in the code reviews!

The AI agents should not review AI agents' config files like CLAUDE.md or AGENTS.md!

5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
The AI agents should never link to any issue or a pull request
in any GitHub repository in the code reviews!

The AI agents should not review AI agents' config files like CLAUDE.md or AGENTS.md!

1 change: 1 addition & 0 deletions ballista-cli/src/tui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,6 +1329,7 @@ mod tests {
job_id: id.to_string(),
job_name: format!("Job {id}"),
status: status.to_string(),
job_status: status.to_string(),
start_time: 0,
end_time: 1,
num_stages: 1,
Expand Down
21 changes: 20 additions & 1 deletion ballista-cli/src/tui/domain/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use std::collections::BTreeMap;
pub struct Job {
pub job_id: String,
pub job_name: String,
pub status: String, // Running, Completed, Failed, Canceled
pub status: String, // Running, Completed, Failed, Canceled
pub job_status: String, // human-readable status/failure detail, e.g. "Failed: <reason>"
Comment on lines +28 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure backward compatibility and robustness against older scheduler versions or cases where the job_status field is omitted from the API response, it is highly recommended to mark this field with #[serde(default)]. This prevents deserialization failures when the field is missing.

Suggested change
pub status: String, // Running, Completed, Failed, Canceled
pub job_status: String, // human-readable status/failure detail, e.g. "Failed: <reason>"
pub status: String, // Running, Completed, Failed, Canceled
#[serde(default)]
pub job_status: String, // human-readable status/failure detail, e.g. "Failed: <reason>"

@augmentcode augmentcode Bot Jun 16, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Job derives Deserialize, and the new required job_status field means the CLI will fail to parse /jobs responses from schedulers that don’t include this field (i.e., reduced backward-compatibility). If mixed-version deployments are expected, this can become a runtime break rather than a compile-time one.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment on lines +28 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a serde default for job_status to prevent decode failures under version skew.

Job is Deserialize, and Line 29 makes job_status mandatory. If a scheduler response omits that field, jobs deserialization fails and the UI falls back to an empty jobs list (see ballista-cli/src/tui/ui/main/jobs/mod.rs, Lines 58-61).

Proposed fix
 #[derive(Deserialize, Clone, Debug)]
 pub struct Job {
     pub job_id: String,
     pub job_name: String,
     pub status: String,     // Running, Completed, Failed, Canceled
+    #[serde(default)]
     pub job_status: String, // human-readable status/failure detail, e.g. "Failed: <reason>"
     pub start_time: i64,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub status: String, // Running, Completed, Failed, Canceled
pub job_status: String, // human-readable status/failure detail, e.g. "Failed: <reason>"
pub status: String, // Running, Completed, Failed, Canceled
#[serde(default)]
pub job_status: String, // human-readable status/failure detail, e.g. "Failed: <reason>"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ballista-cli/src/tui/domain/jobs.rs` around lines 28 - 29, Add a serde
default attribute to the job_status field in the Job struct to allow
deserialization to succeed when the field is omitted from scheduler responses
(due to version skew). Apply the #[serde(default)] macro attribute directly
above the job_status field declaration to provide a default empty string value
when the field is missing, preventing deserialization failures that currently
cause the UI to fall back to an empty jobs list.

pub start_time: i64,
pub end_time: i64,
pub num_stages: usize,
Expand Down Expand Up @@ -440,6 +441,7 @@ mod tests {
job_id: id.to_string(),
job_name: name.to_string(),
status: status.to_string(),
job_status: status.to_string(),
start_time,
end_time,
num_stages,
Expand Down Expand Up @@ -681,6 +683,23 @@ mod tests {
assert_eq!(job.job_id, "j2");
}

#[test]
fn job_status_carries_failure_detail_independent_of_status() {
let job = Job {
job_id: "j1".to_string(),
job_name: "Job One".to_string(),
status: "Failed".to_string(),
job_status: "Failed: division by zero".to_string(),
start_time: 1,
end_time: 2,
num_stages: 1,
completed_stages: 0,
percent_complete: 0,
};
assert_eq!(job.status, "Failed");
assert_eq!(job.job_status, "Failed: division by zero");
}

#[test]
fn selected_job_filters_by_search_term_on_id() {
let jobs = vec![
Expand Down
49 changes: 45 additions & 4 deletions ballista-cli/src/tui/ui/main/jobs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ use ratatui::{
style::Style,
text::Text,
widgets::{
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table, TableState,
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table, TableState, Wrap,
},
};

Expand Down Expand Up @@ -200,24 +200,65 @@ pub fn render_jobs(f: &mut Frame, area: Rect, app: &App) {
app.jobs_data.sort_jobs(&mut sorted_jobs);

if !sorted_jobs.is_empty() {
let selected_job = app
.jobs_data
.table_state
.selected()
.and_then(|idx| sorted_jobs.get(idx).copied());

let (table_area, failed_status_area) =
split_area_for_table_and_failure(selected_job, rects[1]);

let mut scroll_state = app.jobs_data.scrollbar_state;
let mut table_state = app.jobs_data.table_state;
let table_area = vertical_scrollbar::split_area(rects[1]);
let [table_area, scrollbar_area] = vertical_scrollbar::split_area(table_area);
render_jobs_table(
f,
table_area[0],
table_area,
&sorted_jobs,
&mut table_state,
&app.jobs_data.sort_column,
&app.jobs_data.sort_order,
app,
);
render_scrollbar(f, table_area[1], &mut scroll_state);
render_scrollbar(f, scrollbar_area, &mut scroll_state);

if let Some((area, job)) = failed_status_area {
render_job_failure_reason(f, area, job, app);
}
} else {
render_no_jobs(f, rects[1], app.theme.text_info);
}
}

fn split_area_for_table_and_failure(
selected_job: Option<&Job>,
area: Rect,
) -> (Rect, Option<(Rect, &Job)>) {
match selected_job {
Some(job) if job.status == "Failed" => {
let areas = Layout::vertical([
Constraint::Min(5), // Table
Constraint::Length(5), // Failure reason

@augmentcode augmentcode Bot Jun 16, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The failure-detail panel is hard-capped to 5 rows, so longer job.job_status messages will be truncated with no way to view the full error text from the UI.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

])
.split(area);
(areas[0], Some((areas[1], job)))
}
_ => (area, None),
}
Comment on lines +238 to +248

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the terminal window is resized to a very small height, splitting the area with a fixed constraint of Constraint::Length(5) for the failure reason and Constraint::Min(5) for the table can cause layout overlap or hide the table entirely. Adding a guard to only split the area when there is sufficient vertical space (e.g., area.height >= 10) ensures a more robust and adaptive UI.

Suggested change
match selected_job {
Some(job) if job.status == "Failed" => {
let areas = Layout::vertical([
Constraint::Min(5), // Table
Constraint::Length(5), // Failure reason
])
.split(area);
(areas[0], Some((areas[1], job)))
}
_ => (area, None),
}
match selected_job {
Some(job) if job.status == "Failed" && area.height >= 10 => {
let areas = Layout::vertical([
Constraint::Min(5), // Table
Constraint::Length(5), // Failure reason
])
.split(area);
(areas[0], Some((areas[1], job)))
}
_ => (area, None),
}

}

fn render_job_failure_reason(f: &mut Frame, area: Rect, job: &Job, app: &App) {
let block = Block::default()
.borders(Borders::all())
.style(app.theme.text_error);
let paragraph = Paragraph::new(job.job_status.as_str())
.style(app.theme.text_error)
.wrap(Wrap { trim: true })
.block(block);
f.render_widget(paragraph, area);
}

fn render_no_jobs(f: &mut Frame, area: Rect, style: Style) {
let block = Block::default().borders(Borders::all());
let paragraph = Paragraph::new("No registered jobs in the scheduler!")
Expand Down
5 changes: 2 additions & 3 deletions ballista-cli/src/tui/ui/vertical_scrollbar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::widgets::{Scrollbar, ScrollbarOrientation, ScrollbarState};
use std::rc::Rc;

pub(crate) fn render_scrollbar(
frame: &mut Frame,
Expand All @@ -38,10 +37,10 @@ pub(crate) fn render_scrollbar(
/// Splits the given area into two parts:
/// * the first part is the table area
/// * the second part is the scrollbar area.
pub(crate) fn split_area(area: Rect) -> Rc<[Rect]> {
pub(crate) fn split_area(area: Rect) -> [Rect; 2] {
Layout::horizontal([
Constraint::Min(1), // Table
Constraint::Length(1), // Scrollbar
])
.split(area)
.areas(area)
}
Loading