refactor(structure): reorganize codebase into domain-focused modules
This commit is contained in:
331
src/cli/commands/ingest/render.rs
Normal file
331
src/cli/commands/ingest/render.rs
Normal file
@@ -0,0 +1,331 @@
|
||||
fn print_issue_project_summary(path: &str, result: &IngestProjectResult) {
|
||||
let labels_str = if result.labels_created > 0 {
|
||||
format!(", {} new labels", result.labels_created)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
println!(
|
||||
" {}: {} issues fetched{}",
|
||||
Theme::info().render(path),
|
||||
result.issues_upserted,
|
||||
labels_str
|
||||
);
|
||||
|
||||
if result.issues_synced_discussions > 0 {
|
||||
println!(
|
||||
" {} issues -> {} discussions, {} notes",
|
||||
result.issues_synced_discussions, result.discussions_fetched, result.notes_upserted
|
||||
);
|
||||
}
|
||||
|
||||
if result.issues_skipped_discussion_sync > 0 {
|
||||
println!(
|
||||
" {} unchanged issues (discussion sync skipped)",
|
||||
Theme::dim().render(&result.issues_skipped_discussion_sync.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_mr_project_summary(path: &str, result: &IngestMrProjectResult) {
|
||||
let labels_str = if result.labels_created > 0 {
|
||||
format!(", {} new labels", result.labels_created)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let assignees_str = if result.assignees_linked > 0 || result.reviewers_linked > 0 {
|
||||
format!(
|
||||
", {} assignees, {} reviewers",
|
||||
result.assignees_linked, result.reviewers_linked
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
println!(
|
||||
" {}: {} MRs fetched{}{}",
|
||||
Theme::info().render(path),
|
||||
result.mrs_upserted,
|
||||
labels_str,
|
||||
assignees_str
|
||||
);
|
||||
|
||||
if result.mrs_synced_discussions > 0 {
|
||||
let diffnotes_str = if result.diffnotes_count > 0 {
|
||||
format!(" ({} diff notes)", result.diffnotes_count)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
println!(
|
||||
" {} MRs -> {} discussions, {} notes{}",
|
||||
result.mrs_synced_discussions,
|
||||
result.discussions_fetched,
|
||||
result.notes_upserted,
|
||||
diffnotes_str
|
||||
);
|
||||
}
|
||||
|
||||
if result.mrs_skipped_discussion_sync > 0 {
|
||||
println!(
|
||||
" {} unchanged MRs (discussion sync skipped)",
|
||||
Theme::dim().render(&result.mrs_skipped_discussion_sync.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct IngestJsonOutput {
|
||||
ok: bool,
|
||||
data: IngestJsonData,
|
||||
meta: RobotMeta,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct IngestJsonData {
|
||||
resource_type: String,
|
||||
projects_synced: usize,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
issues: Option<IngestIssueStats>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
merge_requests: Option<IngestMrStats>,
|
||||
labels_created: usize,
|
||||
discussions_fetched: usize,
|
||||
notes_upserted: usize,
|
||||
resource_events_fetched: usize,
|
||||
resource_events_failed: usize,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
status_enrichment: Vec<StatusEnrichmentJson>,
|
||||
status_enrichment_errors: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct StatusEnrichmentJson {
|
||||
mode: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
reason: Option<String>,
|
||||
seen: usize,
|
||||
enriched: usize,
|
||||
cleared: usize,
|
||||
without_widget: usize,
|
||||
partial_errors: usize,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
first_partial_error: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct IngestIssueStats {
|
||||
fetched: usize,
|
||||
upserted: usize,
|
||||
synced_discussions: usize,
|
||||
skipped_discussion_sync: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct IngestMrStats {
|
||||
fetched: usize,
|
||||
upserted: usize,
|
||||
synced_discussions: usize,
|
||||
skipped_discussion_sync: usize,
|
||||
assignees_linked: usize,
|
||||
reviewers_linked: usize,
|
||||
diffnotes_count: usize,
|
||||
}
|
||||
|
||||
pub fn print_ingest_summary_json(result: &IngestResult, elapsed_ms: u64) {
|
||||
let (issues, merge_requests) = if result.resource_type == "issues" {
|
||||
(
|
||||
Some(IngestIssueStats {
|
||||
fetched: result.issues_fetched,
|
||||
upserted: result.issues_upserted,
|
||||
synced_discussions: result.issues_synced_discussions,
|
||||
skipped_discussion_sync: result.issues_skipped_discussion_sync,
|
||||
}),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
None,
|
||||
Some(IngestMrStats {
|
||||
fetched: result.mrs_fetched,
|
||||
upserted: result.mrs_upserted,
|
||||
synced_discussions: result.mrs_synced_discussions,
|
||||
skipped_discussion_sync: result.mrs_skipped_discussion_sync,
|
||||
assignees_linked: result.assignees_linked,
|
||||
reviewers_linked: result.reviewers_linked,
|
||||
diffnotes_count: result.diffnotes_count,
|
||||
}),
|
||||
)
|
||||
};
|
||||
|
||||
let status_enrichment: Vec<StatusEnrichmentJson> = result
|
||||
.status_enrichment_projects
|
||||
.iter()
|
||||
.map(|p| StatusEnrichmentJson {
|
||||
mode: p.mode.clone(),
|
||||
reason: p.reason.clone(),
|
||||
seen: p.seen,
|
||||
enriched: p.enriched,
|
||||
cleared: p.cleared,
|
||||
without_widget: p.without_widget,
|
||||
partial_errors: p.partial_errors,
|
||||
first_partial_error: p.first_partial_error.clone(),
|
||||
error: p.error.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let output = IngestJsonOutput {
|
||||
ok: true,
|
||||
data: IngestJsonData {
|
||||
resource_type: result.resource_type.clone(),
|
||||
projects_synced: result.projects_synced,
|
||||
issues,
|
||||
merge_requests,
|
||||
labels_created: result.labels_created,
|
||||
discussions_fetched: result.discussions_fetched,
|
||||
notes_upserted: result.notes_upserted,
|
||||
resource_events_fetched: result.resource_events_fetched,
|
||||
resource_events_failed: result.resource_events_failed,
|
||||
status_enrichment,
|
||||
status_enrichment_errors: result.status_enrichment_errors,
|
||||
},
|
||||
meta: RobotMeta { elapsed_ms },
|
||||
};
|
||||
|
||||
match serde_json::to_string(&output) {
|
||||
Ok(json) => println!("{json}"),
|
||||
Err(e) => eprintln!("Error serializing to JSON: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_ingest_summary(result: &IngestResult) {
|
||||
println!();
|
||||
|
||||
if result.resource_type == "issues" {
|
||||
println!(
|
||||
"{}",
|
||||
Theme::success().render(&format!(
|
||||
"Total: {} issues, {} discussions, {} notes",
|
||||
result.issues_upserted, result.discussions_fetched, result.notes_upserted
|
||||
))
|
||||
);
|
||||
|
||||
if result.issues_skipped_discussion_sync > 0 {
|
||||
println!(
|
||||
"{}",
|
||||
Theme::dim().render(&format!(
|
||||
"Skipped discussion sync for {} unchanged issues.",
|
||||
result.issues_skipped_discussion_sync
|
||||
))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let diffnotes_str = if result.diffnotes_count > 0 {
|
||||
format!(" ({} diff notes)", result.diffnotes_count)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
println!(
|
||||
"{}",
|
||||
Theme::success().render(&format!(
|
||||
"Total: {} MRs, {} discussions, {} notes{}",
|
||||
result.mrs_upserted,
|
||||
result.discussions_fetched,
|
||||
result.notes_upserted,
|
||||
diffnotes_str
|
||||
))
|
||||
);
|
||||
|
||||
if result.mrs_skipped_discussion_sync > 0 {
|
||||
println!(
|
||||
"{}",
|
||||
Theme::dim().render(&format!(
|
||||
"Skipped discussion sync for {} unchanged MRs.",
|
||||
result.mrs_skipped_discussion_sync
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if result.resource_events_fetched > 0 || result.resource_events_failed > 0 {
|
||||
println!(
|
||||
" Resource events: {} fetched{}",
|
||||
result.resource_events_fetched,
|
||||
if result.resource_events_failed > 0 {
|
||||
format!(", {} failed", result.resource_events_failed)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_dry_run_preview(preview: &DryRunPreview) {
|
||||
println!(
|
||||
"{} {}",
|
||||
Theme::info().bold().render("Dry Run Preview"),
|
||||
Theme::warning().render("(no changes will be made)")
|
||||
);
|
||||
println!();
|
||||
|
||||
let type_label = if preview.resource_type == "issues" {
|
||||
"issues"
|
||||
} else {
|
||||
"merge requests"
|
||||
};
|
||||
|
||||
println!(" Resource type: {}", Theme::bold().render(type_label));
|
||||
println!(
|
||||
" Sync mode: {}",
|
||||
if preview.sync_mode == "full" {
|
||||
Theme::warning().render("full (all data will be re-fetched)")
|
||||
} else {
|
||||
Theme::success().render("incremental (only changes since last sync)")
|
||||
}
|
||||
);
|
||||
println!(" Projects: {}", preview.projects.len());
|
||||
println!();
|
||||
|
||||
println!("{}", Theme::info().bold().render("Projects to sync:"));
|
||||
for project in &preview.projects {
|
||||
let sync_status = if !project.has_cursor {
|
||||
Theme::warning().render("initial sync")
|
||||
} else {
|
||||
Theme::success().render("incremental")
|
||||
};
|
||||
|
||||
println!(
|
||||
" {} ({})",
|
||||
Theme::bold().render(&project.path),
|
||||
sync_status
|
||||
);
|
||||
println!(" Existing {}: {}", type_label, project.existing_count);
|
||||
|
||||
if let Some(ref last_synced) = project.last_synced {
|
||||
println!(" Last synced: {}", last_synced);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DryRunJsonOutput {
|
||||
ok: bool,
|
||||
dry_run: bool,
|
||||
data: DryRunPreview,
|
||||
}
|
||||
|
||||
pub fn print_dry_run_preview_json(preview: &DryRunPreview) {
|
||||
let output = DryRunJsonOutput {
|
||||
ok: true,
|
||||
dry_run: true,
|
||||
data: preview.clone(),
|
||||
};
|
||||
|
||||
match serde_json::to_string(&output) {
|
||||
Ok(json) => println!("{json}"),
|
||||
Err(e) => eprintln!("Error serializing to JSON: {e}"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user