diff --git a/enterprise/app/services/voice/call_message_builder.rb b/enterprise/app/services/voice/call_message_builder.rb index 5442ae708..e5157a57a 100644 --- a/enterprise/app/services/voice/call_message_builder.rb +++ b/enterprise/app/services/voice/call_message_builder.rb @@ -8,8 +8,7 @@ class Voice::CallMessageBuilder end def perform! - message = find_message - message ? update_message!(message) : create_message! + find_message || create_message! end private @@ -23,55 +22,16 @@ class Voice::CallMessageBuilder .find_by("content_attributes -> 'data' ->> 'call_sid' = ?", call.provider_call_id) end - def update_message!(message) - existing = (message.content_attributes || {}).fetch('data', {}) - message.update!(content_attributes: { 'data' => data_payload(existing) }) - message - end - def create_message! params = { content: 'Voice Call', - message_type: message_type, + message_type: call.outgoing? ? 'outgoing' : 'incoming', content_type: 'voice_call', - content_attributes: { 'data' => data_payload({}) } + content_attributes: { 'data' => { 'call_sid' => call.provider_call_id } } } Messages::MessageBuilder.new(sender, call.conversation, params).perform end - def data_payload(existing) - now = Time.zone.now.to_i - meta = existing.fetch('meta', {}) - - { - 'call_sid' => call.provider_call_id, - 'status' => call.status.tr('_', '-'), - 'call_direction' => call.display_direction, - 'from_number' => from_number, - 'to_number' => to_number, - 'duration' => call.duration_seconds, - 'recording_url' => existing['recording_url'], - 'transcript' => call.transcript, - 'conference_sid' => call.conference_sid, - 'meta' => { - 'created_at' => meta['created_at'] || now, - 'ringing_at' => meta['ringing_at'] || now - } - } - end - - def from_number - call.incoming? ? call.contact.phone_number : call.inbox.channel&.phone_number - end - - def to_number - call.incoming? ? call.inbox.channel&.phone_number : call.contact.phone_number - end - - def message_type - call.outgoing? ? 'outgoing' : 'incoming' - end - def sender call.outgoing? ? call.accepted_by_agent : call.contact end diff --git a/enterprise/app/services/voice/call_status/manager.rb b/enterprise/app/services/voice/call_status/manager.rb index c45fdfd94..aec3c8b96 100644 --- a/enterprise/app/services/voice/call_status/manager.rb +++ b/enterprise/app/services/voice/call_status/manager.rb @@ -7,7 +7,9 @@ class Voice::CallStatus::Manager apply_call_updates!(status, duration: duration, timestamp: timestamp) call.conversation.update!(last_activity_at: Time.zone.now) - Voice::CallMessageBuilder.perform!(call: call) + # Touch the linked message so the normal message.updated dispatcher + # re-broadcasts it with the fresh Call embedded via push_event_data. + call.message&.touch end private diff --git a/spec/enterprise/services/voice/inbound_call_builder_spec.rb b/spec/enterprise/services/voice/inbound_call_builder_spec.rb index e188373a4..6256b225f 100644 --- a/spec/enterprise/services/voice/inbound_call_builder_spec.rb +++ b/spec/enterprise/services/voice/inbound_call_builder_spec.rb @@ -44,19 +44,13 @@ RSpec.describe Voice::InboundCallBuilder do call = perform_builder voice_message = call.conversation.messages.voice_calls.last - expect(voice_message).to be_present - expect(voice_message.message_type).to eq('incoming') - expect(call.message_id).to eq(voice_message.id) - - data = voice_message.content_attributes['data'] - expect(data).to include( - 'call_sid' => call_sid, - 'status' => 'ringing', - 'call_direction' => 'inbound', - 'conference_sid' => call.conference_sid, - 'from_number' => from_number, - 'to_number' => inbox.channel.phone_number - ) + aggregate_failures do + expect(voice_message).to be_present + expect(voice_message.message_type).to eq('incoming') + expect(call.message_id).to eq(voice_message.id) + expect(voice_message.content_attributes['data']['call_sid']).to eq(call_sid) + expect(voice_message.call).to eq(call) + end end it 'sets the contact name to the phone number for new callers' do diff --git a/spec/enterprise/services/voice/outbound_call_builder_spec.rb b/spec/enterprise/services/voice/outbound_call_builder_spec.rb index 49ed44474..a16ee6e72 100644 --- a/spec/enterprise/services/voice/outbound_call_builder_spec.rb +++ b/spec/enterprise/services/voice/outbound_call_builder_spec.rb @@ -40,14 +40,8 @@ RSpec.describe Voice::OutboundCallBuilder do voice_message = call.conversation.messages.voice_calls.last expect(call.message_id).to eq(voice_message.id) expect(voice_message.message_type).to eq('outgoing') - - expect(voice_message.content_attributes['data']).to include( - 'call_sid' => call_sid, - 'call_direction' => 'outbound', - 'conference_sid' => call.conference_sid, - 'from_number' => channel.phone_number, - 'to_number' => contact.phone_number - ) + expect(voice_message.content_attributes['data']['call_sid']).to eq(call_sid) + expect(voice_message.call).to eq(call) end end diff --git a/spec/enterprise/services/voice/status_update_service_spec.rb b/spec/enterprise/services/voice/status_update_service_spec.rb index 7a28e8d9f..cad3e6575 100644 --- a/spec/enterprise/services/voice/status_update_service_spec.rb +++ b/spec/enterprise/services/voice/status_update_service_spec.rb @@ -42,7 +42,10 @@ RSpec.describe Voice::StatusUpdateService do .and_return(instance_double(Twilio::VoiceWebhookSetupService, perform: "AP#{SecureRandom.hex(16)}")) end - it 'updates the Call and the matching voice_call message with the normalized status' do + it 'updates the Call and touches the linked message on status transition' do + previous_updated_at = message.updated_at + travel 1.second + described_class.new( account: account, call_sid: call_sid, @@ -53,21 +56,17 @@ RSpec.describe Voice::StatusUpdateService do message.reload expect(call.status).to eq('completed') - expect(message.content_attributes.dig('data', 'status')).to eq('completed') + expect(message.updated_at).to be > previous_updated_at end - it 'normalizes busy to no_answer on the Call and no-answer on the message payload' do + it 'normalizes busy to no_answer on the Call' do described_class.new( account: account, call_sid: call_sid, call_status: 'busy' ).perform - call.reload - message.reload - - expect(call.status).to eq('no_answer') - expect(message.content_attributes.dig('data', 'status')).to eq('no-answer') + expect(call.reload.status).to eq('no_answer') end it 'no-ops when no Call matches the provided call_sid' do