Skip to content

Commit 67e675d

Browse files
committed
fix(providers): refine refresh lifecycle UX
1 parent fa5b959 commit 67e675d

2 files changed

Lines changed: 327 additions & 38 deletions

File tree

crates/openshell-cli/src/run.rs

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4381,24 +4381,37 @@ pub async fn provider_refresh_status(
43814381
.into_inner();
43824382

43834383
if response.credentials.is_empty() {
4384-
println!("No refresh configuration found for provider '{name}'.");
4384+
if let Some(credential_key) = credential_key {
4385+
println!(
4386+
"No refresh configuration found for provider '{name}' credential '{credential_key}'."
4387+
);
4388+
} else {
4389+
println!("No refresh configurations found for provider '{name}'.");
4390+
}
43854391
return Ok(());
43864392
}
43874393

4388-
println!(
4389-
"{:<28} {:<28} {:<18} {:<20} {}",
4390-
"PROVIDER".bold(),
4391-
"CREDENTIAL_KEY".bold(),
4392-
"STRATEGY".bold(),
4393-
"EXPIRES_AT".bold(),
4394-
"STATUS".bold(),
4395-
);
4394+
println!("{}", refresh_status_header());
43964395
for status in response.credentials {
43974396
print_refresh_status_row(&status);
43984397
}
43994398
Ok(())
44004399
}
44014400

4401+
fn refresh_status_header() -> String {
4402+
format!(
4403+
"{:<24} {:<28} {:<28} {:<18} {:<20} {:<20} {:<20} {}",
4404+
"PROVIDER".bold(),
4405+
"CREDENTIAL_KEY".bold(),
4406+
"STRATEGY".bold(),
4407+
"STATUS".bold(),
4408+
"EXPIRES_AT".bold(),
4409+
"NEXT_REFRESH".bold(),
4410+
"LAST_REFRESH".bold(),
4411+
"LAST_ERROR".bold(),
4412+
)
4413+
}
4414+
44024415
pub struct ProviderRefreshConfigInput<'a> {
44034416
pub name: &'a str,
44044417
pub credential_key: &'a str,
@@ -4518,20 +4531,44 @@ fn provider_refresh_strategy(strategy: &str) -> Result<ProviderCredentialRefresh
45184531
}
45194532

45204533
fn print_refresh_status_row(status: &ProviderCredentialRefreshStatus) {
4534+
println!("{}", refresh_status_row(status));
4535+
}
4536+
4537+
fn refresh_status_row(status: &ProviderCredentialRefreshStatus) -> String {
45214538
let strategy = ProviderCredentialRefreshStrategy::try_from(status.strategy)
45224539
.unwrap_or(ProviderCredentialRefreshStrategy::Unspecified);
4523-
println!(
4524-
"{:<28} {:<28} {:<18} {:<20} {}",
4540+
format!(
4541+
"{:<24} {:<28} {:<28} {:<18} {:<20} {:<20} {:<20} {}",
45254542
status.provider_name,
45264543
status.credential_key,
45274544
provider_refresh_strategy_name(strategy),
4528-
if status.expires_at_ms > 0 {
4529-
format_epoch_ms(status.expires_at_ms)
4530-
} else {
4531-
"-".to_string()
4532-
},
4533-
status.status
4534-
);
4545+
status.status,
4546+
format_optional_epoch_ms(status.expires_at_ms),
4547+
format_optional_epoch_ms(status.next_refresh_at_ms),
4548+
format_optional_epoch_ms(status.last_refresh_at_ms),
4549+
truncate_status_field(&status.last_error, 72),
4550+
)
4551+
}
4552+
4553+
fn format_optional_epoch_ms(ms: i64) -> String {
4554+
if ms > 0 {
4555+
format_epoch_ms(ms)
4556+
} else {
4557+
"-".to_string()
4558+
}
4559+
}
4560+
4561+
fn truncate_status_field(value: &str, max_chars: usize) -> String {
4562+
if value.is_empty() {
4563+
return "-".to_string();
4564+
}
4565+
let mut chars = value.chars();
4566+
let truncated = chars.by_ref().take(max_chars).collect::<String>();
4567+
if chars.next().is_some() {
4568+
format!("{truncated}...")
4569+
} else {
4570+
truncated
4571+
}
45354572
}
45364573

45374574
fn provider_refresh_strategy_name(strategy: ProviderCredentialRefreshStrategy) -> &'static str {
@@ -6556,8 +6593,9 @@ mod tests {
65566593
git_sync_files, http_health_check, image_requests_gpu, import_local_package_mtls_bundle,
65576594
inferred_provider_type, package_managed_tls_dirs, parse_cli_setting_value,
65586595
parse_credential_pairs, plaintext_gateway_is_remote, progress_step_from_metadata,
6559-
provisioning_timeout_message, ready_false_condition_message, resolve_from,
6560-
sandbox_should_persist, service_expose_status_error, service_url_for_gateway,
6596+
provisioning_timeout_message, ready_false_condition_message, refresh_status_header,
6597+
refresh_status_row, resolve_from, sandbox_should_persist, service_expose_status_error,
6598+
service_url_for_gateway,
65616599
};
65626600
use crate::TEST_ENV_LOCK;
65636601
use hyper::StatusCode;
@@ -6576,7 +6614,8 @@ mod tests {
65766614
PROGRESS_STEP_STARTING_SANDBOX,
65776615
};
65786616
use openshell_core::proto::{
6579-
Provider, SandboxCondition, SandboxStatus, datamodel::v1::ObjectMeta,
6617+
Provider, ProviderCredentialRefreshStatus, ProviderCredentialRefreshStrategy,
6618+
SandboxCondition, SandboxStatus, datamodel::v1::ObjectMeta,
65806619
};
65816620

65826621
struct EnvVarGuard {
@@ -6732,6 +6771,34 @@ mod tests {
67326771
assert_eq!(progress_step_from_metadata("driver-private-step"), None);
67336772
}
67346773

6774+
#[test]
6775+
fn refresh_status_table_includes_operational_fields() {
6776+
let header = refresh_status_header();
6777+
assert!(header.contains("NEXT_REFRESH"));
6778+
assert!(header.contains("LAST_REFRESH"));
6779+
assert!(header.contains("LAST_ERROR"));
6780+
6781+
let row = refresh_status_row(&ProviderCredentialRefreshStatus {
6782+
provider_name: "my-graph".to_string(),
6783+
provider_id: "provider-id".to_string(),
6784+
credential_key: "MS_GRAPH_ACCESS_TOKEN".to_string(),
6785+
strategy: ProviderCredentialRefreshStrategy::Oauth2ClientCredentials as i32,
6786+
status: "error".to_string(),
6787+
expires_at_ms: 1_767_225_600_000,
6788+
next_refresh_at_ms: 1_767_225_660_000,
6789+
last_refresh_at_ms: 1_767_225_000_000,
6790+
last_error: "token endpoint returned a very long error message that should be truncated for table readability"
6791+
.to_string(),
6792+
});
6793+
6794+
assert!(row.contains("my-graph"));
6795+
assert!(row.contains("MS_GRAPH_ACCESS_TOKEN"));
6796+
assert!(row.contains("oauth2_client_credentials"));
6797+
assert!(row.contains("error"));
6798+
assert!(row.contains("2026-01-01 00:00:00"));
6799+
assert!(row.contains("..."));
6800+
}
6801+
67356802
#[cfg(feature = "dev-settings")]
67366803
#[test]
67376804
fn parse_cli_setting_value_parses_bool_aliases() {

0 commit comments

Comments
 (0)