feat: add reporting event actor attribution
This commit is contained in:
@@ -16,7 +16,8 @@ class ReportingEventListener < BaseListener
|
||||
user_id: conversation.assignee_id,
|
||||
conversation_id: conversation.id,
|
||||
event_start_time: conversation.created_at,
|
||||
event_end_time: event_end_time
|
||||
event_end_time: event_end_time,
|
||||
**actor_attributes(actor_from_event(event))
|
||||
)
|
||||
|
||||
create_bot_resolved_event(conversation, reporting_event)
|
||||
@@ -39,7 +40,8 @@ class ReportingEventListener < BaseListener
|
||||
user_id: message.sender_id,
|
||||
conversation_id: conversation.id,
|
||||
event_start_time: last_non_human_activity(conversation),
|
||||
event_end_time: message.created_at
|
||||
event_end_time: message.created_at,
|
||||
**actor_attributes(actor_from_event(event) || message.sender)
|
||||
)
|
||||
|
||||
reporting_event.save!
|
||||
@@ -65,7 +67,8 @@ class ReportingEventListener < BaseListener
|
||||
user_id: conversation.assignee_id,
|
||||
conversation_id: conversation.id,
|
||||
event_start_time: waiting_since,
|
||||
event_end_time: message.created_at
|
||||
event_end_time: message.created_at,
|
||||
**actor_attributes(actor_from_event(event) || message.sender)
|
||||
)
|
||||
reporting_event.save!
|
||||
safe_rollup(reporting_event)
|
||||
@@ -92,7 +95,8 @@ class ReportingEventListener < BaseListener
|
||||
user_id: conversation.assignee_id,
|
||||
conversation_id: conversation.id,
|
||||
event_start_time: conversation.created_at,
|
||||
event_end_time: event_end_time
|
||||
event_end_time: event_end_time,
|
||||
**actor_attributes(actor_from_event(event))
|
||||
)
|
||||
reporting_event.save!
|
||||
safe_rollup(reporting_event)
|
||||
@@ -116,34 +120,44 @@ class ReportingEventListener < BaseListener
|
||||
name: 'conversation_resolved'
|
||||
).where('event_end_time <= ?', event_end_time).order(event_end_time: :desc).first
|
||||
|
||||
# For first-time openings, value is 0
|
||||
# For reopenings, calculate time since resolution
|
||||
if last_resolved_event
|
||||
time_since_resolved = event_end_time.to_i - last_resolved_event.event_end_time.to_i
|
||||
business_hours_value = business_hours(conversation.inbox, last_resolved_event.event_end_time, event_end_time)
|
||||
start_time = last_resolved_event.event_end_time
|
||||
else
|
||||
time_since_resolved = 0
|
||||
business_hours_value = 0
|
||||
start_time = conversation.created_at
|
||||
end
|
||||
|
||||
create_conversation_opened_event(conversation, time_since_resolved, business_hours_value, start_time, event_end_time)
|
||||
create_conversation_opened_event(
|
||||
conversation,
|
||||
conversation_opened_event_attributes(conversation, last_resolved_event, event_end_time),
|
||||
actor_from_event(event)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_conversation_opened_event(conversation, time_since_resolved, business_hours_value, start_time, event_end_time)
|
||||
def conversation_opened_event_attributes(conversation, last_resolved_event, event_end_time)
|
||||
return first_conversation_opened_event_attributes(conversation, event_end_time) if last_resolved_event.blank?
|
||||
|
||||
{
|
||||
value: event_end_time.to_i - last_resolved_event.event_end_time.to_i,
|
||||
value_in_business_hours: business_hours(conversation.inbox, last_resolved_event.event_end_time, event_end_time),
|
||||
event_start_time: last_resolved_event.event_end_time,
|
||||
event_end_time: event_end_time
|
||||
}
|
||||
end
|
||||
|
||||
def first_conversation_opened_event_attributes(conversation, event_end_time)
|
||||
{
|
||||
value: 0,
|
||||
value_in_business_hours: 0,
|
||||
event_start_time: conversation.created_at,
|
||||
event_end_time: event_end_time
|
||||
}
|
||||
end
|
||||
|
||||
def create_conversation_opened_event(conversation, event_attributes, actor)
|
||||
reporting_event = ReportingEvent.new(
|
||||
name: 'conversation_opened',
|
||||
value: time_since_resolved,
|
||||
value_in_business_hours: business_hours_value,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
user_id: conversation.assignee_id,
|
||||
conversation_id: conversation.id,
|
||||
event_start_time: start_time,
|
||||
event_end_time: event_end_time
|
||||
**event_attributes,
|
||||
**actor_attributes(actor)
|
||||
)
|
||||
reporting_event.save!
|
||||
end
|
||||
@@ -160,7 +174,8 @@ class ReportingEventListener < BaseListener
|
||||
user_id: conversation.assignee_id,
|
||||
conversation_id: conversation.id,
|
||||
event_start_time: conversation.created_at,
|
||||
event_end_time: event.timestamp
|
||||
event_end_time: event.timestamp,
|
||||
**actor_attributes(actor_from_event(event))
|
||||
)
|
||||
end
|
||||
|
||||
@@ -175,6 +190,16 @@ class ReportingEventListener < BaseListener
|
||||
safe_rollup(bot_resolved_event)
|
||||
end
|
||||
|
||||
def actor_from_event(event)
|
||||
event.data[:performed_by]
|
||||
end
|
||||
|
||||
def actor_attributes(actor)
|
||||
return {} if actor.blank? || actor.id.blank?
|
||||
|
||||
{ actor_type: actor.class.name, actor_id: actor.id }
|
||||
end
|
||||
|
||||
def safe_rollup(reporting_event)
|
||||
# Rollups are derived from the raw reporting event. If a transient rollup write
|
||||
# failure bubbles out here, Sidekiq retries the dispatcher job and can insert the
|
||||
|
||||
@@ -343,7 +343,7 @@ class Conversation < ApplicationRecord
|
||||
def dispatcher_dispatch(event_name, changed_attributes = nil)
|
||||
Rails.configuration.dispatcher.dispatch(event_name, Time.zone.now, conversation: self, notifiable_assignee_change: notifiable_assignee_change?,
|
||||
changed_attributes: changed_attributes,
|
||||
performed_by: Current.executed_by)
|
||||
performed_by: Current.executed_by || Current.user)
|
||||
end
|
||||
|
||||
def set_unread_count_deletion_data
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Table name: reporting_events
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# actor_type :string
|
||||
# event_end_time :datetime
|
||||
# event_start_time :datetime
|
||||
# name :string
|
||||
@@ -11,20 +12,22 @@
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer
|
||||
# actor_id :bigint
|
||||
# conversation_id :integer
|
||||
# inbox_id :integer
|
||||
# user_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_reporting_events_for_response_distribution (account_id,name,inbox_id,created_at)
|
||||
# index_reporting_events_on_account_id (account_id)
|
||||
# index_reporting_events_on_conversation_id (conversation_id)
|
||||
# index_reporting_events_on_created_at (created_at)
|
||||
# index_reporting_events_on_inbox_id (inbox_id)
|
||||
# index_reporting_events_on_name (name)
|
||||
# index_reporting_events_on_user_id (user_id)
|
||||
# reporting_events__account_id__name__created_at (account_id,name,created_at)
|
||||
# idx_reporting_events_on_account_actor_name_created (account_id,actor_type,actor_id,name,created_at)
|
||||
# index_reporting_events_for_response_distribution (account_id,name,inbox_id,created_at)
|
||||
# index_reporting_events_on_account_id (account_id)
|
||||
# index_reporting_events_on_conversation_id (conversation_id)
|
||||
# index_reporting_events_on_created_at (created_at)
|
||||
# index_reporting_events_on_inbox_id (inbox_id)
|
||||
# index_reporting_events_on_name (name)
|
||||
# index_reporting_events_on_user_id (user_id)
|
||||
# reporting_events__account_id__name__created_at (account_id,name,created_at)
|
||||
#
|
||||
|
||||
class ReportingEvent < ApplicationRecord
|
||||
@@ -36,6 +39,7 @@ class ReportingEvent < ApplicationRecord
|
||||
belongs_to :user, optional: true
|
||||
belongs_to :inbox, optional: true
|
||||
belongs_to :conversation, optional: true
|
||||
belongs_to :actor, polymorphic: true, optional: true
|
||||
|
||||
# Scopes for filtering
|
||||
scope :filter_by_date_range, lambda { |range|
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class AddActorToReportingEvents < ActiveRecord::Migration[7.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_column :reporting_events, :actor_type, :string
|
||||
add_column :reporting_events, :actor_id, :bigint
|
||||
|
||||
add_index :reporting_events,
|
||||
[:account_id, :actor_type, :actor_id, :name, :created_at],
|
||||
name: 'idx_reporting_events_on_account_actor_name_created',
|
||||
algorithm: :concurrently
|
||||
end
|
||||
end
|
||||
+4
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_06_30_000000) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2026_07_01_000000) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -1167,6 +1167,9 @@ ActiveRecord::Schema[7.1].define(version: 2026_06_30_000000) do
|
||||
t.float "value_in_business_hours"
|
||||
t.datetime "event_start_time", precision: nil
|
||||
t.datetime "event_end_time", precision: nil
|
||||
t.string "actor_type"
|
||||
t.bigint "actor_id"
|
||||
t.index ["account_id", "actor_type", "actor_id", "name", "created_at"], name: "idx_reporting_events_on_account_actor_name_created"
|
||||
t.index ["account_id", "name", "created_at"], name: "reporting_events__account_id__name__created_at"
|
||||
t.index ["account_id", "name", "inbox_id", "created_at"], name: "index_reporting_events_for_response_distribution"
|
||||
t.index ["account_id"], name: "index_reporting_events_on_account_id"
|
||||
|
||||
@@ -7,6 +7,8 @@ json.event_end_time reporting_event.event_end_time
|
||||
json.account_id reporting_event.account_id
|
||||
json.inbox_id reporting_event.inbox_id
|
||||
json.user_id reporting_event.user_id
|
||||
json.actor_type reporting_event.actor_type
|
||||
json.actor_id reporting_event.actor_id
|
||||
json.conversation_id reporting_event.conversation_id
|
||||
json.created_at reporting_event.created_at
|
||||
json.updated_at reporting_event.updated_at
|
||||
|
||||
@@ -40,6 +40,16 @@ properties:
|
||||
- number
|
||||
- 'null'
|
||||
description: ID of the user/agent
|
||||
actor_type:
|
||||
type:
|
||||
- string
|
||||
- 'null'
|
||||
description: Type of the actor that caused the reporting event
|
||||
actor_id:
|
||||
type:
|
||||
- number
|
||||
- 'null'
|
||||
description: ID of the actor that caused the reporting event
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
Reference in New Issue
Block a user