fix: improve usage of indicatif for tracking progress (#101)
The multiprogress wasn't used properly and leading to conflicting progress bars (within our own progress bars, as well as the log wrapper). This PR introduce a layer on top of `indicatif::MultiProgress` to properly handle sections of progress bars, where we can dynamically add/update/remove progress bars from any sections. We can see in the demo that new sections + progress bars are added on the fly and that extra logs (e.g. info logs) are appended on top of the progress bars. Progress are also grouped together based on their parent score. Co-authored-by: Ian Letourneau <letourneau.ian@gmail.com> Co-authored-by: johnride <jg@nationtech.io> Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/101
This commit is contained in:
parent
1de96027a1
commit
f0ed548755
@ -10,13 +10,16 @@ use super::{
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum HarmonyEvent {
|
pub enum HarmonyEvent {
|
||||||
HarmonyStarted,
|
HarmonyStarted,
|
||||||
|
HarmonyFinished,
|
||||||
InterpretExecutionStarted {
|
InterpretExecutionStarted {
|
||||||
|
execution_id: String,
|
||||||
topology: String,
|
topology: String,
|
||||||
interpret: String,
|
interpret: String,
|
||||||
score: String,
|
score: String,
|
||||||
message: String,
|
message: String,
|
||||||
},
|
},
|
||||||
InterpretExecutionFinished {
|
InterpretExecutionFinished {
|
||||||
|
execution_id: String,
|
||||||
topology: String,
|
topology: String,
|
||||||
interpret: String,
|
interpret: String,
|
||||||
score: String,
|
score: String,
|
||||||
|
@ -5,6 +5,7 @@ use serde::Serialize;
|
|||||||
use serde_value::Value;
|
use serde_value::Value;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
data::Id,
|
||||||
instrumentation::{self, HarmonyEvent},
|
instrumentation::{self, HarmonyEvent},
|
||||||
interpret::{Interpret, InterpretError, Outcome},
|
interpret::{Interpret, InterpretError, Outcome},
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
@ -20,9 +21,11 @@ pub trait Score<T: Topology>:
|
|||||||
inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
topology: &T,
|
topology: &T,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let id = Id::default();
|
||||||
let interpret = self.create_interpret();
|
let interpret = self.create_interpret();
|
||||||
|
|
||||||
instrumentation::instrument(HarmonyEvent::InterpretExecutionStarted {
|
instrumentation::instrument(HarmonyEvent::InterpretExecutionStarted {
|
||||||
|
execution_id: id.clone().to_string(),
|
||||||
topology: topology.name().into(),
|
topology: topology.name().into(),
|
||||||
interpret: interpret.get_name().to_string(),
|
interpret: interpret.get_name().to_string(),
|
||||||
score: self.name(),
|
score: self.name(),
|
||||||
@ -32,6 +35,7 @@ pub trait Score<T: Topology>:
|
|||||||
let result = interpret.execute(inventory, topology).await;
|
let result = interpret.execute(inventory, topology).await;
|
||||||
|
|
||||||
instrumentation::instrument(HarmonyEvent::InterpretExecutionFinished {
|
instrumentation::instrument(HarmonyEvent::InterpretExecutionFinished {
|
||||||
|
execution_id: id.clone().to_string(),
|
||||||
topology: topology.name().into(),
|
topology: topology.name().into(),
|
||||||
interpret: interpret.get_name().to_string(),
|
interpret: interpret.get_name().to_string(),
|
||||||
score: self.name(),
|
score: self.name(),
|
||||||
|
@ -59,7 +59,7 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for ArgoInterpret {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(Outcome::success(format!(
|
Ok(Outcome::success(format!(
|
||||||
"Successfully installed ArgoCD and {} Applications",
|
"ArgoCD installed with {} applications",
|
||||||
self.argo_apps.len()
|
self.argo_apps.len()
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
Ok(Outcome::success("successfully created app".to_string()))
|
Ok(Outcome::success("Application created".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> InterpretName {
|
fn get_name(&self) -> InterpretName {
|
||||||
|
@ -46,7 +46,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!("Application: {}", self.application.name())
|
format!("{} [ApplicationScore]", self.application.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ impl<T: Topology + HelmCommand> Score<T> for HelmChartScore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!("{} {} HelmChartScore", self.release_name, self.chart_name)
|
format!("{} [HelmChartScore]", self.release_name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,19 +225,20 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret {
|
|||||||
match status {
|
match status {
|
||||||
helm_wrapper_rs::HelmDeployStatus::Deployed => Ok(Outcome::new(
|
helm_wrapper_rs::HelmDeployStatus::Deployed => Ok(Outcome::new(
|
||||||
InterpretStatus::SUCCESS,
|
InterpretStatus::SUCCESS,
|
||||||
"Helm Chart deployed".to_string(),
|
format!("Helm Chart {} deployed", self.score.release_name),
|
||||||
)),
|
)),
|
||||||
helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::new(
|
helm_wrapper_rs::HelmDeployStatus::PendingInstall => Ok(Outcome::new(
|
||||||
InterpretStatus::RUNNING,
|
InterpretStatus::RUNNING,
|
||||||
"Helm Chart Pending install".to_string(),
|
format!("Helm Chart {} pending install...", self.score.release_name),
|
||||||
)),
|
)),
|
||||||
helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::new(
|
helm_wrapper_rs::HelmDeployStatus::PendingUpgrade => Ok(Outcome::new(
|
||||||
InterpretStatus::RUNNING,
|
InterpretStatus::RUNNING,
|
||||||
"Helm Chart pending upgrade".to_string(),
|
format!("Helm Chart {} pending upgrade...", self.score.release_name),
|
||||||
)),
|
|
||||||
helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new(
|
|
||||||
"Failed to install helm chart".to_string(),
|
|
||||||
)),
|
)),
|
||||||
|
helm_wrapper_rs::HelmDeployStatus::Failed => Err(InterpretError::new(format!(
|
||||||
|
"Helm Chart {} installation failed",
|
||||||
|
self.score.release_name
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,10 @@ impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Score<T>
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"ApplicationMonitoringScore".to_string()
|
format!(
|
||||||
|
"{} monitoring [ApplicationMonitoringScore]",
|
||||||
|
self.application.name()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +64,9 @@ impl<T: Topology + PrometheusApplicationMonitoring<CRDPrometheus>> Interpret<T>
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(outcome) => match outcome {
|
Ok(outcome) => match outcome {
|
||||||
PreparationOutcome::Success { details } => Ok(Outcome::success(details)),
|
PreparationOutcome::Success { details: _ } => {
|
||||||
|
Ok(Outcome::success("Prometheus installed".into()))
|
||||||
|
}
|
||||||
PreparationOutcome::Noop => Ok(Outcome::noop()),
|
PreparationOutcome::Noop => Ok(Outcome::noop()),
|
||||||
},
|
},
|
||||||
Err(err) => Err(InterpretError::from(err)),
|
Err(err) => Err(InterpretError::from(err)),
|
||||||
|
@ -28,7 +28,7 @@ impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"NtfyScore".to_string()
|
"alert receiver [NtfyScore]".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +119,7 @@ impl<T: Topology + HelmCommand + K8sclient> Interpret<T> for NtfyInterpret {
|
|||||||
|
|
||||||
debug!("exec into pod done");
|
debug!("exec into pod done");
|
||||||
|
|
||||||
Ok(Outcome::success("installed ntfy".to_string()))
|
Ok(Outcome::success("Ntfy installed".to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> InterpretName {
|
fn get_name(&self) -> InterpretName {
|
||||||
|
@ -61,7 +61,7 @@ impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> S
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"CRDApplicationAlertingScore".into()
|
"prometheus alerting [CRDAlertingScore]".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ impl<T: Topology + K8sclient + PrometheusApplicationMonitoring<CRDPrometheus>> I
|
|||||||
self.install_monitors(self.service_monitors.clone(), &client)
|
self.install_monitors(self.service_monitors.clone(), &client)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(Outcome::success(
|
Ok(Outcome::success(
|
||||||
"deployed application monitoring composants".to_string(),
|
"K8s monitoring components installed".to_string(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ impl<T: Topology + TenantManager> Score<T> for TenantScore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
format!("{} TenantScore", self.config.name)
|
format!("{} [TenantScore]", self.config.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,8 +47,8 @@ impl<T: Topology + TenantManager> Interpret<T> for TenantInterpret {
|
|||||||
topology.provision_tenant(&self.tenant_config).await?;
|
topology.provision_tenant(&self.tenant_config).await?;
|
||||||
|
|
||||||
Ok(Outcome::success(format!(
|
Ok(Outcome::success(format!(
|
||||||
"Successfully provisioned tenant {} with id {}",
|
"Tenant provisioned with id '{}'",
|
||||||
self.tenant_config.name, self.tenant_config.id
|
self.tenant_config.id
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,15 @@ use harmony::{
|
|||||||
instrumentation::{self, HarmonyEvent},
|
instrumentation::{self, HarmonyEvent},
|
||||||
topology::TopologyStatus,
|
topology::TopologyStatus,
|
||||||
};
|
};
|
||||||
use indicatif::{MultiProgress, ProgressBar};
|
use indicatif::MultiProgress;
|
||||||
use indicatif_log_bridge::LogWrapper;
|
use indicatif_log_bridge::LogWrapper;
|
||||||
use std::{
|
use std::sync::{Arc, Mutex};
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::progress;
|
use crate::progress::{IndicatifProgressTracker, ProgressTracker};
|
||||||
|
|
||||||
pub fn init() -> tokio::task::JoinHandle<()> {
|
pub fn init() -> tokio::task::JoinHandle<()> {
|
||||||
configure_logger();
|
let base_progress = configure_logger();
|
||||||
let handle = tokio::spawn(handle_events());
|
let handle = tokio::spawn(handle_events(base_progress));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() {
|
||||||
@ -24,32 +21,45 @@ pub fn init() -> tokio::task::JoinHandle<()> {
|
|||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_logger() {
|
fn configure_logger() -> MultiProgress {
|
||||||
let logger =
|
let logger =
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
||||||
let level = logger.filter();
|
let level = logger.filter();
|
||||||
let multi = MultiProgress::new();
|
let progress = MultiProgress::new();
|
||||||
LogWrapper::new(multi.clone(), logger).try_init().unwrap();
|
|
||||||
|
LogWrapper::new(progress.clone(), logger)
|
||||||
|
.try_init()
|
||||||
|
.unwrap();
|
||||||
log::set_max_level(level);
|
log::set_max_level(level);
|
||||||
|
|
||||||
|
progress
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_events() {
|
async fn handle_events(base_progress: MultiProgress) {
|
||||||
instrumentation::subscribe("Harmony CLI Logger", {
|
let progress_tracker = Arc::new(IndicatifProgressTracker::new(base_progress.clone()));
|
||||||
let sections: Arc<Mutex<HashMap<String, MultiProgress>>> =
|
let preparing_topology = Arc::new(Mutex::new(false));
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
let current_score: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
|
||||||
let progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
|
|
||||||
|
instrumentation::subscribe("Harmony CLI Logger", {
|
||||||
move |event| {
|
move |event| {
|
||||||
let sections_clone = Arc::clone(§ions);
|
let progress_tracker = Arc::clone(&progress_tracker);
|
||||||
let progress_bars_clone = Arc::clone(&progress_bars);
|
let preparing_topology = Arc::clone(&preparing_topology);
|
||||||
|
let current_score = Arc::clone(¤t_score);
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let mut sections = sections_clone.lock().unwrap();
|
let mut preparing_topology = preparing_topology.lock().unwrap();
|
||||||
let mut progress_bars = progress_bars_clone.lock().unwrap();
|
let mut current_score = current_score.lock().unwrap();
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
HarmonyEvent::HarmonyStarted => {}
|
HarmonyEvent::HarmonyStarted => {}
|
||||||
|
HarmonyEvent::HarmonyFinished => {
|
||||||
|
progress_tracker.add_section(
|
||||||
|
"harmony-summary",
|
||||||
|
&format!("\n{} Harmony completed\n\n", crate::theme::EMOJI_HARMONY),
|
||||||
|
);
|
||||||
|
progress_tracker.add_section("harmony-finished", "\n\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
HarmonyEvent::TopologyStateChanged {
|
HarmonyEvent::TopologyStateChanged {
|
||||||
topology,
|
topology,
|
||||||
status,
|
status,
|
||||||
@ -60,111 +70,95 @@ async fn handle_events() {
|
|||||||
match status {
|
match status {
|
||||||
TopologyStatus::Queued => {}
|
TopologyStatus::Queued => {}
|
||||||
TopologyStatus::Preparing => {
|
TopologyStatus::Preparing => {
|
||||||
let section = progress::new_section(format!(
|
progress_tracker.add_section(
|
||||||
"{} Preparing environment: {topology}...",
|
§ion_key,
|
||||||
crate::theme::EMOJI_TOPOLOGY,
|
&format!(
|
||||||
));
|
"\n{} Preparing environment: {topology}...",
|
||||||
(*sections).insert(section_key, section);
|
crate::theme::EMOJI_TOPOLOGY
|
||||||
|
),
|
||||||
|
);
|
||||||
|
(*preparing_topology) = true;
|
||||||
}
|
}
|
||||||
TopologyStatus::Success => {
|
TopologyStatus::Success => {
|
||||||
let section = (*sections).get(§ion_key).unwrap();
|
(*preparing_topology) = false;
|
||||||
let progress = progress::add_spinner(section, "".into());
|
progress_tracker.add_task(§ion_key, "topology-success", "");
|
||||||
|
progress_tracker
|
||||||
progress::success(
|
.finish_task("topology-success", &message.unwrap_or("".into()));
|
||||||
section,
|
|
||||||
Some(progress),
|
|
||||||
message.unwrap_or("".into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
(*sections).remove(§ion_key);
|
|
||||||
}
|
}
|
||||||
TopologyStatus::Noop => {
|
TopologyStatus::Noop => {
|
||||||
let section = (*sections).get(§ion_key).unwrap();
|
(*preparing_topology) = false;
|
||||||
let progress = progress::add_spinner(section, "".into());
|
progress_tracker.add_task(§ion_key, "topology-skip", "");
|
||||||
|
progress_tracker
|
||||||
progress::skip(
|
.skip_task("topology-skip", &message.unwrap_or("".into()));
|
||||||
section,
|
|
||||||
Some(progress),
|
|
||||||
message.unwrap_or("".into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
(*sections).remove(§ion_key);
|
|
||||||
}
|
}
|
||||||
TopologyStatus::Error => {
|
TopologyStatus::Error => {
|
||||||
let section = (*sections).get(§ion_key).unwrap();
|
progress_tracker.add_task(§ion_key, "topology-error", "");
|
||||||
let progress = progress::add_spinner(section, "".into());
|
(*preparing_topology) = false;
|
||||||
|
progress_tracker
|
||||||
progress::error(
|
.fail_task("topology-error", &message.unwrap_or("".into()));
|
||||||
section,
|
|
||||||
Some(progress),
|
|
||||||
message.unwrap_or("".into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
(*sections).remove(§ion_key);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HarmonyEvent::InterpretExecutionStarted {
|
HarmonyEvent::InterpretExecutionStarted {
|
||||||
|
execution_id: task_key,
|
||||||
topology,
|
topology,
|
||||||
interpret,
|
interpret: _,
|
||||||
score,
|
score,
|
||||||
message,
|
message,
|
||||||
} => {
|
} => {
|
||||||
let section_key = if (*sections).contains_key(&topology_key(&topology)) {
|
let is_key_topology = (*preparing_topology)
|
||||||
|
&& progress_tracker.contains_section(&topology_key(&topology));
|
||||||
|
let is_key_current_score = current_score.is_some()
|
||||||
|
&& progress_tracker
|
||||||
|
.contains_section(&score_key(¤t_score.clone().unwrap()));
|
||||||
|
let is_key_score = progress_tracker.contains_section(&score_key(&score));
|
||||||
|
|
||||||
|
let section_key = if is_key_topology {
|
||||||
topology_key(&topology)
|
topology_key(&topology)
|
||||||
} else if (*sections).contains_key(&score_key(&score)) {
|
} else if is_key_current_score {
|
||||||
score_key(&interpret)
|
score_key(¤t_score.clone().unwrap())
|
||||||
|
} else if is_key_score {
|
||||||
|
score_key(&score)
|
||||||
} else {
|
} else {
|
||||||
|
(*current_score) = Some(score.clone());
|
||||||
let key = score_key(&score);
|
let key = score_key(&score);
|
||||||
let section = progress::new_section(format!(
|
progress_tracker.add_section(
|
||||||
"\n{} Interpreting score: {score}...",
|
&key,
|
||||||
crate::theme::EMOJI_SCORE,
|
&format!(
|
||||||
));
|
"{} Interpreting score: {score}...",
|
||||||
(*sections).insert(key.clone(), section);
|
crate::theme::EMOJI_SCORE
|
||||||
|
),
|
||||||
|
);
|
||||||
key
|
key
|
||||||
};
|
};
|
||||||
let section = (*sections).get(§ion_key).unwrap();
|
|
||||||
let progress_bar = progress::add_spinner(section, message);
|
|
||||||
|
|
||||||
(*progress_bars).insert(interpret_key(&interpret), progress_bar);
|
progress_tracker.add_task(§ion_key, &task_key, &message);
|
||||||
}
|
}
|
||||||
HarmonyEvent::InterpretExecutionFinished {
|
HarmonyEvent::InterpretExecutionFinished {
|
||||||
topology,
|
execution_id: task_key,
|
||||||
interpret,
|
topology: _,
|
||||||
|
interpret: _,
|
||||||
score,
|
score,
|
||||||
outcome,
|
outcome,
|
||||||
} => {
|
} => {
|
||||||
let has_topology = (*sections).contains_key(&topology_key(&topology));
|
if current_score.is_some() && current_score.clone().unwrap() == score {
|
||||||
let section_key = if has_topology {
|
(*current_score) = None;
|
||||||
topology_key(&topology)
|
}
|
||||||
} else {
|
|
||||||
score_key(&score)
|
|
||||||
};
|
|
||||||
|
|
||||||
let section = (*sections).get(§ion_key).unwrap();
|
|
||||||
let progress_bar =
|
|
||||||
(*progress_bars).get(&interpret_key(&interpret)).cloned();
|
|
||||||
|
|
||||||
let _ = section.clear();
|
|
||||||
|
|
||||||
match outcome {
|
match outcome {
|
||||||
Ok(outcome) => match outcome.status {
|
Ok(outcome) => match outcome.status {
|
||||||
harmony::interpret::InterpretStatus::SUCCESS => {
|
harmony::interpret::InterpretStatus::SUCCESS => {
|
||||||
progress::success(section, progress_bar, outcome.message)
|
progress_tracker.finish_task(&task_key, &outcome.message);
|
||||||
}
|
}
|
||||||
harmony::interpret::InterpretStatus::NOOP => {
|
harmony::interpret::InterpretStatus::NOOP => {
|
||||||
progress::skip(section, progress_bar, outcome.message)
|
progress_tracker.skip_task(&task_key, &outcome.message);
|
||||||
}
|
}
|
||||||
_ => progress::error(section, progress_bar, outcome.message),
|
_ => progress_tracker.fail_task(&task_key, &outcome.message),
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
progress::error(section, progress_bar, err.to_string());
|
progress_tracker.fail_task(&task_key, &err.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_topology {
|
|
||||||
(*progress_bars).remove(§ion_key);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@ -181,7 +175,3 @@ fn topology_key(topology: &str) -> String {
|
|||||||
fn score_key(score: &str) -> String {
|
fn score_key(score: &str) -> String {
|
||||||
format!("score-{score}")
|
format!("score-{score}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interpret_key(interpret: &str) -> String {
|
|
||||||
format!("interpret-{interpret}")
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use clap::builder::ArgPredicate;
|
use clap::builder::ArgPredicate;
|
||||||
|
use harmony::instrumentation;
|
||||||
use harmony::inventory::Inventory;
|
use harmony::inventory::Inventory;
|
||||||
use harmony::maestro::Maestro;
|
use harmony::maestro::Maestro;
|
||||||
use harmony::{score::Score, topology::Topology};
|
use harmony::{score::Score, topology::Topology};
|
||||||
@ -97,6 +98,7 @@ pub async fn run<T: Topology + Send + Sync + 'static>(
|
|||||||
|
|
||||||
let result = init(maestro, args_struct).await;
|
let result = init(maestro, args_struct).await;
|
||||||
|
|
||||||
|
instrumentation::instrument(instrumentation::HarmonyEvent::HarmonyFinished).unwrap();
|
||||||
let _ = tokio::try_join!(cli_logger_handle);
|
let _ = tokio::try_join!(cli_logger_handle);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,163 @@
|
|||||||
|
use indicatif::{MultiProgress, ProgressBar};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use indicatif::{MultiProgress, ProgressBar};
|
pub trait ProgressTracker: Send + Sync {
|
||||||
|
fn contains_section(&self, id: &str) -> bool;
|
||||||
pub fn new_section(title: String) -> MultiProgress {
|
fn add_section(&self, id: &str, message: &str);
|
||||||
let multi_progress = MultiProgress::new();
|
fn add_task(&self, section_id: &str, task_id: &str, message: &str);
|
||||||
let _ = multi_progress.println(title);
|
fn finish_task(&self, id: &str, message: &str);
|
||||||
|
fn fail_task(&self, id: &str, message: &str);
|
||||||
multi_progress
|
fn skip_task(&self, id: &str, message: &str);
|
||||||
|
fn clear(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_spinner(multi_progress: &MultiProgress, message: String) -> ProgressBar {
|
struct Section {
|
||||||
let progress = multi_progress.add(ProgressBar::new_spinner());
|
header_index: usize,
|
||||||
|
task_count: usize,
|
||||||
progress.set_style(crate::theme::SPINNER_STYLE.clone());
|
pb: ProgressBar,
|
||||||
progress.set_message(message);
|
|
||||||
progress.enable_steady_tick(Duration::from_millis(100));
|
|
||||||
|
|
||||||
progress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn success(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) {
|
struct IndicatifProgressTrackerState {
|
||||||
if let Some(progress) = progress {
|
sections: HashMap<String, Section>,
|
||||||
multi_progress.remove(&progress)
|
tasks: HashMap<String, ProgressBar>,
|
||||||
|
pb_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
let progress = multi_progress.add(ProgressBar::new_spinner());
|
#[derive(Clone)]
|
||||||
progress.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone());
|
pub struct IndicatifProgressTracker {
|
||||||
progress.finish_with_message(message);
|
mp: MultiProgress,
|
||||||
|
state: Arc<Mutex<IndicatifProgressTrackerState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn error(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) {
|
impl IndicatifProgressTracker {
|
||||||
if let Some(progress) = progress {
|
pub fn new(base: MultiProgress) -> Self {
|
||||||
multi_progress.remove(&progress)
|
// The indicatif log bridge will insert a progress bar at the top.
|
||||||
|
// To prevent our first section from being erased, we need to create
|
||||||
|
// a dummy progress bar as our first progress bar.
|
||||||
|
let _ = base.clear();
|
||||||
|
let log_pb = base.add(ProgressBar::new(1));
|
||||||
|
|
||||||
|
let mut sections = HashMap::new();
|
||||||
|
sections.insert(
|
||||||
|
"__log__".into(),
|
||||||
|
Section {
|
||||||
|
header_index: 0,
|
||||||
|
task_count: 0,
|
||||||
|
pb: log_pb.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut tasks = HashMap::new();
|
||||||
|
tasks.insert("__log__".into(), log_pb);
|
||||||
|
|
||||||
|
let state = Arc::new(Mutex::new(IndicatifProgressTrackerState {
|
||||||
|
sections,
|
||||||
|
tasks,
|
||||||
|
pb_count: 1,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Self { mp: base, state }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let progress = multi_progress.add(ProgressBar::new_spinner());
|
impl ProgressTracker for IndicatifProgressTracker {
|
||||||
progress.set_style(crate::theme::ERROR_SPINNER_STYLE.clone());
|
fn add_section(&self, id: &str, message: &str) {
|
||||||
progress.finish_with_message(message);
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
let header_pb = self
|
||||||
|
.mp
|
||||||
|
.add(ProgressBar::new(1).with_style(crate::theme::SECTION_STYLE.clone()));
|
||||||
|
header_pb.finish_with_message(message.to_string());
|
||||||
|
|
||||||
|
let header_index = state.pb_count;
|
||||||
|
state.pb_count += 1;
|
||||||
|
|
||||||
|
state.sections.insert(
|
||||||
|
id.to_string(),
|
||||||
|
Section {
|
||||||
|
header_index,
|
||||||
|
task_count: 0,
|
||||||
|
pb: header_pb,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) {
|
fn add_task(&self, section_id: &str, task_id: &str, message: &str) {
|
||||||
if let Some(progress) = progress {
|
let mut state = self.state.lock().unwrap();
|
||||||
multi_progress.remove(&progress)
|
|
||||||
|
let insertion_index = {
|
||||||
|
let current_section = state
|
||||||
|
.sections
|
||||||
|
.get(section_id)
|
||||||
|
.expect("Section ID not found");
|
||||||
|
current_section.header_index + current_section.task_count + 1 // +1 to insert after header
|
||||||
|
};
|
||||||
|
|
||||||
|
let pb = self.mp.insert(insertion_index, ProgressBar::new_spinner());
|
||||||
|
pb.set_style(crate::theme::SPINNER_STYLE.clone());
|
||||||
|
pb.set_prefix(" ");
|
||||||
|
pb.set_message(message.to_string());
|
||||||
|
pb.enable_steady_tick(Duration::from_millis(80));
|
||||||
|
|
||||||
|
state.pb_count += 1;
|
||||||
|
|
||||||
|
let section = state
|
||||||
|
.sections
|
||||||
|
.get_mut(section_id)
|
||||||
|
.expect("Section ID not found");
|
||||||
|
section.task_count += 1;
|
||||||
|
|
||||||
|
// We inserted a new progress bar, so we must update the header_index
|
||||||
|
// for all subsequent sections.
|
||||||
|
for (id, s) in state.sections.iter_mut() {
|
||||||
|
if id != section_id && s.header_index >= insertion_index {
|
||||||
|
s.header_index += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let progress = multi_progress.add(ProgressBar::new_spinner());
|
state.tasks.insert(task_id.to_string(), pb);
|
||||||
progress.set_style(crate::theme::SKIP_SPINNER_STYLE.clone());
|
}
|
||||||
progress.finish_with_message(message);
|
|
||||||
|
fn finish_task(&self, id: &str, message: &str) {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
if let Some(pb) = state.tasks.get(id) {
|
||||||
|
pb.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone());
|
||||||
|
pb.finish_with_message(message.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fail_task(&self, id: &str, message: &str) {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
if let Some(pb) = state.tasks.get(id) {
|
||||||
|
pb.set_style(crate::theme::ERROR_SPINNER_STYLE.clone());
|
||||||
|
pb.finish_with_message(message.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_task(&self, id: &str, message: &str) {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
if let Some(pb) = state.tasks.get(id) {
|
||||||
|
pb.set_style(crate::theme::SKIP_SPINNER_STYLE.clone());
|
||||||
|
pb.finish_with_message(message.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains_section(&self, id: &str) -> bool {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
state.sections.contains_key(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&self) {
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
state.tasks.values().for_each(|p| self.mp.remove(p));
|
||||||
|
state.tasks.clear();
|
||||||
|
state.sections.values().for_each(|s| self.mp.remove(&s.pb));
|
||||||
|
state.sections.clear();
|
||||||
|
state.pb_count = 0;
|
||||||
|
|
||||||
|
let _ = self.mp.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,11 @@ pub static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", "");
|
|||||||
pub static EMOJI_SCORE: Emoji<'_, '_> = Emoji("🎶", "");
|
pub static EMOJI_SCORE: Emoji<'_, '_> = Emoji("🎶", "");
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
pub static ref SECTION_STYLE: ProgressStyle = ProgressStyle::default_spinner()
|
||||||
|
.template("{wide_msg:.bold}")
|
||||||
|
.unwrap();
|
||||||
pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner()
|
pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner()
|
||||||
.template(" {spinner:.green} {msg}")
|
.template(" {spinner:.green} {wide_msg}")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]);
|
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]);
|
||||||
pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE
|
pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
use indicatif::{MultiProgress, ProgressBar};
|
use harmony_cli::progress::{IndicatifProgressTracker, ProgressTracker};
|
||||||
use indicatif_log_bridge::LogWrapper;
|
use indicatif::MultiProgress;
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::{
|
use std::sync::Arc;
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::instrumentation::{self, HarmonyComposerEvent};
|
use crate::instrumentation::{self, HarmonyComposerEvent};
|
||||||
|
|
||||||
@ -22,85 +19,57 @@ pub fn init() -> tokio::task::JoinHandle<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn configure_logger() {
|
fn configure_logger() {
|
||||||
let logger =
|
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
|
||||||
let level = logger.filter();
|
|
||||||
let multi = MultiProgress::new();
|
|
||||||
LogWrapper::new(multi.clone(), logger).try_init().unwrap();
|
|
||||||
log::set_max_level(level);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_events() {
|
pub async fn handle_events() {
|
||||||
const PROGRESS_SETUP: &str = "project-initialization";
|
let progress_tracker = Arc::new(IndicatifProgressTracker::new(MultiProgress::new()));
|
||||||
|
|
||||||
|
const SETUP_SECTION: &str = "project-initialization";
|
||||||
|
const COMPILTATION_TASK: &str = "compilation";
|
||||||
const PROGRESS_DEPLOYMENT: &str = "deployment";
|
const PROGRESS_DEPLOYMENT: &str = "deployment";
|
||||||
|
|
||||||
instrumentation::subscribe("Harmony Composer Logger", {
|
instrumentation::subscribe("Harmony Composer Logger", {
|
||||||
let progresses: Arc<Mutex<HashMap<String, MultiProgress>>> =
|
|
||||||
Arc::new(Mutex::new(HashMap::new()));
|
|
||||||
let compilation_progress = Arc::new(Mutex::new(None::<ProgressBar>));
|
|
||||||
|
|
||||||
move |event| {
|
move |event| {
|
||||||
let progresses_clone = Arc::clone(&progresses);
|
let progress_tracker = Arc::clone(&progress_tracker);
|
||||||
let compilation_progress_clone = Arc::clone(&compilation_progress);
|
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let mut progresses_guard = progresses_clone.lock().unwrap();
|
|
||||||
let mut compilation_progress_guard = compilation_progress_clone.lock().unwrap();
|
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
HarmonyComposerEvent::HarmonyComposerStarted => {}
|
HarmonyComposerEvent::HarmonyComposerStarted => {}
|
||||||
HarmonyComposerEvent::ProjectInitializationStarted => {
|
HarmonyComposerEvent::ProjectInitializationStarted => {
|
||||||
let multi_progress = harmony_cli::progress::new_section(format!(
|
progress_tracker.add_section(
|
||||||
|
SETUP_SECTION,
|
||||||
|
&format!(
|
||||||
"{} Initializing Harmony project...",
|
"{} Initializing Harmony project...",
|
||||||
harmony_cli::theme::EMOJI_HARMONY,
|
harmony_cli::theme::EMOJI_HARMONY,
|
||||||
));
|
),
|
||||||
(*progresses_guard).insert(PROGRESS_SETUP.to_string(), multi_progress);
|
);
|
||||||
}
|
}
|
||||||
HarmonyComposerEvent::ProjectInitialized => println!("\n"),
|
HarmonyComposerEvent::ProjectInitialized => {}
|
||||||
HarmonyComposerEvent::ProjectCompilationStarted { details } => {
|
HarmonyComposerEvent::ProjectCompilationStarted { details } => {
|
||||||
let initialization_progress =
|
progress_tracker.add_task(SETUP_SECTION, COMPILTATION_TASK, &details);
|
||||||
(*progresses_guard).get(PROGRESS_SETUP).unwrap();
|
|
||||||
let _ = initialization_progress.clear();
|
|
||||||
|
|
||||||
let progress =
|
|
||||||
harmony_cli::progress::add_spinner(initialization_progress, details);
|
|
||||||
*compilation_progress_guard = Some(progress);
|
|
||||||
}
|
}
|
||||||
HarmonyComposerEvent::ProjectCompiled => {
|
HarmonyComposerEvent::ProjectCompiled => {
|
||||||
let initialization_progress =
|
progress_tracker.finish_task(COMPILTATION_TASK, "project compiled");
|
||||||
(*progresses_guard).get(PROGRESS_SETUP).unwrap();
|
|
||||||
|
|
||||||
harmony_cli::progress::success(
|
|
||||||
initialization_progress,
|
|
||||||
(*compilation_progress_guard).take(),
|
|
||||||
"project compiled".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
HarmonyComposerEvent::ProjectCompilationFailed { details } => {
|
HarmonyComposerEvent::ProjectCompilationFailed { details } => {
|
||||||
let initialization_progress =
|
progress_tracker.fail_task(COMPILTATION_TASK, "failed to compile project");
|
||||||
(*progresses_guard).get(PROGRESS_SETUP).unwrap();
|
|
||||||
|
|
||||||
harmony_cli::progress::error(
|
|
||||||
initialization_progress,
|
|
||||||
(*compilation_progress_guard).take(),
|
|
||||||
"failed to compile project".to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
error!("{details}");
|
error!("{details}");
|
||||||
}
|
}
|
||||||
HarmonyComposerEvent::DeploymentStarted { target } => {
|
HarmonyComposerEvent::DeploymentStarted { target } => {
|
||||||
let multi_progress = harmony_cli::progress::new_section(format!(
|
progress_tracker.add_section(
|
||||||
"{} Starting deployment to {target}...\n\n",
|
PROGRESS_DEPLOYMENT,
|
||||||
harmony_cli::theme::EMOJI_DEPLOY
|
&format!(
|
||||||
));
|
"\n{} Deploying project to {target}...\n",
|
||||||
(*progresses_guard).insert(PROGRESS_DEPLOYMENT.to_string(), multi_progress);
|
harmony_cli::theme::EMOJI_DEPLOY,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
HarmonyComposerEvent::DeploymentCompleted => {
|
||||||
|
progress_tracker.clear();
|
||||||
}
|
}
|
||||||
HarmonyComposerEvent::DeploymentCompleted => println!("\n"),
|
|
||||||
HarmonyComposerEvent::Shutdown => {
|
HarmonyComposerEvent::Shutdown => {
|
||||||
for (_, progresses) in (*progresses_guard).iter() {
|
|
||||||
progresses.clear().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user