From e68522318b3f7e070bd6d648fb07906cf9b22d86 Mon Sep 17 00:00:00 2001 From: Macoly Melo <115957403+macolym@users.noreply.github.com> Date: Wed, 24 Sep 2025 03:05:14 -0300 Subject: [PATCH] feat: Enable lock to single thread settings for Telegram (#12367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR implements the **"Lock to Single Conversation"** option for Telegram inboxes, bringing it to parity with WhatsApp, SMS, and other channels. - When **enabled**: resolved conversations can be reopened (single thread). - When **disabled**: new messages from a resolved conversation create a **new conversation**. - Added **agent name display** in outgoing Telegram messages (formatted as `Agent Name: message`). - Updated frontend to display agent name above messages in the dashboard (consistent with WhatsApp behavior). This fixes [#8046](https://github.com/chatwoot/chatwoot/issues/8046). ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? - Unit tests added in `spec/services/telegram/incoming_message_service_spec.rb` - Scenarios covered: - Lock enabled → reopens resolved conversation - Lock disabled → creates new conversation if resolved - Lock disabled → appends to last open conversation - Manual tests: 1. Create a Telegram conversation 2. Mark it as resolved 3. Send a new message from same user 4. ✅ Expected: new conversation created (if lock disabled) ## Checklist: - [x] My code follows the style guidelines of this project - [x] 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 - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules ## Additional Documentation For full technical details of this implementation, please refer to: [TELEGRAM_LOCK_TO_SINGLE_CONVERSATION_IMPLEMENTATION_EN.md](./TELEGRAM_LOCK_TO_SINGLE_CONVERSATION_IMPLEMENTATION_EN.md) --------- Co-authored-by: Muhsin Keloth --- .../dashboard/settings/inbox/Settings.vue | 3 +- .../telegram/incoming_message_service.rb | 8 ++- .../telegram/incoming_message_service_spec.rb | 71 +++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index aa1def9e9..5ae0cec2f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -206,7 +206,8 @@ export default { this.isASmsInbox || this.isAWhatsAppChannel || this.isAFacebookInbox || - this.isAPIInbox + this.isAPIInbox || + this.isATelegramChannel ); }, inboxNameLabel() { diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb index 6eeb192f2..897db29c3 100644 --- a/app/services/telegram/incoming_message_service.rb +++ b/app/services/telegram/incoming_message_service.rb @@ -77,7 +77,13 @@ class Telegram::IncomingMessageService end def set_conversation - @conversation = @contact_inbox.conversations.first + # if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved + @conversation = if @inbox.lock_to_single_conversation + @contact_inbox.conversations.last + else + @contact_inbox.conversations + .where.not(status: :resolved).last + end return if @conversation @conversation = ::Conversation.create!(conversation_params) diff --git a/spec/services/telegram/incoming_message_service_spec.rb b/spec/services/telegram/incoming_message_service_spec.rb index ef43dbe24..528161afe 100644 --- a/spec/services/telegram/incoming_message_service_spec.rb +++ b/spec/services/telegram/incoming_message_service_spec.rb @@ -411,4 +411,75 @@ describe Telegram::IncomingMessageService do end end end + + context 'when lock to single conversation is enabled' do + before do + # ensure message_params exists in this context and has from.id + message_params[:from] ||= {} + message_params[:from][:id] ||= 23 + end + + it 'reopens last conversation if last conversation is resolved' do + telegram_channel.inbox.update!(lock_to_single_conversation: true) + contact_inbox = ContactInbox.find_or_create_by(inbox: telegram_channel.inbox, source_id: message_params[:from][:id]) do |ci| + ci.contact = create(:contact) + end + resolved_conversation = create(:conversation, inbox: telegram_channel.inbox, contact_inbox: contact_inbox, status: :resolved) + + params = { + 'update_id' => 2_342_342_343_242, + 'message' => { 'text' => 'test' }.merge(message_params) + }.with_indifferent_access + + described_class.new(inbox: telegram_channel.inbox, params: params).perform + + expect(telegram_channel.inbox.conversations.count).to eq(1) + expect(resolved_conversation.reload.messages.last.content).to eq('test') + end + end + + context 'when lock to single conversation is disabled' do + before do + # ensure message_params exists in this context and has from.id + message_params[:from] ||= {} + message_params[:from][:id] ||= 23 + end + + it 'creates new conversation if last conversation is resolved' do + telegram_channel.inbox.update!(lock_to_single_conversation: false) + contact_inbox = ContactInbox.find_or_create_by(inbox: telegram_channel.inbox, source_id: message_params[:from][:id]) do |ci| + ci.contact = create(:contact) + end + _resolved_conversation = create(:conversation, inbox: telegram_channel.inbox, contact_inbox: contact_inbox, status: :resolved) + + params = { + 'update_id' => 2_342_342_343_242, + 'message' => { 'text' => 'test' }.merge(message_params) + }.with_indifferent_access + + described_class.new(inbox: telegram_channel.inbox, params: params).perform + + expect(telegram_channel.inbox.conversations.count).to eq(2) + expect(telegram_channel.inbox.conversations.last.messages.first.content).to eq('test') + expect(telegram_channel.inbox.conversations.last.status).to eq('open') + end + + it 'appends to last conversation if last conversation is not resolved' do + telegram_channel.inbox.update!(lock_to_single_conversation: false) + contact_inbox = ContactInbox.find_or_create_by(inbox: telegram_channel.inbox, source_id: message_params[:from][:id]) do |ci| + ci.contact = create(:contact) + end + open_conversation = create(:conversation, inbox: telegram_channel.inbox, contact_inbox: contact_inbox, status: :open) + + params = { + 'update_id' => 2_342_342_343_242, + 'message' => { 'text' => 'test' }.merge(message_params) + }.with_indifferent_access + + described_class.new(inbox: telegram_channel.inbox, params: params).perform + + expect(telegram_channel.inbox.conversations.count).to eq(1) + expect(open_conversation.reload.messages.last.content).to eq('test') + end + end end