Skip to content

Commit 52bfca0

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

1 file changed

Lines changed: 106 additions & 14 deletions

File tree

src/handlers/review_changes_since.rs

Lines changed: 106 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,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

Comments
 (0)