parent
d8d14fc4a4
commit
b1aab228ae
16 changed files with 148 additions and 2 deletions
|
@ -12,6 +12,8 @@ Layout/LineLength:
|
||||||
Max: 150
|
Max: 150
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 125
|
Max: 125
|
||||||
|
Exclude:
|
||||||
|
- 'app/models/conversation.rb'
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 25
|
Max: 25
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
|
|
|
@ -20,6 +20,11 @@ class Api::V1::Accounts::ConversationsController < Api::BaseController
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
def mute
|
||||||
|
@conversation.mute!
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
def toggle_status
|
def toggle_status
|
||||||
@status = @conversation.toggle_status
|
@status = @conversation.toggle_status
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,6 +39,10 @@ class ConversationApi extends ApiClient {
|
||||||
typing_status: status,
|
typing_status: status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mute(conversationId) {
|
||||||
|
return axios.post(`${this.url}/${conversationId}/mute`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ConversationApi();
|
export default new ConversationApi();
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"UPDATE_ERROR": "Couldn't update labels, try again.",
|
"UPDATE_ERROR": "Couldn't update labels, try again.",
|
||||||
"TAG_PLACEHOLDER": "Add new label",
|
"TAG_PLACEHOLDER": "Add new label",
|
||||||
"PLACEHOLDER": "Search or add a label"
|
"PLACEHOLDER": "Search or add a label"
|
||||||
}
|
},
|
||||||
|
"MUTE_CONTACT": "Mute Contact"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,10 +90,14 @@
|
||||||
icon="ion-clock"
|
icon="ion-clock"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<a v-show="!currentChat.muted" class="contact--mute" @click="mute">
|
||||||
|
{{ $t('CONTACT_PANEL.MUTE_CONTACT') }}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
import ContactConversations from './ContactConversations.vue';
|
import ContactConversations from './ContactConversations.vue';
|
||||||
import ContactDetailsItem from './ContactDetailsItem.vue';
|
import ContactDetailsItem from './ContactDetailsItem.vue';
|
||||||
|
@ -117,6 +121,9 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
currentChat: 'getSelectedChat',
|
||||||
|
}),
|
||||||
currentConversationMetaData() {
|
currentConversationMetaData() {
|
||||||
return this.$store.getters[
|
return this.$store.getters[
|
||||||
'conversationMetadata/getConversationMetadata'
|
'conversationMetadata/getConversationMetadata'
|
||||||
|
@ -166,6 +173,9 @@ export default {
|
||||||
onPanelToggle() {
|
onPanelToggle() {
|
||||||
this.onToggle();
|
this.onToggle();
|
||||||
},
|
},
|
||||||
|
mute() {
|
||||||
|
this.$store.dispatch('muteConversation', this.conversationId);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -248,4 +258,10 @@ export default {
|
||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact--mute {
|
||||||
|
color: $alert-color;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -215,6 +215,15 @@ const actions = {
|
||||||
// Handle error
|
// Handle error
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
muteConversation: async ({ commit }, conversationId) => {
|
||||||
|
try {
|
||||||
|
await ConversationApi.mute(conversationId);
|
||||||
|
commit(types.default.MUTE_CONVERSATION);
|
||||||
|
} catch (error) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
|
|
|
@ -10,6 +10,7 @@ const initialSelectedChat = {
|
||||||
id: null,
|
id: null,
|
||||||
meta: {},
|
meta: {},
|
||||||
status: null,
|
status: null,
|
||||||
|
muted: false,
|
||||||
seen: false,
|
seen: false,
|
||||||
agentTyping: 'off',
|
agentTyping: 'off',
|
||||||
dataFetched: false,
|
dataFetched: false,
|
||||||
|
@ -116,6 +117,12 @@ const mutations = {
|
||||||
_state.selectedChat.status = status;
|
_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) {
|
[types.default.SEND_MESSAGE](_state, currentMessage) {
|
||||||
const [chat] = getSelectedChatConversation(_state);
|
const [chat] = getSelectedChatConversation(_state);
|
||||||
const allMessagesExceptCurrent = (chat.messages || []).filter(
|
const allMessagesExceptCurrent = (chat.messages || []).filter(
|
||||||
|
|
|
@ -21,4 +21,16 @@ describe('#actions', () => {
|
||||||
expect(commit.mock.calls).toEqual([]);
|
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',
|
RESOLVE_CONVERSATION: 'RESOLVE_CONVERSATION',
|
||||||
ADD_CONVERSATION: 'ADD_CONVERSATION',
|
ADD_CONVERSATION: 'ADD_CONVERSATION',
|
||||||
UPDATE_CONVERSATION: 'UPDATE_CONVERSATION',
|
UPDATE_CONVERSATION: 'UPDATE_CONVERSATION',
|
||||||
|
MUTE_CONVERSATION: 'MUTE_CONVERSATION',
|
||||||
SEND_MESSAGE: 'SEND_MESSAGE',
|
SEND_MESSAGE: 'SEND_MESSAGE',
|
||||||
ASSIGN_AGENT: 'ASSIGN_AGENT',
|
ASSIGN_AGENT: 'ASSIGN_AGENT',
|
||||||
SET_CHAT_META: 'SET_CHAT_META',
|
SET_CHAT_META: 'SET_CHAT_META',
|
||||||
|
|
|
@ -74,6 +74,15 @@ class Conversation < ApplicationRecord
|
||||||
save
|
save
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mute!
|
||||||
|
resolved!
|
||||||
|
Redis::Alfred.setex(mute_key, 1, mute_period)
|
||||||
|
end
|
||||||
|
|
||||||
|
def muted?
|
||||||
|
!Redis::Alfred.get(mute_key).nil?
|
||||||
|
end
|
||||||
|
|
||||||
def lock!
|
def lock!
|
||||||
update!(locked: true)
|
update!(locked: true)
|
||||||
end
|
end
|
||||||
|
@ -184,4 +193,12 @@ class Conversation < ApplicationRecord
|
||||||
|
|
||||||
messages.create(activity_message_params(content))
|
messages.create(activity_message_params(content))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mute_key
|
||||||
|
format('CONVERSATION::%<id>d::MUTED', id: id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def mute_period
|
||||||
|
6.hours
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -141,7 +141,7 @@ class Message < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def reopen_conversation
|
def reopen_conversation
|
||||||
conversation.open! if incoming? && conversation.resolved?
|
conversation.open! if incoming? && conversation.resolved? && !conversation.muted?
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_message_template_hooks
|
def execute_message_template_hooks
|
||||||
|
|
|
@ -17,6 +17,7 @@ end
|
||||||
|
|
||||||
json.inbox_id conversation.inbox_id
|
json.inbox_id conversation.inbox_id
|
||||||
json.status conversation.status
|
json.status conversation.status
|
||||||
|
json.muted conversation.muted?
|
||||||
json.timestamp conversation.messages.last.try(:created_at).try(:to_i)
|
json.timestamp conversation.messages.last.try(:created_at).try(:to_i)
|
||||||
json.user_last_seen_at conversation.user_last_seen_at.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
|
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]
|
resources :labels, only: [:create, :index]
|
||||||
end
|
end
|
||||||
member do
|
member do
|
||||||
|
post :mute
|
||||||
post :toggle_status
|
post :toggle_status
|
||||||
post :toggle_typing_status
|
post :toggle_typing_status
|
||||||
post :update_last_seen
|
post :update_last_seen
|
||||||
|
|
|
@ -177,4 +177,30 @@ RSpec.describe 'Conversations API', type: :request do
|
||||||
end
|
end
|
||||||
end
|
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
|
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.present?).to eq(true)
|
||||||
expect(conversation.messages.last.attachments.first.file_type).to eq('image')
|
expect(conversation.messages.last.attachments.first.file_type).to eq('image')
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -171,6 +171,37 @@ RSpec.describe Conversation, type: :model do
|
||||||
end
|
end
|
||||||
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
|
describe 'unread_messages' do
|
||||||
subject(:unread_messages) { conversation.unread_messages }
|
subject(:unread_messages) { conversation.unread_messages }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue