use std::path::{Path, PathBuf};
use tracing::debug;
use crate::report::errors::Result;
use crate::report::models::ReportData;
pub const AUTHORS_CSV: &str = "authors.csv";
pub const WEEKLY_CSV: &str = "weekly_activity.csv";
pub fn write_author_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(AUTHORS_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"name",
"email",
"commit_count",
"insertions",
"deletions",
"files_changed",
"first_commit",
"last_commit",
"categories",
])?;
for a in &data.authors {
let categories = serialize_categories(&a.categories);
w.write_record([
a.name.as_str(),
a.email.as_str(),
&a.commit_count.to_string(),
&a.insertions.to_string(),
&a.deletions.to_string(),
&a.files_changed.to_string(),
a.first_commit.as_str(),
a.last_commit.as_str(),
categories.as_str(),
])?;
}
w.flush()?;
debug!(path = %path.display(), rows = data.authors.len(), "wrote authors.csv");
Ok(path)
}
pub fn write_weekly_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(WEEKLY_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"week",
"author",
"repository",
"commit_count",
"insertions",
"deletions",
"categories",
"revert_count",
"bugfix_count",
"ticketed_count",
"quality_score",
"quality_tshirt",
"abandoned_pr_count",
])?;
for row in &data.weekly_activity {
let categories = serialize_categories(&row.categories);
w.write_record([
row.week.as_str(),
row.author.as_str(),
row.repository.as_str(),
&row.commit_count.to_string(),
&row.insertions.to_string(),
&row.deletions.to_string(),
categories.as_str(),
&row.revert_count.to_string(),
&row.bugfix_count.to_string(),
&row.ticketed_count.to_string(),
&format!("{:.4}", row.quality_score),
row.quality_tshirt.as_str(),
&row.abandoned_pr_count.to_string(),
])?;
}
w.flush()?;
debug!(
path = %path.display(),
rows = data.weekly_activity.len(),
"wrote weekly_activity.csv"
);
Ok(path)
}
pub const WEEKLY_METRICS_CSV: &str = "weekly_metrics.csv";
pub const DEV_ACTIVITY_CSV: &str = "developer_activity_summary.csv";
pub const SUMMARY_CSV: &str = "summary.csv";
pub const UNTRACKED_CSV: &str = "untracked_commits.csv";
pub const WEEKLY_CATEGORIZATION_CSV: &str = "weekly_categorization.csv";
pub const WEEKLY_VELOCITY_CSV: &str = "weekly_velocity.csv";
pub const WEEKLY_DORA_CSV: &str = "weekly_dora_metrics.csv";
pub fn write_weekly_metrics_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(WEEKLY_METRICS_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"week_id",
"total_commits",
"feature_commits",
"bugfix_commits",
"maintenance_commits",
"refactor_commits",
"test_commits",
"doc_commits",
"active_developers",
"story_points",
])?;
for m in &data.weekly_metrics {
w.write_record([
m.week.as_str(),
&m.total_commits.to_string(),
&m.feature_commits.to_string(),
&m.bugfix_commits.to_string(),
&m.maintenance_commits.to_string(),
&m.refactor_commits.to_string(),
&m.test_commits.to_string(),
&m.doc_commits.to_string(),
&m.active_developers.to_string(),
&format!("{:.2}", m.story_points),
])?;
}
w.flush()?;
debug!(path = %path.display(), rows = data.weekly_metrics.len(), "wrote weekly_metrics.csv");
Ok(path)
}
pub fn write_developer_activity_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(DEV_ACTIVITY_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"developer_id",
"display_name",
"total_commits",
"active_weeks",
"avg_commits_per_week",
"primary_work_type",
"story_points_total",
"activity_score",
])?;
for d in &data.developer_activity {
w.write_record([
d.developer_id.as_str(),
d.display_name.as_str(),
&d.total_commits.to_string(),
&d.active_weeks.to_string(),
&format!("{:.2}", d.avg_commits_per_week),
d.primary_work_type.as_str(),
&format!("{:.2}", d.story_points_total),
&format!("{:.4}", d.activity_score),
])?;
}
w.flush()?;
debug!(path = %path.display(), rows = data.developer_activity.len(), "wrote developer_activity_summary.csv");
Ok(path)
}
pub fn write_summary_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(SUMMARY_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"date_range",
"total_commits",
"total_developers",
"total_weeks",
"classification_coverage_pct",
])?;
if let Some(s) = &data.summary {
w.write_record([
s.date_range.as_str(),
&s.total_commits.to_string(),
&s.total_developers.to_string(),
&s.total_weeks.to_string(),
&format!("{:.2}", s.classification_coverage_pct),
])?;
}
w.flush()?;
debug!(path = %path.display(), "wrote summary.csv");
Ok(path)
}
pub fn write_untracked_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(UNTRACKED_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record(["sha", "author", "date", "message"])?;
for u in &data.untracked_commits {
w.write_record([
u.sha.as_str(),
u.author.as_str(),
u.date.as_str(),
u.message.as_str(),
])?;
}
w.flush()?;
debug!(path = %path.display(), rows = data.untracked_commits.len(), "wrote untracked_commits.csv");
Ok(path)
}
pub fn write_weekly_categorization_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(WEEKLY_CATEGORIZATION_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record(["week_id", "change_type", "commit_count", "pct_of_week"])?;
for c in &data.weekly_categorization {
w.write_record([
c.week.as_str(),
c.change_type.as_str(),
&c.commit_count.to_string(),
&format!("{:.2}", c.pct_of_week),
])?;
}
w.flush()?;
debug!(path = %path.display(), rows = data.weekly_categorization.len(), "wrote weekly_categorization.csv");
Ok(path)
}
pub fn write_weekly_velocity_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(WEEKLY_VELOCITY_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"week_id",
"prs_merged",
"avg_pr_cycle_time_hours",
"story_points",
"commits_per_developer",
])?;
for v in &data.weekly_velocity {
w.write_record([
v.week.as_str(),
&v.prs_merged.to_string(),
&format!("{:.2}", v.avg_pr_cycle_time_hours),
&format!("{:.2}", v.story_points),
&format!("{:.2}", v.commits_per_developer),
])?;
}
w.flush()?;
debug!(path = %path.display(), rows = data.weekly_velocity.len(), "wrote weekly_velocity.csv");
Ok(path)
}
pub fn write_weekly_dora_csv(data: &ReportData, output_dir: &Path) -> Result<PathBuf> {
let path = output_dir.join(WEEKLY_DORA_CSV);
let mut w = ::csv::Writer::from_path(&path)?;
w.write_record([
"deployment_frequency_per_week",
"lead_time_hours",
"change_failure_rate",
"mttr_hours",
"performance_level",
])?;
if let Some(d) = &data.dora {
w.write_record([
&format!("{:.4}", d.deployment_frequency),
&format!("{:.2}", d.lead_time_hours),
&format!("{:.4}", d.change_failure_rate),
&format!("{:.2}", d.mttr_hours),
d.performance_level.as_str(),
])?;
}
w.flush()?;
debug!(path = %path.display(), "wrote weekly_dora_metrics.csv");
Ok(path)
}
fn serialize_categories(map: &std::collections::HashMap<String, usize>) -> String {
let mut entries: Vec<(&String, &usize)> = map.iter().collect();
entries.sort_by_key(|e| e.0);
entries
.into_iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join(";")
}