diff --git a/app/builders/v2/reports/timeseries/average_report_builder.rb b/app/builders/v2/reports/timeseries/average_report_builder.rb deleted file mode 100644 index 17a6a2745..000000000 --- a/app/builders/v2/reports/timeseries/average_report_builder.rb +++ /dev/null @@ -1,2 +0,0 @@ -class V2::Reports::Timeseries::AverageReportBuilder < V2::Reports::Timeseries::ReportBuilder -end diff --git a/app/builders/v2/reports/timeseries/count_report_builder.rb b/app/builders/v2/reports/timeseries/count_report_builder.rb deleted file mode 100644 index ae9cbd0f9..000000000 --- a/app/builders/v2/reports/timeseries/count_report_builder.rb +++ /dev/null @@ -1,2 +0,0 @@ -class V2::Reports::Timeseries::CountReportBuilder < V2::Reports::Timeseries::ReportBuilder -end diff --git a/spec/builders/v2/reports/timeseries/average_report_builder_spec.rb b/spec/builders/v2/reports/timeseries/average_report_builder_spec.rb deleted file mode 100644 index bf174089c..000000000 --- a/spec/builders/v2/reports/timeseries/average_report_builder_spec.rb +++ /dev/null @@ -1,286 +0,0 @@ -require 'rails_helper' - -describe V2::Reports::Timeseries::AverageReportBuilder do - subject { described_class.new(account, params) } - - let(:account) { create(:account) } - let(:team) { create(:team, account: account) } - let(:inbox) { create(:inbox, account: account) } - let(:label) { create(:label, title: 'spec-billing', account: account) } - let!(:conversation) { create(:conversation, account: account, inbox: inbox, team: team) } - let(:current_time) { '26.10.2020 10:00'.to_datetime } - - let(:params) do - { - type: filter_type, - business_hours: business_hours, - timezone_offset: timezone_offset, - group_by: group_by, - metric: metric, - since: (current_time - 1.week).beginning_of_day.to_i.to_s, - until: current_time.end_of_day.to_i.to_s, - id: filter_id - } - end - let(:timezone_offset) { nil } - let(:group_by) { 'day' } - let(:metric) { 'avg_first_response_time' } - let(:business_hours) { false } - let(:filter_type) { :account } - let(:filter_id) { '' } - - before do - travel_to current_time - conversation.label_list.add(label.title) - conversation.save! - create(:reporting_event, name: 'first_response', value: 80, value_in_business_hours: 10, account: account, created_at: Time.zone.now, - conversation: conversation, inbox: inbox) - create(:reporting_event, name: 'first_response', value: 100, value_in_business_hours: 20, account: account, created_at: 1.hour.ago) - create(:reporting_event, name: 'first_response', value: 93, value_in_business_hours: 30, account: account, created_at: 1.week.ago) - end - - describe '#timeseries' do - context 'when there is no filter applied' do - it 'returns the correct values' do - timeseries_values = subject.timeseries - - expect(timeseries_values).to eq( - [ - { count: 1, timestamp: 1_603_065_600, value: 93.0 }, - { count: 0, timestamp: 1_603_152_000, value: 0 }, - { count: 0, timestamp: 1_603_238_400, value: 0 }, - { count: 0, timestamp: 1_603_324_800, value: 0 }, - { count: 0, timestamp: 1_603_411_200, value: 0 }, - { count: 0, timestamp: 1_603_497_600, value: 0 }, - { count: 0, timestamp: 1_603_584_000, value: 0 }, - { count: 2, timestamp: 1_603_670_400, value: 90.0 } - ] - ) - end - - context 'when business hours is provided' do - let(:business_hours) { true } - - it 'returns correct timeseries' do - timeseries_values = subject.timeseries - - expect(timeseries_values).to eq( - [ - { count: 1, timestamp: 1_603_065_600, value: 30.0 }, - { count: 0, timestamp: 1_603_152_000, value: 0 }, - { count: 0, timestamp: 1_603_238_400, value: 0 }, - { count: 0, timestamp: 1_603_324_800, value: 0 }, - { count: 0, timestamp: 1_603_411_200, value: 0 }, - { count: 0, timestamp: 1_603_497_600, value: 0 }, - { count: 0, timestamp: 1_603_584_000, value: 0 }, - { count: 2, timestamp: 1_603_670_400, value: 15.0 } - ] - ) - end - end - - context 'when rollups are enabled' do - let(:timezone_offset) { '5.5' } - - before do - account.update!(reporting_timezone: 'Chennai') - allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) - - create(:reporting_events_rollup, - account: account, - date: (current_time - 1.week).to_date, - dimension_type: 'account', - dimension_id: account.id, - metric: 'first_response', - count: 1, - sum_value: 93.0, - sum_value_business_hours: 30.0) - - create(:reporting_events_rollup, - account: account, - date: current_time.to_date, - dimension_type: 'account', - dimension_id: account.id, - metric: 'first_response', - count: 2, - sum_value: 180.0, - sum_value_business_hours: 30.0) - end - - it 'preserves empty buckets in the timeseries' do - rollup_timezone = ActiveSupport::TimeZone['Chennai'] - rollup_start_date = DateTime.strptime(params[:since], '%s').in_time_zone(rollup_timezone).to_date - rollup_end_date = (DateTime.strptime(params[:until], '%s') - 1.second).in_time_zone(rollup_timezone).to_date - rollup_dates = rollup_start_date..rollup_end_date - - expected_timeseries = rollup_dates.map do |date| - value = if date == (current_time - 1.week).to_date - 93.0 - elsif date == current_time.to_date - 90.0 - else - 0 - end - - count = if date == (current_time - 1.week).to_date - 1 - elsif date == current_time.to_date - 2 - else - 0 - end - - { count: count, timestamp: date.in_time_zone('Chennai').to_i, value: value } - end - - expect(subject.timeseries).to eq(expected_timeseries) - end - end - - context 'when group_by is provided' do - let(:group_by) { 'week' } - - it 'returns correct timeseries' do - timeseries_values = subject.timeseries - expect(timeseries_values).to eq( - [ - { count: 1, timestamp: (current_time - 1.week).beginning_of_week(:sunday).to_i, value: 93.0 }, - { count: 2, timestamp: current_time.beginning_of_week(:sunday).to_i, value: 90.0 } - ] - ) - end - end - - context 'when timezone offset is provided' do - let(:timezone_offset) { '5.5' } - let(:group_by) { 'week' } - - it 'returns correct timeseries' do - timeseries_values = subject.timeseries - expect(timeseries_values).to eq( - [ - { count: 1, timestamp: (current_time - 1.week).in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 93.0 }, - { count: 2, timestamp: current_time.in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 90.0 } - ] - ) - end - end - - context 'when weekly rollups are enabled' do - let(:group_by) { 'week' } - let(:timezone_offset) { '5.5' } - - before do - account.update!(reporting_timezone: 'Chennai') - allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) - - create(:reporting_events_rollup, - account: account, - date: current_time.to_date - 1.day, - dimension_type: 'account', - dimension_id: account.id, - metric: 'first_response', - count: 1, - sum_value: 80.0, - sum_value_business_hours: 10.0) - - create(:reporting_events_rollup, - account: account, - date: current_time.to_date, - dimension_type: 'account', - dimension_id: account.id, - metric: 'first_response', - count: 1, - sum_value: 100.0, - sum_value_business_hours: 20.0) - end - - it 'groups weeks using sunday boundaries' do - expect(subject.timeseries).to eq( - [ - { count: 0, timestamp: (current_time - 1.week).in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 0 }, - { count: 2, timestamp: current_time.in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 90.0 } - ] - ) - end - end - end - - context 'when the label filter is applied' do - let(:group_by) { 'week' } - let(:filter_type) { 'label' } - let(:filter_id) { label.id } - - it 'returns correct timeseries' do - timeseries_values = subject.timeseries - start_of_the_week = current_time.beginning_of_week(:sunday).to_i - last_week_start_of_the_week = (current_time - 1.week).beginning_of_week(:sunday).to_i - expect(timeseries_values).to eq( - [ - { count: 0, timestamp: last_week_start_of_the_week, value: 0 }, - { count: 1, timestamp: start_of_the_week, value: 80.0 } - ] - ) - end - end - - context 'when the inbox filter is applied' do - let(:group_by) { 'week' } - let(:filter_type) { 'inbox' } - let(:filter_id) { inbox.id } - - it 'returns correct timeseries' do - timeseries_values = subject.timeseries - start_of_the_week = current_time.beginning_of_week(:sunday).to_i - last_week_start_of_the_week = (current_time - 1.week).beginning_of_week(:sunday).to_i - expect(timeseries_values).to eq( - [ - { count: 0, timestamp: last_week_start_of_the_week, value: 0 }, - { count: 1, timestamp: start_of_the_week, value: 80.0 } - ] - ) - end - end - - context 'when the team filter is applied' do - let(:group_by) { 'week' } - let(:filter_type) { 'team' } - let(:filter_id) { team.id } - - it 'returns correct timeseries' do - timeseries_values = subject.timeseries - start_of_the_week = current_time.beginning_of_week(:sunday).to_i - last_week_start_of_the_week = (current_time - 1.week).beginning_of_week(:sunday).to_i - expect(timeseries_values).to eq( - [ - { count: 0, timestamp: last_week_start_of_the_week, value: 0 }, - { count: 1, timestamp: start_of_the_week, value: 80.0 } - ] - ) - end - end - end - - describe '#aggregate_value' do - context 'when there is no filter applied' do - it 'returns the correct average value' do - expect(subject.aggregate_value).to eq 91.0 - end - end - - context 'when rollups are enabled and the agent does not exist' do - let(:filter_type) { :agent } - let(:filter_id) { '999999' } - let(:timezone_offset) { '0' } - - before do - account.update!(reporting_timezone: 'Etc/UTC') - allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) - end - - it 'raises record not found to preserve raw path behavior' do - expect { subject.aggregate_value }.to raise_error(ActiveRecord::RecordNotFound) - end - end - end -end diff --git a/spec/builders/v2/reports/timeseries/count_report_builder_spec.rb b/spec/builders/v2/reports/timeseries/count_report_builder_spec.rb deleted file mode 100644 index b45f32123..000000000 --- a/spec/builders/v2/reports/timeseries/count_report_builder_spec.rb +++ /dev/null @@ -1,176 +0,0 @@ -require 'rails_helper' - -describe V2::Reports::Timeseries::CountReportBuilder do - subject { described_class.new(account, params) } - - let(:account) { create(:account) } - let(:account2) { create(:account) } - let(:user) { create(:user, email: 'agent1@example.com') } - let(:inbox) { create(:inbox, account: account) } - let(:inbox2) { create(:inbox, account: account2) } - let(:current_time) { Time.current } - - let(:params) do - { - type: 'agent', - metric: 'resolutions_count', - since: since_time.beginning_of_day.to_i.to_s, - until: current_time.end_of_day.to_i.to_s, - timezone_offset: timezone_offset, - group_by: group_by, - id: user.id.to_s - } - end - let(:group_by) { 'day' } - let(:since_time) { current_time - 1.day } - let(:timezone_offset) { nil } - - before do - travel_to current_time - - # Add the same user to both accounts - create(:account_user, account: account, user: user) - create(:account_user, account: account2, user: user) - - # Create conversations in account1 - conversation1 = create(:conversation, account: account, inbox: inbox, assignee: user) - conversation2 = create(:conversation, account: account, inbox: inbox, assignee: user) - - # Create conversations in account2 - conversation3 = create(:conversation, account: account2, inbox: inbox2, assignee: user) - conversation4 = create(:conversation, account: account2, inbox: inbox2, assignee: user) - - # User resolves 2 conversations in account1 - create(:reporting_event, - name: 'conversation_resolved', - account: account, - user: user, - conversation: conversation1, - created_at: current_time - 12.hours) - - create(:reporting_event, - name: 'conversation_resolved', - account: account, - user: user, - conversation: conversation2, - created_at: current_time - 6.hours) - - # Same user resolves 3 conversations in account2 - these should NOT be counted for account1 - create(:reporting_event, - name: 'conversation_resolved', - account: account2, - user: user, - conversation: conversation3, - created_at: current_time - 8.hours) - - create(:reporting_event, - name: 'conversation_resolved', - account: account2, - user: user, - conversation: conversation4, - created_at: current_time - 4.hours) - - # Create another conversation in account2 for testing - conversation5 = create(:conversation, account: account2, inbox: inbox2, assignee: user) - create(:reporting_event, - name: 'conversation_resolved', - account: account2, - user: user, - conversation: conversation5, - created_at: current_time - 2.hours) - end - - describe '#aggregate_value' do - it 'returns only resolutions performed by the user in the specified account' do - # User should have 2 resolutions in account1, not 5 (total across both accounts) - expect(subject.aggregate_value).to eq(2) - end - - context 'when rollups are enabled and the agent does not exist' do - let(:timezone_offset) { '0' } - - let(:params) do - super().merge(id: '999999') - end - - before do - account.update!(reporting_timezone: 'Etc/UTC') - allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) - end - - it 'raises record not found to preserve raw path behavior' do - expect { subject.aggregate_value }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - context 'when querying account2' do - subject { described_class.new(account2, params) } - - it 'returns only resolutions for account2' do - # User should have 3 resolutions in account2 - expect(subject.aggregate_value).to eq(3) - end - end - end - - describe '#timeseries' do - it 'filters resolutions by account' do - result = subject.timeseries - # Should only count the 2 resolutions from account1 - total_count = result.sum { |r| r[:value] } - expect(total_count).to eq(2) - end - - context 'when rollups are enabled and grouped by week' do - let(:group_by) { 'week' } - let(:current_time) { Time.zone.parse('2020-10-26 10:00:00 UTC') } - let(:since_time) { current_time - 1.week } - let(:timezone_offset) { '5.5' } - - before do - account.update!(reporting_timezone: 'Chennai') - allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) - - create(:reporting_events_rollup, - account: account, - date: current_time.to_date - 1.day, - dimension_type: 'agent', - dimension_id: user.id, - metric: 'resolutions_count', - count: 1, - sum_value: 0.0, - sum_value_business_hours: 0.0) - - create(:reporting_events_rollup, - account: account, - date: current_time.to_date, - dimension_type: 'agent', - dimension_id: user.id, - metric: 'resolutions_count', - count: 1, - sum_value: 0.0, - sum_value_business_hours: 0.0) - end - - it 'groups weeks using sunday boundaries' do - expect(subject.timeseries).to eq( - [ - { value: 0, timestamp: (current_time - 1.week).in_time_zone('Chennai').beginning_of_week(:sunday).to_i }, - { value: 2, timestamp: current_time.in_time_zone('Chennai').beginning_of_week(:sunday).to_i } - ] - ) - end - end - end - - describe 'account isolation' do - it 'does not leak data between accounts' do - # If account isolation works correctly, the counts should be different - account1_count = described_class.new(account, params).aggregate_value - account2_count = described_class.new(account2, params).aggregate_value - - expect(account1_count).to eq(2) - expect(account2_count).to eq(3) - end - end -end diff --git a/spec/builders/v2/reports/timeseries/report_builder_spec.rb b/spec/builders/v2/reports/timeseries/report_builder_spec.rb new file mode 100644 index 000000000..4ecb2432e --- /dev/null +++ b/spec/builders/v2/reports/timeseries/report_builder_spec.rb @@ -0,0 +1,451 @@ +require 'rails_helper' + +describe V2::Reports::Timeseries::ReportBuilder do + describe 'average metrics' do + subject { described_class.new(account, params) } + + let(:account) { create(:account) } + let(:team) { create(:team, account: account) } + let(:inbox) { create(:inbox, account: account) } + let(:label) { create(:label, title: 'spec-billing', account: account) } + let!(:conversation) { create(:conversation, account: account, inbox: inbox, team: team) } + let(:current_time) { '26.10.2020 10:00'.to_datetime } + + let(:params) do + { + type: filter_type, + business_hours: business_hours, + timezone_offset: timezone_offset, + group_by: group_by, + metric: metric, + since: (current_time - 1.week).beginning_of_day.to_i.to_s, + until: current_time.end_of_day.to_i.to_s, + id: filter_id + } + end + let(:timezone_offset) { nil } + let(:group_by) { 'day' } + let(:metric) { 'avg_first_response_time' } + let(:business_hours) { false } + let(:filter_type) { :account } + let(:filter_id) { '' } + + before do + travel_to current_time + conversation.label_list.add(label.title) + conversation.save! + create(:reporting_event, name: 'first_response', value: 80, value_in_business_hours: 10, account: account, created_at: Time.zone.now, + conversation: conversation, inbox: inbox) + create(:reporting_event, name: 'first_response', value: 100, value_in_business_hours: 20, account: account, created_at: 1.hour.ago) + create(:reporting_event, name: 'first_response', value: 93, value_in_business_hours: 30, account: account, created_at: 1.week.ago) + end + + describe '#timeseries' do + it 'returns the correct values' do + timeseries_values = subject.timeseries + + expect(timeseries_values).to eq( + [ + { count: 1, timestamp: 1_603_065_600, value: 93.0 }, + { count: 0, timestamp: 1_603_152_000, value: 0 }, + { count: 0, timestamp: 1_603_238_400, value: 0 }, + { count: 0, timestamp: 1_603_324_800, value: 0 }, + { count: 0, timestamp: 1_603_411_200, value: 0 }, + { count: 0, timestamp: 1_603_497_600, value: 0 }, + { count: 0, timestamp: 1_603_584_000, value: 0 }, + { count: 2, timestamp: 1_603_670_400, value: 90.0 } + ] + ) + end + + context 'when business hours is provided' do + let(:business_hours) { true } + + it 'returns correct timeseries' do + timeseries_values = subject.timeseries + + expect(timeseries_values).to eq( + [ + { count: 1, timestamp: 1_603_065_600, value: 30.0 }, + { count: 0, timestamp: 1_603_152_000, value: 0 }, + { count: 0, timestamp: 1_603_238_400, value: 0 }, + { count: 0, timestamp: 1_603_324_800, value: 0 }, + { count: 0, timestamp: 1_603_411_200, value: 0 }, + { count: 0, timestamp: 1_603_497_600, value: 0 }, + { count: 0, timestamp: 1_603_584_000, value: 0 }, + { count: 2, timestamp: 1_603_670_400, value: 15.0 } + ] + ) + end + end + + context 'when rollups are enabled' do + let(:timezone_offset) { '5.5' } + + before do + account.update!(reporting_timezone: 'Chennai') + allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) + + create(:reporting_events_rollup, + account: account, + date: (current_time - 1.week).to_date, + dimension_type: 'account', + dimension_id: account.id, + metric: 'first_response', + count: 1, + sum_value: 93.0, + sum_value_business_hours: 30.0) + + create(:reporting_events_rollup, + account: account, + date: current_time.to_date, + dimension_type: 'account', + dimension_id: account.id, + metric: 'first_response', + count: 2, + sum_value: 180.0, + sum_value_business_hours: 30.0) + end + + it 'preserves empty buckets in the timeseries' do + rollup_timezone = ActiveSupport::TimeZone['Chennai'] + rollup_start_date = DateTime.strptime(params[:since], '%s').in_time_zone(rollup_timezone).to_date + rollup_end_date = (DateTime.strptime(params[:until], '%s') - 1.second).in_time_zone(rollup_timezone).to_date + rollup_dates = rollup_start_date..rollup_end_date + + expected_timeseries = rollup_dates.map do |date| + value = if date == (current_time - 1.week).to_date + 93.0 + elsif date == current_time.to_date + 90.0 + else + 0 + end + + count = if date == (current_time - 1.week).to_date + 1 + elsif date == current_time.to_date + 2 + else + 0 + end + + { count: count, timestamp: date.in_time_zone('Chennai').to_i, value: value } + end + + expect(subject.timeseries).to eq(expected_timeseries) + end + end + + context 'when group_by is provided' do + let(:group_by) { 'week' } + + it 'returns correct timeseries' do + timeseries_values = subject.timeseries + expect(timeseries_values).to eq( + [ + { count: 1, timestamp: (current_time - 1.week).beginning_of_week(:sunday).to_i, value: 93.0 }, + { count: 2, timestamp: current_time.beginning_of_week(:sunday).to_i, value: 90.0 } + ] + ) + end + end + + context 'when timezone offset is provided' do + let(:timezone_offset) { '5.5' } + let(:group_by) { 'week' } + + it 'returns correct timeseries' do + timeseries_values = subject.timeseries + expect(timeseries_values).to eq( + [ + { count: 1, timestamp: (current_time - 1.week).in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 93.0 }, + { count: 2, timestamp: current_time.in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 90.0 } + ] + ) + end + end + + context 'when weekly rollups are enabled' do + let(:group_by) { 'week' } + let(:timezone_offset) { '5.5' } + + before do + account.update!(reporting_timezone: 'Chennai') + allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) + + create(:reporting_events_rollup, + account: account, + date: current_time.to_date - 1.day, + dimension_type: 'account', + dimension_id: account.id, + metric: 'first_response', + count: 1, + sum_value: 80.0, + sum_value_business_hours: 10.0) + + create(:reporting_events_rollup, + account: account, + date: current_time.to_date, + dimension_type: 'account', + dimension_id: account.id, + metric: 'first_response', + count: 1, + sum_value: 100.0, + sum_value_business_hours: 20.0) + end + + it 'groups weeks using sunday boundaries' do + expect(subject.timeseries).to eq( + [ + { count: 0, timestamp: (current_time - 1.week).in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 0 }, + { count: 2, timestamp: current_time.in_time_zone('Chennai').beginning_of_week(:sunday).to_i, value: 90.0 } + ] + ) + end + end + + context 'when the label filter is applied' do + let(:group_by) { 'week' } + let(:filter_type) { 'label' } + let(:filter_id) { label.id } + + it 'returns correct timeseries' do + timeseries_values = subject.timeseries + start_of_the_week = current_time.beginning_of_week(:sunday).to_i + last_week_start_of_the_week = (current_time - 1.week).beginning_of_week(:sunday).to_i + expect(timeseries_values).to eq( + [ + { count: 0, timestamp: last_week_start_of_the_week, value: 0 }, + { count: 1, timestamp: start_of_the_week, value: 80.0 } + ] + ) + end + end + + context 'when the inbox filter is applied' do + let(:group_by) { 'week' } + let(:filter_type) { 'inbox' } + let(:filter_id) { inbox.id } + + it 'returns correct timeseries' do + timeseries_values = subject.timeseries + start_of_the_week = current_time.beginning_of_week(:sunday).to_i + last_week_start_of_the_week = (current_time - 1.week).beginning_of_week(:sunday).to_i + expect(timeseries_values).to eq( + [ + { count: 0, timestamp: last_week_start_of_the_week, value: 0 }, + { count: 1, timestamp: start_of_the_week, value: 80.0 } + ] + ) + end + end + + context 'when the team filter is applied' do + let(:group_by) { 'week' } + let(:filter_type) { 'team' } + let(:filter_id) { team.id } + + it 'returns correct timeseries' do + timeseries_values = subject.timeseries + start_of_the_week = current_time.beginning_of_week(:sunday).to_i + last_week_start_of_the_week = (current_time - 1.week).beginning_of_week(:sunday).to_i + expect(timeseries_values).to eq( + [ + { count: 0, timestamp: last_week_start_of_the_week, value: 0 }, + { count: 1, timestamp: start_of_the_week, value: 80.0 } + ] + ) + end + end + end + + describe '#aggregate_value' do + context 'when there is no filter applied' do + it 'returns the correct average value' do + expect(subject.aggregate_value).to eq 91.0 + end + end + + context 'when rollups are enabled and the agent does not exist' do + let(:filter_type) { :agent } + let(:filter_id) { '999999' } + let(:timezone_offset) { '0' } + + before do + account.update!(reporting_timezone: 'Etc/UTC') + allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) + end + + it 'raises record not found to preserve raw path behavior' do + expect { subject.aggregate_value }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + end + + describe 'count metrics' do + subject { described_class.new(account, params) } + + let(:account) { create(:account) } + let(:account2) { create(:account) } + let(:user) { create(:user, email: 'agent1@example.com') } + let(:inbox) { create(:inbox, account: account) } + let(:inbox2) { create(:inbox, account: account2) } + let(:current_time) { Time.current } + + let(:params) do + { + type: 'agent', + metric: 'resolutions_count', + since: since_time.beginning_of_day.to_i.to_s, + until: current_time.end_of_day.to_i.to_s, + timezone_offset: timezone_offset, + group_by: group_by, + id: user.id.to_s + } + end + let(:group_by) { 'day' } + let(:since_time) { current_time - 1.day } + let(:timezone_offset) { nil } + + before do + travel_to current_time + + create(:account_user, account: account, user: user) + create(:account_user, account: account2, user: user) + + conversation1 = create(:conversation, account: account, inbox: inbox, assignee: user) + conversation2 = create(:conversation, account: account, inbox: inbox, assignee: user) + + conversation3 = create(:conversation, account: account2, inbox: inbox2, assignee: user) + conversation4 = create(:conversation, account: account2, inbox: inbox2, assignee: user) + + create(:reporting_event, + name: 'conversation_resolved', + account: account, + user: user, + conversation: conversation1, + created_at: current_time - 12.hours) + + create(:reporting_event, + name: 'conversation_resolved', + account: account, + user: user, + conversation: conversation2, + created_at: current_time - 6.hours) + + create(:reporting_event, + name: 'conversation_resolved', + account: account2, + user: user, + conversation: conversation3, + created_at: current_time - 8.hours) + + create(:reporting_event, + name: 'conversation_resolved', + account: account2, + user: user, + conversation: conversation4, + created_at: current_time - 4.hours) + + conversation5 = create(:conversation, account: account2, inbox: inbox2, assignee: user) + create(:reporting_event, + name: 'conversation_resolved', + account: account2, + user: user, + conversation: conversation5, + created_at: current_time - 2.hours) + end + + describe '#aggregate_value' do + it 'returns only resolutions performed by the user in the specified account' do + expect(subject.aggregate_value).to eq(2) + end + + context 'when rollups are enabled and the agent does not exist' do + let(:timezone_offset) { '0' } + + let(:params) do + super().merge(id: '999999') + end + + before do + account.update!(reporting_timezone: 'Etc/UTC') + allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) + end + + it 'raises record not found to preserve raw path behavior' do + expect { subject.aggregate_value }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'when querying account2' do + subject { described_class.new(account2, params) } + + it 'returns only resolutions for account2' do + expect(subject.aggregate_value).to eq(3) + end + end + end + + describe '#timeseries' do + it 'filters resolutions by account' do + result = subject.timeseries + total_count = result.sum { |row| row[:value] } + expect(total_count).to eq(2) + end + + context 'when rollups are enabled and grouped by week' do + let(:group_by) { 'week' } + let(:current_time) { Time.zone.parse('2020-10-26 10:00:00 UTC') } + let(:since_time) { current_time - 1.week } + let(:timezone_offset) { '5.5' } + + before do + account.update!(reporting_timezone: 'Chennai') + allow(account).to receive(:feature_enabled?).with('reporting_events_rollup').and_return(true) + + create(:reporting_events_rollup, + account: account, + date: current_time.to_date - 1.day, + dimension_type: 'agent', + dimension_id: user.id, + metric: 'resolutions_count', + count: 1, + sum_value: 0.0, + sum_value_business_hours: 0.0) + + create(:reporting_events_rollup, + account: account, + date: current_time.to_date, + dimension_type: 'agent', + dimension_id: user.id, + metric: 'resolutions_count', + count: 1, + sum_value: 0.0, + sum_value_business_hours: 0.0) + end + + it 'groups weeks using sunday boundaries' do + expect(subject.timeseries).to eq( + [ + { value: 0, timestamp: (current_time - 1.week).in_time_zone('Chennai').beginning_of_week(:sunday).to_i }, + { value: 2, timestamp: current_time.in_time_zone('Chennai').beginning_of_week(:sunday).to_i } + ] + ) + end + end + end + + describe 'account isolation' do + it 'does not leak data between accounts' do + account1_count = described_class.new(account, params).aggregate_value + account2_count = described_class.new(account2, params).aggregate_value + + expect(account1_count).to eq(2) + expect(account2_count).to eq(3) + end + end + end +end