Merge branch 'feat/rollup/2-report-data-source' into feat/rollup/3-rollup-read-path
This commit is contained in:
@@ -4,7 +4,7 @@ class V2::Reports::Conversations::BaseReportBuilder
|
||||
private
|
||||
|
||||
def builder_class(metric)
|
||||
return unless ReportingEvents::MetricRegistry.supported_metric?(metric)
|
||||
return unless Reports::ReportMetricRegistry.supported?(metric)
|
||||
|
||||
V2::Reports::Timeseries::ReportBuilder
|
||||
end
|
||||
|
||||
@@ -68,7 +68,7 @@ class ReportingEvents::BackfillService
|
||||
|
||||
def grouped_events(start_utc, end_utc)
|
||||
@account.reporting_events
|
||||
.where(name: ReportingEvents::MetricRegistry::EVENT_METRICS.keys, created_at: start_utc...end_utc)
|
||||
.where(name: ReportingEvents::EventMetricRegistry.event_names, created_at: start_utc...end_utc)
|
||||
.group(:name, :user_id, :inbox_id)
|
||||
.pluck(*AGGREGATE_SELECTS)
|
||||
.map { |grouped_row| grouped_event_attributes(grouped_row) }
|
||||
@@ -83,7 +83,7 @@ class ReportingEvents::BackfillService
|
||||
end
|
||||
|
||||
def accumulate_grouped_aggregates(aggregates, grouped_event)
|
||||
ReportingEvents::MetricRegistry.event_metrics_for_aggregate(
|
||||
ReportingEvents::EventMetricRegistry.metrics_for_aggregate(
|
||||
grouped_event[:event_name],
|
||||
count: grouped_event[:count],
|
||||
sum_value: grouped_event[:sum_value],
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
module ReportingEvents::EventMetricRegistry
|
||||
# Describes one rollup metric emitted by a raw reporting event.
|
||||
# rollup_metric: metric name stored in reporting_events_rollups.
|
||||
# payload_kind: whether the emitted row carries only a count or a duration payload.
|
||||
Metric = Data.define(:rollup_metric, :payload_kind)
|
||||
|
||||
EVENTS = {
|
||||
conversation_resolved: [
|
||||
Metric.new(rollup_metric: :resolutions_count, payload_kind: :count),
|
||||
Metric.new(rollup_metric: :resolution_time, payload_kind: :duration)
|
||||
].freeze,
|
||||
first_response: [
|
||||
Metric.new(rollup_metric: :first_response, payload_kind: :duration)
|
||||
].freeze,
|
||||
reply_time: [
|
||||
Metric.new(rollup_metric: :reply_time, payload_kind: :duration)
|
||||
].freeze,
|
||||
conversation_bot_resolved: [
|
||||
Metric.new(rollup_metric: :bot_resolutions_count, payload_kind: :count)
|
||||
].freeze,
|
||||
conversation_bot_handoff: [
|
||||
Metric.new(rollup_metric: :bot_handoffs_count, payload_kind: :count)
|
||||
].freeze
|
||||
}.freeze
|
||||
|
||||
module_function
|
||||
|
||||
def event_names
|
||||
EVENTS.keys.map(&:to_s)
|
||||
end
|
||||
|
||||
def metrics_for(event)
|
||||
return {} if event.blank?
|
||||
|
||||
metrics_for_aggregate(
|
||||
event.name,
|
||||
count: 1,
|
||||
sum_value: event.try(:value),
|
||||
sum_value_business_hours: event.try(:value_in_business_hours)
|
||||
)
|
||||
end
|
||||
|
||||
def metrics_for_aggregate(event_name, count:, sum_value:, sum_value_business_hours:)
|
||||
return {} if event_name.blank?
|
||||
|
||||
values = {
|
||||
count: count.to_i,
|
||||
sum_value: sum_value.to_f,
|
||||
sum_value_business_hours: sum_value_business_hours.to_f
|
||||
}
|
||||
|
||||
EVENTS.fetch(event_name.to_sym, []).to_h do |metric|
|
||||
[metric.rollup_metric, metric_values(metric.payload_kind, values)]
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method def metric_values(payload_kind, values)
|
||||
case payload_kind
|
||||
when :count
|
||||
count_values(values[:count])
|
||||
when :duration
|
||||
duration_values(values)
|
||||
else
|
||||
raise ArgumentError, "Unknown metric payload kind: #{payload_kind.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
private_class_method def count_values(count)
|
||||
{ count: count, sum_value: 0, sum_value_business_hours: 0 }
|
||||
end
|
||||
|
||||
private_class_method def duration_values(values)
|
||||
{
|
||||
count: values[:count],
|
||||
sum_value: values[:sum_value],
|
||||
sum_value_business_hours: values[:sum_value_business_hours]
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,7 @@
|
||||
# Raw reporting events and rollup rows do not share a single metric namespace; this registry keeps write and read paths aligned.
|
||||
# TODO: Split this into separate registries for raw event mappings and report metric definitions.
|
||||
module ReportingEvents::MetricRegistry
|
||||
# Maps report summary response keys to the metric definitions they read from.
|
||||
SUMMARY_METRICS = {
|
||||
resolutions_count: :resolved_conversations_count,
|
||||
avg_resolution_time: :avg_resolution_time,
|
||||
@@ -7,6 +9,7 @@ module ReportingEvents::MetricRegistry
|
||||
reply_time: :avg_reply_time
|
||||
}.freeze
|
||||
|
||||
# Expands each raw reporting event into the rollup metric payloads persisted for aggregation.
|
||||
EVENT_METRICS = {
|
||||
'conversation_resolved' => lambda do |values|
|
||||
{
|
||||
@@ -20,41 +23,18 @@ module ReportingEvents::MetricRegistry
|
||||
'conversation_bot_handoff' => ->(values) { { bot_handoffs_count: count_metric(values[:count]) } }
|
||||
}.freeze
|
||||
|
||||
# Describes which report metrics are supported and how each one is sourced and aggregated.
|
||||
REPORT_METRICS = {
|
||||
conversations_count: { aggregate: :count }.freeze,
|
||||
incoming_messages_count: { aggregate: :count }.freeze,
|
||||
outgoing_messages_count: { aggregate: :count }.freeze,
|
||||
avg_first_response_time: {
|
||||
raw_event_name: :first_response,
|
||||
rollup_metric: :first_response,
|
||||
aggregate: :average
|
||||
}.freeze,
|
||||
avg_resolution_time: {
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolution_time,
|
||||
aggregate: :average
|
||||
}.freeze,
|
||||
reply_time: {
|
||||
raw_event_name: :reply_time,
|
||||
rollup_metric: :reply_time,
|
||||
aggregate: :average
|
||||
}.freeze,
|
||||
resolutions_count: {
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolutions_count,
|
||||
aggregate: :count
|
||||
}.freeze,
|
||||
bot_resolutions_count: {
|
||||
raw_event_name: :conversation_bot_resolved,
|
||||
rollup_metric: :bot_resolutions_count,
|
||||
aggregate: :count
|
||||
}.freeze,
|
||||
bot_handoffs_count: {
|
||||
raw_event_name: :conversation_bot_handoff,
|
||||
rollup_metric: :bot_handoffs_count,
|
||||
aggregate: :count,
|
||||
raw_count_strategy: :distinct_conversation
|
||||
}.freeze
|
||||
avg_first_response_time: { raw_event_name: :first_response, rollup_metric: :first_response, aggregate: :average }.freeze,
|
||||
avg_resolution_time: { raw_event_name: :conversation_resolved, rollup_metric: :resolution_time, aggregate: :average }.freeze,
|
||||
reply_time: { raw_event_name: :reply_time, rollup_metric: :reply_time, aggregate: :average }.freeze,
|
||||
resolutions_count: { raw_event_name: :conversation_resolved, rollup_metric: :resolutions_count, aggregate: :count }.freeze,
|
||||
bot_resolutions_count: { raw_event_name: :conversation_bot_resolved, rollup_metric: :bot_resolutions_count, aggregate: :count }.freeze,
|
||||
bot_handoffs_count: { raw_event_name: :conversation_bot_handoff, rollup_metric: :bot_handoffs_count, aggregate: :count,
|
||||
raw_count_strategy: :distinct_conversation }.freeze
|
||||
}.freeze
|
||||
|
||||
module_function
|
||||
|
||||
@@ -39,7 +39,7 @@ class ReportingEvents::RollupService
|
||||
end
|
||||
|
||||
def build_rollup_rows
|
||||
event_metrics = ReportingEvents::MetricRegistry.event_metrics_for(@reporting_event)
|
||||
event_metrics = ReportingEvents::EventMetricRegistry.metrics_for(@reporting_event)
|
||||
|
||||
dimensions.each_with_object([]) do |(dimension_type, dimension_id), rows|
|
||||
next if dimension_id.nil?
|
||||
|
||||
@@ -49,7 +49,7 @@ class Reports::DataSource
|
||||
end
|
||||
|
||||
def supported_metric?(metric)
|
||||
metric.blank? || ReportingEvents::MetricRegistry.rollup_supported_metric?(metric)
|
||||
metric.blank? || Reports::ReportMetricRegistry.rollup_supported?(metric)
|
||||
end
|
||||
|
||||
def normalized_timezone_identifier(timezone)
|
||||
@@ -73,11 +73,11 @@ class Reports::DataSource
|
||||
private
|
||||
|
||||
def report_metric
|
||||
@report_metric ||= ReportingEvents::MetricRegistry.report_metric(metric)
|
||||
@report_metric ||= Reports::ReportMetricRegistry.fetch(metric)
|
||||
end
|
||||
|
||||
def average_metric?
|
||||
report_metric&.dig(:aggregate) == :average
|
||||
report_metric&.average?
|
||||
end
|
||||
|
||||
def count_metric?
|
||||
@@ -85,19 +85,19 @@ class Reports::DataSource
|
||||
end
|
||||
|
||||
def rollup_metric
|
||||
report_metric&.dig(:rollup_metric)
|
||||
report_metric&.rollup_metric
|
||||
end
|
||||
|
||||
def raw_event_name
|
||||
report_metric&.dig(:raw_event_name)
|
||||
report_metric&.raw_event_name
|
||||
end
|
||||
|
||||
def raw_count_strategy
|
||||
report_metric&.dig(:raw_count_strategy)
|
||||
report_metric&.raw_count_strategy
|
||||
end
|
||||
|
||||
def summary_metrics
|
||||
@summary_metrics ||= ReportingEvents::MetricRegistry.summary_metrics
|
||||
@summary_metrics ||= Reports::ReportMetricRegistry.summary_metrics
|
||||
end
|
||||
|
||||
def use_business_hours?
|
||||
|
||||
@@ -114,17 +114,17 @@ class Reports::RawDataSource < Reports::DataSource
|
||||
end
|
||||
|
||||
def summary_select_field(definition)
|
||||
if definition[:aggregate] == :count
|
||||
"COUNT(CASE WHEN name = '#{definition[:raw_event_name]}' THEN 1 END) as #{definition[:summary_key]}"
|
||||
if definition.count?
|
||||
"COUNT(CASE WHEN name = '#{definition.raw_event_name}' THEN 1 END) as #{definition.summary_key}"
|
||||
else
|
||||
"AVG(CASE WHEN name = '#{definition[:raw_event_name]}' THEN #{average_value_key} END) as #{definition[:summary_key]}"
|
||||
"AVG(CASE WHEN name = '#{definition.raw_event_name}' THEN #{average_value_key} END) as #{definition.summary_key}"
|
||||
end
|
||||
end
|
||||
|
||||
def summary_attributes_for(record, conversations_count = 0)
|
||||
summary_metrics.each_with_object({ conversations_count: conversations_count.to_i }) do |definition, attributes|
|
||||
value = record&.public_send(definition[:summary_key])
|
||||
attributes[definition[:summary_key]] = definition[:aggregate] == :count ? value.to_i : value
|
||||
value = record&.public_send(definition.summary_key)
|
||||
attributes[definition.summary_key] = definition.count? ? value.to_i : value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
module Reports::ReportMetricRegistry
|
||||
# Describes one public report metric.
|
||||
# name: API-facing metric name requested by reports.
|
||||
# aggregate: whether the metric is a count or average.
|
||||
# raw_event_name: source reporting_events name for raw queries.
|
||||
# rollup_metric: source reporting_events_rollups metric for rollup queries.
|
||||
# summary_key: key used when this metric appears in grouped summary responses.
|
||||
# raw_count_strategy: optional raw-query counting rule, such as distinct conversations.
|
||||
Metric = Data.define(
|
||||
:name,
|
||||
:aggregate,
|
||||
:raw_event_name,
|
||||
:rollup_metric,
|
||||
:summary_key,
|
||||
:raw_count_strategy
|
||||
) do
|
||||
def initialize(name:, aggregate:, raw_event_name: nil, rollup_metric: nil, summary_key: nil, raw_count_strategy: nil)
|
||||
super
|
||||
end
|
||||
|
||||
def average?
|
||||
aggregate == :average
|
||||
end
|
||||
|
||||
def count?
|
||||
aggregate == :count
|
||||
end
|
||||
|
||||
def rollup_supported?
|
||||
rollup_metric.present?
|
||||
end
|
||||
|
||||
def summary?
|
||||
summary_key.present?
|
||||
end
|
||||
end
|
||||
|
||||
METRICS = {
|
||||
conversations_count: Metric.new(
|
||||
name: :conversations_count,
|
||||
aggregate: :count
|
||||
),
|
||||
incoming_messages_count: Metric.new(
|
||||
name: :incoming_messages_count,
|
||||
aggregate: :count
|
||||
),
|
||||
outgoing_messages_count: Metric.new(
|
||||
name: :outgoing_messages_count,
|
||||
aggregate: :count
|
||||
),
|
||||
avg_first_response_time: Metric.new(
|
||||
name: :avg_first_response_time,
|
||||
aggregate: :average,
|
||||
raw_event_name: :first_response,
|
||||
rollup_metric: :first_response,
|
||||
summary_key: :avg_first_response_time
|
||||
),
|
||||
avg_resolution_time: Metric.new(
|
||||
name: :avg_resolution_time,
|
||||
aggregate: :average,
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolution_time,
|
||||
summary_key: :avg_resolution_time
|
||||
),
|
||||
reply_time: Metric.new(
|
||||
name: :reply_time,
|
||||
aggregate: :average,
|
||||
raw_event_name: :reply_time,
|
||||
rollup_metric: :reply_time,
|
||||
summary_key: :avg_reply_time
|
||||
),
|
||||
resolutions_count: Metric.new(
|
||||
name: :resolutions_count,
|
||||
aggregate: :count,
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolutions_count,
|
||||
summary_key: :resolved_conversations_count
|
||||
),
|
||||
bot_resolutions_count: Metric.new(
|
||||
name: :bot_resolutions_count,
|
||||
aggregate: :count,
|
||||
raw_event_name: :conversation_bot_resolved,
|
||||
rollup_metric: :bot_resolutions_count
|
||||
),
|
||||
bot_handoffs_count: Metric.new(
|
||||
name: :bot_handoffs_count,
|
||||
aggregate: :count,
|
||||
raw_event_name: :conversation_bot_handoff,
|
||||
rollup_metric: :bot_handoffs_count,
|
||||
raw_count_strategy: :distinct_conversation
|
||||
)
|
||||
}.freeze
|
||||
|
||||
SUMMARY_METRIC_NAMES = %i[
|
||||
resolutions_count
|
||||
avg_resolution_time
|
||||
avg_first_response_time
|
||||
reply_time
|
||||
].freeze
|
||||
|
||||
module_function
|
||||
|
||||
def fetch(name)
|
||||
return if name.blank?
|
||||
|
||||
METRICS[name.to_sym]
|
||||
end
|
||||
|
||||
def supported?(name)
|
||||
fetch(name).present?
|
||||
end
|
||||
|
||||
def rollup_supported?(name)
|
||||
fetch(name)&.rollup_supported? || false
|
||||
end
|
||||
|
||||
def summary_metrics
|
||||
SUMMARY_METRIC_NAMES.map { |metric_name| METRICS.fetch(metric_name) }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,125 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ReportingEvents::EventMetricRegistry do
|
||||
describe '.event_names' do
|
||||
it 'returns the supported raw event names' do
|
||||
expect(described_class.event_names).to eq(
|
||||
%w[
|
||||
conversation_resolved
|
||||
first_response
|
||||
reply_time
|
||||
conversation_bot_resolved
|
||||
conversation_bot_handoff
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.metrics_for' do
|
||||
it 'returns the emitted rollup metrics for conversation_resolved' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_resolved', value: 120, value_in_business_hours: 45)
|
||||
|
||||
expect(described_class.metrics_for(event)).to eq(
|
||||
resolutions_count: {
|
||||
count: 1,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
},
|
||||
resolution_time: {
|
||||
count: 1,
|
||||
sum_value: 120.0,
|
||||
sum_value_business_hours: 45.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for first_response' do
|
||||
event = instance_double(ReportingEvent, name: 'first_response', value: 80, value_in_business_hours: 20)
|
||||
|
||||
expect(described_class.metrics_for(event)).to eq(
|
||||
first_response: {
|
||||
count: 1,
|
||||
sum_value: 80.0,
|
||||
sum_value_business_hours: 20.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for reply_time' do
|
||||
event = instance_double(ReportingEvent, name: 'reply_time', value: 40, value_in_business_hours: 15)
|
||||
|
||||
expect(described_class.metrics_for(event)).to eq(
|
||||
reply_time: {
|
||||
count: 1,
|
||||
sum_value: 40.0,
|
||||
sum_value_business_hours: 15.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for conversation_bot_resolved' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_bot_resolved')
|
||||
|
||||
expect(described_class.metrics_for(event)).to eq(
|
||||
bot_resolutions_count: {
|
||||
count: 1,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for conversation_bot_handoff' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_bot_handoff')
|
||||
|
||||
expect(described_class.metrics_for(event)).to eq(
|
||||
bot_handoffs_count: {
|
||||
count: 1,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an empty hash for unsupported events' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_created')
|
||||
|
||||
expect(described_class.metrics_for(event)).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
describe '.metrics_for_aggregate' do
|
||||
it 'returns aggregated rollup metrics for conversation_resolved groups' do
|
||||
expect(
|
||||
described_class.metrics_for_aggregate(
|
||||
'conversation_resolved',
|
||||
count: 3,
|
||||
sum_value: 420,
|
||||
sum_value_business_hours: 210
|
||||
)
|
||||
).to eq(
|
||||
resolutions_count: {
|
||||
count: 3,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
},
|
||||
resolution_time: {
|
||||
count: 3,
|
||||
sum_value: 420.0,
|
||||
sum_value_business_hours: 210.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an empty hash for unsupported grouped events' do
|
||||
expect(
|
||||
described_class.metrics_for_aggregate(
|
||||
'conversation_created',
|
||||
count: 2,
|
||||
sum_value: 100,
|
||||
sum_value_business_hours: 50
|
||||
)
|
||||
).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,215 +0,0 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ReportingEvents::MetricRegistry do
|
||||
describe '.event_metrics_for' do
|
||||
it 'returns the emitted rollup metrics for conversation_resolved' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_resolved', value: 120, value_in_business_hours: 45)
|
||||
|
||||
expect(described_class.event_metrics_for(event)).to eq(
|
||||
resolutions_count: {
|
||||
count: 1,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
},
|
||||
resolution_time: {
|
||||
count: 1,
|
||||
sum_value: 120.0,
|
||||
sum_value_business_hours: 45.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for first_response' do
|
||||
event = instance_double(ReportingEvent, name: 'first_response', value: 80, value_in_business_hours: 20)
|
||||
|
||||
expect(described_class.event_metrics_for(event)).to eq(
|
||||
first_response: {
|
||||
count: 1,
|
||||
sum_value: 80.0,
|
||||
sum_value_business_hours: 20.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for reply_time' do
|
||||
event = instance_double(ReportingEvent, name: 'reply_time', value: 40, value_in_business_hours: 15)
|
||||
|
||||
expect(described_class.event_metrics_for(event)).to eq(
|
||||
reply_time: {
|
||||
count: 1,
|
||||
sum_value: 40.0,
|
||||
sum_value_business_hours: 15.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for conversation_bot_resolved' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_bot_resolved')
|
||||
|
||||
expect(described_class.event_metrics_for(event)).to eq(
|
||||
bot_resolutions_count: {
|
||||
count: 1,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the emitted rollup metrics for conversation_bot_handoff' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_bot_handoff')
|
||||
|
||||
expect(described_class.event_metrics_for(event)).to eq(
|
||||
bot_handoffs_count: {
|
||||
count: 1,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an empty hash for unsupported events' do
|
||||
event = instance_double(ReportingEvent, name: 'conversation_created')
|
||||
|
||||
expect(described_class.event_metrics_for(event)).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
describe '.event_metrics_for_aggregate' do
|
||||
it 'returns aggregated rollup metrics for conversation_resolved groups' do
|
||||
expect(
|
||||
described_class.event_metrics_for_aggregate(
|
||||
'conversation_resolved',
|
||||
count: 3,
|
||||
sum_value: 420,
|
||||
sum_value_business_hours: 210
|
||||
)
|
||||
).to eq(
|
||||
resolutions_count: {
|
||||
count: 3,
|
||||
sum_value: 0,
|
||||
sum_value_business_hours: 0
|
||||
},
|
||||
resolution_time: {
|
||||
count: 3,
|
||||
sum_value: 420.0,
|
||||
sum_value_business_hours: 210.0
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns an empty hash for unsupported grouped events' do
|
||||
expect(
|
||||
described_class.event_metrics_for_aggregate(
|
||||
'conversation_created',
|
||||
count: 2,
|
||||
sum_value: 100,
|
||||
sum_value_business_hours: 50
|
||||
)
|
||||
).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
describe '.report_metric' do
|
||||
it 'returns the definition for raw-only count metrics' do
|
||||
expect(described_class.report_metric(:conversations_count)).to eq(
|
||||
aggregate: :count
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns the definition for avg_resolution_time' do
|
||||
expect(described_class.report_metric(:avg_resolution_time)).to eq(
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolution_time,
|
||||
aggregate: :average
|
||||
)
|
||||
end
|
||||
|
||||
it 'locks the distinct conversation strategy for bot_handoffs_count' do
|
||||
expect(described_class.report_metric(:bot_handoffs_count)).to eq(
|
||||
raw_event_name: :conversation_bot_handoff,
|
||||
rollup_metric: :bot_handoffs_count,
|
||||
aggregate: :count,
|
||||
raw_count_strategy: :distinct_conversation
|
||||
)
|
||||
end
|
||||
|
||||
it 'returns nil for unsupported metrics' do
|
||||
expect(described_class.report_metric(:unknown_metric)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '.supported_metric?' do
|
||||
it 'returns true for supported raw-only metrics' do
|
||||
expect(described_class.supported_metric?(:conversations_count)).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for unsupported metrics' do
|
||||
expect(described_class.supported_metric?(:unknown_metric)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.aggregate_for' do
|
||||
it 'returns the aggregate type for a supported metric' do
|
||||
expect(described_class.aggregate_for(:avg_first_response_time)).to eq(:average)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.rollup_supported_metric?' do
|
||||
it 'returns true for rollup-backed metrics' do
|
||||
expect(described_class.rollup_supported_metric?(:reply_time)).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for unsupported metrics' do
|
||||
expect(described_class.rollup_supported_metric?(:conversations_count)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.rollup_metric_for' do
|
||||
it 'returns the rollup metric name' do
|
||||
expect(described_class.rollup_metric_for(:avg_first_response_time)).to eq(:first_response)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.raw_event_name_for' do
|
||||
it 'returns the raw event name' do
|
||||
expect(described_class.raw_event_name_for(:bot_resolutions_count)).to eq(:conversation_bot_resolved)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.summary_metrics' do
|
||||
it 'returns the registry-backed summary metric definitions' do
|
||||
expect(described_class.summary_metrics).to eq(
|
||||
[
|
||||
{
|
||||
metric_name: :resolutions_count,
|
||||
summary_key: :resolved_conversations_count,
|
||||
aggregate: :count,
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolutions_count
|
||||
},
|
||||
{
|
||||
metric_name: :avg_resolution_time,
|
||||
summary_key: :avg_resolution_time,
|
||||
aggregate: :average,
|
||||
raw_event_name: :conversation_resolved,
|
||||
rollup_metric: :resolution_time
|
||||
},
|
||||
{
|
||||
metric_name: :avg_first_response_time,
|
||||
summary_key: :avg_first_response_time,
|
||||
aggregate: :average,
|
||||
raw_event_name: :first_response,
|
||||
rollup_metric: :first_response
|
||||
},
|
||||
{
|
||||
metric_name: :reply_time,
|
||||
summary_key: :avg_reply_time,
|
||||
aggregate: :average,
|
||||
raw_event_name: :reply_time,
|
||||
rollup_metric: :reply_time
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Reports::ReportMetricRegistry do
|
||||
describe '.fetch' do
|
||||
it 'returns the definition for raw-only count metrics' do
|
||||
metric = described_class.fetch(:conversations_count)
|
||||
|
||||
expect(metric.name).to eq(:conversations_count)
|
||||
expect(metric.count?).to be(true)
|
||||
expect(metric.rollup_supported?).to be(false)
|
||||
expect(metric.raw_event_name).to be_nil
|
||||
end
|
||||
|
||||
it 'returns the definition for avg_resolution_time' do
|
||||
metric = described_class.fetch(:avg_resolution_time)
|
||||
|
||||
expect(metric.name).to eq(:avg_resolution_time)
|
||||
expect(metric.average?).to be(true)
|
||||
expect(metric.raw_event_name).to eq(:conversation_resolved)
|
||||
expect(metric.rollup_metric).to eq(:resolution_time)
|
||||
expect(metric.summary_key).to eq(:avg_resolution_time)
|
||||
end
|
||||
|
||||
it 'locks the distinct conversation strategy for bot_handoffs_count' do
|
||||
metric = described_class.fetch(:bot_handoffs_count)
|
||||
|
||||
expect(metric.count?).to be(true)
|
||||
expect(metric.raw_event_name).to eq(:conversation_bot_handoff)
|
||||
expect(metric.rollup_metric).to eq(:bot_handoffs_count)
|
||||
expect(metric.raw_count_strategy).to eq(:distinct_conversation)
|
||||
end
|
||||
|
||||
it 'returns nil for unsupported metrics' do
|
||||
expect(described_class.fetch(:unknown_metric)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '.supported?' do
|
||||
it 'returns true for supported raw-only metrics' do
|
||||
expect(described_class.supported?(:conversations_count)).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for unsupported metrics' do
|
||||
expect(described_class.supported?(:unknown_metric)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.rollup_supported?' do
|
||||
it 'returns true for rollup-backed metrics' do
|
||||
expect(described_class.rollup_supported?(:reply_time)).to be(true)
|
||||
end
|
||||
|
||||
it 'returns false for raw-only metrics' do
|
||||
expect(described_class.rollup_supported?(:conversations_count)).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.summary_metrics' do
|
||||
it 'returns the summary metric definitions in registry order' do
|
||||
expect(
|
||||
described_class.summary_metrics.map do |metric|
|
||||
[metric.name, metric.summary_key, metric.aggregate, metric.raw_event_name, metric.rollup_metric]
|
||||
end
|
||||
).to eq(
|
||||
[
|
||||
[:resolutions_count, :resolved_conversations_count, :count, :conversation_resolved, :resolutions_count],
|
||||
[:avg_resolution_time, :avg_resolution_time, :average, :conversation_resolved, :resolution_time],
|
||||
[:avg_first_response_time, :avg_first_response_time, :average, :first_response, :first_response],
|
||||
[:reply_time, :avg_reply_time, :average, :reply_time, :reply_time]
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user