Compare commits

...

5 commits

Author SHA1 Message Date
45f784eb5c Improve margin on mobile 2025-11-11 19:35:03 +01:00
310a24aa44 Make it look much better
Including dark mode!
2025-11-11 19:26:28 +01:00
1d31b9c3fe Cleanup 2025-11-11 19:04:14 +01:00
73098095a7 Optimize features 2025-11-11 19:03:37 +01:00
491eb1604f 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
2025-11-11 18:47:25 +01:00
12 changed files with 213 additions and 102 deletions

80
Cargo.lock generated
View file

@ -144,7 +144,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"serde_core", "serde_core",
"serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
@ -451,13 +450,13 @@ dependencies = [
"axum", "axum",
"color-eyre", "color-eyre",
"futures", "futures",
"jiff",
"jsonwebtoken", "jsonwebtoken",
"octocrab", "octocrab",
"reqwest", "reqwest",
"serde", "serde",
"sqlx", "sqlx",
"tempfile", "tempfile",
"time",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -713,6 +712,25 @@ version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "h2"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.5" version = "0.15.5"
@ -834,6 +852,7 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"h2",
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
@ -1066,6 +1085,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 +1535,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"
@ -1672,6 +1747,7 @@ dependencies = [
"base64", "base64",
"bytes", "bytes",
"futures-core", "futures-core",
"h2",
"http", "http",
"http-body", "http-body",
"http-body-util", "http-body-util",

View file

@ -5,25 +5,29 @@ edition = "2021"
[dependencies] [dependencies]
askama = "0.14.0" askama = "0.14.0"
axum = { version = "0.8.6", features = ["macros"] } axum = { version = "0.8.6", default-features = false, features = [
"http1",
"matched-path",
"query",
"tokio",
"tower-log",
"tracing",
"macros",
] }
color-eyre = "0.6.3" color-eyre = "0.6.3"
futures = "0.3.30" futures = "0.3.30"
jsonwebtoken = { version = "9.3.1", features = [] } jiff = "0.2.16"
jsonwebtoken = { version = "9.3.1" }
octocrab = "0.47.1" octocrab = "0.47.1"
reqwest = { version = "0.12.7", features = [ reqwest = { version = "0.12.7", features = [
"rustls-tls", "rustls-tls",
"http2",
], default-features = false } ], default-features = false }
serde = { version = "1.0.210", features = ["derive"] } serde = { version = "1.0.210", features = ["derive"] }
sqlx = { version = "0.8.2", features = [ sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
"macros",
"migrate",
"runtime-tokio",
"sqlite",
] }
tempfile = "3.12.0" tempfile = "3.12.0"
time = { version = "0.3.36", features = ["formatting", "macros", "parsing"] }
tokio = { version = "1.40.0", features = ["full"] } tokio = { version = "1.40.0", features = ["full"] }
tracing = "0.1.40" tracing = { version = "0.1.40", features = ["attributes"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[build-dependencies] [build-dependencies]

View file

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

View file

@ -83,7 +83,7 @@ async fn background_builder_inner(db: &Db, github_client: &GitHubClient) -> Resu
match next { match next {
Some((nightly, mode)) => { Some((nightly, mode)) => {
info!(%nightly, %mode, "Building next nightly"); info!(%nightly, %mode, "Building next nightly");
let result = build_every_target_for_toolchain(db, &nightly, mode, &github_client) let result = build_every_target_for_toolchain(db, &nightly, mode, github_client)
.await .await
.wrap_err_with(|| format!("building targets for toolchain {nightly}")); .wrap_err_with(|| format!("building targets for toolchain {nightly}"));
if let Err(err) = result { if let Err(err) = result {

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,7 +91,18 @@ 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.
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 github_client
.issues() .issues()
@ -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")?;
@ -225,7 +247,7 @@ pub async fn notify_build_pass(
.await .await
.wrap_err("closing issue")?; .wrap_err("closing issue")?;
db.finish_notification(issue.issue_number as i64).await?; db.finish_notification(issue.issue_number).await?;
} }
Ok(()) Ok(())

View file

@ -101,14 +101,10 @@ async fn web_build(State(state): State<AppState>, Query(query): Query<BuildQuery
rustflags: build.rustflags, rustflags: build.rustflags,
version: crate::VERSION, version: crate::VERSION,
status: build.status, status: build.status,
build_date: build.build_date.map(|build_date| { build_date: build.build_date.and_then(|build_date| {
time::OffsetDateTime::from_unix_timestamp_nanos(build_date as i128 * 1000000) jiff::Timestamp::from_millisecond(build_date)
.map(|build_date| { .ok()
build_date .map(|build_date| build_date.to_string())
.format(&time::format_description::well_known::Rfc3339)
.unwrap()
})
.unwrap()
}), }),
build_duration_s: build build_duration_s: build
.build_duration_ms .build_duration_ms
@ -290,6 +286,7 @@ async fn web_root(State(state): State<AppState>) -> impl IntoResponse {
nightlies: Vec<String>, nightlies: Vec<String>,
version: &'static str, version: &'static str,
build_count: BuildStats, build_count: BuildStats,
notification_pr_url: String,
} }
let targets = state.db.target_list().await?; let targets = state.db.target_list().await?;
@ -301,6 +298,7 @@ async fn web_root(State(state): State<AppState>) -> impl IntoResponse {
nightlies, nightlies,
version: crate::VERSION, version: crate::VERSION,
build_count, build_count,
notification_pr_url: notification::notification_pr_url(),
}; };
Ok(Html(page.render().unwrap()).into_response()) Ok(Html(page.render().unwrap()).into_response())

View file

@ -1,29 +1,54 @@
:root {
color-scheme: light dark;
}
html { html {
font-family: sans-serif; font-family: sans-serif;
} }
@media screen and (min-width: 768px) {
html {
margin: 20px;
}
}
table {
border-spacing: 0;
}
th, th,
td { td {
border: 1px solid; padding: 2px 5px;
margin: 0; }
tr:not(:last-child) {
th,
td {
border-bottom: 1px solid;
}
} }
.error { .error {
background-color: lightcoral; background-color: light-dark(lightcoral, darkred);
} }
.pass { .pass {
background-color: greenyellow; background-color: light-dark(greenyellow, darkgreen);
} }
.missing { .missing {
background-color: lightgray; background-color: light-dark(lightgray, rgb(85, 85, 85));
} }
.target-header { .target-header {
writing-mode: sideways-lr; writing-mode: sideways-lr;
} }
.build-indicator-big {
padding: 10px;
margin-top: 20px;
}
.build-cell { .build-cell {
font-size: 2rem; font-size: 2rem;
} }
@ -36,13 +61,20 @@ td {
padding: 5px; padding: 5px;
} }
.target-name-col {
white-space: nowrap;
}
.footer { .footer {
margin-top: 20px; margin-top: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
} }
@media (prefers-color-scheme: dark) {
html {
background-color: #1b191c;
color: #e6dae9;
}
a {
color: #e5a5c2;
}
}

View file

@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -6,18 +6,15 @@
<title>Build {{nightly}} {{target}}</title> <title>Build {{nightly}} {{target}}</title>
<link rel="stylesheet" href="/index.css" /> <link rel="stylesheet" href="/index.css" />
<script type="module" defer src="index.js"></script> <script type="module" defer src="index.js"></script>
<style>
.build-indicator-big {
padding: 10px;
}
</style>
</head> </head>
<body> <body>
<h1>Build results for nightly-{{nightly}} target {{target}} {{mode}}</h1> <h1>
Build results for
<a href="/nightly?nightly={{nightly}}">nightly-{{nightly}}</a>, target
<a href="/target?target={{target}}">{{target}}</a> ({{mode}})
</h1>
<a href="/">Home</a> <a href="/">Home</a>
<div style="margin-top: 20px" class="{{status}} build-indicator-big"> <div class="{{status}} build-indicator-big">{{status}}</div>
{{status}}
</div>
{% if let Some(rustflags) = rustflags %} {% if let Some(rustflags) = rustflags %}
<p> <p>
Using rustflags: <b><code>{{rustflags}}</code></b> Using rustflags: <b><code>{{rustflags}}</code></b>

View file

@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -25,6 +25,12 @@
history. history.
</p> </p>
<p>
🔔 does-it-build supports sending notifications to target maintainers via
GitHub issues. You can add yourself with
<a href="{{notification_pr_url}}">a PR</a>. 🔔
</p>
<h1>Nightlies</h1> <h1>Nightlies</h1>
<ul> <ul>
{% for nightly in nightlies.iter().take(5) %} {% for nightly in nightlies.iter().take(5) %}

View file

@ -5,11 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{target}} build history</title> <title>{{target}} build history</title>
<link rel="stylesheet" href="/index.css" /> <link rel="stylesheet" href="/index.css" />
<style>
.build-indicator-big {
padding: 10px;
}
</style>
</head> </head>
<body> <body>
<h1>Target build history for {{target}}</h1> <h1>Target build history for {{target}}</h1>
@ -33,7 +28,7 @@
</p> </p>
<p> <p>
🔔 does-it-build supports sending notifications to target maintainers via GitHub issues. 🔔 does-it-build supports sending notifications to target maintainers via GitHub issues.
You can add yourself with <a href="{{notification_pr_url}}">a PR</a>. You can add yourself with <a href="{{notification_pr_url}}">a PR</a>. 🔔
</p> </p>
{% if let Some(maintainers) = maintainers %} {% if let Some(maintainers) = maintainers %}
<p> <p>