Merge branch 'develop' into fix/next-message-bubbles
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(() => {
|
||||
}}
|
||||
</div>
|
||||
<slot>
|
||||
<div v-if="title" class="truncate text-sm text-n-slate-12">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div v-if="content" class="truncate text-sm text-n-slate-11">
|
||||
{{ content }}
|
||||
</div>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -40,10 +40,12 @@ export default {
|
||||
},
|
||||
emits: ['update:modelValue', 'input', 'blur'],
|
||||
mounted() {
|
||||
// eslint-disable-next-line
|
||||
console.warn(
|
||||
'[DEPRECATED] <WootInput> 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] <WootInput> has be deprecated and will be removed soon. Please use v3/components/Form/Input.vue instead'
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(e) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,9 +1,44 @@
|
||||
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?
|
||||
|
||||
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 CONVERSATION_UPDATE_EVENTS.include?(event_name)
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddMetaToAttachment < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :attachments, :meta, :jsonb, default: {}
|
||||
end
|
||||
end
|
||||
+2
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
Generated
+19
-9
@@ -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: {}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user