Skip to content

Make delivery summary posting optional per program#498

Open
neal-bpm wants to merge 2 commits into
mainfrom
vn/slack-delivery-summary-optional
Open

Make delivery summary posting optional per program#498
neal-bpm wants to merge 2 commits into
mainfrom
vn/slack-delivery-summary-optional

Conversation

@neal-bpm
Copy link
Copy Markdown
Contributor

@neal-bpm neal-bpm commented May 12, 2026

Summary

Make delivery summary posting optional per program, allowing programs to control whether campaign
delivery summaries are posted to their configured Slack channel.

Problem

Previously, campaign delivery summaries were automatically posted to Slack whenever a program had a Slack channel configured. This didn't account for programs that may not want this automated notification,
creating unnecessary Slack traffic for some teams.

Solution

  • Added send_delivery_summaries boolean field to programs (defaults to false)
  • Made the checkbox visible in the form only when a Slack channel is selected, preventing misconfiguration
  • Updated CampaignSummaryPoster to check this flag before posting summaries

Changes

Backend:

  • Added migration to add send_delivery_summaries column to programs table
  • Updated Program schema to include the new field
  • Modified CampaignSummaryPoster.post_summary_for_campaign/1 to check the flag before posting
  • Changed logging from warning to info when summaries are skipped due to the flag

Frontend:

  • Added conditional checkbox in program form that only displays when a Slack channel is selected
  • Uses :if directive to prevent showing the option when channel is not configured

Note

Programs that want delivery summaries need to explicitly enable the checkbox.

Checklist before requesting a review

  • I have performed a self-review of my code
  • [] If it is a core feature, I have added tests.
  • Are there other PRs or Issues that I should link to here?
  • Will this be part of a product update? If yes, please write one phrase
    about this update in the description above.
    Opt-in delivery summaries: programs control whether campaign delivery summaries are posted to Slack channel.

Summary by cubic

Make Slack delivery summaries opt-in per program so teams control whether campaign summaries are posted. Default is off; existing programs with a Slack channel stay enabled to preserve current behavior.

  • New Features

    • Added send_delivery_summaries on programs (default false).
    • Show the checkbox only when a Slack channel is set in the Program form.
    • CampaignSummaryPoster respects the flag; skips and logs info when disabled or no channel.
  • Migration

    • Run the new migrations.
    • Auto-enables send_delivery_summaries for programs that already have a Slack channel; new programs must set a Slack channel and enable “Send Campaign Delivery Summary to Slack.”

Written for commit 1ecd4fd. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a configurable setting to control whether campaign delivery summaries are sent to Slack.
    • Programs can now toggle delivery summary notifications on/off via a new checkbox in settings (visible only when a Slack channel is configured).
    • Existing programs with Slack channels automatically have this feature enabled.

Review Change Stack

  program

Programs may have different preferences about whether campaign delivery summaries should be posted to their Slack channel
- Added `send_delivery_summaries`   boolean field to programs (defaults
  false)
  - Conditionally render the checkbox in the form only when a Slack channel is selected, preventing accidental configuration without a channel
  - Updated CampaignSummaryPoster to  check this flag before posting
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

A new send_delivery_summaries boolean field is added to the Program schema with migrations that create the database column and enable it for programs already configured with Slack channels. The program form conditionally displays a checkbox for this flag when a Slack channel is present. The campaign summary poster now gates its posting logic to skip when the flag is disabled or when no Slack channel is configured.

Changes

Delivery Summaries Feature Flag

