feat: kickstart new aggregator based builder
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
class V2::Reports::Conversations::BaseAggregator < V2::Reports::Timeseries::BaseTimeseriesBuilder
|
||||
def metrics
|
||||
return {} if range.blank?
|
||||
|
||||
compute_metrics
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compute_metrics
|
||||
raise NotImplementedError, 'Subclasses must implement #compute_metrics'
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
class V2::Reports::Conversations::BetterMetricBuilder < V2::Reports::Conversations::BaseReportBuilder
|
||||
COUNT_METRICS = %i[
|
||||
conversations_count
|
||||
incoming_messages_count
|
||||
outgoing_messages_count
|
||||
resolutions_count
|
||||
bot_resolutions_count
|
||||
bot_handoffs_count
|
||||
].freeze
|
||||
|
||||
AVERAGE_METRICS = %i[
|
||||
avg_first_response_time
|
||||
avg_resolution_time
|
||||
reply_time
|
||||
].freeze
|
||||
|
||||
SUMMARY_METRICS = %i[
|
||||
conversations_count
|
||||
outgoing_messages_count
|
||||
avg_first_response_time
|
||||
avg_resolution_time
|
||||
resolutions_count
|
||||
reply_time
|
||||
].freeze
|
||||
|
||||
BOT_SUMMARY_METRICS = %i[
|
||||
bot_resolutions_count
|
||||
bot_handoffs_count
|
||||
].freeze
|
||||
|
||||
AGGREGATOR_CLASSES = [
|
||||
V2::Reports::Conversations::ConversationsAggregator,
|
||||
V2::Reports::Conversations::MessagesAggregator,
|
||||
V2::Reports::Conversations::ReportingEventsAggregator
|
||||
].freeze
|
||||
|
||||
def summary
|
||||
result = SUMMARY_METRICS.index_with { |metric| fetch_metric(metric) }
|
||||
result[:incoming_messages_count] = fetch_metric(:incoming_messages_count) unless params[:type] == :agent
|
||||
result
|
||||
end
|
||||
|
||||
def bot_summary
|
||||
BOT_SUMMARY_METRICS.index_with { |metric| fetch_metric(metric) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_metric(key)
|
||||
aggregated_metrics.fetch(key, default_for(key))
|
||||
end
|
||||
|
||||
def aggregated_metrics
|
||||
@aggregated_metrics ||= AGGREGATOR_CLASSES.each_with_object({}) do |aggregator_class, memo|
|
||||
memo.merge!(aggregator_class.new(account, params).metrics) do |_metric, existing_value, new_value|
|
||||
new_value.nil? ? existing_value : new_value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def default_for(metric)
|
||||
return 0 if COUNT_METRICS.include?(metric)
|
||||
return nil if AVERAGE_METRICS.include?(metric)
|
||||
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
class V2::Reports::Conversations::ConversationsAggregator < V2::Reports::Conversations::BaseAggregator
|
||||
private
|
||||
|
||||
def compute_metrics
|
||||
{
|
||||
conversations_count: base_relation.count
|
||||
}
|
||||
end
|
||||
|
||||
def base_relation
|
||||
scope.conversations
|
||||
.where(account_id: account.id, created_at: range)
|
||||
.unscope(:order)
|
||||
.reorder(nil)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
class V2::Reports::Conversations::MessagesAggregator < V2::Reports::Conversations::BaseAggregator
|
||||
private
|
||||
|
||||
def compute_metrics
|
||||
incoming_count, outgoing_count = base_relation.pluck(
|
||||
Arel.sql(count_sql(Message.message_types[:incoming])),
|
||||
Arel.sql(count_sql(Message.message_types[:outgoing]))
|
||||
).first || [0, 0]
|
||||
|
||||
{
|
||||
incoming_messages_count: incoming_count || 0,
|
||||
outgoing_messages_count: outgoing_count || 0
|
||||
}
|
||||
end
|
||||
|
||||
def base_relation
|
||||
scope.messages
|
||||
.where(account_id: account.id, created_at: range)
|
||||
.unscope(:order)
|
||||
.reorder(nil)
|
||||
end
|
||||
|
||||
def count_sql(message_type)
|
||||
"COUNT(*) FILTER (WHERE message_type = #{message_type})"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
class V2::Reports::Conversations::ReportingEventsAggregator < V2::Reports::Conversations::BaseAggregator
|
||||
COUNT_MAPPINGS = {
|
||||
resolutions_count: :conversation_resolved,
|
||||
bot_resolutions_count: :conversation_bot_resolved,
|
||||
bot_handoffs_count: :conversation_bot_handoff
|
||||
}.freeze
|
||||
|
||||
AVERAGE_MAPPINGS = {
|
||||
avg_first_response_time: :first_response,
|
||||
avg_resolution_time: :conversation_resolved,
|
||||
reply_time: :reply_time
|
||||
}.freeze
|
||||
|
||||
private
|
||||
|
||||
def compute_metrics
|
||||
row = base_relation.pluck(*select_fragments).first || default_row
|
||||
|
||||
counts_hash(row.first(COUNT_MAPPINGS.size)).merge(averages_hash(row.last(AVERAGE_MAPPINGS.size)))
|
||||
end
|
||||
|
||||
def counts_hash(values)
|
||||
COUNT_MAPPINGS.keys.zip(values).to_h.transform_values { |value| value || 0 }
|
||||
end
|
||||
|
||||
def averages_hash(values)
|
||||
AVERAGE_MAPPINGS.keys.zip(values).to_h
|
||||
end
|
||||
|
||||
def select_fragments
|
||||
COUNT_MAPPINGS.map { |_metric, event_name| count_fragment(event_name) } +
|
||||
AVERAGE_MAPPINGS.map { |_metric, event_name| average_fragment(event_name) }
|
||||
end
|
||||
|
||||
def count_fragment(event_name)
|
||||
if event_name == :conversation_bot_handoff
|
||||
Arel.sql("COUNT(DISTINCT CASE WHEN name = '#{event_name}' THEN conversation_id END)")
|
||||
else
|
||||
Arel.sql("COUNT(*) FILTER (WHERE name = '#{event_name}')")
|
||||
end
|
||||
end
|
||||
|
||||
def average_fragment(event_name)
|
||||
Arel.sql("AVG(CASE WHEN name = '#{event_name}' THEN #{value_column} END)")
|
||||
end
|
||||
|
||||
def base_relation
|
||||
scope.reporting_events
|
||||
.where(account_id: account.id, created_at: range)
|
||||
.unscope(:order)
|
||||
.reorder(nil)
|
||||
end
|
||||
|
||||
def value_column
|
||||
params[:business_hours].present? ? 'value_in_business_hours' : 'value'
|
||||
end
|
||||
|
||||
def default_row
|
||||
Array.new(COUNT_MAPPINGS.size, 0) + Array.new(AVERAGE_MAPPINGS.size)
|
||||
end
|
||||
end
|
||||
@@ -125,7 +125,7 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
end
|
||||
|
||||
def build_summary(method)
|
||||
builder = V2::Reports::Conversations::MetricBuilder
|
||||
builder = V2::Reports::Conversations::BetterMetricBuilder
|
||||
current_summary = builder.new(Current.account, current_summary_params).send(method)
|
||||
previous_summary = builder.new(Current.account, previous_summary_params).send(method)
|
||||
current_summary.merge(previous: previous_summary)
|
||||
|
||||
Reference in New Issue
Block a user