@@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result};
44use chrono:: Utc ;
55use graphql_client:: { GraphQLQuery , QueryBody , Response as GraphQlResponse } ;
66use reqwest:: { Client , RequestBuilder , Response } ;
7- use serde:: Serialize ;
7+ use serde:: { Deserialize , Serialize } ;
88use tokio:: time;
99use tracing:: error;
1010
@@ -13,45 +13,177 @@ use crate::{database::Database, github::{
1313 pull_requests:: PullRequestsRepositoryPullRequestsNodesReviewRequestsNodesRequestedReviewer :: User ,
1414} , settings:: Repository as RepoInfo } ;
1515
16+ pub ( crate ) use pull_requests:: { PullRequestState , PullRequestReviewState } ;
17+
1618const GITHUB_FETCH_SIZE : i64 = 10 ;
1719const GITHUB_URL : & str = "https://api.github.com/graphql" ;
1820const APP_USER_AGENT : & str = concat ! ( env!( "CARGO_PKG_NAME" ) , "/" , env!( "CARGO_PKG_VERSION" ) , ) ;
1921const INIT_TIME : & str = "1992-06-05T00:00:00Z" ;
2022
21- type DateTime = String ;
23+ pub ( crate ) type DateTime = chrono :: DateTime < Utc > ;
2224
25+ #[ allow( clippy:: upper_case_acronyms) ]
26+ type URI = String ;
2327#[ derive( GraphQLQuery ) ]
2428#[ graphql(
2529 schema_path = "src/github/schema.graphql" ,
2630 query_path = "src/github/issues.graphql" ,
27- response_derives = "Debug"
31+ response_derives = "Debug" ,
32+ scalar = "DateTime = DateTime"
2833) ]
2934struct Issues ;
3035
3136#[ derive( GraphQLQuery ) ]
3237#[ graphql(
3338 schema_path = "src/github/schema.graphql" ,
34- query_path = "src/github/pull_requests.graphql"
39+ query_path = "src/github/pull_requests.graphql" ,
40+ response_derives = "Debug, Clone, PartialEq, Eq" ,
41+ scalar = "DateTime = DateTime" ,
42+ scalar = "URI = URI"
3543) ]
3644struct PullRequests ;
3745
3846#[ derive( Debug ) ]
39- pub ( super ) struct GitHubIssue {
40- pub ( super ) number : i64 ,
41- pub ( super ) title : String ,
42- pub ( super ) author : String ,
43- pub ( super ) closed_at : Option < DateTime > ,
47+ pub struct GitHubIssue {
48+ pub number : i64 ,
49+ pub title : String ,
50+ pub author : String ,
51+ pub closed_at : Option < DateTime > ,
4452}
4553
46- #[ derive( Debug ) ]
47- pub ( super ) struct GitHubPullRequests {
48- pub ( super ) number : i64 ,
49- pub ( super ) title : String ,
50- pub ( super ) assignees : Vec < String > ,
51- pub ( super ) reviewers : Vec < String > ,
54+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
55+ pub struct RepositoryNode {
56+ pub owner : String ,
57+ pub name : String ,
58+ }
59+
60+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
61+ pub struct GitHubUserConnection {
62+ pub nodes : Vec < String > ,
63+ }
64+
65+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
66+ pub struct LabelNode {
67+ pub name : String ,
68+ }
69+
70+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
71+ pub struct GitHubLabelConnection {
72+ pub nodes : Vec < LabelNode > ,
73+ }
74+
75+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
76+ pub struct CommentNode {
77+ pub body : String ,
78+ #[ serde( rename = "createdAt" ) ]
79+ pub created_at : DateTime ,
80+ #[ serde( rename = "updatedAt" ) ]
81+ pub updated_at : DateTime ,
82+ pub author : Option < String > ,
83+ }
84+
85+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
86+ pub struct GitHubCommentConnection {
87+ #[ serde( rename = "totalCount" ) ]
88+ pub total_count : i32 ,
89+ pub nodes : Vec < CommentNode > ,
90+ }
91+
92+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
93+ pub struct ReviewNode {
94+ pub author : Option < String > ,
95+ pub state : PullRequestReviewState ,
96+ pub body : Option < String > ,
97+ pub url : String ,
98+ #[ serde( rename = "createdAt" ) ]
99+ pub created_at : DateTime ,
100+ #[ serde( rename = "publishedAt" ) ]
101+ pub published_at : DateTime ,
102+ #[ serde( rename = "submittedAt" ) ]
103+ pub submitted_at : DateTime ,
104+ #[ serde( rename = "isMinimized" ) ]
105+ pub is_minimized : bool ,
106+ pub comments : GitHubCommentConnection ,
107+ }
108+
109+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
110+ pub struct GitHubReviewConnection {
111+ #[ serde( rename = "totalCount" ) ]
112+ pub total_count : i32 ,
113+ pub nodes : Vec < ReviewNode > ,
114+ }
115+
116+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
117+ pub struct ReviewRequestNode {
118+ #[ serde( rename = "requestedReviewer" ) ]
119+ pub requested_reviewer : Option < String > ,
120+ }
121+
122+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
123+ pub struct GitHubReviewRequestConnection {
124+ pub nodes : Vec < ReviewRequestNode > ,
125+ }
126+
127+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
128+ pub struct CommitPerson {
129+ pub user : Option < String > ,
130+ }
131+
132+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
133+ pub struct CommitInner {
134+ pub additions : i32 ,
135+ pub deletions : i32 ,
136+ pub message : String ,
137+ #[ serde( rename = "messageBody" ) ]
138+ pub message_body : Option < String > ,
139+ pub author : Option < CommitPerson > ,
140+ #[ serde( rename = "changedFilesIfAvailable" ) ]
141+ pub changed_files_if_available : Option < i32 > ,
142+ #[ serde( rename = "committedDate" ) ]
143+ pub committed_date : DateTime ,
144+ pub committer : Option < CommitPerson > ,
52145}
53146
54- pub ( super ) async fn fetch_periodically (
147+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
148+ pub struct GitHubCommitConnection {
149+ #[ serde( rename = "totalCount" ) ]
150+ pub total_count : i32 ,
151+ #[ serde( rename = "nodes" ) ]
152+ pub nodes : Vec < CommitInner > ,
153+ }
154+
155+ #[ derive( Clone , Debug , Serialize , Deserialize ) ]
156+ pub struct GitHubPullRequests {
157+ pub id : String ,
158+ pub number : i32 ,
159+ pub title : String ,
160+ pub body : Option < String > ,
161+ pub state : PullRequestState ,
162+ #[ serde( rename = "createdAt" ) ]
163+ pub created_at : DateTime ,
164+ #[ serde( rename = "updatedAt" ) ]
165+ pub updated_at : DateTime ,
166+ #[ serde( rename = "closedAt" ) ]
167+ pub closed_at : Option < DateTime > ,
168+ #[ serde( rename = "mergedAt" ) ]
169+ pub merged_at : Option < DateTime > ,
170+ pub author : Option < String > ,
171+ pub additions : i32 ,
172+ pub deletions : i32 ,
173+ pub url : String ,
174+ pub repository : RepositoryNode ,
175+ pub labels : GitHubLabelConnection ,
176+ pub comments : GitHubCommentConnection ,
177+ #[ serde( rename = "reviewDecision" ) ]
178+ pub review_decision : Option < PullRequestReviewState > ,
179+ pub assignees : GitHubUserConnection ,
180+ #[ serde( rename = "reviewRequests" ) ]
181+ pub review_requests : GitHubReviewRequestConnection ,
182+ pub reviews : GitHubReviewConnection ,
183+ pub commits : GitHubCommitConnection ,
184+ }
185+
186+ pub async fn fetch_periodically (
55187 repositories : Arc < Vec < RepoInfo > > ,
56188 token : String ,
57189 period : Duration ,
@@ -126,14 +258,17 @@ async fn send_github_issue_query(
126258 let mut end_cur: Option < String > = None ;
127259 let mut issues: Vec < GitHubIssue > = Vec :: new ( ) ;
128260 loop {
261+ let since_dt = since
262+ . parse :: < DateTime > ( )
263+ . context ( "Failed to parse since date" ) ?;
129264 let var = issues:: Variables {
130265 owner : owner. to_string ( ) ,
131266 name : name. to_string ( ) ,
132267 first : Some ( GITHUB_FETCH_SIZE ) ,
133268 last : None ,
134269 before : None ,
135270 after : end_cur,
136- since : Some ( since . to_string ( ) ) ,
271+ since : Some ( since_dt ) ,
137272 } ;
138273 let resp_body: GraphQlResponse < issues:: ResponseData > =
139274 send_query :: < Issues > ( token, var) . await ?. json ( ) . await ?;
@@ -151,7 +286,7 @@ async fn send_github_issue_query(
151286 number : issue. number ,
152287 title : issue. title . to_string ( ) ,
153288 author,
154- closed_at : issue. closed_at . clone ( ) ,
289+ closed_at : issue. closed_at ,
155290 } ) ;
156291 }
157292 if !repository. issues . page_info . has_next_page {
@@ -175,63 +310,75 @@ async fn send_github_pr_query(
175310) -> Result < Vec < Vec < GitHubPullRequests > > > {
176311 let mut total_prs = Vec :: new ( ) ;
177312 let mut end_cur: Option < String > = None ;
178- let mut prs : Vec < GitHubPullRequests > = Vec :: new ( ) ;
313+
179314 loop {
180315 let var = pull_requests:: Variables {
181316 owner : owner. to_string ( ) ,
182317 name : name. to_string ( ) ,
183318 first : Some ( GITHUB_FETCH_SIZE ) ,
184319 last : None ,
185320 before : None ,
186- after : end_cur,
321+ after : end_cur. take ( ) ,
187322 } ;
188323
189324 let resp_body: GraphQlResponse < pull_requests:: ResponseData > =
190325 send_query :: < PullRequests > ( token, var) . await ?. json ( ) . await ?;
326+
191327 if let Some ( data) = resp_body. data {
192- if let Some ( repository ) = data. repository {
193- if let Some ( nodes ) = repository . pull_requests . nodes . as_ref ( ) {
194- for pr in nodes . iter ( ) . flatten ( ) {
195- let mut assignees : Vec < String > = Vec :: new ( ) ;
196- let mut reviewers : Vec < String > = Vec :: new ( ) ;
197-
198- if let Some ( assignees_nodes ) = pr. assignees . nodes . as_ref ( ) {
199- for pr_assignees in assignees_nodes . iter ( ) . flatten ( ) {
200- assignees . push ( pr_assignees . login . clone ( ) ) ;
328+ if let Some ( repo ) = data. repository {
329+ let mut batch = Vec :: new ( ) ;
330+
331+ if let Some ( nodes ) = repo . pull_requests . nodes {
332+ for pr in nodes . into_iter ( ) . flatten ( ) {
333+ let mut assignees_list = Vec :: new ( ) ;
334+ if let Some ( ass_nodes ) = pr. assignees . nodes {
335+ for node in ass_nodes . into_iter ( ) . flatten ( ) {
336+ assignees_list . push ( node . login ) ;
201337 }
202338 }
203- if let Some ( reviewers_nodes) =
204- pr. review_requests . as_ref ( ) . and_then ( |r| r. nodes . as_ref ( ) )
205- {
206- for pr_reviewers in reviewers_nodes. iter ( ) . flatten ( ) {
207- if let Some ( User ( on_user) ) =
208- pr_reviewers. requested_reviewer . as_ref ( )
209- {
210- reviewers. push ( on_user. login . clone ( ) ) ;
339+ let assignees_conn = GitHubUserConnection {
340+ nodes : assignees_list,
341+ } ;
342+
343+ let mut rr_nodes = Vec :: new ( ) ;
344+ if let Some ( req_conn) = pr. review_requests {
345+ if let Some ( req_nodes) = req_conn. nodes {
346+ for rr in req_nodes. into_iter ( ) . flatten ( ) {
347+ if let Some ( User ( user_node) ) = rr. requested_reviewer {
348+ rr_nodes. push ( ReviewRequestNode {
349+ requested_reviewer : Some ( user_node. login ) ,
350+ } ) ;
351+ }
211352 }
212353 }
213354 }
355+ let requests_conn = GitHubReviewRequestConnection { nodes : rr_nodes } ;
214356
215- prs . push ( GitHubPullRequests {
216- number : pr. number ,
217- title : pr . title . to_string ( ) ,
218- assignees ,
219- reviewers ,
220- } ) ;
221- }
222- if !repository . pull_requests . page_info . has_next_page {
223- total_prs . push ( prs ) ;
224- break ;
357+ let record = GitHubPullRequests {
358+ number : i32 :: try_from ( pr. number )
359+ . context ( "pull request number out of i32 range" ) ? ,
360+ title : pr . title ,
361+ state : pr . state . clone ( ) ,
362+ assignees : assignees_conn ,
363+ review_requests : requests_conn ,
364+ .. Default :: default ( )
365+ } ;
366+ batch . push ( record ) ;
225367 }
226- end_cur = repository. pull_requests . page_info . end_cursor ;
227- continue ;
228368 }
229- end_cur = repository. pull_requests . page_info . end_cursor ;
369+
370+ total_prs. push ( batch) ;
371+ if !repo. pull_requests . page_info . has_next_page {
372+ break ;
373+ }
374+ end_cur = repo. pull_requests . page_info . end_cursor ;
230375 continue ;
231376 }
232377 }
378+
233379 bail ! ( "Failed to parse response data" ) ;
234380 }
381+
235382 Ok ( total_prs)
236383}
237384
0 commit comments