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

PXP-628: [CLI]: Display new release info banner #164

Merged
merged 29 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
95b1d02
feat: add update func
mfauzaan Oct 31, 2023
2697ae3
test: add test cases for dependent functions
mfauzaan Nov 1, 2023
97a4464
style: fix clippy issues
mfauzaan Nov 1, 2023
9427dfc
test: updated snapshots
mfauzaan Nov 1, 2023
aa57f05
test: revert snapshots
mfauzaan Nov 1, 2023
c039d88
test: remove report flag
mfauzaan Nov 2, 2023
70eff49
test: update snapshot of release info
mfauzaan Nov 2, 2023
1edcbeb
test: updated completions
mfauzaan Nov 2, 2023
32f2578
feat: added handling for test cases if there is no config file
mfauzaan Nov 2, 2023
6d61714
feat: changed to remove WKCLIError
mfauzaan Nov 3, 2023
01b4098
feat: updated to print message independtly
mfauzaan Nov 3, 2023
b58bd5d
test: updated snapshots
mfauzaan Nov 3, 2023
201e303
feat: updated checked at snapshot
mfauzaan Nov 3, 2023
4ad53fc
test: update snapshots of config list
mfauzaan Nov 3, 2023
b4277f3
test; updated last updated check date and time
mfauzaan Nov 3, 2023
8b8eca9
fix: update version order from current to release info
mfauzaan Nov 3, 2023
c2f4a90
fix: change to every 7 days
mfauzaan Nov 3, 2023
95c589d
feat: get notification update every 72hours
mfauzaan Nov 3, 2023
cdf6aa6
feat: change to ePrintln
mfauzaan Nov 3, 2023
c651c59
style: fix clippy
mfauzaan Nov 3, 2023
d3a3ae1
refactor: remove all release info from config
mfauzaan Nov 3, 2023
28cb47c
test: update test cases to match new config
mfauzaan Nov 3, 2023
85cae7b
test: update test casse
mfauzaan Nov 3, 2023
598d24f
refactor: update get_latest_release_info paramaters
mfauzaan Nov 6, 2023
6eac3c7
feat: update the url to fetch beta version
mfauzaan Nov 6, 2023
4b4a073
revert: url latest changes
mfauzaan Nov 6, 2023
95b3869
feat: update error message for brew upgrade
mfauzaan Nov 6, 2023
69e3a34
feat: upgrade snapshots to have new update message
mfauzaan Nov 6, 2023
4848b56
test: update all test snapshots for showing banners
mfauzaan Nov 6, 2023
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
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.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ miette = { version = "5.10.0", features = ["fancy"] }
ignore = "0.4.20"
rayon = "1.7.0"
similar = { version = "2.2.1", features = ["inline"] }
semver = "1.0.20"

# Telemetry
wukong-telemetry-macro = { path = "../telemetry-macro" }
Expand Down
2 changes: 1 addition & 1 deletion cli/completions/bash/wukong.bash
Original file line number Diff line number Diff line change
Expand Up @@ -2004,4 +2004,4 @@ _wukong() {
esac
}

complete -F _wukong -o bashdefault -o default wukong
complete -F _wukong -o nosort -o bashdefault -o default wukong
20 changes: 10 additions & 10 deletions cli/completions/fish/wukong.fish
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from info" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from info" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from info" -s h -l help -d 'Print help'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l namespace -d '(optional) The namespace to deploy to' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l version -d '(optional) The version that the deployment will perform against' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l namespace -d '(optional) The namespace to deploy to' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l version -d '(optional) The version that the deployment will perform against' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -s s -l since -d 'Show logs lines newer from relative duration, e.g 5m, 1h, 1d. Also accept datetime in RFC 3339 format' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -s u -l until -d 'Show logs lines older than relative duration, e.g 30m, 2h, 2d. Also accept datetime in RFC 3339 format' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from logs" -l limit -d 'Limiting the number of log entries to return' -r
Expand Down Expand Up @@ -91,8 +91,8 @@ complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from connect; and not __fish_seen_subcommand_from help" -f -a "list" -d 'Listing the currently running Elixir instances, normally under a GKE Pod'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from connect; and not __fish_seen_subcommand_from help" -f -a "connect" -d 'Start the interactive session to connect to the remote Elixir instance'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from connect; and not __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s v -l verbose -d 'Use verbos output. More output per occurrence.

