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: NationTech/harmony#101
This commit is contained in:
2025-08-11 23:47:11 +00:00
parent 1de96027a1
commit f0ed548755
15 changed files with 301 additions and 211 deletions

View File

@@ -1,10 +1,7 @@
use indicatif::{MultiProgress, ProgressBar};
use indicatif_log_bridge::LogWrapper;
use harmony_cli::progress::{IndicatifProgressTracker, ProgressTracker};
use indicatif::MultiProgress;
use log::error;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::sync::Arc;
use crate::instrumentation::{self, HarmonyComposerEvent};
@@ -22,85 +19,57 @@ pub fn init() -> tokio::task::JoinHandle<()> {
}
fn configure_logger() {
let logger =
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);
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build();
}
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";
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| {
let progresses_clone = Arc::clone(&progresses);
let compilation_progress_clone = Arc::clone(&compilation_progress);
let progress_tracker = Arc::clone(&progress_tracker);
async move {
let mut progresses_guard = progresses_clone.lock().unwrap();
let mut compilation_progress_guard = compilation_progress_clone.lock().unwrap();
match event {
HarmonyComposerEvent::HarmonyComposerStarted => {}
HarmonyComposerEvent::ProjectInitializationStarted => {
let multi_progress = harmony_cli::progress::new_section(format!(
"{} Initializing Harmony project...",
harmony_cli::theme::EMOJI_HARMONY,
));
(*progresses_guard).insert(PROGRESS_SETUP.to_string(), multi_progress);
progress_tracker.add_section(
SETUP_SECTION,
&format!(
"{} Initializing Harmony project...",
harmony_cli::theme::EMOJI_HARMONY,
),
);
}
HarmonyComposerEvent::ProjectInitialized => println!("\n"),
HarmonyComposerEvent::ProjectInitialized => {}
HarmonyComposerEvent::ProjectCompilationStarted { details } => {
let initialization_progress =
(*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);
progress_tracker.add_task(SETUP_SECTION, COMPILTATION_TASK, &details);
}
HarmonyComposerEvent::ProjectCompiled => {
let initialization_progress =
(*progresses_guard).get(PROGRESS_SETUP).unwrap();
harmony_cli::progress::success(
initialization_progress,
(*compilation_progress_guard).take(),
"project compiled".to_string(),
);
progress_tracker.finish_task(COMPILTATION_TASK, "project compiled");
}
HarmonyComposerEvent::ProjectCompilationFailed { details } => {
let initialization_progress =
(*progresses_guard).get(PROGRESS_SETUP).unwrap();
harmony_cli::progress::error(
initialization_progress,
(*compilation_progress_guard).take(),
"failed to compile project".to_string(),
);
progress_tracker.fail_task(COMPILTATION_TASK, "failed to compile project");
error!("{details}");
}
HarmonyComposerEvent::DeploymentStarted { target } => {
let multi_progress = harmony_cli::progress::new_section(format!(
"{} Starting deployment to {target}...\n\n",
harmony_cli::theme::EMOJI_DEPLOY
));
(*progresses_guard).insert(PROGRESS_DEPLOYMENT.to_string(), multi_progress);
progress_tracker.add_section(
PROGRESS_DEPLOYMENT,
&format!(
"\n{} Deploying project to {target}...\n",
harmony_cli::theme::EMOJI_DEPLOY,
),
);
}
HarmonyComposerEvent::DeploymentCompleted => {
progress_tracker.clear();
}
HarmonyComposerEvent::DeploymentCompleted => println!("\n"),
HarmonyComposerEvent::Shutdown => {
for (_, progresses) in (*progresses_guard).iter() {
progresses.clear().unwrap();
}
return false;
}
}