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

View file

@ -5,25 +5,29 @@ edition = "2021"
[dependencies]
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"
futures = "0.3.30"
jsonwebtoken = { version = "9.3.1", features = [] }
jiff = "0.2.16"
jsonwebtoken = { version = "9.3.1" }
octocrab = "0.47.1"
reqwest = { version = "0.12.7", features = [
"rustls-tls",
"http2",
], default-features = false }
serde = { version = "1.0.210", features = ["derive"] }
sqlx = { version = "0.8.2", features = [
"macros",
"migrate",
"runtime-tokio",
"sqlite",
] }
sqlx = { version = "0.8.2", features = ["runtime-tokio", "sqlite"] }
tempfile = "3.12.0"
time = { version = "0.3.36", features = ["formatting", "macros", "parsing"] }
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"] }
[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 {
Some((nightly, mode)) => {
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
.wrap_err_with(|| format!("building targets for toolchain {nightly}"));
if let Err(err) = result {

View file

@ -106,6 +106,7 @@ pub struct NotificationIssue {
pub status: NotificationStatus,
pub first_failed_nightly: String,
pub target: String,
pub last_update_date: Option<i64>,
}
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(())
}
}

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

View file

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

View file

@ -1,29 +1,54 @@
:root {
color-scheme: light dark;
}
html {
font-family: sans-serif;
}
@media screen and (min-width: 768px) {
html {
margin: 20px;
}
}
table {
border-spacing: 0;
}
th,
td {
border: 1px solid;
margin: 0;
padding: 2px 5px;
}
tr:not(:last-child) {
th,
td {
border-bottom: 1px solid;
}
}
.error {
background-color: lightcoral;
background-color: light-dark(lightcoral, darkred);
}
.pass {
background-color: greenyellow;
background-color: light-dark(greenyellow, darkgreen);
}
.missing {
background-color: lightgray;
background-color: light-dark(lightgray, rgb(85, 85, 85));
}
.target-header {
writing-mode: sideways-lr;
}
.build-indicator-big {
padding: 10px;
margin-top: 20px;
}
.build-cell {
font-size: 2rem;
}
@ -36,13 +61,20 @@ td {
padding: 5px;
}
.target-name-col {
white-space: nowrap;
}
.footer {
margin-top: 20px;
display: flex;
align-items: center;
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">
<head>
<meta charset="UTF-8" />
@ -6,18 +6,15 @@
<title>Build {{nightly}} {{target}}</title>
<link rel="stylesheet" href="/index.css" />
<script type="module" defer src="index.js"></script>
<style>
.build-indicator-big {
padding: 10px;
}
</style>
</head>
<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>
<div style="margin-top: 20px" class="{{status}} build-indicator-big">
{{status}}
</div>
<div class="{{status}} build-indicator-big">{{status}}</div>
{% if let Some(rustflags) = rustflags %}
<p>
Using rustflags: <b><code>{{rustflags}}</code></b>

View file

@ -1,4 +1,4 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
@ -25,6 +25,12 @@
history.
</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>
<ul>
{% for nightly in nightlies.iter().take(5) %}

View file

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