feat: account calls dashboard index endpoint (#14780)
## Description Adds a backend endpoint that powers an account-wide calls dashboard, letting users list and filter all calls in the account. ## Linear Ticket - https://linear.app/chatwoot/issue/UPM-28/voice-call-dashboard-view ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Calls 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(:contact) { create(:contact, :with_phone_number, account: account) }
|
||||
let(:conversation) { create(:conversation, account: account, inbox: inbox, contact: contact) }
|
||||
let!(:agent_call) do
|
||||
create(:call, account: account, inbox: inbox, conversation: conversation, contact: contact,
|
||||
accepted_by_agent: agent, status: 'completed', transcript: 'hello world')
|
||||
end
|
||||
let!(:other_call) do
|
||||
create(:call, account: account, inbox: inbox, conversation: conversation, contact: contact, accepted_by_agent: admin)
|
||||
end
|
||||
|
||||
before { create(:inbox_member, user: agent, inbox: inbox) }
|
||||
|
||||
describe 'GET /api/v1/accounts/:account_id/calls' do
|
||||
it 'returns 401 when unauthenticated' do
|
||||
get "/api/v1/accounts/#{account.id}/calls"
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
|
||||
it 'returns the whole account with sensitive fields for an administrator' do
|
||||
get "/api/v1/accounts/#{account.id}/calls", headers: admin.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
body = response.parsed_body
|
||||
expect(body['payload'].map { |c| c['id'] }).to contain_exactly(agent_call.id, other_call.id)
|
||||
item = body['payload'].find { |c| c['id'] == agent_call.id }
|
||||
expect(item['transcript']).to eq('hello world')
|
||||
expect(item['contact']['phone_number']).to eq(contact.phone_number)
|
||||
end
|
||||
|
||||
it 'scopes the list to calls the agent accepted' do
|
||||
get "/api/v1/accounts/#{account.id}/calls", headers: agent.create_new_auth_token
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
body = response.parsed_body
|
||||
expect(body['meta']['count']).to eq(1)
|
||||
expect(body['payload'].map { |c| c['id'] }).to contain_exactly(agent_call.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe CallFinder 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) }
|
||||
|
||||
before { create(:inbox_member, user: agent, inbox: inbox) }
|
||||
|
||||
def perform(user, params = {})
|
||||
Current.account = account
|
||||
Current.account_user = account.account_users.find_by(user_id: user.id)
|
||||
described_class.new(user, account, params).perform
|
||||
end
|
||||
|
||||
describe 'visibility' do
|
||||
let!(:agent_call) do
|
||||
create(:call, account: account, inbox: inbox, conversation: conversation, contact: conversation.contact, accepted_by_agent: agent)
|
||||
end
|
||||
let!(:other_call) do
|
||||
create(:call, account: account, inbox: inbox, conversation: conversation, contact: conversation.contact, accepted_by_agent: admin)
|
||||
end
|
||||
|
||||
it 'lets an administrator see every call in the account' do
|
||||
result = perform(admin)
|
||||
expect(result[:count]).to eq(2)
|
||||
expect(result[:calls].map(&:id)).to contain_exactly(agent_call.id, other_call.id)
|
||||
end
|
||||
|
||||
it 'lets an agent with report_manage see every call in the account' do
|
||||
report_manager = create(:user, account: account, role: :agent)
|
||||
custom_role = create(:custom_role, account: account, permissions: ['report_manage'])
|
||||
account.account_users.find_by(user_id: report_manager.id).update!(custom_role: custom_role)
|
||||
|
||||
result = perform(report_manager)
|
||||
expect(result[:calls].map(&:id)).to contain_exactly(agent_call.id, other_call.id)
|
||||
end
|
||||
|
||||
it 'limits a regular agent to calls they accepted in accessible conversations' do
|
||||
result = perform(agent)
|
||||
expect(result[:calls].map(&:id)).to contain_exactly(agent_call.id)
|
||||
end
|
||||
|
||||
it 'limits a custom-role agent without report_manage to their own accepted calls' do
|
||||
scoped_agent = create(:user, account: account, role: :agent)
|
||||
custom_role = create(:custom_role, account: account, permissions: ['conversation_manage'])
|
||||
account.account_users.find_by(user_id: scoped_agent.id).update!(custom_role: custom_role)
|
||||
create(:inbox_member, user: scoped_agent, inbox: inbox)
|
||||
scoped_call = create(:call, account: account, inbox: inbox, conversation: conversation,
|
||||
contact: conversation.contact, accepted_by_agent: scoped_agent)
|
||||
|
||||
result = perform(scoped_agent)
|
||||
expect(result[:calls].map(&:id)).to contain_exactly(scoped_call.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'filters' do
|
||||
let(:inbox2) { create(:inbox, account: account) }
|
||||
let(:conversation2) { create(:conversation, account: account, inbox: inbox2) }
|
||||
let(:agent2) { create(:user, account: account, role: :agent) }
|
||||
let!(:ringing) do
|
||||
create(:call, account: account, inbox: inbox, conversation: conversation, contact: conversation.contact,
|
||||
status: 'ringing', direction: :incoming, accepted_by_agent: agent)
|
||||
end
|
||||
let!(:in_progress) do
|
||||
create(:call, account: account, inbox: inbox, conversation: conversation, contact: conversation.contact,
|
||||
status: 'in_progress', direction: :incoming, accepted_by_agent: agent)
|
||||
end
|
||||
let!(:completed) do
|
||||
create(:call, account: account, inbox: inbox2, conversation: conversation2, contact: conversation2.contact,
|
||||
status: 'completed', direction: :outgoing, accepted_by_agent: agent2, created_at: 10.days.ago)
|
||||
end
|
||||
|
||||
it 'filters by status using the display value' do
|
||||
expect(perform(admin, status: 'in-progress')[:calls].map(&:id)).to contain_exactly(in_progress.id)
|
||||
end
|
||||
|
||||
it 'filters by direction using the display label' do
|
||||
expect(perform(admin, direction: 'outbound')[:calls].map(&:id)).to contain_exactly(completed.id)
|
||||
end
|
||||
|
||||
it 'filters by inbox' do
|
||||
expect(perform(admin, inbox_id: inbox2.id)[:calls].map(&:id)).to contain_exactly(completed.id)
|
||||
end
|
||||
|
||||
it 'filters by agent' do
|
||||
expect(perform(admin, agent_id: agent2.id)[:calls].map(&:id)).to contain_exactly(completed.id)
|
||||
end
|
||||
|
||||
it 'filters by created_at date range' do
|
||||
params = { since: 2.days.ago.to_i.to_s, until: 1.hour.from_now.to_i.to_s }
|
||||
expect(perform(admin, params)[:calls].map(&:id)).to contain_exactly(ringing.id, in_progress.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'account scoping' do
|
||||
it 'never returns calls from another account' do
|
||||
other_account = create(:account)
|
||||
other_conversation = create(:conversation, account: other_account)
|
||||
create(:call, account: other_account, inbox: other_conversation.inbox, conversation: other_conversation,
|
||||
contact: other_conversation.contact)
|
||||
|
||||
expect(perform(admin)[:count]).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user