Only send notification update after a month of inactivity

A lower cadence was requested in https://github.com/Noratrieb/does-it-build/pull/10#issuecomment-3514228933
This commit is contained in:
nora 2025-11-11 18:46:29 +01:00
parent b244cf05b4
commit 491eb1604f
6 changed files with 115 additions and 54 deletions

57
Cargo.lock generated
View file

@ -451,6 +451,7 @@ dependencies = [
"axum", "axum",
"color-eyre", "color-eyre",
"futures", "futures",
"jiff",
"jsonwebtoken", "jsonwebtoken",
"octocrab", "octocrab",
"reqwest", "reqwest",
@ -1066,6 +1067,47 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "jiff"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.61.2",
]
[[package]]
name = "jiff-static"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "jiff-tzdb"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
[[package]]
name = "jiff-tzdb-platform"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
dependencies = [
"jiff-tzdb",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.82" version = "0.3.82"
@ -1475,6 +1517,21 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.4" version = "0.1.4"

View file

@ -8,6 +8,7 @@ askama = "0.14.0"
axum = { version = "0.8.6", features = ["macros"] } axum = { version = "0.8.6", features = ["macros"] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
futures = "0.3.30" futures = "0.3.30"
jiff = "0.2.16"
jsonwebtoken = { version = "9.3.1", features = [] } jsonwebtoken = { version = "9.3.1", features = [] }
octocrab = "0.47.1" octocrab = "0.47.1"
reqwest = { version = "0.12.7", features = [ reqwest = { version = "0.12.7", features = [

View file

@ -0,0 +1,4 @@
-- Add migration script here
ALTER TABLE notification_issues
ADD COLUMN "last_update_date" INTEGER NULL;

View file

@ -106,6 +106,7 @@ pub struct NotificationIssue {
pub status: NotificationStatus, pub status: NotificationStatus,
pub first_failed_nightly: String, pub first_failed_nightly: String,
pub target: String, pub target: String,
pub last_update_date: Option<i64>,
} }
impl Db { impl Db {
@ -314,13 +315,14 @@ impl Db {
pub async fn insert_notification(&self, notification: NotificationIssue) -> Result<()> { pub async fn insert_notification(&self, notification: NotificationIssue) -> Result<()> {
sqlx::query( sqlx::query(
"INSERT INTO notification_issues\ "INSERT INTO notification_issues\
(issue_number, status, first_failed_nightly, target)\ (issue_number, status, first_failed_nightly, target, last_update_date)\
VALUES (?, ?, ?, ?)", VALUES (?, ?, ?, ?, ?)",
) )
.bind(notification.issue_number) .bind(notification.issue_number)
.bind(notification.status) .bind(notification.status)
.bind(notification.first_failed_nightly) .bind(notification.first_failed_nightly)
.bind(notification.target) .bind(notification.target)
.bind(notification.last_update_date)
.execute(&self.conn) .execute(&self.conn)
.await .await
.wrap_err("inserting new notification")?; .wrap_err("inserting new notification")?;
@ -336,4 +338,18 @@ impl Db {
.wrap_err("marking notification as closed")?; .wrap_err("marking notification as closed")?;
Ok(()) Ok(())
} }
pub async fn set_notification_last_update(
&self,
issue_number: i64,
last_update: jiff::Timestamp,
) -> Result<()> {
sqlx::query("UPDATE notification_issues SET last_update_date = ? WHERE issue_number = ?")
.bind(last_update.as_millisecond())
.bind(issue_number)
.execute(&self.conn)
.await
.wrap_err("marking notification as closed")?;
Ok(())
}
} }

View file

@ -1,39 +0,0 @@
use color_eyre::{eyre::Context, Result};
use octocrab::issues;
pub struct GitHubClient {
pub send_pings: bool,
owner: String,
repo: String,
pub client: octocrab::Octocrab,
}
impl GitHubClient {
pub async fn new(
send_pings: bool,
client: octocrab::Octocrab,
owner: String,
repo: String,
) -> Result<Self> {
let installation = client
.apps()
.get_repository_installation(&owner, &repo)
.await
.wrap_err_with(|| format!("getting installation for {owner}/{repo}"))?;
let client = client
.installation(installation.id)
.wrap_err("getting client for installation")?;
Ok(Self {
send_pings,
owner,
repo,
client,
})
}
pub fn issues(&self) -> issues::IssueHandler<'_> {
self.client.issues(&self.owner, &self.repo)
}
}

View file

@ -84,8 +84,6 @@ pub async fn notify_build_failure(
return Ok(()); return Ok(());
}; };
info!("Creating issue for target {target}, notifying {notify_usernames:?}");
let issue = db.find_existing_notification(target).await?; let issue = db.find_existing_notification(target).await?;
let url = format!( let url = format!(
@ -93,14 +91,25 @@ pub async fn notify_build_failure(
); );
if let Some(issue) = issue { if let Some(issue) = issue {
// An existing issue, send a comment. // An existing issue, send a comment if it's been a month since the last update.
github_client if issue.last_update_date.is_none_or(|last_update_date| {
.issues() jiff::Timestamp::from_millisecond(last_update_date).is_ok_and(|last_update_date| {
.create_comment( jiff::Timestamp::now()
issue.issue_number as u64, .since(last_update_date)
format!( .is_ok_and(|diff| diff.get_months() > 0)
"💥 The target {target} still fails to build on the nightly {nightly}! })
}) {
info!(
"Sending update for {target}, since enough time has elapsed since the last update"
);
github_client
.issues()
.create_comment(
issue.issue_number as u64,
format!(
"💥 The target {target} still fails to build on the nightly {nightly}!
<{url}> <{url}>
@ -111,14 +120,26 @@ pub async fn notify_build_failure(
``` ```
</details> </details>
This update is sent after a month of inactivity.
" "
), ),
) )
.await .await
.wrap_err("creating update comment")?; .wrap_err("creating update comment")?;
db.set_notification_last_update(issue.issue_number, jiff::Timestamp::now())
.await
.wrap_err("updating last_update_date in DB")?;
} else {
info!("Not sending update for {target}, since not enough time has elapsed since the last one");
}
return Ok(()); return Ok(());
} }
info!("Creating issue for target {target}, notifying {notify_usernames:?}");
// Ensure the labels exist. // Ensure the labels exist.
let label = github_client.issues().get_label(target).await; let label = github_client.issues().get_label(target).await;
match label { match label {
@ -176,6 +197,7 @@ This issue will be closed automatically when this target works again!"
issue_number: issue.number as i64, issue_number: issue.number as i64,
status: NotificationStatus::Open, status: NotificationStatus::Open,
target: target.into(), target: target.into(),
last_update_date: Some(jiff::Timestamp::now().as_millisecond()),
}) })
.await .await
.wrap_err("inserting issue into DB")?; .wrap_err("inserting issue into DB")?;