feat: Ability to toggle conversation continuity via email (#3817)
Fixes: #3368 Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
parent
34e8ad9dc5
commit
59deffc7e3
9 changed files with 113 additions and 50 deletions
|
@ -310,6 +310,10 @@
|
||||||
"ENABLED": "Enabled",
|
"ENABLED": "Enabled",
|
||||||
"DISABLED": "Disabled"
|
"DISABLED": "Disabled"
|
||||||
},
|
},
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL": {
|
||||||
|
"ENABLED": "Enabled",
|
||||||
|
"DISABLED": "Disabled"
|
||||||
|
},
|
||||||
"ENABLE_HMAC": {
|
"ENABLE_HMAC": {
|
||||||
"LABEL": "Enable"
|
"LABEL": "Enable"
|
||||||
}
|
}
|
||||||
|
@ -356,6 +360,8 @@
|
||||||
"AUTO_ASSIGNMENT": "Enable auto assignment",
|
"AUTO_ASSIGNMENT": "Enable auto assignment",
|
||||||
"ENABLE_CSAT": "Enable CSAT",
|
"ENABLE_CSAT": "Enable CSAT",
|
||||||
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
|
"ENABLE_CSAT_SUB_TEXT": "Enable/Disable CSAT(Customer satisfaction) survey after resolving a conversation",
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL": "Enable conversation continuity via email",
|
||||||
|
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Conversations will continue over email if the contact email address is available.",
|
||||||
"INBOX_UPDATE_TITLE": "Inbox Settings",
|
"INBOX_UPDATE_TITLE": "Inbox Settings",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "Update your inbox settings",
|
"INBOX_UPDATE_SUB_TEXT": "Update your inbox settings",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.",
|
"AUTO_ASSIGNMENT_SUB_TEXT": "Enable or disable the automatic assignment of new conversations to the agents added to this inbox.",
|
||||||
|
|
|
@ -215,7 +215,7 @@
|
||||||
</p>
|
</p>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="medium-9 columns">
|
<label v-if="isAWebWidgetInbox" class="medium-9 columns">
|
||||||
{{ $t('INBOX_MGMT.SETTINGS_POPUP.ALLOW_MESSAGES_AFTER_RESOLVED') }}
|
{{ $t('INBOX_MGMT.SETTINGS_POPUP.ALLOW_MESSAGES_AFTER_RESOLVED') }}
|
||||||
<select v-model="allowMessagesAfterResolved">
|
<select v-model="allowMessagesAfterResolved">
|
||||||
<option :value="true">
|
<option :value="true">
|
||||||
|
@ -234,6 +234,25 @@
|
||||||
</p>
|
</p>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label v-if="isAWebWidgetInbox" class="medium-9 columns">
|
||||||
|
{{ $t('INBOX_MGMT.SETTINGS_POPUP.ENABLE_CONTINUITY_VIA_EMAIL') }}
|
||||||
|
<select v-model="continuityViaEmail">
|
||||||
|
<option :value="true">
|
||||||
|
{{ $t('INBOX_MGMT.EDIT.ENABLE_CONTINUITY_VIA_EMAIL.ENABLED') }}
|
||||||
|
</option>
|
||||||
|
<option :value="false">
|
||||||
|
{{ $t('INBOX_MGMT.EDIT.ENABLE_CONTINUITY_VIA_EMAIL.DISABLED') }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p class="help-text">
|
||||||
|
{{
|
||||||
|
$t(
|
||||||
|
'INBOX_MGMT.SETTINGS_POPUP.ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT'
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
|
||||||
<label v-if="isAWebWidgetInbox">
|
<label v-if="isAWebWidgetInbox">
|
||||||
{{ $t('INBOX_MGMT.FEATURES.LABEL') }}
|
{{ $t('INBOX_MGMT.FEATURES.LABEL') }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -440,6 +459,7 @@ export default {
|
||||||
isAgentListUpdating: false,
|
isAgentListUpdating: false,
|
||||||
csatSurveyEnabled: false,
|
csatSurveyEnabled: false,
|
||||||
allowMessagesAfterResolved: true,
|
allowMessagesAfterResolved: true,
|
||||||
|
continuityViaEmail: true,
|
||||||
selectedInboxName: '',
|
selectedInboxName: '',
|
||||||
channelWebsiteUrl: '',
|
channelWebsiteUrl: '',
|
||||||
webhookUrl: '',
|
webhookUrl: '',
|
||||||
|
@ -604,6 +624,7 @@ export default {
|
||||||
this.emailCollectEnabled = this.inbox.enable_email_collect;
|
this.emailCollectEnabled = this.inbox.enable_email_collect;
|
||||||
this.csatSurveyEnabled = this.inbox.csat_survey_enabled;
|
this.csatSurveyEnabled = this.inbox.csat_survey_enabled;
|
||||||
this.allowMessagesAfterResolved = this.inbox.allow_messages_after_resolved;
|
this.allowMessagesAfterResolved = this.inbox.allow_messages_after_resolved;
|
||||||
|
this.continuityViaEmail = this.inbox.continuity_via_email;
|
||||||
this.channelWebsiteUrl = this.inbox.website_url;
|
this.channelWebsiteUrl = this.inbox.website_url;
|
||||||
this.channelWelcomeTitle = this.inbox.welcome_title;
|
this.channelWelcomeTitle = this.inbox.welcome_title;
|
||||||
this.channelWelcomeTagline = this.inbox.welcome_tagline;
|
this.channelWelcomeTagline = this.inbox.welcome_tagline;
|
||||||
|
@ -659,6 +680,7 @@ export default {
|
||||||
reply_time: this.replyTime || 'in_a_few_minutes',
|
reply_time: this.replyTime || 'in_a_few_minutes',
|
||||||
hmac_mandatory: this.hmacMandatory,
|
hmac_mandatory: this.hmacMandatory,
|
||||||
tweets_enabled: this.tweetsEnabled,
|
tweets_enabled: this.tweetsEnabled,
|
||||||
|
continuity_via_email: this.continuityViaEmail,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (this.avatarFile) {
|
if (this.avatarFile) {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
# Table name: channel_web_widgets
|
# Table name: channel_web_widgets
|
||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
|
# continuity_via_email :boolean default(TRUE), not null
|
||||||
# feature_flags :integer default(3), not null
|
# feature_flags :integer default(3), not null
|
||||||
# hmac_mandatory :boolean default(FALSE)
|
# hmac_mandatory :boolean default(FALSE)
|
||||||
# hmac_token :string
|
# hmac_token :string
|
||||||
|
@ -29,7 +30,8 @@ class Channel::WebWidget < ApplicationRecord
|
||||||
include FlagShihTzu
|
include FlagShihTzu
|
||||||
|
|
||||||
self.table_name = 'channel_web_widgets'
|
self.table_name = 'channel_web_widgets'
|
||||||
EDITABLE_ATTRS = [:website_url, :widget_color, :welcome_title, :welcome_tagline, :reply_time, :pre_chat_form_enabled, :hmac_mandatory,
|
EDITABLE_ATTRS = [:website_url, :widget_color, :welcome_title, :welcome_tagline, :reply_time, :pre_chat_form_enabled,
|
||||||
|
:continuity_via_email, :hmac_mandatory,
|
||||||
{ pre_chat_form_options: [:pre_chat_message, :require_email] },
|
{ pre_chat_form_options: [:pre_chat_message, :require_email] },
|
||||||
{ selected_feature_flags: [] }].freeze
|
{ selected_feature_flags: [] }].freeze
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,14 @@ class Message < ApplicationRecord
|
||||||
::MessageTemplates::HookExecutionService.new(message: self).perform
|
::MessageTemplates::HookExecutionService.new(message: self).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_notifiable_webwidget?
|
||||||
|
inbox.web_widget? && inbox.channel.continuity_via_email
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_notifiable_channel?
|
||||||
|
email_notifiable_webwidget? || %w[Email].include?(inbox.inbox_type)
|
||||||
|
end
|
||||||
|
|
||||||
def email_notifiable_message?
|
def email_notifiable_message?
|
||||||
return false unless outgoing? || input_csat?
|
return false unless outgoing? || input_csat?
|
||||||
return false if private?
|
return false if private?
|
||||||
|
@ -194,7 +202,8 @@ class Message < ApplicationRecord
|
||||||
|
|
||||||
def can_notify_via_mail?
|
def can_notify_via_mail?
|
||||||
return unless email_notifiable_message?
|
return unless email_notifiable_message?
|
||||||
return false if conversation.contact.email.blank? || !(%w[Website Email].include? inbox.inbox_type)
|
return unless email_notifiable_channel?
|
||||||
|
return if conversation.contact.email.blank?
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,36 +34,43 @@ if resource.web_widget?
|
||||||
json.hmac_token resource.channel.try(:hmac_token)
|
json.hmac_token resource.channel.try(:hmac_token)
|
||||||
json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled)
|
json.pre_chat_form_enabled resource.channel.try(:pre_chat_form_enabled)
|
||||||
json.pre_chat_form_options resource.channel.try(:pre_chat_form_options)
|
json.pre_chat_form_options resource.channel.try(:pre_chat_form_options)
|
||||||
|
json.continuity_via_email resource.channel.try(:continuity_via_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
## Facebook Attributes
|
## Facebook Attributes
|
||||||
json.page_id resource.channel.try(:page_id)
|
if resource.facebook?
|
||||||
json.reauthorization_required resource.channel.try(:reauthorization_required?) if resource.facebook?
|
json.page_id resource.channel.try(:page_id)
|
||||||
|
json.reauthorization_required resource.channel.try(:reauthorization_required?)
|
||||||
|
end
|
||||||
|
|
||||||
## Twilio Attributes
|
## Twilio Attributes
|
||||||
json.phone_number resource.channel.try(:phone_number)
|
json.phone_number resource.channel.try(:phone_number)
|
||||||
json.medium resource.channel.try(:medium) if resource.twilio?
|
json.medium resource.channel.try(:medium) if resource.twilio?
|
||||||
|
|
||||||
## Email Channel Attributes
|
if resource.email?
|
||||||
json.forward_to_email resource.channel.try(:forward_to_email)
|
## Email Channel Attributes
|
||||||
json.email resource.channel.try(:email) if resource.email?
|
json.forward_to_email resource.channel.try(:forward_to_email)
|
||||||
|
json.email resource.channel.try(:email)
|
||||||
|
|
||||||
## IMAP
|
## IMAP
|
||||||
json.imap_email resource.channel.try(:imap_email) if resource.email?
|
json.imap_email resource.channel.try(:imap_email)
|
||||||
json.imap_password resource.channel.try(:imap_password) if resource.email?
|
json.imap_password resource.channel.try(:imap_password)
|
||||||
json.imap_address resource.channel.try(:imap_address) if resource.email?
|
json.imap_address resource.channel.try(:imap_address)
|
||||||
json.imap_port resource.channel.try(:imap_port) if resource.email?
|
json.imap_port resource.channel.try(:imap_port)
|
||||||
json.imap_enabled resource.channel.try(:imap_enabled) if resource.email?
|
json.imap_enabled resource.channel.try(:imap_enabled)
|
||||||
json.imap_enable_ssl resource.channel.try(:imap_enable_ssl) if resource.email?
|
json.imap_enable_ssl resource.channel.try(:imap_enable_ssl)
|
||||||
|
|
||||||
## SMTP
|
## SMTP
|
||||||
json.smtp_email resource.channel.try(:smtp_email) if resource.email?
|
json.smtp_email resource.channel.try(:smtp_email)
|
||||||
json.smtp_password resource.channel.try(:smtp_password) if resource.email?
|
json.smtp_password resource.channel.try(:smtp_password)
|
||||||
json.smtp_address resource.channel.try(:smtp_address) if resource.email?
|
json.smtp_address resource.channel.try(:smtp_address)
|
||||||
json.smtp_port resource.channel.try(:smtp_port) if resource.email?
|
json.smtp_port resource.channel.try(:smtp_port)
|
||||||
json.smtp_enabled resource.channel.try(:smtp_enabled) if resource.email?
|
json.smtp_enabled resource.channel.try(:smtp_enabled)
|
||||||
json.smtp_domain resource.channel.try(:smtp_domain) if resource.email?
|
json.smtp_domain resource.channel.try(:smtp_domain)
|
||||||
|
end
|
||||||
|
|
||||||
## API Channel Attributes
|
## API Channel Attributes
|
||||||
json.webhook_url resource.channel.try(:webhook_url) if resource.api?
|
if resource.api?
|
||||||
json.inbox_identifier resource.channel.try(:identifier) if resource.api?
|
json.webhook_url resource.channel.try(:webhook_url)
|
||||||
|
json.inbox_identifier resource.channel.try(:identifier)
|
||||||
|
end
|
||||||
|
|
|
@ -305,11 +305,11 @@ Rails.application.routes.draw do
|
||||||
resources :users, only: [:index, :new, :create, :show, :edit, :update]
|
resources :users, only: [:index, :new, :create, :show, :edit, :update]
|
||||||
resources :access_tokens, only: [:index, :show]
|
resources :access_tokens, only: [:index, :show]
|
||||||
resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update]
|
resources :installation_configs, only: [:index, :new, :create, :show, :edit, :update]
|
||||||
|
resources :agent_bots, only: [:index, :new, :create, :show, :edit, :update]
|
||||||
|
resources :platform_apps, only: [:index, :new, :create, :show, :edit, :update]
|
||||||
|
|
||||||
# resources that doesn't appear in primary navigation in super admin
|
# resources that doesn't appear in primary navigation in super admin
|
||||||
resources :account_users, only: [:new, :create, :destroy]
|
resources :account_users, only: [:new, :create, :destroy]
|
||||||
resources :agent_bots, only: [:index, :new, :create, :show, :edit, :update]
|
|
||||||
resources :platform_apps, only: [:index, :new, :create, :show, :edit, :update]
|
|
||||||
end
|
end
|
||||||
authenticated :super_admin do
|
authenticated :super_admin do
|
||||||
mount Sidekiq::Web => '/monitoring/sidekiq'
|
mount Sidekiq::Web => '/monitoring/sidekiq'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class EnableConversationContinuity < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
add_column :channel_web_widgets, :continuity_via_email, :boolean, null: false, default: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -274,6 +274,7 @@ ActiveRecord::Schema.define(version: 2022_01_21_055444) do
|
||||||
t.boolean "pre_chat_form_enabled", default: false
|
t.boolean "pre_chat_form_enabled", default: false
|
||||||
t.jsonb "pre_chat_form_options", default: {}
|
t.jsonb "pre_chat_form_options", default: {}
|
||||||
t.boolean "hmac_mandatory", default: false
|
t.boolean "hmac_mandatory", default: false
|
||||||
|
t.boolean "continuity_via_email", default: true, null: false
|
||||||
t.index ["hmac_token"], name: "index_channel_web_widgets_on_hmac_token", unique: true
|
t.index ["hmac_token"], name: "index_channel_web_widgets_on_hmac_token", unique: true
|
||||||
t.index ["website_token"], name: "index_channel_web_widgets_on_website_token", unique: true
|
t.index ["website_token"], name: "index_channel_web_widgets_on_website_token", unique: true
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,33 +56,44 @@ RSpec.describe Message, type: :model do
|
||||||
expect(hook_execution_service).to have_received(:perform)
|
expect(hook_execution_service).to have_received(:perform)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calls notify email method on after save for outgoing messages' do
|
context 'with conversation continuity' do
|
||||||
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
it 'calls notify email method on after save for outgoing messages in website channel' do
|
||||||
message.message_type = 'outgoing'
|
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
||||||
message.save!
|
message.message_type = 'outgoing'
|
||||||
expect(ConversationReplyEmailWorker).to have_received(:perform_in)
|
message.save!
|
||||||
end
|
expect(ConversationReplyEmailWorker).to have_received(:perform_in)
|
||||||
|
end
|
||||||
|
|
||||||
it 'wont call notify email method for private notes' do
|
it 'does not call notify email for website channel if continuity is disabled' do
|
||||||
message.private = true
|
message.inbox = create(:inbox, account: message.account,
|
||||||
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
channel: build(:channel_widget, account: message.account, continuity_via_email: false))
|
||||||
message.save!
|
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
||||||
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
|
message.message_type = 'outgoing'
|
||||||
end
|
message.save!
|
||||||
|
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
|
||||||
|
end
|
||||||
|
|
||||||
it 'calls EmailReply worker if the channel is email' do
|
it 'wont call notify email method for private notes' do
|
||||||
message.inbox = create(:inbox, account: message.account, channel: build(:channel_email, account: message.account))
|
message.private = true
|
||||||
allow(EmailReplyWorker).to receive(:perform_in).and_return(true)
|
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
||||||
message.message_type = 'outgoing'
|
message.save!
|
||||||
message.save!
|
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
|
||||||
expect(EmailReplyWorker).to have_received(:perform_in).with(1.second, message.id)
|
end
|
||||||
end
|
|
||||||
|
|
||||||
it 'wont call notify email method unless its website or email channel' do
|
it 'calls EmailReply worker if the channel is email' do
|
||||||
message.inbox = create(:inbox, account: message.account, channel: build(:channel_api, account: message.account))
|
message.inbox = create(:inbox, account: message.account, channel: build(:channel_email, account: message.account))
|
||||||
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
allow(EmailReplyWorker).to receive(:perform_in).and_return(true)
|
||||||
message.save!
|
message.message_type = 'outgoing'
|
||||||
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
|
message.save!
|
||||||
|
expect(EmailReplyWorker).to have_received(:perform_in).with(1.second, message.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'wont call notify email method unless its website or email channel' do
|
||||||
|
message.inbox = create(:inbox, account: message.account, channel: build(:channel_api, account: message.account))
|
||||||
|
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
|
||||||
|
message.save!
|
||||||
|
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue