diff --git a/Cargo.lock b/Cargo.lock index d8fd4ed..1c75241 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,6 +451,7 @@ dependencies = [ "axum", "color-eyre", "futures", + "jiff", "jsonwebtoken", "octocrab", "reqwest", @@ -1066,6 +1067,47 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "js-sys" version = "0.3.82" @@ -1475,6 +1517,21 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" 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]] name = "potential_utf" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 4227ae4..431f16d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ askama = "0.14.0" axum = { version = "0.8.6", features = ["macros"] } color-eyre = "0.6.3" futures = "0.3.30" +jiff = "0.2.16" jsonwebtoken = { version = "9.3.1", features = [] } octocrab = "0.47.1" reqwest = { version = "0.12.7", features = [ diff --git a/migrations/20251111165945_notifications_last_update.sql b/migrations/20251111165945_notifications_last_update.sql new file mode 100644 index 0000000..ada4ff7 --- /dev/null +++ b/migrations/20251111165945_notifications_last_update.sql @@ -0,0 +1,4 @@ +-- Add migration script here + +ALTER TABLE notification_issues + ADD COLUMN "last_update_date" INTEGER NULL; diff --git a/src/db.rs b/src/db.rs index b25518a..66f20f2 100644 --- a/src/db.rs +++ b/src/db.rs @@ -106,6 +106,7 @@ pub struct NotificationIssue { pub status: NotificationStatus, pub first_failed_nightly: String, pub target: String, + pub last_update_date: Option, } impl Db { @@ -314,13 +315,14 @@ impl Db { pub async fn insert_notification(&self, notification: NotificationIssue) -> Result<()> { sqlx::query( "INSERT INTO notification_issues\ - (issue_number, status, first_failed_nightly, target)\ - VALUES (?, ?, ?, ?)", + (issue_number, status, first_failed_nightly, target, last_update_date)\ + VALUES (?, ?, ?, ?, ?)", ) .bind(notification.issue_number) .bind(notification.status) .bind(notification.first_failed_nightly) .bind(notification.target) + .bind(notification.last_update_date) .execute(&self.conn) .await .wrap_err("inserting new notification")?; @@ -336,4 +338,18 @@ impl Db { .wrap_err("marking notification as closed")?; 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(()) + } } diff --git a/src/github.rs b/src/github.rs deleted file mode 100644 index 954915b..0000000 --- a/src/github.rs +++ /dev/null @@ -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 { - 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) - } -} diff --git a/src/notification.rs b/src/notification.rs index 4d8c87f..f606173 100644 --- a/src/notification.rs +++ b/src/notification.rs @@ -84,8 +84,6 @@ pub async fn notify_build_failure( return Ok(()); }; - info!("Creating issue for target {target}, notifying {notify_usernames:?}"); - let issue = db.find_existing_notification(target).await?; let url = format!( @@ -93,14 +91,25 @@ pub async fn notify_build_failure( ); 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 - .issues() - .create_comment( - issue.issue_number as u64, - format!( - "💥 The target {target} still fails to build on the nightly {nightly}! + if issue.last_update_date.is_none_or(|last_update_date| { + jiff::Timestamp::from_millisecond(last_update_date).is_ok_and(|last_update_date| { + jiff::Timestamp::now() + .since(last_update_date) + .is_ok_and(|diff| diff.get_months() > 0) + }) + }) { + 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}> @@ -111,14 +120,26 @@ pub async fn notify_build_failure( ``` + +This update is sent after a month of inactivity. " - ), - ) - .await - .wrap_err("creating update comment")?; + ), + ) + .await + .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(()); } + info!("Creating issue for target {target}, notifying {notify_usernames:?}"); + // Ensure the labels exist. let label = github_client.issues().get_label(target).await; match label { @@ -176,6 +197,7 @@ This issue will be closed automatically when this target works again!" issue_number: issue.number as i64, status: NotificationStatus::Open, target: target.into(), + last_update_date: Some(jiff::Timestamp::now().as_millisecond()), }) .await .wrap_err("inserting issue into DB")?;