mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	 Abdulkadir Poyraz
					Abdulkadir Poyraz
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							d8d14fc4a4
						
					
				
				
					commit
					b1aab228ae
				
			| @@ -12,6 +12,8 @@ Layout/LineLength: | ||||
|   Max: 150 | ||||
| Metrics/ClassLength: | ||||
|   Max: 125 | ||||
|   Exclude: | ||||
|     - 'app/models/conversation.rb' | ||||
| RSpec/ExampleLength: | ||||
|   Max: 25 | ||||
| Style/Documentation: | ||||
|   | ||||
| @@ -20,6 +20,11 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController | ||||
|  | ||||
|   def show; end | ||||
|  | ||||
|   def mute | ||||
|     @conversation.mute! | ||||
|     head :ok | ||||
|   end | ||||
|  | ||||
|   def toggle_status | ||||
|     @status = @conversation.toggle_status | ||||
|   end | ||||
|   | ||||
| @@ -39,6 +39,10 @@ class ConversationApi extends ApiClient { | ||||
|       typing_status: status, | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   mute(conversationId) { | ||||
|     return axios.post(`${this.url}/${conversationId}/mute`); | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default new ConversationApi(); | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
|       "UPDATE_ERROR": "Couldn't update labels, try again.", | ||||
|       "TAG_PLACEHOLDER": "Add new label", | ||||
|       "PLACEHOLDER": "Search or add a label" | ||||
|     } | ||||
|     }, | ||||
|     "MUTE_CONTACT": "Mute Contact" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -90,10 +90,14 @@ | ||||
|         icon="ion-clock" | ||||
|       /> | ||||
|     </div> | ||||
|     <a v-show="!currentChat.muted" class="contact--mute" @click="mute"> | ||||
|       {{ $t('CONTACT_PANEL.MUTE_CONTACT') }} | ||||
|     </a> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import { mapGetters } from 'vuex'; | ||||
| import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue'; | ||||
| import ContactConversations from './ContactConversations.vue'; | ||||
| import ContactDetailsItem from './ContactDetailsItem.vue'; | ||||
| @@ -117,6 +121,9 @@ export default { | ||||
|     }, | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters({ | ||||
|       currentChat: 'getSelectedChat', | ||||
|     }), | ||||
|     currentConversationMetaData() { | ||||
|       return this.$store.getters[ | ||||
|         'conversationMetadata/getConversationMetadata' | ||||
| @@ -166,6 +173,9 @@ export default { | ||||
|     onPanelToggle() { | ||||
|       this.onToggle(); | ||||
|     }, | ||||
|     mute() { | ||||
|       this.$store.dispatch('muteConversation', this.conversationId); | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
| </script> | ||||
| @@ -248,4 +258,10 @@ export default { | ||||
|     padding: 0.2rem; | ||||
|   } | ||||
| } | ||||
|  | ||||
| .contact--mute { | ||||
|   color: $alert-color; | ||||
|   display: block; | ||||
|   text-align: center; | ||||
| } | ||||
| </style> | ||||
|   | ||||
| @@ -215,6 +215,15 @@ const actions = { | ||||
|       // Handle error | ||||
|     } | ||||
|   }, | ||||
|  | ||||
|   muteConversation: async ({ commit }, conversationId) => { | ||||
|     try { | ||||
|       await ConversationApi.mute(conversationId); | ||||
|       commit(types.default.MUTE_CONVERSATION); | ||||
|     } catch (error) { | ||||
|       // | ||||
|     } | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| export default actions; | ||||
|   | ||||
| @@ -10,6 +10,7 @@ const initialSelectedChat = { | ||||
|   id: null, | ||||
|   meta: {}, | ||||
|   status: null, | ||||
|   muted: false, | ||||
|   seen: false, | ||||
|   agentTyping: 'off', | ||||
|   dataFetched: false, | ||||
| @@ -116,6 +117,12 @@ const mutations = { | ||||
|     _state.selectedChat.status = status; | ||||
|   }, | ||||
|  | ||||
|   [types.default.MUTE_CONVERSATION](_state) { | ||||
|     const [chat] = getSelectedChatConversation(_state); | ||||
|     chat.muted = true; | ||||
|     _state.selectedChat.muted = true; | ||||
|   }, | ||||
|  | ||||
|   [types.default.SEND_MESSAGE](_state, currentMessage) { | ||||
|     const [chat] = getSelectedChatConversation(_state); | ||||
|     const allMessagesExceptCurrent = (chat.messages || []).filter( | ||||
|   | ||||
| @@ -21,4 +21,16 @@ describe('#actions', () => { | ||||
|       expect(commit.mock.calls).toEqual([]); | ||||
|     }); | ||||
|   }); | ||||
|   describe('#muteConversation', () => { | ||||
|     it('sends correct actions if API is success', async () => { | ||||
|       axios.get.mockResolvedValue(null); | ||||
|       await actions.muteConversation({ commit }, 1); | ||||
|       expect(commit.mock.calls).toEqual([[types.default.MUTE_CONVERSATION]]); | ||||
|     }); | ||||
|     it('sends correct actions if API is error', async () => { | ||||
|       axios.get.mockRejectedValue({ message: 'Incorrect header' }); | ||||
|       await actions.getConversation({ commit }); | ||||
|       expect(commit.mock.calls).toEqual([]); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
| @@ -22,6 +22,7 @@ export default { | ||||
|   RESOLVE_CONVERSATION: 'RESOLVE_CONVERSATION', | ||||
|   ADD_CONVERSATION: 'ADD_CONVERSATION', | ||||
|   UPDATE_CONVERSATION: 'UPDATE_CONVERSATION', | ||||
|   MUTE_CONVERSATION: 'MUTE_CONVERSATION', | ||||
|   SEND_MESSAGE: 'SEND_MESSAGE', | ||||
|   ASSIGN_AGENT: 'ASSIGN_AGENT', | ||||
|   SET_CHAT_META: 'SET_CHAT_META', | ||||
|   | ||||
| @@ -74,6 +74,15 @@ class Conversation < ApplicationRecord | ||||
|     save | ||||
|   end | ||||
|  | ||||
|   def mute! | ||||
|     resolved! | ||||
|     Redis::Alfred.setex(mute_key, 1, mute_period) | ||||
|   end | ||||
|  | ||||
|   def muted? | ||||
|     !Redis::Alfred.get(mute_key).nil? | ||||
|   end | ||||
|  | ||||
|   def lock! | ||||
|     update!(locked: true) | ||||
|   end | ||||
| @@ -184,4 +193,12 @@ class Conversation < ApplicationRecord | ||||
|  | ||||
|     messages.create(activity_message_params(content)) | ||||
|   end | ||||
|  | ||||
|   def mute_key | ||||
|     format('CONVERSATION::%<id>d::MUTED', id: id) | ||||
|   end | ||||
|  | ||||
|   def mute_period | ||||
|     6.hours | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -141,7 +141,7 @@ class Message < ApplicationRecord | ||||
|   end | ||||
|  | ||||
|   def reopen_conversation | ||||
|     conversation.open! if incoming? && conversation.resolved? | ||||
|     conversation.open! if incoming? && conversation.resolved? && !conversation.muted? | ||||
|   end | ||||
|  | ||||
|   def execute_message_template_hooks | ||||
|   | ||||
| @@ -17,6 +17,7 @@ end | ||||
|  | ||||
| json.inbox_id conversation.inbox_id | ||||
| json.status conversation.status | ||||
| json.muted conversation.muted? | ||||
| json.timestamp conversation.messages.last.try(:created_at).try(:to_i) | ||||
| json.user_last_seen_at conversation.user_last_seen_at.to_i | ||||
| json.agent_last_seen_at conversation.agent_last_seen_at.to_i | ||||
|   | ||||
| @@ -48,6 +48,7 @@ Rails.application.routes.draw do | ||||
|             resources :labels, only: [:create, :index] | ||||
|           end | ||||
|           member do | ||||
|             post :mute | ||||
|             post :toggle_status | ||||
|             post :toggle_typing_status | ||||
|             post :update_last_seen | ||||
|   | ||||
| @@ -177,4 +177,30 @@ RSpec.describe 'Conversations API', type: :request do | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'POST /api/v1/accounts/{account.id}/conversations/:id/mute' do | ||||
|     let(:conversation) { create(:conversation, account: account) } | ||||
|  | ||||
|     context 'when it is an unauthenticated user' do | ||||
|       it 'returns unauthorized' do | ||||
|         post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/mute" | ||||
|  | ||||
|         expect(response).to have_http_status(:unauthorized) | ||||
|       end | ||||
|     end | ||||
|  | ||||
|     context 'when it is an authenticated user' do | ||||
|       let(:agent) { create(:user, account: account, role: :agent) } | ||||
|  | ||||
|       it 'mutes conversation' do | ||||
|         post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/mute", | ||||
|              headers: agent.create_new_auth_token, | ||||
|              as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|         expect(conversation.reload.resolved?).to eq(true) | ||||
|         expect(conversation.reload.muted?).to eq(true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -59,6 +59,19 @@ RSpec.describe '/api/v1/widget/messages', type: :request do | ||||
|         expect(conversation.messages.last.attachments.first.file.present?).to eq(true) | ||||
|         expect(conversation.messages.last.attachments.first.file_type).to eq('image') | ||||
|       end | ||||
|  | ||||
|       it 'does not reopen conversation when conversation is muted' do | ||||
|         conversation.mute! | ||||
|  | ||||
|         message_params = { content: 'hello world', timestamp: Time.current } | ||||
|         post api_v1_widget_messages_url, | ||||
|              params: { website_token: web_widget.website_token, message: message_params }, | ||||
|              headers: { 'X-Auth-Token' => token }, | ||||
|              as: :json | ||||
|  | ||||
|         expect(response).to have_http_status(:success) | ||||
|         expect(conversation.reload.resolved?).to eq(true) | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -171,6 +171,37 @@ RSpec.describe Conversation, type: :model do | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe '#mute!' do | ||||
|     subject(:mute!) { conversation.mute! } | ||||
|  | ||||
|     let(:conversation) { create(:conversation) } | ||||
|  | ||||
|     it 'marks conversation as resolved' do | ||||
|       mute! | ||||
|       expect(conversation.reload.resolved?).to eq(true) | ||||
|     end | ||||
|  | ||||
|     it 'marks conversation as muted in redis' do | ||||
|       mute! | ||||
|       expect(Redis::Alfred.get(conversation.send(:mute_key))).not_to eq(nil) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe '#muted?' do | ||||
|     subject(:muted?) { conversation.muted? } | ||||
|  | ||||
|     let(:conversation) { create(:conversation) } | ||||
|  | ||||
|     it 'return true if conversation is muted' do | ||||
|       conversation.mute! | ||||
|       expect(muted?).to eq(true) | ||||
|     end | ||||
|  | ||||
|     it 'returns false if conversation is not muted' do | ||||
|       expect(muted?).to eq(false) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'unread_messages' do | ||||
|     subject(:unread_messages) { conversation.unread_messages } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user