From d52f4267ba42783273ef183a59e9f56495571aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gabriel=20Franco?= Date: Wed, 11 Jun 2025 23:19:36 -0300 Subject: [PATCH 001/109] feat: Add i18n on whatsapp list button (#10852) Fixes #10862 --------- Co-authored-by: Pranav --- .../whatsapp/providers/base_service.rb | 2 +- config/locales/en.yml | 2 ++ config/locales/pt_BR.yml | 6 +++-- .../whatsapp360_dialog_service_spec.rb | 23 +++++++++---------- .../providers/whatsapp_cloud_service_spec.rb | 23 +++++++++---------- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/services/whatsapp/providers/base_service.rb b/app/services/whatsapp/providers/base_service.rb index b60c02369..97665f7ef 100644 --- a/app/services/whatsapp/providers/base_service.rb +++ b/app/services/whatsapp/providers/base_service.rb @@ -100,7 +100,7 @@ class Whatsapp::Providers::BaseService rows = create_rows(message.content_attributes['items']) section1 = { 'rows' => rows } sections = [section1] - json_hash = { :button => 'Choose an item', 'sections' => sections } + json_hash = { :button => I18n.t('conversations.messages.whatsapp.list_button_label'), 'sections' => sections } create_payload('list', message.outgoing_content, JSON.generate(json_hash)) end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 59d7b8850..463aa713e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -152,6 +152,8 @@ en: instagram_story_content: '%{story_sender} mentioned you in the story: ' instagram_deleted_story_content: This story is no longer available. deleted: This message was deleted + whatsapp: + list_button_label: 'Choose an item' delivery_status: error_code: 'Error code: %{error_code}' activity: diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index a46e5b9c4..19570ab07 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -138,6 +138,8 @@ pt_BR: instagram_story_content: '%{story_sender} mencionou você na conversa: ' instagram_deleted_story_content: Este Story não está mais disponível. deleted: Esta mensagem foi excluída + whatsapp: + list_button_label: 'Escolha um item' delivery_status: error_code: 'Código de erro: %{error_code}' activity: @@ -213,7 +215,7 @@ pt_BR: slack: name: 'Slack' short_description: 'Receba notificações e responda as conversas diretamente no Slack.' - description: "Integre Chatwoot com Slack para manter seu time em sincronia. Essa integração permite que você receba notificações de novas conversas e as responda diretamente na interface do Slack." + description: 'Integre Chatwoot com Slack para manter seu time em sincronia. Essa integração permite que você receba notificações de novas conversas e as responda diretamente na interface do Slack.' webhooks: name: 'Webhooks' description: 'Eventos webhook fornecem atualizações sobre atividades em tempo real na sua conta Chatwoot. Você pode se inscrever em seus eventos preferidos, e o Chatwoot enviará as chamadas HTTP com as atualizações.' @@ -224,7 +226,7 @@ pt_BR: google_translate: name: 'Tradutor do Google' short_description: 'Traduzir automaticamente mensagens de clientes para agentes.' - description: "Integre o Google Tradutor para ajudar os agentes a traduzir facilmente as mensagens dos clientes. Esta integração detecta automaticamente o idioma e o converte para o idioma preferido do agente ou do administrador." + description: 'Integre o Google Tradutor para ajudar os agentes a traduzir facilmente as mensagens dos clientes. Esta integração detecta automaticamente o idioma e o converte para o idioma preferido do agente ou do administrador.' openai: name: 'OpenAI' short_description: 'Sugestões, resumos e aprimoramento de mensagem e resposta com IA.' diff --git a/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb b/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb index bba3f5914..572e96685 100644 --- a/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb +++ b/spec/services/whatsapp/providers/whatsapp360_dialog_service_spec.rb @@ -51,16 +51,15 @@ describe Whatsapp::Providers::Whatsapp360DialogService do end it 'calls message endpoints with list payload when number of items is greater than 3' do + items = %w[Burito Pasta Sushi Salad].map { |i| { title: i, value: i } } message = create(:message, message_type: :outgoing, content: 'test', inbox: whatsapp_channel.inbox, - content_type: 'input_select', - content_attributes: { - items: [ - { title: 'Burito', value: 'Burito' }, - { title: 'Pasta', value: 'Pasta' }, - { title: 'Sushi', value: 'Sushi' }, - { title: 'Salad', value: 'Salad' } - ] - }) + content_type: 'input_select', content_attributes: { items: items }) + + expected_action = { + button: I18n.t('conversations.messages.whatsapp.list_button_label'), + sections: [{ rows: %w[Burito Pasta Sushi Salad].map { |i| { id: i, title: i } } }] + }.to_json + stub_request(:post, 'https://waba.360dialog.io/v1/messages') .with( body: { @@ -70,9 +69,9 @@ describe Whatsapp::Providers::Whatsapp360DialogService do body: { text: 'test' }, - action: '{"button":"Choose an item","sections":[{"rows":[{"id":"Burito","title":"Burito"},' \ - '{"id":"Pasta","title":"Pasta"},{"id":"Sushi","title":"Sushi"},{"id":"Salad","title":"Salad"}]}]}' - }, type: 'interactive' + action: expected_action + }, + type: 'interactive' }.to_json ).to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers) expect(service.send_message('+123456789', message)).to eq 'message_id' diff --git a/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb b/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb index 134d69fee..1bef635b3 100644 --- a/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb +++ b/spec/services/whatsapp/providers/whatsapp_cloud_service_spec.rb @@ -124,16 +124,15 @@ describe Whatsapp::Providers::WhatsappCloudService do end it 'calls message endpoints with list payload when number of items is greater than 3' do + items = %w[Burito Pasta Sushi Salad].map { |i| { title: i, value: i } } message = create(:message, message_type: :outgoing, content: 'test', inbox: whatsapp_channel.inbox, - content_type: 'input_select', - content_attributes: { - items: [ - { title: 'Burito', value: 'Burito' }, - { title: 'Pasta', value: 'Pasta' }, - { title: 'Sushi', value: 'Sushi' }, - { title: 'Salad', value: 'Salad' } - ] - }) + content_type: 'input_select', content_attributes: { items: items }) + + expected_action = { + button: I18n.t('conversations.messages.whatsapp.list_button_label'), + sections: [{ rows: %w[Burito Pasta Sushi Salad].map { |i| { id: i, title: i } } }] + }.to_json + stub_request(:post, 'https://graph.facebook.com/v13.0/123456789/messages') .with( body: { @@ -143,9 +142,9 @@ describe Whatsapp::Providers::WhatsappCloudService do body: { text: 'test' }, - action: '{"button":"Choose an item","sections":[{"rows":[{"id":"Burito","title":"Burito"},' \ - '{"id":"Pasta","title":"Pasta"},{"id":"Sushi","title":"Sushi"},{"id":"Salad","title":"Salad"}]}]}' - }, type: 'interactive' + action: expected_action + }, + type: 'interactive' }.to_json ).to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers) expect(service.send_message('+123456789', message)).to eq 'message_id' From 288df3a3996e09b402508cb7473645890329a6bf Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 12 Jun 2025 02:05:34 -0400 Subject: [PATCH 002/109] fix: Flaky Instagram unsend message test (#11712) - Use direct message object reference instead of re-querying through inbox - Add message.reload after unsend operation to get updated state - Remove unnecessary inbox reload that could cause timing issues - Remove redundant assertions for better test atomicity Co-authored-by: Muhsin Keloth --- spec/jobs/webhooks/instagram_events_job_spec.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spec/jobs/webhooks/instagram_events_job_spec.rb b/spec/jobs/webhooks/instagram_events_job_spec.rb index 23e7f9e5a..dfd9f1aed 100644 --- a/spec/jobs/webhooks/instagram_events_job_spec.rb +++ b/spec/jobs/webhooks/instagram_events_job_spec.rb @@ -234,16 +234,15 @@ describe Webhooks::InstagramEventsJob do account_id: instagram_inbox.account_id ) - instagram_inbox.reload - expect(instagram_inbox.messages.count).to be 1 instagram_webhook.perform_now(message_events[:unsend][:entry]) - expect(instagram_inbox.messages.last.content).to eq 'This message was deleted' - expect(instagram_inbox.messages.last.deleted).to be true - expect(instagram_inbox.messages.last.attachments.count).to be 0 - expect(instagram_inbox.messages.last.reload.deleted).to be true + message.reload + + expect(message.content).to eq 'This message was deleted' + expect(message.deleted).to be true + expect(message.attachments.count).to be 0 end it 'creates incoming message with attachments in the instagram direct inbox' do From f28bb70d67eb09161e9c1f06796fe0a4c30f8e9d Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:38:45 +0530 Subject: [PATCH 003/109] fix: Prevent count flicker on loading more conversations (#11706) --- app/javascript/dashboard/components/ChatList.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/components/ChatList.vue b/app/javascript/dashboard/components/ChatList.vue index de895c76e..b14a62f08 100644 --- a/app/javascript/dashboard/components/ChatList.vue +++ b/app/javascript/dashboard/components/ChatList.vue @@ -109,6 +109,7 @@ const advancedFilterTypes = ref( attributeName: t(`FILTER.ATTRIBUTES.${filter.attributeI18nKey}`), })) ); +const isInitialLoad = ref(false); const currentUser = useMapGetter('getCurrentUser'); const chatLists = useMapGetter('getFilteredConversations'); @@ -376,6 +377,7 @@ function setFiltersFromUISettings() { function emitConversationLoaded() { emit('conversationLoad'); + isInitialLoad.value = false; // [VITE] removing this since the library has changed // nextTick(() => { // // Addressing a known issue in the virtual list library where dynamically added items @@ -420,6 +422,7 @@ function onApplyFilter(payload) { foldersQuery.value = filterQueryGenerator(payload); store.dispatch('conversationPage/reset'); store.dispatch('emptyAllConversations'); + isInitialLoad.value = true; fetchFilteredConversations(payload); } @@ -574,6 +577,7 @@ function resetAndFetchData() { store.dispatch('conversationPage/reset'); store.dispatch('emptyAllConversations'); store.dispatch('clearConversationFilters'); + isInitialLoad.value = true; if (hasActiveFolders.value) { const payload = activeFolder.value.query; fetchSavedFilteredConversations(payload); @@ -854,7 +858,7 @@ watch(conversationFilters, (newVal, oldVal) => { :active-status="activeStatus" :is-on-expanded-layout="isOnExpandedLayout" :conversation-stats="conversationStats" - :is-list-loading="chatListLoading" + :is-list-loading="isInitialLoad" @add-folders="onClickOpenAddFoldersModal" @delete-folders="onClickOpenDeleteFoldersModal" @filters-modal="onToggleAdvanceFiltersModal" From 6baca4059723cae330ad3621d07bbee8cc7fac59 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:01:41 +0530 Subject: [PATCH 004/109] chore: Display divider only when multiple portals are available (#11709) --- app/views/public/api/v1/portals/_header.html.erb | 14 +++++++------- app/views/public/api/v1/portals/_hero.html.erb | 2 +- .../public/api/v1/portals/_mobile_menu.html.erb | 7 +++++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/views/public/api/v1/portals/_header.html.erb b/app/views/public/api/v1/portals/_header.html.erb index 694cd240f..ae1162e05 100644 --- a/app/views/public/api/v1/portals/_header.html.erb +++ b/app/views/public/api/v1/portals/_header.html.erb @@ -1,18 +1,18 @@