Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): release date column for package listings #4203

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 31 additions & 6 deletions crates/fluvio-cli/src/client/hub/smartmodule/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub struct SmartModuleHubListOpts {
#[clap(flatten)]
output: OutputFormat,

/// Show exact time instead of relative time for `Published` column
#[arg(long, default_value = "false")]
exact_time: bool,

#[arg(long, hide = true)]
system: bool,

Expand All @@ -26,7 +30,12 @@ pub struct SmartModuleHubListOpts {
impl SmartModuleHubListOpts {
pub async fn process<O: Terminal + Debug + Send + Sync>(self, out: Arc<O>) -> Result<()> {
let pl = get_pkg_list(HUB_API_LIST_META, &self.remote, self.system).await?;
output::smartmodules_response_to_output(out, pl.packages, self.output.format)?;
output::smartmodules_response_to_output(
out,
pl.packages,
self.output.format,
self.exact_time,
)?;
Ok(())
}
}
Expand All @@ -45,13 +54,17 @@ mod output {
use anyhow::Result;

use fluvio_extension_common::output::OutputType;
use fluvio_extension_common::time::TimeElapsedFormatter;
use fluvio_extension_common::Terminal;
use fluvio_extension_common::output::TableOutputHandler;
use fluvio_extension_common::t_println;
use fluvio_hub_util::PackageMeta;
use fluvio_hub_util::{PackageMeta, PackageMetaExt};

#[derive(Serialize)]
struct ListSmartModules(Vec<PackageMeta>);
struct ListSmartModules {
pkgs: Vec<PackageMeta>,
exact_time: bool,
}

// -----------------------------------
// Format Output
Expand All @@ -62,11 +75,15 @@ mod output {
out: std::sync::Arc<O>,
list_smartmodules: Vec<PackageMeta>,
output_type: OutputType,
exact_time: bool,
) -> Result<()> {
debug!("smartmodules: {:#?}", list_smartmodules);

if !list_smartmodules.is_empty() {
let smartmodules = ListSmartModules(list_smartmodules);
let smartmodules = ListSmartModules {
pkgs: list_smartmodules,
exact_time,
};
out.render_list(&smartmodules, output_type)?;
Ok(())
} else {
Expand All @@ -81,7 +98,7 @@ mod output {
impl TableOutputHandler for ListSmartModules {
/// table header implementation
fn header(&self) -> Row {
Row::from(["SMARTMODULE", "Visibility"])
Row::from(["SmartModule", "Visibility", "Published"])
}

/// return errors in string format
Expand All @@ -91,12 +108,20 @@ mod output {

/// table content implementation
fn content(&self) -> Vec<Row> {
self.0
self.pkgs
.iter()
.map(|e| {
Row::from([
Cell::new(e.pkg_name()).set_alignment(CellAlignment::Left),
Cell::new(&e.visibility).set_alignment(CellAlignment::Left),
Cell::new(
e.published_at()
.map(|date| {
TimeElapsedFormatter::default()
.time_since_str(date, self.exact_time)
sehz marked this conversation as resolved.
Show resolved Hide resolved
})
.unwrap_or(String::from("N/A")),
),
])
})
.collect()
Expand Down
1 change: 1 addition & 0 deletions crates/fluvio-extension-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ installation = ["fluvio"]
[dependencies]
anyhow = { workspace = true }
async-trait = { workspace = true }
chrono = { workspace = true, features = ["clock"] }
clap = { workspace = true, features = ["std", "derive", "help", "usage", "error-context"], default-features = false }
comfy-table = { workspace = true }
serde = { workspace = true, features = ['derive'] }
Expand Down
2 changes: 2 additions & 0 deletions crates/fluvio-extension-common/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use clap::Parser;
mod hex_dump;
pub use self::hex_dump::*;

pub mod time;

use crate::output::OutputType;

#[derive(Debug, Parser, Default, Clone)]
Expand Down
158 changes: 158 additions & 0 deletions crates/fluvio-extension-common/src/common/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use chrono::{DateTime, Utc};

const SIMPLE_DATE_FORMAT: &str = "%Y/%m/%d";
const EXACT_FORMAT: &str = "%Y-%m-%d %H:%M:%S %Z";

pub struct TimeElapsedFormatter {
bot: DateTime<Utc>,
}

impl Default for TimeElapsedFormatter {
fn default() -> Self {
Self { bot: Utc::now() }
}
}

impl TimeElapsedFormatter {
pub fn new(bot: DateTime<Utc>) -> Self {
Self { bot }
}

/// Returns a string representation of the time elapsed between the-
/// `bot` (beginning of time) and the `date` provided.
///
/// If `exact` is true, the exact date and time will be returned
/// instead of the free-form expression.
///
/// Expressions supported are:
///
/// - `Just now`
/// - `X minutes ago`
/// - `X hours ago`
/// - `Yesterday`
/// - `X days ago`
/// - `X weeks ago`
/// - `X months ago`
/// - `X years ago`
/// - `YYYY/MM/DD` (if the date is more 2 a years ago)
pub fn time_since_str(&self, date: DateTime<Utc>, exact: bool) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

should add docs on rule

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here I have added docs: e96128c, I will use such statements in the docs as guide for free-form expressions.

let duration = self.bot.signed_duration_since(date);

if exact {
return date.format(EXACT_FORMAT).to_string();
}

if duration.num_weeks() >= 1 {
return date.format(SIMPLE_DATE_FORMAT).to_string();
}

if duration.num_days() >= 2 {
return format!("{} days ago", duration.num_days());
}

if duration.num_days() == 1 {
return String::from("Yesterday");
}

if duration.num_hours() >= 1 && duration.num_hours() < 24 {
return format!("{} hours ago", duration.num_hours());
}

if duration.num_minutes() >= 1 && duration.num_minutes() < 59 {
return format!("{} minutes ago", duration.num_minutes());
}

if duration.num_minutes() < 1 {
return String::from("Just now");
}

date.format(SIMPLE_DATE_FORMAT).to_string()
}
}

#[cfg(test)]
mod tests {
use chrono::{DateTime, Days, TimeZone, Timelike, Utc};

use crate::time::TimeElapsedFormatter;

fn december_17_1989_10_30_00() -> DateTime<Utc> {
Utc.with_ymd_and_hms(1989, 12, 17, 10, 30, 0)
.unwrap()
.to_utc()
}

fn make_fixed_time_elapsed_fmtr() -> TimeElapsedFormatter {
TimeElapsedFormatter::new(december_17_1989_10_30_00())
}

#[test]
fn determines_exact_date() {
let next_min = december_17_1989_10_30_00().minute() - 30;
let date = december_17_1989_10_30_00()
.with_minute(next_min)
.expect("failed to set min");
let formatted = make_fixed_time_elapsed_fmtr().time_since_str(date, true);

assert_eq!(formatted, "1989-12-17 10:00:00 UTC");
}

#[test]
fn simple_date_format_for_exceeding_a_week() {
let date = december_17_1989_10_30_00()
.checked_sub_days(Days::new(7))
.expect("failed to sub days");
let formatted = make_fixed_time_elapsed_fmtr().time_since_str(date, false);

assert_eq!(formatted, "1989/12/10");
Copy link
Contributor

@sehz sehz Oct 4, 2024

Choose a reason for hiding this comment

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

why it is different from determines_exact_date
Shouldn't it be 1989/12/10 00:00::00 UTC ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its just simpler to keep it compact, an output including time and thus timezone (e.g. 1989/12/10 00:00::00 UTC) would only appear if user uses exact-date.

Do you rather using exact date as fallback for longer periods?

Copy link
Contributor

Choose a reason for hiding this comment

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

I thought it automatically converts to exact if exceed certain time. This is only 7 days, so shouldn't be a week or something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could have week counter yeah, perhaps we should set boundaries?
I think we could render exact date for more than a year? Otherwise show months, weeks and days?

Copy link
Contributor

Choose a reason for hiding this comment

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

There is well established library like: https://www.npmjs.com/package/humanize-duration that specifies behavior

}

#[test]
fn renders_days_passed() {
let date = december_17_1989_10_30_00()
.checked_sub_days(Days::new(2))
.expect("failed to sub days");
let formatted = make_fixed_time_elapsed_fmtr().time_since_str(date, false);

assert_eq!(formatted, "2 days ago");
}

#[test]
fn renders_yesterday() {
let date = december_17_1989_10_30_00()
.checked_sub_days(Days::new(1))
.expect("failed to sub days");
let formatted = make_fixed_time_elapsed_fmtr().time_since_str(date, false);

assert_eq!(formatted, "Yesterday");
}

#[test]
fn renders_hours() {
let next_hour = december_17_1989_10_30_00().hour() - 8;
let date = december_17_1989_10_30_00()
.with_hour(next_hour)
.expect("failed to set hour");
let formatted = make_fixed_time_elapsed_fmtr().time_since_str(date, false);

assert_eq!(formatted, "8 hours ago");
}

#[test]
fn renders_minutes() {
let next_min = december_17_1989_10_30_00().minute() - 30;
let date = december_17_1989_10_30_00()
.with_minute(next_min)
.expect("failed to set min");
let formatted = make_fixed_time_elapsed_fmtr().time_since_str(date, false);

assert_eq!(formatted, "30 minutes ago");
}

#[test]
sehz marked this conversation as resolved.
Show resolved Hide resolved
fn renders_just_now() {
let formatted = TimeElapsedFormatter::default().time_since_str(Utc::now(), false);

assert_eq!(formatted, "Just now");
}
}
37 changes: 29 additions & 8 deletions crates/fluvio-hub-util/src/cmd/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ pub struct ConnectorHubListOpts {
#[clap(flatten)]
output: OutputFormat,

#[arg(long, hide = true)]
system: bool,
/// Show exact time instead of relative time for `Published` column
#[arg(long, default_value = "false")]
exact_time: bool,

#[arg(long, hide_short_help = true)]
remote: Option<String>,

#[arg(long, hide = true)]
system: bool,
}

impl ConnectorHubListOpts {
pub async fn process<O: Terminal + Debug + Send + Sync>(self, out: Arc<O>) -> Result<()> {
let pl = get_pkg_list(HUB_API_CONN_LIST, &self.remote, self.system).await?;
output::tableformat(out, pl.packages, self.output.format)?;
output::tableformat(out, pl.packages, self.output.format, self.exact_time)?;
Ok(())
}
}
Expand All @@ -42,13 +46,18 @@ mod output {
use anyhow::Result;

use fluvio_extension_common::output::OutputType;
use fluvio_extension_common::time::TimeElapsedFormatter;
use fluvio_extension_common::Terminal;
use fluvio_extension_common::output::TableOutputHandler;
use fluvio_extension_common::t_println;
use crate::PackageMeta;

use crate::{PackageMeta, PackageMetaExt};

#[derive(Serialize)]
struct ListConnectors(Vec<PackageMeta>);
struct ListConnectors {
pkgs: Vec<PackageMeta>,
exact_time: bool,
}

// -----------------------------------
// Format Output
Expand All @@ -59,11 +68,15 @@ mod output {
out: std::sync::Arc<O>,
list_pkgs: Vec<PackageMeta>,
output_type: OutputType,
exact_time: bool,
) -> Result<()> {
debug!("connectors: {:#?}", list_pkgs);

if !list_pkgs.is_empty() {
let connectors = ListConnectors(list_pkgs);
let connectors = ListConnectors {
pkgs: list_pkgs,
exact_time,
};
out.render_list(&connectors, output_type)?;
Ok(())
} else {
Expand All @@ -78,7 +91,7 @@ mod output {
impl TableOutputHandler for ListConnectors {
/// table header implementation
fn header(&self) -> Row {
Row::from(["CONNECTOR", "Visibility"])
Row::from(["Connector", "Visibility", "Published"])
}

/// return errors in string format
Expand All @@ -88,12 +101,20 @@ mod output {

/// table content implementation
fn content(&self) -> Vec<Row> {
self.0
self.pkgs
.iter()
.map(|e| {
Row::from([
Cell::new(e.pkg_name()).set_alignment(CellAlignment::Left),
Cell::new(&e.visibility).set_alignment(CellAlignment::Left),
Cell::new(
e.published_at()
.map(|date| {
TimeElapsedFormatter::default()
.time_since_str(date, self.exact_time)
})
.unwrap_or(String::from("N/A")),
),
])
})
.collect()
Expand Down
Loading