feat: Ability to lock to single conversation (#5881)

Adds the ability to lock conversation to a single thread for Whatsapp and Sms Inboxes when using outbound messages.

demo: https://www.loom.com/share/c9e1e563c8914837a4139dfdd2503fef

fixes: #4975

Co-authored-by: Nithin David <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
Sojan Jose
2022-11-25 13:01:04 +03:00
committed by GitHub
parent 8813c77907
commit b05d06a28a
13 changed files with 171 additions and 47 deletions

View File

@@ -0,0 +1,40 @@
class ConversationBuilder
pattr_initialize [:params!, :contact_inbox!]
def perform
look_up_exising_conversation || create_new_conversation
end
private
def look_up_exising_conversation
return unless @contact_inbox.inbox.lock_to_single_conversation?
@contact_inbox.conversations.last
end
def create_new_conversation
::Conversation.create!(conversation_params)
end
def conversation_params
additional_attributes = params[:additional_attributes]&.permit! || {}
custom_attributes = params[:custom_attributes]&.permit! || {}
status = params[:status].present? ? { status: params[:status] } : {}
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
# commenting this out to see if there are any errors, if not we can remove this in subsequent releases
# status = { status: 'pending' } if status[:status] == 'bot'
{
account_id: @contact_inbox.inbox.account_id,
inbox_id: @contact_inbox.inbox_id,
contact_id: @contact_inbox.contact_id,
contact_inbox_id: @contact_inbox.id,
additional_attributes: additional_attributes,
custom_attributes: custom_attributes,
snoozed_until: params[:snoozed_until],
assignee_id: params[:assignee_id],
team_id: params[:team_id]
}.merge(status)
end
end

View File

@@ -24,7 +24,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
def create
ActiveRecord::Base.transaction do
@conversation = ::Conversation.create!(conversation_params)
@conversation = ConversationBuilder.new(params: params, contact_inbox: @contact_inbox).perform
Messages::MessageBuilder.new(Current.user, @conversation, params[:message]).perform if params[:message].present?
end
end
@@ -99,8 +99,10 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def set_conversation_status
status = params[:status] == 'bot' ? 'pending' : params[:status]
@conversation.status = status
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
# commenting this out to see if there are any errors, if not we can remove this in subsequent releases
# status = params[:status] == 'bot' ? 'pending' : params[:status]
@conversation.status = params[:status]
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
end
@@ -152,26 +154,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
).perform
end
def conversation_params
additional_attributes = params[:additional_attributes]&.permit! || {}
custom_attributes = params[:custom_attributes]&.permit! || {}
status = params[:status].present? ? { status: params[:status] } : {}
# TODO: temporary fallback for the old bot status in conversation, we will remove after couple of releases
status = { status: 'pending' } if status[:status] == 'bot'
{
account_id: Current.account.id,
inbox_id: @contact_inbox.inbox_id,
contact_id: @contact_inbox.contact_id,
contact_inbox_id: @contact_inbox.id,
additional_attributes: additional_attributes,
custom_attributes: custom_attributes,
snoozed_until: params[:snoozed_until],
assignee_id: params[:assignee_id],
team_id: params[:team_id]
}.merge(status)
end
def conversation_finder
@conversation_finder ||= ConversationFinder.new(Current.user, params)
end

View File

@@ -113,7 +113,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def inbox_attributes
[:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved]
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone, :allow_messages_after_resolved,
:lock_to_single_conversation]
end
def permitted_params(channel_attributes = [])

View File

@@ -388,6 +388,10 @@
"ENABLED": "Enabled",
"DISABLED": "Disabled"
},
"LOCK_TO_SINGLE_CONVERSATION": {
"ENABLED": "Enabled",
"DISABLED": "Disabled"
},
"ENABLE_HMAC": {
"LABEL": "Enable"
}
@@ -441,6 +445,8 @@
"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.",
"LOCK_TO_SINGLE_CONVERSATION": "Lock to single conversation",
"LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT": "Enable or disable multiple conversations for the same contact in this inbox",
"INBOX_UPDATE_TITLE": "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.",

