diff --git a/app/models/reporting_event.rb b/app/models/reporting_event.rb index 2f141786b..f083e32a1 100644 --- a/app/models/reporting_event.rb +++ b/app/models/reporting_event.rb @@ -35,4 +35,21 @@ class ReportingEvent < ApplicationRecord belongs_to :user, optional: true belongs_to :inbox, optional: true belongs_to :conversation, optional: true + + # Scopes for filtering + scope :filter_by_date_range, lambda { |range| + where(created_at: range) if range.present? + } + + scope :filter_by_inbox_id, lambda { |inbox_id| + where(inbox_id: inbox_id) if inbox_id.present? + } + + scope :filter_by_user_id, lambda { |user_id| + where(user_id: user_id) if user_id.present? + } + + scope :filter_by_name, lambda { |name| + where(name: name) if name.present? + } end diff --git a/config/routes.rb b/config/routes.rb index 639c51da7..9c6866e1d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -141,6 +141,7 @@ Rails.application.routes.draw do post :custom_attributes get :attachments get :inbox_assistant + get :reporting_events if ChatwootApp.enterprise? end end @@ -186,6 +187,7 @@ Rails.application.routes.draw do get :download end end + resources :reporting_events, only: [:index] if ChatwootApp.enterprise? resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy] resources :custom_filters, only: [:index, :show, :create, :update, :destroy] resources :inboxes, only: [:index, :show, :create, :update, :destroy] do diff --git a/enterprise/app/controllers/api/v1/accounts/reporting_events_controller.rb b/enterprise/app/controllers/api/v1/accounts/reporting_events_controller.rb new file mode 100644 index 000000000..eb282b0ee --- /dev/null +++ b/enterprise/app/controllers/api/v1/accounts/reporting_events_controller.rb @@ -0,0 +1,30 @@ +class Api::V1::Accounts::ReportingEventsController < Api::V1::Accounts::EnterpriseAccountsController + include DateRangeHelper + + RESULTS_PER_PAGE = 25 + + before_action :check_admin_authorization? + before_action :set_reporting_events, only: [:index] + before_action :set_current_page, only: [:index] + + def index + @reporting_events = @reporting_events.page(@current_page).per(RESULTS_PER_PAGE) + @total_count = @reporting_events.total_count + end + + private + + def set_reporting_events + @reporting_events = Current.account.reporting_events + .includes(:conversation, :user, :inbox) + .filter_by_date_range(range) + .filter_by_inbox_id(params[:inbox_id]) + .filter_by_user_id(params[:user_id]) + .filter_by_name(params[:name]) + .order(created_at: :desc) + end + + def set_current_page + @current_page = (params[:page] || 1).to_i + end +end diff --git a/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb b/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb index c0ecc2a45..f87c06658 100644 --- a/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb +++ b/enterprise/app/controllers/enterprise/api/v1/accounts/conversations_controller.rb @@ -11,6 +11,10 @@ module Enterprise::Api::V1::Accounts::ConversationsController end end + def reporting_events + @reporting_events = @conversation.reporting_events.order(created_at: :asc) + end + def permitted_update_params super.merge(params.permit(:sla_policy_id)) end diff --git a/enterprise/app/views/api/v1/accounts/conversations/reporting_events.json.jbuilder b/enterprise/app/views/api/v1/accounts/conversations/reporting_events.json.jbuilder new file mode 100644 index 000000000..691296522 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/conversations/reporting_events.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @reporting_events do |reporting_event| + json.partial! 'api/v1/models/reporting_event', formats: [:json], reporting_event: reporting_event +end diff --git a/enterprise/app/views/api/v1/accounts/reporting_events/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/reporting_events/index.json.jbuilder new file mode 100644 index 000000000..ae8531955 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/reporting_events/index.json.jbuilder @@ -0,0 +1,11 @@ +json.payload do + json.array! @reporting_events do |reporting_event| + json.partial! 'api/v1/models/reporting_event', formats: [:json], reporting_event: reporting_event + end +end + +json.meta do + json.count @total_count + json.current_page @current_page + json.total_pages @reporting_events.total_pages +end diff --git a/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder b/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder new file mode 100644 index 000000000..f8e5f08ba --- /dev/null +++ b/enterprise/app/views/api/v1/models/_reporting_event.json.jbuilder @@ -0,0 +1,12 @@ +json.id reporting_event.id +json.name reporting_event.name +json.value reporting_event.value +json.value_in_business_hours reporting_event.value_in_business_hours +json.event_start_time reporting_event.event_start_time +json.event_end_time reporting_event.event_end_time +json.account_id reporting_event.account_id +json.inbox_id reporting_event.inbox_id +json.user_id reporting_event.user_id +json.conversation_id reporting_event.conversation_id +json.created_at reporting_event.created_at +json.updated_at reporting_event.updated_at diff --git a/spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb index bbb3f1eb3..472dc959b 100644 --- a/spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb +++ b/spec/enterprise/controllers/api/v1/accounts/conversations_controller_spec.rb @@ -102,4 +102,146 @@ RSpec.describe 'Conversations API', type: :request do end end end + + describe 'GET /api/v1/accounts/{account.id}/conversations/:id/reporting_events' do + let(:conversation) { create(:conversation, account: account) } + let(:inbox) { conversation.inbox } + let(:agent) { administrator } + + before do + # Create reporting events for this conversation + @event1 = create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: 'first_response', + value: 120, + created_at: 3.hours.ago) + + @event2 = create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: 'reply_time', + value: 45, + created_at: 2.hours.ago) + + @event3 = create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: 'resolution', + value: 300, + created_at: 1.hour.ago) + + # Create an event for a different conversation (should not be included) + other_conversation = create(:conversation, account: account) + create(:reporting_event, + account: account, + conversation: other_conversation, + inbox: other_conversation.inbox, + user: agent, + name: 'other_conversation_event', + value: 60) + end + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/reporting_events", + as: :json + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user with conversation access' do + it 'returns all reporting events for the conversation' do + get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/reporting_events", + headers: administrator.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + # Should return array directly (no pagination) + expect(json_response).to be_an(Array) + expect(json_response.size).to eq(3) + + # Check they are sorted by created_at asc (oldest first) + expect(json_response.first['name']).to eq('first_response') + expect(json_response.last['name']).to eq('resolution') + + # Verify it doesn't include events from other conversations + event_names = json_response.map { |e| e['name'] } + expect(event_names).not_to include('other_conversation_event') + end + + it 'returns empty array when conversation has no reporting events' do + conversation_without_events = create(:conversation, account: account) + + get "/api/v1/accounts/#{account.id}/conversations/#{conversation_without_events.display_id}/reporting_events", + headers: administrator.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response).to be_an(Array) + expect(json_response).to be_empty + end + end + + context 'when agent has limited access' do + let(:limited_agent) { create(:user, account: account, role: :agent) } + + it 'returns unauthorized for unassigned conversation without permission' do + get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/reporting_events", + headers: limited_agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:unauthorized) + end + + it 'returns reporting events when agent is assigned to the conversation' do + conversation.update!(assignee: limited_agent) + # Also create inbox member for the agent + create(:inbox_member, user: limited_agent, inbox: conversation.inbox) + + get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/reporting_events", + headers: limited_agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response).to be_an(Array) + expect(json_response.size).to eq(3) + end + end + + context 'when agent has team access' do + let(:team_agent) { create(:user, account: account, role: :agent) } + let(:team) { create(:team, account: account) } + + before do + create(:team_member, team: team, user: team_agent) + conversation.update!(team: team) + end + + it 'allows accessing conversation reporting events via team membership' do + get "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/reporting_events", + headers: team_agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response).to be_an(Array) + expect(json_response.size).to eq(3) + end + end + end end diff --git a/spec/enterprise/controllers/api/v1/accounts/reporting_events_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/reporting_events_controller_spec.rb new file mode 100644 index 000000000..6b3ffc4ba --- /dev/null +++ b/spec/enterprise/controllers/api/v1/accounts/reporting_events_controller_spec.rb @@ -0,0 +1,217 @@ +require 'rails_helper' + +RSpec.describe 'Enterprise Reporting Events API', type: :request do + let!(:account) { create(:account) } + let!(:admin) { create(:user, account: account, role: :administrator) } + let!(:agent) { create(:user, account: account, role: :agent) } + let!(:inbox) { create(:inbox, account: account) } + let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: agent) } + + describe 'GET /api/v1/accounts/{account.id}/reporting_events' do + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/reporting_events", + as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated normal agent user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/reporting_events", + headers: agent.create_new_auth_token, + as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated admin user' do + before do + create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: 'first_response', + value: 120, + created_at: 3.days.ago) + create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: 'resolution', + value: 300, + created_at: 2.days.ago) + create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: 'reply_time', + value: 45, + created_at: 1.day.ago) + end + + it 'fetches reporting events with pagination' do + get "/api/v1/accounts/#{account.id}/reporting_events", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + # Check structure and pagination + expect(json_response).to have_key('payload') + expect(json_response).to have_key('meta') + expect(json_response['meta']['count']).to eq(3) + + # Check events are sorted by created_at desc (newest first) + events = json_response['payload'] + expect(events.size).to eq(3) + expect(events.first['name']).to eq('reply_time') + expect(events.last['name']).to eq('first_response') + end + + it 'filters reporting events by date range using since and until' do + get "/api/v1/accounts/#{account.id}/reporting_events", + params: { since: 2.5.days.ago.to_time.to_i.to_s, until: 1.5.days.ago.to_time.to_i.to_s }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['meta']['count']).to eq(1) + expect(json_response['payload'].first['name']).to eq('resolution') + end + + it 'filters reporting events by inbox_id' do + other_inbox = create(:inbox, account: account) + other_conversation = create(:conversation, account: account, inbox: other_inbox) + create(:reporting_event, + account: account, + conversation: other_conversation, + inbox: other_inbox, + user: agent, + name: 'other_inbox_event') + + get "/api/v1/accounts/#{account.id}/reporting_events", + params: { inbox_id: inbox.id }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['meta']['count']).to eq(3) + expect(json_response['payload'].map { |e| e['name'] }).not_to include('other_inbox_event') + end + + it 'filters reporting events by user_id (agent)' do + other_agent = create(:user, account: account, role: :agent) + create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: other_agent, + name: 'other_agent_event') + + get "/api/v1/accounts/#{account.id}/reporting_events", + params: { user_id: agent.id }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['meta']['count']).to eq(3) + expect(json_response['payload'].map { |e| e['name'] }).not_to include('other_agent_event') + end + + it 'filters reporting events by name' do + get "/api/v1/accounts/#{account.id}/reporting_events", + params: { name: 'first_response' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['meta']['count']).to eq(1) + expect(json_response['payload'].first['name']).to eq('first_response') + end + + it 'supports combining multiple filters' do + # Create more test data + other_conversation = create(:conversation, account: account, inbox: inbox, assignee: agent) + create(:reporting_event, + account: account, + conversation: other_conversation, + inbox: inbox, + user: agent, + name: 'first_response', + value: 90, + created_at: 2.days.ago) + + get "/api/v1/accounts/#{account.id}/reporting_events", + params: { + inbox_id: inbox.id, + user_id: agent.id, + name: 'first_response', + since: 4.days.ago.to_time.to_i.to_s, + until: Time.zone.now.to_time.to_i.to_s + }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['meta']['count']).to eq(2) + expect(json_response['payload'].map { |e| e['name'] }).to all(eq('first_response')) + end + + context 'with pagination' do + before do + # Create more events to test pagination + 30.times do |i| + create(:reporting_event, + account: account, + conversation: conversation, + inbox: inbox, + user: agent, + name: "event_#{i}", + created_at: i.hours.ago) + end + end + + it 'returns 25 events per page by default' do + get "/api/v1/accounts/#{account.id}/reporting_events", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['payload'].size).to eq(25) + expect(json_response['meta']['count']).to eq(33) # 30 + 3 original events + expect(json_response['meta']['current_page']).to eq(1) + end + + it 'supports page navigation' do + get "/api/v1/accounts/#{account.id}/reporting_events", + params: { page: 2 }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + json_response = response.parsed_body + + expect(json_response['payload'].size).to eq(8) # Remaining events + expect(json_response['meta']['current_page']).to eq(2) + end + end + end + end +end diff --git a/spec/factories/reporting_events.rb b/spec/factories/reporting_events.rb index 1db6a87df..aff731ba1 100644 --- a/spec/factories/reporting_events.rb +++ b/spec/factories/reporting_events.rb @@ -1,11 +1,13 @@ FactoryBot.define do factory :reporting_event do - name { 'MyString' } + name { 'first_response' } value { 1.5 } value_in_business_hours { 1 } - account_id { 1 } - inbox_id { 1 } - user_id { 1 } - conversation_id { 1 } + account + inbox { association :inbox, account: account } + user { association :user, account: account } + conversation { association :conversation, account: account, inbox: inbox } + event_start_time { 2.hours.ago } + event_end_time { 1.hour.ago } end end diff --git a/swagger/definitions/index.yml b/swagger/definitions/index.yml index b244deb7f..c24831f84 100644 --- a/swagger/definitions/index.yml +++ b/swagger/definitions/index.yml @@ -248,3 +248,9 @@ contact_conversations_response: $ref: ./resource/contact_conversations_response.yml contactable_inboxes_response: $ref: ./resource/contactable_inboxes_response.yml +reporting_event: + $ref: ./resource/reporting_event.yml +reporting_event_meta: + $ref: ./resource/reporting_event_meta.yml +reporting_events_list_response: + $ref: ./resource/reporting_events_list_response.yml diff --git a/swagger/definitions/resource/reporting_event.yml b/swagger/definitions/resource/reporting_event.yml new file mode 100644 index 000000000..85cf71b8c --- /dev/null +++ b/swagger/definitions/resource/reporting_event.yml @@ -0,0 +1,47 @@ +type: object +properties: + id: + type: number + description: ID of the reporting event + name: + type: string + description: Name of the event (e.g., first_response, resolution, reply_time) + value: + type: number + format: double + description: Value of the metric in seconds + value_in_business_hours: + type: number + format: double + description: Value of the metric in seconds, calculated only for business hours + event_start_time: + type: string + format: date-time + description: The timestamp when the event started + event_end_time: + type: string + format: date-time + description: The timestamp when the event ended + account_id: + type: number + description: ID of the account + conversation_id: + type: number + nullable: true + description: ID of the conversation + inbox_id: + type: number + nullable: true + description: ID of the inbox + user_id: + type: number + nullable: true + description: ID of the user/agent + created_at: + type: string + format: date-time + description: The timestamp when the reporting event was created + updated_at: + type: string + format: date-time + description: The timestamp when the reporting event was last updated diff --git a/swagger/definitions/resource/reporting_event_meta.yml b/swagger/definitions/resource/reporting_event_meta.yml new file mode 100644 index 000000000..5d2646c51 --- /dev/null +++ b/swagger/definitions/resource/reporting_event_meta.yml @@ -0,0 +1,11 @@ +type: object +properties: + count: + type: integer + description: Total number of reporting events + current_page: + type: integer + description: Current page number + total_pages: + type: integer + description: Total number of pages diff --git a/swagger/definitions/resource/reporting_events_list_response.yml b/swagger/definitions/resource/reporting_events_list_response.yml new file mode 100644 index 000000000..847c5336a --- /dev/null +++ b/swagger/definitions/resource/reporting_events_list_response.yml @@ -0,0 +1,10 @@ +type: object +properties: + meta: + $ref: '#/components/schemas/reporting_event_meta' + description: Metadata about the reporting events list response + payload: + type: array + items: + $ref: '#/components/schemas/reporting_event' + description: List of reporting events diff --git a/swagger/paths/application/conversation/reporting_events.yml b/swagger/paths/application/conversation/reporting_events.yml new file mode 100644 index 000000000..f19ccbe8b --- /dev/null +++ b/swagger/paths/application/conversation/reporting_events.yml @@ -0,0 +1,29 @@ +tags: + - Conversations +operationId: get-conversation-reporting-events +summary: Conversation Reporting Events +security: + - userApiKey: [] +description: Get reporting events for a specific conversation. This endpoint returns events such as first response time, resolution time, and other metrics for the conversation, sorted by creation time in ascending order. +responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/reporting_event' + description: Array of reporting events for the conversation + '403': + description: Access denied + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' + '404': + description: Conversation not found + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/application/reporting_events/index.yml b/swagger/paths/application/reporting_events/index.yml new file mode 100644 index 000000000..d55c99545 --- /dev/null +++ b/swagger/paths/application/reporting_events/index.yml @@ -0,0 +1,47 @@ +tags: + - Reports +operationId: get-account-reporting-events +summary: Account Reporting Events +security: + - userApiKey: [] +description: Get paginated reporting events for the account. This endpoint returns reporting events such as first response time, resolution time, and other metrics. Only administrators can access this endpoint. Results are paginated with 25 items per page. +parameters: + - $ref: '#/components/parameters/page' + - in: query + name: since + schema: + type: string + description: The timestamp from where events should start (Unix timestamp in seconds) + - in: query + name: until + schema: + type: string + description: The timestamp from where events should stop (Unix timestamp in seconds) + - in: query + name: inbox_id + schema: + type: number + description: Filter events by inbox ID + - in: query + name: user_id + schema: + type: number + description: Filter events by user/agent ID + - in: query + name: name + schema: + type: string + description: Filter events by event name (e.g., first_response, resolution, reply_time) +responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/reporting_events_list_response' + '403': + description: Access denied - Only administrators can access this endpoint + content: + application/json: + schema: + $ref: '#/components/schemas/bad_request_error' diff --git a/swagger/paths/index.yml b/swagger/paths/index.yml index b2fcb4594..d5126a8d1 100644 --- a/swagger/paths/index.yml +++ b/swagger/paths/index.yml @@ -389,6 +389,15 @@ post: $ref: ./application/conversation/labels/create.yml +# Conversation Reporting Events + +/api/v1/accounts/{account_id}/conversations/{conversation_id}/reporting_events: + parameters: + - $ref: '#/components/parameters/account_id' + - $ref: '#/components/parameters/conversation_id' + get: + $ref: ./application/conversation/reporting_events.yml + # Inboxes /api/v1/accounts/{account_id}/inboxes: $ref: ./application/inboxes/index.yml @@ -535,6 +544,13 @@ ### Reports +# Account Reporting Events +/api/v1/accounts/{account_id}/reporting_events: + parameters: + - $ref: '#/components/parameters/account_id' + get: + $ref: ./application/reporting_events/index.yml + # List /api/v2/accounts/{account_id}/reports: parameters: diff --git a/swagger/swagger.json b/swagger/swagger.json index 9bb9ff95a..7a3e5b02d 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -5140,6 +5140,65 @@ } } }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/reporting_events": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Conversations" + ], + "operationId": "get-conversation-reporting-events", + "summary": "Conversation Reporting Events", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get reporting events for a specific conversation. This endpoint returns events such as first response time, resolution time, and other metrics for the conversation, sorted by creation time in ascending order.", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "Array of reporting events for the conversation" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, "/api/v1/accounts/{account_id}/inboxes": { "get": { "tags": [ @@ -7311,6 +7370,93 @@ } } }, + "/api/v1/accounts/{account_id}/reporting_events": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Reports" + ], + "operationId": "get-account-reporting-events", + "summary": "Account Reporting Events", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get paginated reporting events for the account. This endpoint returns reporting events such as first response time, resolution time, and other metrics. Only administrators can access this endpoint. Results are paginated with 25 items per page.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string" + }, + "description": "The timestamp from where events should start (Unix timestamp in seconds)" + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string" + }, + "description": "The timestamp from where events should stop (Unix timestamp in seconds)" + }, + { + "in": "query", + "name": "inbox_id", + "schema": { + "type": "number" + }, + "description": "Filter events by inbox ID" + }, + { + "in": "query", + "name": "user_id", + "schema": { + "type": "number" + }, + "description": "Filter events by user/agent ID" + }, + { + "in": "query", + "name": "name", + "schema": { + "type": "string" + }, + "description": "Filter events by event name (e.g., first_response, resolution, reply_time)" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reporting_events_list_response" + } + } + } + }, + "403": { + "description": "Access denied - Only administrators can access this endpoint", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, "/api/v2/accounts/{account_id}/reports": { "parameters": [ { @@ -12066,6 +12212,100 @@ "description": "List of contactable inboxes for the contact" } } + }, + "reporting_event": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the reporting event" + }, + "name": { + "type": "string", + "description": "Name of the event (e.g., first_response, resolution, reply_time)" + }, + "value": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds" + }, + "value_in_business_hours": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds, calculated only for business hours" + }, + "event_start_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event started" + }, + "event_end_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event ended" + }, + "account_id": { + "type": "number", + "description": "ID of the account" + }, + "conversation_id": { + "type": "number", + "nullable": true, + "description": "ID of the conversation" + }, + "inbox_id": { + "type": "number", + "nullable": true, + "description": "ID of the inbox" + }, + "user_id": { + "type": "number", + "nullable": true, + "description": "ID of the user/agent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was last updated" + } + } + }, + "reporting_event_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of reporting events" + }, + "current_page": { + "type": "integer", + "description": "Current page number" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages" + } + } + }, + "reporting_events_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/reporting_event_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "List of reporting events" + } + } } }, "parameters": { diff --git a/swagger/tag_groups/application_swagger.json b/swagger/tag_groups/application_swagger.json index 600df4597..72902b3b8 100644 --- a/swagger/tag_groups/application_swagger.json +++ b/swagger/tag_groups/application_swagger.json @@ -3683,6 +3683,65 @@ } } }, + "/api/v1/accounts/{account_id}/conversations/{conversation_id}/reporting_events": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + }, + { + "$ref": "#/components/parameters/conversation_id" + } + ], + "get": { + "tags": [ + "Conversations" + ], + "operationId": "get-conversation-reporting-events", + "summary": "Conversation Reporting Events", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get reporting events for a specific conversation. This endpoint returns events such as first response time, resolution time, and other metrics for the conversation, sorted by creation time in ascending order.", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "Array of reporting events for the conversation" + } + } + } + }, + "403": { + "description": "Access denied", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + }, + "404": { + "description": "Conversation not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, "/api/v1/accounts/{account_id}/inboxes": { "get": { "tags": [ @@ -5854,6 +5913,93 @@ } } }, + "/api/v1/accounts/{account_id}/reporting_events": { + "parameters": [ + { + "$ref": "#/components/parameters/account_id" + } + ], + "get": { + "tags": [ + "Reports" + ], + "operationId": "get-account-reporting-events", + "summary": "Account Reporting Events", + "security": [ + { + "userApiKey": [] + } + ], + "description": "Get paginated reporting events for the account. This endpoint returns reporting events such as first response time, resolution time, and other metrics. Only administrators can access this endpoint. Results are paginated with 25 items per page.", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "in": "query", + "name": "since", + "schema": { + "type": "string" + }, + "description": "The timestamp from where events should start (Unix timestamp in seconds)" + }, + { + "in": "query", + "name": "until", + "schema": { + "type": "string" + }, + "description": "The timestamp from where events should stop (Unix timestamp in seconds)" + }, + { + "in": "query", + "name": "inbox_id", + "schema": { + "type": "number" + }, + "description": "Filter events by inbox ID" + }, + { + "in": "query", + "name": "user_id", + "schema": { + "type": "number" + }, + "description": "Filter events by user/agent ID" + }, + { + "in": "query", + "name": "name", + "schema": { + "type": "string" + }, + "description": "Filter events by event name (e.g., first_response, resolution, reply_time)" + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/reporting_events_list_response" + } + } + } + }, + "403": { + "description": "Access denied - Only administrators can access this endpoint", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bad_request_error" + } + } + } + } + } + } + }, "/api/v2/accounts/{account_id}/reports": { "parameters": [ { @@ -10573,6 +10719,100 @@ "description": "List of contactable inboxes for the contact" } } + }, + "reporting_event": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the reporting event" + }, + "name": { + "type": "string", + "description": "Name of the event (e.g., first_response, resolution, reply_time)" + }, + "value": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds" + }, + "value_in_business_hours": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds, calculated only for business hours" + }, + "event_start_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event started" + }, + "event_end_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event ended" + }, + "account_id": { + "type": "number", + "description": "ID of the account" + }, + "conversation_id": { + "type": "number", + "nullable": true, + "description": "ID of the conversation" + }, + "inbox_id": { + "type": "number", + "nullable": true, + "description": "ID of the inbox" + }, + "user_id": { + "type": "number", + "nullable": true, + "description": "ID of the user/agent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was last updated" + } + } + }, + "reporting_event_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of reporting events" + }, + "current_page": { + "type": "integer", + "description": "Current page number" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages" + } + } + }, + "reporting_events_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/reporting_event_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "List of reporting events" + } + } } }, "parameters": { diff --git a/swagger/tag_groups/client_swagger.json b/swagger/tag_groups/client_swagger.json index 16c1d5bc7..6d8b7701d 100644 --- a/swagger/tag_groups/client_swagger.json +++ b/swagger/tag_groups/client_swagger.json @@ -5019,6 +5019,100 @@ "description": "List of contactable inboxes for the contact" } } + }, + "reporting_event": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the reporting event" + }, + "name": { + "type": "string", + "description": "Name of the event (e.g., first_response, resolution, reply_time)" + }, + "value": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds" + }, + "value_in_business_hours": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds, calculated only for business hours" + }, + "event_start_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event started" + }, + "event_end_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event ended" + }, + "account_id": { + "type": "number", + "description": "ID of the account" + }, + "conversation_id": { + "type": "number", + "nullable": true, + "description": "ID of the conversation" + }, + "inbox_id": { + "type": "number", + "nullable": true, + "description": "ID of the inbox" + }, + "user_id": { + "type": "number", + "nullable": true, + "description": "ID of the user/agent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was last updated" + } + } + }, + "reporting_event_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of reporting events" + }, + "current_page": { + "type": "integer", + "description": "Current page number" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages" + } + } + }, + "reporting_events_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/reporting_event_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "List of reporting events" + } + } } }, "parameters": { diff --git a/swagger/tag_groups/other_swagger.json b/swagger/tag_groups/other_swagger.json index c8a5294d3..9e8c57c04 100644 --- a/swagger/tag_groups/other_swagger.json +++ b/swagger/tag_groups/other_swagger.json @@ -4434,6 +4434,100 @@ "description": "List of contactable inboxes for the contact" } } + }, + "reporting_event": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the reporting event" + }, + "name": { + "type": "string", + "description": "Name of the event (e.g., first_response, resolution, reply_time)" + }, + "value": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds" + }, + "value_in_business_hours": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds, calculated only for business hours" + }, + "event_start_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event started" + }, + "event_end_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event ended" + }, + "account_id": { + "type": "number", + "description": "ID of the account" + }, + "conversation_id": { + "type": "number", + "nullable": true, + "description": "ID of the conversation" + }, + "inbox_id": { + "type": "number", + "nullable": true, + "description": "ID of the inbox" + }, + "user_id": { + "type": "number", + "nullable": true, + "description": "ID of the user/agent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was last updated" + } + } + }, + "reporting_event_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of reporting events" + }, + "current_page": { + "type": "integer", + "description": "Current page number" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages" + } + } + }, + "reporting_events_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/reporting_event_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "List of reporting events" + } + } } }, "parameters": { diff --git a/swagger/tag_groups/platform_swagger.json b/swagger/tag_groups/platform_swagger.json index f816b8c94..7c79f1c5b 100644 --- a/swagger/tag_groups/platform_swagger.json +++ b/swagger/tag_groups/platform_swagger.json @@ -5195,6 +5195,100 @@ "description": "List of contactable inboxes for the contact" } } + }, + "reporting_event": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "ID of the reporting event" + }, + "name": { + "type": "string", + "description": "Name of the event (e.g., first_response, resolution, reply_time)" + }, + "value": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds" + }, + "value_in_business_hours": { + "type": "number", + "format": "double", + "description": "Value of the metric in seconds, calculated only for business hours" + }, + "event_start_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event started" + }, + "event_end_time": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the event ended" + }, + "account_id": { + "type": "number", + "description": "ID of the account" + }, + "conversation_id": { + "type": "number", + "nullable": true, + "description": "ID of the conversation" + }, + "inbox_id": { + "type": "number", + "nullable": true, + "description": "ID of the inbox" + }, + "user_id": { + "type": "number", + "nullable": true, + "description": "ID of the user/agent" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was created" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the reporting event was last updated" + } + } + }, + "reporting_event_meta": { + "type": "object", + "properties": { + "count": { + "type": "integer", + "description": "Total number of reporting events" + }, + "current_page": { + "type": "integer", + "description": "Current page number" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages" + } + } + }, + "reporting_events_list_response": { + "type": "object", + "properties": { + "meta": { + "$ref": "#/components/schemas/reporting_event_meta" + }, + "payload": { + "type": "array", + "items": { + "$ref": "#/components/schemas/reporting_event" + }, + "description": "List of reporting events" + } + } } }, "parameters": {