Chore: Events Cleanup (#739)

* Chore: Events Cleanup

- fix failing tests
- add additional webhook events
- clean up event logic

* Fix conversation status update in action cable

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
Sojan Jose 2020-04-18 20:25:58 +05:30 committed by GitHub
parent ecccb103a0
commit c0ce70e87b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 150 additions and 69 deletions

View file

@ -100,7 +100,7 @@ group :development, :test do
gem 'factory_bot_rails'
gem 'faker'
gem 'listen'
gem 'mock_redis'
gem 'mock_redis', git: 'https://github.com/sds/mock_redis', ref: '16d00789f0341a3aac35126c0ffe97a596753ff9'
gem 'pry-rails'
gem 'rspec-rails', '~> 4.0.0.beta2'
gem 'rubocop', require: false

View file

@ -5,6 +5,13 @@ GIT
twitty (0.1.0)
oauth
GIT
remote: https://github.com/sds/mock_redis
revision: 16d00789f0341a3aac35126c0ffe97a596753ff9
ref: 16d00789f0341a3aac35126c0ffe97a596753ff9
specs:
mock_redis (0.22.0)
GIT
remote: https://github.com/tzmfreedom/json_refs
revision: e32deb073ce9aef39bdd63556bffd7fe7c2a803d
@ -270,7 +277,6 @@ GEM
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.0)
mock_redis (0.22.0)
msgpack (1.3.3)
multi_json (1.14.1)
multi_xml (0.6.0)
@ -526,7 +532,7 @@ DEPENDENCIES
letter_opener
listen
mini_magick
mock_redis
mock_redis!
nightfury
pg
pry-rails

View file

