feat: add activity message for priority change (#6933)
* feat: add priority const * feat: add toggle priority method * feat: update controller route and specs * refactor: status change method * refactor: abstract label change and mute activity * feat: add priority change_activity * fix: interpolation for previous_changes * refactor: reduce cognitive complexity of priority_change_activity * refactor: move priority activity message handler to a separate module * refactor: move typing logic to a service * refactor: tests to reduce complexity * fix: typo * fix: constants * fix: priority conditions * fix: add a response * fix: argument destructuring in I18n.t --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
parent
6b2736aa63
commit
a34729c153
@ -64,13 +64,14 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
|
assign_conversation if @conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_typing_status
|
def toggle_priority
|
||||||
case params[:typing_status]
|
@conversation.toggle_priority(params[:priority])
|
||||||
when 'on'
|
head :ok
|
||||||
trigger_typing_event(CONVERSATION_TYPING_ON, params[:is_private])
|
|
||||||
when 'off'
|
|
||||||
trigger_typing_event(CONVERSATION_TYPING_OFF, params[:is_private])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def toggle_typing_status
|
||||||
|
typing_status_manager = ::Conversations::TypingStatusManager.new(@conversation, current_user, params)
|
||||||
|
typing_status_manager.toggle_typing_status
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -111,11 +112,6 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
@conversation.update_assignee(@agent)
|
@conversation.update_assignee(@agent)
|
||||||
end
|
end
|
||||||
|
|
||||||
def trigger_typing_event(event, is_private)
|
|
||||||
user = current_user.presence || @resource
|
|
||||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: is_private)
|
|
||||||
end
|
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
@conversation ||= Current.account.conversations.find_by!(display_id: params[:id])
|
@conversation ||= Current.account.conversations.find_by!(display_id: params[:id])
|
||||||
authorize @conversation.inbox, :show?
|
authorize @conversation.inbox, :show?
|
||||||
|
|||||||
@ -52,6 +52,12 @@ class ConversationApi extends ApiClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
togglePriority({ conversationId, priority }) {
|
||||||
|
return axios.post(`${this.url}/${conversationId}/toggle_priority`, {
|
||||||
|
priority,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
assignAgent({ conversationId, agentId }) {
|
assignAgent({ conversationId, agentId }) {
|
||||||
return axios.post(
|
return axios.post(
|
||||||
`${this.url}/${conversationId}/assignments?assignee_id=${agentId}`,
|
`${this.url}/${conversationId}/assignments?assignee_id=${agentId}`,
|
||||||
|
|||||||
@ -19,6 +19,14 @@ export const CONVERSATION_STATUS = {
|
|||||||
PENDING: 'pending',
|
PENDING: 'pending',
|
||||||
SNOOZED: 'snoozed',
|
SNOOZED: 'snoozed',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CONVERSATION_PRIORITY = {
|
||||||
|
URGENT: 'urgent',
|
||||||
|
HIGH: 'high',
|
||||||
|
LOW: 'low',
|
||||||
|
MEDIUM: 'medium',
|
||||||
|
};
|
||||||
|
|
||||||
// Size in mega bytes
|
// Size in mega bytes
|
||||||
export const MAXIMUM_FILE_UPLOAD_SIZE = 40;
|
export const MAXIMUM_FILE_UPLOAD_SIZE = 40;
|
||||||
export const MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL = 5;
|
export const MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL = 5;
|
||||||
|
|||||||
@ -1,80 +1,77 @@
|
|||||||
module ActivityMessageHandler
|
module ActivityMessageHandler
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
include PriorityActivityMessageHandler
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def create_activity
|
def create_activity
|
||||||
user_name = Current.user.name if Current.user.present?
|
user_name = Current.user.name if Current.user.present?
|
||||||
status_change_activity(user_name) if saved_change_to_status?
|
status_change_activity(user_name) if saved_change_to_status?
|
||||||
|
priority_change_activity(user_name) if saved_change_to_priority?
|
||||||
create_label_change(activity_message_ownner(user_name)) if saved_change_to_label_list?
|
create_label_change(activity_message_ownner(user_name)) if saved_change_to_label_list?
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_change_activity(user_name)
|
def status_change_activity(user_name)
|
||||||
return send_automation_activity if Current.executed_by.present?
|
content = if Current.executed_by.present?
|
||||||
|
automation_status_change_activity_content
|
||||||
create_status_change_message(user_name)
|
else
|
||||||
|
user_status_change_activity_content(user_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity_message_params(content)
|
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||||
{ account_id: account_id, inbox_id: inbox_id, message_type: :activity, content: content }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_status_change_message(user_name)
|
def user_status_change_activity_content(user_name)
|
||||||
content = if user_name
|
if user_name
|
||||||
I18n.t("conversations.activity.status.#{status}", user_name: user_name)
|
I18n.t("conversations.activity.status.#{status}", user_name: user_name)
|
||||||
elsif Current.contact.present? && resolved?
|
elsif Current.contact.present? && resolved?
|
||||||
I18n.t('conversations.activity.status.contact_resolved', contact_name: Current.contact.name.capitalize)
|
I18n.t('conversations.activity.status.contact_resolved', contact_name: Current.contact.name.capitalize)
|
||||||
elsif resolved?
|
elsif resolved?
|
||||||
I18n.t('conversations.activity.status.auto_resolved', duration: auto_resolve_duration)
|
I18n.t('conversations.activity.status.auto_resolved', duration: auto_resolve_duration)
|
||||||
end
|
end
|
||||||
|
|
||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_automation_activity
|
def automation_status_change_activity_content
|
||||||
content = if Current.executed_by.instance_of?(AutomationRule)
|
if Current.executed_by.instance_of?(AutomationRule)
|
||||||
I18n.t("conversations.activity.status.#{status}", user_name: 'Automation System')
|
I18n.t("conversations.activity.status.#{status}", user_name: 'Automation System')
|
||||||
elsif Current.executed_by.instance_of?(Contact)
|
elsif Current.executed_by.instance_of?(Contact)
|
||||||
Current.executed_by = nil
|
Current.executed_by = nil
|
||||||
I18n.t('conversations.activity.status.system_auto_open')
|
I18n.t('conversations.activity.status.system_auto_open')
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
def activity_message_params(content)
|
||||||
|
{ account_id: account_id, inbox_id: inbox_id, message_type: :activity, content: content }
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_label_added(user_name, labels = [])
|
def create_label_added(user_name, labels = [])
|
||||||
return unless labels.size.positive?
|
create_label_change_activity('added', user_name, labels)
|
||||||
|
|
||||||
params = { user_name: user_name, labels: labels.join(', ') }
|
|
||||||
content = I18n.t('conversations.activity.labels.added', **params)
|
|
||||||
|
|
||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_label_removed(user_name, labels = [])
|
def create_label_removed(user_name, labels = [])
|
||||||
|
create_label_change_activity('removed', user_name, labels)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_label_change_activity(change_type, user_name, labels = [])
|
||||||
return unless labels.size.positive?
|
return unless labels.size.positive?
|
||||||
|
|
||||||
params = { user_name: user_name, labels: labels.join(', ') }
|
content = I18n.t("conversations.activity.labels.#{change_type}", user_name: user_name, labels: labels.join(', '))
|
||||||
content = I18n.t('conversations.activity.labels.removed', **params)
|
|
||||||
|
|
||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_muted_message
|
def create_muted_message
|
||||||
return unless Current.user
|
create_mute_change_activity('muted')
|
||||||
|
|
||||||
params = { user_name: Current.user.name }
|
|
||||||
content = I18n.t('conversations.activity.muted', **params)
|
|
||||||
|
|
||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_unmuted_message
|
def create_unmuted_message
|
||||||
|
create_mute_change_activity('unmuted')
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_mute_change_activity(change_type)
|
||||||
return unless Current.user
|
return unless Current.user
|
||||||
|
|
||||||
params = { user_name: Current.user.name }
|
content = I18n.t("conversations.activity.#{change_type}", user_name: Current.user.name)
|
||||||
content = I18n.t('conversations.activity.unmuted', **params)
|
|
||||||
|
|
||||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
33
app/models/concerns/priority_activity_message_handler.rb
Normal file
33
app/models/concerns/priority_activity_message_handler.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
module PriorityActivityMessageHandler
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def priority_change_activity(user_name)
|
||||||
|
old_priority, new_priority = previous_changes.values_at('priority')[0]
|
||||||
|
return unless priority_change?(old_priority, new_priority)
|
||||||
|
|
||||||
|
user = Current.executed_by.instance_of?(AutomationRule) ? 'Automation System' : user_name
|
||||||
|
content = build_priority_change_content(user, old_priority, new_priority)
|
||||||
|
|
||||||
|
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||||
|
end
|
||||||
|
|
||||||
|
def priority_change?(old_priority, new_priority)
|
||||||
|
old_priority.present? || new_priority.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_priority_change_content(user_name, old_priority = nil, new_priority = nil)
|
||||||
|
change_type = get_priority_change_type(old_priority, new_priority)
|
||||||
|
|
||||||
|
I18n.t("conversations.activity.priority.#{change_type}", user_name: user_name, new_priority: new_priority, old_priority: old_priority)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_priority_change_type(old_priority, new_priority)
|
||||||
|
case [old_priority.present?, new_priority.present?]
|
||||||
|
when [true, true] then 'updated'
|
||||||
|
when [false, true] then 'added'
|
||||||
|
when [true, false] then 'removed'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -149,6 +149,11 @@ class Conversation < ApplicationRecord
|
|||||||
save
|
save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def toggle_priority(priority = nil)
|
||||||
|
self.priority = priority.presence
|
||||||
|
save
|
||||||
|
end
|
||||||
|
|
||||||
def bot_handoff!
|
def bot_handoff!
|
||||||
open!
|
open!
|
||||||
dispatcher_dispatch(CONVERSATION_BOT_HANDOFF)
|
dispatcher_dispatch(CONVERSATION_BOT_HANDOFF)
|
||||||
|
|||||||
26
app/services/conversations/typing_status_manager.rb
Normal file
26
app/services/conversations/typing_status_manager.rb
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
class Conversations::TypingStatusManager
|
||||||
|
include Events::Types
|
||||||
|
|
||||||
|
attr_reader :conversation, :user, :params
|
||||||
|
|
||||||
|
def initialize(conversation, user, params)
|
||||||
|
@conversation = conversation
|
||||||
|
@user = user
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def trigger_typing_event(event, is_private)
|
||||||
|
user = @user.presence || @resource
|
||||||
|
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: is_private)
|
||||||
|
end
|
||||||
|
|
||||||
|
def toggle_typing_status
|
||||||
|
case params[:typing_status]
|
||||||
|
when 'on'
|
||||||
|
trigger_typing_event(CONVERSATION_TYPING_ON, params[:is_private])
|
||||||
|
when 'off'
|
||||||
|
trigger_typing_event(CONVERSATION_TYPING_OFF, params[:is_private])
|
||||||
|
end
|
||||||
|
# Return the head :ok response from the controller
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -129,6 +129,10 @@ en:
|
|||||||
snoozed: "Conversation was snoozed by %{user_name}"
|
snoozed: "Conversation was snoozed by %{user_name}"
|
||||||
auto_resolved: "Conversation was marked resolved by system due to %{duration} days of inactivity"
|
auto_resolved: "Conversation was marked resolved by system due to %{duration} days of inactivity"
|
||||||
system_auto_open: System reopened the conversation due to a new incoming message.
|
system_auto_open: System reopened the conversation due to a new incoming message.
|
||||||
|
priority:
|
||||||
|
added: '%{user_name} set the priority to %{new_priority}'
|
||||||
|
updated: '%{user_name} changed the priority from %{old_priority} to %{new_priority}'
|
||||||
|
removed: '%{user_name} removed the priority'
|
||||||
assignee:
|
assignee:
|
||||||
self_assigned: "%{user_name} self-assigned this conversation"
|
self_assigned: "%{user_name} self-assigned this conversation"
|
||||||
assigned: "Assigned to %{assignee_name} by %{user_name}"
|
assigned: "Assigned to %{assignee_name} by %{user_name}"
|
||||||
|
|||||||
@ -91,6 +91,7 @@ Rails.application.routes.draw do
|
|||||||
post :unmute
|
post :unmute
|
||||||
post :transcript
|
post :transcript
|
||||||
post :toggle_status
|
post :toggle_status
|
||||||
|
post :toggle_priority
|
||||||
post :toggle_typing_status
|
post :toggle_typing_status
|
||||||
post :update_last_seen
|
post :update_last_seen
|
||||||
post :unread
|
post :unread
|
||||||
|
|||||||
@ -434,6 +434,52 @@ RSpec.describe 'Conversations API', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_priority' do
|
||||||
|
let(:conversation) { create(:conversation, account: account) }
|
||||||
|
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:inbox_member, user: agent, inbox: conversation.inbox)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'toggles the conversation priority to nil if no value is passed' do
|
||||||
|
expect(conversation.priority).to be_nil
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
params: { priority: 'low' },
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(conversation.reload.priority).to eq('low')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'toggles the conversation priority' do
|
||||||
|
conversation.priority = 'low'
|
||||||
|
conversation.save!
|
||||||
|
expect(conversation.reload.priority).to eq('low')
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_priority",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(conversation.reload.priority).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_typing_status' do
|
describe 'POST /api/v1/accounts/{account.id}/conversations/:id/toggle_typing_status' do
|
||||||
let(:conversation) { create(:conversation, account: account) }
|
let(:conversation) { create(:conversation, account: account) }
|
||||||
|
|
||||||
|
|||||||
@ -292,6 +292,47 @@ RSpec.describe Conversation, type: :model do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#toggle_priority' do
|
||||||
|
it 'defaults priority to nil when created' do
|
||||||
|
conversation = create(:conversation, status: 'open')
|
||||||
|
expect(conversation.priority).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'toggles the priority to nil if nothing is passed' do
|
||||||
|
conversation = create(:conversation, status: 'open', priority: 'high')
|
||||||
|
expect(conversation.toggle_priority).to be(true)
|
||||||
|
expect(conversation.reload.priority).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the priority to low' do
|
||||||
|
conversation = create(:conversation, status: 'open')
|
||||||
|
|
||||||
|
expect(conversation.toggle_priority('low')).to be(true)
|
||||||
|
expect(conversation.reload.priority).to eq('low')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the priority to medium' do
|
||||||
|
conversation = create(:conversation, status: 'open')
|
||||||
|
|
||||||
|
expect(conversation.toggle_priority('medium')).to be(true)
|
||||||
|
expect(conversation.reload.priority).to eq('medium')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the priority to high' do
|
||||||
|
conversation = create(:conversation, status: 'open')
|
||||||
|
|
||||||
|
expect(conversation.toggle_priority('high')).to be(true)
|
||||||
|
expect(conversation.reload.priority).to eq('high')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the priority to urgent' do
|
||||||
|
conversation = create(:conversation, status: 'open')
|
||||||
|
|
||||||
|
expect(conversation.toggle_priority('urgent')).to be(true)
|
||||||
|
expect(conversation.reload.priority).to eq('urgent')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#ensure_snooze_until_reset' do
|
describe '#ensure_snooze_until_reset' do
|
||||||
it 'resets the snoozed_until when status is toggled' do
|
it 'resets the snoozed_until when status is toggled' do
|
||||||
conversation = create(:conversation, status: 'snoozed', snoozed_until: 2.days.from_now)
|
conversation = create(:conversation, status: 'snoozed', snoozed_until: 2.days.from_now)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user