diff --git a/Cargo.lock b/Cargo.lock index e29c12f..a945b19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,7 +417,7 @@ dependencies = [ [[package]] name = "gh-lens" -version = "0.0.7" +version = "0.0.8" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 30814db..a2df036 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "gh-lens" description = "CLI to analyze your activity on GitHub" -version = "0.0.7" +version = "0.0.8" edition = "2021" license = "MIT" diff --git a/src/github/client.rs b/src/github/client.rs index d4d95c2..d2bf8d7 100644 --- a/src/github/client.rs +++ b/src/github/client.rs @@ -16,6 +16,13 @@ pub struct PullRequestsSummary { end_date: String, prs_count: i64, + comments_count: PullRequestCommentsCount, + commits_count: PullRequestCommitsCount, + changed_files_count: PullRequestChangedFilesCount, + time_to_first_contacted: PullRequestTimeToFirstContacted, + time_to_approved: PullRequestTimeToApproved, + time_to_merged: PullRequestTimeToMerged, + prs_summaries: Vec, } @@ -34,6 +41,152 @@ pub struct PullRequestSummary { merged_at: Option, } +#[derive(Debug, Serialize)] +pub struct PullRequestCommentsCount { + sum: i64, + average: f64, +} + +#[derive(Debug, Serialize)] +pub struct PullRequestCommitsCount { + sum: i64, + average: f64, +} + +#[derive(Debug, Serialize)] +pub struct PullRequestChangedFilesCount { + sum: i64, + average: f64, +} + +#[derive(Debug, Serialize)] +pub struct PullRequestTimeToFirstContacted { + average: f64, // sec +} + +#[derive(Debug, Serialize)] +pub struct PullRequestTimeToApproved { + average: f64, // sec +} + +#[derive(Debug, Serialize)] +pub struct PullRequestTimeToMerged { + average: f64, // sec +} + +impl PullRequestsSummary { + fn aggregate_summary(&mut self) { + self.aggregate_comments_count(); + self.aggregate_commits_count(); + self.aggregate_changed_files_count(); + self.aggregate_time_to_first_contacted(); + self.aggregate_time_to_approved(); + self.aggregate_time_to_merged(); + } + + fn aggregate_comments_count(&mut self) { + self.comments_count.sum = self + .prs_summaries + .iter() + .map(|summary| summary.comments_count) + .sum(); + self.comments_count.average = if self.prs_summaries.is_empty() { + 0.0 + } else { + self.comments_count.sum as f64 / self.prs_summaries.len() as f64 + }; + } + + fn aggregate_commits_count(&mut self) { + self.commits_count.sum = self + .prs_summaries + .iter() + .map(|summary| summary.commits_count) + .sum(); + self.commits_count.average = if self.prs_count == 0 { + 0.0 + } else { + self.commits_count.sum as f64 / self.prs_count as f64 + }; + } + + fn aggregate_changed_files_count(&mut self) { + self.changed_files_count.sum = self + .prs_summaries + .iter() + .map(|summary| summary.changed_files_count) + .sum(); + self.changed_files_count.average = if self.prs_count == 0 { + 0.0 + } else { + self.changed_files_count.sum as f64 / self.prs_count as f64 + }; + } + + fn aggregate_time_to_first_contacted(&mut self) { + let mut count = 0; + let mut total_seconds = 0; + for summary in self.prs_summaries.iter() { + if summary.first_contacted_at.is_none() { + continue; + }; + count += 1; + total_seconds += summary + .first_contacted_at + .as_ref() + .unwrap() + .diff_seconds(&summary.created_at); + } + self.time_to_first_contacted.average = if count == 0 { + 0.0 + } else { + total_seconds as f64 / count as f64 + }; + } + + fn aggregate_time_to_approved(&mut self) { + let mut count = 0; + let mut total_seconds = 0; + for summary in self.prs_summaries.iter() { + if summary.approved_at.is_none() { + continue; + }; + count += 1; + total_seconds += summary + .approved_at + .as_ref() + .unwrap() + .diff_seconds(&summary.created_at); + } + self.time_to_approved.average = if count == 0 { + 0.0 + } else { + total_seconds as f64 / count as f64 + }; + } + + fn aggregate_time_to_merged(&mut self) { + let mut count = 0; + let mut total_seconds = 0; + for summary in self.prs_summaries.iter() { + if summary.merged_at.is_none() { + continue; + }; + count += 1; + total_seconds += summary + .merged_at + .as_ref() + .unwrap() + .diff_seconds(&summary.created_at); + } + self.time_to_merged.average = if count == 0 { + 0.0 + } else { + total_seconds as f64 / count as f64 + }; + } +} + impl Client { pub fn new(token: String) -> Self { let octocrab = octocrab::Octocrab::builder() @@ -62,6 +215,21 @@ impl Client { start_date, end_date, prs_count: 0, + comments_count: PullRequestCommentsCount { + sum: 0, + average: 0.0, + }, + commits_count: PullRequestCommitsCount { + sum: 0, + average: 0.0, + }, + changed_files_count: PullRequestChangedFilesCount { + sum: 0, + average: 0.0, + }, + time_to_first_contacted: PullRequestTimeToFirstContacted { average: 0.0 }, + time_to_approved: PullRequestTimeToApproved { average: 0.0 }, + time_to_merged: PullRequestTimeToMerged { average: 0.0 }, prs_summaries: vec![], }; @@ -234,6 +402,8 @@ impl Client { }; } + summary.aggregate_summary(); + summary } @@ -290,6 +460,23 @@ impl Client { start_date: start_date.clone(), end_date: end_date.clone(), prs_count: 0, + comments_count: PullRequestCommentsCount { + sum: 0, + average: 0.0, + }, + commits_count: PullRequestCommitsCount { + sum: 0, + average: 0.0, + }, + changed_files_count: PullRequestChangedFilesCount { + sum: 0, + average: 0.0, + }, + time_to_first_contacted: PullRequestTimeToFirstContacted { + average: 0.0, + }, + time_to_approved: PullRequestTimeToApproved { average: 0.0 }, + time_to_merged: PullRequestTimeToMerged { average: 0.0 }, prs_summaries: vec![], }); @@ -454,6 +641,9 @@ impl Client { }) }); } + summaries.entry(individual.clone()).and_modify(|summary| { + summary.aggregate_summary(); + }); } if !prs.page_info.has_next_page { diff --git a/src/github/gql/scaler.rs b/src/github/gql/scaler.rs index c9d10dc..9d667ed 100644 --- a/src/github/gql/scaler.rs +++ b/src/github/gql/scaler.rs @@ -19,3 +19,13 @@ impl TryFrom for chrono::DateTime { chrono::DateTime::parse_from_rfc3339(value.0.as_str()).map(Into::into) } } + +impl DateTime { + pub fn diff_seconds(&self, other: &DateTime) -> i64 { + let target = self.0.parse::>().unwrap(); + let compare = other.0.parse::>().unwrap(); + let duration = target - compare; + + duration.num_seconds() + } +}