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,13 +56,23 @@ 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
it 'calls notify email method on after save for outgoing messages in website channel' do
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true) allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
message.message_type = 'outgoing' message.message_type = 'outgoing'
message.save! message.save!
expect(ConversationReplyEmailWorker).to have_received(:perform_in) expect(ConversationReplyEmailWorker).to have_received(:perform_in)
end end
it 'does not call notify email for website channel if continuity is disabled' do
message.inbox = create(:inbox, account: message.account,
channel: build(:channel_widget, account: message.account, continuity_via_email: false))
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
message.message_type = 'outgoing'
message.save!
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
end
it 'wont call notify email method for private notes' do it 'wont call notify email method for private notes' do
message.private = true message.private = true
allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true) allow(ConversationReplyEmailWorker).to receive(:perform_in).and_return(true)
@ -85,6 +95,7 @@ RSpec.describe Message, type: :model do
expect(ConversationReplyEmailWorker).not_to have_received(:perform_in) expect(ConversationReplyEmailWorker).not_to have_received(:perform_in)
end end
end end
end
context 'when content_type is blank' do context 'when content_type is blank' do
let(:message) { build(:message, content_type: nil, account: create(:account)) } let(:message) { build(:message, content_type: nil, account: create(:account)) }