Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion src/demo.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::model::{CiCheck, CiCheckState, CiState, Pr, ReviewState};
use crate::model::{CiCheck, CiCheckState, CiState, MergeBlockers, Pr, ReviewState};
use std::sync::atomic::{AtomicU64, Ordering};

static DEMO_TICK: AtomicU64 = AtomicU64::new(0);
Expand Down Expand Up @@ -29,6 +29,8 @@ struct DemoPrSpec {
ci: CiProfile,
is_draft: bool,
is_viewer_author: bool,
/// Optional merge blockers for demo purposes.
blockers: Option<MergeBlockers>,
}

fn fnv1a_64(s: &str) -> u64 {
Expand Down Expand Up @@ -147,6 +149,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "you-inc",
Expand All @@ -159,6 +162,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: true,
blockers: None,
},
DemoPrSpec {
owner: "orbit",
Expand All @@ -171,6 +175,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RedNew,
is_draft: true,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "windmill-labs",
Expand All @@ -183,6 +188,15 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RedStuck,
is_draft: false,
is_viewer_author: false,
// Demo: has merge conflicts
blockers: Some(MergeBlockers {
has_conflicts: true,
required_approvals: Some(2),
current_approvals: 0,
required_checks: vec!["build".to_string(), "test".to_string()],
failing_required_checks: vec!["test".to_string()],
is_behind_base: false,
}),
},
DemoPrSpec {
owner: "paperplane",
Expand All @@ -195,6 +209,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RunningLong,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "acme-inc",
Expand All @@ -207,6 +222,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RunningShort,
is_draft: true,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "honeycombio",
Expand All @@ -219,6 +235,15 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: false,
// Demo: needs more approvals
blockers: Some(MergeBlockers {
has_conflicts: false,
required_approvals: Some(2),
current_approvals: 1,
required_checks: vec![],
failing_required_checks: vec![],
is_behind_base: true,
}),
},
DemoPrSpec {
owner: "orbit",
Expand All @@ -231,6 +256,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: true,
blockers: None,
},
DemoPrSpec {
owner: "paperplane",
Expand All @@ -243,6 +269,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::NoCi,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "acme-inc",
Expand All @@ -255,6 +282,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RunningLong,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "windmill-labs",
Expand All @@ -267,6 +295,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "orbit",
Expand All @@ -279,6 +308,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RedNew,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "paperplane",
Expand All @@ -291,6 +321,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::NoCi,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "honeycombio",
Expand All @@ -303,6 +334,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "acme-inc",
Expand All @@ -315,6 +347,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RedNew,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "windmill-labs",
Expand All @@ -327,6 +360,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::Green,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
DemoPrSpec {
owner: "paperplane",
Expand All @@ -339,6 +373,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
ci: CiProfile::RunningLong,
is_draft: false,
is_viewer_author: false,
blockers: None,
},
];

Expand Down Expand Up @@ -391,6 +426,7 @@ pub fn generate_demo_prs(now: i64, tick: u64) -> Vec<Pr> {
mergeable: Some("MERGEABLE".to_string()),
merge_state_status: Some("CLEAN".to_string()),
is_viewer_author: s.is_viewer_author,
merge_blockers: s.blockers.clone(),
}
})
.collect()
Expand Down
116 changes: 113 additions & 3 deletions src/github.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::model::{CiCheck, CiCheckState, CiState, Pr, ReviewState};
use crate::model::{CiCheck, CiCheckState, CiState, MergeBlockers, Pr, ReviewState};
use crate::timeutil::{parse_github_datetime_to_unix, unix_to_ymd};
use octocrab::Octocrab;
use std::collections::HashMap;
Expand Down Expand Up @@ -102,6 +102,26 @@ struct Commits {
nodes: Option<Vec<CommitNode>>,
}

#[derive(Debug, serde::Deserialize)]
struct ReviewsConnection {
#[serde(rename = "totalCount")]
total_count: Option<i32>,
}

#[derive(Debug, serde::Deserialize)]
struct BranchProtectionRule {
#[serde(rename = "requiredApprovingReviewCount")]
required_approving_review_count: Option<i32>,
#[serde(rename = "requiredStatusCheckContexts")]
required_status_check_contexts: Option<Vec<String>>,
}

#[derive(Debug, serde::Deserialize)]
struct BaseRef {
#[serde(rename = "branchProtectionRule")]
branch_protection_rule: Option<BranchProtectionRule>,
}