@ -8,7 +8,8 @@ class ActionCableConnector extends BaseActionCableConnector {
'message.created': this.onMessageCreated,
'message.updated': this.onMessageUpdated,
'conversation.created': this.onConversationCreated,
'status_change:conversation': this.onStatusChange,
'conversation.opened': this.onStatusChange,
'conversation.resolved': this.onStatusChange,
'user:logout': this.onLogout,
'page:reload': this.onReload,
'assignee.changed': this.onAssigneeChanged,
@ -40,7 +41,7 @@ class ActionCableConnector extends BaseActionCableConnector {
onReload = () => window.location.reload();
onStatusChange = data => {
this.app.$store.dispatch('addConversation', data);
this.app.$store.dispatch('updateConversation', data);
};
}

View file

@ -149,8 +149,15 @@ const actions = {
commit(types.default.ADD_MESSAGE, message);
},
addConversation({ commit }, conversation) {
commit(types.default.ADD_CONVERSATION, conversation);
addConversation({ commit, state }, conversation) {
const { currentInbox } = state;
if (!currentInbox || Number(currentInbox) === conversation.inbox_id) {
commit(types.default.ADD_CONVERSATION, conversation);
}
},
updateConversation({ commit }, conversation) {
commit(types.default.UPDATE_CONVERSATION, conversation);
},
toggleTyping: async ({ commit }, { status, inboxId, contactId }) => {

View file

@ -153,6 +153,24 @@ const mutations = {
_state.allConversations.push(conversation);
},
[types.default.UPDATE_CONVERSATION](_state, conversation) {
const { allConversations } = _state;
const currentConversationIndex = allConversations.findIndex(
c => c.id === conversation.id
);
if (currentConversationIndex > -1) {
const currentConversation = {
...allConversations[currentConversationIndex],
status: conversation.status,
};
Vue.set(allConversations, currentConversationIndex, currentConversation);
if (_state.selectedChat.id === conversation.id) {
_state.selectedChat.status = conversation.status;
window.bus.$emit('scrollToMessage');
}
}
},
[types.default.MARK_SEEN](_state) {
_state.selectedChat.seen = true;
},

View file

@ -21,6 +21,7 @@ export default {
CLEAR_ALL_MESSAGES: 'CLEAR_ALL_MESSAGES',
RESOLVE_CONVERSATION: 'RESOLVE_CONVERSATION',
ADD_CONVERSATION: 'ADD_CONVERSATION',
UPDATE_CONVERSATION: 'UPDATE_CONVERSATION',
SEND_MESSAGE: 'SEND_MESSAGE',
ASSIGN_AGENT: 'ASSIGN_AGENT',
SET_CHAT_META: 'SET_CHAT_META',

View file

@ -3,21 +3,21 @@ class ActionCableListener < BaseListener
def conversation_created(event)
conversation, account, timestamp = extract_conversation_and_account(event)
send_to_administrators(account.administrators, CONVERSATION_CREATED, conversation.push_event_data)
send_to_agents(conversation.inbox.members, CONVERSATION_CREATED, conversation.push_event_data)
send_to_agents(account, conversation.inbox.members, CONVERSATION_CREATED, conversation.push_event_data)
end
def conversation_read(event)
conversation, account, timestamp = extract_conversation_and_account(event)
send_to_administrators(account.administrators, CONVERSATION_READ, conversation.push_event_data)
send_to_agents(conversation.inbox.members, CONVERSATION_READ, conversation.push_event_data)
send_to_agents(account, conversation.inbox.members, CONVERSATION_READ, conversation.push_event_data)
end
def message_created(event)
message, account, timestamp = extract_message_and_account(event)
conversation = message.conversation
send_to_administrators(account.administrators, MESSAGE_CREATED, message.push_event_data)
send_to_agents(conversation.inbox.members, MESSAGE_CREATED, message.push_event_data)
send_to_agents(account, conversation.inbox.members, MESSAGE_CREATED, message.push_event_data)
send_to_contact(conversation.contact, MESSAGE_CREATED, message)
end
@ -25,55 +25,58 @@ class ActionCableListener < BaseListener
message, account, timestamp = extract_message_and_account(event)
conversation = message.conversation
contact = conversation.contact
members = conversation.inbox.members.pluck(:pubsub_token)
send_to_members(members, MESSAGE_UPDATED, message.push_event_data)
send_to_agents(account, conversation.inbox.members, MESSAGE_UPDATED, message.push_event_data)
send_to_contact(contact, MESSAGE_UPDATED, message)
end
def conversation_reopened(event)
def conversation_resolved(event)
conversation, account, timestamp = extract_conversation_and_account(event)
send_to_administrators(account.administrators, CONVERSATION_REOPENED, conversation.push_event_data)
send_to_agents(conversation.inbox.members, CONVERSATION_REOPENED, conversation.push_event_data)
send_to_agents(account, conversation.inbox.members, CONVERSATION_RESOLVED, conversation.push_event_data)
end
def conversation_opened(event)
conversation, account, timestamp = extract_conversation_and_account(event)
send_to_agents(account, conversation.inbox.members, CONVERSATION_OPENED, conversation.push_event_data)
end
def conversation_lock_toggle(event)
conversation, account, timestamp = extract_conversation_and_account(event)
send_to_administrators(account.administrators, CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
send_to_agents(conversation.inbox.members, CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
send_to_agents(account, conversation.inbox.members, CONVERSATION_LOCK_TOGGLE, conversation.lock_event_data)
end
def assignee_changed(event)
conversation, account, timestamp = extract_conversation_and_account(event)
send_to_administrators(account.administrators, ASSIGNEE_CHANGED, conversation.push_event_data)
send_to_agents(conversation.inbox.members, ASSIGNEE_CHANGED, conversation.push_event_data)
send_to_agents(account, conversation.inbox.members, ASSIGNEE_CHANGED, conversation.push_event_data)
end
def contact_created(event)
contact, account, timestamp = extract_contact_and_account(event)
send_to_administrators(account.administrators, CONTACT_CREATED, contact.push_event_data)
send_to_agents(account.agents, CONTACT_CREATED, contact.push_event_data)
send_to_agents(account, account.agents, CONTACT_CREATED, contact.push_event_data)
end
def contact_updated(event)
contact, account, timestamp = extract_contact_and_account(event)
send_to_administrators(account.administrators, CONTACT_UPDATED, contact.push_event_data)
send_to_agents(account.agents, CONTACT_UPDATED, contact.push_event_data)
send_to_agents(account, account.agents, CONTACT_UPDATED, contact.push_event_data)
end
private
def send_to_administrators(admins, event_name, data)
admin_tokens = admins.pluck(:pubsub_token)
return if admin_tokens.blank?
::ActionCableBroadcastJob.perform_later(admin_tokens, event_name, data)
end
def send_to_agents(agents, event_name, data)
def send_to_agents(account, agents, event_name, data)
agent_tokens = agents.pluck(:pubsub_token)
return if agent_tokens.blank?
admin_tokens = account.administrators.pluck(:pubsub_token)
::ActionCableBroadcastJob.perform_later(agent_tokens, event_name, data)
pubsub_tokens = (agent_tokens + admin_tokens).uniq
return if pubsub_tokens.blank?
::ActionCableBroadcastJob.perform_later(pubsub_tokens, event_name, data)
end
def send_to_contact(contact, event_name, message)

View file

@ -1,4 +1,28 @@
class AgentBotListener < BaseListener
def conversation_resolved(event)
conversation = extract_conversation_and_account(event)[0]
inbox = conversation.inbox
return if inbox.agent_bot_inbox.blank?
return unless inbox.agent_bot_inbox.active?
agent_bot = inbox.agent_bot_inbox.agent_bot
payload = conversation.webhook_data.merge(event: __method__.to_s)
AgentBotJob.perform_later(agent_bot.outgoing_url, payload)
end
def conversation_opened(event)
conversation = extract_conversation_and_account(event)[0]
inbox = conversation.inbox
return if inbox.agent_bot_inbox.blank?
return unless inbox.agent_bot_inbox.active?
agent_bot = inbox.agent_bot_inbox.agent_bot
payload = conversation.webhook_data.merge(event: __method__.to_s)
AgentBotJob.perform_later(agent_bot.outgoing_url, payload)
end
def message_created(event)
message = extract_message_and_account(event)[0]
inbox = message.inbox

View file

@ -7,9 +7,13 @@ class ReportingListener < BaseListener
def conversation_resolved(event)
conversation, account, timestamp = extract_conversation_and_account(event)
time_to_resolve = conversation.updated_at.to_i - conversation.created_at.to_i
agent = conversation.assignee
::Reports::UpdateAgentIdentity.new(account, agent, timestamp).update_avg_resolution_time(time_to_resolve)
::Reports::UpdateAgentIdentity.new(account, agent, timestamp).incr_resolutions_count
if conversation.assignee.present?
agent = conversation.assignee
::Reports::UpdateAgentIdentity.new(account, agent, timestamp).update_avg_resolution_time(time_to_resolve)
::Reports::UpdateAgentIdentity.new(account, agent, timestamp).incr_resolutions_count
end
::Reports::UpdateAccountIdentity.new(account, timestamp).update_avg_resolution_time(time_to_resolve)
::Reports::UpdateAccountIdentity.new(account, timestamp).incr_resolutions_count
end

View file

@ -1,4 +1,18 @@
class WebhookListener < BaseListener
def conversation_resolved(event)
conversation = extract_conversation_and_account(event)[0]
inbox = conversation.inbox
payload = conversation.webhook_data.merge(event: __method__.to_s)
deliver_webhook_payloads(payload, inbox)
end
def conversation_opened(event)
conversation = extract_conversation_and_account(event)[0]
inbox = conversation.inbox
payload = conversation.webhook_data.merge(event: __method__.to_s)
deliver_webhook_payloads(payload, inbox)
end
def message_created(event)
message = extract_message_and_account(event)[0]
inbox = message.inbox

View file

@ -54,7 +54,7 @@ class Conversation < ApplicationRecord
after_update :notify_status_change, :create_activity, :send_email_notification_to_assignee
after_create :send_events, :run_round_robin
after_create :notify_conversation_creation, :run_round_robin
acts_as_taggable_on :labels
@ -110,11 +110,7 @@ class Conversation < ApplicationRecord
self.status = :bot if inbox.agent_bot_inbox&.active?
end
def dispatch_events
dispatcher_dispatch(CONVERSATION_RESOLVED)
end
def send_events
def notify_conversation_creation
dispatcher_dispatch(CONVERSATION_CREATED)
end
@ -160,7 +156,8 @@ class Conversation < ApplicationRecord
def notify_status_change
{
CONVERSATION_RESOLVED => -> { saved_change_to_status? && resolved? && assignee.present? },
CONVERSATION_OPENED => -> { saved_change_to_status? && open? },
CONVERSATION_RESOLVED => -> { saved_change_to_status? && resolved? },
CONVERSATION_READ => -> { saved_change_to_user_last_seen_at? },
CONVERSATION_LOCK_TOGGLE => -> { saved_change_to_locked? },
ASSIGNEE_CHANGED => -> { saved_change_to_assignee_id? }

View file

@ -139,10 +139,7 @@ class Message < ApplicationRecord
end
def reopen_conversation
if incoming? && conversation.resolved?
conversation.toggle_status
Rails.configuration.dispatcher.dispatch(CONVERSATION_REOPENED, Time.zone.now, conversation: conversation)
end
conversation.open! if incoming? && conversation.resolved?
end
def execute_message_template_hooks

View file

@ -1,27 +1,33 @@
# frozen_string_literal: true
module Events::Types
CONVERSATION_CREATED = 'conversation.created'
CONVERSATION_RESOLVED = 'conversation.resolved'
CONVERSATION_READ = 'conversation.read'
WEBWIDGET_TRIGGERED = 'webwidget.triggered'
MESSAGE_CREATED = 'message.created'
FIRST_REPLY_CREATED = 'first.reply.created'
MESSAGE_UPDATED = 'message.updated'
CONVERSATION_REOPENED = 'conversation.reopened'
CONVERSATION_LOCK_TOGGLE = 'conversation.lock_toggle'
ASSIGNEE_CHANGED = 'assignee.changed'
CONTACT_CREATED = 'contact.created'
CONTACT_UPDATED = 'contact.updated'
# account events
ACCOUNT_CREATED = 'account.created'
ACCOUNT_DESTROYED = 'account.destroyed'
# channel events
WEBWIDGET_TRIGGERED = 'webwidget.triggered'
# conversation events
CONVERSATION_CREATED = 'conversation.created'
CONVERSATION_READ = 'conversation.read'
CONVERSATION_OPENED = 'conversation.opened'
CONVERSATION_RESOLVED = 'conversation.resolved'
CONVERSATION_LOCK_TOGGLE = 'conversation.lock_toggle'
ASSIGNEE_CHANGED = 'assignee.changed'
# message events
MESSAGE_CREATED = 'message.created'
FIRST_REPLY_CREATED = 'first.reply.created'
MESSAGE_UPDATED = 'message.updated'
# contact events
CONTACT_CREATED = 'contact.created'
CONTACT_UPDATED = 'contact.updated'
# subscription events
AGENT_ADDED = 'agent.added'
AGENT_REMOVED = 'agent.removed'
SUBSCRIPTION_CREATED = 'subscription.created'
SUBSCRIPTION_REACTIVATED = 'subscription.reactivated'
SUBSCRIPTION_DEACTIVATED = 'subscription.deactivated'

View file

@ -20,9 +20,12 @@ describe ActionCableListener do
let(:event_name) { :'message.created' }
it 'sends message to account admins, inbox agents and the contact' do
expect(ActionCableBroadcastJob).to receive(:perform_later).with([admin.pubsub_token], 'message.created', message.push_event_data)
expect(ActionCableBroadcastJob).to receive(:perform_later).with([agent.pubsub_token], 'message.created', message.push_event_data)
expect(ActionCableBroadcastJob).to receive(:perform_later).with([conversation.contact.pubsub_token], 'message.created', message.push_event_data)
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
[agent.pubsub_token, admin.pubsub_token], 'message.created', message.push_event_data
)
expect(ActionCableBroadcastJob).to receive(:perform_later).with(
[conversation.contact.pubsub_token], 'message.created', message.push_event_data
)
listener.message_created(event)
end
end