1+ use std:: sync:: { Arc , LazyLock } ;
2+
13use anyhow:: Context as _;
24
35use crate :: {
6+ cache,
47 config:: ReviewChangesSinceConfig ,
58 github:: { Comment , Event , Issue , IssueCommentAction , IssueCommentEvent } ,
69 handlers:: Context ,
710} ;
811
12+ static REVIEW_BODY_CACHE : LazyLock <
13+ tokio:: sync:: Mutex < cache:: LeastRecentlyUsedCache < String , ReviewBodyState > > ,
14+ > = LazyLock :: new ( || tokio:: sync:: Mutex :: new ( cache:: LeastRecentlyUsedCache :: new ( 1000 ) ) ) ;
15+
16+ #[ derive( Copy , Clone , Debug ) ]
17+ enum ReviewBodyState {
18+ Present ,
19+ Absent ,
20+ }
21+
22+ impl cache:: EstimatedSize for ReviewBodyState {
23+ fn estimated_size ( & self ) -> usize {
24+ std:: mem:: size_of :: < Self > ( )
25+ }
26+ }
27+
928/// Checks if this event is a PR review creation and adds in the body (if there is one)
1029/// a link our `gh-changes-since` endpoint to view changes since this review.
1130pub ( crate ) async fn handle (
@@ -14,6 +33,7 @@ pub(crate) async fn handle(
1433 event : & Event ,
1534 _config : & ReviewChangesSinceConfig ,
1635) -> anyhow:: Result < ( ) > {
36+ // Match on each review and top-level review comment
1737 if let Event :: IssueComment (
1838 event @ IssueCommentEvent {
1939 action : IssueCommentAction :: Created ,
@@ -23,33 +43,105 @@ pub(crate) async fn handle(
2343 } ,
2444 comment :
2545 Comment {
26- pr_review_state : Some ( _ ) ,
46+ in_reply_to_id : None ,
2747 ..
2848 } ,
2949 ..
3050 } ,
3151 ) = event
32- && !event. comment . body . is_empty ( )
3352 {
34- // Add link our gh-changes-since endpoint to view changes since this review
35-
3653 let issue_repo = event. issue . repository ( ) ;
3754 let pr_num = event. issue . number ;
3855
3956 let base = & event. issue . base . as_ref ( ) . context ( "no base" ) ?. sha ;
4057 let head = & event. issue . head . as_ref ( ) . context ( "no head" ) ?. sha ;
4158
4259 let link = format ! ( "https://{host}/gh-changes-since/{issue_repo}/{pr_num}/{base}..{head}" ) ;
43- let new_body = format ! (
44- "{}\n \n *[View changes since this review]({link})*" ,
45- event. comment. body
46- ) ;
47-
48- event
49- . issue
50- . edit_review ( & ctx. github , event. comment . id , & new_body)
51- . await
52- . context ( "failed to update the review body" ) ?;
60+
61+ if event. comment . pull_request_review_id . is_none ( ) && event. comment . pr_review_state . is_some ( )
62+ {
63+ // this is a review (not a review comment)
64+
65+ {
66+ // first let's store it's review body state in the cache to avoid future api calls
67+ // when the review comments webhook arrives (a few milliseconds after)
68+ let cache_key = format ! (
69+ "{}/{}/{}" ,
70+ & event. repository. full_name, event. issue. number, event. comment. id
71+ ) ;
72+ REVIEW_BODY_CACHE . lock ( ) . await . put (
73+ cache_key,
74+ Arc :: new ( if event. comment . body . is_empty ( ) {
75+ ReviewBodyState :: Absent
76+ } else {
77+ ReviewBodyState :: Present
78+ } ) ,
79+ ) ;
80+ }
81+
82+ if !event. comment . body . is_empty ( ) {
83+ // the review body is not empty, we can add to it the link to
84+ // our gh-changes-since endpoint
85+ let new_body = format ! (
86+ "{}\n \n *[View changes since this review]({link})*" ,
87+ event. comment. body,
88+ ) ;
89+
90+ event
91+ . issue
92+ . edit_review ( & ctx. github , event. comment . id , & new_body)
93+ . await
94+ . context ( "failed to update the review body" ) ?;
95+ }
96+ } else if !event. comment . body . is_empty ( )
97+ && let Some ( review_id) = event. comment . pull_request_review_id
98+ {
99+ // this is a review comment (not a review), we need to check if the parent
100+ // review already has a body (and as such a link)
101+
102+ // fetch the parent review body state, first look into the cache
103+ let review_body_state = {
104+ let cache_key = format ! (
105+ "{}/{}/{}" ,
106+ & event. repository. full_name, event. issue. number, review_id
107+ ) ;
108+ match { REVIEW_BODY_CACHE . lock ( ) . await . get ( & cache_key) } {
109+ Some ( state) => * state,
110+ None => {
111+ let review = event
112+ . issue
113+ . get_review ( & ctx. github , review_id)
114+ . await
115+ . context ( "unable to fetch the parent review" ) ?;
116+ let state = if review. body . is_empty ( ) {
117+ ReviewBodyState :: Absent
118+ } else {
119+ ReviewBodyState :: Present
120+ } ;
121+ REVIEW_BODY_CACHE
122+ . lock ( )
123+ . await
124+ . put ( cache_key, Arc :: new ( state) ) ;
125+ state
126+ }
127+ }
128+ } ;
129+
130+ if let ReviewBodyState :: Absent = review_body_state {
131+ // parent review is empty, let's add the link to the review comment instead
132+
133+ let new_body = format ! (
134+ "*[View changes since the review]({link})*\n \n {}" ,
135+ event. comment. body
136+ ) ;
137+
138+ event
139+ . issue
140+ . edit_review_comment ( & ctx. github , event. comment . id , & new_body)
141+ . await
142+ . context ( "failed to update the review comment body" ) ?;
143+ }
144+ }
53145 }
54146
55147 Ok ( ( ) )
0 commit comments