mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 03:57:52 +00:00
feat(v4): Add API to fetch aggregate reports for inboxes (#10604)
The Inbox Overview section is being updated to offer a more detailed report, showing an overall view of the account grouped by inboxes. To view detailed reports and access specific graphs for individual inboxes, click on the inbox name to navigate to its dedicated report page. --------- Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
62
app/builders/v2/reports/inbox_summary_builder.rb
Normal file
62
app/builders/v2/reports/inbox_summary_builder.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
class V2::Reports::InboxSummaryBuilder < V2::Reports::BaseSummaryBuilder
|
||||
pattr_initialize [:account!, :params!]
|
||||
|
||||
def build
|
||||
load_data
|
||||
prepare_report
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :conversations_count, :resolved_count,
|
||||
:avg_resolution_time, :avg_first_response_time, :avg_reply_time
|
||||
|
||||
def load_data
|
||||
@conversations_count = fetch_conversations_count
|
||||
@resolved_count = fetch_resolved_count
|
||||
@avg_resolution_time = fetch_average_time('conversation_resolved')
|
||||
@avg_first_response_time = fetch_average_time('first_response')
|
||||
@avg_reply_time = fetch_average_time('reply_time')
|
||||
end
|
||||
|
||||
def fetch_conversations_count
|
||||
account.conversations.where(created_at: range).group(group_by_key).count
|
||||
end
|
||||
|
||||
def fetch_resolved_count
|
||||
reporting_events.where(name: 'conversation_resolved').group(group_by_key).count
|
||||
end
|
||||
|
||||
def fetch_average_time(event_name)
|
||||
get_grouped_average(reporting_events.where(name: event_name))
|
||||
end
|
||||
|
||||
def reporting_events
|
||||
@reporting_events ||= account.reporting_events.where(created_at: range)
|
||||
end
|
||||
|
||||
def prepare_report
|
||||
account.inboxes.map do |inbox|
|
||||
build_inbox_stats(inbox)
|
||||
end
|
||||
end
|
||||
|
||||
def build_inbox_stats(inbox)
|
||||
{
|
||||
id: inbox.id,
|
||||
conversations_count: conversations_count[inbox.id] || 0,
|
||||
resolved_conversations_count: resolved_count[inbox.id] || 0,
|
||||
avg_resolution_time: avg_resolution_time[inbox.id],
|
||||
avg_first_response_time: avg_first_response_time[inbox.id],
|
||||
avg_reply_time: avg_reply_time[inbox.id]
|
||||
}
|
||||
end
|
||||
|
||||
def group_by_key
|
||||
:inbox_id
|
||||
end
|
||||
|
||||
def average_value_key
|
||||
ActiveModel::Type::Boolean.new.cast(params[:business_hours]) ? :value_in_business_hours : :value
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
before_action :prepare_builder_params, only: [:agent, :team]
|
||||
before_action :prepare_builder_params, only: [:agent, :team, :inbox]
|
||||
|
||||
def agent
|
||||
render_report_with(V2::Reports::AgentSummaryBuilder)
|
||||
@@ -10,6 +10,10 @@ class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseContr
|
||||
render_report_with(V2::Reports::TeamSummaryBuilder)
|
||||
end
|
||||
|
||||
def inbox
|
||||
render_report_with(V2::Reports::InboxSummaryBuilder)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
@@ -26,8 +30,7 @@ class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseContr
|
||||
|
||||
def render_report_with(builder_class)
|
||||
builder = builder_class.new(account: Current.account, params: @builder_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
render json: builder.build
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
|
||||
@@ -328,6 +328,7 @@ Rails.application.routes.draw do
|
||||
collection do
|
||||
get :agent
|
||||
get :team
|
||||
get :inbox
|
||||
end
|
||||
end
|
||||
resources :reports, only: [:index] do
|
||||
|
||||
101
spec/builders/v2/reports/inbox_summary_builder_spec.rb
Normal file
101
spec/builders/v2/reports/inbox_summary_builder_spec.rb
Normal file
@@ -0,0 +1,101 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe V2::Reports::InboxSummaryBuilder do
|
||||
let(:account) { create(:account) }
|
||||
let(:i1) { create(:inbox, account: account) }
|
||||
let(:i2) { create(:inbox, account: account) }
|
||||
let(:params) do
|
||||
{
|
||||
business_hours: business_hours,
|
||||
since: 1.week.ago.beginning_of_day,
|
||||
until: Time.current.end_of_day
|
||||
}
|
||||
end
|
||||
let(:builder) { described_class.new(account: account, params: params) }
|
||||
|
||||
before do
|
||||
c1 = create(:conversation, account: account, inbox: i1, created_at: 2.days.ago)
|
||||
c2 = create(:conversation, account: account, inbox: i2, created_at: 1.day.ago)
|
||||
c2.resolved!
|
||||
create(:reporting_event, account: account, conversation: c2, inbox: i2, name: 'conversation_resolved', value: 100, value_in_business_hours: 60,
|
||||
created_at: 1.day.ago)
|
||||
create(:reporting_event, account: account, conversation: c1, inbox: i1, name: 'first_response', value: 50, value_in_business_hours: 30,
|
||||
created_at: 1.day.ago)
|
||||
create(:reporting_event, account: account, conversation: c1, inbox: i1, name: 'reply_time', value: 30, value_in_business_hours: 10,
|
||||
created_at: 1.day.ago)
|
||||
create(:reporting_event, account: account, conversation: c1, inbox: i1, name: 'reply_time', value: 40, value_in_business_hours: 20,
|
||||
created_at: 1.day.ago)
|
||||
end
|
||||
|
||||
describe '#build' do
|
||||
subject(:report) { builder.build }
|
||||
|
||||
context 'when business hours is disabled' do
|
||||
let(:business_hours) { false }
|
||||
|
||||
it 'includes correct stats for each inbox' do
|
||||
expect(report).to eq(
|
||||
[
|
||||
{
|
||||
id: i1.id,
|
||||
conversations_count: 1,
|
||||
resolved_conversations_count: 0,
|
||||
avg_resolution_time: nil,
|
||||
avg_first_response_time: 50.0,
|
||||
avg_reply_time: 35.0
|
||||
}, {
|
||||
id: i2.id,
|
||||
conversations_count: 1,
|
||||
resolved_conversations_count: 1,
|
||||
avg_resolution_time: 100.0,
|
||||
avg_first_response_time: nil,
|
||||
avg_reply_time: nil
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when business hours is enabled' do
|
||||
let(:business_hours) { true }
|
||||
|
||||
it 'uses business hours values for calculations' do
|
||||
expect(report).to eq(
|
||||
[
|
||||
{
|
||||
id: i1.id,
|
||||
conversations_count: 1,
|
||||
resolved_conversations_count: 0,
|
||||
avg_resolution_time: nil,
|
||||
avg_first_response_time: 30.0,
|
||||
avg_reply_time: 15.0
|
||||
}, {
|
||||
id: i2.id,
|
||||
conversations_count: 1,
|
||||
resolved_conversations_count: 1,
|
||||
avg_resolution_time: 60.0,
|
||||
avg_first_response_time: nil,
|
||||
avg_reply_time: nil
|
||||
}
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is no data for an inbox' do
|
||||
let!(:empty_inbox) { create(:inbox, account: account) }
|
||||
let(:business_hours) { false }
|
||||
|
||||
it 'returns nil values for metrics' do
|
||||
expect(report).to include(
|
||||
id: empty_inbox.id,
|
||||
conversations_count: 0,
|
||||
resolved_conversations_count: 0,
|
||||
avg_resolution_time: nil,
|
||||
avg_first_response_time: nil,
|
||||
avg_reply_time: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -59,6 +59,57 @@ RSpec.describe 'Summary Reports API', type: :request do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/summary_reports/inbox' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/inbox"
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is an authenticated user' do
|
||||
let(:params) do
|
||||
{
|
||||
since: start_of_today.to_s,
|
||||
until: end_of_today.to_s,
|
||||
business_hours: true
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns unauthorized for inbox' do
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/inbox",
|
||||
params: params,
|
||||
headers: agent.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'calls V2::Reports::InboxSummaryBuilder with the right params if the user is an admin' do
|
||||
inbox_summary_builder = double
|
||||
allow(V2::Reports::InboxSummaryBuilder).to receive(:new).and_return(inbox_summary_builder)
|
||||
allow(inbox_summary_builder).to receive(:build).and_return([{ id: 1, conversations_count: 110 }])
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/summary_reports/inbox",
|
||||
params: params,
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(V2::Reports::InboxSummaryBuilder).to have_received(:new).with(account: account, params: params)
|
||||
expect(inbox_summary_builder).to have_received(:build)
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = response.parsed_body
|
||||
|
||||
expect(json_response.length).to eq(1)
|
||||
expect(json_response.first['id']).to eq(1)
|
||||
expect(json_response.first['conversations_count']).to eq(110)
|
||||
expect(json_response.first['avg_reply_time']).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/accounts/:account_id/summary_reports/team' do
|
||||
context 'when it is an unauthenticated user' do
|
||||
it 'returns unauthorized' do
|
||||
|
||||
Reference in New Issue
Block a user