From 2eb746bf1fc730986df7ae085d4f9708f60ae562 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Tue, 7 Oct 2025 20:40:57 -0600 Subject: [PATCH] Configuration for PR report style (#13) Add a new "Description" style that updates the PR description instead of adding comments. --- ...d2717ce8ed1a38d31a9f0652ed4ec9091b419.json | 12 ++ ...784df93a969f7a381854eee2777370e90906.json} | 14 ++- ...e2511d3f1668d07956ac60a1450ddd8901135.json | 12 -- ...a11d00dd4a6d1bb30bc852aaa792089a3403.json} | 6 +- ...e87e65bba44f4344aa4575a081b8d1f6eddb3.json | 12 ++ ...7af307f061f92c8d284addfecfe5215476ea.json} | 28 +++-- ...9dcfb8dbe4ec110cf956df61c89a9a491a8a.json} | 14 ++- ...8ef17184498e031c3f19aae4b27af4ab9fb0d.json | 12 -- ...80222eaee8a4bde86de607dcafd9e3cebcc56.json | 12 -- crates/core/src/models.rs | 44 +++++++- crates/db/src/lib.rs | 24 ++-- crates/github/src/webhook.rs | 105 ++++++++++++------ crates/web/src/handlers/manage.rs | 19 +++- migrations/21_project_pr_report_style.sql | 1 + 14 files changed, 217 insertions(+), 98 deletions(-) create mode 100644 .sqlx/query-0a643e5596bd37e5ba8018e9decd2717ce8ed1a38d31a9f0652ed4ec9091b419.json rename .sqlx/{query-415ca8dbe5fb5b2d573200257e96f2689efdbd0af3397ebdc9f4dec0a8709bd3.json => query-3d625ccc90b0a896ab906107c60e784df93a969f7a381854eee2777370e90906.json} (79%) delete mode 100644 .sqlx/query-5bdd491e42bf700a271a5fc8431e2511d3f1668d07956ac60a1450ddd8901135.json rename .sqlx/{query-f7bf7260f20ecc8e216bfcf1c62d5c50ae1bb982ce29a63073f89a7723fc6f11.json => query-66c72864237c9c44358e1d3a3333a11d00dd4a6d1bb30bc852aaa792089a3403.json} (56%) create mode 100644 .sqlx/query-77fa30390ff028ea9fff086a695e87e65bba44f4344aa4575a081b8d1f6eddb3.json rename .sqlx/{query-fc441fa93f0d46f6a0a541ae625154cc07efb5d4496d80d489f23a39f0e6b032.json => query-86318ebdf4ce242b320f209e79ae7af307f061f92c8d284addfecfe5215476ea.json} (69%) rename .sqlx/{query-d66c20bc973f3199c05ca1e7f2e3b87cb3bf82646329034caeea9c48d9586823.json => query-9ea268b7129fc3851ce05790e7789dcfb8dbe4ec110cf956df61c89a9a491a8a.json} (81%) delete mode 100644 .sqlx/query-c0ffba953513be9fccf5427dd818ef17184498e031c3f19aae4b27af4ab9fb0d.json delete mode 100644 .sqlx/query-dd7c314e4ac52388eb79a79c5c980222eaee8a4bde86de607dcafd9e3cebcc56.json create mode 100644 migrations/21_project_pr_report_style.sql diff --git a/.sqlx/query-0a643e5596bd37e5ba8018e9decd2717ce8ed1a38d31a9f0652ed4ec9091b419.json b/.sqlx/query-0a643e5596bd37e5ba8018e9decd2717ce8ed1a38d31a9f0652ed4ec9091b419.json new file mode 100644 index 0000000..414ae67 --- /dev/null +++ b/.sqlx/query-0a643e5596bd37e5ba8018e9decd2717ce8ed1a38d31a9f0652ed4ec9091b419.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style, header_image_id, enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)\n ON CONFLICT (id) DO NOTHING\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 13 + }, + "nullable": [] + }, + "hash": "0a643e5596bd37e5ba8018e9decd2717ce8ed1a38d31a9f0652ed4ec9091b419" +} diff --git a/.sqlx/query-415ca8dbe5fb5b2d573200257e96f2689efdbd0af3397ebdc9f4dec0a8709bd3.json b/.sqlx/query-3d625ccc90b0a896ab906107c60e784df93a969f7a381854eee2777370e90906.json similarity index 79% rename from .sqlx/query-415ca8dbe5fb5b2d573200257e96f2689efdbd0af3397ebdc9f4dec0a8709bd3.json rename to .sqlx/query-3d625ccc90b0a896ab906107c60e784df93a969f7a381854eee2777370e90906.json index c2f3252..5c0c11b 100644 --- a/.sqlx/query-415ca8dbe5fb5b2d573200257e96f2689efdbd0af3397ebdc9f4dec0a8709bd3.json +++ b/.sqlx/query-3d625ccc90b0a896ab906107c60e784df93a969f7a381854eee2777370e90906.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ", + "query": "\n SELECT id AS \"id!\", owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style AS \"pr_report_style!\", header_image_id, enabled\n FROM projects\n WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE\n ", "describe": { "columns": [ { @@ -54,13 +54,18 @@ "type_info": "Bool" }, { - "name": "header_image_id", + "name": "pr_report_style!", "ordinal": 10, + "type_info": "Text" + }, + { + "name": "header_image_id", + "ordinal": 11, "type_info": "Blob" }, { "name": "enabled", - "ordinal": 11, + "ordinal": 12, "type_info": "Bool" } ], @@ -78,9 +83,10 @@ true, true, false, + false, true, false ] }, - "hash": "415ca8dbe5fb5b2d573200257e96f2689efdbd0af3397ebdc9f4dec0a8709bd3" + "hash": "3d625ccc90b0a896ab906107c60e784df93a969f7a381854eee2777370e90906" } diff --git a/.sqlx/query-5bdd491e42bf700a271a5fc8431e2511d3f1668d07956ac60a1450ddd8901135.json b/.sqlx/query-5bdd491e42bf700a271a5fc8431e2511d3f1668d07956ac60a1450ddd8901135.json deleted file mode 100644 index 8695018..0000000 --- a/.sqlx/query-5bdd491e42bf700a271a5fc8431e2511d3f1668d07956ac60a1450ddd8901135.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 12 - }, - "nullable": [] - }, - "hash": "5bdd491e42bf700a271a5fc8431e2511d3f1668d07956ac60a1450ddd8901135" -} diff --git a/.sqlx/query-f7bf7260f20ecc8e216bfcf1c62d5c50ae1bb982ce29a63073f89a7723fc6f11.json b/.sqlx/query-66c72864237c9c44358e1d3a3333a11d00dd4a6d1bb30bc852aaa792089a3403.json similarity index 56% rename from .sqlx/query-f7bf7260f20ecc8e216bfcf1c62d5c50ae1bb982ce29a63073f89a7723fc6f11.json rename to .sqlx/query-66c72864237c9c44358e1d3a3333a11d00dd4a6d1bb30bc852aaa792089a3403.json index 878cef0..d264da1 100644 --- a/.sqlx/query-f7bf7260f20ecc8e216bfcf1c62d5c50ae1bb982ce29a63073f89a7723fc6f11.json +++ b/.sqlx/query-66c72864237c9c44358e1d3a3333a11d00dd4a6d1bb30bc852aaa792089a3403.json @@ -1,12 +1,12 @@ { "db_name": "SQLite", - "query": "\n UPDATE projects\n SET owner = ?, repo = ?, name = ?, short_name = ?, default_category = ?, default_version = ?, platform = ?, workflow_id = ?, enable_pr_comments = ?, header_image_id = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP\n WHERE id = ?\n ", + "query": "\n UPDATE projects\n SET owner = ?, repo = ?, name = ?, short_name = ?, default_category = ?, default_version = ?, platform = ?, workflow_id = ?, enable_pr_comments = ?, pr_report_style = ?, header_image_id = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP\n WHERE id = ?\n ", "describe": { "columns": [], "parameters": { - "Right": 12 + "Right": 13 }, "nullable": [] }, - "hash": "f7bf7260f20ecc8e216bfcf1c62d5c50ae1bb982ce29a63073f89a7723fc6f11" + "hash": "66c72864237c9c44358e1d3a3333a11d00dd4a6d1bb30bc852aaa792089a3403" } diff --git a/.sqlx/query-77fa30390ff028ea9fff086a695e87e65bba44f4344aa4575a081b8d1f6eddb3.json b/.sqlx/query-77fa30390ff028ea9fff086a695e87e65bba44f4344aa4575a081b8d1f6eddb3.json new file mode 100644 index 0000000..bfbf46e --- /dev/null +++ b/.sqlx/query-77fa30390ff028ea9fff086a695e87e65bba44f4344aa4575a081b8d1f6eddb3.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style, header_image_id, enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)\n ", + "describe": { + "columns": [], + "parameters": { + "Right": 13 + }, + "nullable": [] + }, + "hash": "77fa30390ff028ea9fff086a695e87e65bba44f4344aa4575a081b8d1f6eddb3" +} diff --git a/.sqlx/query-fc441fa93f0d46f6a0a541ae625154cc07efb5d4496d80d489f23a39f0e6b032.json b/.sqlx/query-86318ebdf4ce242b320f209e79ae7af307f061f92c8d284addfecfe5215476ea.json similarity index 69% rename from .sqlx/query-fc441fa93f0d46f6a0a541ae625154cc07efb5d4496d80d489f23a39f0e6b032.json rename to .sqlx/query-86318ebdf4ce242b320f209e79ae7af307f061f92c8d284addfecfe5215476ea.json index 91f0fe5..aa4b17b 100644 --- a/.sqlx/query-fc441fa93f0d46f6a0a541ae625154cc07efb5d4496d80d489f23a39f0e6b032.json +++ b/.sqlx/query-86318ebdf4ce242b320f209e79ae7af307f061f92c8d284addfecfe5215476ea.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_category,\n default_version,\n platform,\n workflow_id,\n enable_pr_comments AS \"enable_pr_comments!\",\n header_image_id,\n enabled AS \"enabled!\",\n git_commit,\n git_commit_message,\n MAX(timestamp) AS \"timestamp: time::OffsetDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version)\n FILTER (WHERE version IS NOT NULL) AS versions\n FROM projects LEFT JOIN reports ON (\n reports.project_id = projects.id\n AND reports.timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ", + "query": "\n SELECT\n projects.id AS \"project_id!\",\n owner AS \"owner!\",\n repo AS \"repo!\",\n name,\n short_name,\n default_category,\n default_version,\n platform,\n workflow_id,\n enable_pr_comments AS \"enable_pr_comments!\",\n pr_report_style AS \"pr_report_style!\",\n header_image_id,\n enabled AS \"enabled!\",\n git_commit,\n git_commit_message,\n MAX(timestamp) AS \"timestamp: time::OffsetDateTime\",\n JSON_GROUP_ARRAY(version ORDER BY version)\n FILTER (WHERE version IS NOT NULL) AS versions\n FROM projects LEFT JOIN reports ON (\n reports.project_id = projects.id\n AND reports.timestamp = (\n SELECT MAX(timestamp)\n FROM reports\n WHERE project_id = projects.id\n )\n )\n GROUP BY projects.id\n ORDER BY MAX(timestamp) DESC\n ", "describe": { "columns": [ { @@ -54,33 +54,38 @@ "type_info": "Bool" }, { - "name": "header_image_id", + "name": "pr_report_style!", "ordinal": 10, + "type_info": "Text" + }, + { + "name": "header_image_id", + "ordinal": 11, "type_info": "Blob" }, { "name": "enabled!", - "ordinal": 11, + "ordinal": 12, "type_info": "Bool" }, { "name": "git_commit", - "ordinal": 12, - "type_info": "Text" - }, - { - "name": "git_commit_message", "ordinal": 13, "type_info": "Text" }, { - "name": "timestamp: time::OffsetDateTime", + "name": "git_commit_message", "ordinal": 14, "type_info": "Text" }, { - "name": "versions", + "name": "timestamp: time::OffsetDateTime", "ordinal": 15, + "type_info": "Datetime" + }, + { + "name": "versions", + "ordinal": 16, "type_info": "Text" } ], @@ -103,8 +108,9 @@ true, true, true, + true, true ] }, - "hash": "fc441fa93f0d46f6a0a541ae625154cc07efb5d4496d80d489f23a39f0e6b032" + "hash": "86318ebdf4ce242b320f209e79ae7af307f061f92c8d284addfecfe5215476ea" } diff --git a/.sqlx/query-d66c20bc973f3199c05ca1e7f2e3b87cb3bf82646329034caeea9c48d9586823.json b/.sqlx/query-9ea268b7129fc3851ce05790e7789dcfb8dbe4ec110cf956df61c89a9a491a8a.json similarity index 81% rename from .sqlx/query-d66c20bc973f3199c05ca1e7f2e3b87cb3bf82646329034caeea9c48d9586823.json rename to .sqlx/query-9ea268b7129fc3851ce05790e7789dcfb8dbe4ec110cf956df61c89a9a491a8a.json index 5946f66..a89c4f5 100644 --- a/.sqlx/query-d66c20bc973f3199c05ca1e7f2e3b87cb3bf82646329034caeea9c48d9586823.json +++ b/.sqlx/query-9ea268b7129fc3851ce05790e7789dcfb8dbe4ec110cf956df61c89a9a491a8a.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "\n SELECT owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled\n FROM projects\n WHERE id = ?\n ", + "query": "\n SELECT owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style AS \"pr_report_style!\", header_image_id, enabled\n FROM projects\n WHERE id = ?\n ", "describe": { "columns": [ { @@ -49,13 +49,18 @@ "type_info": "Bool" }, { - "name": "header_image_id", + "name": "pr_report_style!", "ordinal": 9, + "type_info": "Text" + }, + { + "name": "header_image_id", + "ordinal": 10, "type_info": "Blob" }, { "name": "enabled", - "ordinal": 10, + "ordinal": 11, "type_info": "Bool" } ], @@ -72,9 +77,10 @@ true, true, false, + false, true, false ] }, - "hash": "d66c20bc973f3199c05ca1e7f2e3b87cb3bf82646329034caeea9c48d9586823" + "hash": "9ea268b7129fc3851ce05790e7789dcfb8dbe4ec110cf956df61c89a9a491a8a" } diff --git a/.sqlx/query-c0ffba953513be9fccf5427dd818ef17184498e031c3f19aae4b27af4ab9fb0d.json b/.sqlx/query-c0ffba953513be9fccf5427dd818ef17184498e031c3f19aae4b27af4ab9fb0d.json deleted file mode 100644 index 845a245..0000000 --- a/.sqlx/query-c0ffba953513be9fccf5427dd818ef17184498e031c3f19aae4b27af4ab9fb0d.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)\n ON CONFLICT (id) DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 12 - }, - "nullable": [] - }, - "hash": "c0ffba953513be9fccf5427dd818ef17184498e031c3f19aae4b27af4ab9fb0d" -} diff --git a/.sqlx/query-dd7c314e4ac52388eb79a79c5c980222eaee8a4bde86de607dcafd9e3cebcc56.json b/.sqlx/query-dd7c314e4ac52388eb79a79c5c980222eaee8a4bde86de607dcafd9e3cebcc56.json deleted file mode 100644 index 3c6f8b1..0000000 --- a/.sqlx/query-dd7c314e4ac52388eb79a79c5c980222eaee8a4bde86de607dcafd9e3cebcc56.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n DELETE FROM report_report_units\n WHERE report_id NOT IN (SELECT id FROM reports)\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 0 - }, - "nullable": [] - }, - "hash": "dd7c314e4ac52388eb79a79c5c980222eaee8a4bde86de607dcafd9e3cebcc56" -} diff --git a/crates/core/src/models.rs b/crates/core/src/models.rs index dd70228..ea9a875 100644 --- a/crates/core/src/models.rs +++ b/crates/core/src/models.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, str::FromStr, sync::Arc}; +use std::{borrow::Cow, fmt, str::FromStr, sync::Arc}; use objdiff_core::bindings::report::{Measures, Report, ReportCategory, ReportUnit}; use serde::Serialize; @@ -7,6 +7,46 @@ use time::UtcDateTime; // BLAKE3 hash of the image data pub type ImageId = [u8; 32]; +#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum PullReportStyle { + #[default] + Comment, + Description, +} + +impl PullReportStyle { + pub const fn variants() -> &'static [Self] { &[Self::Comment, Self::Description] } + + pub fn as_str(&self) -> &'static str { + match self { + Self::Comment => "comment", + Self::Description => "description", + } + } +} + +impl FromStr for PullReportStyle { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "comment" => Ok(Self::Comment), + "description" => Ok(Self::Description), + _ => Err(()), + } + } +} + +impl fmt::Display for PullReportStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Comment => "Comment", + Self::Description => "Description", + }) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct Project { pub id: u64, @@ -19,6 +59,7 @@ pub struct Project { pub platform: Option, pub workflow_id: Option, pub enable_pr_comments: bool, + pub pr_report_style: PullReportStyle, pub header_image_id: Option, pub enabled: bool, } @@ -36,6 +77,7 @@ impl Default for Project { platform: None, workflow_id: None, enable_pr_comments: true, + pr_report_style: PullReportStyle::Comment, header_image_id: None, enabled: true, } diff --git a/crates/db/src/lib.rs b/crates/db/src/lib.rs index c053f5d..e391f8b 100644 --- a/crates/db/src/lib.rs +++ b/crates/db/src/lib.rs @@ -87,10 +87,11 @@ impl Database { let mut tx = self.pool.begin().await?; let project_id = project.id as i64; let header_image_id = project.header_image_id.as_ref().map(|b| b.as_slice()); + let pr_report_style = project.pr_report_style.as_str(); sqlx::query!( r#" - INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style, header_image_id, enabled, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) ON CONFLICT (id) DO NOTHING "#, project_id, @@ -103,6 +104,7 @@ impl Database { project.platform, project.workflow_id, project.enable_pr_comments, + pr_report_style, header_image_id, project.enabled, ) @@ -391,7 +393,7 @@ impl Database { let mut conn = self.pool.acquire().await?; let project = match sqlx::query!( r#" - SELECT id AS "id!", owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled + SELECT id AS "id!", owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style AS "pr_report_style!", header_image_id, enabled FROM projects WHERE owner = ? COLLATE NOCASE AND repo = ? COLLATE NOCASE "#, @@ -412,6 +414,7 @@ impl Database { platform: row.platform, workflow_id: row.workflow_id, enable_pr_comments: row.enable_pr_comments, + pr_report_style: row.pr_report_style.parse().unwrap_or_default(), header_image_id: row.header_image_id.and_then(|b| b.try_into().ok()), enabled: row.enabled, }, @@ -429,7 +432,7 @@ impl Database { let project_id_db = project_id as i64; let project = match sqlx::query!( r#" - SELECT owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled + SELECT owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style AS "pr_report_style!", header_image_id, enabled FROM projects WHERE id = ? "#, @@ -449,6 +452,7 @@ impl Database { platform: row.platform, workflow_id: row.workflow_id, enable_pr_comments: row.enable_pr_comments, + pr_report_style: row.pr_report_style.parse().unwrap_or_default(), header_image_id: row.header_image_id.and_then(|b| b.try_into().ok()), enabled: row.enabled, }, @@ -584,6 +588,7 @@ impl Database { platform, workflow_id, enable_pr_comments AS "enable_pr_comments!", + pr_report_style AS "pr_report_style!", header_image_id, enabled AS "enabled!", git_commit, @@ -618,6 +623,7 @@ impl Database { platform: row.platform, workflow_id: row.workflow_id, enable_pr_comments: row.enable_pr_comments, + pr_report_style: row.pr_report_style.parse().unwrap_or_default(), header_image_id: row.header_image_id.and_then(|b| b.try_into().ok()), enabled: row.enabled, }, @@ -986,10 +992,11 @@ impl Database { let mut conn = self.pool.acquire().await?; let project_id = project.id as i64; let header_image_id = project.header_image_id.as_ref().map(|b| b.as_slice()); + let pr_report_style = project.pr_report_style.as_str(); sqlx::query!( r#" UPDATE projects - SET owner = ?, repo = ?, name = ?, short_name = ?, default_category = ?, default_version = ?, platform = ?, workflow_id = ?, enable_pr_comments = ?, header_image_id = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP + SET owner = ?, repo = ?, name = ?, short_name = ?, default_category = ?, default_version = ?, platform = ?, workflow_id = ?, enable_pr_comments = ?, pr_report_style = ?, header_image_id = ?, enabled = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? "#, project.owner, @@ -1001,6 +1008,7 @@ impl Database { project.platform, project.workflow_id, project.enable_pr_comments, + pr_report_style, header_image_id, project.enabled, project_id, @@ -1014,10 +1022,11 @@ impl Database { let mut conn = self.pool.acquire().await?; let project_id = project.id as i64; let header_image_id = project.header_image_id.as_ref().map(|b| b.as_slice()); + let pr_report_style = project.pr_report_style.as_str(); sqlx::query!( r#" - INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, header_image_id, enabled, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + INSERT INTO projects (id, owner, repo, name, short_name, default_category, default_version, platform, workflow_id, enable_pr_comments, pr_report_style, header_image_id, enabled, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) "#, project_id, project.owner, @@ -1029,6 +1038,7 @@ impl Database { project.platform, project.workflow_id, project.enable_pr_comments, + pr_report_style, header_image_id, project.enabled, ) diff --git a/crates/github/src/webhook.rs b/crates/github/src/webhook.rs index d8bd762..89b53f5 100644 --- a/crates/github/src/webhook.rs +++ b/crates/github/src/webhook.rs @@ -7,7 +7,7 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use decomp_dev_core::{AppError, config::GitHubConfig}; +use decomp_dev_core::{AppError, config::GitHubConfig, models::PullReportStyle}; use decomp_dev_db::Database; use hmac::{Hmac, Mac}; use octocrab::{ @@ -290,9 +290,6 @@ async fn handle_workflow_run_completed( continue; } tracing::info!("Processing pull request {}", pull_request.id); - let issues = client.issues_by_id(repository_id); - // Only fetch first page for now - let existing_comments = issues.list_comments(pull_request.number).send().await?; // Get all versions that exist on the base branch to check for missing reports let base_versions = state @@ -359,35 +356,81 @@ async fn handle_workflow_run_completed( if !version_comments.is_empty() { let combined_comment = generate_combined_comment(version_comments); - // Find the first existing comment that contains "Report for" any version - let existing_report_comments: Vec<_> = existing_comments - .items - .iter() - .filter(|comment| { - // TODO check author ID - comment.body.as_ref().is_some_and(|body| body.contains("### Report for ")) - }) - .collect(); - - if let Some(first_comment) = existing_report_comments.first() { - // Update the first comment - issues - .update_comment(first_comment.id, combined_comment) - .await - .context("Failed to update existing comment")?; - - // Delete any additional report comments - for comment in existing_report_comments.iter().skip(1) { - if let Err(e) = issues.delete_comment(comment.id).await { - tracing::warn!("Failed to delete old comment {}: {}", comment.id, e); + if project_info.project.pr_report_style == PullReportStyle::Description { + let pulls = + client.pulls(&project_info.project.owner, &project_info.project.repo); + let pull = pulls.get(pull_request.number).await?; + let start_marker = ""; + let end_marker = ""; + let new_section = format!("{start_marker}\n{combined_comment}\n{end_marker}"); + let existing_body = pull.body.unwrap_or_default(); + let new_body = if let Some(start_idx) = existing_body.find(start_marker) { + if let Some(end_rel) = existing_body[start_idx..].find(end_marker) { + let end_idx = start_idx + end_rel + end_marker.len(); + format!( + "{}{}{}", + &existing_body[..start_idx], + new_section, + &existing_body[end_idx..] + ) + } else { + format!("{existing_body}\n\n---\n\n{new_section}") } - } - } else { - // Create new comment - issues - .create_comment(pull_request.number, combined_comment) + } else if existing_body.trim().is_empty() { + new_section + } else { + format!("{}\n\n---\n\n{}", existing_body.trim(), new_section) + }; + + pulls + .update(pull_request.number) + .body(new_body) + .send() .await - .context("Failed to create comment")?; + .context("Failed to update pull request body")?; + } else { + let issues = client.issues_by_id(repository_id); + // Only fetch first page for now + let existing_comments = + issues.list_comments(pull_request.number).send().await?; + + // Find the first existing comment that contains "Report for" any version + let existing_report_comments: Vec<_> = existing_comments + .items + .iter() + .filter(|comment| { + // TODO check author ID + comment + .body + .as_ref() + .is_some_and(|body| body.contains("### Report for ")) + }) + .collect(); + + if let Some(first_comment) = existing_report_comments.first() { + // Update the first comment + issues + .update_comment(first_comment.id, combined_comment.clone()) + .await + .context("Failed to update existing comment")?; + + // Delete any additional report comments + for comment in existing_report_comments.iter().skip(1) { + if let Err(e) = issues.delete_comment(comment.id).await { + tracing::warn!( + "Failed to delete old comment {}: {}", + comment.id, + e + ); + } + } + } else { + // Create new comment + issues + .create_comment(pull_request.number, combined_comment) + .await + .context("Failed to create comment")?; + } } } } diff --git a/crates/web/src/handlers/manage.rs b/crates/web/src/handlers/manage.rs index 6a76031..d0dfd43 100644 --- a/crates/web/src/handlers/manage.rs +++ b/crates/web/src/handlers/manage.rs @@ -13,7 +13,7 @@ use decomp_dev_auth::CurrentUser; use decomp_dev_core::{ AppError, models::{ - ALL_PLATFORMS, CachedReportFile, Project, ProjectInfo, ProjectVisibility, + ALL_PLATFORMS, CachedReportFile, Project, ProjectInfo, ProjectVisibility, PullReportStyle, project_visibility, }, }; @@ -513,6 +513,17 @@ async fn render_manage_project( " (requires GitHub App installation)" } } + label { + "PR report style" + select name="pr_report_style" disabled[installation_id.is_none()] { + @for &style in PullReportStyle::variants() { + option value=(style.as_str()) selected[project_info.project.pr_report_style == style] { (style) } + } + } + @if installation_id.is_none() { + " (requires GitHub App installation)" + } + } hr; label { "Hero image " @@ -579,6 +590,7 @@ pub struct ProjectForm { pub default_version: Option, pub workflow_id: String, pub enable_pr_comments: Option, + pub pr_report_style: Option, pub header_image: Option, pub clear_header_image: Option, pub enabled: Option, @@ -645,6 +657,11 @@ pub async fn manage_project_save( } else { project_info.project.enable_pr_comments }, + pr_report_style: if installation_id.is_some() { + form.pr_report_style.as_deref().and_then(|s| s.parse().ok()).unwrap_or_default() + } else { + project_info.project.pr_report_style + }, header_image_id, enabled: form.enabled.is_some_and(|v| v == "on"), }; diff --git a/migrations/21_project_pr_report_style.sql b/migrations/21_project_pr_report_style.sql new file mode 100644 index 0000000..34362e9 --- /dev/null +++ b/migrations/21_project_pr_report_style.sql @@ -0,0 +1 @@ +ALTER TABLE projects ADD COLUMN pr_report_style TEXT NOT NULL DEFAULT 'comment';