feat: Add live agent load report api (#4297)
This change allows the admin user to fetch conversation metrics for an account, agents, and filter conversation metrics for a specific agent. Fixes #4305
This commit is contained in:
parent
ccf52a620b
commit
5e8fd689c9
14 changed files with 455 additions and 119 deletions
|
@ -1,5 +1,6 @@
|
|||
class V2::ReportBuilder
|
||||
include DateRangeHelper
|
||||
include ReportHelper
|
||||
attr_reader :account, :params
|
||||
|
||||
DEFAULT_GROUP_BY = 'day'.freeze
|
||||
|
@ -40,23 +41,16 @@ class V2::ReportBuilder
|
|||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
case params[:type]
|
||||
when :account
|
||||
account
|
||||
when :inbox
|
||||
inbox
|
||||
when :agent
|
||||
user
|
||||
when :label
|
||||
label
|
||||
when :team
|
||||
team
|
||||
def conversation_metrics
|
||||
if params[:type].equal?(:account)
|
||||
conversations
|
||||
else
|
||||
agent_metrics
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inbox
|
||||
@inbox ||= account.inboxes.find(params[:id])
|
||||
end
|
||||
|
@ -84,47 +78,26 @@ class V2::ReportBuilder
|
|||
)
|
||||
end
|
||||
|
||||
def conversations_count
|
||||
(get_grouped_values scope.conversations).count
|
||||
def agent_metrics
|
||||
users = @account.users
|
||||
users = users.where(id: params[:user_id]) if params[:user_id].present?
|
||||
users.each_with_object([]) do |user, arr|
|
||||
@user = user
|
||||
arr << {
|
||||
user: { id: user.id, name: user.name, thumbnail: user.avatar_url },
|
||||
metric: conversations
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def incoming_messages_count
|
||||
(get_grouped_values scope.messages.incoming.unscope(:order)).count
|
||||
end
|
||||
|
||||
def outgoing_messages_count
|
||||
(get_grouped_values scope.messages.outgoing.unscope(:order)).count
|
||||
end
|
||||
|
||||
def resolutions_count
|
||||
(get_grouped_values scope.conversations.resolved).count
|
||||
end
|
||||
|
||||
def avg_first_response_time
|
||||
(get_grouped_values scope.reporting_events.where(name: 'first_response')).average(:value)
|
||||
end
|
||||
|
||||
def avg_resolution_time
|
||||
(get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')).average(:value)
|
||||
end
|
||||
|
||||
def avg_resolution_time_summary
|
||||
avg_rt = scope.reporting_events
|
||||
.where(name: 'conversation_resolved', created_at: range)
|
||||
.average(:value)
|
||||
|
||||
return 0 if avg_rt.blank?
|
||||
|
||||
avg_rt
|
||||
end
|
||||
|
||||
def avg_first_response_time_summary
|
||||
avg_frt = scope.reporting_events
|
||||
.where(name: 'first_response', created_at: range)
|
||||
.average(:value)
|
||||
|
||||
return 0 if avg_frt.blank?
|
||||
|
||||
avg_frt
|
||||
def conversations
|
||||
@open_conversations = scope.conversations.open
|
||||
first_response_count = scope.reporting_events.where(name: 'first_response', conversation_id: @open_conversations.pluck('id')).count
|
||||
metric = {
|
||||
open: @open_conversations.count,
|
||||
unattended: @open_conversations.count - first_response_count
|
||||
}
|
||||
metric[:unassigned] = @open_conversations.unassigned.count if params[:type].equal?(:account)
|
||||
metric
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,12 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||
render layout: false, template: 'api/v2/accounts/reports/teams.csv.erb', format: 'csv'
|
||||
end
|
||||
|
||||
def conversations
|
||||
return head :unprocessable_entity if params[:type].blank?
|
||||
|
||||
render json: conversation_metrics
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
|
@ -73,6 +79,13 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||
}
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
type: params[:type].to_sym,
|
||||
user_id: params[:user_id]
|
||||
}
|
||||
end
|
||||
|
||||
def range
|
||||
{
|
||||
current: {
|
||||
|
@ -91,4 +104,8 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||
summary[:previous] = V2::ReportBuilder.new(Current.account, previous_summary_params).summary
|
||||
summary
|
||||
end
|
||||
|
||||
def conversation_metrics
|
||||
V2::ReportBuilder.new(Current.account, conversation_params).conversation_metrics
|
||||
end
|
||||
end
|
||||
|
|
62
app/helpers/report_helper.rb
Normal file
62
app/helpers/report_helper.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
module ReportHelper
|
||||
private
|
||||
|
||||
def scope
|
||||
case params[:type]
|
||||
when :account
|
||||
account
|
||||
when :inbox
|
||||
inbox
|
||||
when :agent
|
||||
user
|
||||
when :label
|
||||
label
|
||||
when :team
|
||||
team
|
||||
end
|
||||
end
|
||||
|
||||
def conversations_count
|
||||
(get_grouped_values scope.conversations).count
|
||||
end
|
||||
|
||||
def incoming_messages_count
|
||||
(get_grouped_values scope.messages.incoming.unscope(:order)).count
|
||||
end
|
||||
|
||||
def outgoing_messages_count
|
||||
(get_grouped_values scope.messages.outgoing.unscope(:order)).count
|
||||
end
|
||||
|
||||
def resolutions_count
|
||||
(get_grouped_values scope.conversations.resolved).count
|
||||
end
|
||||
|
||||
def avg_first_response_time
|
||||
(get_grouped_values scope.reporting_events.where(name: 'first_response')).average(:value)
|
||||
end
|
||||
|
||||
def avg_resolution_time
|
||||
(get_grouped_values scope.reporting_events.where(name: 'conversation_resolved')).average(:value)
|
||||
end
|
||||
|
||||
def avg_resolution_time_summary
|
||||
avg_rt = scope.reporting_events
|
||||
.where(name: 'conversation_resolved', created_at: range)
|
||||
.average(:value)
|
||||
|
||||
return 0 if avg_rt.blank?
|
||||
|
||||
avg_rt
|
||||
end
|
||||
|
||||
def avg_first_response_time_summary
|
||||
avg_frt = scope.reporting_events
|
||||
.where(name: 'first_response', created_at: range)
|
||||
.average(:value)
|
||||
|
||||
return 0 if avg_frt.blank?
|
||||
|
||||
avg_frt
|
||||
end
|
||||
end
|
|
@ -212,6 +212,7 @@ Rails.application.routes.draw do
|
|||
get :inboxes
|
||||
get :labels
|
||||
get :teams
|
||||
get :conversations
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,6 +57,68 @@ RSpec.describe 'Reports API', type: :request do
|
|||
expect(current_day_metric.length).to eq(1)
|
||||
expect(current_day_metric[0]['value']).to eq(10)
|
||||
end
|
||||
|
||||
it 'return conversation metrics in account level' do
|
||||
unassigned_conversation = create(:conversation, account: account, inbox: inbox,
|
||||
assignee: nil, created_at: Time.zone.today)
|
||||
unassigned_conversation.save!
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||
params: {
|
||||
type: :account
|
||||
},
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
json_response = JSON.parse(response.body)
|
||||
|
||||
expect(json_response['open']).to eq(11)
|
||||
expect(json_response['unattended']).to eq(11)
|
||||
expect(json_response['unassigned']).to eq(1)
|
||||
end
|
||||
|
||||
it 'return conversation metrics for user in account level' do
|
||||
create_list(:conversation, 2, account: account, inbox: inbox,
|
||||
assignee: admin, created_at: Time.zone.today)
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||
params: {
|
||||
type: :agent
|
||||
},
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response.blank?).to be false
|
||||
user_metrics = json_response.find { |item| item['user']['name'] == admin[:name] }
|
||||
expect(user_metrics.present?).to be true
|
||||
|
||||
expect(user_metrics['metric']['open']).to eq(2)
|
||||
expect(user_metrics['metric']['unattended']).to eq(2)
|
||||
end
|
||||
|
||||
it 'return conversation metrics for specific user in account level' do
|
||||
create_list(:conversation, 2, account: account, inbox: inbox,
|
||||
assignee: admin, created_at: Time.zone.today)
|
||||
|
||||
get "/api/v2/accounts/#{account.id}/reports/conversations",
|
||||
params: {
|
||||
type: :agent,
|
||||
user_id: user.id
|
||||
},
|
||||
headers: admin.create_new_auth_token,
|
||||
as: :json
|
||||
|
||||
expect(response).to have_http_status(:success)
|
||||
|
||||
json_response = JSON.parse(response.body)
|
||||
expect(json_response.blank?).to be false
|
||||
expect(json_response[0]['metric']['open']).to eq(10)
|
||||
expect(json_response[0]['metric']['unattended']).to eq(10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -149,10 +149,11 @@ extended_message:
|
|||
- $ref: ./resource/extension/message/with_source_sender.yml
|
||||
|
||||
|
||||
## report list
|
||||
report:
|
||||
type: array
|
||||
description: 'array of conversation count based on date'
|
||||
items:
|
||||
allOf:
|
||||
- $ref: './resource/report.yml'
|
||||
## report
|
||||
account_summary:
|
||||
$ref: './resource/reports/summary.yml'
|
||||
agent_conversation_metrics:
|
||||
$ref: './resource/reports/conversation/agent.yml'
|
||||
|
||||
|
||||
|
||||
|
|
18
swagger/definitions/resource/reports/conversation/agent.yml
Normal file
18
swagger/definitions/resource/reports/conversation/agent.yml
Normal file
|
@ -0,0 +1,18 @@
|
|||
type: object
|
||||
properties:
|
||||
user:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: number
|
||||
name:
|
||||
type: string
|
||||
thumbnail:
|
||||
type: string
|
||||
metric:
|
||||
type: object
|
||||
properties:
|
||||
open:
|
||||
type: number
|
||||
unattended:
|
||||
type: number
|
23
swagger/paths/application/reports/conversation/account.yml
Normal file
23
swagger/paths/application/reports/conversation/account.yml
Normal file
|
@ -0,0 +1,23 @@
|
|||
tags:
|
||||
- Reports
|
||||
operationId: get-account-conversation-metrics
|
||||
summary: Account Conversation Metrics
|
||||
description: Get conversation metrics for Account
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
schema:
|
||||
type: object
|
||||
description: 'Object of account conversation metrics'
|
||||
properties:
|
||||
open:
|
||||
type: number
|
||||
unattended:
|
||||
type: number
|
||||
unassigned:
|
||||
type: number
|
||||
|
||||
404:
|
||||
description: reports not found
|
||||
403:
|
||||
description: Access denied
|
18
swagger/paths/application/reports/conversation/agent.yml
Normal file
18
swagger/paths/application/reports/conversation/agent.yml
Normal file
|
@ -0,0 +1,18 @@
|
|||
tags:
|
||||
- Reports
|
||||
operationId: get-agent-conversation-metrics
|
||||
summary: Agent Conversation Metrics
|
||||
description: Get conversation metrics for Agent
|
||||
responses:
|
||||
200:
|
||||
description: Success
|
||||
schema:
|
||||
type: array
|
||||
description: 'Array of agent based conversation metrics'
|
||||
items:
|
||||
$ref: '#/definitions/agent_conversation_metrics'
|
||||
|
||||
404:
|
||||
description: reports not found
|
||||
403:
|
||||
description: Access denied
|
|
@ -10,7 +10,12 @@ responses:
|
|||
type: array
|
||||
description: 'Array of date based conversation statistics'
|
||||
items:
|
||||
$ref: '#/definitions/report'
|
||||
type: object
|
||||
properties:
|
||||
value:
|
||||
type: string
|
||||
timestamp:
|
||||
type: number
|
||||
404:
|
||||
description: reports not found
|
||||
403:
|
||||
|
|
|
@ -7,10 +7,8 @@ responses:
|
|||
200:
|
||||
description: Success
|
||||
schema:
|
||||
type: array
|
||||
description: 'Array of date based conversation statistics'
|
||||
items:
|
||||
$ref: '#/definitions/report'
|
||||
description: 'Object of summary metrics'
|
||||
$ref: '#/definitions/account_summary'
|
||||
404:
|
||||
description: reports not found
|
||||
403:
|
||||
|
|
|
@ -391,3 +391,35 @@
|
|||
description: The timestamp from where report should stop.
|
||||
get:
|
||||
$ref: './application/reports/summary.yml'
|
||||
|
||||
# Conversation metrics for account
|
||||
/api/v2/accounts/{account_id}/reports/conversations:
|
||||
parameters:
|
||||
- $ref: '#/parameters/account_id'
|
||||
- in: query
|
||||
name: type
|
||||
type: string
|
||||
enum:
|
||||
- account
|
||||
required: true
|
||||
description: Type of report
|
||||
get:
|
||||
$ref: './application/reports/conversation/account.yml'
|
||||
|
||||
# Conversation metrics for agent
|
||||
/api/v2/accounts/{account_id}/reports/conversations/:
|
||||
parameters:
|
||||
- $ref: '#/parameters/account_id'
|
||||
- in: query
|
||||
name: type
|
||||
type: string
|
||||
enum:
|
||||
- agent
|
||||
required: true
|
||||
description: Type of report
|
||||
- in: query
|
||||
name: user_id
|
||||
type: string
|
||||
description: The numeric ID of the user
|
||||
get:
|
||||
$ref: './application/reports/conversation/agent.yml'
|
|
@ -3604,7 +3604,15 @@
|
|||
"type": "array",
|
||||
"description": "Array of date based conversation statistics",
|
||||
"items": {
|
||||
"$ref": "#/definitions/report"
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -3651,14 +3659,110 @@
|
|||
"operationId": "list-all-conversation-statistics-summary",
|
||||
"summary": "Get Account reports summary",
|
||||
"description": "Get Account reports summary for a specific type and date range",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/account_summary"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "reports not found"
|
||||
},
|
||||
"403": {
|
||||
"description": "Access denied"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/accounts/{account_id}/reports/conversations": {
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/account_id"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"account"
|
||||
],
|
||||
"required": true,
|
||||
"description": "Type of report"
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"tags": [
|
||||
"Reports"
|
||||
],
|
||||
"operationId": "get-account-conversation-metrics",
|
||||
"summary": "Account Conversation Metrics",
|
||||
"description": "Get conversation metrics for Account",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"description": "Object of account conversation metrics",
|
||||
"properties": {
|
||||
"open": {
|
||||
"type": "number"
|
||||
},
|
||||
"unattended": {
|
||||
"type": "number"
|
||||
},
|
||||
"unassigned": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "reports not found"
|
||||
},
|
||||
"403": {
|
||||
"description": "Access denied"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v2/accounts/{account_id}/reports/conversations/": {
|
||||
"parameters": [
|
||||
{
|
||||
"$ref": "#/parameters/account_id"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "type",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"agent"
|
||||
],
|
||||
"required": true,
|
||||
"description": "Type of report"
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "user_id",
|
||||
"type": "string",
|
||||
"description": "The numeric ID of the user"
|
||||
}
|
||||
],
|
||||
"get": {
|
||||
"tags": [
|
||||
"Reports"
|
||||
],
|
||||
"operationId": "get-agent-conversation-metrics",
|
||||
"summary": "Agent Conversation Metrics",
|
||||
"description": "Get conversation metrics for Agent",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"description": "Array of date based conversation statistics",
|
||||
"description": "Array of agent based conversation metrics",
|
||||
"items": {
|
||||
"$ref": "#/definitions/report"
|
||||
"$ref": "#/definitions/agent_conversation_metrics"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4879,58 +4983,80 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"type": "array",
|
||||
"description": "array of conversation count based on date",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_first_response_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"avg_resolution_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"conversations_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"incoming_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"outgoing_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"resolutions_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"previous": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_first_response_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"avg_resolution_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"conversations_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"incoming_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"outgoing_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"resolutions_count": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
"account_summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_first_response_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"avg_resolution_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"conversations_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"incoming_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"outgoing_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"resolutions_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"previous": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avg_first_response_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"avg_resolution_time": {
|
||||
"type": "string"
|
||||
},
|
||||
"conversations_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"incoming_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"outgoing_messages_count": {
|
||||
"type": "number"
|
||||
},
|
||||
"resolutions_count": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"agent_conversation_metrics": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metric": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"open": {
|
||||
"type": "number"
|
||||
},
|
||||
"unattended": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue