diff --git a/app/javascript/dashboard/components/widgets/WootWriter/AudioRecorder.vue b/app/javascript/dashboard/components/widgets/WootWriter/AudioRecorder.vue index 68072c846..55db1aad8 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/AudioRecorder.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/AudioRecorder.vue @@ -13,15 +13,19 @@ import videojs from 'video.js'; import alertMixin from '../../../../shared/mixins/alertMixin'; import Recorder from 'opus-recorder'; + +// Workers to record Audio .ogg and .wav import encoderWorker from 'opus-recorder/dist/encoderWorker.min'; import waveWorker from 'opus-recorder/dist/waveWorker.min'; import WaveSurfer from 'wavesurfer.js'; import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js'; -import 'videojs-wavesurfer/dist/videojs.wavesurfer.js'; +import 'videojs-wavesurfer/dist/videojs.wavesurfer.js'; import 'videojs-record/dist/videojs.record.js'; -import 'videojs-record/dist/plugins/videojs.record.opus-recorder.js'; + +import OpusRecorderEngine from 'videojs-record/dist/plugins/videojs.record.opus-recorder.js'; + import { format, addSeconds } from 'date-fns'; import { AUDIO_FORMATS } from 'shared/constants/messages'; @@ -33,7 +37,7 @@ export default { props: { audioRecordFormat: { type: String, - default: AUDIO_FORMATS.WEBM, + default: AUDIO_FORMATS.WAV, }, }, data() { @@ -79,24 +83,18 @@ export default { maxLength: 900, timeSlice: 1000, maxFileSize: 15 * 1024 * 1024, - ...(this.audioRecordFormat === AUDIO_FORMATS.WEBM && { - monitorGain: 0, - recordingGain: 1, - numberOfChannels: 1, - encoderSampleRate: 16000, - originalSampleRateOverride: 16000, - streamPages: true, - maxFramesPerPage: 1, - encoderFrameSize: 1, - encoderPath: waveWorker, + displayMilliseconds: false, + audioChannels: 1, + audioSampleRate: 48000, + audioBitRate: 128, + audioEngine: 'opus-recorder', + ...(this.audioRecordFormat === AUDIO_FORMATS.WAV && { + audioMimeType: 'audio/wav', + audioWorkerURL: waveWorker, }), ...(this.audioRecordFormat === AUDIO_FORMATS.OGG && { - displayMilliseconds: false, - audioEngine: 'opus-recorder', + audioMimeType: 'audio/ogg', audioWorkerURL: encoderWorker, - audioChannels: 1, - audioSampleRate: 48000, - audioBitRate: 128, }), }, }, @@ -134,6 +132,11 @@ export default { }, methods: { deviceReady() { + if (this.player.record().engine instanceof OpusRecorderEngine) { + if (this.audioRecordFormat === AUDIO_FORMATS.WAV) { + this.player.record().engine.audioType = 'audio/wav'; + } + } this.player.record().start(); }, startRecord() { diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index bae30d407..1c6f4f796 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -501,10 +501,10 @@ export default { return `draft-${this.conversationIdByRoute}-${this.replyType}`; }, audioRecordFormat() { - if (this.isAWebWidgetInbox) { - return AUDIO_FORMATS.WEBM; + if (this.isAWhatsAppChannel) { + return AUDIO_FORMATS.OGG; } - return AUDIO_FORMATS.OGG; + return AUDIO_FORMATS.WAV; }, messageVariables() { const variables = getMessageVariables({ diff --git a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js index d25b70589..b2d68c94f 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/actions.spec.js @@ -404,6 +404,33 @@ describe('#actions', () => { expect(commit.mock.calls).toEqual([[types.CLEAR_CONVERSATION_FILTERS]]); }); }); + + describe('#updateConversationLastActivity', () => { + it('sends correct action', async () => { + await actions.updateConversationLastActivity( + { commit }, + { conversationId: 1, lastActivityAt: 12121212 } + ); + expect(commit.mock.calls).toEqual([ + [ + 'UPDATE_CONVERSATION_LAST_ACTIVITY', + { conversationId: 1, lastActivityAt: 12121212 }, + ], + ]); + }); + }); + + describe('#setChatSortFilter', () => { + it('sends correct action', async () => { + await actions.setChatSortFilter( + { commit }, + { data: 'sort_on_created_at' } + ); + expect(commit.mock.calls).toEqual([ + ['CHANGE_CHAT_SORT_FILTER', { data: 'sort_on_created_at' }], + ]); + }); + }); }); describe('#deleteMessage', () => { diff --git a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js index 7dc0dade5..f9d775df2 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/getters.spec.js @@ -6,43 +6,127 @@ commonHelpers(); describe('#getters', () => { describe('#getAllConversations', () => { - it('order conversations based on last message date', () => { + it('order conversations based on last activity', () => { const state = { allConversations: [ { id: 1, messages: [ { - created_at: 1466424480, + content: 'test1', }, ], + created_at: 2466424490, + last_activity_at: 2466424490, }, { id: 2, - messages: [ - { - created_at: 2466424490, - }, - ], + messages: [{ content: 'test2' }], + created_at: 1466424480, + last_activity_at: 1466424480, }, ], }; + + expect(getters.getAllConversations(state)).toEqual([ + { + id: 1, + messages: [ + { + content: 'test1', + }, + ], + created_at: 2466424490, + last_activity_at: 2466424490, + }, + { + id: 2, + messages: [{ content: 'test2' }], + created_at: 1466424480, + last_activity_at: 1466424480, + }, + ]); + }); + it('order conversations based on created at', () => { + const state = { + allConversations: [ + { + id: 1, + messages: [ + { + content: 'test1', + }, + ], + created_at: 1683645801, // Tuesday, 9 May 2023 + last_activity_at: 2466424490, + }, + { + id: 2, + messages: [{ content: 'test2' }], + created_at: 1652109801, // Monday, 9 May 2022 + last_activity_at: 1466424480, + }, + ], + chatSortFilter: 'sort_on_created_at', + }; + expect(getters.getAllConversations(state)).toEqual([ { id: 2, - messages: [ - { - created_at: 2466424490, - }, - ], + messages: [{ content: 'test2' }], + created_at: 1652109801, + last_activity_at: 1466424480, }, { id: 1, messages: [ { - created_at: 1466424480, + content: 'test1', }, ], + created_at: 1683645801, + last_activity_at: 2466424490, + }, + ]); + }); + it('order conversations based on default order', () => { + const state = { + allConversations: [ + { + id: 1, + messages: [ + { + content: 'test1', + }, + ], + created_at: 2466424490, + last_activity_at: 2466424490, + }, + { + id: 2, + messages: [{ content: 'test2' }], + created_at: 1466424480, + last_activity_at: 1466424480, + }, + ], + }; + + expect(getters.getAllConversations(state)).toEqual([ + { + id: 1, + messages: [ + { + content: 'test1', + }, + ], + created_at: 2466424490, + last_activity_at: 2466424490, + }, + { + id: 2, + messages: [{ content: 'test2' }], + created_at: 1466424480, + last_activity_at: 1466424480, }, ]); }); diff --git a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js index 738de5c55..2c4bdb2cc 100644 --- a/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js +++ b/app/javascript/dashboard/store/modules/specs/conversations/mutations.spec.js @@ -39,6 +39,19 @@ describe('#mutations', () => { describe('#ASSIGN_TEAM', () => { it('clears current chat window', () => { + const state = { allConversations: [{ id: 1, meta: {} }] }; + mutations[types.UPDATE_CONVERSATION_LAST_ACTIVITY](state, { + lastActivityAt: 1602256198, + conversationId: 1, + }); + + expect(state.allConversations).toEqual([ + { id: 1, meta: {}, last_activity_at: 1602256198 }, + ]); + }); + }); + describe('#UPDATE_CONVERSATION_LAST_ACTIVITY', () => { + it('update conversation last activity', () => { const state = { allConversations: [{ id: 1, meta: {} }] }; mutations[types.ASSIGN_TEAM](state, { team: { id: 1, name: 'Team 1' }, @@ -50,6 +63,16 @@ describe('#mutations', () => { }); }); + describe('#CHANGE_CHAT_SORT_FILTER', () => { + it('update conversation sort filter', () => { + const state = { chatSortFilter: 'latest' }; + mutations[types.CHANGE_CHAT_SORT_FILTER](state, { + data: 'sort_on_created_at', + }); + expect(state.chatSortFilter).toEqual({ data: 'sort_on_created_at' }); + }); + }); + describe('#SET_CURRENT_CHAT_WINDOW', () => { it('set current chat window', () => { const state = { selectedChatId: 1 }; diff --git a/app/javascript/shared/constants/messages.js b/app/javascript/shared/constants/messages.js index 63ca99afc..77501cb76 100644 --- a/app/javascript/shared/constants/messages.js +++ b/app/javascript/shared/constants/messages.js @@ -84,6 +84,7 @@ export const CSAT_RATINGS = [ export const AUDIO_FORMATS = { WEBM: 'audio/webm', OGG: 'audio/ogg', + WAV: 'audio/wav', }; export const MESSAGE_VARIABLES = [ diff --git a/app/models/conversation.rb b/app/models/conversation.rb index fa59e8d24..e01fb3863 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -195,6 +195,10 @@ class Conversation < ApplicationRecord messages.chat.last(5) end + def csat_survey_link + "#{ENV.fetch('FRONTEND_URL', nil)}/survey/responses/#{uuid}" + end + private def execute_after_update_commit_callbacks diff --git a/app/models/message.rb b/app/models/message.rb index 3b1ddda11..a73cbfd66 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -27,6 +27,7 @@ # index_messages_on_additional_attributes_campaign_id (((additional_attributes -> 'campaign_id'::text))) USING gin # index_messages_on_content (content) USING gin # index_messages_on_conversation_id (conversation_id) +# index_messages_on_created_at (created_at) # index_messages_on_inbox_id (inbox_id) # index_messages_on_sender_type_and_sender_id (sender_type,sender_id) # index_messages_on_source_id (source_id) diff --git a/app/views/mailers/conversation_reply_mailer/reply_with_summary.html.erb b/app/views/mailers/conversation_reply_mailer/reply_with_summary.html.erb index 34665b777..c6cb77ad2 100644 --- a/app/views/mailers/conversation_reply_mailer/reply_with_summary.html.erb +++ b/app/views/mailers/conversation_reply_mailer/reply_with_summary.html.erb @@ -5,18 +5,26 @@ <% @messages.each do |message| %> - <%= message.incoming? ? 'You' : message.sender&.available_name || message.sender&.name %> + <%= message.incoming? ? 'You' : message.sender&.available_name || message.sender&.name || 'Bot' %> - - <% if message.content %> + + <% if (message.content_type == 'input_csat' && message.message_type == 'template') %> +

