graphql.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. use anyhow::Context;
  2. use async_trait::async_trait;
  3. #[cynic::schema_for_derives(file = "src/github/github.graphql", module = "schema")]
  4. mod queries {
  5. use super::schema;
  6. pub type DateTime = chrono::DateTime<chrono::Utc>;
  7. cynic::impl_scalar!(DateTime, schema::DateTime);
  8. #[derive(cynic::FragmentArguments, Debug)]
  9. pub struct LeastRecentlyReviewedPullRequestsArguments {
  10. pub repository_owner: String,
  11. pub repository_name: String,
  12. pub after: Option<String>,
  13. }
  14. #[derive(cynic::QueryFragment, Debug)]
  15. #[cynic(
  16. graphql_type = "Query",
  17. argument_struct = "LeastRecentlyReviewedPullRequestsArguments"
  18. )]
  19. pub struct LeastRecentlyReviewedPullRequests {
  20. #[arguments(owner = &args.repository_owner, name = &args.repository_name)]
  21. pub repository: Option<Repository>,
  22. }
  23. #[derive(cynic::QueryFragment, Debug)]
  24. #[cynic(argument_struct = "LeastRecentlyReviewedPullRequestsArguments")]
  25. pub struct Repository {
  26. #[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 })]
  27. pub pull_requests: PullRequestConnection,
  28. }
  29. #[derive(cynic::QueryFragment, Debug)]
  30. pub struct PullRequestConnection {
  31. pub total_count: i32,
  32. pub page_info: PageInfo,
  33. pub nodes: Option<Vec<Option<PullRequest>>>,
  34. }
  35. #[derive(cynic::QueryFragment, Debug)]
  36. pub struct PullRequest {
  37. pub number: i32,
  38. pub created_at: DateTime,
  39. pub url: Uri,
  40. pub title: String,
  41. #[arguments(first = 100)]
  42. pub labels: Option<LabelConnection>,
  43. pub is_draft: bool,
  44. #[arguments(first = 100)]
  45. pub assignees: UserConnection,
  46. #[arguments(first = 100, order_by = IssueCommentOrder { direction: OrderDirection::Desc, field: IssueCommentOrderField::UpdatedAt })]
  47. pub comments: IssueCommentConnection,
  48. #[arguments(last = 5)]
  49. pub reviews: Option<PullRequestReviewConnection>,
  50. }
  51. #[derive(cynic::QueryFragment, Debug)]
  52. pub struct PullRequestReviewConnection {
  53. pub total_count: i32,
  54. pub nodes: Option<Vec<Option<PullRequestReview>>>,
  55. }
  56. #[derive(cynic::QueryFragment, Debug)]
  57. pub struct PullRequestReview {
  58. pub author: Option<Actor>,
  59. pub created_at: DateTime,
  60. }
  61. #[derive(cynic::QueryFragment, Debug)]
  62. pub struct UserConnection {
  63. pub nodes: Option<Vec<Option<User>>>,
  64. }
  65. #[derive(cynic::QueryFragment, Debug)]
  66. pub struct User {
  67. pub login: String,
  68. }
  69. #[derive(cynic::QueryFragment, Debug)]
  70. pub struct PageInfo {
  71. pub has_next_page: bool,
  72. pub end_cursor: Option<String>,
  73. }
  74. #[derive(cynic::QueryFragment, Debug)]
  75. pub struct LabelConnection {
  76. pub nodes: Option<Vec<Option<Label>>>,
  77. }
  78. #[derive(cynic::QueryFragment, Debug)]
  79. pub struct Label {
  80. pub name: String,
  81. }
  82. #[derive(cynic::QueryFragment, Debug)]
  83. pub struct IssueCommentConnection {
  84. pub total_count: i32,
  85. pub nodes: Option<Vec<Option<IssueComment>>>,
  86. }
  87. #[derive(cynic::QueryFragment, Debug)]
  88. pub struct IssueComment {
  89. pub author: Option<Actor>,
  90. pub created_at: DateTime,
  91. }
  92. #[derive(cynic::Enum, Clone, Copy, Debug)]
  93. pub enum IssueCommentOrderField {
  94. UpdatedAt,
  95. }
  96. #[derive(cynic::Enum, Clone, Copy, Debug)]
  97. pub enum IssueOrderField {
  98. Comments,
  99. CreatedAt,
  100. UpdatedAt,
  101. }
  102. #[derive(cynic::Enum, Clone, Copy, Debug)]
  103. pub enum OrderDirection {
  104. Asc,
  105. Desc,
  106. }
  107. #[derive(cynic::Enum, Clone, Copy, Debug)]
  108. pub enum PullRequestState {
  109. Closed,
  110. Merged,
  111. Open,
  112. }
  113. #[derive(cynic::InputObject, Debug)]
  114. pub struct IssueOrder {
  115. pub direction: OrderDirection,
  116. pub field: IssueOrderField,
  117. }
  118. #[derive(cynic::InputObject, Debug)]
  119. pub struct IssueCommentOrder {
  120. pub direction: OrderDirection,
  121. pub field: IssueCommentOrderField,
  122. }
  123. #[derive(cynic::QueryFragment, Debug)]
  124. pub struct Actor {
  125. pub login: String,
  126. }
  127. #[derive(cynic::Scalar, Debug, Clone)]
  128. pub struct Uri(pub String);
  129. }
  130. mod schema {
  131. cynic::use_schema!("src/github/github.graphql");
  132. }
  133. #[async_trait]
  134. pub trait IssuesQuery {
  135. async fn query<'a>(
  136. &'a self,
  137. repo: &'a super::Repository,
  138. client: &'a super::GithubClient,
  139. ) -> anyhow::Result<Vec<crate::actions::IssueDecorator>>;
  140. }
  141. pub struct LeastRecentlyReviewedPullRequests;
  142. #[async_trait]
  143. impl IssuesQuery for LeastRecentlyReviewedPullRequests {
  144. async fn query<'a>(
  145. &'a self,
  146. repo: &'a super::Repository,
  147. client: &'a super::GithubClient,
  148. ) -> anyhow::Result<Vec<crate::actions::IssueDecorator>> {
  149. use cynic::QueryBuilder;
  150. let repository_owner = repo.owner().to_owned();
  151. let repository_name = repo.name().to_owned();
  152. let mut prs = vec![];
  153. let mut args = queries::LeastRecentlyReviewedPullRequestsArguments {
  154. repository_owner,
  155. repository_name: repository_name.clone(),
  156. after: None,
  157. };
  158. loop {
  159. let query = queries::LeastRecentlyReviewedPullRequests::build(&args);
  160. let req = client.post(super::Repository::GITHUB_GRAPHQL_API_URL);
  161. let req = req.json(&query);
  162. let (resp, req_dbg) = client._send_req(req).await?;
  163. let response = resp.json().await.context(req_dbg)?;
  164. let data: cynic::GraphQlResponse<queries::LeastRecentlyReviewedPullRequests> =
  165. query.decode_response(response).with_context(|| {
  166. format!("failed to parse response for `LeastRecentlyReviewedPullRequests`")
  167. })?;
  168. if let Some(errors) = data.errors {
  169. anyhow::bail!("There were graphql errors. {:?}", errors);
  170. }
  171. let repository = data
  172. .data
  173. .ok_or_else(|| anyhow::anyhow!("No data returned."))?
  174. .repository
  175. .ok_or_else(|| anyhow::anyhow!("No repository."))?;
  176. prs.extend(
  177. repository
  178. .pull_requests
  179. .nodes
  180. .unwrap_or_default()
  181. .into_iter(),
  182. );
  183. let page_info = repository.pull_requests.page_info;
  184. if !page_info.has_next_page || page_info.end_cursor.is_none() {
  185. break;
  186. }
  187. args.after = page_info.end_cursor;
  188. }
  189. let mut prs: Vec<_> = prs
  190. .into_iter()
  191. .filter_map(|pr| pr)
  192. .filter_map(|pr| {
  193. if pr.is_draft {
  194. return None;
  195. }
  196. let labels = pr
  197. .labels
  198. .map(|labels| {
  199. labels
  200. .nodes
  201. .map(|nodes| {
  202. nodes
  203. .into_iter()
  204. .filter_map(|node| node)
  205. .map(|node| node.name)
  206. .collect::<Vec<_>>()
  207. })
  208. .unwrap_or_default()
  209. })
  210. .unwrap_or_default();
  211. if !labels.iter().any(|label| label == "T-compiler") {
  212. return None;
  213. }
  214. let labels = labels.join(", ");
  215. let assignees: Vec<_> = pr
  216. .assignees
  217. .nodes
  218. .unwrap_or_default()
  219. .into_iter()
  220. .filter_map(|user| user)
  221. .map(|user| user.login)
  222. .collect();
  223. let mut reviews = pr
  224. .reviews
  225. .map(|reviews| {
  226. reviews
  227. .nodes
  228. .map(|nodes| {
  229. nodes
  230. .into_iter()
  231. .filter_map(|n| n)
  232. .map(|review| {
  233. (
  234. review
  235. .author
  236. .map(|a| a.login)
  237. .unwrap_or("N/A".to_string()),
  238. review.created_at,
  239. )
  240. })
  241. .collect::<Vec<_>>()
  242. })
  243. .unwrap_or_default()
  244. })
  245. .unwrap_or_default();
  246. reviews.sort_by_key(|r| r.1);
  247. let comments = pr
  248. .comments
  249. .nodes
  250. .map(|nodes| {
  251. nodes
  252. .into_iter()
  253. .filter_map(|n| n)
  254. .map(|comment| {
  255. (
  256. comment.author.map(|a| a.login).unwrap_or("N/A".to_string()),
  257. comment.created_at,
  258. )
  259. })
  260. .collect::<Vec<_>>()
  261. })
  262. .unwrap_or_default();
  263. let mut comments: Vec<_> = comments
  264. .into_iter()
  265. .filter(|comment| assignees.contains(&comment.0))
  266. .collect();
  267. comments.sort_by_key(|c| c.1);
  268. let updated_at = std::cmp::max(
  269. reviews.last().map(|t| t.1).unwrap_or(pr.created_at),
  270. comments.last().map(|t| t.1).unwrap_or(pr.created_at),
  271. );
  272. let assignees = assignees.join(", ");
  273. Some((
  274. updated_at,
  275. pr.number as u64,
  276. pr.title,
  277. pr.url.0,
  278. repository_name.clone(),
  279. labels,
  280. assignees,
  281. ))
  282. })
  283. .collect();
  284. prs.sort_by_key(|pr| pr.0);
  285. let prs: Vec<_> = prs
  286. .into_iter()
  287. .take(5)
  288. .map(
  289. |(updated_at, number, title, html_url, repo_name, labels, assignees)| {
  290. let updated_at = crate::actions::to_human(updated_at);
  291. crate::actions::IssueDecorator {
  292. number,
  293. title,
  294. html_url,
  295. repo_name,
  296. labels,
  297. assignees,
  298. updated_at,
  299. }
  300. },
  301. )
  302. .collect();
  303. Ok(prs)
  304. }
  305. }