Skip to content

Commit ea4682c

Browse files
committed
Handle view changes link for body-less reviews with review comments
1 parent beae2a3 commit ea4682c

1 file changed

Lines changed: 105 additions & 14 deletions

File tree

src/handlers/review_changes_since.rs

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
1+
use std::sync::{Arc, LazyLock};
2+
13
use anyhow::Context as _;
24

35
use 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.
1130
pub(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

Comments
 (0)