feat: Add an API to support querying metrics by ChannelType (#13255)
This API gives you how many conversations exist per channel, broken down
by status in a given time period. The max time period is capped to 6
months for now.
**Input Params:**
- **since:** Unix timestamp (seconds) - start of date range
- **until:** Unix timestamp (seconds) - end of date range
**Response Payload:**
```json
{
"Channel::Sms": {
"resolved": 85,
"snoozed": 10,
"open": 5,
"pending": 5,
"total": 100
},
"Channel::Email": {
"resolved": 72,
"snoozed": 15,
"open": 13,
"pending": 13,
"total": 100
},
"Channel::WebWidget": {
"resolved": 90,
"snoozed": 7,
"open": 3,
"pending": 3,
"total": 100
}
}
```
**Definitons:**
resolved = Number of conversations created within the selected time
period that are currently marked as resolved.
snoozed = Number of conversations created within the selected time
period that are currently marked as snoozed.
pending = Number of conversations created within the selected time
period that are currently marked as pending.
open = Number of conversations created within the selected time period
that are currently open.
total = Total number of conversations created within the selected time
period, across all statuses.
This commit is contained in:
38
app/builders/v2/reports/channel_summary_builder.rb
Normal file
38
app/builders/v2/reports/channel_summary_builder.rb
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
class V2::Reports::ChannelSummaryBuilder
|
||||||
|
include DateRangeHelper
|
||||||
|
|
||||||
|
pattr_initialize [:account!, :params!]
|
||||||
|
|
||||||
|
def build
|
||||||
|
conversations_by_channel_and_status.transform_values { |status_counts| build_channel_stats(status_counts) }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def conversations_by_channel_and_status
|
||||||
|
account.conversations
|
||||||
|
.joins(:inbox)
|
||||||
|
.where(created_at: range)
|
||||||
|
.group('inboxes.channel_type', 'conversations.status')
|
||||||
|
.count
|
||||||
|
.each_with_object({}) do |((channel_type, status), count), grouped|
|
||||||
|
grouped[channel_type] ||= {}
|
||||||
|
grouped[channel_type][status] = count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_channel_stats(status_counts)
|
||||||
|
open_count = status_counts['open'] || 0
|
||||||
|
resolved_count = status_counts['resolved'] || 0
|
||||||
|
pending_count = status_counts['pending'] || 0
|
||||||
|
snoozed_count = status_counts['snoozed'] || 0
|
||||||
|
|
||||||
|
{
|
||||||
|
open: open_count,
|
||||||
|
resolved: resolved_count,
|
||||||
|
pending: pending_count,
|
||||||
|
snoozed: snoozed_count,
|
||||||
|
total: open_count + resolved_count + pending_count + snoozed_count
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
|
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
|
||||||
before_action :check_authorization
|
before_action :check_authorization
|
||||||
before_action :prepare_builder_params, only: [:agent, :team, :inbox, :label]
|
before_action :prepare_builder_params, only: [:agent, :team, :inbox, :label, :channel]
|
||||||
|
|
||||||
def agent
|
def agent
|
||||||
render_report_with(V2::Reports::AgentSummaryBuilder)
|
render_report_with(V2::Reports::AgentSummaryBuilder)
|
||||||
@@ -18,6 +18,12 @@ class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseContr
|
|||||||
render_report_with(V2::Reports::LabelSummaryBuilder)
|
render_report_with(V2::Reports::LabelSummaryBuilder)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def channel
|
||||||
|
return render_could_not_create_error(I18n.t('errors.reports.date_range_too_long')) if date_range_too_long?
|
||||||
|
|
||||||
|
render_report_with(V2::Reports::ChannelSummaryBuilder)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def check_authorization
|
def check_authorization
|
||||||
@@ -40,4 +46,12 @@ class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseContr
|
|||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:since, :until, :business_hours)
|
params.permit(:since, :until, :business_hours)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def date_range_too_long?
|
||||||
|
return false if permitted_params[:since].blank? || permitted_params[:until].blank?
|
||||||
|
|
||||||
|
since_time = Time.zone.at(permitted_params[:since].to_i)
|
||||||
|
until_time = Time.zone.at(permitted_params[:until].to_i)
|
||||||
|
(until_time - since_time) > 6.months
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ en:
|
|||||||
plan_not_eligible: Top-ups are only available for paid plans. Please upgrade your plan first.
|
plan_not_eligible: Top-ups are only available for paid plans. Please upgrade your plan first.
|
||||||
stripe_customer_not_configured: Stripe customer not configured
|
stripe_customer_not_configured: Stripe customer not configured
|
||||||
no_payment_method: No payment methods found. Please add a payment method before making a purchase.
|
no_payment_method: No payment methods found. Please add a payment method before making a purchase.
|
||||||
|
reports:
|
||||||
|
date_range_too_long: Date range cannot exceed 6 months
|
||||||
profile:
|
profile:
|
||||||
mfa:
|
mfa:
|
||||||
enabled: MFA enabled successfully
|
enabled: MFA enabled successfully
|
||||||
|
|||||||
@@ -418,6 +418,7 @@ Rails.application.routes.draw do
|
|||||||
get :team
|
get :team
|
||||||
get :inbox
|
get :inbox
|
||||||
get :label
|
get :label
|
||||||
|
get :channel
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :reports, only: [:index] do
|
resources :reports, only: [:index] do
|
||||||
|
|||||||
92
spec/builders/v2/reports/channel_summary_builder_spec.rb
Normal file
92
spec/builders/v2/reports/channel_summary_builder_spec.rb
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe V2::Reports::ChannelSummaryBuilder do
|
||||||
|
let!(:account) { create(:account) }
|
||||||
|
let!(:web_widget_inbox) { create(:inbox, account: account) }
|
||||||
|
let!(:email_inbox) { create(:inbox, :with_email, account: account) }
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
since: 1.week.ago.beginning_of_day,
|
||||||
|
until: Time.current.end_of_day
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:builder) { described_class.new(account: account, params: params) }
|
||||||
|
|
||||||
|
describe '#build' do
|
||||||
|
subject(:report) { builder.build }
|
||||||
|
|
||||||
|
context 'when there are conversations with different statuses across channels' do
|
||||||
|
before do
|
||||||
|
# Web widget conversations
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :open, created_at: 2.days.ago)
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :open, created_at: 3.days.ago)
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :resolved, created_at: 2.days.ago)
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :pending, created_at: 1.day.ago)
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :snoozed, created_at: 1.day.ago)
|
||||||
|
|
||||||
|
# Email conversations
|
||||||
|
create(:conversation, account: account, inbox: email_inbox, status: :open, created_at: 2.days.ago)
|
||||||
|
create(:conversation, account: account, inbox: email_inbox, status: :resolved, created_at: 1.day.ago)
|
||||||
|
create(:conversation, account: account, inbox: email_inbox, status: :resolved, created_at: 3.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns correct counts grouped by channel type' do
|
||||||
|
expect(report['Channel::WebWidget']).to eq(
|
||||||
|
open: 2,
|
||||||
|
resolved: 1,
|
||||||
|
pending: 1,
|
||||||
|
snoozed: 1,
|
||||||
|
total: 5
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(report['Channel::Email']).to eq(
|
||||||
|
open: 1,
|
||||||
|
resolved: 2,
|
||||||
|
pending: 0,
|
||||||
|
snoozed: 0,
|
||||||
|
total: 3
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when conversations are outside the date range' do
|
||||||
|
before do
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :open, created_at: 2.days.ago)
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :resolved, created_at: 2.weeks.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'only includes conversations within the date range' do
|
||||||
|
expect(report['Channel::WebWidget']).to eq(
|
||||||
|
open: 1,
|
||||||
|
resolved: 0,
|
||||||
|
pending: 0,
|
||||||
|
snoozed: 0,
|
||||||
|
total: 1
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when there are no conversations' do
|
||||||
|
it 'returns an empty hash' do
|
||||||
|
expect(report).to eq({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a channel has only one status type' do
|
||||||
|
before do
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :resolved, created_at: 1.day.ago)
|
||||||
|
create(:conversation, account: account, inbox: web_widget_inbox, status: :resolved, created_at: 2.days.ago)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns zeros for other statuses' do
|
||||||
|
expect(report['Channel::WebWidget']).to eq(
|
||||||
|
open: 0,
|
||||||
|
resolved: 2,
|
||||||
|
pending: 0,
|
||||||
|
snoozed: 0,
|
||||||
|
total: 2
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -10,19 +10,14 @@ RSpec.describe 'API Base', type: :request do
|
|||||||
let!(:conversation) { create(:conversation, account: account) }
|
let!(:conversation) { create(:conversation, account: account) }
|
||||||
|
|
||||||
it 'sets Current attributes for the request and then returns the response' do
|
it 'sets Current attributes for the request and then returns the response' do
|
||||||
# expect Current.account_user is set to the admin's account_user
|
# This test verifies that Current.user, Current.account, and Current.account_user
|
||||||
allow(Current).to receive(:user=).and_call_original
|
# are properly set during request processing. We verify this indirectly:
|
||||||
allow(Current).to receive(:account=).and_call_original
|
# - A successful response proves Current.account_user was set (required for authorization)
|
||||||
allow(Current).to receive(:account_user=).and_call_original
|
# - The correct conversation data proves Current.account was set (scopes the query)
|
||||||
|
|
||||||
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}",
|
||||||
headers: { api_access_token: admin.access_token.token },
|
headers: { api_access_token: admin.access_token.token },
|
||||||
as: :json
|
as: :json
|
||||||
|
|
||||||
expect(Current).to have_received(:user=).with(admin).at_least(:once)
|
|
||||||
expect(Current).to have_received(:account=).with(account).at_least(:once)
|
|
||||||
expect(Current).to have_received(:account_user=).with(admin.account_users.first).at_least(:once)
|
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(response.parsed_body['id']).to eq(conversation.display_id)
|
expect(response.parsed_body['id']).to eq(conversation.display_id)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -160,4 +160,68 @@ RSpec.describe 'Summary Reports API', type: :request do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET /api/v2/accounts/:account_id/summary_reports/channel' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get "/api/v2/accounts/#{account.id}/summary_reports/channel"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns unauthorized for agents' do
|
||||||
|
get "/api/v2/accounts/#{account.id}/summary_reports/channel",
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls V2::Reports::ChannelSummaryBuilder with the right params if the user is an admin' do
|
||||||
|
channel_summary_builder = double
|
||||||
|
allow(V2::Reports::ChannelSummaryBuilder).to receive(:new).and_return(channel_summary_builder)
|
||||||
|
allow(channel_summary_builder).to receive(:build)
|
||||||
|
.and_return({
|
||||||
|
'Channel::WebWidget' => { open: 5, resolved: 10, pending: 2, snoozed: 1, total: 18 }
|
||||||
|
})
|
||||||
|
|
||||||
|
get "/api/v2/accounts/#{account.id}/summary_reports/channel",
|
||||||
|
params: params,
|
||||||
|
headers: admin.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(V2::Reports::ChannelSummaryBuilder).to have_received(:new).with(
|
||||||
|
account: account,
|
||||||
|
params: hash_including(since: start_of_today.to_s, until: end_of_today.to_s)
|
||||||
|
)
|
||||||
|
expect(channel_summary_builder).to have_received(:build)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
json_response = response.parsed_body
|
||||||
|
|
||||||
|
expect(json_response['Channel::WebWidget']['open']).to eq(5)
|
||||||
|
expect(json_response['Channel::WebWidget']['total']).to eq(18)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns unprocessable_entity when date range exceeds 6 months' do
|
||||||
|
get "/api/v2/accounts/#{account.id}/summary_reports/channel",
|
||||||
|
params: { since: 1.year.ago.to_i.to_s, until: Time.current.to_i.to_s },
|
||||||
|
headers: admin.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
expect(response.parsed_body['error']).to eq(I18n.t('errors.reports.date_range_too_long'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -223,6 +223,8 @@ account_summary:
|
|||||||
$ref: './resource/reports/summary.yml'
|
$ref: './resource/reports/summary.yml'
|
||||||
agent_conversation_metrics:
|
agent_conversation_metrics:
|
||||||
$ref: './resource/reports/conversation/agent.yml'
|
$ref: './resource/reports/conversation/agent.yml'
|
||||||
|
channel_summary:
|
||||||
|
$ref: './resource/reports/channel_summary.yml'
|
||||||
|
|
||||||
contact_detail:
|
contact_detail:
|
||||||
$ref: ./resource/contact_detail.yml
|
$ref: ./resource/contact_detail.yml
|
||||||
|
|||||||
34
swagger/definitions/resource/reports/channel_summary.yml
Normal file
34
swagger/definitions/resource/reports/channel_summary.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
type: object
|
||||||
|
description: Channel summary report containing conversation counts grouped by channel type and status. Available in version 4.10.0+.
|
||||||
|
additionalProperties:
|
||||||
|
type: object
|
||||||
|
description: Conversation statistics for a specific channel type (e.g., Channel::WebWidget, Channel::Api)
|
||||||
|
properties:
|
||||||
|
open:
|
||||||
|
type: number
|
||||||
|
description: Number of open conversations
|
||||||
|
resolved:
|
||||||
|
type: number
|
||||||
|
description: Number of resolved conversations
|
||||||
|
pending:
|
||||||
|
type: number
|
||||||
|
description: Number of pending conversations
|
||||||
|
snoozed:
|
||||||
|
type: number
|
||||||
|
description: Number of snoozed conversations
|
||||||
|
total:
|
||||||
|
type: number
|
||||||
|
description: Total number of conversations
|
||||||
|
example:
|
||||||
|
Channel::WebWidget:
|
||||||
|
open: 10
|
||||||
|
resolved: 20
|
||||||
|
pending: 5
|
||||||
|
snoozed: 2
|
||||||
|
total: 37
|
||||||
|
Channel::Api:
|
||||||
|
open: 5
|
||||||
|
resolved: 15
|
||||||
|
pending: 3
|
||||||
|
snoozed: 1
|
||||||
|
total: 24
|
||||||
30
swagger/paths/application/reports/channel_summary.yml
Normal file
30
swagger/paths/application/reports/channel_summary.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
tags:
|
||||||
|
- Reports
|
||||||
|
operationId: get-channel-summary-report
|
||||||
|
summary: Get conversation statistics grouped by channel type
|
||||||
|
security:
|
||||||
|
- userApiKey: []
|
||||||
|
description: |
|
||||||
|
Get conversation counts grouped by channel type and status for a given date range.
|
||||||
|
Returns statistics for each channel type including open, resolved, pending, snoozed, and total conversation counts.
|
||||||
|
|
||||||
|
**Note:** This API endpoint is available only in Chatwoot version 4.10.0 and above. The date range is limited to a maximum of 6 months.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/channel_summary'
|
||||||
|
'400':
|
||||||
|
description: Date range exceeds 6 months limit
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/bad_request_error'
|
||||||
|
'403':
|
||||||
|
description: Access denied
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/bad_request_error'
|
||||||
@@ -639,6 +639,28 @@
|
|||||||
get:
|
get:
|
||||||
$ref: './application/reports/conversation/agent.yml'
|
$ref: './application/reports/conversation/agent.yml'
|
||||||
|
|
||||||
|
# Channel summary report (Available in 4.10.0+)
|
||||||
|
/api/v2/accounts/{account_id}/summary_reports/channel:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/account_id'
|
||||||
|
- in: query
|
||||||
|
name: since
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The timestamp from where report should start (Unix timestamp).
|
||||||
|
- in: query
|
||||||
|
name: until
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The timestamp from where report should stop (Unix timestamp).
|
||||||
|
- in: query
|
||||||
|
name: business_hours
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
description: Whether to filter by business hours.
|
||||||
|
get:
|
||||||
|
$ref: './application/reports/channel_summary.yml'
|
||||||
|
|
||||||
# Conversations Messages
|
# Conversations Messages
|
||||||
/accounts/{account_id}/conversations/{conversation_id}/messages:
|
/accounts/{account_id}/conversations/{conversation_id}/messages:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -7870,6 +7870,82 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v2/accounts/{account_id}/summary_reports/channel": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/account_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "since",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "The timestamp from where report should start (Unix timestamp)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "until",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "The timestamp from where report should stop (Unix timestamp)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "business_hours",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"description": "Whether to filter by business hours."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Reports"
|
||||||
|
],
|
||||||
|
"operationId": "get-channel-summary-report",
|
||||||
|
"summary": "Get conversation statistics grouped by channel type",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"userApiKey": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get conversation counts grouped by channel type and status for a given date range.\nReturns statistics for each channel type including open, resolved, pending, snoozed, and total conversation counts.\n\n**Note:** This API endpoint is available only in Chatwoot version 4.10.0 and above. The date range is limited to a maximum of 6 months.\n",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/channel_summary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Date range exceeds 6 months limit",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/bad_request_error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Access denied",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/bad_request_error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/accounts/{account_id}/conversations/{conversation_id}/messages": {
|
"/accounts/{account_id}/conversations/{conversation_id}/messages": {
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
@@ -11659,6 +11735,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"channel_summary": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Channel summary report containing conversation counts grouped by channel type and status. Available in version 4.10.0+.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Conversation statistics for a specific channel type (e.g., Channel::WebWidget, Channel::Api)",
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of open conversations"
|
||||||
|
},
|
||||||
|
"resolved": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of resolved conversations"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of pending conversations"
|
||||||
|
},
|
||||||
|
"snoozed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of snoozed conversations"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Total number of conversations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"Channel::WebWidget": {
|
||||||
|
"open": 10,
|
||||||
|
"resolved": 20,
|
||||||
|
"pending": 5,
|
||||||
|
"snoozed": 2,
|
||||||
|
"total": 37
|
||||||
|
},
|
||||||
|
"Channel::Api": {
|
||||||
|
"open": 5,
|
||||||
|
"resolved": 15,
|
||||||
|
"pending": 3,
|
||||||
|
"snoozed": 1,
|
||||||
|
"total": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"contact_detail": {
|
"contact_detail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -6412,6 +6412,82 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/api/v2/accounts/{account_id}/summary_reports/channel": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/parameters/account_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "since",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "The timestamp from where report should start (Unix timestamp)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "until",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "The timestamp from where report should stop (Unix timestamp)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "business_hours",
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"description": "Whether to filter by business hours."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Reports"
|
||||||
|
],
|
||||||
|
"operationId": "get-channel-summary-report",
|
||||||
|
"summary": "Get conversation statistics grouped by channel type",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"userApiKey": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get conversation counts grouped by channel type and status for a given date range.\nReturns statistics for each channel type including open, resolved, pending, snoozed, and total conversation counts.\n\n**Note:** This API endpoint is available only in Chatwoot version 4.10.0 and above. The date range is limited to a maximum of 6 months.\n",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/channel_summary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Date range exceeds 6 months limit",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/bad_request_error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Access denied",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/bad_request_error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -10166,6 +10242,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"channel_summary": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Channel summary report containing conversation counts grouped by channel type and status. Available in version 4.10.0+.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Conversation statistics for a specific channel type (e.g., Channel::WebWidget, Channel::Api)",
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of open conversations"
|
||||||
|
},
|
||||||
|
"resolved": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of resolved conversations"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of pending conversations"
|
||||||
|
},
|
||||||
|
"snoozed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of snoozed conversations"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Total number of conversations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"Channel::WebWidget": {
|
||||||
|
"open": 10,
|
||||||
|
"resolved": 20,
|
||||||
|
"pending": 5,
|
||||||
|
"snoozed": 2,
|
||||||
|
"total": 37
|
||||||
|
},
|
||||||
|
"Channel::Api": {
|
||||||
|
"open": 5,
|
||||||
|
"resolved": 15,
|
||||||
|
"pending": 3,
|
||||||
|
"snoozed": 1,
|
||||||
|
"total": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"contact_detail": {
|
"contact_detail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -4378,6 +4378,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"channel_summary": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Channel summary report containing conversation counts grouped by channel type and status. Available in version 4.10.0+.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Conversation statistics for a specific channel type (e.g., Channel::WebWidget, Channel::Api)",
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of open conversations"
|
||||||
|
},
|
||||||
|
"resolved": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of resolved conversations"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of pending conversations"
|
||||||
|
},
|
||||||
|
"snoozed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of snoozed conversations"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Total number of conversations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"Channel::WebWidget": {
|
||||||
|
"open": 10,
|
||||||
|
"resolved": 20,
|
||||||
|
"pending": 5,
|
||||||
|
"snoozed": 2,
|
||||||
|
"total": 37
|
||||||
|
},
|
||||||
|
"Channel::Api": {
|
||||||
|
"open": 5,
|
||||||
|
"resolved": 15,
|
||||||
|
"pending": 3,
|
||||||
|
"snoozed": 1,
|
||||||
|
"total": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"contact_detail": {
|
"contact_detail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -3793,6 +3793,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"channel_summary": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Channel summary report containing conversation counts grouped by channel type and status. Available in version 4.10.0+.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Conversation statistics for a specific channel type (e.g., Channel::WebWidget, Channel::Api)",
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of open conversations"
|
||||||
|
},
|
||||||
|
"resolved": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of resolved conversations"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of pending conversations"
|
||||||
|
},
|
||||||
|
"snoozed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of snoozed conversations"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Total number of conversations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"Channel::WebWidget": {
|
||||||
|
"open": 10,
|
||||||
|
"resolved": 20,
|
||||||
|
"pending": 5,
|
||||||
|
"snoozed": 2,
|
||||||
|
"total": 37
|
||||||
|
},
|
||||||
|
"Channel::Api": {
|
||||||
|
"open": 5,
|
||||||
|
"resolved": 15,
|
||||||
|
"pending": 3,
|
||||||
|
"snoozed": 1,
|
||||||
|
"total": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"contact_detail": {
|
"contact_detail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -4554,6 +4554,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"channel_summary": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Channel summary report containing conversation counts grouped by channel type and status. Available in version 4.10.0+.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Conversation statistics for a specific channel type (e.g., Channel::WebWidget, Channel::Api)",
|
||||||
|
"properties": {
|
||||||
|
"open": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of open conversations"
|
||||||
|
},
|
||||||
|
"resolved": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of resolved conversations"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of pending conversations"
|
||||||
|
},
|
||||||
|
"snoozed": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Number of snoozed conversations"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Total number of conversations"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"Channel::WebWidget": {
|
||||||
|
"open": 10,
|
||||||
|
"resolved": 20,
|
||||||
|
"pending": 5,
|
||||||
|
"snoozed": 2,
|
||||||
|
"total": 37
|
||||||
|
},
|
||||||
|
"Channel::Api": {
|
||||||
|
"open": 5,
|
||||||
|
"resolved": 15,
|
||||||
|
"pending": 3,
|
||||||
|
"snoozed": 1,
|
||||||
|
"total": 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"contact_detail": {
|
"contact_detail": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
Reference in New Issue
Block a user