diff --git a/app/controllers/api/v2/accounts/live_reports_controller.rb b/app/controllers/api/v2/accounts/live_reports_controller.rb new file mode 100644 index 000000000..1c703764b --- /dev/null +++ b/app/controllers/api/v2/accounts/live_reports_controller.rb @@ -0,0 +1,64 @@ +class Api::V2::Accounts::LiveReportsController < Api::V1::Accounts::BaseController + before_action :load_conversations, only: [:conversation_metrics, :grouped_conversation_metrics] + before_action :set_group_scope, only: [:grouped_conversation_metrics] + + before_action :check_authorization + + def conversation_metrics + render json: { + open: @conversations.open.count, + unattended: @conversations.open.unattended.count, + unassigned: @conversations.open.unassigned.count, + pending: @conversations.pending.count + } + end + + def grouped_conversation_metrics + count_by_group = @conversations.open.group(@group_scope).count + unattended_by_group = @conversations.open.unattended.group(@group_scope).count + unassigned_by_group = @conversations.open.unassigned.group(@group_scope).count + + group_metrics = count_by_group.map do |group_id, count| + metric = { + open: count, + unattended: unattended_by_group[group_id] || 0, + unassigned: unassigned_by_group[group_id] || 0 + } + metric[@group_scope] = group_id + metric + end + + render json: group_metrics + end + + private + + def check_authorization + authorize :report, :view? + end + + def set_group_scope + render json: { error: 'invalid group_by' }, status: :unprocessable_entity and return unless %w[ + team_id + assignee_id + ].include?(permitted_params[:group_by]) + + @group_scope = permitted_params[:group_by] + end + + def team + return unless permitted_params[:team_id] + + @team ||= Current.account.teams.find(permitted_params[:team_id]) + end + + def load_conversations + scope = Current.account.conversations + scope = scope.where(team_id: team.id) if team.present? + @conversations = scope + end + + def permitted_params + params.permit(:team_id, :group_by) + end +end diff --git a/config/routes.rb b/config/routes.rb index 5340ec9cd..3e2616a46 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -337,6 +337,12 @@ Rails.application.routes.draw do get :bot_metrics end end + resources :live_reports, only: [] do + collection do + get :conversation_metrics + get :grouped_conversation_metrics + end + end end end end diff --git a/spec/controllers/api/v2/accounts/live_reports_controller_spec.rb b/spec/controllers/api/v2/accounts/live_reports_controller_spec.rb new file mode 100644 index 000000000..4e070ac5b --- /dev/null +++ b/spec/controllers/api/v2/accounts/live_reports_controller_spec.rb @@ -0,0 +1,171 @@ +require 'rails_helper' + +RSpec.describe 'Api::V2::Accounts::LiveReports', type: :request do + let(:account) { create(:account) } + let(:admin) { create(:user, account: account, role: :administrator) } + let(:agent) { create(:user, account: account, role: :agent) } + let!(:team) { create(:team, account: account) } + let(:team_member) { create(:team_member, team: team, user: admin) } + + describe 'GET /api/v2/accounts/{account.id}/live_reports/conversation_metrics' do + context 'when unauthenticated' do + it 'returns unauthorized' do + get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when authenticated but not authorized' do + it 'returns forbidden' do + get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics", + headers: agent.create_new_auth_token, + as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when authenticated and authorized' do + before do + create(:conversation, :with_assignee, account: account, status: :open) + create(:conversation, account: account, status: :open) + create(:conversation, :with_assignee, account: account, status: :pending) + create(:conversation, :with_assignee, account: account, status: :open) do |conversation| + create(:message, account: account, conversation: conversation, message_type: :outgoing) + end + end + + it 'returns conversation metrics' do + get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics", + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + + response_data = response.parsed_body + expect(response_data['open']).to eq(3) + expect(response_data['unattended']).to eq(2) + expect(response_data['unassigned']).to eq(1) + expect(response_data['pending']).to eq(1) + end + + context 'with team_id parameter' do + before do + create(:conversation, account: account, status: :open, team_id: team.id) + create(:conversation, account: account, status: :open) + end + + it 'returns metrics filtered by team' do + get "/api/v2/accounts/#{account.id}/live_reports/conversation_metrics", + params: { team_id: team.id }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + + response_data = response.parsed_body + expect(response_data['open']).to eq(1) + expect(response_data['unattended']).to eq(1) + expect(response_data['unassigned']).to eq(1) + expect(response_data['pending']).to eq(0) + end + end + end + end + + describe 'GET /api/v2/accounts/{account.id}/live_reports/grouped_conversation_metrics' do + context 'when unauthenticated' do + it 'returns unauthorized' do + get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics", + params: { group_by: 'team_id' } + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when authenticated but not authorized' do + it 'returns forbidden' do + get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics", + params: { group_by: 'team_id' }, + headers: agent.create_new_auth_token, + as: :json + expect(response).to have_http_status(:unauthorized) + end + end + + context 'with invalid group_by parameter' do + it 'returns unprocessable_entity error' do + get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics", + params: { group_by: 'invalid_param' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:unprocessable_entity) + expect(response.parsed_body['error']).to eq('invalid group_by') + end + end + + context 'when grouped by team_id' do + let(:assignee1) { create(:user, account: account) } + + before do + create(:conversation, account: account, status: :open, team_id: team.id) + create(:conversation, account: account, status: :open, team_id: team.id) + create(:conversation, account: account, status: :open, team_id: team.id) do |conversation| + create(:message, account: account, conversation: conversation, message_type: :outgoing) + end + + create(:conversation, account: account, status: :open, assignee_id: assignee1.id) + create(:conversation, account: account, status: :open) do |conversation| + create(:message, account: account, conversation: conversation, message_type: :outgoing) + end + end + + it 'returns metrics grouped by team' do + get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics", + params: { group_by: 'team_id' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + + response_data = response.parsed_body + expect(response_data.size).to eq(2) + expect(response_data).to include( + { 'team_id' => nil, 'open' => 2, 'unattended' => 1, 'unassigned' => 1 } + ) + expect(response_data).to include( + { 'team_id' => team.id, 'open' => 3, 'unattended' => 2, 'unassigned' => 3 } + ) + end + end + + context 'when filtering by assignee_id' do + let(:assignee1) { create(:user, account: account) } + + before do + create(:conversation, assignee_id: agent.id, account: account, status: :open) + create(:conversation, account: account, status: :open) + create(:conversation, assignee_id: agent.id, account: account, status: :open) do |conversation| + create(:message, account: account, conversation: conversation, message_type: :outgoing) + end + end + + it 'returns metrics grouped by assignee' do + get "/api/v2/accounts/#{account.id}/live_reports/grouped_conversation_metrics", + params: { group_by: 'assignee_id' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + + response_data = response.parsed_body + expect(response_data.size).to eq 2 + expect(response_data).to include( + { 'assignee_id' => agent.id, 'open' => 2, 'unassigned' => 0, 'unattended' => 1 } + ) + expect(response_data).to include( + { 'assignee_id' => nil, 'open' => 1, 'unassigned' => 1, 'unattended' => 1 } + ) + end + end + end +end diff --git a/spec/factories/teams.rb b/spec/factories/teams.rb index 597396a82..f51269a61 100644 --- a/spec/factories/teams.rb +++ b/spec/factories/teams.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :team do - name { 'MyString' } + sequence(:name) { |n| "Team #{n}" } description { 'MyText' } allow_auto_assign { true } account