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:
+17
-3
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user