Layer / File(s) Summary
Schema field and database migrations
lib/bike_brigade/delivery/program.ex, priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs, priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs
The Program schema adds send_delivery_summaries with default false and updates the changeset cast list. Two migrations create the column and backfill true for programs with existing Slack channels.
Program form checkbox control
lib/bike_brigade_web/live/program_live/form_component.html.heex
A conditionally-rendered checkbox field appears only when slack_channel_id is present, allowing users to toggle campaign delivery summary notifications.
Campaign summary posting gate
lib/bike_brigade/campaign_summary_poster.ex
The entry point now passes the program's send_delivery_summaries flag to the internal posting function, which skips with a log message when disabled or when no Slack channel is configured, and only executes the full posting pipeline when both enabled and channel-present.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • bikebrigade/dispatch#486: Introduces the scheduled Slack summary sender that calls post_summary_for_campaign/1; this PR gates that same posting logic with the new send_delivery_summaries flag via conditional do_post_summary/3 clauses.

Suggested reviewers

  • mveytsman

Poem

🐰 A flag is born to guide the way,
Campaign summaries now have their say—
To Slack they flow, when enabled true,
With channels set and forms on cue,
Migrations dance and schemas play!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: making delivery summary posting optional per program, which is the core objective of the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description comprehensively covers the changes, problem, solution, and implementation details with clear sections and a product update phrase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vn/slack-delivery-summary-optional

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

  channel configured. This is the existing behaviour.
