Skip to content

Commit 803ed20

Browse files
committed
Move GraphQL to a separate package.
1 parent 1a9b8b8 commit 803ed20

File tree

8 files changed

+350
-332
lines changed

8 files changed

+350
-332
lines changed

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ route-recognizer = "0.3.0"
3939
cynic = { version = "0.14" }
4040
itertools = "0.10.2"
4141
tower = { version = "0.4.13", features = ["util", "limit", "buffer", "load-shed"] }
42+
github-graphql = { path = "github-graphql" }
4243

4344
[dependencies.serde]
4445
version = "1"

github-graphql/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "github-graphql"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
chrono = { version = "0.4", features = ["serde"] }
8+
cynic = { version = "0.14" }
File renamed without changes.

github-graphql/src/lib.rs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
//! Definitions for GitHub GraphQL.
2+
//!
3+
//! See <https://docs.github.com/en/graphql> for more GitHub's GraphQL API.
4+
5+
// This schema can be downloaded from https://docs.github.com/en/graphql/overview/public-schema
6+
#[cynic::schema_for_derives(file = "src/github.graphql", module = "schema")]
7+
pub mod queries {
8+
use super::schema;
9+
10+
pub type DateTime = chrono::DateTime<chrono::Utc>;
11+
12+
cynic::impl_scalar!(DateTime, schema::DateTime);
13+
14+
#[derive(cynic::FragmentArguments, Debug)]
15+
pub struct LeastRecentlyReviewedPullRequestsArguments {
16+
pub repository_owner: String,
17+
pub repository_name: String,
18+
pub after: Option<String>,
19+
}
20+
21+
#[derive(cynic::QueryFragment, Debug)]
22+
#[cynic(
23+
graphql_type = "Query",
24+
argument_struct = "LeastRecentlyReviewedPullRequestsArguments"
25+
)]
26+
pub struct LeastRecentlyReviewedPullRequests {
27+
#[arguments(owner = &args.repository_owner, name = &args.repository_name)]
28+
pub repository: Option<Repository>,
29+
}
30+
31+
#[derive(cynic::QueryFragment, Debug)]
32+
#[cynic(argument_struct = "LeastRecentlyReviewedPullRequestsArguments")]
33+
pub struct Repository {
34+
#[arguments(states = Some(vec![PullRequestState::Open]), first = 100, after = &args.after, labels = Some(vec!["S-waiting-on-review".to_string()]), order_by = IssueOrder { direction: OrderDirection::Asc, field: IssueOrderField::UpdatedAt })]
35+
pub pull_requests: PullRequestConnection,
36+
}
37+
38+
#[derive(cynic::QueryFragment, Debug)]
39+
pub struct PullRequestConnection {
40+
pub total_count: i32,
41+
pub page_info: PageInfo,
42+
pub nodes: Option<Vec<Option<PullRequest>>>,
43+
}
44+
45+
#[derive(cynic::QueryFragment, Debug)]
46+
pub struct PullRequest {
47+
pub number: i32,
48+
pub created_at: DateTime,
49+
pub url: Uri,
50+
pub title: String,
51+
#[arguments(first = 100)]
52+
pub labels: Option<LabelConnection>,
53+
pub is_draft: bool,
54+
#[arguments(first = 100)]
55+
pub assignees: UserConnection,
56+
#[arguments(first = 100, order_by = IssueCommentOrder { direction: OrderDirection::Desc, field: IssueCommentOrderField::UpdatedAt })]
57+
pub comments: IssueCommentConnection,
58+
#[arguments(last = 20)]
59+
pub latest_reviews: Option<PullRequestReviewConnection>,
60+
}
61+
62+
#[derive(cynic::QueryFragment, Debug)]
63+
pub struct PullRequestReviewConnection {
64+
pub total_count: i32,
65+
pub nodes: Option<Vec<Option<PullRequestReview>>>,
66+
}
67+
68+
#[derive(cynic::QueryFragment, Debug)]
69+
pub struct PullRequestReview {
70+
pub author: Option<Actor>,
71+
pub created_at: DateTime,
72+
}
73+
74+
#[derive(cynic::QueryFragment, Debug)]
75+
pub struct UserConnection {
76+
pub nodes: Option<Vec<Option<User>>>,
77+
}
78+
79+
#[derive(cynic::QueryFragment, Debug)]
80+
pub struct User {
81+
pub login: String,
82+
}
83+
84+
#[derive(cynic::QueryFragment, Debug)]
85+
pub struct PageInfo {
86+
pub has_next_page: bool,
87+
pub end_cursor: Option<String>,
88+
}
89+
90+
#[derive(cynic::QueryFragment, Debug)]
91+
pub struct LabelConnection {
92+
pub nodes: Option<Vec<Option<Label>>>,
93+
}
94+
95+
#[derive(cynic::QueryFragment, Debug)]
96+
pub struct Label {
97+
pub name: String,
98+
}
99+
100+
#[derive(cynic::QueryFragment, Debug)]
101+
pub struct IssueCommentConnection {
102+
pub total_count: i32,
103+
pub nodes: Option<Vec<Option<IssueComment>>>,
104+
}
105+
106+
#[derive(cynic::QueryFragment, Debug)]
107+
pub struct IssueComment {
108+
pub author: Option<Actor>,
109+
pub created_at: DateTime,
110+
}
111+
112+
#[derive(cynic::Enum, Clone, Copy, Debug)]
113+
pub enum IssueCommentOrderField {
114+
UpdatedAt,
115+
}
116+
117+
#[derive(cynic::Enum, Clone, Copy, Debug)]
118+
pub enum IssueOrderField {
119+
Comments,
120+
CreatedAt,
121+
UpdatedAt,
122+
}
123+
124+
#[derive(cynic::Enum, Clone, Copy, Debug)]
125+
pub enum OrderDirection {
126+
Asc,
127+
Desc,
128+
}
129+
130+
#[derive(cynic::Enum, Clone, Copy, Debug)]
131+
pub enum PullRequestState {
132+
Closed,
133+
Merged,
134+
Open,
135+
}
136+
137+
#[derive(cynic::InputObject, Debug)]
138+
pub struct IssueOrder {
139+
pub direction: OrderDirection,
140+
pub field: IssueOrderField,
141+
}
142+
143+
#[derive(cynic::InputObject, Debug)]
144+
pub struct IssueCommentOrder {
145+
pub direction: OrderDirection,
146+
pub field: IssueCommentOrderField,
147+
}
148+
149+
#[derive(cynic::QueryFragment, Debug)]
150+
pub struct Actor {
151+
pub login: String,
152+
}
153+
154+
#[derive(cynic::Scalar, Debug, Clone)]
155+
pub struct Uri(pub String);
156+
}
157+
158+
mod schema {
159+
cynic::use_schema!("src/github.graphql");
160+
}

