use chrono::prelude::{DateTime, Utc};
use serde::Serialize;
#[derive(Serialize)]
pub struct Pr {
pub title: String,
pub url: String,
pub last_commit_pushed_date: Option<DateTime<Utc>>,
pub last_commit_age_min: Option<i64>,
pub tests_result: TestsState,
pub open_conversations: i64,
pub num_approvals: i64,
pub num_reviewers: i64,
pub additions: i64,
pub deletions: i64,
pub based_on_main_branch: bool,
pub files: Files,
pub blame: bool,
pub labels: Labels,
pub requested: bool,
pub codeowner: bool,
}
#[derive(Serialize)]
pub enum TestsState {
Pending,
Success,
Failure,
None,
}
#[derive(Serialize)]
pub enum ReviewRequested {
RequestedAsCodeOwner,
RequestedNotAsCodeOwner,
NotRequested,
}
#[derive(Serialize)]
pub struct Files(pub Vec<String>);
#[derive(Serialize)]
pub struct Labels(pub Vec<Label>);
#[derive(Serialize)]
pub struct Label {
pub name: String,
pub color: String,
}
#[derive(Serialize)]
pub struct ScoredPr {
pub pr: Pr,
pub score: Score,
}
#[derive(Debug, Serialize)]
pub struct Score {
pub age: f64,
pub tests_result: f64,
pub open_conversations: f64,
pub num_approvals: f64,
pub num_reviewers: f64,
pub additions: f64,
pub deletions: f64,
pub based_on_main_branch: f64,
pub blame: f64,
pub requested: f64,
pub codeowner: f64,
}
impl Score {
pub fn from_pr(required_approvals: u8, pr: &Pr) -> Score {
let tests_result_i = match pr.tests_result {
TestsState::Success => 0,
TestsState::Pending => 1,
TestsState::Failure => 2,
TestsState::None => 0, };
Score {
age: pr.last_commit_age_min.unwrap_or(0) as f64 / 60.0 * 2.0,
tests_result: (tests_result_i - 1) as f64 * -200.0,
open_conversations: pr.open_conversations as f64 * -30.0,
num_approvals: (pr.num_approvals - required_approvals as i64) as f64 * -80.0,
num_reviewers: (pr.num_reviewers - required_approvals as i64) as f64 * -50.0,
additions: pr.additions as f64 * -0.5,
deletions: pr.deletions as f64 * -0.1,
based_on_main_branch: pr.based_on_main_branch as u8 as f64 * 200.0,
blame: pr.blame as u8 as f64 * 400.0,
requested: pr.requested as u8 as f64 * 800.0,
codeowner: pr.codeowner as u8 as f64 * 400.0,
}
}
pub fn total(&self) -> f64 {
self.age
+ self.tests_result
+ self.open_conversations
+ self.num_approvals
+ self.num_reviewers
+ self.additions
+ self.deletions
+ self.based_on_main_branch
+ self.blame
+ self.requested
+ self.codeowner
}
}
impl std::fmt::Display for Pr {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{} - {} {}", self.url, self.title, self.labels,)
}
}
impl std::fmt::Display for Labels {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|label| label.to_string())
.collect::<Vec<String>>()
.join(" "),
)
}
}
impl std::fmt::Display for Label {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "({})", self.name,)
}
}
#[derive(Debug, Serialize)]
pub struct Review {
pub state: ReviewState,
pub url: String,
pub pr_title: String,
}
#[derive(Debug, Serialize)]
pub enum ReviewState {
Dismissed,
WithAddressedConversations,
}
impl std::fmt::Display for ReviewState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let text = match self {
ReviewState::Dismissed => "Dismissed",
ReviewState::WithAddressedConversations => "With addressed conversations",
};
write!(f, "{}", text)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pr_fmt_method() {
let pr = Pr {
title: "Some important changes".to_string(),
url: "https://github.com/frisoft/ateam/pull/1".to_string(),
last_commit_pushed_date: None,
last_commit_age_min: None,
tests_result: TestsState::Success,
open_conversations: 0,
num_approvals: 1,
num_reviewers: 2,
additions: 1000,
deletions: 999,
based_on_main_branch: true,
files: Files(vec![]),
blame: false,
labels: Labels(vec![]),
requested: true,
codeowner: false,
};
assert_eq!(
format!("{}", pr),
"https://github.com/frisoft/ateam/pull/1 - Some important changes ",
);
}
}