Skip to content

feat(status): display duration#650

Open
FrancescElies wants to merge 1 commit intoNukesor:mainfrom
FrancescElies:cesc/duration
Open

feat(status): display duration#650
FrancescElies wants to merge 1 commit intoNukesor:mainfrom
FrancescElies:cesc/duration

Conversation

@FrancescElies
Copy link
Copy Markdown

@FrancescElies FrancescElies commented Dec 12, 2025

Problem when looking at pueue status it takes too many brain cycles to figure out how long a task took.

This pr adds duration column to pueue status
image

Checklist

Please make sure the PR adheres to this project's standards:

  • I included a new entry to the CHANGELOG.md.
  • I checked cargo clippy and cargo fmt. The CI will fail otherwise anyway.
  • (If applicable) I added tests for this feature or adjusted existing tests.
  • (If applicable) I checked if anything in the wiki needs to be changed.

Comment thread pueue/src/format.rs
enqueue_at.format(format_string).to_string()
}

pub fn humanize_duration(d: TimeDelta) -> String {
Copy link
Copy Markdown
Author

@FrancescElies FrancescElies Dec 12, 2025

Choose a reason for hiding this comment

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

Since I don't know if this pr is eve desired, I intentionally avoided overhead and didn't add any tests for this function.

Considered using chrono-humanize but didn't want to add a dependency without asking, that crate is being used by nushell too.

Copy link
Copy Markdown
Owner

@Nukesor Nukesor Jan 11, 2026

Choose a reason for hiding this comment

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

I'm ok with chrono-humanize :).
Although, I'm wondering, whether we should have a configurable duration format.

Personally, I would like to have the duration be as compact as possible. Something along the line of 1 days 05:35:45.

From what I've seen, there's no strftime-like API for Duration, which is a bit annoying. In practice, it shouldn't be too hard to build this ourselves (with a limited set of variables), since we already have handlebars templating in pueue due to the callback logic. With that, we could provide a simple strftime-like duration_format configuration option on top. Maybe even a days_duration_format for very long tasks?

@FrancescElies FrancescElies force-pushed the cesc/duration branch 2 times, most recently from fd3fef5 to eea58e1 Compare December 12, 2025 13:12
@FrancescElies FrancescElies marked this pull request as ready for review December 12, 2025 13:13
@Nukesor
Copy link
Copy Markdown
Owner

Nukesor commented Dec 15, 2025

I like it.

The main reason why there aren't more columns in the pueue status output yet, though, is that the table is already crowded as is.

If there's task with delays, dependencies and priorities, we're already at 9 columns.
Add any somewhat lengthy command and/or path to that, and it will become hard to read if you don't expand the terminal to the full screen width.

Meaning, while I like this feature, I feel like we can only add this if we come up with a good solution to toggle this. Preferably via the config file. While we're at it, we should probably come up with something more "general" regarding status styling.

We already have these settings:

    /// Whether aliases specified in `pueue_aliases.yml` should be expanded in the `pueue status`
    /// or shown in their short form.
    #[serde(default = "Default::default")]
    pub show_expanded_aliases: bool,
    /// The max amount of lines each task get's in the `pueue status` view.
    pub max_status_lines: Option<usize>,
    /// The format that will be used to display time formats in `pueue status`.
    #[serde(default = "default_status_time_format")]
    pub status_time_format: String,
    /// The format that will be used to display datetime formats in `pueue status`.
    #[serde(default = "default_status_datetime_format")]
    pub status_datetime_format: String,

They should probably be enclosed in a custom status subsection of the client section.
This could be a backwards compatible change, that breaks gracefully on v5.

In there, we could have something like an additional_columns array of enum AdditionalColumns where duration is one of the possible columns.

I.e.

client:
  # .. other client settings
  status:
    show_expanded_aliases: true
    max_status_lines: 5
    status_time_format: "foo"
    status_datetime_format: "foo"
    additional_columns:
      - "Duration"

@FrancescElies
Copy link
Copy Markdown
Author

I agree, one more column here now, another one tomorrow because of something else and soon the table is too bloated.

Personally I feel we could swap The End column with Duration, I normally care when a task started and how long it took, real end time is secondary for me, but I understand if you don't feel that way.

If you are ok with the swap we would stay with the same number of columns and do the other suggestions in separate prs.

With regards to your status subsection i can go and do that and see how it feels either in this pr or a separate one.

Another option is to add duration only to the pueue status --json (which now I realize I didn't) and let the user select what it wants, using jq or similar and then making your own alias , e.g.
image

@FrancescElies
Copy link
Copy Markdown
Author

FrancescElies commented Dec 22, 2025

TODO: add additional_columns done

let (_settings, config_found) = Settings::read(&Some(old_settings_path))
let (settings, config_found) = Settings::read(&Some(old_settings_path))
.wrap_err("Failed to read old config with defaults:")?;
assert_eq!(settings.client.status.show_expanded_aliases, false);
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

unless i missed something here basically I added a test to show that the migration hack works

end: Local::now(),
start,
end,
duration,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

basically adds duration to pueue status --json

Currently being serialized as a tuple because of timedelta, but maybe we would like to humanize this tuple too.

Image

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Yeah. I feel like seconds would be good enough. But if we would need a custom serializer for this, I'm also okay with a struct. The lesser code and (code) complexity, the better :D

Comment thread pueue_lib/src/settings.rs
/// Replicate deprecated fields into their new location.
/// WARN: This should only be used until deprecated fields are fully removed.
#[expect(deprecated)]
pub fn replicate_deprecated_fields(&mut self) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@Nukesor this is basically a hack to replicate old values into the new structure fields.

Maybe there is a cleaner way of achieving the same thing but other things like modifiying serde deserializers made my head spin and felt overcomplicated.

There must be a better way but this is what I came up with

@FrancescElies FrancescElies force-pushed the cesc/duration branch 2 times, most recently from 5242308 to f23920a Compare December 23, 2025 09:15
@FrancescElies
Copy link
Copy Markdown
Author

@Nukesor if you could have a look again it would be great, i added additional_columns field and status is handled in a backwards compatible way, compat code could be later on in v5 safely removed.

@Nukesor
Copy link
Copy Markdown
Owner

Nukesor commented Dec 26, 2025

Heyo, I'll probaly only get to reviewing this in the new year :D
The last month was pretty hectic and I'm away for the next few days :)

Copy link
Copy Markdown
Owner

@Nukesor Nukesor left a comment

Choose a reason for hiding this comment

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

Neat, thanks for your work :)

I have a few improvements in mind, but the overall thing already looks pretty good!

end: Local::now(),
start,
end,
duration,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Yeah. I feel like seconds would be good enough. But if we would need a custom serializer for this, I'm also okay with a struct. The lesser code and (code) complexity, the better :D

Comment thread pueue/src/format.rs
Comment on lines +18 to +28
let days = millis / 86_400_000;
millis %= 86_400_000;

let hours = millis / 3_600_000;
millis %= 3_600_000;

let minutes = millis / 60_000;
millis %= 60_000;

let seconds = millis / 1000;
millis %= 1000;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

TimeDelta has num_* functions. They do exactly what you're doing here.

Comment thread pueue/src/format.rs
enqueue_at.format(format_string).to_string()
}

