diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb
index 77f82e507..de19128cf 100644
--- a/app/controllers/api/v1/accounts/conversations_controller.rb
+++ b/app/controllers/api/v1/accounts/conversations_controller.rb
@@ -110,9 +110,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def update_last_seen
- # NOTE: Use old `agent_last_seen_at`, so we reference messages received after that
- Rails.configuration.dispatcher.dispatch(Events::Types::MESSAGES_READ, Time.zone.now, conversation: @conversation,
- last_seen_at: @conversation.agent_last_seen_at)
+ dispatch_messages_read_event if assignee?
update_last_seen_on_conversation(DateTime.now.utc, assignee?)
end
@@ -206,6 +204,12 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
def assignee?
@conversation.assignee_id? && Current.user == @conversation.assignee
end
+
+ def dispatch_messages_read_event
+ # NOTE: Use old `agent_last_seen_at`, so we reference messages received after that
+ Rails.configuration.dispatcher.dispatch(Events::Types::MESSAGES_READ, Time.zone.now, conversation: @conversation,
+ last_seen_at: @conversation.agent_last_seen_at)
+ end
end
Api::V1::Accounts::ConversationsController.prepend_mod_with('Api::V1::Accounts::ConversationsController')
diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
index d88e34562..14ebc31d4 100644
--- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
@@ -267,7 +267,10 @@
"PROVIDER_URL": {
"LABEL": "Provider URL",
"PLACEHOLDER": "If provider is not running locally, please provide the URL",
- "ERROR":"Please enter a valid URL"
+ "ERROR": "Please enter a valid URL"
+ },
+ "MARK_AS_READ": {
+ "LABEL": "Send read receipts"
},
"ADVANCED_OPTIONS": "Advanced options",
"BAILEYS": {
@@ -558,7 +561,10 @@
"WHATSAPP_PROVIDER_URL_SUBHEADER": "If the provider is not running locally, please provide the URL.",
"WHATSAPP_PROVIDER_URL_PLACEHOLDER": "Enter the provider URL",
"WHATSAPP_PROVIDER_URL_ERROR": "Please enter a valid URL",
- "UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
+ "UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings",
+ "WHATSAPP_MARK_AS_READ_TITLE": "Read receipts",
+ "WHATSAPP_MARK_AS_READ_SUBHEADER": "If turned off, when a message is viewed in Chatwoot, a read receipt will not be sent to the sender. Your messages will still be able to receive read receipts from the sender.",
+ "WHATSAPP_MARK_AS_READ_LABEL": "Send read receipts"
},
"HELP_CENTER": {
"LABEL": "Help Center",
diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json
index b1713e19e..5217cb6ec 100644
--- a/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/pt_BR/inboxMgmt.json
@@ -267,7 +267,10 @@
"PROVIDER_URL": {
"LABEL": "URL do provedor",
"PLACEHOLDER": "Se o provedor não está rodando localmente, por favor, insira a URL do provedor",
- "ERROR":"Por favor, insira uma URL válida"
+ "ERROR": "Por favor, insira uma URL válida"
+ },
+ "MARK_AS_READ": {
+ "LABEL": "Enviar confirmações de leitura"
},
"ADVANCED_OPTIONS": "Opções avançadas",
"BAILEYS": {
@@ -558,7 +561,10 @@
"WHATSAPP_PROVIDER_URL_SUBHEADER": "Se o provedor não estiver rodando localmente, por favor, forneça a URL.",
"WHATSAPP_PROVIDER_URL_PLACEHOLDER": "Digite a URL do provedor",
"WHATSAPP_PROVIDER_URL_ERROR": "Por favor, insira uma URL válida",
- "UPDATE_PRE_CHAT_FORM_SETTINGS": "Atualizar configurações do Formulário Pre Chat"
+ "UPDATE_PRE_CHAT_FORM_SETTINGS": "Atualizar configurações do Formulário Pre Chat",
+ "WHATSAPP_MARK_AS_READ_TITLE": "Confirmações de leitura",
+ "WHATSAPP_MARK_AS_READ_SUBHEADER": "Se essa opção estiver desativada, ao visualizar uma mensagem pelo Chatwoot, não será enviada uma confirmação de leitura para o remetente. As suas mensagens ainda poderão receber confirmações de leitura.",
+ "WHATSAPP_MARK_AS_READ_LABEL": "Enviar confirmações de leitura"
},
"HELP_CENTER": {
"LABEL": "Centro de Ajuda",
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue
index 72680cb1d..b5565c2bc 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/BaileysWhatsapp.vue
@@ -8,10 +8,13 @@ import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
import { isValidURL } from '../../../../../helper/URLHelper';
import NextButton from 'dashboard/components-next/button/Button.vue';
+import Switch from 'dashboard/components-next/switch/Switch.vue';
export default {
components: {
NextButton,
+ // eslint-disable-next-line vue/no-reserved-component-names
+ Switch,
},
setup() {
return { v$: useVuelidate() };
@@ -23,6 +26,7 @@ export default {
apiKey: '',
providerUrl: '',
showAdvancedOptions: false,
+ markAsRead: true,
};
},
computed: {
@@ -47,6 +51,15 @@ export default {
}
try {
+ const providerConfig = {
+ mark_as_read: this.markAsRead,
+ };
+
+ if (this.apiKey || this.providerUrl) {
+ providerConfig.api_key = this.apiKey;
+ providerConfig.url = this.providerUrl;
+ }
+
const whatsappChannel = await this.$store.dispatch(
'inboxes/createChannel',
{
@@ -55,13 +68,7 @@ export default {
type: 'whatsapp',
phone_number: this.phoneNumber,
provider: 'baileys',
- provider_config:
- this.apiKey || this.providerUrl
- ? {
- api_key: this.apiKey,
- url: this.providerUrl,
- }
- : {},
+ provider_config: providerConfig,
},
}
);
@@ -159,6 +166,17 @@ export default {
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue
index 805b8e458..96987b2a7 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/ConfigurationPage.vue
@@ -10,6 +10,7 @@ import { requiredIf } from '@vuelidate/validators';
import { isValidURL } from '../../../../../helper/URLHelper';
import WhatsappBaileysLinkDeviceModal from '../components/WhatsappBaileysLinkDeviceModal.vue';
import InboxName from '../../../../../components/widgets/InboxName.vue';
+import Switch from 'dashboard/components-next/switch/Switch.vue';
export default {
components: {
@@ -19,6 +20,8 @@ export default {
NextButton,
WhatsappBaileysLinkDeviceModal,
InboxName,
+ // eslint-disable-next-line vue/no-reserved-component-names
+ Switch,
},
mixins: [inboxMixin],
props: {
@@ -36,6 +39,7 @@ export default {
whatsAppInboxAPIKey: '',
whatsAppProviderUrl: '',
showBaileysLinkDeviceModal: false,
+ markAsRead: true,
};
},
validations() {
@@ -57,6 +61,7 @@ export default {
methods: {
setDefaults() {
this.hmacMandatory = this.inbox.hmac_mandatory || false;
+ this.markAsRead = this.inbox.provider_config.mark_as_read ?? true;
},
handleHmacFlag() {
this.updateInbox();
@@ -114,6 +119,24 @@ export default {
useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
}
},
+ async updateWhatsAppMarkAsRead() {
+ try {
+ const payload = {
+ id: this.inbox.id,
+ formData: false,
+ channel: {
+ provider_config: {
+ ...this.inbox.provider_config,
+ mark_as_read: this.markAsRead,
+ },
+ },
+ };
+ await this.$store.dispatch('inboxes/updateInbox', payload);
+ useAlert(this.$t('INBOX_MGMT.EDIT.API.SUCCESS_MESSAGE'));
+ } catch (error) {
+ useAlert(this.$t('INBOX_MGMT.EDIT.API.ERROR_MESSAGE'));
+ }
+ },
onOpenBaileysLinkDeviceModal() {
this.showBaileysLinkDeviceModal = true;
},
@@ -383,6 +406,23 @@ export default {
+
+
+
+
+
+
diff --git a/app/models/channel/whatsapp.rb b/app/models/channel/whatsapp.rb
index 66e3a3883..53bdccdd1 100644
--- a/app/models/channel/whatsapp.rb
+++ b/app/models/channel/whatsapp.rb
@@ -93,6 +93,8 @@ class Channel::Whatsapp < ApplicationRecord
def read_messages(messages, conversation:)
return unless provider_service.respond_to?(:read_messages)
+ # NOTE: This is the default behavior, so `mark_as_read` being `nil` is the same as `true`.
+ return if provider_config&.dig('mark_as_read') == false
provider_service.read_messages(conversation.contact.phone_number, messages)
end
diff --git a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb
index 43ec3bb55..de9cf7f3c 100644
--- a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb
@@ -688,18 +688,33 @@ RSpec.describe 'Conversations API', type: :request do
expect(conversation.reload.assignee_last_seen_at).not_to be_nil
end
- it 'dispatches messages.read event' do
+ it 'dispatches messages.read event when user is assignee' do
freeze_time
- conversation.update!(agent_last_seen_at: 1.hour.ago)
+
+ previous_agent_last_seen_at = 1.hour.ago
+ conversation.update!(agent_last_seen_at: previous_agent_last_seen_at, assignee: agent)
+
allow(Rails.configuration.dispatcher).to receive(:dispatch)
- .with(Events::Types::MESSAGES_READ, Time.zone.now, conversation: conversation, last_seen_at: conversation.agent_last_seen_at)
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
- expect(Rails.configuration.dispatcher).to have_received(:dispatch)
+ expect(Rails.configuration.dispatcher)
+ .to have_received(:dispatch)
+ .with(Events::Types::MESSAGES_READ, Time.zone.now, conversation: conversation, last_seen_at: previous_agent_last_seen_at)
+ end
+
+ it 'does not dispatch messages.read event when user is not assignee' do
+ allow(Rails.configuration.dispatcher).to receive(:dispatch)
+
+ post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/update_last_seen",
+ headers: agent.create_new_auth_token,
+ as: :json
+
+ expect(response).to have_http_status(:success)
+ expect(Rails.configuration.dispatcher).not_to have_received(:dispatch)
end
end
end
diff --git a/spec/factories/channel/channel_whatsapp.rb b/spec/factories/channel/channel_whatsapp.rb
index 36e696e81..fd0786a00 100644
--- a/spec/factories/channel/channel_whatsapp.rb
+++ b/spec/factories/channel/channel_whatsapp.rb
@@ -77,7 +77,7 @@ FactoryBot.define do
channel_whatsapp.define_singleton_method(:validate_provider_config) { nil } unless options.validate_provider_config
if channel_whatsapp.provider == 'baileys'
channel_whatsapp.provider_config = channel_whatsapp.provider_config.merge({ 'api_key' => 'test_key', 'provider_url' => 'https://baileys.api',
- 'phone_number_id' => '123456789' })
+ 'phone_number_id' => '123456789', 'mark_as_read' => true })
elsif channel_whatsapp.provider == 'whatsapp_cloud'
channel_whatsapp.provider_config = channel_whatsapp.provider_config.merge({ 'api_key' => 'test_key', 'phone_number_id' => '123456789',
'business_account_id' => '123456789' })
diff --git a/spec/models/channel/whatsapp_spec.rb b/spec/models/channel/whatsapp_spec.rb
index bc597e6dc..9150ba01e 100644
--- a/spec/models/channel/whatsapp_spec.rb
+++ b/spec/models/channel/whatsapp_spec.rb
@@ -121,7 +121,9 @@ RSpec.describe Channel::Whatsapp do
end
describe '#read_messages' do
- let(:channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false, sync_templates: false) }
+ let(:channel) do
+ create(:channel_whatsapp, provider: 'baileys', provider_config: { mark_as_read: true }, validate_provider_config: false, sync_templates: false)
+ end
let(:conversation) { create(:conversation) }
let(:message) { create(:message) }
@@ -137,13 +139,30 @@ RSpec.describe Channel::Whatsapp do
expect(provider_double).to have_received(:read_messages)
end
- it 'does not call method if provider service does not implement it' do
- channel = create(:channel_whatsapp, provider: 'whatsapp_cloud', validate_provider_config: false, sync_templates: false)
- provider_double = instance_double(Whatsapp::Providers::WhatsappCloudService)
- allow(Whatsapp::Providers::WhatsappCloudService).to receive(:new)
+ it 'call method when the provider config mark_as_read is nil' do
+ channel.update!(provider_config: {})
+ provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
+ allow(provider_double).to receive(:read_messages).with([message], conversation.contact.phone_number)
+ allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
.with(whatsapp_channel: channel)
.and_return(provider_double)
+ channel.read_messages([message], conversation: conversation)
+
+ expect(provider_double).to have_received(:read_messages)
+ end
+
+ it 'does not call method if provider service does not implement it' do
+ channel.update!(provider: 'whatsapp_cloud')
+
+ expect do
+ channel.read_messages([message], conversation: conversation)
+ end.not_to raise_error
+ end
+
+ it 'does not call method if provider config mark_as_read is false' do
+ channel.update!(provider_config: { mark_as_read: false })
+
expect do
channel.read_messages([message], conversation: conversation)
end.not_to raise_error