Expand All @@ -104,8 +104,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help (see more with \'--help\')'
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l namespace -d '(optional) The namespace to list the running instances' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -l version -d '(optional) The version of the application to filter the returning running instances' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from application; and __fish_seen_subcommand_from instances; and __fish_seen_subcommand_from connect" -s v -l verbose -d 'Use verbos output. More output per occurrence.

Expand Down Expand Up @@ -206,8 +206,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from list" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from list" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l artifact -d 'The build artifact that the deployment will use' -r
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s v -l verbose -d 'Use verbos output. More output per occurrence.
Expand All @@ -220,8 +220,8 @@ By default, it\'ll only report errors.
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s q -l quiet -d 'Do not print log message'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -l canary -d 'Use the Canary channel API'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from execute" -s h -l help -d 'Print help'
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod ,staging }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue ,green }"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l namespace -d 'The namespace to deploy to' -r -f -a "{prod '',staging ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -l version -d 'The version that the deployment will perform against' -r -f -a "{blue '',green ''}"
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -s a -l application -d 'Override the application name that the CLI will perform the command against. If the flag is not used, then the CLI will use the default application name from the config' -r
complete -c wukong -n "__fish_seen_subcommand_from deployment; and __fish_seen_subcommand_from rollback" -s v -l verbose -d 'Use verbos output. More output per occurrence.

Expand Down
10 changes: 8 additions & 2 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
},
config::{ApiChannel, Config},
error::WKCliError,
update,
};

mod application;
Expand Down Expand Up @@ -109,7 +110,7 @@ impl ClapApp {
};
debug!("API channel: {:?}", channel);

match &self.command_group {
let command = match &self.command_group {
CommandGroup::Init => handle_init(channel).await,
CommandGroup::Completion { shell } => handle_completion(*shell),
CommandGroup::Login => handle_login().await,
Expand All @@ -123,7 +124,12 @@ impl ClapApp {
CommandGroup::Config(config) => config.handle_command(),
CommandGroup::Dev(dev) => dev.handle_command(self).await,
CommandGroup::Tui => handle_tui(channel).await,
}
};

// Check for CLI updates:
update::check_for_update().await;

command
}
}

Expand Down
8 changes: 8 additions & 0 deletions cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct Config {
pub core: CoreConfig,
pub auth: Option<AuthConfig>,
pub vault: Option<VaultConfig>,
pub update_check: Option<UpdateCheck>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)]
Expand Down Expand Up @@ -94,6 +95,12 @@ pub struct AuthConfig {
pub refresh_token: String,
}

// ReleaseInfo stores information about a release
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct UpdateCheck {
pub last_update_checked_at: String,
}

