-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1781 from apiraino/add-webhook-to-update-pr-workl…
…oad-queues Add webhook handler to update PR workload queues
- Loading branch information
Showing
5 changed files
with
137 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
//! This module updates the PR workqueue of the Rust project contributors | ||
//! | ||
//! Purpose: | ||
//! | ||
//! - Adds the PR to the workqueue of one team member (when the PR has been assigned) | ||
//! - Removes the PR from the workqueue of one team member (when the PR is unassigned or closed) | ||
use crate::{ | ||
config::ReviewPrefsConfig, | ||
db::notifications::record_username, | ||
github::{IssuesAction, IssuesEvent}, | ||
handlers::Context, | ||
}; | ||
use anyhow::Context as _; | ||
use tokio_postgres::Client as DbClient; | ||
|
||
pub(super) struct ReviewPrefsInput {} | ||
|
||
pub(super) async fn parse_input( | ||
_ctx: &Context, | ||
event: &IssuesEvent, | ||
config: Option<&ReviewPrefsConfig>, | ||
) -> Result<Option<ReviewPrefsInput>, String> { | ||
// NOTE: this config check MUST exist. Else, the triagebot will emit an error | ||
// about this feature not being enabled | ||
if config.is_none() { | ||
return Ok(None); | ||
}; | ||
|
||
// Execute this handler only if this is a PR ... | ||
if !event.issue.is_pr() { | ||
return Ok(None); | ||
} | ||
|
||
// ... and if the action is an assignment or unassignment with an assignee | ||
match event.action { | ||
IssuesAction::Assigned { .. } | IssuesAction::Unassigned { .. } => { | ||
Ok(Some(ReviewPrefsInput {})) | ||
} | ||
_ => Ok(None), | ||
} | ||
} | ||
|
||
pub(super) async fn handle_input<'a>( | ||
ctx: &Context, | ||
_config: &ReviewPrefsConfig, | ||
event: &IssuesEvent, | ||
_inputs: ReviewPrefsInput, | ||
) -> anyhow::Result<()> { | ||
let db_client = ctx.db.get().await; | ||
|
||
// extract the assignee matching the assignment or unassignment enum variants or return and ignore this handler | ||
let IssuesEvent { | ||
action: IssuesAction::Assigned { assignee } | IssuesAction::Unassigned { assignee }, | ||
.. | ||
} = event | ||
else { | ||
return Ok(()); | ||
}; | ||
|
||
// ensure the team member object of this action exists in the `users` table | ||
record_username(&db_client, assignee.id.unwrap(), &assignee.login) | ||
.await | ||
.context("failed to record username")?; | ||
|
||
if matches!(event.action, IssuesAction::Unassigned { .. }) { | ||
delete_pr_from_workqueue(&db_client, assignee.id.unwrap(), event.issue.number) | ||
.await | ||
.context("Failed to remove PR from workqueue")?; | ||
} | ||
|
||
if matches!(event.action, IssuesAction::Assigned { .. }) { | ||
upsert_pr_into_workqueue(&db_client, assignee.id.unwrap(), event.issue.number) | ||
.await | ||
.context("Failed to add PR to workqueue")?; | ||
} | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Add a PR to the workqueue of a team member. | ||
/// Ensures no accidental PR duplicates. | ||
async fn upsert_pr_into_workqueue( | ||
db: &DbClient, | ||
user_id: u64, | ||
pr: u64, | ||
) -> anyhow::Result<u64, anyhow::Error> { | ||
let q = " | ||
INSERT INTO review_prefs | ||
(user_id, assigned_prs) VALUES ($1, $2) | ||
ON CONFLICT (user_id) | ||
DO UPDATE SET assigned_prs = uniq(sort(array_append(review_prefs.assigned_prs, $3)));"; | ||
db.execute(q, &[&(user_id as i64), &vec![pr as i32], &(pr as i32)]) | ||
.await | ||
.context("Upsert DB error") | ||
} | ||
|
||
/// Delete a PR from the workqueue of a team member | ||
async fn delete_pr_from_workqueue( | ||
db: &DbClient, | ||
user_id: u64, | ||
pr: u64, | ||
) -> anyhow::Result<u64, anyhow::Error> { | ||
let q = " | ||
UPDATE review_prefs r | ||
SET assigned_prs = array_remove(r.assigned_prs, $2) | ||
WHERE r.user_id = $1;"; | ||
db.execute(q, &[&(user_id as i64), &(pr as i32)]) | ||
.await | ||
.context("Update DB error") | ||
} |