Compare commits

...

3 Commits

Author SHA1 Message Date
Ian Letourneau
387ae9f494 group related scores together 2025-08-09 23:20:45 -04:00
Ian Letourneau
336e1cfefe rename a few scores & interprets
Some checks failed
Run Check Script / check (pull_request) Has been cancelled
2025-08-09 23:18:15 -04:00
Ian Letourneau
403e199062 fix: improve usage of indicatif for tracking progress 2025-08-09 23:18:15 -04:00
16 changed files with 301 additions and 212 deletions

View File

@ -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,

View File

@ -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(),

View File

@ -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()
))) )))
} }

View File

@ -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 {

View File

@ -46,7 +46,7 @@ where
} }
fn name(&self) -> String { fn name(&self) -> String {
format!("Application: {}", self.application.name()) format!("{} [ApplicationScore]", self.application.name())
} }
} }

View File

@ -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
))),
} }
} }

View File

@ -7,7 +7,6 @@ use serde::Serialize;
use crate::{ use crate::{
config::HARMONY_DATA_DIR, config::HARMONY_DATA_DIR,
data::{Id, Version}, data::{Id, Version},
instrumentation::{self, HarmonyEvent},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory, inventory::Inventory,
score::Score, score::Score,

View File

@ -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)),

View File

@ -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 {

View File

@ -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(),
)) ))
} }

View File

@ -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
))) )))
} }

View File

@ -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(&sections); 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(&current_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}...", &section_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(&section_key).unwrap(); (*preparing_topology) = false;
let progress = progress::add_spinner(section, "".into()); progress_tracker.add_task(&section_key, "topology-success", "");
progress_tracker
progress::success( .finish_task("topology-success", &message.unwrap_or("".into()));
section,
Some(progress),
message.unwrap_or("".into()),
);
(*sections).remove(&section_key);
} }
TopologyStatus::Noop => { TopologyStatus::Noop => {
let section = (*sections).get(&section_key).unwrap(); (*preparing_topology) = false;
let progress = progress::add_spinner(section, "".into()); progress_tracker.add_task(&section_key, "topology-skip", "");
progress_tracker
progress::skip( .skip_task("topology-skip", &message.unwrap_or("".into()));
section,
Some(progress),
message.unwrap_or("".into()),
);
(*sections).remove(&section_key);
} }
TopologyStatus::Error => { TopologyStatus::Error => {
let section = (*sections).get(&section_key).unwrap(); progress_tracker.add_task(&section_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(&section_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(&current_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(&current_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(&section_key).unwrap();
let progress_bar = progress::add_spinner(section, message);
(*progress_bars).insert(interpret_key(&interpret), progress_bar); progress_tracker.add_task(&section_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(&section_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(&section_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}")
}

View File

@ -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
} }

View File

@ -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,
}
#[derive(Clone)]
pub struct IndicatifProgressTracker {
mp: MultiProgress,
state: Arc<Mutex<IndicatifProgressTrackerState>>,
}
impl IndicatifProgressTracker {
pub fn new(base: MultiProgress) -> Self {
// 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 }
}
}
impl ProgressTracker for IndicatifProgressTracker {
fn add_section(&self, id: &str, message: &str) {
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,
},
);
} }
let progress = multi_progress.add(ProgressBar::new_spinner()); fn add_task(&self, section_id: &str, task_id: &str, message: &str) {
progress.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone()); let mut state = self.state.lock().unwrap();
progress.finish_with_message(message);
}
pub fn error(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { let insertion_index = {
if let Some(progress) = progress { let current_section = state
multi_progress.remove(&progress) .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;
}
}
state.tasks.insert(task_id.to_string(), pb);
} }
let progress = multi_progress.add(ProgressBar::new_spinner()); fn finish_task(&self, id: &str, message: &str) {
progress.set_style(crate::theme::ERROR_SPINNER_STYLE.clone()); let state = self.state.lock().unwrap();
progress.finish_with_message(message); if let Some(pb) = state.tasks.get(id) {
} pb.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone());
pb.finish_with_message(message.to_string());
pub fn skip(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { }
if let Some(progress) = progress {
multi_progress.remove(&progress)
} }
let progress = multi_progress.add(ProgressBar::new_spinner()); fn fail_task(&self, id: &str, message: &str) {
progress.set_style(crate::theme::SKIP_SPINNER_STYLE.clone()); let state = self.state.lock().unwrap();
progress.finish_with_message(message); 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();
}
} }

View File

@ -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

View File

@ -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(
"{} Initializing Harmony project...", SETUP_SECTION,
harmony_cli::theme::EMOJI_HARMONY, &format!(
)); "{} Initializing Harmony project...",
(*progresses_guard).insert(PROGRESS_SETUP.to_string(), multi_progress); harmony_cli::theme::EMOJI_HARMONY,
),
);
} }
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;
} }
} }