Click here to rate the conversation.

+ <% elsif message.content.present? %> <%= CommonMarker.render_html(message.content).html_safe %> <% end %> - <% if message.attachments %> - <% message.attachments.each do |attachment| %> - Attachment [Click here to view] - <% end %> + <% if message.attachments.count.positive? %> +

+ <% if message.content.present? %> +


+ <% end %> + This message contains <%= message.attachments.count > 1 ? 'attachments' : 'an attachment' %>. + <% message.attachments.each do |attachment| %> +
- View the attachment here. + <% end %> +

<% end %> diff --git a/db/migrate/20230509101256_add_index_to_messages_created_at.rb b/db/migrate/20230509101256_add_index_to_messages_created_at.rb new file mode 100644 index 000000000..0bee32749 --- /dev/null +++ b/db/migrate/20230509101256_add_index_to_messages_created_at.rb @@ -0,0 +1,6 @@ +class AddIndexToMessagesCreatedAt < ActiveRecord::Migration[7.0] + disable_ddl_transaction! + def change + add_index :messages, [:created_at], name: 'index_messages_on_created_at', algorithm: :concurrently + end +end diff --git a/db/schema.rb b/db/schema.rb index d3753968a..84f3af8fc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_04_26_130150) do +ActiveRecord::Schema[7.0].define(version: 2023_05_09_101256) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -667,6 +667,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_04_26_130150) do t.index ["account_id"], name: "index_messages_on_account_id" t.index ["content"], name: "index_messages_on_content", opclass: :gin_trgm_ops, using: :gin t.index ["conversation_id"], name: "index_messages_on_conversation_id" + t.index ["created_at"], name: "index_messages_on_created_at" t.index ["inbox_id"], name: "index_messages_on_inbox_id" t.index ["sender_type", "sender_id"], name: "index_messages_on_sender_type_and_sender_id" t.index ["source_id"], name: "index_messages_on_source_id"