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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- The `Reason` column asks `squeue` for `%r` rather than the dual-purpose `%R`, which returns the allocated nodes for anything that is not pending or failed. It now says why a job is in its current state instead of repeating the `Node` column on every running row, sorting by it sorts by the reason, and a job with no reason shows the usual `-` instead of `None`.

## [0.2.0] - 2026-08-28

### Added
Expand Down
6 changes: 5 additions & 1 deletion src/backend/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,11 @@ pub fn decode_squeue_output(raw: &str, fmt: &str) -> Vec<Job> {
"%V" => job.submit_time = Some(val),
"%S" => job.start_time = Some(val),
"%e" => job.end_time = Some(val),
"%R" => job.reason = Some(val),
// squeue prints "None" when a job has no reason, which the
// table shows as the same "-" placeholder as any other unset
// cell. %R is deliberately not accepted here: it returns the
// nodelist for anything that is not pending or failed.
"%r" => job.reason = (val != "None").then_some(val),
_ => {}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/views/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl JobField {
JobField::SubmitTime => "%V",
JobField::StartTime => "%S",
JobField::EndTime => "%e",
JobField::PendReason => "%R",
JobField::PendReason => "%r",
}
}

Expand Down
25 changes: 24 additions & 1 deletion tests/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn blank_lines_are_skipped() {
}

/// Every column the UI can request must decode into a field; otherwise the
/// column silently renders empty (the bug the `%R` column once had).
/// column silently renders empty.
#[test]
fn every_field_code_is_decoded() {
for field in JobField::enumerate() {
Expand All @@ -77,3 +77,26 @@ fn every_field_code_is_decoded() {
);
}
}

/// `%R` is dual purpose: for a running job it returns the allocated nodes,
/// so a Reason column built on it just repeats the Node column.
#[test]
fn the_reason_column_asks_for_the_single_purpose_code() {
assert_eq!(JobField::PendReason.format_code(), "%r");
}

#[test]
fn reason_decodes_for_a_pending_job() {
let raw = line(&["1002", "N/A", "Priority"]);
let jobs = decode_squeue_output(&raw, &["%i", "%N", "%r"].join(FIELD_SEP));
assert_eq!(jobs[0].reason.as_deref(), Some("Priority"));
}

/// `squeue` writes "None" rather than an empty cell for a job with no reason,
/// and the table shows an unset reason as the same "-" as any other blank.
#[test]
fn a_reason_of_none_reads_as_unset() {
let raw = line(&["1001", "None"]);
let jobs = decode_squeue_output(&raw, &["%i", "%r"].join(FIELD_SEP));
assert_eq!(jobs[0].reason, None);
}