impl Default for Config {
fn default() -> Self {
let mut home_dir = dirs::home_dir().unwrap();
Expand All @@ -107,6 +114,7 @@ impl Default for Config {
},
auth: None,
vault: None,
update_check: None,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod error;
mod loader;
mod logger;
pub mod output;
mod update;
mod utils;
mod wukong_client;

Expand Down
196 changes: 196 additions & 0 deletions cli/src/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use crate::{
config::{Config, UpdateCheck},
utils::compare_with_current_time,
};
use aion::*;
use chrono::Utc;
use clap::crate_version;
use log::debug;
use owo_colors::OwoColorize;
use reqwest::Client;
use semver::Version;
use serde::{Deserialize, Serialize};

const WUKONG_GITHUB_REPO: &str = "mindvalley/wukong-cli";
const GITHUB_API_URL: &str = "https://api.github.com";

#[derive(Debug, Serialize, Deserialize)]
pub struct GithubLatestReleaseInfo {
pub tag_name: String,
pub url: String,
pub published_at: String,
pub html_url: String,
}

// check_for_update checks whether this wukong has had a newer release on Github
pub async fn check_for_update() {
let last_updated_at = get_last_update_checked_at();

if let Some(last_updated_at) = last_updated_at {
let last_update_checked_since = compare_with_current_time(&last_updated_at);

if last_update_checked_since >= -(72.hours()) {
debug!("No need to check for update");
return;
}
}

debug!("Checking for update");

if let Some(latest_release_info) = get_latest_release_info(GITHUB_API_URL).await {
update_last_update_checked_at();
print_update_message(latest_release_info);
}
}

fn print_update_message(latest_release_info: GithubLatestReleaseInfo) {
let current_version = crate_version!().to_string();

let has_update = version_greater_than(&latest_release_info.tag_name, &current_version);

if has_update {
debug!("New release found");
eprintln!(
"{} {} {} {}",
"A new release of wukong is available:".yellow(),
current_version.cyan(),
"→".cyan(),
latest_release_info.tag_name.cyan(),
);

eprintln!("To upgrade, run: brew update && brew upgrade wukong");
eprintln!("{}", latest_release_info.url.yellow());
} else {
debug!("No new release found");
}
}

fn get_last_update_checked_at() -> Option<String> {
let config = Config::load_from_default_path().map_err(|e| {
debug!("Error: {:?}", e);
});

if let Ok(config) = config {
if let Some(config) = config.update_check {
return Some(config.last_update_checked_at);
}
}

None
}

fn update_last_update_checked_at() {
let config = Config::load_from_default_path();

match config {
Ok(mut config) => {
config.update_check = Some(UpdateCheck {
last_update_checked_at: Utc::now().to_rfc3339(),
});

let _ = config.save_to_default_path().map_err(|e| {
debug!("Error: {:?}", e);
});
}
Err(e) => {
debug!("Error: {:?}", e);
}
};
}

fn version_greater_than(new_version: &str, current_version: &str) -> bool {
if let (Ok(new_version), Ok(current_version)) =
(Version::parse(new_version), Version::parse(current_version))
{
new_version > current_version
} else {
false
}
}

async fn get_latest_release_info(github_api_url: &str) -> Option<GithubLatestReleaseInfo> {
let client = Client::new();

let url = format!(
"{}/repos/{}/releases/latest",
github_api_url, WUKONG_GITHUB_REPO
);

let response = client
.get(&url)
.header("user-agent", "wukong-cli")
.send()
.await
.map_err(|e| {
debug!("Error: {:?}", e);
});

if let Ok(response) = response {
let github_release_info = response
.json::<GithubLatestReleaseInfo>()
.await
.map_err(|e| {
debug!("Error: {:?}", e);
});

if let Ok(github_release_info) = github_release_info {
return Some(github_release_info);
}
}

None
}

#[cfg(test)]
mod tests {
use super::*;
use httpmock::prelude::*;

#[test]
fn test_version_greater_than() {
// Test cases for version comparison.
assert!(version_greater_than("1.1.0", "1.0.0"));
assert!(!version_greater_than("1.0.0", "1.1.0"));
assert!(!version_greater_than("1.0.0", "1.0.0"));
assert!(!version_greater_than("invalid", "1.0.0"));
assert!(!version_greater_than("1.0.0", "invalid"));
}

#[tokio::test]
async fn test_get_latest_release_info() {
let server = MockServer::start();

let api_resp = r#"{
"url": "https://github.com/mindvalley/wukong-cli/releases/tag/1.2.0",
"html_url": "https://github.com/mindvalley/wukong-cli/releases/tag/1.2.0",
"id": 120063991,
"tag_name": "1.2.0",
"published_at": "2023-09-06T07:08:46Z",
"body": null
}"#;

let url = format!("/repos/{}/releases/latest", WUKONG_GITHUB_REPO);

let mock_server = server.mock(|when, then| {
when.method(GET)
.path(url)
.header("user-agent", "wukong-cli");
then.status(200)
.header("content-type", "application/json; charset=UTF-8")
.body(api_resp);
});
println!("{:?}", &server.base_url());

let release_info = get_latest_release_info(&server.base_url()).await;

mock_server.assert();

let release_info = release_info.unwrap();
assert_eq!(release_info.tag_name, "1.2.0");
assert_eq!(release_info.published_at, "2023-09-06T07:08:46Z");
assert_eq!(
release_info.url,
"https://github.com/mindvalley/wukong-cli/releases/tag/1.2.0"
);
}
}
9 changes: 9 additions & 0 deletions cli/tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ id_token = "id_token"
access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"

[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
"#,
)
.unwrap();
Expand Down Expand Up @@ -200,6 +203,9 @@ id_token = "id_token"
access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"

[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
"#,
)
.unwrap();
Expand Down Expand Up @@ -230,6 +236,9 @@ id_token = "id_token"
access_token = "access_token"
expiry_time = "2023-02-19T06:55:51.501915+00:00"
refresh_token = "refresh_token"

[update_check]
last_update_checked_at = "2023-11-03T08:51:02.800908+00:00"
"#,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
source: cli/tests/application.rs
expression: "std::str::from_utf8(&output.stderr).unwrap()"
---
A new release of wukong is available: 2.0.0-alpha2 → 2.0.1
To upgrade, run: brew update && brew upgrade wukong
https://api.github.com/repos/mindvalley/wukong-cli/releases/128072901
Error - You are un-authenticated.
Suggestion -
Your access token is invalid. Run "wukong login" to authenticate with your okta account.
Expand Down
Loading