pub fn humanize_duration(d: TimeDelta) -> String {
Copy link
Copy Markdown
Owner

@Nukesor Nukesor Jan 11, 2026

Choose a reason for hiding this comment

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

I'm ok with chrono-humanize :).
Although, I'm wondering, whether we should have a configurable duration format.

Personally, I would like to have the duration be as compact as possible. Something along the line of 1 days 05:35:45.

From what I've seen, there's no strftime-like API for Duration, which is a bit annoying. In practice, it shouldn't be too hard to build this ourselves (with a limited set of variables), since we already have handlebars templating in pueue due to the callback logic. With that, we could provide a simple strftime-like duration_format configuration option on top. Maybe even a days_duration_format for very long tasks?

Comment on lines +42 to 63
fn test_restore_from_4_0_2() -> Result<()> {
let settings_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("data")
.join("v4.0.2_settings.yml");

// Open v4.0.2 file and ensure the settings file can be read.
let (settings, config_found) = Settings::read(&Some(settings_path))
.wrap_err("Failed to read old config with defaults:")?;
assert_eq!(settings.client.status.show_expanded_aliases, true);
assert_eq!(settings.client.status.max_lines, Some(42));
assert_eq!(settings.client.status.time_format, "mock402-%H:%M:%S");
assert_eq!(
settings.client.status.datetime_format,
"mock402-%Y-%m-%d\n%H:%M:%S"
);
assert_eq!(
settings.client.status.additional_columns,
vec!["Duration".to_owned(),]
);

assert!(config_found);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Since we're not introducing a new major version, this test isn't necessary. We just need to ensure backwards compatibility to the oldest supported version.

Just remove it :)

Comment on lines +10 to +11
time_format: "mock402-%H:%M:%S"
datetime_format: "mock402-%Y-%m-%d\n%H:%M:%S"
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Why mock402?
Same above for the mock015

Comment thread pueue_lib/src/settings.rs
Comment on lines +120 to 133
#[serde(default = "Default::default")]
#[deprecated(since = "4.0.1", note = "use `status` instead")]
pub show_expanded_aliases: bool,
/// The max amount of lines each task get's in the `pueue status` view.
#[deprecated(since = "4.0.1", note = "use `status` instead")]
pub max_status_lines: Option<usize>,
/// The format that will be used to display time formats in `pueue status`.
#[serde(default = "default_status_time_format")]
#[deprecated(since = "4.0.1", note = "use `status` instead")]
pub status_time_format: String,
/// The format that will be used to display datetime formats in `pueue status`.
#[serde(default = "default_status_datetime_format")]
#[deprecated(since = "4.0.1", note = "use `status` instead")]
pub status_datetime_format: String,
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Neat! Clean deprecation :)
I think, I like this config format a lot better. The old one started to become a bit crowded and overloaded/unclear.

