diff --git a/.cursor/rules.md b/.cursor/rules.md new file mode 100644 index 0000000000..00385e6f5f --- /dev/null +++ b/.cursor/rules.md @@ -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! + diff --git a/.gemini/rules.md b/.gemini/rules.md new file mode 100644 index 0000000000..00385e6f5f --- /dev/null +++ b/.gemini/rules.md @@ -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! + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..00385e6f5f --- /dev/null +++ b/AGENTS.md @@ -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! + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..00385e6f5f --- /dev/null +++ b/CLAUDE.md @@ -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! + diff --git a/ballista-cli/src/tui/app.rs b/ballista-cli/src/tui/app.rs index 3b8357af07..3798942dec 100644 --- a/ballista-cli/src/tui/app.rs +++ b/ballista-cli/src/tui/app.rs @@ -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, diff --git a/ballista-cli/src/tui/domain/jobs.rs b/ballista-cli/src/tui/domain/jobs.rs index a7c1c87d0b..1d1c7e6368 100644 --- a/ballista-cli/src/tui/domain/jobs.rs +++ b/ballista-cli/src/tui/domain/jobs.rs @@ -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: " pub start_time: i64, pub end_time: i64, pub num_stages: usize, @@ -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, @@ -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![ diff --git a/ballista-cli/src/tui/ui/main/jobs/mod.rs b/ballista-cli/src/tui/ui/main/jobs/mod.rs index bcbd15c083..bcf0f25185 100644 --- a/ballista-cli/src/tui/ui/main/jobs/mod.rs +++ b/ballista-cli/src/tui/ui/main/jobs/mod.rs @@ -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, }, }; @@ -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 + ]) + .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!") diff --git a/ballista-cli/src/tui/ui/vertical_scrollbar.rs b/ballista-cli/src/tui/ui/vertical_scrollbar.rs index bfecc50060..ee87d8fe8f 100644 --- a/ballista-cli/src/tui/ui/vertical_scrollbar.rs +++ b/ballista-cli/src/tui/ui/vertical_scrollbar.rs @@ -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, @@ -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) }