From 8faccba052e9ebb0cc33acb5ba2626373866720d Mon Sep 17 00:00:00 2001 From: Pranav Date: Mon, 10 Feb 2025 20:22:11 -0800 Subject: [PATCH 1/8] chore: Update the precision of the updated_at timestamp in conversation model (#10875) Use to_f instead of to_i to preserve the millisecond precision in the UI. --- app/presenters/conversations/event_data_presenter.rb | 2 +- .../api/v1/conversations/partials/_conversation.json.jbuilder | 2 +- spec/models/conversation_spec.rb | 2 +- spec/presenters/conversations/event_data_presenter_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/presenters/conversations/event_data_presenter.rb b/app/presenters/conversations/event_data_presenter.rb index 1ba188de8..2ef69080d 100644 --- a/app/presenters/conversations/event_data_presenter.rb +++ b/app/presenters/conversations/event_data_presenter.rb @@ -43,7 +43,7 @@ class Conversations::EventDataPresenter < SimpleDelegator last_activity_at: last_activity_at.to_i, timestamp: last_activity_at.to_i, created_at: created_at.to_i, - updated_at: updated_at.to_i + updated_at: updated_at.to_f } end end diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index 2e6474953..8867ba695 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -44,7 +44,7 @@ json.muted conversation.muted? json.snoozed_until conversation.snoozed_until json.status conversation.status json.created_at conversation.created_at.to_i -json.updated_at conversation.updated_at.to_i +json.updated_at conversation.updated_at.to_f json.timestamp conversation.last_activity_at.to_i json.first_reply_created_at conversation.first_reply_created_at.to_i json.unread_count conversation.unread_incoming_messages.count diff --git a/spec/models/conversation_spec.rb b/spec/models/conversation_spec.rb index 0c8f9de50..1938fb204 100644 --- a/spec/models/conversation_spec.rb +++ b/spec/models/conversation_spec.rb @@ -538,7 +538,7 @@ RSpec.describe Conversation do contact_last_seen_at: conversation.contact_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i, created_at: conversation.created_at.to_i, - updated_at: conversation.updated_at.to_i, + updated_at: conversation.updated_at.to_f, waiting_since: conversation.waiting_since.to_i, priority: nil, unread_count: 0 diff --git a/spec/presenters/conversations/event_data_presenter_spec.rb b/spec/presenters/conversations/event_data_presenter_spec.rb index 6f2534efb..a645caf1d 100644 --- a/spec/presenters/conversations/event_data_presenter_spec.rb +++ b/spec/presenters/conversations/event_data_presenter_spec.rb @@ -31,7 +31,7 @@ RSpec.describe Conversations::EventDataPresenter do contact_last_seen_at: conversation.contact_last_seen_at.to_i, agent_last_seen_at: conversation.agent_last_seen_at.to_i, created_at: conversation.created_at.to_i, - updated_at: conversation.updated_at.to_i, + updated_at: conversation.updated_at.to_f, waiting_since: conversation.waiting_since.to_i, priority: nil, unread_count: 0 From 3c78d25306e0c7e307d8740ad85ff2eae971ce4f Mon Sep 17 00:00:00 2001 From: Pranav Date: Mon, 10 Feb 2025 23:16:15 -0800 Subject: [PATCH 2/8] chore: Reload conversation data in ActionCableBroadcastJob before sending (#10876) During high-traffic periods, events may appear out of order, causing the conversation job to queue outdated data, which can lead to issues in the UI. This update ensures that only the latest available data is sent to the UI. The conversation object is refreshed before sending it to the UI. --- app/jobs/action_cable_broadcast_job.rb | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/jobs/action_cable_broadcast_job.rb b/app/jobs/action_cable_broadcast_job.rb index b1208a97c..ce1230029 100644 --- a/app/jobs/action_cable_broadcast_job.rb +++ b/app/jobs/action_cable_broadcast_job.rb @@ -2,8 +2,34 @@ class ActionCableBroadcastJob < ApplicationJob queue_as :critical def perform(members, event_name, data) + return if members.blank? + + broadcast_data = prepare_broadcast_data(event_name, data) + broadcast_to_members(members, event_name, broadcast_data) + end + + private + + # Ensures that only the latest available data is sent to prevent UI issues + # caused by out-of-order events during high-traffic periods. This prevents + # the conversation job from processing outdated data. + def prepare_broadcast_data(event_name, data) + return data unless event_name == 'conversation.updated' + + account = Account.find(data[:account_id]) + conversation = account.conversations.find_by!(display_id: data[:id]) + conversation.push_event_data.merge(account_id: data[:account_id]) + end + + def broadcast_to_members(members, event_name, broadcast_data) members.each do |member| - ActionCable.server.broadcast(member, { event: event_name, data: data }) + ActionCable.server.broadcast( + member, + { + event: event_name, + data: broadcast_data + } + ) end end end From 4b12a8a51e7414353b24700f28224858d5c418a7 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 11 Feb 2025 00:33:45 -0800 Subject: [PATCH 3/8] chore: Add more conversation events for reload (#10877) Followup PR for https://github.com/chatwoot/chatwoot/pull/10876. This PR just adds all the events related to conversation update to be reloaded before sending it to the UI. --- app/jobs/action_cable_broadcast_job.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/jobs/action_cable_broadcast_job.rb b/app/jobs/action_cable_broadcast_job.rb index ce1230029..74dab618d 100644 --- a/app/jobs/action_cable_broadcast_job.rb +++ b/app/jobs/action_cable_broadcast_job.rb @@ -1,5 +1,14 @@ class ActionCableBroadcastJob < ApplicationJob queue_as :critical + include Events::Types + + CONVERSATION_UPDATE_EVENTS = [ + CONVERSATION_READ, + CONVERSATION_UPDATED, + TEAM_CHANGED, + ASSIGNEE_CHANGED, + CONVERSATION_STATUS_CHANGED + ].freeze def perform(members, event_name, data) return if members.blank? @@ -14,7 +23,7 @@ class ActionCableBroadcastJob < ApplicationJob # caused by out-of-order events during high-traffic periods. This prevents # the conversation job from processing outdated data. def prepare_broadcast_data(event_name, data) - return data unless event_name == 'conversation.updated' + return data unless CONVERSATION_UPDATE_EVENTS.include?(event_name) account = Account.find(data[:account_id]) conversation = account.conversations.find_by!(display_id: data[:id]) From a428dfc3f447665487cdc3e56d8554d6085177bf Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Feb 2025 17:45:31 +0530 Subject: [PATCH 4/8] feat: handle mine event for incoming messages (#10867) Handle `mine` condition, missed in https://github.com/chatwoot/chatwoot/pull/10529 --- .../helper/AudioAlerts/DashboardAudioNotificationHelper.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/helper/AudioAlerts/DashboardAudioNotificationHelper.js b/app/javascript/dashboard/helper/AudioAlerts/DashboardAudioNotificationHelper.js index 21cb0fc9f..9e79bb3a2 100644 --- a/app/javascript/dashboard/helper/AudioAlerts/DashboardAudioNotificationHelper.js +++ b/app/javascript/dashboard/helper/AudioAlerts/DashboardAudioNotificationHelper.js @@ -152,7 +152,10 @@ export class DashboardAudioNotificationHelper { const shouldPlayAudio = []; - if (audioAlertType.includes(EVENT_TYPES.ASSIGNED)) { + if ( + audioAlertType.includes(EVENT_TYPES.ASSIGNED) || + audioAlertType.includes('mine') + ) { shouldPlayAudio.push(assignedToMe); } if (audioAlertType.includes(EVENT_TYPES.UNASSIGNED)) { From 84822a013a01808829071a28f4f027130e45f58b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Feb 2025 17:45:59 +0530 Subject: [PATCH 5/8] fix: inconsistent reply box cc update (#10799) This PR target two issues ### CC & BCC not updated correctly When moving from one conversation to another, the store may not have the list of all the messages. A fetch is subsequently made to get the messages. However, this update does not trigger the `currentChat` watcher. This PR fixes it by adding a new watcher on `currentChat.messages`. We also update the `setCCAndToEmailsFromLastChat` method to reset the `cc`, `bcc` and `to` fields if the last email is not found. This ensures that the data is not carried forward from a previous email Fixes: https://github.com/chatwoot/chatwoot/issues/10477 ### To address are not added correctly to the `CC` If the `to` address of a previous email has multiple recipient, there was no case to add them to the CC. Fixes: https://github.com/chatwoot/chatwoot/issues/8925 --- Depends on: https://github.com/chatwoot/utils/pull/41 --- .../widgets/conversation/ReplyBox.vue | 57 +++++++------------ .../store/modules/conversations/getters.js | 18 ++---- package.json | 2 +- pnpm-lock.yaml | 28 ++++++--- 4 files changed, 48 insertions(+), 57 deletions(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index d3675dbac..7a876aaa6 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -30,7 +30,7 @@ import { import WhatsappTemplates from './WhatsappTemplates/Modal.vue'; import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper'; import inboxMixin, { INBOX_FEATURES } from 'shared/mixins/inboxMixin'; -import { trimContent, debounce } from '@chatwoot/utils'; +import { trimContent, debounce, getRecipients } from '@chatwoot/utils'; import wootConstants from 'dashboard/constants/globals'; import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events'; import fileUploadMixin from 'dashboard/mixins/fileUploadMixin'; @@ -388,7 +388,6 @@ export default { watch: { currentChat(conversation) { const { can_reply: canReply } = conversation; - this.setCCAndToEmailsFromLastChat(); if (this.isOnPrivateNote) { @@ -403,6 +402,19 @@ export default { this.fetchAndSetReplyTo(); }, + // When moving from one conversation to another, the store may not have the + // list of all the messages. A fetch is subsequently made to get the messages. + // However, this update does not trigger the `currentChat` watcher. + // We can add a deep watcher to it, but then, that would be too broad of a net to cast + // And would impact performance too. So we watch the messages directly. + // The watcher here is `deep` too, because the messages array is mutated and + // not replaced. So, a shallow watcher would not catch the change. + 'currentChat.messages': { + handler() { + this.setCCAndToEmailsFromLastChat(); + }, + deep: true, + }, conversationIdByRoute(conversationId, oldConversationId) { if (conversationId !== oldConversationId) { this.setToDraft(oldConversationId, this.replyType); @@ -989,45 +1001,20 @@ export default { this.ccEmails = value.ccEmails; }, setCCAndToEmailsFromLastChat() { - if (!this.lastEmail) return; - - const { - content_attributes: { email: emailAttributes = {} }, - } = this.lastEmail; - - // Retrieve the email of the current conversation's sender const conversationContact = this.currentChat?.meta?.sender?.email || ''; - let cc = emailAttributes.cc ? [...emailAttributes.cc] : []; - let to = []; + const { email: inboxEmail, forward_to_email: forwardToEmail } = + this.inbox; - // there might be a situation where the current conversation will include a message from a third person, - // and the current conversation contact is in CC. - // This is an edge-case, reported here: CW-1511 [ONLY FOR INTERNAL REFERENCE] - // So we remove the current conversation contact's email from the CC list if present - if (cc.includes(conversationContact)) { - cc = cc.filter(email => email !== conversationContact); - } - - // If the last incoming message sender is different from the conversation contact, add them to the "to" - // and add the conversation contact to the CC - if (!emailAttributes.from.includes(conversationContact)) { - to.push(...emailAttributes.from); - cc.push(conversationContact); - } - - // Remove the conversation contact's email from the BCC list if present - let bcc = (emailAttributes.bcc || []).filter( - email => email !== conversationContact + const { cc, bcc, to } = getRecipients( + this.lastEmail, + conversationContact, + inboxEmail, + forwardToEmail ); - // Ensure only unique email addresses are in the CC list - bcc = [...new Set(bcc)]; - cc = [...new Set(cc)]; - to = [...new Set(to)]; - + this.toEmails = to.join(', '); this.ccEmails = cc.join(', '); this.bccEmails = bcc.join(', '); - this.toEmails = to.join(', '); }, fetchAndSetReplyTo() { const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO; diff --git a/app/javascript/dashboard/store/modules/conversations/getters.js b/app/javascript/dashboard/store/modules/conversations/getters.js index 085766bfd..3695de6a1 100644 --- a/app/javascript/dashboard/store/modules/conversations/getters.js +++ b/app/javascript/dashboard/store/modules/conversations/getters.js @@ -27,18 +27,12 @@ const getters = { const selectedChat = _getters.getSelectedChat; const { messages = [] } = selectedChat; const lastEmail = [...messages].reverse().find(message => { - const { - content_attributes: contentAttributes = {}, - message_type: messageType, - } = message; - const { email = {} } = contentAttributes; - const isIncomingOrOutgoing = - messageType === MESSAGE_TYPE.OUTGOING || - messageType === MESSAGE_TYPE.INCOMING; - if (email.from && isIncomingOrOutgoing) { - return true; - } - return false; + const { message_type: messageType } = message; + if (message.private) return false; + + return [MESSAGE_TYPE.OUTGOING, MESSAGE_TYPE.INCOMING].includes( + messageType + ); }); return lastEmail; diff --git a/package.json b/package.json index b3fbda8f0..aefa5439d 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@breezystack/lamejs": "^1.2.7", "@chatwoot/ninja-keys": "1.2.3", "@chatwoot/prosemirror-schema": "1.1.1-next", - "@chatwoot/utils": "^0.0.35", + "@chatwoot/utils": "^0.0.38", "@formkit/core": "^1.6.7", "@formkit/vue": "^1.6.7", "@hcaptcha/vue3-hcaptcha": "^1.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20142653e..6bbf22593 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,8 +23,8 @@ importers: specifier: 1.1.1-next version: 1.1.1-next '@chatwoot/utils': - specifier: ^0.0.35 - version: 0.0.35 + specifier: ^0.0.38 + version: 0.0.38 '@formkit/core': specifier: ^1.6.7 version: 1.6.7 @@ -376,6 +376,10 @@ packages: resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.26.7': + resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.26.0': resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} engines: {node: '>=6.9.0'} @@ -393,8 +397,8 @@ packages: '@chatwoot/prosemirror-schema@1.1.1-next': resolution: {integrity: sha512-/M2qZ+ZF7GlQNt1riwVP499fvp3hxSqd5iy8hxyF9pkj9qQ+OKYn5JK+v3qwwqQY3IxhmNOn1Lp6tm7vstrd9Q==} - '@chatwoot/utils@0.0.35': - resolution: {integrity: sha512-uSRbd3pFp+IcEhsRtK1XGcFGFJc+X/YIwQnQrVDXsvsX3Mm7HEANj+Yz6J2clfHotajniwJwH2u5/y48+JrTyA==} + '@chatwoot/utils@0.0.38': + resolution: {integrity: sha512-6CTvuueBQLZJcm++pI2ZBY8Pp7OP3WzPCYyXoCagl8ZLpOfpjyVkLx9fc81falOoaVa/r+7EZ85Cv7vkT0ZyQw==} engines: {node: '>=10'} '@codemirror/commands@6.7.0': @@ -2372,8 +2376,8 @@ packages: resolution: {integrity: sha512-m1WR0xGiC6j6jNFAyW4Nvh4WxAi4JF4w9jRJwSI8nBmNcyZXPcP9VUQG+6gHQXAmqaGEKDKhOqAtENDC941UkA==} engines: {node: '>=0.11'} - date-fns@2.29.3: - resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} date-format-parse@0.2.7: @@ -5092,6 +5096,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.26.7': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/types@7.26.0': dependencies: '@babel/helper-string-parser': 7.25.9 @@ -5125,9 +5133,9 @@ snapshots: prosemirror-utils: 1.2.2(prosemirror-model@1.22.3)(prosemirror-state@1.4.3) prosemirror-view: 1.34.1 - '@chatwoot/utils@0.0.35': + '@chatwoot/utils@0.0.38': dependencies: - date-fns: 2.29.3 + date-fns: 2.30.0 '@codemirror/commands@6.7.0': dependencies: @@ -7380,7 +7388,9 @@ snapshots: date-fns@2.21.1: {} - date-fns@2.29.3: {} + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.26.7 date-format-parse@0.2.7: {} From cf025e0fa4302e496f4f27db6dcc92af195f4c31 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:58:36 +0530 Subject: [PATCH 6/8] chore: Remove the background SVG from the help center (#10857) # Pull Request Template ## Description This PR will remove the hexagon background image from public portal. Fixes https://linear.app/chatwoot/issue/CW-4013/remove-the-background-from-the-help-center ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? **Screenshots** **Before** image image **After** image image ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- app/helpers/portal_helper.rb | 3 +-- spec/helpers/portal_helper_spec.rb | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/helpers/portal_helper.rb b/app/helpers/portal_helper.rb index 342bb62aa..2b1ab7b21 100644 --- a/app/helpers/portal_helper.rb +++ b/app/helpers/portal_helper.rb @@ -5,8 +5,7 @@ module PortalHelper end def generate_portal_bg(portal_color, theme) - bg_image = theme == 'dark' ? 'hexagon-dark.svg' : 'hexagon-light.svg' - "url(/assets/images/hc/#{bg_image}) #{generate_portal_bg_color(portal_color, theme)}" + generate_portal_bg_color(portal_color, theme) end def generate_gradient_to_bottom(theme) diff --git a/spec/helpers/portal_helper_spec.rb b/spec/helpers/portal_helper_spec.rb index 84ed4ba72..dfd37138a 100644 --- a/spec/helpers/portal_helper_spec.rb +++ b/spec/helpers/portal_helper_spec.rb @@ -33,26 +33,26 @@ describe PortalHelper do describe '#generate_portal_bg' do context 'when theme is dark' do it 'returns the correct background with dark grid image and color mix with black' do - expected_bg = 'url(/assets/images/hc/hexagon-dark.svg) color-mix(in srgb, #ff0000 20%, black)' + expected_bg = 'color-mix(in srgb, #ff0000 20%, black)' expect(helper.generate_portal_bg('#ff0000', 'dark')).to eq(expected_bg) end end context 'when theme is not dark' do it 'returns the correct background with light grid image and color mix with white' do - expected_bg = 'url(/assets/images/hc/hexagon-light.svg) color-mix(in srgb, #ff0000 20%, white)' + expected_bg = 'color-mix(in srgb, #ff0000 20%, white)' expect(helper.generate_portal_bg('#ff0000', 'light')).to eq(expected_bg) end end context 'when provided with various colors' do it 'adjusts the background appropriately for dark theme' do - expected_bg = 'url(/assets/images/hc/hexagon-dark.svg) color-mix(in srgb, #00ff00 20%, black)' + expected_bg = 'color-mix(in srgb, #00ff00 20%, black)' expect(helper.generate_portal_bg('#00ff00', 'dark')).to eq(expected_bg) end it 'adjusts the background appropriately for light theme' do - expected_bg = 'url(/assets/images/hc/hexagon-light.svg) color-mix(in srgb, #0000ff 20%, white)' + expected_bg = 'color-mix(in srgb, #0000ff 20%, white)' expect(helper.generate_portal_bg('#0000ff', 'light')).to eq(expected_bg) end end From a780de4b640da515939a8e0ead0bb889437a41ca Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 11 Feb 2025 19:15:46 +0530 Subject: [PATCH 7/8] refactor: show deprecation warnings in dev only (#10868) Fixes: https://github.com/chatwoot/chatwoot/issues/10734 --- app/javascript/dashboard/components/Modal.vue | 2 +- app/javascript/dashboard/components/buttons/Button.vue | 10 ++++++---- .../dashboard/components/widgets/forms/Input.vue | 10 ++++++---- app/javascript/v3/components/Button/SubmitButton.vue | 10 ++++++---- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/javascript/dashboard/components/Modal.vue b/app/javascript/dashboard/components/Modal.vue index a1f7ba5ed..653e6cde0 100644 --- a/app/javascript/dashboard/components/Modal.vue +++ b/app/javascript/dashboard/components/Modal.vue @@ -57,7 +57,7 @@ useEventListener(document.body, 'mouseup', onMouseUp); useEventListener(document, 'keydown', onKeydown); onMounted(() => { - if (onClose && typeof onClose === 'function') { + if (import.meta.env.DEV && onClose && typeof onClose === 'function') { // eslint-disable-next-line no-console console.warn( "[DEPRECATED] The 'onClose' prop is deprecated. Please use the 'close' event instead." diff --git a/app/javascript/dashboard/components/buttons/Button.vue b/app/javascript/dashboard/components/buttons/Button.vue index 1ec445890..64b5ecb9a 100644 --- a/app/javascript/dashboard/components/buttons/Button.vue +++ b/app/javascript/dashboard/components/buttons/Button.vue @@ -28,10 +28,12 @@ export default { }, }, created() { - // eslint-disable-next-line - console.warn( - '[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead' - ); + if (import.meta.env.DEV) { + // eslint-disable-next-line + console.warn( + '[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead' + ); + } }, }; diff --git a/app/javascript/dashboard/components/widgets/forms/Input.vue b/app/javascript/dashboard/components/widgets/forms/Input.vue index b34d81259..bd6d49470 100644 --- a/app/javascript/dashboard/components/widgets/forms/Input.vue +++ b/app/javascript/dashboard/components/widgets/forms/Input.vue @@ -40,10 +40,12 @@ export default { }, emits: ['update:modelValue', 'input', 'blur'], mounted() { - // eslint-disable-next-line - console.warn( - '[DEPRECATED] has be deprecated and will be removed soon. Please use v3/components/Form/Input.vue instead' - ); + if (import.meta.env.DEV) { + // eslint-disable-next-line no-console + console.warn( + '[DEPRECATED] has be deprecated and will be removed soon. Please use v3/components/Form/Input.vue instead' + ); + } }, methods: { onChange(e) { diff --git a/app/javascript/v3/components/Button/SubmitButton.vue b/app/javascript/v3/components/Button/SubmitButton.vue index 24c04acbb..88dad0799 100644 --- a/app/javascript/v3/components/Button/SubmitButton.vue +++ b/app/javascript/v3/components/Button/SubmitButton.vue @@ -40,10 +40,12 @@ export default { }, }, created() { - // eslint-disable-next-line - console.warn( - '[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead' - ); + if (import.meta.env.DEV) { + // eslint-disable-next-line + console.warn( + '[DEPRECATED] This component has been deprecated and will be removed soon. Please use v3/components/Form/Button.vue instead' + ); + } }, }; From 55d41b112b0558c3afa79718e1f3b8864b18893c Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 11 Feb 2025 19:39:54 +0530 Subject: [PATCH 8/8] feat: Show shared contact's name in Telegram channel (#10856) # Pull Request Template ## Description This PR adds the ability to see the shared contact name in Telegram channels. ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Loom video** https://www.loom.com/share/cd318056ad4d44d4a1fc4b5d4ad38d60?sid=26d833ae-ded9-4cf0-9af7-81eecfa37f19 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Shivam Mishra --- .../message/bubbles/BaseAttachment.vue | 4 ++ .../message/bubbles/Contact.vue | 15 +++-- app/models/attachment.rb | 4 +- .../telegram/incoming_message_service.rb | 6 +- .../20250207040150_add_meta_to_attachment.rb | 5 ++ db/schema.rb | 3 +- spec/models/attachment_spec.rb | 61 +++++++++++++++++++ 7 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 db/migrate/20250207040150_add_meta_to_attachment.rb diff --git a/app/javascript/dashboard/components-next/message/bubbles/BaseAttachment.vue b/app/javascript/dashboard/components-next/message/bubbles/BaseAttachment.vue index d494de908..8beceee26 100644 --- a/app/javascript/dashboard/components-next/message/bubbles/BaseAttachment.vue +++ b/app/javascript/dashboard/components-next/message/bubbles/BaseAttachment.vue @@ -10,6 +10,7 @@ defineProps({ iconBgColor: { type: String, default: 'bg-n-alpha-3' }, senderTranslationKey: { type: String, required: true }, content: { type: String, required: true }, + title: { type: String, default: '' }, // Title can be any name, description, etc action: { type: Object, required: true, @@ -48,6 +49,9 @@ const senderName = computed(() => { }} +
+ {{ title }} +
{{ content }}
diff --git a/app/javascript/dashboard/components-next/message/bubbles/Contact.vue b/app/javascript/dashboard/components-next/message/bubbles/Contact.vue index 7230a7e05..dda249559 100644 --- a/app/javascript/dashboard/components-next/message/bubbles/Contact.vue +++ b/app/javascript/dashboard/components-next/message/bubbles/Contact.vue @@ -11,7 +11,7 @@ import { ExceptionWithMessage, } from 'shared/helpers/CustomErrors'; -const { content, attachments } = useMessageContext(); +const { attachments } = useMessageContext(); const $store = useStore(); const { t } = useI18n(); @@ -24,6 +24,12 @@ const phoneNumber = computed(() => { return attachment.value.fallbackTitle; }); +const contactName = computed(() => { + const { meta } = attachment.value ?? {}; + const { firstName, lastName } = meta ?? {}; + return `${firstName ?? ''} ${lastName ?? ''}`.trim(); +}); + const formattedPhoneNumber = computed(() => { return phoneNumber.value.replace(/\s|-|[A-Za-z]/g, ''); }); @@ -32,13 +38,9 @@ const rawPhoneNumber = computed(() => { return phoneNumber.value.replace(/\D/g, ''); }); -const name = computed(() => { - return content.value; -}); - function getContactObject() { const contactItem = { - name: name.value, + name: contactName.value, phone_number: `+${rawPhoneNumber.value}`, }; return contactItem; @@ -99,6 +101,7 @@ const action = computed(() => ({ icon="i-teenyicons-user-circle-solid" icon-bg-color="bg-[#D6409F]" sender-translation-key="CONVERSATION.SHARED_ATTACHMENT.CONTACT" + :title="contactName" :content="phoneNumber" :action="formattedPhoneNumber ? action : null" /> diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 65e56a21f..0bfd9a978 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -9,6 +9,7 @@ # external_url :string # fallback_title :string # file_type :integer default("image") +# meta :jsonb # created_at :datetime not null # updated_at :datetime not null # account_id :integer not null @@ -116,7 +117,8 @@ class Attachment < ApplicationRecord def contact_metadata { - fallback_title: fallback_title + fallback_title: fallback_title, + meta: meta || {} } end diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb index d39005030..e337a03f6 100644 --- a/app/services/telegram/incoming_message_service.rb +++ b/app/services/telegram/incoming_message_service.rb @@ -143,7 +143,11 @@ class Telegram::IncomingMessageService @message.attachments.new( account_id: @message.account_id, file_type: :contact, - fallback_title: contact_card['phone_number'].to_s + fallback_title: contact_card['phone_number'].to_s, + meta: { + first_name: contact_card['first_name'], + last_name: contact_card['last_name'] + } ) end diff --git a/db/migrate/20250207040150_add_meta_to_attachment.rb b/db/migrate/20250207040150_add_meta_to_attachment.rb new file mode 100644 index 000000000..32188b4d7 --- /dev/null +++ b/db/migrate/20250207040150_add_meta_to_attachment.rb @@ -0,0 +1,5 @@ +class AddMetaToAttachment < ActiveRecord::Migration[7.0] + def change + add_column :attachments, :meta, :jsonb, default: {} + end +end diff --git a/db/schema.rb b/db/schema.rb index 17e5c46da..6bea44c8b 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: 2025_01_16_061033) do +ActiveRecord::Schema[7.0].define(version: 2025_02_07_040150) do # These extensions should be enabled to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -174,6 +174,7 @@ ActiveRecord::Schema[7.0].define(version: 2025_01_16_061033) do t.datetime "updated_at", precision: nil, null: false t.string "fallback_title" t.string "extension" + t.jsonb "meta", default: {} t.index ["account_id"], name: "index_attachments_on_account_id" t.index ["message_id"], name: "index_attachments_on_message_id" end diff --git a/spec/models/attachment_spec.rb b/spec/models/attachment_spec.rb index b1d19bd0e..241125538 100644 --- a/spec/models/attachment_spec.rb +++ b/spec/models/attachment_spec.rb @@ -67,4 +67,65 @@ RSpec.describe Attachment do expect(message.attachments.first.push_event_data[:data_url]).not_to eq message.attachments.first.external_url end end + + describe 'meta data handling' do + let(:message) { create(:message) } + + context 'when attachment is a contact type' do + let(:contact_attachment) do + message.attachments.create!( + account_id: message.account_id, + file_type: :contact, + fallback_title: '+1234567890', + meta: { + first_name: 'John', + last_name: 'Doe' + } + ) + end + + it 'stores and retrieves meta data correctly' do + expect(contact_attachment.meta['first_name']).to eq('John') + expect(contact_attachment.meta['last_name']).to eq('Doe') + end + + it 'includes meta data in push_event_data' do + event_data = contact_attachment.push_event_data + expect(event_data[:meta]).to eq({ + 'first_name' => 'John', + 'last_name' => 'Doe' + }) + end + + it 'returns empty hash for meta if not set' do + attachment = message.attachments.create!( + account_id: message.account_id, + file_type: :contact, + fallback_title: '+1234567890' + ) + expect(attachment.push_event_data[:meta]).to eq({}) + end + end + + context 'when meta is used with other file types' do + let(:image_attachment) do + attachment = message.attachments.new( + account_id: message.account_id, + file_type: :image, + meta: { description: 'Test image' } + ) + attachment.file.attach( + io: Rails.root.join('spec/assets/avatar.png').open, + filename: 'avatar.png', + content_type: 'image/png' + ) + attachment.save! + attachment + end + + it 'preserves meta data with file attachments' do + expect(image_attachment.meta['description']).to eq('Test image') + end + end + end end