feat: show processing status for one-off campaigns (#14592)

## Summary

One-off SMS and WhatsApp campaigns now show a `Processing` state while
the audience send is in progress. The campaign moves to `Completed`
after processing finishes, and already-processing campaigns are skipped
by the scheduler to avoid duplicate sends.

## Closes

- [CW-6037: feat: Introduce an in-progress status for
campaigns](https://linear.app/chatwoot/issue/CW-6037/feat-introduce-an-in-progress-status-for-campaigns)

## Screenshot

SMS campaign card showing the new `Processing` status.

<img width="3840" height="2160" alt="framed-campaign-processing-status"
src="https://github.com/user-attachments/assets/de7913b5-65fb-4121-9034-24a568eb0382"
/>

## What changed

- Added `processing` as a campaign status.
- Mark one-off campaigns as `processing` under a row lock before the
send service runs.
- Complete SMS, Twilio SMS, and WhatsApp one-off campaigns after
audience processing finishes.
- Keep campaigns in `processing` if an unexpected service error escapes,
so the scheduler does not automatically resend the audience.
- Added the `Processing` label for SMS and WhatsApp campaign cards.

## Known operational behavior

If a worker is interrupted or an unexpected service error escapes after
a campaign is marked `processing`, the campaign can remain in
`processing`. This is intentional for now to avoid automatic
full-audience resends. Installation admins can decide whether to mark
the campaign completed or restart it manually from the Rails console
after checking what was sent.

## How to test

- Create a one-off SMS or WhatsApp campaign scheduled for now.
- Run the scheduled job or trigger the campaign job.
- Confirm the campaign card shows `Processing` while the audience is
being processed. For small audiences, refresh during processing or use a
larger audience so the state is observable.
- Confirm the campaign moves to `Completed` after audience processing
finishes.
- Confirm an already-processing campaign is not enqueued again by the
scheduled job.
This commit is contained in:
Sojan Jose
2026-06-01 16:47:17 +05:30
committed by GitHub
parent ed3059c1aa
commit f27bbef73b
11 changed files with 132 additions and 14 deletions
+17 -3
View File
@@ -47,7 +47,7 @@ class Campaign < ApplicationRecord
enum campaign_type: { ongoing: 0, one_off: 1 }
# TODO : enabled attribute is unneccessary . lets move that to the campaign status with additional statuses like draft, disabled etc.
enum campaign_status: { active: 0, completed: 1 }
enum campaign_status: { active: 0, completed: 1, processing: 2 }
has_many :conversations, dependent: :nullify, autosave: true
@@ -56,13 +56,27 @@ class Campaign < ApplicationRecord
def trigger!
return unless one_off?
return if completed?
return unless feature_enabled?
return unless mark_processing!
execute_campaign
end
private
def feature_enabled?
inbox.inbox_type != 'Whatsapp' || account.feature_enabled?(:whatsapp_campaign)
end
def mark_processing!
# Multiple scheduler jobs can pick the same active campaign; lock before flipping status to avoid duplicate sends.
with_lock do
next if completed? || processing?
processing!
end
end
def execute_campaign
case inbox.inbox_type
when 'Twilio SMS'
@@ -70,7 +84,7 @@ class Campaign < ApplicationRecord
when 'Sms'
Sms::OneoffSmsCampaignService.new(campaign: self).perform
when 'Whatsapp'
Whatsapp::OneoffCampaignService.new(campaign: self).perform if account.feature_enabled?(:whatsapp_campaign)
Whatsapp::OneoffCampaignService.new(campaign: self).perform
end
end