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:
Sojan Jose 2022-01-26 15:59:48 -08:00 committed by GitHub
parent 34e8ad9dc5
commit 59deffc7e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 50 deletions

View file

@ -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.",

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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