Compare commits
3 Commits
eef73decb5
...
fc0d9cb1d3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc0d9cb1d3 | ||
|
|
c8b47bf8f8 | ||
|
|
a570327a6b |
@@ -126,6 +126,7 @@ const COMMAND_FLAGS: &[(&str, &[&str])] = &[
|
|||||||
"--no-file-changes",
|
"--no-file-changes",
|
||||||
"--dry-run",
|
"--dry-run",
|
||||||
"--no-dry-run",
|
"--no-dry-run",
|
||||||
|
"--timings",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -59,12 +59,14 @@ pub struct ProjectSummary {
|
|||||||
pub events_failed: usize,
|
pub events_failed: usize,
|
||||||
pub statuses_enriched: usize,
|
pub statuses_enriched: usize,
|
||||||
pub statuses_seen: usize,
|
pub statuses_seen: usize,
|
||||||
|
pub status_errors: usize,
|
||||||
pub mr_diffs_fetched: usize,
|
pub mr_diffs_fetched: usize,
|
||||||
pub mr_diffs_failed: usize,
|
pub mr_diffs_failed: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-project status enrichment result, collected during ingestion.
|
/// Per-project status enrichment result, collected during ingestion.
|
||||||
pub struct ProjectStatusEnrichment {
|
pub struct ProjectStatusEnrichment {
|
||||||
|
pub path: String,
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
pub seen: usize,
|
pub seen: usize,
|
||||||
@@ -664,6 +666,7 @@ async fn run_ingest_inner(
|
|||||||
total
|
total
|
||||||
.status_enrichment_projects
|
.status_enrichment_projects
|
||||||
.push(ProjectStatusEnrichment {
|
.push(ProjectStatusEnrichment {
|
||||||
|
path: path.clone(),
|
||||||
mode: result.status_enrichment_mode.clone(),
|
mode: result.status_enrichment_mode.clone(),
|
||||||
reason: result.status_unsupported_reason.clone(),
|
reason: result.status_unsupported_reason.clone(),
|
||||||
seen: result.statuses_seen,
|
seen: result.statuses_seen,
|
||||||
@@ -682,6 +685,8 @@ async fn run_ingest_inner(
|
|||||||
events_failed: result.resource_events_failed,
|
events_failed: result.resource_events_failed,
|
||||||
statuses_enriched: result.statuses_enriched,
|
statuses_enriched: result.statuses_enriched,
|
||||||
statuses_seen: result.statuses_seen,
|
statuses_seen: result.statuses_seen,
|
||||||
|
status_errors: result.partial_error_count
|
||||||
|
+ usize::from(result.status_enrichment_error.is_some()),
|
||||||
mr_diffs_fetched: 0,
|
mr_diffs_fetched: 0,
|
||||||
mr_diffs_failed: 0,
|
mr_diffs_failed: 0,
|
||||||
});
|
});
|
||||||
@@ -716,6 +721,7 @@ async fn run_ingest_inner(
|
|||||||
events_failed: result.resource_events_failed,
|
events_failed: result.resource_events_failed,
|
||||||
statuses_enriched: 0,
|
statuses_enriched: 0,
|
||||||
statuses_seen: 0,
|
statuses_seen: 0,
|
||||||
|
status_errors: 0,
|
||||||
mr_diffs_fetched: result.mr_diffs_fetched,
|
mr_diffs_fetched: result.mr_diffs_fetched,
|
||||||
mr_diffs_failed: result.mr_diffs_failed,
|
mr_diffs_failed: result.mr_diffs_failed,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ use tracing::Instrument;
|
|||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use crate::cli::progress::{finish_stage, nested_progress, stage_spinner_v2};
|
use crate::cli::progress::{format_stage_line, nested_progress, stage_spinner_v2};
|
||||||
use crate::core::error::Result;
|
use crate::core::error::Result;
|
||||||
use crate::core::metrics::{MetricsLayer, StageTiming};
|
use crate::core::metrics::{MetricsLayer, StageTiming};
|
||||||
use crate::core::shutdown::ShutdownSignal;
|
use crate::core::shutdown::ShutdownSignal;
|
||||||
|
|
||||||
use super::embed::run_embed;
|
use super::embed::run_embed;
|
||||||
use super::generate_docs::run_generate_docs;
|
use super::generate_docs::run_generate_docs;
|
||||||
use super::ingest::{DryRunPreview, IngestDisplay, ProjectSummary, run_ingest, run_ingest_dry_run};
|
use super::ingest::{
|
||||||
|
DryRunPreview, IngestDisplay, ProjectStatusEnrichment, ProjectSummary, run_ingest,
|
||||||
|
run_ingest_dry_run,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct SyncOptions {
|
pub struct SyncOptions {
|
||||||
@@ -37,7 +40,9 @@ pub struct SyncResult {
|
|||||||
pub mr_diffs_fetched: usize,
|
pub mr_diffs_fetched: usize,
|
||||||
pub mr_diffs_failed: usize,
|
pub mr_diffs_failed: usize,
|
||||||
pub documents_regenerated: usize,
|
pub documents_regenerated: usize,
|
||||||
|
pub documents_errored: usize,
|
||||||
pub documents_embedded: usize,
|
pub documents_embedded: usize,
|
||||||
|
pub embedding_failed: usize,
|
||||||
pub status_enrichment_errors: usize,
|
pub status_enrichment_errors: usize,
|
||||||
pub statuses_enriched: usize,
|
pub statuses_enriched: usize,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
@@ -46,6 +51,15 @@ pub struct SyncResult {
|
|||||||
pub mr_projects: Vec<ProjectSummary>,
|
pub mr_projects: Vec<ProjectSummary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply semantic color to a stage-completion icon glyph.
|
||||||
|
fn color_icon(icon: &str, has_errors: bool) -> String {
|
||||||
|
if has_errors {
|
||||||
|
Theme::warning().render(icon)
|
||||||
|
} else {
|
||||||
|
Theme::success().render(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn run_sync(
|
pub async fn run_sync(
|
||||||
config: &Config,
|
config: &Config,
|
||||||
options: SyncOptions,
|
options: SyncOptions,
|
||||||
@@ -104,15 +118,61 @@ pub async fn run_sync(
|
|||||||
result.statuses_enriched += sep.enriched;
|
result.statuses_enriched += sep.enriched;
|
||||||
}
|
}
|
||||||
result.issue_projects = issues_result.project_summaries;
|
result.issue_projects = issues_result.project_summaries;
|
||||||
let issues_summary = format!(
|
let issues_elapsed = stage_start.elapsed();
|
||||||
|
if !options.robot_mode {
|
||||||
|
let (status_summary, status_has_errors) =
|
||||||
|
summarize_status_enrichment(&issues_result.status_enrichment_projects);
|
||||||
|
let status_icon = color_icon(
|
||||||
|
if status_has_errors {
|
||||||
|
Icons::warning()
|
||||||
|
} else {
|
||||||
|
Icons::success()
|
||||||
|
},
|
||||||
|
status_has_errors,
|
||||||
|
);
|
||||||
|
let mut status_lines = vec![format_stage_line(
|
||||||
|
&status_icon,
|
||||||
|
"Status",
|
||||||
|
&status_summary,
|
||||||
|
issues_elapsed,
|
||||||
|
)];
|
||||||
|
status_lines.extend(status_sub_rows(&issues_result.status_enrichment_projects));
|
||||||
|
print_static_lines(&status_lines);
|
||||||
|
}
|
||||||
|
let mut issues_summary = format!(
|
||||||
"{} issues from {} {}",
|
"{} issues from {} {}",
|
||||||
format_number(result.issues_updated as i64),
|
format_number(result.issues_updated as i64),
|
||||||
issues_result.projects_synced,
|
issues_result.projects_synced,
|
||||||
if issues_result.projects_synced == 1 { "project" } else { "projects" }
|
if issues_result.projects_synced == 1 { "project" } else { "projects" }
|
||||||
);
|
);
|
||||||
finish_stage(&spinner, Icons::success(), "Issues", &issues_summary, stage_start.elapsed());
|
append_failures(
|
||||||
if !options.robot_mode {
|
&mut issues_summary,
|
||||||
print_issue_sub_rows(&result.issue_projects);
|
&[
|
||||||
|
("event failures", issues_result.resource_events_failed),
|
||||||
|
("status errors", issues_result.status_enrichment_errors),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let issues_icon = color_icon(
|
||||||
|
if issues_result.resource_events_failed > 0 || issues_result.status_enrichment_errors > 0
|
||||||
|
{
|
||||||
|
Icons::warning()
|
||||||
|
} else {
|
||||||
|
Icons::success()
|
||||||
|
},
|
||||||
|
issues_result.resource_events_failed > 0 || issues_result.status_enrichment_errors > 0,
|
||||||
|
);
|
||||||
|
if options.robot_mode {
|
||||||
|
emit_stage_line(&spinner, &issues_icon, "Issues", &issues_summary, issues_elapsed);
|
||||||
|
} else {
|
||||||
|
let sub_rows = issue_sub_rows(&result.issue_projects);
|
||||||
|
emit_stage_block(
|
||||||
|
&spinner,
|
||||||
|
&issues_icon,
|
||||||
|
"Issues",
|
||||||
|
&issues_summary,
|
||||||
|
issues_elapsed,
|
||||||
|
&sub_rows,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if signal.is_cancelled() {
|
if signal.is_cancelled() {
|
||||||
@@ -143,15 +203,33 @@ pub async fn run_sync(
|
|||||||
result.mr_diffs_fetched += mrs_result.mr_diffs_fetched;
|
result.mr_diffs_fetched += mrs_result.mr_diffs_fetched;
|
||||||
result.mr_diffs_failed += mrs_result.mr_diffs_failed;
|
result.mr_diffs_failed += mrs_result.mr_diffs_failed;
|
||||||
result.mr_projects = mrs_result.project_summaries;
|
result.mr_projects = mrs_result.project_summaries;
|
||||||
let mrs_summary = format!(
|
let mrs_elapsed = stage_start.elapsed();
|
||||||
|
let mut mrs_summary = format!(
|
||||||
"{} merge requests from {} {}",
|
"{} merge requests from {} {}",
|
||||||
format_number(result.mrs_updated as i64),
|
format_number(result.mrs_updated as i64),
|
||||||
mrs_result.projects_synced,
|
mrs_result.projects_synced,
|
||||||
if mrs_result.projects_synced == 1 { "project" } else { "projects" }
|
if mrs_result.projects_synced == 1 { "project" } else { "projects" }
|
||||||
);
|
);
|
||||||
finish_stage(&spinner, Icons::success(), "MRs", &mrs_summary, stage_start.elapsed());
|
append_failures(
|
||||||
if !options.robot_mode {
|
&mut mrs_summary,
|
||||||
print_mr_sub_rows(&result.mr_projects);
|
&[
|
||||||
|
("event failures", mrs_result.resource_events_failed),
|
||||||
|
("diff failures", mrs_result.mr_diffs_failed),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
let mrs_icon = color_icon(
|
||||||
|
if mrs_result.resource_events_failed > 0 || mrs_result.mr_diffs_failed > 0 {
|
||||||
|
Icons::warning()
|
||||||
|
} else {
|
||||||
|
Icons::success()
|
||||||
|
},
|
||||||
|
mrs_result.resource_events_failed > 0 || mrs_result.mr_diffs_failed > 0,
|
||||||
|
);
|
||||||
|
if options.robot_mode {
|
||||||
|
emit_stage_line(&spinner, &mrs_icon, "MRs", &mrs_summary, mrs_elapsed);
|
||||||
|
} else {
|
||||||
|
let sub_rows = mr_sub_rows(&result.mr_projects);
|
||||||
|
emit_stage_block(&spinner, &mrs_icon, "MRs", &mrs_summary, mrs_elapsed, &sub_rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
if signal.is_cancelled() {
|
if signal.is_cancelled() {
|
||||||
@@ -175,12 +253,22 @@ pub async fn run_sync(
|
|||||||
});
|
});
|
||||||
let docs_result = run_generate_docs(config, options.full, None, Some(docs_cb))?;
|
let docs_result = run_generate_docs(config, options.full, None, Some(docs_cb))?;
|
||||||
result.documents_regenerated = docs_result.regenerated;
|
result.documents_regenerated = docs_result.regenerated;
|
||||||
|
result.documents_errored = docs_result.errored;
|
||||||
docs_bar.finish_and_clear();
|
docs_bar.finish_and_clear();
|
||||||
let docs_summary = format!(
|
let mut docs_summary = format!(
|
||||||
"{} documents generated",
|
"{} documents generated",
|
||||||
format_number(result.documents_regenerated as i64),
|
format_number(result.documents_regenerated as i64),
|
||||||
);
|
);
|
||||||
finish_stage(&spinner, Icons::success(), "Docs", &docs_summary, stage_start.elapsed());
|
append_failures(&mut docs_summary, &[("errors", docs_result.errored)]);
|
||||||
|
let docs_icon = color_icon(
|
||||||
|
if docs_result.errored > 0 {
|
||||||
|
Icons::warning()
|
||||||
|
} else {
|
||||||
|
Icons::success()
|
||||||
|
},
|
||||||
|
docs_result.errored > 0,
|
||||||
|
);
|
||||||
|
emit_stage_line(&spinner, &docs_icon, "Docs", &docs_summary, stage_start.elapsed());
|
||||||
} else {
|
} else {
|
||||||
debug!("Sync: skipping document generation (--no-docs)");
|
debug!("Sync: skipping document generation (--no-docs)");
|
||||||
}
|
}
|
||||||
@@ -202,17 +290,49 @@ pub async fn run_sync(
|
|||||||
match run_embed(config, options.full, false, Some(embed_cb), signal).await {
|
match run_embed(config, options.full, false, Some(embed_cb), signal).await {
|
||||||
Ok(embed_result) => {
|
Ok(embed_result) => {
|
||||||
result.documents_embedded = embed_result.docs_embedded;
|
result.documents_embedded = embed_result.docs_embedded;
|
||||||
|
result.embedding_failed = embed_result.failed;
|
||||||
embed_bar.finish_and_clear();
|
embed_bar.finish_and_clear();
|
||||||
let embed_summary = format!(
|
let mut embed_summary = format!(
|
||||||
"{} chunks embedded",
|
"{} chunks embedded",
|
||||||
format_number(embed_result.chunks_embedded as i64),
|
format_number(embed_result.chunks_embedded as i64),
|
||||||
);
|
);
|
||||||
finish_stage(&spinner, Icons::success(), "Embed", &embed_summary, stage_start.elapsed());
|
let mut tail_parts = Vec::new();
|
||||||
|
if embed_result.failed > 0 {
|
||||||
|
tail_parts.push(format!("{} failed", embed_result.failed));
|
||||||
|
}
|
||||||
|
if embed_result.skipped > 0 {
|
||||||
|
tail_parts.push(format!("{} skipped", embed_result.skipped));
|
||||||
|
}
|
||||||
|
if !tail_parts.is_empty() {
|
||||||
|
embed_summary.push_str(&format!(" ({})", tail_parts.join(", ")));
|
||||||
|
}
|
||||||
|
let embed_icon = color_icon(
|
||||||
|
if embed_result.failed > 0 {
|
||||||
|
Icons::warning()
|
||||||
|
} else {
|
||||||
|
Icons::success()
|
||||||
|
},
|
||||||
|
embed_result.failed > 0,
|
||||||
|
);
|
||||||
|
emit_stage_line(
|
||||||
|
&spinner,
|
||||||
|
&embed_icon,
|
||||||
|
"Embed",
|
||||||
|
&embed_summary,
|
||||||
|
stage_start.elapsed(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
embed_bar.finish_and_clear();
|
embed_bar.finish_and_clear();
|
||||||
let warn_summary = format!("skipped ({})", e);
|
let warn_summary = format!("skipped ({})", e);
|
||||||
finish_stage(&spinner, Icons::warning(), "Embed", &warn_summary, stage_start.elapsed());
|
let warn_icon = color_icon(Icons::warning(), true);
|
||||||
|
emit_stage_line(
|
||||||
|
&spinner,
|
||||||
|
&warn_icon,
|
||||||
|
"Embed",
|
||||||
|
&warn_summary,
|
||||||
|
stage_start.elapsed(),
|
||||||
|
);
|
||||||
warn!(error = %e, "Embedding stage failed (Ollama may be unavailable), continuing");
|
warn!(error = %e, "Embedding stage failed (Ollama may be unavailable), continuing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,6 +363,7 @@ pub fn print_sync(
|
|||||||
result: &SyncResult,
|
result: &SyncResult,
|
||||||
elapsed: std::time::Duration,
|
elapsed: std::time::Duration,
|
||||||
metrics: Option<&MetricsLayer>,
|
metrics: Option<&MetricsLayer>,
|
||||||
|
show_timings: bool,
|
||||||
) {
|
) {
|
||||||
let has_data = result.issues_updated > 0
|
let has_data = result.issues_updated > 0
|
||||||
|| result.mrs_updated > 0
|
|| result.mrs_updated > 0
|
||||||
@@ -252,51 +373,92 @@ pub fn print_sync(
|
|||||||
|| result.documents_regenerated > 0
|
|| result.documents_regenerated > 0
|
||||||
|| result.documents_embedded > 0
|
|| result.documents_embedded > 0
|
||||||
|| result.statuses_enriched > 0;
|
|| result.statuses_enriched > 0;
|
||||||
|
let has_failures = result.resource_events_failed > 0
|
||||||
|
|| result.mr_diffs_failed > 0
|
||||||
|
|| result.status_enrichment_errors > 0
|
||||||
|
|| result.documents_errored > 0
|
||||||
|
|| result.embedding_failed > 0;
|
||||||
|
|
||||||
if !has_data {
|
if !has_data && !has_failures {
|
||||||
println!(
|
println!(
|
||||||
"\n {} ({:.1}s)\n",
|
"\n {} ({})\n",
|
||||||
Theme::dim().render("Already up to date"),
|
Theme::dim().render("Already up to date"),
|
||||||
elapsed.as_secs_f64()
|
Theme::timing().render(&format!("{:.1}s", elapsed.as_secs_f64()))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Headline: what happened, how long
|
let headline = if has_failures {
|
||||||
|
Theme::warning().bold().render("Sync completed with issues")
|
||||||
|
} else {
|
||||||
|
Theme::success().bold().render("Synced")
|
||||||
|
};
|
||||||
println!(
|
println!(
|
||||||
"\n {} {} issues and {} MRs in {:.1}s",
|
"\n {} {} issues and {} MRs in {}",
|
||||||
Theme::success().bold().render("Synced"),
|
headline,
|
||||||
Theme::bold().render(&result.issues_updated.to_string()),
|
Theme::info()
|
||||||
Theme::bold().render(&result.mrs_updated.to_string()),
|
.bold()
|
||||||
elapsed.as_secs_f64()
|
.render(&result.issues_updated.to_string()),
|
||||||
|
Theme::info().bold().render(&result.mrs_updated.to_string()),
|
||||||
|
Theme::timing().render(&format!("{:.1}s", elapsed.as_secs_f64()))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Detail: supporting counts, compact middle-dot format, zero-suppressed
|
// Detail: supporting counts, compact middle-dot format, zero-suppressed
|
||||||
let mut details: Vec<String> = Vec::new();
|
let mut details: Vec<String> = Vec::new();
|
||||||
if result.discussions_fetched > 0 {
|
if result.discussions_fetched > 0 {
|
||||||
details.push(format!("{} discussions", result.discussions_fetched));
|
details.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
Theme::info().render(&result.discussions_fetched.to_string()),
|
||||||
|
Theme::dim().render("discussions")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if result.resource_events_fetched > 0 {
|
if result.resource_events_fetched > 0 {
|
||||||
details.push(format!("{} events", result.resource_events_fetched));
|
details.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
Theme::info().render(&result.resource_events_fetched.to_string()),
|
||||||
|
Theme::dim().render("events")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if result.mr_diffs_fetched > 0 {
|
if result.mr_diffs_fetched > 0 {
|
||||||
details.push(format!("{} diffs", result.mr_diffs_fetched));
|
details.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
Theme::info().render(&result.mr_diffs_fetched.to_string()),
|
||||||
|
Theme::dim().render("diffs")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if result.statuses_enriched > 0 {
|
if result.statuses_enriched > 0 {
|
||||||
details.push(format!("{} statuses updated", result.statuses_enriched));
|
details.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
Theme::info().render(&result.statuses_enriched.to_string()),
|
||||||
|
Theme::dim().render("statuses updated")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if !details.is_empty() {
|
if !details.is_empty() {
|
||||||
println!(" {}", Theme::dim().render(&details.join(" \u{b7} ")));
|
let sep = Theme::dim().render(" \u{b7} ");
|
||||||
|
println!(" {}", details.join(&sep));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Documents: regeneration + embedding as a second detail line
|
// Documents: regeneration + embedding as a second detail line
|
||||||
let mut doc_parts: Vec<String> = Vec::new();
|
let mut doc_parts: Vec<String> = Vec::new();
|
||||||
if result.documents_regenerated > 0 {
|
if result.documents_regenerated > 0 {
|
||||||
doc_parts.push(format!("{} docs regenerated", result.documents_regenerated));
|
doc_parts.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
Theme::info().render(&result.documents_regenerated.to_string()),
|
||||||
|
Theme::dim().render("docs regenerated")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
if result.documents_embedded > 0 {
|
if result.documents_embedded > 0 {
|
||||||
doc_parts.push(format!("{} embedded", result.documents_embedded));
|
doc_parts.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
Theme::info().render(&result.documents_embedded.to_string()),
|
||||||
|
Theme::dim().render("embedded")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if result.documents_errored > 0 {
|
||||||
|
doc_parts
|
||||||
|
.push(Theme::error().render(&format!("{} doc errors", result.documents_errored)));
|
||||||
}
|
}
|
||||||
if !doc_parts.is_empty() {
|
if !doc_parts.is_empty() {
|
||||||
println!(" {}", Theme::dim().render(&doc_parts.join(" \u{b7} ")));
|
let sep = Theme::dim().render(" \u{b7} ");
|
||||||
|
println!(" {}", doc_parts.join(&sep));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Errors: visually prominent, only if non-zero
|
// Errors: visually prominent, only if non-zero
|
||||||
@@ -310,6 +472,9 @@ pub fn print_sync(
|
|||||||
if result.status_enrichment_errors > 0 {
|
if result.status_enrichment_errors > 0 {
|
||||||
errors.push(format!("{} status errors", result.status_enrichment_errors));
|
errors.push(format!("{} status errors", result.status_enrichment_errors));
|
||||||
}
|
}
|
||||||
|
if result.embedding_failed > 0 {
|
||||||
|
errors.push(format!("{} embedding failures", result.embedding_failed));
|
||||||
|
}
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
println!(" {}", Theme::error().render(&errors.join(" \u{b7} ")));
|
println!(" {}", Theme::error().render(&errors.join(" \u{b7} ")));
|
||||||
}
|
}
|
||||||
@@ -319,70 +484,192 @@ pub fn print_sync(
|
|||||||
|
|
||||||
if let Some(metrics) = metrics {
|
if let Some(metrics) = metrics {
|
||||||
let stages = metrics.extract_timings();
|
let stages = metrics.extract_timings();
|
||||||
if !stages.is_empty() {
|
if should_print_timings(show_timings, &stages) {
|
||||||
print_timing_summary(&stages);
|
print_timing_summary(&stages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_issue_sub_rows(projects: &[ProjectSummary]) {
|
fn issue_sub_rows(projects: &[ProjectSummary]) -> Vec<String> {
|
||||||
if projects.len() <= 1 {
|
projects
|
||||||
return;
|
.iter()
|
||||||
}
|
.map(|p| {
|
||||||
for p in projects {
|
let mut parts: Vec<String> = Vec::new();
|
||||||
let mut parts: Vec<String> = Vec::new();
|
parts.push(format!(
|
||||||
parts.push(format!(
|
"{} {}",
|
||||||
"{} {}",
|
p.items_upserted,
|
||||||
p.items_upserted,
|
if p.items_upserted == 1 {
|
||||||
if p.items_upserted == 1 {
|
"issue"
|
||||||
"issue"
|
} else {
|
||||||
} else {
|
"issues"
|
||||||
"issues"
|
}
|
||||||
|
));
|
||||||
|
if p.discussions_synced > 0 {
|
||||||
|
parts.push(format!("{} discussions", p.discussions_synced));
|
||||||
}
|
}
|
||||||
));
|
if p.statuses_seen > 0 || p.statuses_enriched > 0 {
|
||||||
if p.discussions_synced > 0 {
|
parts.push(format!("{} statuses updated", p.statuses_enriched));
|
||||||
parts.push(format!("{} discussions", p.discussions_synced));
|
}
|
||||||
|
if p.events_fetched > 0 {
|
||||||
|
parts.push(format!("{} events", p.events_fetched));
|
||||||
|
}
|
||||||
|
if p.status_errors > 0 {
|
||||||
|
parts.push(Theme::warning().render(&format!("{} status errors", p.status_errors)));
|
||||||
|
}
|
||||||
|
if p.events_failed > 0 {
|
||||||
|
parts.push(Theme::warning().render(&format!("{} event failures", p.events_failed)));
|
||||||
|
}
|
||||||
|
let sep = Theme::dim().render(" \u{b7} ");
|
||||||
|
let detail = parts.join(&sep);
|
||||||
|
let path = Theme::muted().render(&format!("{:<30}", p.path));
|
||||||
|
format!(" {path} {detail}")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status_sub_rows(projects: &[ProjectStatusEnrichment]) -> Vec<String> {
|
||||||
|
projects
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
let total_errors = p.partial_errors + usize::from(p.error.is_some());
|
||||||
|
let mut parts: Vec<String> = vec![format!("{} statuses updated", p.enriched)];
|
||||||
|
if p.cleared > 0 {
|
||||||
|
parts.push(format!("{} cleared", p.cleared));
|
||||||
|
}
|
||||||
|
if p.seen > 0 {
|
||||||
|
parts.push(format!("{} seen", p.seen));
|
||||||
|
}
|
||||||
|
if total_errors > 0 {
|
||||||
|
parts.push(Theme::warning().render(&format!("{} errors", total_errors)));
|
||||||
|
} else if p.mode == "skipped" {
|
||||||
|
if let Some(reason) = &p.reason {
|
||||||
|
parts.push(Theme::dim().render(&format!("skipped ({reason})")));
|
||||||
|
} else {
|
||||||
|
parts.push(Theme::dim().render("skipped"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let sep = Theme::dim().render(" \u{b7} ");
|
||||||
|
let detail = parts.join(&sep);
|
||||||
|
let path = Theme::muted().render(&format!("{:<30}", p.path));
|
||||||
|
format!(" {path} {detail}")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mr_sub_rows(projects: &[ProjectSummary]) -> Vec<String> {
|
||||||
|
projects
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
let mut parts: Vec<String> = Vec::new();
|
||||||
|
parts.push(format!(
|
||||||
|
"{} {}",
|
||||||
|
p.items_upserted,
|
||||||
|
if p.items_upserted == 1 { "MR" } else { "MRs" }
|
||||||
|
));
|
||||||
|
if p.discussions_synced > 0 {
|
||||||
|
parts.push(format!("{} discussions", p.discussions_synced));
|
||||||
|
}
|
||||||
|
if p.mr_diffs_fetched > 0 {
|
||||||
|
parts.push(format!("{} diffs", p.mr_diffs_fetched));
|
||||||
|
}
|
||||||
|
if p.events_fetched > 0 {
|
||||||
|
parts.push(format!("{} events", p.events_fetched));
|
||||||
|
}
|
||||||
|
if p.mr_diffs_failed > 0 {
|
||||||
|
parts
|
||||||
|
.push(Theme::warning().render(&format!("{} diff failures", p.mr_diffs_failed)));
|
||||||
|
}
|
||||||
|
if p.events_failed > 0 {
|
||||||
|
parts.push(Theme::warning().render(&format!("{} event failures", p.events_failed)));
|
||||||
|
}
|
||||||
|
let sep = Theme::dim().render(" \u{b7} ");
|
||||||
|
let detail = parts.join(&sep);
|
||||||
|
let path = Theme::muted().render(&format!("{:<30}", p.path));
|
||||||
|
format!(" {path} {detail}")
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_stage_line(
|
||||||
|
pb: &indicatif::ProgressBar,
|
||||||
|
icon: &str,
|
||||||
|
label: &str,
|
||||||
|
summary: &str,
|
||||||
|
elapsed: std::time::Duration,
|
||||||
|
) {
|
||||||
|
pb.finish_and_clear();
|
||||||
|
print_static_lines(&[format_stage_line(icon, label, summary, elapsed)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_stage_block(
|
||||||
|
pb: &indicatif::ProgressBar,
|
||||||
|
icon: &str,
|
||||||
|
label: &str,
|
||||||
|
summary: &str,
|
||||||
|
elapsed: std::time::Duration,
|
||||||
|
sub_rows: &[String],
|
||||||
|
) {
|
||||||
|
pb.finish_and_clear();
|
||||||
|
let mut lines = Vec::with_capacity(1 + sub_rows.len());
|
||||||
|
lines.push(format_stage_line(icon, label, summary, elapsed));
|
||||||
|
lines.extend(sub_rows.iter().cloned());
|
||||||
|
print_static_lines(&lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_static_lines(lines: &[String]) {
|
||||||
|
crate::cli::progress::multi().suspend(|| {
|
||||||
|
for line in lines {
|
||||||
|
println!("{line}");
|
||||||
}
|
}
|
||||||
if p.statuses_enriched > 0 {
|
});
|
||||||
parts.push(format!("{} statuses updated", p.statuses_enriched));
|
}
|
||||||
}
|
|
||||||
if p.events_fetched > 0 {
|
fn should_print_timings(show_timings: bool, stages: &[StageTiming]) -> bool {
|
||||||
parts.push(format!("{} events", p.events_fetched));
|
show_timings && !stages.is_empty()
|
||||||
}
|
}
|
||||||
let detail = parts.join(" \u{b7} ");
|
|
||||||
let _ = crate::cli::progress::multi().println(format!(
|
fn append_failures(summary: &mut String, failures: &[(&str, usize)]) {
|
||||||
" {}",
|
let rendered: Vec<String> = failures
|
||||||
Theme::dim().render(&format!("{:<30} {}", p.path, detail))
|
.iter()
|
||||||
));
|
.filter_map(|(label, count)| {
|
||||||
|
(*count > 0).then_some(Theme::warning().render(&format!("{count} {label}")))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if !rendered.is_empty() {
|
||||||
|
summary.push_str(&format!(" ({})", rendered.join(", ")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_mr_sub_rows(projects: &[ProjectSummary]) {
|
fn summarize_status_enrichment(projects: &[ProjectStatusEnrichment]) -> (String, bool) {
|
||||||
if projects.len() <= 1 {
|
let statuses_enriched: usize = projects.iter().map(|p| p.enriched).sum();
|
||||||
return;
|
let statuses_seen: usize = projects.iter().map(|p| p.seen).sum();
|
||||||
}
|
let statuses_cleared: usize = projects.iter().map(|p| p.cleared).sum();
|
||||||
for p in projects {
|
let status_errors: usize = projects
|
||||||
let mut parts: Vec<String> = Vec::new();
|
.iter()
|
||||||
|
.map(|p| p.partial_errors + usize::from(p.error.is_some()))
|
||||||
|
.sum();
|
||||||
|
let skipped = projects.iter().filter(|p| p.mode == "skipped").count();
|
||||||
|
|
||||||
|
let mut parts = vec![format!(
|
||||||
|
"{} statuses updated",
|
||||||
|
format_number(statuses_enriched as i64)
|
||||||
|
)];
|
||||||
|
if statuses_cleared > 0 {
|
||||||
parts.push(format!(
|
parts.push(format!(
|
||||||
"{} {}",
|
"{} cleared",
|
||||||
p.items_upserted,
|
format_number(statuses_cleared as i64)
|
||||||
if p.items_upserted == 1 { "MR" } else { "MRs" }
|
|
||||||
));
|
|
||||||
if p.discussions_synced > 0 {
|
|
||||||
parts.push(format!("{} discussions", p.discussions_synced));
|
|
||||||
}
|
|
||||||
if p.mr_diffs_fetched > 0 {
|
|
||||||
parts.push(format!("{} diffs", p.mr_diffs_fetched));
|
|
||||||
}
|
|
||||||
if p.events_fetched > 0 {
|
|
||||||
parts.push(format!("{} events", p.events_fetched));
|
|
||||||
}
|
|
||||||
let detail = parts.join(" \u{b7} ");
|
|
||||||
let _ = crate::cli::progress::multi().println(format!(
|
|
||||||
" {}",
|
|
||||||
Theme::dim().render(&format!("{:<30} {}", p.path, detail))
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if statuses_seen > 0 {
|
||||||
|
parts.push(format!("{} seen", format_number(statuses_seen as i64)));
|
||||||
|
}
|
||||||
|
if status_errors > 0 {
|
||||||
|
parts.push(format!("{} errors", format_number(status_errors as i64)));
|
||||||
|
} else if projects.is_empty() || skipped == projects.len() {
|
||||||
|
parts.push("skipped".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
(parts.join(" \u{b7} "), status_errors > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section(title: &str) {
|
fn section(title: &str) {
|
||||||
@@ -595,3 +882,151 @@ pub fn print_sync_dry_run_json(result: &SyncDryRunResult) {
|
|||||||
|
|
||||||
println!("{}", serde_json::to_string(&output).unwrap());
|
println!("{}", serde_json::to_string(&output).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_failures_skips_zeroes() {
|
||||||
|
let mut summary = "base".to_string();
|
||||||
|
append_failures(&mut summary, &[("errors", 0), ("failures", 0)]);
|
||||||
|
assert_eq!(summary, "base");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn append_failures_renders_non_zero_counts() {
|
||||||
|
let mut summary = "base".to_string();
|
||||||
|
append_failures(&mut summary, &[("errors", 2), ("failures", 1)]);
|
||||||
|
assert!(summary.contains("base"));
|
||||||
|
assert!(summary.contains("2 errors"));
|
||||||
|
assert!(summary.contains("1 failures"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn summarize_status_enrichment_reports_skipped_when_all_skipped() {
|
||||||
|
let projects = vec![ProjectStatusEnrichment {
|
||||||
|
path: "vs/typescript-code".to_string(),
|
||||||
|
mode: "skipped".to_string(),
|
||||||
|
reason: None,
|
||||||
|
seen: 0,
|
||||||
|
enriched: 0,
|
||||||
|
cleared: 0,
|
||||||
|
without_widget: 0,
|
||||||
|
partial_errors: 0,
|
||||||
|
first_partial_error: None,
|
||||||
|
error: None,
|
||||||
|
}];
|
||||||
|
|
||||||
|
let (summary, has_errors) = summarize_status_enrichment(&projects);
|
||||||
|
assert!(summary.contains("0 statuses updated"));
|
||||||
|
assert!(summary.contains("skipped"));
|
||||||
|
assert!(!has_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn summarize_status_enrichment_reports_errors() {
|
||||||
|
let projects = vec![ProjectStatusEnrichment {
|
||||||
|
path: "vs/typescript-code".to_string(),
|
||||||
|
mode: "fetched".to_string(),
|
||||||
|
reason: None,
|
||||||
|
seen: 3,
|
||||||
|
enriched: 1,
|
||||||
|
cleared: 1,
|
||||||
|
without_widget: 0,
|
||||||
|
partial_errors: 2,
|
||||||
|
first_partial_error: None,
|
||||||
|
error: Some("boom".to_string()),
|
||||||
|
}];
|
||||||
|
|
||||||
|
let (summary, has_errors) = summarize_status_enrichment(&projects);
|
||||||
|
assert!(summary.contains("1 statuses updated"));
|
||||||
|
assert!(summary.contains("1 cleared"));
|
||||||
|
assert!(summary.contains("3 seen"));
|
||||||
|
assert!(summary.contains("3 errors"));
|
||||||
|
assert!(has_errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_print_timings_only_when_enabled_and_non_empty() {
|
||||||
|
let stages = vec![StageTiming {
|
||||||
|
name: "x".to_string(),
|
||||||
|
elapsed_ms: 10,
|
||||||
|
items_processed: 0,
|
||||||
|
items_skipped: 0,
|
||||||
|
errors: 0,
|
||||||
|
rate_limit_hits: 0,
|
||||||
|
retries: 0,
|
||||||
|
project: None,
|
||||||
|
sub_stages: vec![],
|
||||||
|
}];
|
||||||
|
|
||||||
|
assert!(should_print_timings(true, &stages));
|
||||||
|
assert!(!should_print_timings(false, &stages));
|
||||||
|
assert!(!should_print_timings(true, &[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn issue_sub_rows_include_project_and_statuses() {
|
||||||
|
let rows = issue_sub_rows(&[ProjectSummary {
|
||||||
|
path: "vs/typescript-code".to_string(),
|
||||||
|
items_upserted: 2,
|
||||||
|
discussions_synced: 0,
|
||||||
|
events_fetched: 0,
|
||||||
|
events_failed: 0,
|
||||||
|
statuses_enriched: 1,
|
||||||
|
statuses_seen: 5,
|
||||||
|
status_errors: 0,
|
||||||
|
mr_diffs_fetched: 0,
|
||||||
|
mr_diffs_failed: 0,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert!(rows[0].contains("vs/typescript-code"));
|
||||||
|
assert!(rows[0].contains("2 issues"));
|
||||||
|
assert!(rows[0].contains("1 statuses updated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mr_sub_rows_include_project_and_diff_failures() {
|
||||||
|
let rows = mr_sub_rows(&[ProjectSummary {
|
||||||
|
path: "vs/python-code".to_string(),
|
||||||
|
items_upserted: 3,
|
||||||
|
discussions_synced: 0,
|
||||||
|
events_fetched: 0,
|
||||||
|
events_failed: 0,
|
||||||
|
statuses_enriched: 0,
|
||||||
|
statuses_seen: 0,
|
||||||
|
status_errors: 0,
|
||||||
|
mr_diffs_fetched: 4,
|
||||||
|
mr_diffs_failed: 1,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert!(rows[0].contains("vs/python-code"));
|
||||||
|
assert!(rows[0].contains("3 MRs"));
|
||||||
|
assert!(rows[0].contains("4 diffs"));
|
||||||
|
assert!(rows[0].contains("1 diff failures"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn status_sub_rows_include_project_and_skip_reason() {
|
||||||
|
let rows = status_sub_rows(&[ProjectStatusEnrichment {
|
||||||
|
path: "vs/python-code".to_string(),
|
||||||
|
mode: "skipped".to_string(),
|
||||||
|
reason: Some("disabled".to_string()),
|
||||||
|
seen: 0,
|
||||||
|
enriched: 0,
|
||||||
|
cleared: 0,
|
||||||
|
without_widget: 0,
|
||||||
|
partial_errors: 0,
|
||||||
|
first_partial_error: None,
|
||||||
|
error: None,
|
||||||
|
}]);
|
||||||
|
|
||||||
|
assert_eq!(rows.len(), 1);
|
||||||
|
assert!(rows[0].contains("vs/python-code"));
|
||||||
|
assert!(rows[0].contains("0 statuses updated"));
|
||||||
|
assert!(rows[0].contains("skipped (disabled)"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -789,6 +789,10 @@ pub struct SyncArgs {
|
|||||||
|
|
||||||
#[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
|
#[arg(long = "no-dry-run", hide = true, overrides_with = "dry_run")]
|
||||||
pub no_dry_run: bool,
|
pub no_dry_run: bool,
|
||||||
|
|
||||||
|
/// Show detailed timing breakdown for sync stages
|
||||||
|
#[arg(short = 't', long = "timings")]
|
||||||
|
pub timings: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::sync::LazyLock;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing_subscriber::fmt::MakeWriter;
|
use tracing_subscriber::fmt::MakeWriter;
|
||||||
|
|
||||||
use crate::cli::render::Icons;
|
use crate::cli::render::{Icons, Theme};
|
||||||
|
|
||||||
static MULTI: LazyLock<MultiProgress> = LazyLock::new(MultiProgress::new);
|
static MULTI: LazyLock<MultiProgress> = LazyLock::new(MultiProgress::new);
|
||||||
|
|
||||||
@@ -56,12 +56,21 @@ pub fn nested_progress(msg: &str, len: u64, robot_mode: bool) -> ProgressBar {
|
|||||||
///
|
///
|
||||||
/// Output: ` ✓ Label summary elapsed`
|
/// Output: ` ✓ Label summary elapsed`
|
||||||
pub fn finish_stage(pb: &ProgressBar, icon: &str, label: &str, summary: &str, elapsed: Duration) {
|
pub fn finish_stage(pb: &ProgressBar, icon: &str, label: &str, summary: &str, elapsed: Duration) {
|
||||||
let elapsed_str = format_elapsed(elapsed);
|
let line = format_stage_line(icon, label, summary, elapsed);
|
||||||
let line = format!(" {icon} {label:<12}{summary:>40} {elapsed_str:>8}",);
|
|
||||||
pb.set_style(ProgressStyle::with_template("{msg}").expect("valid template"));
|
pb.set_style(ProgressStyle::with_template("{msg}").expect("valid template"));
|
||||||
pb.finish_with_message(line);
|
pb.finish_with_message(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a static stage line showing icon, label, summary, and elapsed.
|
||||||
|
///
|
||||||
|
/// Output: ` ✓ Label summary elapsed`
|
||||||
|
pub fn format_stage_line(icon: &str, label: &str, summary: &str, elapsed: Duration) -> String {
|
||||||
|
let elapsed_str = format_elapsed(elapsed);
|
||||||
|
let styled_label = Theme::info().bold().render(&format!("{label:<12}"));
|
||||||
|
let styled_elapsed = Theme::timing().render(&format!("{elapsed_str:>8}"));
|
||||||
|
format!(" {icon} {styled_label}{summary:>40} {styled_elapsed}")
|
||||||
|
}
|
||||||
|
|
||||||
/// Format a Duration as a compact human string (e.g. "1.2s", "42ms", "1m 5s").
|
/// Format a Duration as a compact human string (e.g. "1.2s", "42ms", "1m 5s").
|
||||||
fn format_elapsed(d: Duration) -> String {
|
fn format_elapsed(d: Duration) -> String {
|
||||||
let ms = d.as_millis();
|
let ms = d.as_millis();
|
||||||
@@ -203,4 +212,13 @@ mod tests {
|
|||||||
assert_eq!(format_elapsed(Duration::from_secs(65)), "1m 5s");
|
assert_eq!(format_elapsed(Duration::from_secs(65)), "1m 5s");
|
||||||
assert_eq!(format_elapsed(Duration::from_secs(120)), "2m 0s");
|
assert_eq!(format_elapsed(Duration::from_secs(120)), "2m 0s");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_stage_line_includes_label_summary_and_elapsed() {
|
||||||
|
let line = format_stage_line("✔", "Issues", "10 issues", Duration::from_millis(4200));
|
||||||
|
assert!(line.contains("✔"));
|
||||||
|
assert!(line.contains("Issues"));
|
||||||
|
assert!(line.contains("10 issues"));
|
||||||
|
assert!(line.contains("4.2s"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2099,7 +2099,7 @@ async fn handle_sync_cmd(
|
|||||||
"{}",
|
"{}",
|
||||||
Theme::warning().render("Interrupted by Ctrl+C. Partial results:")
|
Theme::warning().render("Interrupted by Ctrl+C. Partial results:")
|
||||||
);
|
);
|
||||||
print_sync(&result, elapsed, Some(metrics));
|
print_sync(&result, elapsed, Some(metrics), args.timings);
|
||||||
if released > 0 {
|
if released > 0 {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"{}",
|
"{}",
|
||||||
@@ -2122,7 +2122,7 @@ async fn handle_sync_cmd(
|
|||||||
if robot_mode {
|
if robot_mode {
|
||||||
print_sync_json(&result, elapsed.as_millis() as u64, Some(metrics));
|
print_sync_json(&result, elapsed.as_millis() as u64, Some(metrics));
|
||||||
} else {
|
} else {
|
||||||
print_sync(&result, elapsed, Some(metrics));
|
print_sync(&result, elapsed, Some(metrics), args.timings);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user