#[derive(Debug, serde::Deserialize)]
struct PullRequestNode {
number: i64,
Expand All @@ -123,6 +143,9 @@ struct PullRequestNode {
#[serde(rename = "mergeStateStatus")]
merge_state_status: Option<String>,
commits: Option<Commits>,
reviews: Option<ReviewsConnection>,
#[serde(rename = "baseRef")]
base_ref: Option<BaseRef>,
}

#[derive(Debug, serde::Deserialize)]
Expand Down Expand Up @@ -179,6 +202,9 @@ struct SearchNode {
#[serde(rename = "mergeStateStatus")]
merge_state_status: Option<String>,
commits: Option<Commits>,
reviews: Option<ReviewsConnection>,
#[serde(rename = "baseRef")]
base_ref: Option<BaseRef>,
}

impl SearchNode {
Expand All @@ -200,6 +226,8 @@ impl SearchNode {
mergeable: self.mergeable,
merge_state_status: self.merge_state_status,
commits: self.commits,
reviews: self.reviews,
base_ref: self.base_ref,
})
}
}
Expand Down Expand Up @@ -236,6 +264,15 @@ query($page_size: Int!, $cursor: String) {
}
}
}
reviews(states: [APPROVED], first: 50) {
totalCount
}
baseRef {
branchProtectionRule {
requiredApprovingReviewCount
requiredStatusCheckContexts
}
}
commits(last: 1) {
nodes {
commit {
Expand Down Expand Up @@ -294,6 +331,15 @@ query($page_size: Int!, $cursor: String, $search_query: String!) {
}
}
}
reviews(states: [APPROVED], first: 50) {
totalCount
}
baseRef {
branchProtectionRule {
requiredApprovingReviewCount
requiredStatusCheckContexts
}
}
commits(last: 1) {
nodes {
commit {
Expand Down Expand Up @@ -479,13 +525,68 @@ fn is_review_requested_by_user(node: &PullRequestNode, viewer_login: &str) -> bo
false
}

fn compute_merge_blockers(node: &PullRequestNode, ci_checks: &[CiCheck]) -> MergeBlockers {
let has_conflicts = node
.mergeable
.as_deref()
.is_some_and(|s| s.eq_ignore_ascii_case("CONFLICTING"));

let is_behind_base = node
.merge_state_status
.as_deref()
.is_some_and(|s| s.eq_ignore_ascii_case("BEHIND"));

// Get branch protection info
let (required_approvals, required_checks) = node
.base_ref
.as_ref()
.and_then(|br| br.branch_protection_rule.as_ref())
.map(|bpr| {
let approvals = bpr.required_approving_review_count.map(|c| c as u32);
let checks = bpr
.required_status_check_contexts
.clone()
.unwrap_or_default();
(approvals, checks)
})
.unwrap_or((None, Vec::new()));

let current_approvals = node
.reviews
.as_ref()
.and_then(|r| r.total_count)
.unwrap_or(0) as u32;

// Find failing required checks
let check_names_success: std::collections::HashSet<_> = ci_checks
.iter()
.filter(|c| matches!(c.state, CiCheckState::Success))
.map(|c| c.name.as_str())
.collect();

let failing_required_checks: Vec<String> = required_checks
.iter()
.filter(|name| !check_names_success.contains(name.as_str()))
.cloned()
.collect();

MergeBlockers {
has_conflicts,
required_approvals,
current_approvals,
required_checks,
failing_required_checks,
is_behind_base,
}
}

fn to_pr(node: PullRequestNode, is_requested: bool, viewer_login: &str) -> Option<Pr> {
let ci_checks = map_ci_checks(&node);
let ci_state = derive_ci_state(rollup_state(&node), &ci_checks);
let last_commit_sha = node.head_ref_oid.clone();
let review_state = map_review_state(&node, is_requested);
let owner = node.repository.owner.login;
let repo = node.repository.name;
let owner = node.repository.owner.login.clone();
let repo = node.repository.name.clone();
let author = node
.author
.as_ref()
Expand All @@ -500,6 +601,13 @@ fn to_pr(node: PullRequestNode, is_requested: bool, viewer_login: &str) -> Optio
.map(|a| a.login.as_str() == viewer_login)
.unwrap_or(false);

let merge_blockers = compute_merge_blockers(&node, &ci_checks);
let merge_blockers = if merge_blockers.is_clear() {
None
} else {
Some(merge_blockers)
};

Some(Pr {
pr_key,
owner,
Expand All @@ -517,6 +625,7 @@ fn to_pr(node: PullRequestNode, is_requested: bool, viewer_login: &str) -> Optio
mergeable: node.mergeable.clone(),
merge_state_status: node.merge_state_status.clone(),
is_viewer_author,
merge_blockers,
})
}

Expand Down Expand Up @@ -589,6 +698,7 @@ mod tests {
mergeable: None,
merge_state_status: None,
is_viewer_author: true,
merge_blockers: None,
};
let mut requested = authored.clone();
requested.is_viewer_author = false;
Expand Down
Loading