mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-03 12:37:56 +00:00
feat: save timezone from leadsquared API (#11583)
This commit is contained in:
@@ -6,17 +6,18 @@ class Crm::Leadsquared::Mappers::ConversationMapper
|
|||||||
# so this limits it
|
# so this limits it
|
||||||
ACTIVITY_NOTE_MAX_SIZE = 1800
|
ACTIVITY_NOTE_MAX_SIZE = 1800
|
||||||
|
|
||||||
def self.map_conversation_activity(conversation)
|
def self.map_conversation_activity(hook, conversation)
|
||||||
new(conversation).conversation_activity
|
new(hook, conversation).conversation_activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.map_transcript_activity(conversation, messages = nil)
|
def self.map_transcript_activity(hook, conversation)
|
||||||
new(conversation, messages).transcript_activity
|
new(hook, conversation).transcript_activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(conversation, messages = nil)
|
def initialize(hook, conversation)
|
||||||
|
@hook = hook
|
||||||
|
@timezone = Time.find_zone(hook.settings['timezone']) || Time.zone
|
||||||
@conversation = conversation
|
@conversation = conversation
|
||||||
@messages = messages
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_activity
|
def conversation_activity
|
||||||
@@ -41,14 +42,14 @@ class Crm::Leadsquared::Mappers::ConversationMapper
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :conversation, :messages
|
attr_reader :conversation
|
||||||
|
|
||||||
def formatted_creation_time
|
def formatted_creation_time
|
||||||
conversation.created_at.strftime('%Y-%m-%d %H:%M:%S')
|
conversation.created_at.in_time_zone(@timezone).strftime('%Y-%m-%d %H:%M:%S')
|
||||||
end
|
end
|
||||||
|
|
||||||
def transcript_messages
|
def transcript_messages
|
||||||
@transcript_messages ||= messages || conversation.messages.chat.select(&:conversation_transcriptable?)
|
@transcript_messages ||= conversation.messages.chat.select(&:conversation_transcriptable?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def format_messages
|
def format_messages
|
||||||
@@ -77,8 +78,7 @@ class Crm::Leadsquared::Mappers::ConversationMapper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def message_time(message)
|
def message_time(message)
|
||||||
# TODO: Figure out what timezone to send the time in
|
message.created_at.in_time_zone(@timezone).strftime('%Y-%m-%d %H:%M')
|
||||||
message.created_at.strftime('%Y-%m-%d %H:%M')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def sender_name(message)
|
def sender_name(message)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class Crm::Leadsquared::ProcessorService < Crm::BaseProcessorService
|
|||||||
activity_type: 'conversation',
|
activity_type: 'conversation',
|
||||||
activity_code_key: 'conversation_activity_code',
|
activity_code_key: 'conversation_activity_code',
|
||||||
metadata_key: 'created_activity_id',
|
metadata_key: 'created_activity_id',
|
||||||
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_conversation_activity(conversation)
|
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_conversation_activity(@hook, conversation)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ class Crm::Leadsquared::ProcessorService < Crm::BaseProcessorService
|
|||||||
activity_type: 'transcript',
|
activity_type: 'transcript',
|
||||||
activity_code_key: 'transcript_activity_code',
|
activity_code_key: 'transcript_activity_code',
|
||||||
metadata_key: 'transcript_activity_id',
|
metadata_key: 'transcript_activity_id',
|
||||||
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_transcript_activity(conversation)
|
activity_note: Crm::Leadsquared::Mappers::ConversationMapper.map_transcript_activity(@hook, conversation)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ class Crm::Leadsquared::SetupService
|
|||||||
response = @client.get('Authentication.svc/UserByAccessKey.Get')
|
response = @client.get('Authentication.svc/UserByAccessKey.Get')
|
||||||
endpoint_host = response['LSQCommonServiceURLs']['api']
|
endpoint_host = response['LSQCommonServiceURLs']['api']
|
||||||
app_host = response['LSQCommonServiceURLs']['app']
|
app_host = response['LSQCommonServiceURLs']['app']
|
||||||
|
timezone = response['TimeZone']
|
||||||
|
|
||||||
endpoint_url = "https://#{endpoint_host}/v2/"
|
endpoint_url = "https://#{endpoint_host}/v2/"
|
||||||
app_url = "https://#{app_host}/"
|
app_url = "https://#{app_host}/"
|
||||||
|
|
||||||
update_hook_settings({ :endpoint_url => endpoint_url, :app_url => app_url })
|
update_hook_settings({ :endpoint_url => endpoint_url, :app_url => app_url, :timezone => timezone })
|
||||||
|
|
||||||
# replace the clients
|
# replace the clients
|
||||||
@client = Crm::Leadsquared::Api::BaseClient.new(@access_key, @secret_key, endpoint_url)
|
@client = Crm::Leadsquared::Api::BaseClient.new(@access_key, @secret_key, endpoint_url)
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ leadsquared:
|
|||||||
'secret_key': { 'type': 'string' },
|
'secret_key': { 'type': 'string' },
|
||||||
'endpoint_url': { 'type': 'string' },
|
'endpoint_url': { 'type': 'string' },
|
||||||
'app_url': { 'type': 'string' },
|
'app_url': { 'type': 'string' },
|
||||||
|
'timezone': { 'type': 'string' },
|
||||||
'enable_conversation_activity': { 'type': 'boolean' },
|
'enable_conversation_activity': { 'type': 'boolean' },
|
||||||
'enable_transcript_activity': { 'type': 'boolean' },
|
'enable_transcript_activity': { 'type': 'boolean' },
|
||||||
'conversation_activity_score': { 'type': 'string' },
|
'conversation_activity_score': { 'type': 'string' },
|
||||||
|
|||||||
@@ -6,15 +6,39 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
let(:conversation) { create(:conversation, account: account, inbox: inbox) }
|
let(:conversation) { create(:conversation, account: account, inbox: inbox) }
|
||||||
let(:user) { create(:user, name: 'John Doe') }
|
let(:user) { create(:user, name: 'John Doe') }
|
||||||
let(:contact) { create(:contact, name: 'Jane Smith') }
|
let(:contact) { create(:contact, name: 'Jane Smith') }
|
||||||
|
let(:hook) do
|
||||||
|
create(:integrations_hook, :leadsquared, account: account, settings: {
|
||||||
|
'access_key' => 'test_access_key',
|
||||||
|
'secret_key' => 'test_secret_key',
|
||||||
|
'endpoint_url' => 'https://api.leadsquared.com/v2',
|
||||||
|
'timezone' => 'UTC'
|
||||||
|
})
|
||||||
|
end
|
||||||
|
let(:hook_with_pst) do
|
||||||
|
create(:integrations_hook, :leadsquared, account: account, settings: {
|
||||||
|
'access_key' => 'test_access_key',
|
||||||
|
'secret_key' => 'test_secret_key',
|
||||||
|
'endpoint_url' => 'https://api.leadsquared.com/v2',
|
||||||
|
'timezone' => 'America/Los_Angeles'
|
||||||
|
})
|
||||||
|
end
|
||||||
|
let(:hook_without_timezone) do
|
||||||
|
create(:integrations_hook, :leadsquared, account: account, settings: {
|
||||||
|
'access_key' => 'test_access_key',
|
||||||
|
'secret_key' => 'test_secret_key',
|
||||||
|
'endpoint_url' => 'https://api.leadsquared.com/v2'
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
account.enable_features('crm_integration')
|
||||||
allow(GlobalConfig).to receive(:get).with('BRAND_NAME').and_return({ 'BRAND_NAME' => 'TestBrand' })
|
allow(GlobalConfig).to receive(:get).with('BRAND_NAME').and_return({ 'BRAND_NAME' => 'TestBrand' })
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.map_conversation_activity' do
|
describe '.map_conversation_activity' do
|
||||||
it 'generates conversation activity note' do
|
it 'generates conversation activity note with UTC timezone' do
|
||||||
travel_to(Time.zone.parse('2024-01-01 10:00:00')) do
|
travel_to(Time.zone.parse('2024-01-01 10:00:00 UTC')) do
|
||||||
result = described_class.map_conversation_activity(conversation)
|
result = described_class.map_conversation_activity(hook, conversation)
|
||||||
|
|
||||||
expect(result).to include('New conversation started on TestBrand')
|
expect(result).to include('New conversation started on TestBrand')
|
||||||
expect(result).to include('Channel: Test Inbox')
|
expect(result).to include('Channel: Test Inbox')
|
||||||
@@ -23,12 +47,29 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
expect(result).to include('View in TestBrand: http://')
|
expect(result).to include('View in TestBrand: http://')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'formats time according to hook timezone setting' do
|
||||||
|
travel_to(Time.zone.parse('2024-01-01 18:00:00 UTC')) do
|
||||||
|
result = described_class.map_conversation_activity(hook_with_pst, conversation)
|
||||||
|
|
||||||
|
# PST is UTC-8, so 18:00 UTC becomes 10:00:00 PST
|
||||||
|
expect(result).to include('Created: 2024-01-01 10:00:00')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'falls back to system timezone when hook has no timezone setting' do
|
||||||
|
travel_to(Time.zone.parse('2024-01-01 10:00:00')) do
|
||||||
|
result = described_class.map_conversation_activity(hook_without_timezone, conversation)
|
||||||
|
|
||||||
|
expect(result).to include('Created: 2024-01-01 10:00:00')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.map_transcript_activity' do
|
describe '.map_transcript_activity' do
|
||||||
context 'when conversation has no messages' do
|
context 'when conversation has no messages' do
|
||||||
it 'returns no messages message' do
|
it 'returns no messages message' do
|
||||||
result = described_class.map_transcript_activity(conversation)
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
expect(result).to eq('No messages in conversation')
|
expect(result).to eq('No messages in conversation')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -68,7 +109,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'generates transcript with messages in reverse chronological order' do
|
it 'generates transcript with messages in reverse chronological order' do
|
||||||
result = described_class.map_transcript_activity(conversation)
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
|
|
||||||
expect(result).to include('Conversation Transcript from TestBrand')
|
expect(result).to include('Conversation Transcript from TestBrand')
|
||||||
expect(result).to include('Channel: Test Inbox')
|
expect(result).to include('Channel: Test Inbox')
|
||||||
@@ -83,6 +124,22 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
expect(message_positions['[2024-01-01 10:01] Jane Smith: Hi there']).to be < message_positions['[2024-01-01 10:00] John Doe: Hello']
|
expect(message_positions['[2024-01-01 10:01] Jane Smith: Hi there']).to be < message_positions['[2024-01-01 10:00] John Doe: Hello']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'formats message times according to hook timezone setting' do
|
||||||
|
travel_to(Time.zone.parse('2024-01-01 18:00:00 UTC')) do
|
||||||
|
create(:message,
|
||||||
|
conversation: conversation,
|
||||||
|
sender: user,
|
||||||
|
content: 'Test message',
|
||||||
|
message_type: :outgoing,
|
||||||
|
created_at: Time.zone.parse('2024-01-01 18:00:00 UTC'))
|
||||||
|
|
||||||
|
result = described_class.map_transcript_activity(hook_with_pst, conversation)
|
||||||
|
|
||||||
|
# PST is UTC-8, so 18:00 UTC becomes 10:00 PST
|
||||||
|
expect(result).to include('[2024-01-01 10:00] John Doe: Test message')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when message has attachments' do
|
context 'when message has attachments' do
|
||||||
let(:message_with_attachment) do
|
let(:message_with_attachment) do
|
||||||
create(:message, :with_attachment,
|
create(:message, :with_attachment,
|
||||||
@@ -96,7 +153,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
before { message_with_attachment }
|
before { message_with_attachment }
|
||||||
|
|
||||||
it 'includes attachment information' do
|
it 'includes attachment information' do
|
||||||
result = described_class.map_transcript_activity(conversation)
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
|
|
||||||
expect(result).to include('See attachment')
|
expect(result).to include('See attachment')
|
||||||
expect(result).to include('[Attachment: image]')
|
expect(result).to include('[Attachment: image]')
|
||||||
@@ -116,7 +173,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
before { empty_message }
|
before { empty_message }
|
||||||
|
|
||||||
it 'shows no content placeholder' do
|
it 'shows no content placeholder' do
|
||||||
result = described_class.map_transcript_activity(conversation)
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
expect(result).to include('[No content]')
|
expect(result).to include('[No content]')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -134,25 +191,12 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
before { unnamed_sender_message }
|
before { unnamed_sender_message }
|
||||||
|
|
||||||
it 'uses sender type and id' do
|
it 'uses sender type and id' do
|
||||||
result = described_class.map_transcript_activity(conversation)
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
expect(result).to include("User #{unnamed_sender_message.sender_id}")
|
expect(result).to include("User #{unnamed_sender_message.sender_id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when specific messages are provided' do
|
|
||||||
let(:message1) { create(:message, conversation: conversation, content: 'Message 1', message_type: :outgoing) }
|
|
||||||
let(:message2) { create(:message, conversation: conversation, content: 'Message 2', message_type: :outgoing) }
|
|
||||||
let(:specific_messages) { [message1] }
|
|
||||||
|
|
||||||
it 'only includes provided messages' do
|
|
||||||
result = described_class.map_transcript_activity(conversation, specific_messages)
|
|
||||||
|
|
||||||
expect(result).to include('Message 1')
|
|
||||||
expect(result).not_to include('Message 2')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when messages exceed the ACTIVITY_NOTE_MAX_SIZE' do
|
context 'when messages exceed the ACTIVITY_NOTE_MAX_SIZE' do
|
||||||
it 'truncates messages to stay within the character limit' do
|
it 'truncates messages to stay within the character limit' do
|
||||||
# Create a large number of messages with reasonably sized content
|
# Create a large number of messages with reasonably sized content
|
||||||
@@ -169,7 +213,7 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
created_at: Time.zone.parse("2024-01-01 #{10 + i}:00:00"))
|
created_at: Time.zone.parse("2024-01-01 #{10 + i}:00:00"))
|
||||||
end
|
end
|
||||||
|
|
||||||
result = described_class.map_transcript_activity(conversation, messages)
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
|
|
||||||
# Verify latest message is included (message 14)
|
# Verify latest message is included (message 14)
|
||||||
expect(result).to include("[2024-01-02 00:00] John Doe: #{long_message_content} 14")
|
expect(result).to include("[2024-01-02 00:00] John Doe: #{long_message_content} 14")
|
||||||
@@ -189,13 +233,13 @@ RSpec.describe Crm::Leadsquared::Mappers::ConversationMapper do
|
|||||||
it 'respects the ACTIVITY_NOTE_MAX_SIZE constant' do
|
it 'respects the ACTIVITY_NOTE_MAX_SIZE constant' do
|
||||||
# Create a single message that would exceed the limit by itself
|
# Create a single message that would exceed the limit by itself
|
||||||
giant_content = 'A' * 2000
|
giant_content = 'A' * 2000
|
||||||
message = create(:message,
|
create(:message,
|
||||||
conversation: conversation,
|
conversation: conversation,
|
||||||
sender: user,
|
sender: user,
|
||||||
content: giant_content,
|
content: giant_content,
|
||||||
message_type: :outgoing)
|
message_type: :outgoing)
|
||||||
|
|
||||||
result = described_class.map_transcript_activity(conversation, [message])
|
result = described_class.map_transcript_activity(hook, conversation)
|
||||||
|
|
||||||
# Extract just the formatted messages part
|
# Extract just the formatted messages part
|
||||||
id = conversation.display_id
|
id = conversation.display_id
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ RSpec.describe Crm::Leadsquared::ProcessorService do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
allow(Crm::Leadsquared::Mappers::ConversationMapper).to receive(:map_conversation_activity)
|
allow(Crm::Leadsquared::Mappers::ConversationMapper).to receive(:map_conversation_activity)
|
||||||
.with(conversation)
|
.with(hook, conversation)
|
||||||
.and_return(activity_note)
|
.and_return(activity_note)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ RSpec.describe Crm::Leadsquared::ProcessorService do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
allow(Crm::Leadsquared::Mappers::ConversationMapper).to receive(:map_transcript_activity)
|
allow(Crm::Leadsquared::Mappers::ConversationMapper).to receive(:map_transcript_activity)
|
||||||
.with(conversation)
|
.with(hook, conversation)
|
||||||
.and_return(activity_note)
|
.and_return(activity_note)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ RSpec.describe Crm::Leadsquared::SetupService do
|
|||||||
let(:activity_client) { instance_double(Crm::Leadsquared::Api::ActivityClient) }
|
let(:activity_client) { instance_double(Crm::Leadsquared::Api::ActivityClient) }
|
||||||
let(:endpoint_response) do
|
let(:endpoint_response) do
|
||||||
{
|
{
|
||||||
|
'TimeZone' => 'Asia/Kolkata',
|
||||||
'LSQCommonServiceURLs' => {
|
'LSQCommonServiceURLs' => {
|
||||||
'api' => 'api-in.leadsquared.com',
|
'api' => 'api-in.leadsquared.com',
|
||||||
'app' => 'app.leadsquared.com'
|
'app' => 'app.leadsquared.com'
|
||||||
@@ -45,6 +46,7 @@ RSpec.describe Crm::Leadsquared::SetupService do
|
|||||||
updated_settings = hook.reload.settings
|
updated_settings = hook.reload.settings
|
||||||
expect(updated_settings['endpoint_url']).to eq('https://api-in.leadsquared.com/v2/')
|
expect(updated_settings['endpoint_url']).to eq('https://api-in.leadsquared.com/v2/')
|
||||||
expect(updated_settings['app_url']).to eq('https://app.leadsquared.com/')
|
expect(updated_settings['app_url']).to eq('https://app.leadsquared.com/')
|
||||||
|
expect(updated_settings['timezone']).to eq('Asia/Kolkata')
|
||||||
expect(updated_settings['conversation_activity_code']).to eq(1001)
|
expect(updated_settings['conversation_activity_code']).to eq(1001)
|
||||||
expect(updated_settings['transcript_activity_code']).to eq(1002)
|
expect(updated_settings['transcript_activity_code']).to eq(1002)
|
||||||
end
|
end
|
||||||
@@ -71,6 +73,7 @@ RSpec.describe Crm::Leadsquared::SetupService do
|
|||||||
updated_settings = hook.reload.settings
|
updated_settings = hook.reload.settings
|
||||||
expect(updated_settings['endpoint_url']).to eq('https://api-in.leadsquared.com/v2/')
|
expect(updated_settings['endpoint_url']).to eq('https://api-in.leadsquared.com/v2/')
|
||||||
expect(updated_settings['app_url']).to eq('https://app.leadsquared.com/')
|
expect(updated_settings['app_url']).to eq('https://app.leadsquared.com/')
|
||||||
|
expect(updated_settings['timezone']).to eq('Asia/Kolkata')
|
||||||
expect(updated_settings['conversation_activity_code']).to eq(1001)
|
expect(updated_settings['conversation_activity_code']).to eq(1001)
|
||||||
expect(updated_settings['transcript_activity_code']).to eq(1002)
|
expect(updated_settings['transcript_activity_code']).to eq(1002)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user