diff --git a/app/listeners/reporting_event_listener.rb b/app/listeners/reporting_event_listener.rb index 40aa0b0ac..c5aa59f88 100644 --- a/app/listeners/reporting_event_listener.rb +++ b/app/listeners/reporting_event_listener.rb @@ -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 diff --git a/app/models/conversation.rb b/app/models/conversation.rb index d9c06c4d8..6d8af6df3 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -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 diff --git a/app/models/reporting_event.rb b/app/models/reporting_event.rb index 6c2c10b01..86035d3ee 100644 --- a/app/models/reporting_event.rb +++ b/app/models/reporting_event.rb @@ -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| diff --git a/db/migrate/20260701000000_add_actor_to_reporting_events.rb b/db/migrate/20260701000000_add_actor_to_reporting_events.rb new file mode 100644 index 000000000..d570dadbe --- /dev/null +++ b/db/migrate/20260701000000_add_actor_to_reporting_events.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index f9622f3b1..0ca0c5539 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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" diff --git a/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder b/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder index f8e5f08ba..a3355d443 100644 --- a/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder +++ b/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder @@ -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 diff --git a/swagger/definitions/resource/reporting_event.yml b/swagger/definitions/resource/reporting_event.yml index cea859db5..532f32041 100644 --- a/swagger/definitions/resource/reporting_event.yml +++ b/swagger/definitions/resource/reporting_event.yml @@ -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