11use std:: { sync:: Arc , time:: Duration } ;
2+ use std:: str:: FromStr ;
23
3- use anyhow:: { bail, Context , Result } ;
4+ use anyhow:: { bail, Error , Result } ;
45use chrono:: Utc ;
56use graphql_client:: { GraphQLQuery , QueryBody , Response as GraphQlResponse } ;
67use reqwest:: { Client , RequestBuilder , Response } ;
7- use serde:: Serialize ;
8+ use serde:: { Deserialize , Serialize } ;
89use tokio:: time;
910use tracing:: error;
10-
11+ use crate :: graphql :: issue :: { Comment , SubIssue , ParentIssue , ProjectV2Item , ProjectV2ItemConnection , PullRequestRef } ;
1112use crate :: { database:: Database , github:: {
12- issues:: IssuesRepositoryIssuesNodesAuthor :: User as userName,
1313 pull_requests:: PullRequestsRepositoryPullRequestsNodesReviewRequestsNodesRequestedReviewer :: User ,
1414} , settings:: Repository as RepoInfo } ;
15-
1615const GITHUB_FETCH_SIZE : i64 = 10 ;
1716const GITHUB_URL : & str = "https://api.github.com/graphql" ;
1817const APP_USER_AGENT : & str = concat ! ( env!( "CARGO_PKG_NAME" ) , "/" , env!( "CARGO_PKG_VERSION" ) , ) ;
1918const INIT_TIME : & str = "1992-06-05T00:00:00Z" ;
2019
2120type DateTime = String ;
21+ #[ allow( clippy:: upper_case_acronyms) ]
22+ type URI = String ;
2223
2324#[ derive( GraphQLQuery ) ]
2425#[ graphql(
@@ -27,6 +28,18 @@ type DateTime = String;
2728 response_derives = "Debug"
2829) ]
2930struct Issues ;
31+ pub ( crate ) use issues:: IssueState ;
32+
33+ impl FromStr for IssueState {
34+ type Err = Error ;
35+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
36+ match s {
37+ "OPEN" => Ok ( IssueState :: OPEN ) ,
38+ "CLOSED" => Ok ( IssueState :: CLOSED ) ,
39+ other => Err ( Error :: msg ( format ! ( "Unknown IssueState: {other}" ) ) ) ,
40+ }
41+ }
42+ }
3043
3144#[ derive( GraphQLQuery ) ]
3245#[ graphql(
@@ -35,12 +48,25 @@ struct Issues;
3548) ]
3649struct PullRequests ;
3750
38- #[ derive( Debug ) ]
51+ #[ derive( Debug , Serialize , Deserialize ) ]
3952pub ( super ) struct GitHubIssue {
53+ pub ( super ) id : String ,
4054 pub ( super ) number : i64 ,
4155 pub ( super ) title : String ,
4256 pub ( super ) author : String ,
43- pub ( super ) closed_at : Option < DateTime > ,
57+ pub ( super ) body : String ,
58+ pub ( super ) state : IssueState ,
59+ pub ( super ) assignees : Vec < String > ,
60+ pub ( super ) labels : Vec < String > ,
61+ pub ( super ) comments : Vec < Comment > ,
62+ pub ( super ) project_items : ProjectV2ItemConnection ,
63+ pub ( super ) sub_issues : Vec < SubIssue > ,
64+ pub ( super ) parent : Option < ParentIssue > ,
65+ pub ( super ) url : String ,
66+ pub ( super ) closed_by_pull_requests : Vec < PullRequestRef > ,
67+ pub ( super ) created_at : String ,
68+ pub ( super ) updated_at : String ,
69+ pub ( super ) closed_at : Option < String > ,
4470}
4571
4672#[ derive( Debug ) ]
@@ -116,6 +142,7 @@ pub(super) async fn fetch_periodically(
116142 }
117143}
118144
145+ #[ allow( clippy:: too_many_lines) ]
119146async fn send_github_issue_query (
120147 owner : & str ,
121148 name : & str ,
@@ -132,38 +159,137 @@ async fn send_github_issue_query(
132159 first : Some ( GITHUB_FETCH_SIZE ) ,
133160 last : None ,
134161 before : None ,
135- after : end_cur,
162+ after : end_cur. take ( ) ,
136163 since : Some ( since. to_string ( ) ) ,
137164 } ;
138165 let resp_body: GraphQlResponse < issues:: ResponseData > =
139166 send_query :: < Issues > ( token, var) . await ?. json ( ) . await ?;
140167 if let Some ( data) = resp_body. data {
141168 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 ( ) ;
169+ let nodes = repository. issues . nodes . unwrap_or_default ( ) ;
170+ for issue in nodes. into_iter ( ) . flatten ( ) {
171+ let author = match issue. author {
172+ Some ( issues:: IssuesRepositoryIssuesNodesAuthor :: User ( u) ) => u. login ,
173+ _ => String :: new ( ) ,
174+ } ;
145175
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 ;
176+ issues. push ( GitHubIssue {
177+ id : issue. id ,
178+ number : issue. number ,
179+ title : issue. title ,
180+ author,
181+ body : issue. body ,
182+ state : issue. state ,
183+ assignees : issue. assignees . nodes . unwrap_or_default ( ) . into_iter ( ) . flatten ( ) . map ( |n| n. login ) . collect ( ) ,
184+ labels : issue. labels . and_then ( |l| l. nodes ) . unwrap_or_default ( ) . into_iter ( ) . flatten ( ) . map ( |node| node. name ) . collect ( ) ,
185+ comments : issue. comments . nodes . unwrap_or_default ( ) . into_iter ( ) . flatten ( ) . map ( |comment| Comment {
186+ author : match comment. author {
187+ Some ( issues:: IssuesRepositoryIssuesNodesCommentsNodesAuthor :: User ( u) ) => u. login ,
188+ _ => String :: new ( ) ,
189+ } ,
190+ body : comment. body ,
191+ created_at : comment. created_at ,
192+ id : comment. id ,
193+ repository_name : comment. repository . name ,
194+ updated_at : comment. updated_at ,
195+ url : comment. url ,
196+ } ) . collect ( ) ,
197+ project_items : ProjectV2ItemConnection {
198+ total_count : issue. project_items . total_count . try_into ( ) . unwrap_or_default ( ) ,
199+ nodes : issue
200+ . project_items
201+ . nodes
202+ . unwrap_or_default ( )
203+ . into_iter ( )
204+ . flatten ( )
205+ . map ( |node| ProjectV2Item {
206+ id : node. id ,
207+ todo_status : node. todo_status . map ( |e| format ! ( "{e:?}" ) ) ,
208+ todo_priority : node. todo_priority . map ( |e| format ! ( "{e:?}" ) ) ,
209+ todo_size : node. todo_size . map ( |e| format ! ( "{e:?}" ) ) ,
210+ todo_initiation_option :
211+ node. todo_initiation_option . map ( |e| format ! ( "{e:?}" ) ) ,
212+ todo_pending_days : node. todo_pending_days . map ( |e| {
213+ format ! ( "{e:?}" )
214+ . chars ( )
215+ . filter ( char:: is_ascii_digit)
216+ . collect :: < String > ( )
217+ . parse :: < i32 > ( )
218+ . unwrap_or_default ( )
219+ } ) ,
220+
221+ } )
222+ . collect ( ) ,
223+ } ,
224+
225+ sub_issues : issue
226+ . sub_issues
227+ . nodes
228+ . into_iter ( )
229+ . flatten ( )
230+ . flatten ( )
231+ . map ( |si| SubIssue {
232+ id : si. id ,
233+ number : si. number . try_into ( ) . unwrap_or_default ( ) ,
234+ title : si. title ,
235+ state : si. state ,
236+ closed_at : si. closed_at ,
237+ created_at : si. created_at ,
238+ updated_at : si. updated_at ,
239+ author : match si. author {
240+ Some ( issues:: IssuesRepositoryIssuesNodesSubIssuesNodesAuthor :: User ( u) ) => u. login ,
241+ _ => String :: new ( ) ,
242+ } ,
243+ assignees : si
244+ . assignees
245+ . nodes
246+ . into_iter ( )
247+ . flatten ( )
248+ . flatten ( )
249+ . map ( |n| n. login )
250+ . collect ( ) ,
251+ } )
252+ . collect ( ) ,
253+ parent : issue. parent . map ( |parent| ParentIssue {
254+ id : parent. id ,
255+ number : i32:: try_from ( parent. number ) . unwrap_or_default ( ) ,
256+ title : parent. title ,
257+ } ) ,
258+ url : issue. url ,
259+ closed_by_pull_requests : issue
260+ . closed_by_pull_requests_references
261+ . map ( |r| r. edges )
262+ . unwrap_or_default ( )
263+ . into_iter ( )
264+ . flatten ( )
265+ . flatten ( )
266+ . filter_map ( |edge| edge. node . map ( |node| PullRequestRef {
267+ number : i32:: try_from ( node. number ) . unwrap_or_default ( ) ,
268+ state : format ! ( "{:?}" , node. state) ,
269+ closed_at : node. closed_at ,
270+ created_at : node. created_at ,
271+ updated_at : node. updated_at ,
272+ author : match node. author {
273+ Some ( issues:: IssuesRepositoryIssuesNodesClosedByPullRequestsReferencesEdgesNodeAuthor :: User ( u) ) => u. login ,
274+ _ => String :: new ( ) ,
275+ } ,
276+ url : node. url ,
277+ } ) )
278+ . collect ( ) ,
279+ created_at : issue. created_at ,
280+ updated_at : issue. updated_at ,
281+ closed_at : issue. closed_at ,
282+ } ) ;
283+ }
284+ if !repository. issues . page_info . has_next_page {
285+ total_issue. push ( issues) ;
286+ break ;
163287 }
288+ end_cur = repository. issues . page_info . end_cursor ;
289+ continue ;
164290 }
291+ bail ! ( "Failed to parse response data" ) ;
165292 }
166- bail ! ( "Failed to parse response data" ) ;
167293 }
168294 Ok ( total_issue)
169295}
0 commit comments