Skip to content

Commit

Permalink
Add labels to issues and PRs (#497)
Browse files Browse the repository at this point in the history
Closes #396

Signed-off-by: Sergio Castaño Arteaga <[email protected]>
  • Loading branch information
tegioz authored May 22, 2024
1 parent 69db07e commit 3f3e55f
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 12 deletions.
4 changes: 2 additions & 2 deletions src/cfg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::github::{DynGH, File, TeamSlug, UserName};
use anyhow::{format_err, Result};
use anyhow::{bail, Result};
use ignore::gitignore::GitignoreBuilder;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, time::Duration};
Expand Down Expand Up @@ -119,7 +119,7 @@ impl CfgProfile {
.and_then(|allowed_voters| allowed_voters.teams.as_ref())
{
if !teams.is_empty() {
return Err(format_err!(ERR_TEAMS_NOT_ALLOWED));
bail!(ERR_TEAMS_NOT_ALLOWED);
}
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ pub(crate) type UserName = String;
#[async_trait]
#[cfg_attr(test, automock)]
pub(crate) trait GH {
/// Add labels to the provided issue.
async fn add_labels(
&self,
inst_id: u64,
owner: &str,
repo: &str,
issue_number: i64,
labels: &[&str],
) -> Result<()>;

/// Create a check run for the head commit in the provided pull request.
async fn create_check_run(
&self,
Expand Down Expand Up @@ -119,6 +129,16 @@ pub(crate) trait GH {
body: &str,
) -> Result<CommentId>;

/// Remove label from the provided issue.
async fn remove_label(
&self,
inst_id: u64,
owner: &str,
repo: &str,
issue_number: i64,
label: &str,
) -> Result<()>;

/// Check if the user given is a collaborator of the provided repository.
async fn user_is_collaborator(
&self,
Expand Down Expand Up @@ -150,6 +170,27 @@ impl GHApi {

#[async_trait]
impl GH for GHApi {
/// [GH::add_labels]
async fn add_labels(
&self,
inst_id: u64,
owner: &str,
repo: &str,
issue_number: i64,
labels: &[&str],
) -> Result<()> {
let client = self.app_client.installation(InstallationId(inst_id));
let labels = labels
.iter()
.map(ToString::to_string)
.collect::<Vec<String>>();
client
.issues(owner, repo)
.add_labels(issue_number as u64, &labels)
.await?;
Ok(())
}

/// [GH::create_check_run]
async fn create_check_run(
&self,
Expand Down Expand Up @@ -383,6 +424,23 @@ impl GH for GHApi {
Ok(comment.id.0 as i64)
}

/// [GH::remove_label]
async fn remove_label(
&self,
inst_id: u64,
owner: &str,
repo: &str,
issue_number: i64,
label: &str,
) -> Result<()> {
let client = self.app_client.installation(InstallationId(inst_id));
client
.issues(owner, repo)
.remove_label(issue_number as u64, label)
.await?;
Ok(())
}

/// [GH::user_is_collaborator]
async fn user_is_collaborator(
&self,
Expand Down
109 changes: 101 additions & 8 deletions src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ const STATUS_CHECK_FREQUENCY: Duration = Duration::from_secs(60 * 30);
/// How often we try to auto close votes that have already passed.
const AUTO_CLOSE_FREQUENCY: Duration = Duration::from_secs(60 * 60 * 24);

/// Label used to tag issues/prs where a vote has been created.
const GITVOTE_LABEL: &str = "gitvote";

/// Label used to tag issues/prs where a vote is open.
const VOTE_OPEN_LABEL: &str = "vote open";

/// A votes processor is in charge of creating the requested votes, stopping
/// them at the scheduled time and publishing results, etc.
pub(crate) struct Processor {
Expand Down Expand Up @@ -313,6 +319,17 @@ impl Processor {
.await?;
}

// Add "vote open" label to issue/pr
self.gh
.add_labels(
inst_id,
owner,
repo,
i.issue_number,
&[GITVOTE_LABEL, VOTE_OPEN_LABEL],
)
.await?;

debug!(?vote_id, "created");
Ok(())
}
Expand Down Expand Up @@ -352,15 +369,23 @@ impl Processor {
.post_comment(inst_id, owner, repo, i.issue_number, &body)
.await?;

// Create check run if needed
if cancelled_vote_id.is_some() && i.is_pull_request {
let check_details = CheckDetails {
status: "completed".to_string(),
conclusion: Some("success".to_string()),
summary: "Vote cancelled".to_string(),
};
// Create check run and remove "vote open" label if the vote was cancelled
if cancelled_vote_id.is_some() {
// Create check run if the vote is on a pull request
if i.is_pull_request {
let check_details = CheckDetails {
status: "completed".to_string(),
conclusion: Some("success".to_string()),
summary: "Vote cancelled".to_string(),
};
self.gh
.create_check_run(inst_id, owner, repo, i.issue_number, &check_details)
.await?;
}

// Remove "vote open" label from issue/pr
self.gh
.create_check_run(inst_id, owner, repo, i.issue_number, &check_details)
.remove_label(inst_id, owner, repo, i.issue_number, VOTE_OPEN_LABEL)
.await?;
}

Expand Down Expand Up @@ -469,6 +494,11 @@ impl Processor {
.await?;
}

// Remove "vote open" label from issue/pr
self.gh
.remove_label(inst_id, owner, repo, vote.issue_number, VOTE_OPEN_LABEL)
.await?;

debug!("closed");
Ok(Some(()))
}
Expand Down Expand Up @@ -834,6 +864,15 @@ mod tests {
&& body == expected_body.as_str()
})
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(COMMENT_ID))));
gh.expect_add_labels()
.withf(|inst_id, owner, repo, issue_number, labels| {
*inst_id == INST_ID
&& owner == ORG
&& repo == REPO
&& *issue_number == ISSUE_NUM
&& labels == vec![GITVOTE_LABEL, VOTE_OPEN_LABEL]
})
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
votes_processor
Expand Down Expand Up @@ -899,6 +938,15 @@ mod tests {
}),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
gh.expect_add_labels()
.withf(|inst_id, owner, repo, issue_number, labels| {
*inst_id == INST_ID
&& owner == ORG
&& repo == REPO
&& *issue_number == ISSUE_NUM
&& labels == vec![GITVOTE_LABEL, VOTE_OPEN_LABEL]
})
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
votes_processor
Expand Down Expand Up @@ -1007,6 +1055,15 @@ mod tests {
&& body == expected_body.as_str()
})
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(COMMENT_ID))));
gh.expect_remove_label()
.with(
eq(INST_ID),
eq(ORG),
eq(REPO),
eq(ISSUE_NUM),
eq(VOTE_OPEN_LABEL),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
let event = setup_test_issue_comment_event();

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
Expand Down Expand Up @@ -1049,6 +1106,15 @@ mod tests {
}),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
gh.expect_remove_label()
.with(
eq(INST_ID),
eq(ORG),
eq(REPO),
eq(ISSUE_NUM),
eq(VOTE_OPEN_LABEL),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
let event = setup_test_pr_event();

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
Expand Down Expand Up @@ -1216,6 +1282,15 @@ mod tests {
&& body == expected_body.as_str()
})
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(COMMENT_ID))));
gh.expect_remove_label()
.with(
eq(INST_ID),
eq(ORG),
eq(REPO),
eq(ISSUE_NUM),
eq(VOTE_OPEN_LABEL),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
votes_processor.close_finished_vote().await.unwrap();
Expand Down Expand Up @@ -1256,6 +1331,15 @@ mod tests {
}),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
gh.expect_remove_label()
.with(
eq(INST_ID),
eq(ORG),
eq(REPO),
eq(ISSUE_NUM),
eq(VOTE_OPEN_LABEL),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
votes_processor.close_finished_vote().await.unwrap();
Expand Down Expand Up @@ -1300,6 +1384,15 @@ mod tests {
}),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
gh.expect_remove_label()
.with(
eq(INST_ID),
eq(ORG),
eq(REPO),
eq(ISSUE_NUM),
eq(VOTE_OPEN_LABEL),
)
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));

let votes_processor = Processor::new(Arc::new(db), Arc::new(gh));
votes_processor.close_finished_vote().await.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions src/results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
cfg::CfgProfile,
github::{DynGH, UserName},
};
use anyhow::{format_err, Result};
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt};
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
Expand Down Expand Up @@ -76,7 +76,7 @@ impl VoteOption {
REACTION_IN_FAVOR => Self::InFavor,
REACTION_AGAINST => Self::Against,
REACTION_ABSTAIN => Self::Abstain,
_ => return Err(format_err!("reaction not supported")),
_ => bail!("reaction not supported"),
};
Ok(vote_option)
}
Expand Down

0 comments on commit 3f3e55f

Please sign in to comment.