Merge branch 'develop' of github.com:chatwoot/chatwoot into feature/cw-1605

This commit is contained in:
Shivam Mishra
2023-05-10 17:50:37 +05:30
11 changed files with 200 additions and 42 deletions
@@ -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() {
@@ -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({
@@ -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', () => {
@@ -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,
},
]);
});
@@ -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 };
@@ -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 = [
+4
View File
@@ -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
+1
View File
@@ -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)
@@ -5,18 +5,26 @@
<% @messages.each do |message| %>
<tr>
<td>
<b><%= message.incoming? ? 'You' : message.sender&.available_name || message.sender&.name %></b>
<b><%= message.incoming? ? 'You' : message.sender&.available_name || message.sender&.name || 'Bot' %></b>
</td>
</tr>
<tr>
<td style="padding: 10px 20px; margin: 5px 0 20px 0; background: #F2F3F7; border-radius: 10px; display: inline-block; font-family: 'Helvetica Neue',Tahoma,Arial,sans-serif; text-align: start; unicode-bidi: plaintext;">
<% if message.content %>
<td style="padding: 0px 16px; margin: 4px 0 8px 0; background: #F5FAFF; border-radius: 5px; display: inline-block; font-family: 'Helvetica Neue',Tahoma,Arial,sans-serif; text-align: start; unicode-bidi: plaintext;">
<% if (message.content_type == 'input_csat' && message.message_type == 'template') %>
<p>Click <a href="<%= message.conversation.csat_survey_link %>" _target="blank">here</a> to rate the conversation.</p>
<% elsif message.content.present? %>
<%= CommonMarker.render_html(message.content).html_safe %>
<% end %>
<% if message.attachments %>
<% message.attachments.each do |attachment| %>
Attachment [<a href="<%= attachment.file_url %>" _target="blank">Click here to view</a>]
<% end %>
<% if message.attachments.count.positive? %>
<p>
<% if message.content.present? %>
<hr style="border: 0; border-bottom: 1px solid #AEC3D5;"/>
<% end %>
This message contains <%= message.attachments.count > 1 ? 'attachments' : 'an attachment' %>.
<% message.attachments.each do |attachment| %>
<br />- View the attachment <a href="<%= attachment.file_url %>" _target="blank">here</a>.
<% end %>
</p>
<% end %>
</td>
</tr>
@@ -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
+2 -1
View File
@@ -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"