@neal-bpm neal-bpm requested a review from mveytsman May 19, 2026 19:40
@neal-bpm neal-bpm marked this pull request as ready for review May 19, 2026 19:40
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/bike_brigade/campaign_summary_poster.ex`:
- Around line 69-77: Add a defensive clause to do_post_summary/3 so non-true
values for the send_delivery_summaries flag (nil, false, or any other unexpected
value) don't cause a FunctionClauseError; for example add a clause like defp
do_post_summary(campaign, _channel_id, flag) when flag != true do
Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled
for program (flag: #{inspect(flag)})") end to catch nil/false/invalid flag
values before the do_post_summary(campaign, channel_id, true) clause.

In
`@priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs`:
- Line 6: The migration currently adds the column send_delivery_summaries with a
default but allows NULLs; update the migration's change function so the add
statement for :send_delivery_summaries uses null: false (e.g. add
:send_delivery_summaries, :boolean, default: false, null: false) to enforce a
non-null boolean at the DB level; ensure the migration's down/rollback behavior
(or change callback) remains correct after this change.

In
`@priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs`:
- Around line 8-12: The migration currently references the schema module
BikeBrigade.Delivery.Program which can break replay; change the query to use the
table name string instead (e.g., from(p in "programs", update: [set:
[send_delivery_summaries: true]], where: not is_nil(p.slack_channel_id))) and
keep the Repo.update_all call, so the migration uses raw table-based Ecto
queries rather than the application schema module.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0231233d-f6ef-4f85-bbc2-b8c75047e0ec

📥 Commits

Reviewing files that changed from the base of the PR and between ff808be and 1ecd4fd.

📒 Files selected for processing (5)
  • lib/bike_brigade/campaign_summary_poster.ex
  • lib/bike_brigade/delivery/program.ex
  • lib/bike_brigade_web/live/program_live/form_component.html.heex
  • priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs
  • priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs

Comment on lines +69 to +77
defp do_post_summary(campaign, _, false) do
Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled for program")
end

defp do_post_summary(campaign, nil) do
Logger.warning("Skipping campaign #{campaign.id}: no Slack channel configured for program")
Slack.Operations.notify_campaign_error(campaign, "No Slack channel configured")
defp do_post_summary(campaign, nil, _) do
Logger.info("Skipping campaign #{campaign.id}: no Slack channel configured for program")
end

defp do_post_summary(campaign, channel_id) do
defp do_post_summary(campaign, channel_id, true) do
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle non-boolean flag values defensively in do_post_summary/3.

If send_delivery_summaries is nil and slack_channel_id is present, no clause matches and this can crash with FunctionClauseError.

Defensive clause update
-defp do_post_summary(campaign, _, false) do
+defp do_post_summary(campaign, _, enabled) when enabled != true do
   Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled for program")
 end

 defp do_post_summary(campaign, nil, _) do
   Logger.info("Skipping campaign #{campaign.id}: no Slack channel configured for program")
 end

 defp do_post_summary(campaign, channel_id, true) do
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
defp do_post_summary(campaign, _, false) do
Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled for program")
end
defp do_post_summary(campaign, nil) do
Logger.warning("Skipping campaign #{campaign.id}: no Slack channel configured for program")
Slack.Operations.notify_campaign_error(campaign, "No Slack channel configured")
defp do_post_summary(campaign, nil, _) do
Logger.info("Skipping campaign #{campaign.id}: no Slack channel configured for program")
end
defp do_post_summary(campaign, channel_id) do
defp do_post_summary(campaign, channel_id, true) do
defp do_post_summary(campaign, _, enabled) when enabled != true do
Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled for program")
end
defp do_post_summary(campaign, nil, _) do
Logger.info("Skipping campaign #{campaign.id}: no Slack channel configured for program")
end
defp do_post_summary(campaign, channel_id, true) do
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/bike_brigade/campaign_summary_poster.ex` around lines 69 - 77, Add a
defensive clause to do_post_summary/3 so non-true values for the
send_delivery_summaries flag (nil, false, or any other unexpected value) don't
cause a FunctionClauseError; for example add a clause like defp
do_post_summary(campaign, _channel_id, flag) when flag != true do
Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled
for program (flag: #{inspect(flag)})") end to catch nil/false/invalid flag
values before the do_post_summary(campaign, channel_id, true) clause.


def change do
alter table(:programs) do
add :send_delivery_summaries, :boolean, default: false
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce non-null boolean for send_delivery_summaries.

This column should be null: false; otherwise NULL can leak in and break boolean-gated logic at runtime.

Suggested migration change
 alter table(:programs) do
-  add :send_delivery_summaries, :boolean, default: false
+  add :send_delivery_summaries, :boolean, default: false, null: false
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
add :send_delivery_summaries, :boolean, default: false
def change do
alter table(:programs) do
add :send_delivery_summaries, :boolean, default: false, null: false
end
end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs`
at line 6, The migration currently adds the column send_delivery_summaries with
a default but allows NULLs; update the migration's change function so the add
statement for :send_delivery_summaries uses null: false (e.g. add
:send_delivery_summaries, :boolean, default: false, null: false) to enforce a
non-null boolean at the DB level; ensure the migration's down/rollback behavior
(or change callback) remains correct after this change.

Comment on lines +8 to +12
from(prog in BikeBrigade.Delivery.Program,
update: [set: [send_delivery_summaries: true]],
where: not is_nil(prog.slack_channel_id)
)
|> Repo.update_all([])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid app schema modules inside migrations.

Using BikeBrigade.Delivery.Program in a migration can break replay on new environments after future schema changes. Use SQL (or raw table-based query) instead.

Safer migration approach
 def up do
-  from(prog in BikeBrigade.Delivery.Program,
-    update: [set: [send_delivery_summaries: true]],
-    where: not is_nil(prog.slack_channel_id)
-  )
-  |> Repo.update_all([])
+  execute("""
+  UPDATE programs
+  SET send_delivery_summaries = TRUE
+  WHERE slack_channel_id IS NOT NULL
+  """)
 end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from(prog in BikeBrigade.Delivery.Program,
update: [set: [send_delivery_summaries: true]],
where: not is_nil(prog.slack_channel_id)
)
|> Repo.update_all([])
execute("""
UPDATE programs
SET send_delivery_summaries = TRUE
WHERE slack_channel_id IS NOT NULL
""")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs`
around lines 8 - 12, The migration currently references the schema module
BikeBrigade.Delivery.Program which can break replay; change the query to use the
table name string instead (e.g., from(p in "programs", update: [set:
[send_delivery_summaries: true]], where: not is_nil(p.slack_channel_id))) and
keep the Repo.update_all call, so the migration uses raw table-based Ecto
queries rather than the application schema module.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs">

<violation number="1" location="priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs:6">
P1: Add `null: false` to the column definition to enforce the boolean constraint at the database level. Without it, NULL values can leak in and cause `FunctionClauseError` in `do_post_summary/3` which only matches `true` or `false`.</violation>
</file>

<file name="priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs">

<violation number="1" location="priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs:8">
P2: Using application schema modules (`BikeBrigade.Delivery.Program`) in migrations can break replay on new environments if the schema later changes. Use raw SQL via `execute/1` instead to keep the migration self-contained.</violation>
</file>

<file name="lib/bike_brigade/campaign_summary_poster.ex">

<violation number="1" location="lib/bike_brigade/campaign_summary_poster.ex:69">
P1: When `send_delivery_summaries` is `nil` (the column permits NULL since the migration lacks `null: false`), and `slack_channel_id` is present, no function clause matches—resulting in a `FunctionClauseError` at runtime. Use a guard like `when enabled != true` instead of matching on the literal `false` to handle nil defensively.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


def change do
alter table(:programs) do
add :send_delivery_summaries, :boolean, default: false
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Add null: false to the column definition to enforce the boolean constraint at the database level. Without it, NULL values can leak in and cause FunctionClauseError in do_post_summary/3 which only matches true or false.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At priv/repo/migrations/20260512195708_add_send_delivery_summaries_to_programs.exs, line 6:

<comment>Add `null: false` to the column definition to enforce the boolean constraint at the database level. Without it, NULL values can leak in and cause `FunctionClauseError` in `do_post_summary/3` which only matches `true` or `false`.</comment>

<file context>
@@ -0,0 +1,9 @@
+
+  def change do
+    alter table(:programs) do
+      add :send_delivery_summaries, :boolean, default: false
+    end
+  end
</file context>

)
end

