mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	feat: Add support for template variables in messages content (#6215)
Fixes: #6078 Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
		
							
								
								
									
										17
									
								
								app/drops/contact_drop.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/drops/contact_drop.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | class ContactDrop < BaseDrop | ||||||
|  |   def email | ||||||
|  |     @obj.try(:email) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def phone_number | ||||||
|  |     @obj.try(:phone_number) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def first_name | ||||||
|  |     @obj.try(:name).try(:split).try(:first) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def last_name | ||||||
|  |     @obj.try(:name).try(:split).try(:last) if @obj.try(:name).try(:split).try(:size) > 1 | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -2,4 +2,12 @@ class UserDrop < BaseDrop | |||||||
|   def available_name |   def available_name | ||||||
|     @obj.try(:available_name) |     @obj.try(:available_name) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   def first_name | ||||||
|  |     @obj.try(:name).try(:split).try(:first) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def last_name | ||||||
|  |     @obj.try(:name).try(:split).try(:last) if @obj.try(:name).try(:split).try(:size) > 1 | ||||||
|  |   end | ||||||
| end | end | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								app/models/concerns/liquidable.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/models/concerns/liquidable.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | module Liquidable | ||||||
|  |   extend ActiveSupport::Concern | ||||||
|  |  | ||||||
|  |   included do | ||||||
|  |     acts_as_taggable_on :labels | ||||||
|  |     before_create :process_liquid_in_content | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   private | ||||||
|  |  | ||||||
|  |   def message_drops | ||||||
|  |     { | ||||||
|  |       'contact' => ContactDrop.new(conversation.contact), | ||||||
|  |       'agent' => UserDrop.new(sender), | ||||||
|  |       'conversation' => ConversationDrop.new(conversation), | ||||||
|  |       'inbox' => InboxDrop.new(inbox) | ||||||
|  |     } | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def liquid_processable_message? | ||||||
|  |     content.present? && message_type == 'outgoing' | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def process_liquid_in_content | ||||||
|  |     return unless liquid_processable_message? | ||||||
|  |  | ||||||
|  |     template = Liquid::Template.parse(modified_liquid_content) | ||||||
|  |     self.content = template.render(message_drops) | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   def modified_liquid_content | ||||||
|  |     # This regex is used to match the code blocks in the content | ||||||
|  |     # We don't want to process liquid in code blocks | ||||||
|  |     content.gsub(/`(.*?)`/m, '{% raw %}`\\1`{% endraw %}') | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -32,6 +32,7 @@ | |||||||
|  |  | ||||||
| class Message < ApplicationRecord | class Message < ApplicationRecord | ||||||
|   include MessageFilterHelpers |   include MessageFilterHelpers | ||||||
|  |   include Liquidable | ||||||
|   NUMBER_OF_PERMITTED_ATTACHMENTS = 15 |   NUMBER_OF_PERMITTED_ATTACHMENTS = 15 | ||||||
|  |  | ||||||
|   before_validation :ensure_content_type |   before_validation :ensure_content_type | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								spec/drops/contact_drop_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								spec/drops/contact_drop_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | describe ::ContactDrop do | ||||||
|  |   subject(:contact_drop) { described_class.new(contact) } | ||||||
|  |  | ||||||
|  |   let!(:contact) { create(:contact) } | ||||||
|  |  | ||||||
|  |   context 'when first name' do | ||||||
|  |     it 'returns first name' do | ||||||
|  |       contact.update!(name: 'John Doe') | ||||||
|  |       expect(subject.first_name).to eq 'John' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   context 'when last name' do | ||||||
|  |     it 'returns the last name' do | ||||||
|  |       contact.update!(name: 'John Doe') | ||||||
|  |       expect(subject.last_name).to eq 'Doe' | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     it 'returns empty when last name not present' do | ||||||
|  |       contact.update!(name: 'John') | ||||||
|  |       expect(subject.last_name).to be_nil | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										26
									
								
								spec/drops/user_drop_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								spec/drops/user_drop_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | describe ::UserDrop do | ||||||
|  |   subject(:user_drop) { described_class.new(user) } | ||||||
|  |  | ||||||
|  |   let!(:user) { create(:user) } | ||||||
|  |  | ||||||
|  |   context 'when first name' do | ||||||
|  |     it 'returns first name' do | ||||||
|  |       user.update!(name: 'John Doe') | ||||||
|  |       expect(subject.first_name).to eq 'John' | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  |  | ||||||
|  |   context 'when last name' do | ||||||
|  |     it 'returns the last name' do | ||||||
|  |       user.update!(name: 'John Doe') | ||||||
|  |       expect(subject.last_name).to eq 'Doe' | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     it 'returns empty when last name not present' do | ||||||
|  |       user.update!(name: 'John') | ||||||
|  |       expect(subject.last_name).to be_nil | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
							
								
								
									
										58
									
								
								spec/models/concerns/liquidable_shared.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								spec/models/concerns/liquidable_shared.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | require 'rails_helper' | ||||||
|  |  | ||||||
|  | shared_examples_for 'liqudable' do | ||||||
|  |   context 'when liquid is present in content' do | ||||||
|  |     let(:contact) { create(:contact, name: 'john', phone_number: '+912883') } | ||||||
|  |     let(:conversation) { create(:conversation, id: 1, contact: contact) } | ||||||
|  |  | ||||||
|  |     context 'when message is incoming' do | ||||||
|  |       let(:message) { build(:message, conversation: conversation, message_type: 'incoming') } | ||||||
|  |  | ||||||
|  |       it 'will not process liquid in content' do | ||||||
|  |         message.content = 'hey {{contact.name}} how are you?' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'hey {{contact.name}} how are you?' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |  | ||||||
|  |     context 'when message is outgoing' do | ||||||
|  |       let(:message) { build(:message, conversation: conversation, message_type: 'outgoing') } | ||||||
|  |  | ||||||
|  |       it 'set replaces liquid variables in message' do | ||||||
|  |         message.content = 'hey {{contact.name}} how are you?' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'hey john how are you?' | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'process liquid operators like default value' do | ||||||
|  |         message.content = 'Can we send you an email at {{ contact.email | default: "default"  }} ?' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'Can we send you an email at default ?' | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'return empty string when value is not available' do | ||||||
|  |         message.content = 'Can we send you an email at {{contact.email}}?' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'Can we send you an email at ?' | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'will not process liquid tags in multiple code blocks' do | ||||||
|  |         message.content = 'hey {{contact.name}} how are you? ```code: {{contact.name}}``` ``` code: {{contact.name}} ``` test `{{contact.name}}`' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'hey john how are you? ```code: {{contact.name}}``` ``` code: {{contact.name}} ``` test `{{contact.name}}`' | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'will not process liquid tags in single ticks' do | ||||||
|  |         message.content = 'hey {{contact.name}} how are you? ` code: {{contact.name}} ` ` code: {{contact.name}} ` test' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'hey john how are you? ` code: {{contact.name}} ` ` code: {{contact.name}} ` test' | ||||||
|  |       end | ||||||
|  |  | ||||||
|  |       it 'will not throw error for broken quotes' do | ||||||
|  |         message.content = 'hey {{contact.name}} how are you? ` code: {{contact.name}} ` ` code: {{contact.name}} test' | ||||||
|  |         message.save! | ||||||
|  |         expect(message.content).to eq 'hey john how are you? ` code: {{contact.name}} ` ` code: john test' | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
|  |  | ||||||
| require 'rails_helper' | require 'rails_helper' | ||||||
|  | require Rails.root.join 'spec/models/concerns/liquidable_shared.rb' | ||||||
|  |  | ||||||
| RSpec.describe Message, type: :model do | RSpec.describe Message, type: :model do | ||||||
|   context 'with validations' do |   context 'with validations' do | ||||||
| @@ -9,6 +10,10 @@ RSpec.describe Message, type: :model do | |||||||
|     it { is_expected.to validate_presence_of(:account_id) } |     it { is_expected.to validate_presence_of(:account_id) } | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  |   describe 'concerns' do | ||||||
|  |     it_behaves_like 'liqudable' | ||||||
|  |   end | ||||||
|  |  | ||||||
|   describe '#reopen_conversation' do |   describe '#reopen_conversation' do | ||||||
|     let(:conversation) { create(:conversation) } |     let(:conversation) { create(:conversation) } | ||||||
|     let(:message) { build(:message, message_type: :incoming, conversation: conversation) } |     let(:message) { build(:message, message_type: :incoming, conversation: conversation) } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Muhsin Keloth
					Muhsin Keloth