Configuration for PR report style (#13)

Add a new "Description" style that updates the PR description instead of adding comments.
This commit is contained in:
Luke Street
2025-10-07 20:40:57 -06:00
committed by GitHub
parent c42c1f7088
commit 2eb746bf1f
14 changed files with 217 additions and 98 deletions
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
+43 -1
View File
@@ -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<Self, Self::Err> {
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<String>,
pub workflow_id: Option<String>,
pub enable_pr_comments: bool,
pub pr_report_style: PullReportStyle,
pub header_image_id: Option<ImageId>,
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,
}
+17 -7
View File
@@ -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,
)
+74 -31
View File
@@ -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 = "<!-- decomp.dev report start -->";
let end_marker = "<!-- decomp.dev report end -->";
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")?;
}
}
}
}
+18 -1
View File
@@ -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<String>,
pub workflow_id: String,
pub enable_pr_comments: Option<String>,
pub pr_report_style: Option<String>,
pub header_image: Option<Bytes>,
pub clear_header_image: Option<String>,
pub enabled: Option<String>,
@@ -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"),
};
@@ -0,0 +1 @@
ALTER TABLE projects ADD COLUMN pr_report_style TEXT NOT NULL DEFAULT 'comment';