mirror of https://github.com/cncf/gitvote.git
Compare commits
9 Commits
gitvote-ch
...
main
Author | SHA1 | Date |
---|---|---|
|
8e7eafba8d | |
|
9b4063c6f8 | |
|
426286cf74 | |
|
9183e10d6a | |
|
3915b82736 | |
|
38a1ef34ce | |
|
c4d5cbeb8e | |
|
216216c007 | |
|
fbdb69ced9 |
|
@ -9,7 +9,7 @@ permissions: read-all
|
|||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
@ -24,7 +24,7 @@ jobs:
|
|||
with:
|
||||
python-version: 3.7
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.6.1
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
- name: Run chart-testing (list-changed)
|
||||
id: list-changed
|
||||
run: |
|
||||
|
@ -35,7 +35,7 @@ jobs:
|
|||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config .ct.yaml --target-branch ${{ github.event.repository.default_branch }}
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.10.0
|
||||
uses: helm/kind-action@v1.12.0
|
||||
if: steps.list-changed.outputs.changed == 'true'
|
||||
- name: Run chart-testing (install)
|
||||
run: ct install --config .ct.yaml --target-branch ${{ github.event.repository.default_branch }}
|
||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.83.0
|
||||
toolchain: 1.87.0
|
||||
components: clippy, rustfmt
|
||||
- name: Run clippy
|
||||
run: cargo clippy --all-targets --all-features -- --deny warnings
|
||||
|
@ -30,6 +30,6 @@ jobs:
|
|||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: 1.83.0
|
||||
toolchain: 1.87.0
|
||||
- name: Run backend tests
|
||||
run: cargo test
|
||||
|
|
|
@ -9,7 +9,7 @@ permissions: read-all
|
|||
|
||||
jobs:
|
||||
build-and-publish-images:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
|
18
ADOPTERS.md
18
ADOPTERS.md
|
@ -2,12 +2,26 @@
|
|||
|
||||
If your organization is using GitVote, please consider adding it to this list by submitting a pull request.
|
||||
|
||||
|
||||
- [AsyncAPI](https://github.com/asyncapi/community/blob/master/voting.md)
|
||||
- [AWS Labs](https://github.com/awslabs)
|
||||
- [CloudNativePG](https://cloudnative-pg.io)
|
||||
- [CNCF](https://cncf.io)
|
||||
- [DevRel Foundation](https://dev-rel.org/)
|
||||
- [Fintech Open Source Foundation](www.finos.org)
|
||||
- [JSON Schema](https://json-schema.org)
|
||||
- [K8sGateway](https://k8sgateway.io)
|
||||
- [KServe](https://kserve.github.io/website/latest/)
|
||||
- [Kuadrant](https://kuadrant.io)
|
||||
- [Kuma](https://kuma.io)
|
||||
- [Kyverno](https://kyverno.io)
|
||||
- [Microcks](https://microcks.io/)
|
||||
- [NFDI4Health](https://github.com/nfdi4health)
|
||||
- [Open Component Model](https://ocm.software)
|
||||
- [OpenGemini](https://opengemini.org)
|
||||
- [OpenSSF](https://openssf.org)
|
||||
- [ORAS](https://oras.land)
|
||||
- [OSCAL Compass](https://github.com/oscal-compass)
|
||||
- [Ratify Project](https://ratify.dev)
|
||||
- [ResBaz Arizona](https://researchbazaar.arizona.edu)
|
||||
- [TODO Group](https://todogroup.org)
|
||||
- [Universal Blue](https://universal-blue.org)
|
||||
- [WasmEdge](https://wasmedge.org/)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
54
Cargo.toml
54
Cargo.toml
|
@ -4,58 +4,56 @@ description = "GitVote server"
|
|||
version = "1.4.0"
|
||||
license = "Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.83"
|
||||
rust-version = "1.87"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.94"
|
||||
askama = "0.12.1"
|
||||
askama_axum = "0.4.0"
|
||||
anyhow = "1.0.98"
|
||||
askama = { version = "0.14.0", features = ["serde_json"] }
|
||||
async-channel = "2.3.1"
|
||||
async-trait = "0.1.83"
|
||||
axum = { version = "0.7.9", features = ["macros"] }
|
||||
clap = { version = "4.5.23", features = ["derive"] }
|
||||
deadpool-postgres = { version = "0.14.0", features = ["serde"] }
|
||||
async-trait = "0.1.88"
|
||||
axum = { version = "0.8.4", features = ["macros"] }
|
||||
clap = { version = "4.5.40", features = ["derive"] }
|
||||
deadpool-postgres = { version = "0.14.1", features = ["serde"] }
|
||||
figment = { version = "0.10.19", features = ["yaml", "env"] }
|
||||
futures = "0.3.31"
|
||||
graphql_client = { version = "0.14.0", features = ["reqwest"] }
|
||||
hex = "0.4.3"
|
||||
hmac = "0.12.1"
|
||||
http = "0.2.12"
|
||||
humantime = "2.1.0"
|
||||
http = "1.3.1"
|
||||
humantime = "2.2.0"
|
||||
humantime-serde = "1.1.1"
|
||||
ignore = "0.4.23"
|
||||
jsonwebtoken = "9.3.0"
|
||||
lazy_static = "1.5.0"
|
||||
octocrab = "=0.33.3"
|
||||
openssl = { version = "0.10.68", features = ["vendored"] }
|
||||
postgres-openssl = "0.5.0"
|
||||
jsonwebtoken = "9.3.1"
|
||||
octocrab = "0.44.1"
|
||||
openssl = { version = "0.10.73", features = ["vendored"] }
|
||||
postgres-openssl = "0.5.1"
|
||||
regex = "1.11.1"
|
||||
reqwest = "0.12.9"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
reqwest = "0.12.20"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.34"
|
||||
sha2 = "0.10.8"
|
||||
thiserror = "2.0.6"
|
||||
time = { version = "0.3.37", features = ["serde"] }
|
||||
tokio = { version = "1.42.0", features = [
|
||||
sha2 = "0.10.9"
|
||||
thiserror = "2.0.12"
|
||||
time = { version = "0.3.41", features = ["serde"] }
|
||||
tokio = { version = "1.45.1", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"signal",
|
||||
"time",
|
||||
] }
|
||||
tokio-postgres = { version = "0.7.12", features = [
|
||||
tokio-postgres = { version = "0.7.13", features = [
|
||||
"with-uuid-1",
|
||||
"with-serde_json-1",
|
||||
"with-time-0_3",
|
||||
] }
|
||||
tokio-util = { version = "0.7.13", features = ["rt"] }
|
||||
tower = { version = "0.5.1", features = ["util"] }
|
||||
tower-http = { version = "0.6.2", features = ["trace"] }
|
||||
tokio-util = { version = "0.7.15", features = ["rt"] }
|
||||
tower = { version = "0.5.2", features = ["util"] }
|
||||
tower-http = { version = "0.6.6", features = ["trace"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
|
||||
uuid = { version = "1.11.0", features = ["serde", "v4"] }
|
||||
uuid = { version = "1.17.0", features = ["serde", "v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
http-body = "1.0.1"
|
||||
hyper = "1.5.1"
|
||||
hyper = "1.6.0"
|
||||
mockall = "0.13.1"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Build gitvote
|
||||
FROM rust:1-alpine3.21 as builder
|
||||
FROM rust:1-alpine3.22 as builder
|
||||
RUN apk --no-cache add musl-dev perl make
|
||||
WORKDIR /gitvote
|
||||
COPY src src
|
||||
|
@ -10,7 +10,7 @@ WORKDIR /gitvote/src
|
|||
RUN cargo build --release
|
||||
|
||||
# Final stage
|
||||
FROM alpine:3.21.0
|
||||
FROM alpine:3.22.0
|
||||
RUN apk --no-cache add ca-certificates && addgroup -S gitvote && adduser -S gitvote -G gitvote
|
||||
USER gitvote
|
||||
WORKDIR /home/gitvote
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Build tern
|
||||
FROM golang:1.23.4-alpine3.21 AS tern
|
||||
FROM golang:1.24.4-alpine3.22 AS tern
|
||||
RUN apk --no-cache add git
|
||||
RUN go install github.com/jackc/tern@latest
|
||||
|
||||
# Build final image
|
||||
FROM alpine:3.21.0
|
||||
FROM alpine:3.22.0
|
||||
RUN addgroup -S gitvote && adduser -S gitvote -G gitvote
|
||||
USER gitvote
|
||||
WORKDIR /home/gitvote
|
||||
|
|
11
src/cmd.rs
11
src/cmd.rs
|
@ -2,9 +2,9 @@
|
|||
//! GitHub events.
|
||||
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::LazyLock;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{
|
||||
|
@ -19,11 +19,10 @@ const CMD_CREATE_VOTE: &str = "vote";
|
|||
const CMD_CANCEL_VOTE: &str = "cancel-vote";
|
||||
const CMD_CHECK_VOTE: &str = "check-vote";
|
||||
|
||||
lazy_static! {
|
||||
/// Regex used to detect commands in issues/prs comments.
|
||||
static ref CMD: Regex = Regex::new(r"(?m)^/(vote|cancel-vote|check-vote)-?([a-zA-Z0-9]*)\s*$")
|
||||
.expect("invalid CMD regexp");
|
||||
}
|
||||
/// Regex used to detect commands in issues/prs comments.
|
||||
static CMD: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?m)^/(vote|cancel-vote|check-vote)-?([a-zA-Z0-9]*)\s*$").expect("invalid CMD regexp")
|
||||
});
|
||||
|
||||
/// Represents a command to be executed, usually created from a GitHub event.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
|
|
|
@ -191,7 +191,7 @@ impl GH for GHApi {
|
|||
issue_number: i64,
|
||||
labels: &[&str],
|
||||
) -> Result<()> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
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(())
|
||||
|
@ -206,7 +206,7 @@ impl GH for GHApi {
|
|||
issue_number: i64,
|
||||
check_details: &CheckDetails,
|
||||
) -> Result<()> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let pr = client.pulls(owner, repo).get(issue_number as u64).await?;
|
||||
let url = format!("{GITHUB_API_URL}/repos/{owner}/{repo}/check-runs");
|
||||
let mut body = json!({
|
||||
|
@ -220,7 +220,7 @@ impl GH for GHApi {
|
|||
});
|
||||
if let Some(conclusion) = &check_details.conclusion {
|
||||
body["conclusion"] = json!(conclusion);
|
||||
};
|
||||
}
|
||||
let _: Value = client.post(url, Some(&body)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -235,7 +235,7 @@ impl GH for GHApi {
|
|||
title: &str,
|
||||
body: &str,
|
||||
) -> Result<()> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
|
||||
// Fetch some repository details needed to create a discussion
|
||||
let response: graphql_client::Response<announcement_repo_query::ResponseData> = client
|
||||
|
@ -326,7 +326,7 @@ impl GH for GHApi {
|
|||
|
||||
/// [GH::get_collaborators]
|
||||
async fn get_collaborators(&self, inst_id: u64, owner: &str, repo: &str) -> Result<Vec<UserName>> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let url = format!("{GITHUB_API_URL}/repos/{owner}/{repo}/collaborators");
|
||||
let first_page: Page<User> = client.get(url, None::<&()>).await?;
|
||||
let collaborators = client.all_pages(first_page).await?.into_iter().map(|u| u.login).collect();
|
||||
|
@ -341,7 +341,7 @@ impl GH for GHApi {
|
|||
repo: &str,
|
||||
comment_id: i64,
|
||||
) -> Result<Vec<Reaction>> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let url = format!("{GITHUB_API_URL}/repos/{owner}/{repo}/issues/comments/{comment_id}/reactions",);
|
||||
let first_page: Page<Reaction> = client.get(url, None::<&()>).await?;
|
||||
let reactions = client.all_pages(first_page).await?;
|
||||
|
@ -350,20 +350,19 @@ impl GH for GHApi {
|
|||
|
||||
/// [GH::get_config_file]
|
||||
async fn get_config_file(&self, inst_id: u64, owner: &str, repo: &str) -> Option<String> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let Ok(client) = self.app_client.installation(InstallationId(inst_id)) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Try to get the config file from the repository. Otherwise try
|
||||
// getting the organization wide config file in the .github repo.
|
||||
let mut content: Option<String> = None;
|
||||
for repo in &[repo, ORG_CONFIG_REPO] {
|
||||
match client.repos(owner, *repo).get_content().path(CONFIG_FILE).send().await {
|
||||
Ok(resp) => {
|
||||
if resp.items.len() == 1 {
|
||||
content = resp.items[0].decoded_content();
|
||||
break;
|
||||
}
|
||||
if let Ok(resp) = client.repos(owner, *repo).get_content().path(CONFIG_FILE).send().await {
|
||||
if resp.items.len() == 1 {
|
||||
content = resp.items[0].decoded_content();
|
||||
break;
|
||||
}
|
||||
Err(_) => continue,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,7 +371,7 @@ impl GH for GHApi {
|
|||
|
||||
/// [GH::get_pr_files]
|
||||
async fn get_pr_files(&self, inst_id: u64, owner: &str, repo: &str, pr_number: i64) -> Result<Vec<File>> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let url = format!("{GITHUB_API_URL}/repos/{owner}/{repo}/pulls/{pr_number}/files");
|
||||
let first_page: Page<File> = client.get(url, None::<&()>).await?;
|
||||
let files: Vec<File> = client.all_pages(first_page).await?;
|
||||
|
@ -387,7 +386,7 @@ impl GH for GHApi {
|
|||
team: &str,
|
||||
exclude_maintainers: bool,
|
||||
) -> Result<Vec<UserName>> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let url = format!("{GITHUB_API_URL}/orgs/{org}/teams/{team}/members");
|
||||
let first_page: Page<User> = client
|
||||
.get(
|
||||
|
@ -404,7 +403,7 @@ impl GH for GHApi {
|
|||
|
||||
/// [GH::is_check_required]
|
||||
async fn is_check_required(&self, inst_id: u64, owner: &str, repo: &str, branch: &str) -> Result<bool> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let url = format!("{GITHUB_API_URL}/repos/{owner}/{repo}/branches/{branch}");
|
||||
let branch: Branch = client.get(url, None::<&()>).await?;
|
||||
let is_check_required = if let Some(required_checks) =
|
||||
|
@ -426,7 +425,7 @@ impl GH for GHApi {
|
|||
issue_number: i64,
|
||||
body: &str,
|
||||
) -> Result<i64> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let comment = client.issues(owner, repo).create_comment(issue_number as u64, body).await?;
|
||||
Ok(comment.id.0 as i64)
|
||||
}
|
||||
|
@ -440,14 +439,21 @@ impl GH for GHApi {
|
|||
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(())
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
match client.issues(owner, repo).remove_label(issue_number as u64, label).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(octocrab::Error::GitHub { source, backtrace: _ })
|
||||
if source.message == "Label does not exist" =>
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// [GH::user_is_collaborator]
|
||||
async fn user_is_collaborator(&self, inst_id: u64, owner: &str, repo: &str, user: &str) -> Result<bool> {
|
||||
let client = self.app_client.installation(InstallationId(inst_id));
|
||||
let client = self.app_client.installation(InstallationId(inst_id))?;
|
||||
let url = format!("{GITHUB_API_URL}/repos/{owner}/{repo}/collaborators/{user}",);
|
||||
let resp = client._get(url).await?;
|
||||
if resp.status() == StatusCode::NO_CONTENT {
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
//! supported endpoints.
|
||||
|
||||
use anyhow::{format_err, Error, Result};
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
body::Bytes,
|
||||
extract::{FromRef, State},
|
||||
http::{HeaderMap, HeaderValue, StatusCode},
|
||||
response::IntoResponse,
|
||||
response::{Html, IntoResponse},
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
|
@ -65,7 +66,11 @@ pub(crate) fn setup_router(
|
|||
/// Handler that returns the index document.
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn index() -> impl IntoResponse {
|
||||
tmpl::Index {}
|
||||
let template = tmpl::Index {};
|
||||
match template.render() {
|
||||
Ok(html) => Ok(Html(html)),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Handler that processes webhook events from GitHub.
|
||||
|
@ -92,7 +97,7 @@ async fn event(
|
|||
.is_err()
|
||||
{
|
||||
return Err((StatusCode::BAD_REQUEST, "no valid signature found".to_string()));
|
||||
};
|
||||
}
|
||||
|
||||
// Parse event
|
||||
let event = match Event::try_from((headers.get(GITHUB_EVENT_HEADER), &body[..])) {
|
||||
|
@ -122,7 +127,7 @@ async fn event(
|
|||
})?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok("no command detected")
|
||||
}
|
||||
|
@ -191,7 +196,7 @@ async fn set_check_status(db: DynDB, gh: DynGH, event: &PullRequestEvent) -> Res
|
|||
gh.create_check_run(inst_id, owner, repo, pr, &check_details).await?;
|
||||
}
|
||||
PullRequestEventAction::Other => {}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ async fn main() -> Result<()> {
|
|||
match cfg.log.format {
|
||||
LogFormat::Json => ts.json().init(),
|
||||
LogFormat::Pretty => ts.init(),
|
||||
};
|
||||
}
|
||||
|
||||
// Setup database
|
||||
let mut builder = SslConnector::builder(SslMethod::tls())?;
|
||||
|
|
126
src/processor.rs
126
src/processor.rs
|
@ -45,7 +45,16 @@ const AUTO_CLOSE_FREQUENCY: Duration = Duration::from_secs(60 * 60 * 24);
|
|||
const GITVOTE_LABEL: &str = "gitvote";
|
||||
|
||||
/// Label used to tag issues/prs where a vote is open.
|
||||
const VOTE_OPEN_LABEL: &str = "vote open";
|
||||
const VOTE_OPEN_LABEL: &str = "gitvote/open";
|
||||
|
||||
/// Label used to tag issues/prs where a vote is closed.
|
||||
const VOTE_CLOSED_LABEL: &str = "gitvote/closed";
|
||||
|
||||
/// Label used to tag issues/prs where a vote passed.
|
||||
const VOTE_PASSED_LABEL: &str = "gitvote/passed";
|
||||
|
||||
/// Label used to tag issues/prs where a vote failed.
|
||||
const VOTE_FAILED_LABEL: &str = "gitvote/failed";
|
||||
|
||||
/// A votes processor is in charge of creating the requested votes, stopping
|
||||
/// them at the scheduled time and publishing results, etc. It relies on some
|
||||
|
@ -210,7 +219,13 @@ impl CommandsHandler {
|
|||
self.gh.create_check_run(inst_id, owner, repo, i.issue_number, &check_details).await?;
|
||||
}
|
||||
|
||||
// Add "vote open" label to issue/pr
|
||||
// Update issue/pr labels
|
||||
//
|
||||
// We try to remove the vote closed and result labels just in case they
|
||||
// were left from a previous vote that was closed
|
||||
_ = self.gh.remove_label(inst_id, owner, repo, i.issue_number, VOTE_CLOSED_LABEL).await;
|
||||
_ = self.gh.remove_label(inst_id, owner, repo, i.issue_number, VOTE_PASSED_LABEL).await;
|
||||
_ = self.gh.remove_label(inst_id, owner, repo, i.issue_number, VOTE_FAILED_LABEL).await;
|
||||
self.gh
|
||||
.add_labels(
|
||||
inst_id,
|
||||
|
@ -272,8 +287,8 @@ impl CommandsHandler {
|
|||
self.gh.create_check_run(inst_id, owner, repo, i.issue_number, &check_details).await?;
|
||||
}
|
||||
|
||||
// Remove "vote open" label from issue/pr
|
||||
self.gh.remove_label(inst_id, owner, repo, i.issue_number, VOTE_OPEN_LABEL).await?;
|
||||
// Update issue/pr labels
|
||||
_ = self.gh.remove_label(inst_id, owner, repo, i.issue_number, VOTE_OPEN_LABEL).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -447,8 +462,17 @@ impl VotesCloser {
|
|||
self.gh.create_check_run(inst_id, owner, repo, vote.issue_number, &check_details).await?;
|
||||
}
|
||||
|
||||
// Remove "vote open" label from issue/pr
|
||||
self.gh.remove_label(inst_id, owner, repo, vote.issue_number, VOTE_OPEN_LABEL).await?;
|
||||
// Update issue/pr labels
|
||||
let mut labels_to_add = vec![VOTE_CLOSED_LABEL];
|
||||
if let Some(results) = &results {
|
||||
if results.passed {
|
||||
labels_to_add.push(VOTE_PASSED_LABEL);
|
||||
} else {
|
||||
labels_to_add.push(VOTE_FAILED_LABEL);
|
||||
}
|
||||
}
|
||||
_ = self.gh.remove_label(inst_id, owner, repo, vote.issue_number, VOTE_OPEN_LABEL).await;
|
||||
self.gh.add_labels(inst_id, owner, repo, vote.issue_number, &labels_to_add).await?;
|
||||
|
||||
debug!("closed");
|
||||
Ok(Some(()))
|
||||
|
@ -928,6 +952,36 @@ mod tests {
|
|||
})
|
||||
.times(1)
|
||||
.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_CLOSED_LABEL),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_remove_label()
|
||||
.with(
|
||||
eq(INST_ID),
|
||||
eq(ORG),
|
||||
eq(REPO),
|
||||
eq(ISSUE_NUM),
|
||||
eq(VOTE_PASSED_LABEL),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_remove_label()
|
||||
.with(
|
||||
eq(INST_ID),
|
||||
eq(ORG),
|
||||
eq(REPO),
|
||||
eq(ISSUE_NUM),
|
||||
eq(VOTE_FAILED_LABEL),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_add_labels()
|
||||
.withf(|inst_id, owner, repo, issue_number, labels| {
|
||||
*inst_id == INST_ID
|
||||
|
@ -1003,6 +1057,36 @@ mod tests {
|
|||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_remove_label()
|
||||
.with(
|
||||
eq(INST_ID),
|
||||
eq(ORG),
|
||||
eq(REPO),
|
||||
eq(ISSUE_NUM),
|
||||
eq(VOTE_CLOSED_LABEL),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_remove_label()
|
||||
.with(
|
||||
eq(INST_ID),
|
||||
eq(ORG),
|
||||
eq(REPO),
|
||||
eq(ISSUE_NUM),
|
||||
eq(VOTE_PASSED_LABEL),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_remove_label()
|
||||
.with(
|
||||
eq(INST_ID),
|
||||
eq(ORG),
|
||||
eq(REPO),
|
||||
eq(ISSUE_NUM),
|
||||
eq(VOTE_FAILED_LABEL),
|
||||
)
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
gh.expect_add_labels()
|
||||
.withf(|inst_id, owner, repo, issue_number, labels| {
|
||||
*inst_id == INST_ID
|
||||
|
@ -1374,6 +1458,16 @@ mod tests {
|
|||
.with(eq(INST_ID), eq(ORG), eq(REPO), eq(ISSUE_NUM), eq(VOTE_OPEN_LABEL))
|
||||
.times(1)
|
||||
.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![VOTE_CLOSED_LABEL, VOTE_PASSED_LABEL]
|
||||
})
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
|
||||
let votes_closer = VotesCloser::new(Arc::new(db), Arc::new(gh));
|
||||
votes_closer.close_finished_vote().await.unwrap();
|
||||
|
@ -1434,6 +1528,16 @@ mod tests {
|
|||
.with(eq(INST_ID), eq(ORG), eq(REPO), eq(ISSUE_NUM), eq(VOTE_OPEN_LABEL))
|
||||
.times(1)
|
||||
.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![VOTE_CLOSED_LABEL, VOTE_PASSED_LABEL]
|
||||
})
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
|
||||
let votes_closer = VotesCloser::new(Arc::new(db), Arc::new(gh));
|
||||
votes_closer.close_finished_vote().await.unwrap();
|
||||
|
@ -1498,6 +1602,16 @@ mod tests {
|
|||
.with(eq(INST_ID), eq(ORG), eq(REPO), eq(ISSUE_NUM), eq(VOTE_OPEN_LABEL))
|
||||
.times(1)
|
||||
.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![VOTE_CLOSED_LABEL, VOTE_FAILED_LABEL]
|
||||
})
|
||||
.times(1)
|
||||
.returning(|_, _, _, _, _| Box::pin(future::ready(Ok(()))));
|
||||
|
||||
let votes_closer = VotesCloser::new(Arc::new(db), Arc::new(gh));
|
||||
votes_closer.close_finished_vote().await.unwrap();
|
||||
|
|
|
@ -216,6 +216,7 @@ mod filters {
|
|||
#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)]
|
||||
pub(crate) fn non_binding(
|
||||
votes: &HashMap<UserName, UserVote>,
|
||||
_: &dyn askama::Values,
|
||||
max: &i64,
|
||||
) -> askama::Result<Vec<(UserName, UserVote)>> {
|
||||
let mut non_binding_votes: Vec<(UserName, UserVote)> =
|
||||
|
|
Loading…
Reference in New Issue