Comment thread pueue_lib/src/settings.rs
Comment on lines +141 to +156
pub struct Status {
/// Whether aliases specified in `pueue_aliases.yml` should be expanded in the `pueue status`
/// or shown in their short form.
#[serde(default = "Default::default")]
pub show_expanded_aliases: bool,
/// The max amount of lines each task get's in the `pueue status` view.
pub max_lines: Option<usize>,
/// The format that will be used to display time formats in `pueue status`.
#[serde(default = "default_status_time_format")]
pub time_format: String,
/// The format that will be used to display datetime formats in `pueue status`.
#[serde(default = "default_status_datetime_format")]
pub datetime_format: String,
#[serde(default = "default_additional_columns")]
pub additional_columns: Vec<String>,
}
Copy link
Copy Markdown
Owner

@Nukesor Nukesor Jan 11, 2026

Choose a reason for hiding this comment

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

I have a proposal to ease the transition.

How about, we make all of these optional?
We then expose getters for these fields on Settings, which return the Status configs (if set), with a fallback to the old configs.

When the old config options are removed in the future, we then simply return the default_status_datetime_format, etc. when a config value is None.

-> We move the default value logic from the deserialization step to a function.

This should make the whole replicate_deprecated_fields logic unnecessary and make this thing easier.

@FrancescElies
Copy link
Copy Markdown
Author

I didn’t forget your feedback. I’ll get back to you as soon as life allows me to sit down on this again

@Nukesor
Copy link
Copy Markdown
Owner

Nukesor commented Feb 13, 2026

No worries. I sometimes disappear for weeks at a time as well. Life happens and open-source is volunteer work ;D

Don't stress yourself.

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.

2 participants