@@ -4,21 +4,35 @@ 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
11+ use crate :: github:: issues:: {
12+ IssuesRepositoryIssuesNodesAuthor :: User as IssueAuthor ,
13+ IssuesRepositoryIssuesNodesCommentsNodesAuthor :: User as IssueCommentsAuthor ,
14+ IssuesRepositoryIssuesNodesProjectItemsNodesTodoInitiationOption as TodoInitOption ,
15+ IssuesRepositoryIssuesNodesProjectItemsNodesTodoPendingDays as TodoPendingDays ,
16+ IssuesRepositoryIssuesNodesProjectItemsNodesTodoPriority as TodoPriority ,
17+ IssuesRepositoryIssuesNodesProjectItemsNodesTodoSize as TodoSize ,
18+ IssuesRepositoryIssuesNodesProjectItemsNodesTodoStatus as TodoStatus ,
19+ IssuesRepositoryIssuesNodesSubIssuesNodesAuthor :: User as SubIssueAuthor ,
20+ IssuesRepositoryIssuesNodesClosedByPullRequestsReferencesEdgesNodeAuthor :: User as PullRequestRefAuthor ,
21+ } ;
22+ use crate :: graphql:: issue:: { CommentConnection , Comment , SubIssue , ParentIssue , ProjectV2Item , ProjectV2ItemConnection , PullRequestRef } ;
1123use crate :: { database:: Database , github:: {
12- issues:: IssuesRepositoryIssuesNodesAuthor :: User as userName,
1324 pull_requests:: PullRequestsRepositoryPullRequestsNodesReviewRequestsNodesRequestedReviewer :: User ,
1425} , settings:: Repository as RepoInfo } ;
26+ pub ( crate ) use issues:: { IssueState , PullRequestState } ;
1527
1628const GITHUB_FETCH_SIZE : i64 = 10 ;
1729const GITHUB_URL : & str = "https://api.github.com/graphql" ;
1830const APP_USER_AGENT : & str = concat ! ( env!( "CARGO_PKG_NAME" ) , "/" , env!( "CARGO_PKG_VERSION" ) , ) ;
1931const INIT_TIME : & str = "1992-06-05T00:00:00Z" ;
2032
21- type DateTime = String ;
33+ pub ( crate ) type DateTime = chrono:: DateTime < Utc > ;
34+ #[ allow( clippy:: upper_case_acronyms) ]
35+ type URI = String ;
2236
2337#[ derive( GraphQLQuery ) ]
2438#[ graphql(
@@ -35,17 +49,30 @@ struct Issues;
3549) ]
3650struct PullRequests ;
3751
38- #[ derive( Debug ) ]
52+ #[ derive( Debug , Serialize , Deserialize ) ]
3953pub ( super ) struct GitHubIssue {
40- pub ( super ) number : i64 ,
54+ pub ( super ) id : String ,
55+ pub ( super ) number : i32 ,
4156 pub ( super ) title : String ,
4257 pub ( super ) author : String ,
58+ pub ( super ) body : String ,
59+ pub ( super ) state : IssueState ,
60+ pub ( super ) assignees : Vec < String > ,
61+ pub ( super ) labels : Vec < String > ,
62+ pub ( super ) comments : CommentConnection ,
63+ pub ( super ) project_items : ProjectV2ItemConnection ,
64+ pub ( super ) sub_issues : Vec < SubIssue > ,
65+ pub ( super ) parent : Option < ParentIssue > ,
66+ pub ( super ) url : String ,
67+ pub ( super ) closed_by_pull_requests : Vec < PullRequestRef > ,
68+ pub ( super ) created_at : DateTime ,
69+ pub ( super ) updated_at : DateTime ,
4370 pub ( super ) closed_at : Option < DateTime > ,
4471}
4572
4673#[ derive( Debug ) ]
4774pub ( super ) struct GitHubPullRequests {
48- pub ( super ) number : i64 ,
75+ pub ( super ) number : i32 ,
4976 pub ( super ) title : String ,
5077 pub ( super ) assignees : Vec < String > ,
5178 pub ( super ) reviewers : Vec < String > ,
@@ -116,6 +143,7 @@ pub(super) async fn fetch_periodically(
116143 }
117144}
118145
146+ #[ allow( clippy:: too_many_lines) ]
119147async fn send_github_issue_query (
120148 owner : & str ,
121149 name : & str ,
@@ -132,38 +160,187 @@ async fn send_github_issue_query(
132160 first : Some ( GITHUB_FETCH_SIZE ) ,
133161 last : None ,
134162 before : None ,
135- after : end_cur,
136- since : Some ( since. to_string ( ) ) ,
163+ after : end_cur. take ( ) ,
164+ since : Some ( since. parse :: < DateTime > ( ) ? ) ,
137165 } ;
138166 let resp_body: GraphQlResponse < issues:: ResponseData > =
139167 send_query :: < Issues > ( token, var) . await ?. json ( ) . await ?;
140168 if let Some ( data) = resp_body. data {
141169 if let Some ( repository) = data. repository {
142- if let Some ( nodes) = repository. issues . nodes . as_ref ( ) {
143- for issue in nodes. iter ( ) . flatten ( ) {
144- let mut author: String = String :: new ( ) ;
145-
146- let author_ref = issue. author . as_ref ( ) . context ( "Missing issue author" ) ?;
147- if let userName( on_user) = author_ref {
148- author. clone_from ( & on_user. login . clone ( ) ) ;
149- }
150- issues. push ( GitHubIssue {
151- number : issue. number ,
152- title : issue. title . to_string ( ) ,
153- author,
154- closed_at : issue. closed_at . clone ( ) ,
155- } ) ;
156- }
157- if !repository. issues . page_info . has_next_page {
158- total_issue. push ( issues) ;
159- break ;
160- }
161- end_cur = repository. issues . page_info . end_cursor ;
162- continue ;
170+ let nodes = repository. issues . nodes . unwrap_or_default ( ) ;
171+ for issue in nodes. into_iter ( ) . flatten ( ) {
172+ let author = match issue. author . context ( "Missing issue author" ) ? {
173+ IssueAuthor ( u) => u. login ,
174+ _ => String :: new ( ) ,
175+ } ;
176+ issues. push ( GitHubIssue {
177+ id : issue. id ,
178+ number : issue. number . try_into ( ) . unwrap_or_default ( ) ,
179+ title : issue. title ,
180+ author,
181+ body : issue. body ,
182+ state : issue. state ,
183+ assignees : issue
184+ . assignees
185+ . nodes
186+ . unwrap_or_default ( )
187+ . into_iter ( )
188+ . flatten ( )
189+ . map ( |n| n. login )
190+ . collect ( ) ,
191+ labels : issue
192+ . labels
193+ . and_then ( |l| l. nodes )
194+ . unwrap_or_default ( )
195+ . into_iter ( )
196+ . flatten ( )
197+ . map ( |node| node. name )
198+ . collect ( ) ,
199+ comments : CommentConnection {
200+ total_count : issue. comments . total_count . try_into ( ) . unwrap_or_default ( ) ,
201+ nodes : issue
202+ . comments
203+ . nodes
204+ . unwrap_or_default ( )
205+ . into_iter ( )
206+ . flatten ( )
207+ . map ( |comment| Comment {
208+ author : match comment. author {
209+ Some ( IssueCommentsAuthor ( u) ) => u. login ,
210+ _ => String :: new ( ) ,
211+ } ,
212+ body : comment. body ,
213+ created_at : comment. created_at ,
214+ id : comment. id ,
215+ repository_name : comment. repository . name ,
216+ updated_at : comment. updated_at ,
217+ url : comment. url ,
218+ } )
219+ . collect ( ) ,
220+ } ,
221+ project_items : ProjectV2ItemConnection {
222+ total_count : issue
223+ . project_items
224+ . total_count
225+ . try_into ( )
226+ . unwrap_or_default ( ) ,
227+ nodes : issue
228+ . project_items
229+ . nodes
230+ . unwrap_or_default ( )
231+ . into_iter ( )
232+ . flatten ( )
233+ . map ( |node| ProjectV2Item {
234+ id : node. id ,
235+ todo_status : node. todo_status . and_then ( |status| match status {
236+ TodoStatus :: ProjectV2ItemFieldSingleSelectValue ( inner) => {
237+ inner. name
238+ }
239+ _ => None ,
240+ } ) ,
241+ todo_priority : node. todo_priority . and_then ( |priority| {
242+ match priority {
243+ TodoPriority :: ProjectV2ItemFieldSingleSelectValue (
244+ inner,
245+ ) => inner. name ,
246+ _ => None ,
247+ }
248+ } ) ,
249+ todo_size : node. todo_size . and_then ( |size| match size {
250+ TodoSize :: ProjectV2ItemFieldSingleSelectValue ( inner) => {
251+ inner. name
252+ }
253+ _ => None ,
254+ } ) ,
255+ todo_initiation_option : node. todo_initiation_option . and_then (
256+ |init| match init {
257+ TodoInitOption :: ProjectV2ItemFieldSingleSelectValue (
258+ inner,
259+ ) => inner. name ,
260+ _ => None ,
261+ } ,
262+ ) ,
263+ todo_pending_days : node. todo_pending_days . and_then ( |days| {
264+ match days {
265+ TodoPendingDays :: ProjectV2ItemFieldNumberValue (
266+ inner,
267+ ) => inner. number ,
268+ _ => None ,
269+ }
270+ } ) ,
271+ } )
272+ . collect ( ) ,
273+ } ,
274+ sub_issues : issue
275+ . sub_issues
276+ . nodes
277+ . into_iter ( )
278+ . flatten ( )
279+ . flatten ( )
280+ . map ( |si| SubIssue {
281+ id : si. id ,
282+ number : si. number . try_into ( ) . unwrap_or_default ( ) ,
283+ title : si. title ,
284+ state : si. state ,
285+ closed_at : si. closed_at ,
286+ created_at : si. created_at ,
287+ updated_at : si. updated_at ,
288+ author : match si. author {
289+ Some ( SubIssueAuthor ( u) ) => u. login ,
290+ _ => String :: new ( ) ,
291+ } ,
292+ assignees : si
293+ . assignees
294+ . nodes
295+ . into_iter ( )
296+ . flatten ( )
297+ . flatten ( )
298+ . map ( |n| n. login )
299+ . collect ( ) ,
300+ } )
301+ . collect ( ) ,
302+ parent : issue. parent . map ( |parent| ParentIssue {
303+ id : parent. id ,
304+ number : parent. number . try_into ( ) . unwrap_or_default ( ) ,
305+ title : parent. title ,
306+ } ) ,
307+ url : issue. url ,
308+ closed_by_pull_requests : issue
309+ . closed_by_pull_requests_references
310+ . map ( |r| r. edges )
311+ . unwrap_or_default ( )
312+ . into_iter ( )
313+ . flatten ( )
314+ . flatten ( )
315+ . filter_map ( |edge| {
316+ edge. node . map ( |node| PullRequestRef {
317+ number : node. number . try_into ( ) . unwrap_or_default ( ) ,
318+ state : node. state ,
319+ closed_at : node. closed_at ,
320+ created_at : node. created_at ,
321+ updated_at : node. updated_at ,
322+ author : match node. author {
323+ Some ( PullRequestRefAuthor ( u) ) => u. login ,
324+ _ => String :: new ( ) ,
325+ } ,
326+ url : node. url ,
327+ } )
328+ } )
329+ . collect ( ) ,
330+ created_at : issue. created_at ,
331+ updated_at : issue. updated_at ,
332+ closed_at : issue. closed_at ,
333+ } ) ;
163334 }
335+ if !repository. issues . page_info . has_next_page {
336+ total_issue. push ( issues) ;
337+ break ;
338+ }
339+ end_cur = repository. issues . page_info . end_cursor ;
340+ continue ;
164341 }
342+ bail ! ( "Failed to parse response data" ) ;
165343 }
166- bail ! ( "Failed to parse response data" ) ;
167344 }
168345 Ok ( total_issue)
169346}
@@ -213,7 +390,7 @@ async fn send_github_pr_query(
213390 }
214391
215392 prs. push ( GitHubPullRequests {
216- number : pr. number ,
393+ number : pr. number . try_into ( ) . unwrap_or_default ( ) ,
217394 title : pr. title . to_string ( ) ,
218395 assignees,
219396 reviewers,
0 commit comments