diff --git a/crates/squawk/src/config.rs b/crates/squawk/src/config.rs index e7312bef..04d3fbb6 100644 --- a/crates/squawk/src/config.rs +++ b/crates/squawk/src/config.rs @@ -17,6 +17,8 @@ const FILE_NAME: &str = ".squawk.toml"; pub struct UploadToGitHubConfig { #[serde(default)] pub fail_on_violations: Option, + #[serde(default)] + pub only_comment_on_violations: Option, } #[derive(Debug, Default, Deserialize)] @@ -258,6 +260,16 @@ fail_on_violations = true assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))); } #[test] + fn load_only_comment_on_violations() { + let squawk_toml = NamedTempFile::new().expect("generate tempFile"); + let file = r" +[upload_to_github] +only_comment_on_violations = true + "; + fs::write(&squawk_toml, file).expect("Unable to write file"); + assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))); + } + #[test] fn load_included_rules() { let squawk_toml = NamedTempFile::new().expect("generate tempFile"); let file = r#" diff --git a/crates/squawk/src/github.rs b/crates/squawk/src/github.rs index 0d62432f..87130f9e 100644 --- a/crates/squawk/src/github.rs +++ b/crates/squawk/src/github.rs @@ -88,6 +88,7 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { let UploadToGithubArgs { paths, fail_on_violations, + only_comment_on_violations, github_private_key, github_api_url, github_token, @@ -106,6 +107,14 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { fail_on_violations }; + let only_comment_on_violations = if let Some(only_comment_on_violations_cfg) = + cfg.upload_to_github.only_comment_on_violations + { + only_comment_on_violations_cfg + } else { + only_comment_on_violations + }; + let github_app = create_gh_app( &github_api_url, &github_install_id, @@ -133,8 +142,16 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { info!("no files checked, exiting"); return Ok(()); } - info!("generating github comment body"); - let comment_body = get_comment_body(&file_results, VERSION); + + let violations: usize = file_results.iter().map(|f| f.violations.len()).sum(); + + let comment_body = if only_comment_on_violations && violations == 0 { + info!("no violations found, posting clean summary comment"); + get_clean_comment_body(file_results.len()) + } else { + info!("generating github comment body"); + get_comment_body(&file_results, VERSION) + }; comment_on_pr( github_app.as_ref(), @@ -151,8 +168,6 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { fmt_github_annotations(&mut handle, &file_results)?; } - let violations: usize = file_results.iter().map(|f| f.violations.len()).sum(); - if fail_on_violations && violations > 0 { let files = file_results.len(); bail!("Found {violations} violation(s) across {files} file(s)"); @@ -161,6 +176,10 @@ pub fn check_and_comment_on_pr(cfg: Config) -> Result<()> { Ok(()) } +fn get_clean_comment_body(file_count: usize) -> String { + format!("{COMMENT_HEADER}\n\nāœ… 0 violations across {file_count} file(s)") +} + fn get_comment_body(files: &[CheckReport], version: &str) -> String { let violations_count: usize = files.iter().map(|x| x.violations.len()).sum(); let violations_emoji = get_violations_emoji(violations_count); @@ -428,6 +447,12 @@ ALTER TABLE "core_recipe" ADD COLUMN "foo" integer DEFAULT 10; assert_snapshot!(body); } + #[test] + fn generating_clean_comment_no_violations() { + let body = super::get_clean_comment_body(8); + assert_snapshot!(body); + } + /// Ideally the logic won't leave a comment when there are no migrations but /// better safe than sorry #[test] diff --git a/crates/squawk/src/main.rs b/crates/squawk/src/main.rs index 41e89a57..69b91d7b 100644 --- a/crates/squawk/src/main.rs +++ b/crates/squawk/src/main.rs @@ -26,6 +26,9 @@ pub struct UploadToGithubArgs { /// Exits with an error if violations are found #[arg(long)] fail_on_violations: bool, + /// Only posts a report comment on violations + #[arg(long)] + only_comment_on_violations: bool, #[arg(long, env = "SQUAWK_GITHUB_PRIVATE_KEY")] github_private_key: Option, #[arg(long, env = "SQUAWK_GITHUB_PRIVATE_KEY_BASE64")] diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap index b8a89dea..1a628ab9 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_assume_in_transaction.snap @@ -14,6 +14,7 @@ Ok( ), upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap index cc6e930b..e45130e2 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_cfg_full.snap @@ -26,6 +26,7 @@ Ok( ), upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap index 183c8182..cd23a328 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_paths.snap @@ -14,6 +14,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap index 9aabf238..0e92f595 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules.snap @@ -14,6 +14,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap index 01910f51..33eddb6a 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_excluded_rules_with_alias.snap @@ -15,6 +15,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap index 994c15fb..83e4b93b 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_fail_on_violations.snap @@ -14,6 +14,7 @@ Ok( fail_on_violations: Some( true, ), + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap index 1977a09b..7131a556 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_included_rules.snap @@ -14,6 +14,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap new file mode 100644 index 00000000..5f821f19 --- /dev/null +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_only_comment_on_violations.snap @@ -0,0 +1,21 @@ +--- +source: crates/squawk/src/config.rs +expression: "ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))" +--- +Ok( + Some( + ConfigFile { + excluded_paths: [], + excluded_rules: [], + included_rules: [], + pg_version: None, + assume_in_transaction: None, + upload_to_github: UploadToGitHubConfig { + fail_on_violations: None, + only_comment_on_violations: Some( + true, + ), + }, + }, + ), +) diff --git a/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap b/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap index c380224b..7c7fe260 100644 --- a/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap +++ b/crates/squawk/src/snapshots/squawk__config__test_config__load_pg_version.snap @@ -20,6 +20,7 @@ Ok( assume_in_transaction: None, upload_to_github: UploadToGitHubConfig { fail_on_violations: None, + only_comment_on_violations: None, }, }, ), diff --git a/crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap b/crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap new file mode 100644 index 00000000..0309b320 --- /dev/null +++ b/crates/squawk/src/snapshots/squawk__github__test_github_comment__generating_clean_comment_no_violations.snap @@ -0,0 +1,7 @@ +--- +source: crates/squawk/src/github.rs +expression: body +--- +# Squawk Report + +āœ… 0 violations across 8 file(s) diff --git a/docs/docs/cli.md b/docs/docs/cli.md index ea22f9bb..097c7aef 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -149,6 +149,7 @@ excluded_paths = [ ] [upload_to_github] fail_on_violations = true +only_comment_on_violations = true ``` See the [Squawk website](https://squawkhq.com/docs/rules) for documentation on each rule with examples and reasoning. diff --git a/docs/docs/github_app.md b/docs/docs/github_app.md index a33d0fe6..55e4962d 100644 --- a/docs/docs/github_app.md +++ b/docs/docs/github_app.md @@ -161,3 +161,23 @@ To use Squawk as a GitHub App, Squawk needs a corresponding GitHub App so it can +## Only comment on violations + +By default, Squawk posts a PR comment on every run. To suppress the full report +when there are no violations, pass `--only-comment-on-violations` to the +`upload-to-github` subcommand: + +```bash +squawk upload-to-github --only-comment-on-violations example.sql +``` + +Or set it in `.squawk.toml`: + +```toml +[upload_to_github] +only_comment_on_violations = true +``` + +When no violations are found, Squawk will post a brief āœ… summary instead of +the full report. +