View File

@@ -258,6 +258,28 @@
</p>
</label>
<label
v-if="canLocktoSingleConversation"
class="medium-9 columns settings-item"
>
{{ $t('INBOX_MGMT.SETTINGS_POPUP.LOCK_TO_SINGLE_CONVERSATION') }}
<select v-model="locktoSingleConversation">
<option :value="true">
{{ $t('INBOX_MGMT.EDIT.LOCK_TO_SINGLE_CONVERSATION.ENABLED') }}
</option>
<option :value="false">
{{ $t('INBOX_MGMT.EDIT.LOCK_TO_SINGLE_CONVERSATION.DISABLED') }}
</option>
</select>
<p class="help-text">
{{
$t(
'INBOX_MGMT.SETTINGS_POPUP.LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT'
)
}}
</p>
</label>
<label v-if="isAWebWidgetInbox">
{{ $t('INBOX_MGMT.FEATURES.LABEL') }}
</label>
@@ -380,6 +402,7 @@ export default {
greetingMessage: '',
emailCollectEnabled: false,
csatSurveyEnabled: false,
locktoSingleConversation: false,
allowMessagesAfterResolved: true,
continuityViaEmail: true,
selectedInboxName: '',
@@ -496,6 +519,9 @@ export default {
}
return this.inbox.name;
},
canLocktoSingleConversation() {
return this.isASmsInbox || this.isAWhatsAppChannel;
},
inboxNameLabel() {
if (this.isAWebWidgetInbox) {
return this.$t('INBOX_MGMT.ADD.WEBSITE_NAME.LABEL');
@@ -567,6 +593,7 @@ export default {
this.channelWelcomeTagline = this.inbox.welcome_tagline;
this.selectedFeatureFlags = this.inbox.selected_feature_flags || [];
this.replyTime = this.inbox.reply_time;
this.locktoSingleConversation = this.inbox.lock_to_single_conversation;
});
},
async updateInbox() {
@@ -579,6 +606,7 @@ export default {
allow_messages_after_resolved: this.allowMessagesAfterResolved,
greeting_enabled: this.greetingEnabled,
greeting_message: this.greetingMessage || '',
lock_to_single_conversation: this.locktoSingleConversation,
channel: {
widget_color: this.inbox.widget_color,
website_url: this.channelWebsiteUrl,

View File

@@ -89,7 +89,19 @@ export const mutations = {
},
[types.default.ADD_CONTACT_CONVERSATION]: ($state, { id, data }) => {
const conversations = $state.records[id] || [];
Vue.set($state.records, id, [...conversations, data]);
const updatedConversations = [...conversations];
const index = conversations.findIndex(
conversation => conversation.id === data.id
);
if (index !== -1) {
updatedConversations[index] = { ...conversations[index], ...data };
} else {
updatedConversations.push(data);
}
Vue.set($state.records, id, updatedConversations);
},
[types.default.DELETE_CONTACT_CONVERSATION]: ($state, id) => {
Vue.delete($state.records, id);

View File

@@ -14,6 +14,7 @@
# enable_email_collect :boolean default(TRUE)
# greeting_enabled :boolean default(FALSE)
# greeting_message :string
# lock_to_single_conversation :boolean default(FALSE), not null
# name :string not null
# out_of_office_message :string
# timezone :string default("UTC")

View File

@@ -15,12 +15,13 @@ json.working_hours resource.weekly_schedule
json.timezone resource.timezone
json.callback_webhook_url resource.callback_webhook_url
json.allow_messages_after_resolved resource.allow_messages_after_resolved
json.tweets_enabled resource.channel.try(:tweets_enabled) if resource.twitter?
json.lock_to_single_conversation resource.lock_to_single_conversation
## Channel specific settings
## TODO : Clean up and move the attributes into channel sub section
json.tweets_enabled resource.channel.try(:tweets_enabled) if resource.twitter?
## WebWidget Attributes
json.widget_color resource.channel.try(:widget_color)
json.website_url resource.channel.try(:website_url)