Compare commits

...
10 changed files with 83 additions and 8 deletions
@@ -29,6 +29,6 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController
def campaign_params
params.require(:campaign).permit(:title, :description, :message, :enabled, :trigger_only_during_business_hours, :inbox_id, :sender_id,
:scheduled_at, audience: [:type, :id], trigger_rules: {}, template_params: {})
:scheduled_at, :allow_bots, audience: [:type, :id], trigger_rules: {}, template_params: {})
end
end
@@ -47,6 +47,7 @@ const initialState = {
senderId: 0,
enabled: true,
triggerOnlyDuringBusinessHours: false,
allowBots: false,
endPoint: '',
timeOnPage: 10,
};
@@ -139,6 +140,7 @@ const prepareCampaignDetails = () => ({
sender_id: state.senderId || null,
enabled: state.enabled,
trigger_only_during_business_hours: state.triggerOnlyDuringBusinessHours,
allow_bots: state.allowBots,
trigger_rules: {
url: state.endPoint,
time_on_page: state.timeOnPage,
@@ -166,6 +168,7 @@ const updateStateFromCampaign = campaign => {
sender,
enabled,
trigger_only_during_business_hours: triggerOnlyDuringBusinessHours,
allow_bots: allowBots,
trigger_rules: { url: endPoint, time_on_page: timeOnPage },
} = campaign;
@@ -176,6 +179,7 @@ const updateStateFromCampaign = campaign => {
senderId: sender?.id ?? 0,
enabled,
triggerOnlyDuringBusinessHours,
allowBots,
endPoint,
timeOnPage,
});
@@ -295,6 +299,13 @@ defineExpose({ prepareCampaignDetails, isSubmitDisabled });
}}
</span>
</label>
<label class="flex items-center gap-2">
<input v-model="state.allowBots" type="checkbox" />
<span class="text-sm font-medium text-n-slate-12">
{{ t('CAMPAIGN.LIVE_CHAT.CREATE.FORM.OTHER_PREFERENCES.ALLOW_BOTS') }}
</span>
</label>
</fieldset>
<div
@@ -57,7 +57,8 @@
"OTHER_PREFERENCES": {
"TITLE": "Other preferences",
"ENABLED": "Enable campaign",
"TRIGGER_ONLY_BUSINESS_HOURS": "Trigger only during business hours"
"TRIGGER_ONLY_BUSINESS_HOURS": "Trigger only during business hours",
"ALLOW_BOTS": "Allow bots to handle conversations"
},
"BUTTONS": {
"CREATE": "Create",
+1
View File
@@ -3,6 +3,7 @@
# Table name: campaigns
#
# id :bigint not null, primary key
# allow_bots :boolean default(FALSE), not null
# audience :jsonb
# campaign_status :integer default("active"), not null
# campaign_type :integer default("ongoing"), not null
+7 -3
View File
@@ -228,9 +228,13 @@ class Conversation < ApplicationRecord
def determine_conversation_status
self.status = :resolved and return if contact.blocked?
# Message template hooks aren't executed for conversations from campaigns
# So making these conversations open for agent visibility
return if campaign.present?
# Handle campaign conversations
if campaign.present?
# If campaign allows bots and inbox has an active bot, start as pending
# Otherwise, start as open (default) for immediate agent visibility
self.status = :pending if campaign.allow_bots && inbox.active_bot?
return
end
# TODO: make this an inbox config instead of assuming bot conversations should start as pending
self.status = :pending if inbox.active_bot?
@@ -19,5 +19,6 @@ if resource.campaign_type == 'one_off'
end
json.trigger_rules resource.trigger_rules
json.trigger_only_during_business_hours resource.trigger_only_during_business_hours
json.allow_bots resource.allow_bots
json.created_at resource.created_at
json.updated_at resource.updated_at
@@ -0,0 +1,5 @@
class AddAllowBotsToCampaigns < ActiveRecord::Migration[7.1]
def change
add_column :campaigns, :allow_bots, :boolean, default: false, null: false
end
end
+2 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2025_10_22_152158) do
ActiveRecord::Schema[7.1].define(version: 2025_11_05_114654) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -278,6 +278,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_10_22_152158) do
t.datetime "scheduled_at", precision: nil
t.boolean "trigger_only_during_business_hours", default: false
t.jsonb "template_params"
t.boolean "allow_bots", default: false, null: false
t.index ["account_id"], name: "index_campaigns_on_account_id"
t.index ["campaign_status"], name: "index_campaigns_on_campaign_status"
t.index ["campaign_type"], name: "index_campaigns_on_campaign_type"
@@ -108,6 +108,29 @@ RSpec.describe 'Campaigns API', type: :request do
expect(JSON.parse(response.body, symbolize_names: true)[:title]).to eq('test')
end
it 'creates a new campaign with allow_bots enabled' do
post "/api/v1/accounts/#{account.id}/campaigns",
params: { inbox_id: inbox.id, title: 'test', message: 'test message', allow_bots: true },
headers: administrator.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
response_data = JSON.parse(response.body, symbolize_names: true)
expect(response_data[:title]).to eq('test')
expect(response_data[:allow_bots]).to be true
end
it 'creates a new campaign with allow_bots disabled by default' do
post "/api/v1/accounts/#{account.id}/campaigns",
params: { inbox_id: inbox.id, title: 'test', message: 'test message' },
headers: administrator.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
response_data = JSON.parse(response.body, symbolize_names: true)
expect(response_data[:allow_bots]).to be false
end
it 'creates a new ongoing campaign' do
post "/api/v1/accounts/#{account.id}/campaigns",
params: { inbox_id: inbox.id, title: 'test', message: 'test message', trigger_rules: { url: 'https://test.com' } },
@@ -189,6 +212,16 @@ RSpec.describe 'Campaigns API', type: :request do
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body, symbolize_names: true)[:title]).to eq('test')
end
it 'updates the campaign allow_bots setting' do
patch "/api/v1/accounts/#{account.id}/campaigns/#{campaign.display_id}",
params: { allow_bots: true },
headers: administrator.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(JSON.parse(response.body, symbolize_names: true)[:allow_bots]).to be true
end
end
end
+20 -2
View File
@@ -576,10 +576,28 @@ RSpec.describe Conversation do
expect(conversation.status).to eq('pending')
end
it 'returns conversation as open if campaign is present' do
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: create(:campaign))
it 'returns conversation as open if campaign is present without allow_bots' do
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: create(:campaign, allow_bots: false))
expect(conversation.status).to eq('open')
end
it 'returns conversation as pending if campaign has allow_bots enabled' do
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: create(:campaign, allow_bots: true))
expect(conversation.status).to eq('pending')
end
it 'returns conversation as open if campaign has allow_bots but inbox has no bot' do
regular_inbox = create(:inbox, account: bot_inbox.inbox.account)
conversation = create(:conversation, inbox: regular_inbox, campaign: create(:campaign, allow_bots: true))
expect(conversation.status).to eq('open')
end
it 'does not start as pending when allow_bots is explicitly disabled even with bot inbox' do
campaign = create(:campaign, allow_bots: false)
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: campaign)
expect(conversation.status).to eq('open')
expect(conversation.status).not_to eq('pending')
end
end
describe '#botintegration: when conversation created in inbox with dialogflow integration' do