defp do_post_summary(campaign, _, false) do
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When send_delivery_summaries is nil (the column permits NULL since the migration lacks null: false), and slack_channel_id is present, no function clause matches—resulting in a FunctionClauseError at runtime. Use a guard like when enabled != true instead of matching on the literal false to handle nil defensively.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/bike_brigade/campaign_summary_poster.ex, line 69:

<comment>When `send_delivery_summaries` is `nil` (the column permits NULL since the migration lacks `null: false`), and `slack_channel_id` is present, no function clause matches—resulting in a `FunctionClauseError` at runtime. Use a guard like `when enabled != true` instead of matching on the literal `false` to handle nil defensively.</comment>

<file context>
@@ -58,15 +58,23 @@ defmodule BikeBrigade.CampaignSummaryPoster do
+    )
+  end
+
+  defp do_post_summary(campaign, _, false) do
+    Logger.info("Skipping campaign #{campaign.id}: delivery summaries not enabled for program")
   end
</file context>
Suggested change
defp do_post_summary(campaign, _, false) do
defp do_post_summary(campaign, _, enabled) when enabled != true do

alias BikeBrigade.Repo

def up do
from(prog in BikeBrigade.Delivery.Program,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Using application schema modules (BikeBrigade.Delivery.Program) in migrations can break replay on new environments if the schema later changes. Use raw SQL via execute/1 instead to keep the migration self-contained.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At priv/repo/migrations/20260519000001_enable_send_delivery_summaries_for_programs_with_slack_channel.exs, line 8:

<comment>Using application schema modules (`BikeBrigade.Delivery.Program`) in migrations can break replay on new environments if the schema later changes. Use raw SQL via `execute/1` instead to keep the migration self-contained.</comment>

<file context>
@@ -0,0 +1,16 @@
+  alias BikeBrigade.Repo
+
+  def up do
+    from(prog in BikeBrigade.Delivery.Program,
+      update: [set: [send_delivery_summaries: true]],
+      where: not is_nil(prog.slack_channel_id)
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant