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,104 @@ 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 ( ) {
62+ // this is a review (not a review comment)
63+
64+ {
65+ // first let's store it's review body state in the cache to avoid future api calls
66+ // when the review comments webhook arrives (a few milliseconds after)
67+ let cache_key = format ! (
68+ "{}/{}/{}" ,
69+ & event. repository. full_name, event. issue. number, event. comment. id
70+ ) ;
71+ REVIEW_BODY_CACHE . lock ( ) . await . put (
72+ cache_key,
73+ Arc :: new ( if event. comment . body . is_empty ( ) {
74+ ReviewBodyState :: Absent
75+ } else {
76+ ReviewBodyState :: Present
77+ } ) ,
78+ ) ;
79+ }
80+
81+ if !event. comment . body . is_empty ( ) {
82+ // the review body is not empty, we can add to it the link to
83+ // our gh-changes-since endpoint
84+ let new_body = format ! (
85+ "{}\n \n *[View changes since this review]({link})*" ,
86+ event. comment. body,
87+ ) ;
88+
89+ event
90+ . issue
91+ . edit_review ( & ctx. github , event. comment . id , & new_body)
92+ . await
93+ . context ( "failed to update the review body" ) ?;
94+ }
95+ } else if !event. comment . body . is_empty ( )
96+ && let Some ( review_id) = event. comment . pull_request_review_id
97+ {
98+ // this is a review comment (not a review), we need to check if the parent
99+ // review already has a body (and as such a link)
100+
101+ // fetch the parent review body state, first look into the cache
102+ let review_body_state = {
103+ let cache_key = format ! (
104+ "{}/{}/{}" ,
105+ & event. repository. full_name, event. issue. number, review_id
106+ ) ;
107+ match { REVIEW_BODY_CACHE . lock ( ) . await . get ( & cache_key) } {
108+ Some ( state) => * state,
109+ None => {
110+ let review = event
111+ . issue
112+ . get_review ( & ctx. github , review_id)
113+ . await
114+ . context ( "unable to fetch the parent review" ) ?;
115+ let state = if review. body . is_empty ( ) {
116+ ReviewBodyState :: Absent
117+ } else {
118+ ReviewBodyState :: Present
119+ } ;
120+ REVIEW_BODY_CACHE
121+ . lock ( )
122+ . await
123+ . put ( cache_key, Arc :: new ( state) ) ;
124+ state
125+ }
126+ }
127+ } ;
128+
129+ if let ReviewBodyState :: Absent = review_body_state {
130+ // parent review is empty, let's add the link to the review comment instead
131+
132+ let new_body = format ! (
133+ "*[View changes since the above review was submitted]({link})*\n \n {}" ,
134+ event. comment. body
135+ ) ;
136+
137+ event
138+ . issue
139+ . edit_review_comment ( & ctx. github , event. comment . id , & new_body)
140+ . await
141+ . context ( "failed to update the review comment body" ) ?;
142+ }
143+ }
53144 }
54145
55146 Ok ( ( ) )
0 commit comments