src/agenda.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ pub fn prioritization<'a>() -> Box<dyn Action> {
414414
QueryMap {
415415
name: "top_unreviewed_prs",
416416
kind: QueryKind::List,
417-
query: Arc::new(github::graphql::LeastRecentlyReviewedPullRequests),
417+
query: Arc::new(github::LeastRecentlyReviewedPullRequests),
418418
},
419419
],
420420
},

src/github.rs

Lines changed: 171 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ use std::{
1414
};
1515
use tracing as log;
1616

17-
pub mod graphql;
18-
1917
#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
2018
pub struct User {
2119
pub login: String,
@@ -1487,6 +1485,177 @@ pub trait IssuesQuery {
14871485
) -> anyhow::Result<Vec<crate::actions::IssueDecorator>>;
14881486
}
14891487

1488+
pub struct LeastRecentlyReviewedPullRequests;
1489+
#[async_trait]
1490+
impl IssuesQuery for LeastRecentlyReviewedPullRequests {
1491+
async fn query<'a>(
1492+
&'a self,
1493+
repo: &'a Repository,
1494+
_include_fcp_details: bool,
1495+
client: &'a GithubClient,
1496+
) -> anyhow::Result<Vec<crate::actions::IssueDecorator>> {
1497+
use cynic::QueryBuilder;
1498+
use github_graphql::queries;
1499+
1500+
let repository_owner = repo.owner().to_owned();
1501+
let repository_name = repo.name().to_owned();
1502+
1503+
let mut prs: Vec<Option<queries::PullRequest>> = vec![];
1504+
1505+
let mut args = queries::LeastRecentlyReviewedPullRequestsArguments {
1506+
repository_owner,
1507+
repository_name: repository_name.clone(),
1508+
after: None,
1509+
};
1510+
loop {
1511+
let query = queries::LeastRecentlyReviewedPullRequests::build(&args);
1512+
let req = client.post(Repository::GITHUB_GRAPHQL_API_URL);
1513+
let req = req.json(&query);
1514+
1515+
let (resp, req_dbg) = client._send_req(req).await?;
1516+
let response = resp.json().await.context(req_dbg)?;
1517+
let data: cynic::GraphQlResponse<queries::LeastRecentlyReviewedPullRequests> =
1518+
query.decode_response(response).with_context(|| {
1519+
format!("failed to parse response for `LeastRecentlyReviewedPullRequests`")
1520+
})?;
1521+
if let Some(errors) = data.errors {
1522+
anyhow::bail!("There were graphql errors. {:?}", errors);
1523+
}
1524+
let repository = data
1525+
.data
1526+
.ok_or_else(|| anyhow::anyhow!("No data returned."))?
1527+
.repository
1528+
.ok_or_else(|| anyhow::anyhow!("No repository."))?;
1529+
prs.extend(
1530+
repository
1531+
.pull_requests
1532+
.nodes
1533+
.unwrap_or_default()
1534+
.into_iter(),
1535+
);
1536+
let page_info = repository.pull_requests.page_info;
1537+
if !page_info.has_next_page || page_info.end_cursor.is_none() {
1538+
break;
1539+
}
1540+
args.after = page_info.end_cursor;
1541+
}
1542+
1543+
let mut prs: Vec<_> = prs
1544+
.into_iter()
1545+
.filter_map(|pr| pr)
1546+
.filter_map(|pr| {
1547+
if pr.is_draft {
1548+
return None;
1549+
}
1550+
let labels = pr.labels;
1551+
let labels = (|| -> Option<_> {
1552+
let labels = labels?;
1553+
let nodes = labels.nodes?;
1554+
let labels = nodes
1555+
.into_iter()
1556+
.filter_map(|node| node)
1557+
.map(|node| node.name)
1558+
.collect::<Vec<_>>();
1559+
Some(labels)
1560+
})()
1561+
.unwrap_or_default();
1562+
if !labels.iter().any(|label| label == "T-compiler") {
1563+
return None;
1564+
}
1565+
let labels = labels.join(", ");
1566+
1567+
let assignees: Vec<_> = pr
1568+
.assignees
1569+
.nodes
1570+
.unwrap_or_default()
1571+
.into_iter()
1572+
.filter_map(|user| user)
1573+
.map(|user| user.login)
1574+
.collect();
1575+
1576+
let latest_reviews = pr.latest_reviews;
1577+
let mut reviews = (|| -> Option<_> {
1578+
let reviews = latest_reviews?;
1579+
let nodes = reviews.nodes?;
1580+
let reviews = nodes
1581+
.into_iter()
1582+
.filter_map(|node| node)
1583+
.filter_map(|node| {
1584+
let created_at = node.created_at;
1585+
node.author.map(|author| (author, created_at))
1586+
})
1587+
.map(|(author, created_at)| (author.login, created_at))
1588+
.collect::<Vec<_>>();
1589+
Some(reviews)
1590+
})()
1591+
.unwrap_or_default();
1592+
reviews.sort_by_key(|r| r.1);
1593+
1594+
let comments = pr.comments;
1595+
let comments = (|| -> Option<_> {
1596+
let nodes = comments.nodes?;
1597+
let comments = nodes
1598+
.into_iter()
1599+
.filter_map(|node| node)
1600+
.filter_map(|node| {
1601+
let created_at = node.created_at;
1602+
node.author.map(|author| (author, created_at))
1603+
})
1604+
.map(|(author, created_at)| (author.login, created_at))
1605+
.collect::<Vec<_>>();
1606+
Some(comments)
1607+
})()
1608+
.unwrap_or_default();
1609+
let mut comments: Vec<_> = comments
1610+
.into_iter()
1611+
.filter(|comment| assignees.contains(&comment.0))
1612+
.collect();
1613+
comments.sort_by_key(|c| c.1);
1614+
1615+
let updated_at = std::cmp::max(
1616+
reviews.last().map(|t| t.1).unwrap_or(pr.created_at),
1617+
comments.last().map(|t| t.1).unwrap_or(pr.created_at),
1618+
);
1619+
let assignees = assignees.join(", ");
1620+
1621+
Some((
1622+
updated_at,
1623+
pr.number as u64,
1624+
pr.title,
1625+
pr.url.0,
1626+
repository_name.clone(),
1627+
labels,
1628+
assignees,
1629+
))
1630+
})
1631+
.collect();
1632+
prs.sort_by_key(|pr| pr.0);
1633+
1634+
let prs: Vec<_> = prs
1635+
.into_iter()
1636+
.take(50)
1637+
.map(
1638+
|(updated_at, number, title, html_url, repo_name, labels, assignees)| {
1639+
let updated_at_hts = crate::actions::to_human(updated_at);
1640+
1641+
crate::actions::IssueDecorator {
1642+
number,
1643+
title,
1644+
html_url,
1645+
repo_name,
1646+
labels,
1647+
assignees,
1648+
updated_at_hts,
1649+
fcp_details: None,
1650+
}
1651+
},
1652+
)
1653+
.collect();
1654+
1655+
Ok(prs)
1656+
}
1657+
}
1658+
14901659
#[cfg(test)]
14911660
mod tests {
14921661
use super::*;

0 commit comments

Comments
 (0)