From ce55e80bb47d91c11eada488e0e352472553ece9 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 17 Apr 2025 14:31:52 +0530 Subject: [PATCH 01/82] chore: Add the support for xml file in attachment (#11328) # Pull Request Template ## Description Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes # (issue) ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] 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 - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --- app/javascript/shared/constants/messages.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/shared/constants/messages.js b/app/javascript/shared/constants/messages.js index b827216a5..e0a57b11c 100644 --- a/app/javascript/shared/constants/messages.js +++ b/app/javascript/shared/constants/messages.js @@ -44,6 +44,7 @@ export const ALLOWED_FILE_TYPES = 'video/*,' + '.3gpp,' + 'text/csv, text/plain, application/json, application/pdf, text/rtf,' + + 'application/xml, text/xml,' + 'application/zip, application/x-7z-compressed application/vnd.rar application/x-tar,' + 'application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/vnd.oasis.opendocument.text,' + 'application/vnd.openxmlformats-officedocument.presentationml.presentation, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,' + From 97895db85e6286f8b75f524b13c77b076c2797fc Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 17 Apr 2025 15:47:56 +0530 Subject: [PATCH 02/82] chore: Disable warnings old Instagram inbox for messenger conversations (#11329) We have added warnings for existing Instagram messenger inboxes via https://github.com/chatwoot/chatwoot/pull/11303. However, an issue arose where warnings were incorrectly displaying for messenger conversations. This PR resolves that issue. --- .../components/widgets/conversation/MessagesView.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 620d52379..77f411708 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -213,12 +213,17 @@ export default { // Check there is a instagram inbox exists with the same instagram_id hasDuplicateInstagramInbox() { const instagramId = this.inbox.instagram_id; + const { additional_attributes: additionalAttributes = {} } = this.inbox; const instagramInbox = this.$store.getters['inboxes/getInstagramInboxByInstagramId']( instagramId ); - return this.inbox.channel_type === INBOX_TYPES.FB && instagramInbox; + return ( + this.inbox.channel_type === INBOX_TYPES.FB && + additionalAttributes.type === 'instagram_direct_message' && + instagramInbox + ); }, replyWindowBannerMessage() { From aeef0910840b3a61d43885e420f784ff32677c49 Mon Sep 17 00:00:00 2001 From: mnsbr <44938971+mnsbr@users.noreply.github.com> Date: Mon, 21 Apr 2025 14:27:38 +0800 Subject: [PATCH 03/82] fix: correct typo in CampaignConversationBuilder (#11336) ## Description Fixed a typo in the `CampaignConversationBuilder` class. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? Verified that the typo fix does not affect functionality by running the existing test suite. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] My changes generate no new warnings - [x] New and existing unit tests pass locally with my changes Co-authored-by: Shivam Mishra --- app/builders/campaigns/campaign_conversation_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/builders/campaigns/campaign_conversation_builder.rb b/app/builders/campaigns/campaign_conversation_builder.rb index 3b3f262c9..0e9b90105 100644 --- a/app/builders/campaigns/campaign_conversation_builder.rb +++ b/app/builders/campaigns/campaign_conversation_builder.rb @@ -9,7 +9,7 @@ class Campaigns::CampaignConversationBuilder @contact_inbox.lock! # We won't send campaigns if a conversation is already present - raise 'Conversation alread present' if @contact_inbox.reload.conversations.present? + raise 'Conversation already present' if @contact_inbox.reload.conversations.present? @conversation = ::Conversation.create!(conversation_params) Messages::MessageBuilder.new(@campaign.sender, @conversation, message_params).perform From c24a6dc74c6ce0b557215ae942702a81ee20527e Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Mon, 21 Apr 2025 13:48:56 +0530 Subject: [PATCH 04/82] fix: Prevent CC/BCC field reset on chat activity actions (#11342) # Pull Request Template ## Description This PR fixes the CC/BCC field reset issue on activity action. Fixes [CW-4256](https://linear.app/chatwoot/issue/CW-4256/emails-added-in-cc-and-bcc-disappears-when-you-click-on-assign-to-me), #5234 ### Cause of the Issue Previously, the CC and BCC fields in the reply box were being reset whenever a conversation activity occurred (such as assignment, status change, etc.). This happened because watchers on the current chat messages array and current chat object would trigger `setCCAndToEmailsFromLastChat` even when the last message was an activity or system message, not a real email. ### Solution - The updated logic ensures that the CC and BCC fields are only set under the following conditions: **1**. Switching to a new conversation. **2**. In the same conversation, only if the last message is not an activity (i.e., only for actual emails). - It uses the `lastEmail` computed property, which filters out private and activity messages, ensuring only real email context changes update the fields. - This prevents user-entered `CC/BCC` values from being cleared after system events (e.g., assignments or status changes), while still updating the fields correctly when relevant messages are received. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ### Loom video **Before** https://www.loom.com/share/2ec50dd1c0ed4eaf9170465274bea41e?sid=cc8b88cb-fd39-473a-8df3-78a242c8407b **After** https://www.loom.com/share/17fd2d96d5d84a049dcbf20d401d2ada?sid=8949ad48-7769-49d2-92c5-267da8c60d6e ## 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 - [ ] 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 --- .../widgets/conversation/ReplyBox.vue | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index ce47553ea..e70c7e1a4 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -388,9 +388,14 @@ export default { }, }, watch: { - currentChat(conversation) { + currentChat(conversation, oldConversation) { const { can_reply: canReply } = conversation; - this.setCCAndToEmailsFromLastChat(); + if (oldConversation && oldConversation.id !== conversation.id) { + // Only update email fields when switching to a completely different conversation (by ID) + // This prevents overwriting user input (e.g., CC/BCC fields) when performing actions + // like self-assign or other updates that do not actually change the conversation context + this.setCCAndToEmailsFromLastChat(); + } if (this.isOnPrivateNote) { return; @@ -406,13 +411,12 @@ export default { }, // 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 watcher handles two main cases: + // 1. When switching conversations and messages are fetched/updated, ensures CC/BCC fields are set from the latest OUTGOING/INCOMING email (not activity/private messages). + // 2. Fixes and issue where CC/BCC fields could be reset/lost after assignment/activity actions or message mutations that did not represent a true email context change. + lastEmail: { + handler(lastEmail) { + if (!lastEmail) return; this.setCCAndToEmailsFromLastChat(); }, deep: true, From 1531772365a6f4041c57db913a1d6a88e094505d Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Mon, 21 Apr 2025 14:17:39 +0530 Subject: [PATCH 05/82] chore: Migrated Instagram inbox warning style issues (#11332) **Before** ![CleanShot 2025-04-17 at 22 53 28@2x](https://github.com/user-attachments/assets/00841dd1-ca1f-47b3-9972-6c9100bd2291) ![CleanShot 2025-04-17 at 22 53 46@2x](https://github.com/user-attachments/assets/648a4770-3aea-4b84-9456-6941744a4d65) **After** ![CleanShot 2025-04-17 at 22 50 40@2x](https://github.com/user-attachments/assets/d37250fd-5400-4548-82f4-ab0c3b417b3a) ![CleanShot 2025-04-17 at 22 50 47@2x](https://github.com/user-attachments/assets/ca611297-8dd0-4bd9-b6b6-02660c252aa9) --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- .../inbox/channels/instagram/DuplicateInboxBanner.vue | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/DuplicateInboxBanner.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/DuplicateInboxBanner.vue index 925d089c5..8bb51cde9 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/DuplicateInboxBanner.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/instagram/DuplicateInboxBanner.vue @@ -1,5 +1,6 @@ From 0a394e16ca4d4bf5b3c672e0b2fc30dadc65e19a Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Mon, 21 Apr 2025 14:46:51 +0530 Subject: [PATCH 06/82] chore: Audit message characters across all channels (#11343) - Audited message characters across all channels. - Replaced `isAInstagramChannel` with `isAnInstagramChannel` --- .../components-next/message/MessageMeta.vue | 6 ++--- .../widgets/WootWriter/ReplyBottomPanel.vue | 2 +- .../widgets/conversation/MessagesView.vue | 4 +-- .../widgets/conversation/ReplyBox.vue | 26 ++++++++++++++----- .../dashboard/composables/useInbox.js | 4 +-- .../dashboard/settings/inbox/Settings.vue | 2 +- .../shared/helpers/MessageTypeHelper.js | 14 +++++++++- app/javascript/shared/mixins/inboxMixin.js | 2 +- 8 files changed, 42 insertions(+), 18 deletions(-) diff --git a/app/javascript/dashboard/components-next/message/MessageMeta.vue b/app/javascript/dashboard/components-next/message/MessageMeta.vue index 8d7c22dab..e0602cb89 100644 --- a/app/javascript/dashboard/components-next/message/MessageMeta.vue +++ b/app/javascript/dashboard/components-next/message/MessageMeta.vue @@ -19,7 +19,7 @@ const { isAWebWidgetInbox, isAWhatsAppChannel, isAnEmailChannel, - isAInstagramChannel, + isAnInstagramChannel, } = useInbox(); const { @@ -60,7 +60,7 @@ const isSent = computed(() => { isAFacebookInbox.value || isASmsInbox.value || isATelegramChannel.value || - isAInstagramChannel.value + isAnInstagramChannel.value ) { return sourceId.value && status.value === MESSAGE_STATUS.SENT; } @@ -100,7 +100,7 @@ const isRead = computed(() => { isAWhatsAppChannel.value || isATwilioChannel.value || isAFacebookInbox.value || - isAInstagramChannel.value + isAnInstagramChannel.value ) { return sourceId.value && status.value === MESSAGE_STATUS.READ; } diff --git a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue index cf59532e0..47fe01e9c 100644 --- a/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue +++ b/app/javascript/dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue @@ -202,7 +202,7 @@ export default { if (this.isALineChannel) { return ALLOWED_FILE_TYPES_FOR_LINE; } - if (this.isAInstagramChannel || this.isInstagramDM) { + if (this.isAnInstagramChannel || this.isInstagramDM) { return ALLOWED_FILE_TYPES_FOR_INSTAGRAM; } diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 77f411708..8d0c77f1b 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -249,7 +249,7 @@ export default { return this.$t('CONVERSATION.CANNOT_REPLY'); }, replyWindowLink() { - if (this.isAFacebookInbox || this.isAInstagramChannel) { + if (this.isAFacebookInbox || this.isAnInstagramChannel) { return REPLY_POLICY.FACEBOOK; } if (this.isAWhatsAppCloudChannel) { @@ -264,7 +264,7 @@ export default { if ( this.isAWhatsAppChannel || this.isAFacebookInbox || - this.isAInstagramChannel + this.isAnInstagramChannel ) { return this.$t('CONVERSATION.24_HOURS_WINDOW'); } diff --git a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue index e70c7e1a4..4fa83e029 100644 --- a/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue +++ b/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue @@ -241,15 +241,27 @@ export default { if (this.isAFacebookInbox) { return MESSAGE_MAX_LENGTH.FACEBOOK; } - if (this.isAWhatsAppChannel) { + if (this.isAnInstagramChannel) { + return MESSAGE_MAX_LENGTH.INSTAGRAM; + } + if (this.isATwilioWhatsAppChannel) { return MESSAGE_MAX_LENGTH.TWILIO_WHATSAPP; } + if (this.isAWhatsAppCloudChannel) { + return MESSAGE_MAX_LENGTH.WHATSAPP_CLOUD; + } if (this.isASmsInbox) { return MESSAGE_MAX_LENGTH.TWILIO_SMS; } if (this.isAnEmailChannel) { return MESSAGE_MAX_LENGTH.EMAIL; } + if (this.isATwilioSMSChannel) { + return MESSAGE_MAX_LENGTH.TWILIO_SMS; + } + if (this.isAWhatsAppChannel) { + return MESSAGE_MAX_LENGTH.WHATSAPP_CLOUD; + } return MESSAGE_MAX_LENGTH.GENERAL; }, showFileUpload() { @@ -262,7 +274,7 @@ export default { this.isASmsInbox || this.isATelegramChannel || this.isALineChannel || - this.isAInstagramChannel + this.isAnInstagramChannel ); }, replyButtonLabel() { @@ -693,7 +705,7 @@ export default { // When users send messages containing both text and attachments on Instagram, Instagram treats them as separate messages. // Although Chatwoot combines these into a single message, Instagram sends separate echo events for each component. // This can create duplicate messages in Chatwoot. To prevent this issue, we'll handle text and attachments as separate messages. - const isOnInstagram = this.isAInstagramChannel; + const isOnInstagram = this.isAnInstagramChannel; if ((isOnWhatsApp || isOnInstagram) && !this.isPrivate) { this.sendMessageAsMultipleMessages(this.message); } else { @@ -947,7 +959,7 @@ export default { const multipleMessagePayload = []; if (this.attachedFiles && this.attachedFiles.length) { - let caption = this.isAInstagramChannel ? '' : message; + let caption = this.isAnInstagramChannel ? '' : message; this.attachedFiles.forEach(attachment => { const attachedFile = this.globalConfig.directUploadsEnabled ? attachment.blobSignedId @@ -963,7 +975,7 @@ export default { attachmentPayload = this.setReplyToInPayload(attachmentPayload); multipleMessagePayload.push(attachmentPayload); // For WhatsApp, only the first attachment gets a caption - if (!this.isAInstagramChannel) caption = ''; + if (!this.isAnInstagramChannel) caption = ''; }); } @@ -972,8 +984,8 @@ export default { // For Instagram, we need a separate text message // For WhatsApp, we only need a text message if there are no attachments if ( - (this.isAInstagramChannel && this.message) || - (!this.isAInstagramChannel && hasNoAttachments) + (this.isAnInstagramChannel && this.message) || + (!this.isAnInstagramChannel && hasNoAttachments) ) { let messagePayload = { conversationId: this.currentChat.id, diff --git a/app/javascript/dashboard/composables/useInbox.js b/app/javascript/dashboard/composables/useInbox.js index 2c59bc6e5..67ce11ae2 100644 --- a/app/javascript/dashboard/composables/useInbox.js +++ b/app/javascript/dashboard/composables/useInbox.js @@ -121,7 +121,7 @@ export const useInbox = () => { ); }); - const isAInstagramChannel = computed(() => { + const isAnInstagramChannel = computed(() => { return channelType.value === INBOX_TYPES.INSTAGRAM; }); @@ -141,6 +141,6 @@ export const useInbox = () => { isAWhatsAppCloudChannel, is360DialogWhatsAppChannel, isAnEmailChannel, - isAInstagramChannel, + isAnInstagramChannel, }; }; diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue index aa9eaf596..73d9963bd 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue @@ -204,7 +204,7 @@ export default { return false; }, instagramUnauthorized() { - return this.isAInstagramChannel && this.inbox.reauthorization_required; + return this.isAnInstagramChannel && this.inbox.reauthorization_required; }, // Check if a instagram inbox exists with the same instagram_id hasDuplicateInstagramInbox() { diff --git a/app/javascript/shared/helpers/MessageTypeHelper.js b/app/javascript/shared/helpers/MessageTypeHelper.js index 4ec3f8bbc..1e6cf454c 100644 --- a/app/javascript/shared/helpers/MessageTypeHelper.js +++ b/app/javascript/shared/helpers/MessageTypeHelper.js @@ -4,8 +4,20 @@ export const isASubmittedFormMessage = (message = {}) => export const MESSAGE_MAX_LENGTH = { GENERAL: 10000, - FACEBOOK: 1000, + // https://developers.facebook.com/docs/messenger-platform/reference/send-api#request + FACEBOOK: 2000, + // https://developers.facebook.com/docs/instagram-platform/instagram-api-with-instagram-login/messaging-api#send-a-text-message + INSTAGRAM: 1000, + // https://www.twilio.com/docs/glossary/what-sms-character-limit TWILIO_SMS: 320, + // https://help.twilio.com/articles/360033806753-Maximum-Message-Length-with-Twilio-Programmable-Messaging TWILIO_WHATSAPP: 1600, + // https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages#text-object + WHATSAPP_CLOUD: 4096, + // https://support.bandwidth.com/hc/en-us/articles/360010235373-What-are-Bandwidth-s-SMS-character-limits-and-concatenation-practices + BANDWIDTH_SMS: 160, + // https://core.telegram.org/bots/api#sendmessage + TELEGRAM: 4096, + LINE: 2000, EMAIL: 25000, }; diff --git a/app/javascript/shared/mixins/inboxMixin.js b/app/javascript/shared/mixins/inboxMixin.js index 94f5997f7..273e9f8b4 100644 --- a/app/javascript/shared/mixins/inboxMixin.js +++ b/app/javascript/shared/mixins/inboxMixin.js @@ -121,7 +121,7 @@ export default { this.isATwilioWhatsAppChannel ); }, - isAInstagramChannel() { + isAnInstagramChannel() { return this.channelType === INBOX_TYPES.INSTAGRAM; }, }, From cce1b874c1288a2b0053c6b5ce85e6de585831fd Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Mon, 21 Apr 2025 15:29:55 +0530 Subject: [PATCH 07/82] chore: update EE LICENCE year (#11344) # Pull Request Template ## Description Please include a summary of the change and issue(s) fixed. Also, mention relevant motivation, context, and any dependencies that this change requires. Fixes # (issue) ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality not to work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] 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 - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> --- enterprise/LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/LICENSE b/enterprise/LICENSE index 7ffa68d10..dd92c09e9 100644 --- a/enterprise/LICENSE +++ b/enterprise/LICENSE @@ -1,5 +1,5 @@ The Chatwoot Enterprise license (the “Enterprise License”) -Copyright (c) 2017-2021 Chatwoot Inc +Copyright (c) 2017-2025 Chatwoot Inc With regard to the Chatwoot Software: From 5ccb131a67a050c328d043484c29654d7a0f20e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 16:43:46 -0700 Subject: [PATCH 08/82] chore(deps-dev): Bump vite from 5.4.17 to 5.4.18 (#11324) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.17 to 5.4.18.
Release notes

Sourced from vite's releases.

v5.4.18

Please refer to CHANGELOG.md for details.

Changelog

Sourced from vite's changelog.

5.4.18 (2025-04-10)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=vite&package-manager=npm_and_yarn&previous-version=5.4.17&new-version=5.4.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/chatwoot/chatwoot/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 4 +- pnpm-lock.yaml | 248 ++++++++++++++++++++++++------------------------- 2 files changed, 126 insertions(+), 126 deletions(-) diff --git a/package.json b/package.json index c27e12a99..18f4b9268 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,7 @@ "prosemirror-model": "^1.22.3", "size-limit": "^8.2.4", "tailwindcss": "^3.4.13", - "vite": "^5.4.17", + "vite": "^5.4.18", "vite-plugin-ruby": "^5.0.0", "vitest": "3.0.5" }, @@ -154,7 +154,7 @@ "pnpm": { "overrides": { "vite-node": "2.0.1", - "vite": "5.4.17", + "vite": "5.4.18", "vitest": "3.0.5" } }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd45c7647..73799ff19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: vite-node: 2.0.1 - vite: 5.4.17 + vite: 5.4.18 vitest: 3.0.5 importers: @@ -72,7 +72,7 @@ importers: version: 8.20.5(vue@3.5.12(typescript@5.6.2)) '@vitejs/plugin-vue': specifier: ^5.1.4 - version: 5.1.4(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2)) + version: 5.1.4(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2)) '@vue/compiler-sfc': specifier: ^3.5.8 version: 3.5.8 @@ -238,7 +238,7 @@ importers: version: 1.8.1(tailwindcss@3.4.13) '@histoire/plugin-vue': specifier: 0.17.15 - version: 0.17.15(histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)))(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2)) + version: 0.17.15(histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)))(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2)) '@iconify-json/logos': specifier: ^1.2.3 version: 1.2.3 @@ -301,7 +301,7 @@ importers: version: 6.0.0 histoire: specifier: 0.17.15 - version: 0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + version: 0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) husky: specifier: ^7.0.0 version: 7.0.4 @@ -330,11 +330,11 @@ importers: specifier: ^3.4.13 version: 3.4.13 vite: - specifier: 5.4.17 - version: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + specifier: 5.4.18 + version: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) vite-plugin-ruby: specifier: ^5.0.0 - version: 5.0.0(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + version: 5.0.0(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) vitest: specifier: 3.0.5 version: 3.0.5(@types/node@22.7.0)(jsdom@24.1.3)(sass@1.79.3)(terser@5.33.0) @@ -860,7 +860,7 @@ packages: '@histoire/shared@0.17.17': resolution: {integrity: sha512-ueGtURysonT0MujCObPCR57+mgZluMEXCrbc2FBgKAD/DoAt38tNwSGsmLldk2O6nTr7lr6ClbVSgWrLwgY6Xw==} peerDependencies: - vite: 5.4.17 + vite: 5.4.18 '@histoire/vendors@0.17.17': resolution: {integrity: sha512-QZvmffdoJlLuYftPIkOU5Q2FPAdG2JjMuQ5jF7NmEl0n1XnmbMqtRkdYTZ4eF6CO1KLZ0Zyf6gBQvoT1uWNcjA==} @@ -1039,103 +1039,103 @@ packages: '@rails/ujs@7.1.400': resolution: {integrity: sha512-YwvXm3BR5tn+VCAKYGycLejMRVZE3Ionj5gFjEeGXCZnI0Rpi+7dKpmyu90kdUY7dRUFpHTdu9zZceEzFLl38w==} - '@rollup/rollup-android-arm-eabi@4.39.0': - resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} + '@rollup/rollup-android-arm-eabi@4.40.0': + resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.39.0': - resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} + '@rollup/rollup-android-arm64@4.40.0': + resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.39.0': - resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} + '@rollup/rollup-darwin-arm64@4.40.0': + resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.39.0': - resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} + '@rollup/rollup-darwin-x64@4.40.0': + resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.39.0': - resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} + '@rollup/rollup-freebsd-arm64@4.40.0': + resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.39.0': - resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} + '@rollup/rollup-freebsd-x64@4.40.0': + resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.39.0': - resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.39.0': - resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} + '@rollup/rollup-linux-arm-musleabihf@4.40.0': + resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.39.0': - resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} + '@rollup/rollup-linux-arm64-gnu@4.40.0': + resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.39.0': - resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} + '@rollup/rollup-linux-arm64-musl@4.40.0': + resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.39.0': - resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': + resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': - resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.39.0': - resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} + '@rollup/rollup-linux-riscv64-gnu@4.40.0': + resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.39.0': - resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + '@rollup/rollup-linux-riscv64-musl@4.40.0': + resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.39.0': - resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} + '@rollup/rollup-linux-s390x-gnu@4.40.0': + resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.39.0': - resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} + '@rollup/rollup-linux-x64-gnu@4.40.0': + resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.39.0': - resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} + '@rollup/rollup-linux-x64-musl@4.40.0': + resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.39.0': - resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} + '@rollup/rollup-win32-arm64-msvc@4.40.0': + resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.39.0': - resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} + '@rollup/rollup-win32-ia32-msvc@4.40.0': + resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.39.0': - resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} + '@rollup/rollup-win32-x64-msvc@4.40.0': + resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} cpu: [x64] os: [win32] @@ -1800,7 +1800,7 @@ packages: resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: 5.4.17 + vite: 5.4.18 vue: ^3.2.25 '@vitest/coverage-v8@3.0.5': @@ -1819,7 +1819,7 @@ packages: resolution: {integrity: sha512-CLPNBFBIE7x6aEGbIjaQAX03ZZlBMaWwAjBdMkIf/cAn6xzLTiM3zYqO/WAbieEjsAZir6tO71mzeHZoodThvw==} peerDependencies: msw: ^2.4.9 - vite: 5.4.17 + vite: 5.4.18 peerDependenciesMeta: msw: optional: true @@ -3100,7 +3100,7 @@ packages: resolution: {integrity: sha512-DiRMSIgj340z+zikqf0f3Pj0CTv2/xtdBMBIAO1EARat+QXxMwumbfK41Gi7f9IIBr+UVmomNcwFxVY2EM/vrw==} hasBin: true peerDependencies: - vite: 5.4.17 + vite: 5.4.18 hotkeys-js@3.8.7: resolution: {integrity: sha512-ckAx3EkUr5XjDwjEHDorHxRO2Kb7z6Z2Sxul4MbBkN8Nho7XDslQsgMJT+CiJ5Z4TgRxxvKHEpuLE3imzqy4Lg==} @@ -4344,8 +4344,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.39.0: - resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} + rollup@4.40.0: + resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -4863,10 +4863,10 @@ packages: vite-plugin-ruby@5.0.0: resolution: {integrity: sha512-c8PjTp21Ah/ttgnNUyu0qvCXZI08Jr9I24oUKg3TRIRhF5GcOZ++6wtlTCrNFd9COEQbpXHxlRIXd/MEg0iZJw==} peerDependencies: - vite: 5.4.17 + vite: 5.4.18 - vite@5.4.17: - resolution: {integrity: sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==} + vite@5.4.18: + resolution: {integrity: sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -5672,10 +5672,10 @@ snapshots: highlight.js: 11.10.0 vue: 3.5.12(typescript@5.6.2) - '@histoire/app@0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': + '@histoire/app@0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': dependencies: - '@histoire/controls': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) - '@histoire/shared': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/controls': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/shared': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) '@histoire/vendors': 0.17.17 '@types/flexsearch': 0.7.6 flexsearch: 0.7.21 @@ -5683,7 +5683,7 @@ snapshots: transitivePeerDependencies: - vite - '@histoire/controls@0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': + '@histoire/controls@0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': dependencies: '@codemirror/commands': 6.7.0 '@codemirror/lang-json': 6.0.1 @@ -5692,26 +5692,26 @@ snapshots: '@codemirror/state': 6.4.1 '@codemirror/theme-one-dark': 6.1.2 '@codemirror/view': 6.34.1 - '@histoire/shared': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/shared': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) '@histoire/vendors': 0.17.17 transitivePeerDependencies: - vite - '@histoire/plugin-vue@0.17.15(histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)))(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))': + '@histoire/plugin-vue@0.17.15(histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)))(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))': dependencies: - '@histoire/controls': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) - '@histoire/shared': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/controls': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/shared': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) '@histoire/vendors': 0.17.17 change-case: 4.1.2 globby: 13.2.2 - histoire: 0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + histoire: 0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) launch-editor: 2.9.1 pathe: 1.1.2 vue: 3.5.12(typescript@5.6.2) transitivePeerDependencies: - vite - '@histoire/shared@0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': + '@histoire/shared@0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': dependencies: '@histoire/vendors': 0.17.17 '@types/fs-extra': 9.0.13 @@ -5719,7 +5719,7 @@ snapshots: chokidar: 3.6.0 pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) '@histoire/vendors@0.17.17': {} @@ -5944,64 +5944,64 @@ snapshots: '@rails/ujs@7.1.400': {} - '@rollup/rollup-android-arm-eabi@4.39.0': + '@rollup/rollup-android-arm-eabi@4.40.0': optional: true - '@rollup/rollup-android-arm64@4.39.0': + '@rollup/rollup-android-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-arm64@4.39.0': + '@rollup/rollup-darwin-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-x64@4.39.0': + '@rollup/rollup-darwin-x64@4.40.0': optional: true - '@rollup/rollup-freebsd-arm64@4.39.0': + '@rollup/rollup-freebsd-arm64@4.40.0': optional: true - '@rollup/rollup-freebsd-x64@4.39.0': + '@rollup/rollup-freebsd-x64@4.40.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.39.0': + '@rollup/rollup-linux-arm-musleabihf@4.40.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.39.0': + '@rollup/rollup-linux-arm64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.39.0': + '@rollup/rollup-linux-arm64-musl@4.40.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.39.0': + '@rollup/rollup-linux-riscv64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.39.0': + '@rollup/rollup-linux-riscv64-musl@4.40.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.39.0': + '@rollup/rollup-linux-s390x-gnu@4.40.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.39.0': + '@rollup/rollup-linux-x64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-x64-musl@4.39.0': + '@rollup/rollup-linux-x64-musl@4.40.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.39.0': + '@rollup/rollup-win32-arm64-msvc@4.40.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.39.0': + '@rollup/rollup-win32-ia32-msvc@4.40.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.39.0': + '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true '@rtsao/scc@1.1.0': {} @@ -6802,9 +6802,9 @@ snapshots: global: 4.4.0 is-function: 1.0.2 - '@vitejs/plugin-vue@5.1.4(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))': + '@vitejs/plugin-vue@5.1.4(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))(vue@3.5.12(typescript@5.6.2))': dependencies: - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) vue: 3.5.12(typescript@5.6.2) '@vitest/coverage-v8@3.0.5(vitest@3.0.5(@types/node@22.7.0)(jsdom@24.1.3)(sass@1.79.3)(terser@5.33.0))': @@ -6832,13 +6832,13 @@ snapshots: chai: 5.1.2 tinyrainbow: 2.0.0 - '@vitest/mocker@3.0.5(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': + '@vitest/mocker@3.0.5(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0))': dependencies: '@vitest/spy': 3.0.5 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) '@vitest/pretty-format@3.0.5': dependencies: @@ -8393,12 +8393,12 @@ snapshots: highlight.js@11.10.0: {} - histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)): + histoire@0.17.15(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)): dependencies: '@akryum/tinypool': 0.3.1 - '@histoire/app': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) - '@histoire/controls': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) - '@histoire/shared': 0.17.17(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/app': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/controls': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@histoire/shared': 0.17.17(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) '@histoire/vendors': 0.17.17 '@types/flexsearch': 0.7.6 '@types/markdown-it': 12.2.3 @@ -8425,7 +8425,7 @@ snapshots: sade: 1.8.1 shiki-es: 0.2.0 sirv: 2.0.4 - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) vite-node: 2.0.1(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - '@types/node' @@ -9766,30 +9766,30 @@ snapshots: dependencies: glob: 7.2.3 - rollup@4.39.0: + rollup@4.40.0: dependencies: '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.39.0 - '@rollup/rollup-android-arm64': 4.39.0 - '@rollup/rollup-darwin-arm64': 4.39.0 - '@rollup/rollup-darwin-x64': 4.39.0 - '@rollup/rollup-freebsd-arm64': 4.39.0 - '@rollup/rollup-freebsd-x64': 4.39.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 - '@rollup/rollup-linux-arm-musleabihf': 4.39.0 - '@rollup/rollup-linux-arm64-gnu': 4.39.0 - '@rollup/rollup-linux-arm64-musl': 4.39.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 - '@rollup/rollup-linux-riscv64-gnu': 4.39.0 - '@rollup/rollup-linux-riscv64-musl': 4.39.0 - '@rollup/rollup-linux-s390x-gnu': 4.39.0 - '@rollup/rollup-linux-x64-gnu': 4.39.0 - '@rollup/rollup-linux-x64-musl': 4.39.0 - '@rollup/rollup-win32-arm64-msvc': 4.39.0 - '@rollup/rollup-win32-ia32-msvc': 4.39.0 - '@rollup/rollup-win32-x64-msvc': 4.39.0 + '@rollup/rollup-android-arm-eabi': 4.40.0 + '@rollup/rollup-android-arm64': 4.40.0 + '@rollup/rollup-darwin-arm64': 4.40.0 + '@rollup/rollup-darwin-x64': 4.40.0 + '@rollup/rollup-freebsd-arm64': 4.40.0 + '@rollup/rollup-freebsd-x64': 4.40.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 + '@rollup/rollup-linux-arm-musleabihf': 4.40.0 + '@rollup/rollup-linux-arm64-gnu': 4.40.0 + '@rollup/rollup-linux-arm64-musl': 4.40.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-musl': 4.40.0 + '@rollup/rollup-linux-s390x-gnu': 4.40.0 + '@rollup/rollup-linux-x64-gnu': 4.40.0 + '@rollup/rollup-linux-x64-musl': 4.40.0 + '@rollup/rollup-win32-arm64-msvc': 4.40.0 + '@rollup/rollup-win32-ia32-msvc': 4.40.0 + '@rollup/rollup-win32-x64-msvc': 4.40.0 fsevents: 2.3.3 rope-sequence@1.3.2: {} @@ -10378,7 +10378,7 @@ snapshots: debug: 4.4.0 pathe: 1.1.2 picocolors: 1.1.1 - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - '@types/node' - less @@ -10390,19 +10390,19 @@ snapshots: - supports-color - terser - vite-plugin-ruby@5.0.0(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)): + vite-plugin-ruby@5.0.0(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)): dependencies: debug: 4.3.5 fast-glob: 3.3.2 - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) transitivePeerDependencies: - supports-color - vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0): + vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0): dependencies: esbuild: 0.21.5 postcss: 8.5.3 - rollup: 4.39.0 + rollup: 4.40.0 optionalDependencies: '@types/node': 22.7.0 fsevents: 2.3.3 @@ -10412,7 +10412,7 @@ snapshots: vitest@3.0.5(@types/node@22.7.0)(jsdom@24.1.3)(sass@1.79.3)(terser@5.33.0): dependencies: '@vitest/expect': 3.0.5 - '@vitest/mocker': 3.0.5(vite@5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) + '@vitest/mocker': 3.0.5(vite@5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0)) '@vitest/pretty-format': 3.0.5 '@vitest/runner': 3.0.5 '@vitest/snapshot': 3.0.5 @@ -10428,7 +10428,7 @@ snapshots: tinyexec: 0.3.2 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 5.4.17(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.18(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) vite-node: 2.0.1(@types/node@22.7.0)(sass@1.79.3)(terser@5.33.0) why-is-node-running: 2.3.0 optionalDependencies: From 9c711bab742b89bc1c24ec144a859671852b6bb3 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 22 Apr 2025 05:23:52 +0530 Subject: [PATCH 09/82] fix: Inconsistent widget bubble focus outline shape (#11345) # Pull Request Template ## Description This PR resolves the issue where the focus outline for the standard bubble type appears as a square in Safari, while it appears circular in Chrome. Fixes [CW-4252](https://linear.app/chatwoot/issue/CW-4252/v41-circle-around-campaign-pop-up-warped), https://github.com/chatwoot/chatwoot/issues/11327 **Cause** In Chrome, the focus outline for the standard bubble type is circular, but in Safari, it appears square. This is due to a 20px margin on the SVG inside the bubble. **Solution** The `20px` margin was removed from the SVG, fixing the focus outline to appear circular in Safari browser. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ### Loom Video https://www.loom.com/share/cc4244f369f84b98afaef539b79abcfe?sid=e957df16-1a53-4349-8bdc-705b2ed82d45 ### Screenshots **Before** https://www.loom.com/share/e858417722c64df6801ea87e4b89779f?sid=81a0acec-c5f0-4daa-832c-1f23289e2352 image **After** https://www.loom.com/share/3946546382884a33a8fef89f81faf7c2?sid=feeaf18c-2b3d-4d4f-bcdf-70335b456dd1 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/javascript/sdk/sdk.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/sdk/sdk.js b/app/javascript/sdk/sdk.js index 95d132258..0a7191812 100644 --- a/app/javascript/sdk/sdk.js +++ b/app/javascript/sdk/sdk.js @@ -135,7 +135,6 @@ export const SDK_CSS = ` .woot-widget-bubble svg { all: revert; height: 24px; - margin: 20px; width: 24px; } From e3bacd27d817528a83065614038217e29ed23ba5 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 22 Apr 2025 20:54:09 +0530 Subject: [PATCH 10/82] feat: Add custom domain to article URL if custom domain exists for the portal (#11349) Portals can have custom domains. When inserting or previewing articles, we consider the frontend URL. This PR fixes article URL generation to properly include the portal's custom domain. --- .../dashboard/helper/portalHelper.js | 49 +++++++++++++++++-- .../helper/specs/portalHelper.spec.js | 42 ++++++++++++++++ .../conversation/ConversationView.vue | 1 + .../ArticleSearch/SearchPopover.vue | 15 +++++- .../pages/PortalsArticlesEditPage.vue | 12 +++-- 5 files changed, 109 insertions(+), 10 deletions(-) diff --git a/app/javascript/dashboard/helper/portalHelper.js b/app/javascript/dashboard/helper/portalHelper.js index 09b648330..ec681ef1b 100644 --- a/app/javascript/dashboard/helper/portalHelper.js +++ b/app/javascript/dashboard/helper/portalHelper.js @@ -1,16 +1,55 @@ -export const buildPortalURL = portalSlug => { - const { hostURL, helpCenterURL } = window.chatwootConfig; +/** + * Formats a custom domain with https protocol if needed + * @param {string} customDomain - The custom domain to format + * @returns {string} Formatted domain with https protocol + */ +const formatCustomDomain = customDomain => + customDomain.startsWith('https') ? customDomain : `https://${customDomain}`; + +/** + * Gets the default base URL from configuration + * @returns {string} The default base URL + * @throws {Error} If no valid base URL is found + */ +const getDefaultBaseURL = () => { + const { hostURL, helpCenterURL } = window.chatwootConfig || {}; const baseURL = helpCenterURL || hostURL || ''; - return `${baseURL}/hc/${portalSlug}`; + + if (!baseURL) { + throw new Error('No valid base URL found in configuration'); + } + + return `${baseURL}/hc`; +}; + +/** + * Gets the base URL from configuration or custom domain + * @param {string} [customDomain] - Optional custom domain for the portal + * @returns {string} The base URL for the portal + */ +const getPortalBaseURL = customDomain => + customDomain ? formatCustomDomain(customDomain) : getDefaultBaseURL(); + +/** + * Builds a portal URL using the provided portal slug and optional custom domain + * @param {string} portalSlug - The slug identifier for the portal + * @param {string} [customDomain] - Optional custom domain for the portal + * @returns {string} The complete portal URL + * @throws {Error} If portalSlug is not provided or invalid + */ +export const buildPortalURL = (portalSlug, customDomain) => { + const baseURL = getPortalBaseURL(customDomain); + return `${baseURL}/${portalSlug}`; }; export const buildPortalArticleURL = ( portalSlug, categorySlug, locale, - articleSlug + articleSlug, + customDomain ) => { - const portalURL = buildPortalURL(portalSlug); + const portalURL = buildPortalURL(portalSlug, customDomain); return `${portalURL}/articles/${articleSlug}`; }; diff --git a/app/javascript/dashboard/helper/specs/portalHelper.spec.js b/app/javascript/dashboard/helper/specs/portalHelper.spec.js index 9c1a47255..daa188b28 100644 --- a/app/javascript/dashboard/helper/specs/portalHelper.spec.js +++ b/app/javascript/dashboard/helper/specs/portalHelper.spec.js @@ -25,5 +25,47 @@ describe('PortalHelper', () => { ).toEqual('https://help.chatwoot.com/hc/handbook/articles/article-slug'); window.chatwootConfig = {}; }); + + it('returns the correct url with custom domain', () => { + window.chatwootConfig = { + hostURL: 'https://app.chatwoot.com', + helpCenterURL: 'https://help.chatwoot.com', + }; + expect( + buildPortalArticleURL( + 'handbook', + 'culture', + 'fr', + 'article-slug', + 'custom-domain.dev' + ) + ).toEqual('https://custom-domain.dev/handbook/articles/article-slug'); + }); + + it('handles https in custom domain correctly', () => { + window.chatwootConfig = { + hostURL: 'https://app.chatwoot.com', + helpCenterURL: 'https://help.chatwoot.com', + }; + expect( + buildPortalArticleURL( + 'handbook', + 'culture', + 'fr', + 'article-slug', + 'https://custom-domain.dev' + ) + ).toEqual('https://custom-domain.dev/handbook/articles/article-slug'); + }); + + it('uses hostURL when helpCenterURL is not available', () => { + window.chatwootConfig = { + hostURL: 'https://app.chatwoot.com', + helpCenterURL: '', + }; + expect( + buildPortalArticleURL('handbook', 'culture', 'fr', 'article-slug') + ).toEqual('https://app.chatwoot.com/hc/handbook/articles/article-slug'); + }); }); }); diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue b/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue index d0e1610e9..4b86841f6 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue @@ -120,6 +120,7 @@ export default { mounted() { this.$store.dispatch('agents/get'); + this.$store.dispatch('portals/index'); this.initialize(); this.$watch('$store.state.route', () => this.initialize()); this.$watch('chatList.length', () => { diff --git a/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue b/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue index 809e58249..f54db1597 100644 --- a/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue +++ b/app/javascript/dashboard/routes/dashboard/helpcenter/components/ArticleSearch/SearchPopover.vue @@ -1,6 +1,7 @@ diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactNotes.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactNotes.vue new file mode 100644 index 000000000..66b6876b1 --- /dev/null +++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactNotes.vue @@ -0,0 +1,51 @@ + + + From fa4c1fadba1a9a43a40d3180f7acee10b024294f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 29 Apr 2025 18:12:16 +0530 Subject: [PATCH 23/82] feat: use numbers when fetching from the API (#11391) --- app/services/crm/leadsquared/setup_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/crm/leadsquared/setup_service.rb b/app/services/crm/leadsquared/setup_service.rb index df8a96456..956ff1a10 100644 --- a/app/services/crm/leadsquared/setup_service.rb +++ b/app/services/crm/leadsquared/setup_service.rb @@ -57,7 +57,7 @@ class Crm::Leadsquared::SetupService activity_id = find_or_create_activity_type(activity_type, existing_types) if activity_id.present? - activity_codes[activity_type[:setting_key]] = activity_id + activity_codes[activity_type[:setting_key]] = activity_id.to_i else Rails.logger.error "Failed to find or create activity type: #{activity_type[:name]}" end From 970e76ace8c8326108a7d1d3db09c23d11c9eedb Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Tue, 29 Apr 2025 15:33:11 -0700 Subject: [PATCH 24/82] feat: API Endpoints to update message status (#11387) - Added an api endpoint for update message status ( available only for api inboxes ) - Moved message status management to a service. - Handles case where read status arrive before delivered fixes: #10314 , #9962 --- .../conversations/messages_controller.rb | 19 +++++- .../update_message_status_job.rb | 2 +- .../facebook/send_on_facebook_service.rb | 8 +-- app/services/instagram/base_send_service.rb | 2 +- app/services/line/send_on_line_service.rb | 4 +- .../messages/status_update_service.rb | 34 ++++++++++ app/services/twilio/send_on_twilio_service.rb | 2 +- .../messages/update.json.jbuilder | 1 + app/workers/email_reply_worker.rb | 2 +- config/routes.rb | 2 +- lib/webhooks/trigger.rb | 2 +- .../conversations/messages_controller_spec.rb | 64 ++++++++++++++++++- .../messages/status_update_service_spec.rb | 46 +++++++++++++ 13 files changed, 173 insertions(+), 15 deletions(-) create mode 100644 app/services/messages/status_update_service.rb create mode 100644 app/views/api/v1/accounts/conversations/messages/update.json.jbuilder create mode 100644 spec/services/messages/status_update_service_spec.rb diff --git a/app/controllers/api/v1/accounts/conversations/messages_controller.rb b/app/controllers/api/v1/accounts/conversations/messages_controller.rb index 63226f342..67381a715 100644 --- a/app/controllers/api/v1/accounts/conversations/messages_controller.rb +++ b/app/controllers/api/v1/accounts/conversations/messages_controller.rb @@ -1,4 +1,6 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController + before_action :ensure_api_inbox, only: :update + def index @messages = message_finder.perform end @@ -11,6 +13,11 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts:: render_could_not_create_error(e.message) end + def update + Messages::StatusUpdateService.new(message, permitted_params[:status], permitted_params[:external_error]).perform + @message = message + end + def destroy ActiveRecord::Base.transaction do message.update!(content: I18n.t('conversations.messages.deleted'), content_type: :text, content_attributes: { deleted: true }) @@ -21,7 +28,9 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts:: def retry return if message.blank? - message.update!(status: :sent, content_attributes: {}) + service = Messages::StatusUpdateService.new(message, 'sent') + service.perform + message.update!(content_attributes: {}) ::SendReplyJob.perform_later(message.id) rescue StandardError => e render_could_not_create_error(e.message) @@ -56,10 +65,16 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts:: end def permitted_params - params.permit(:id, :target_language) + params.permit(:id, :target_language, :status, :external_error) end def already_translated_content_available? message.translations.present? && message.translations[permitted_params[:target_language]].present? end + + # API inbox check + def ensure_api_inbox + # Only API inboxes can update messages + render json: { error: 'Message status update is only allowed for API inboxes' }, status: :forbidden unless @conversation.inbox.api? + end end diff --git a/app/jobs/conversations/update_message_status_job.rb b/app/jobs/conversations/update_message_status_job.rb index 1e6333b41..6fcef4361 100644 --- a/app/jobs/conversations/update_message_status_job.rb +++ b/app/jobs/conversations/update_message_status_job.rb @@ -15,7 +15,7 @@ class Conversations::UpdateMessageStatusJob < ApplicationJob conversation.messages.where(status: %w[sent delivered]) .where.not(message_type: 'incoming') .where('messages.created_at <= ?', timestamp).find_each do |message| - message.update!(status: status) + Messages::StatusUpdateService.new(message, status).perform end end end diff --git a/app/services/facebook/send_on_facebook_service.rb b/app/services/facebook/send_on_facebook_service.rb index 5dde19c5a..5e9212edd 100644 --- a/app/services/facebook/send_on_facebook_service.rb +++ b/app/services/facebook/send_on_facebook_service.rb @@ -16,7 +16,7 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService rescue Facebook::Messenger::FacebookError => e # TODO : handle specific errors or else page will get disconnected handle_facebook_error(e) - message.update!(status: :failed, external_error: e.message) + Messages::StatusUpdateService.new(message, 'failed', e.message).perform end def send_message_to_facebook(delivery_params) @@ -24,7 +24,7 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService return if parsed_result.nil? if parsed_result['error'].present? - message.update!(status: :failed, external_error: external_error(parsed_result)) + Messages::StatusUpdateService.new(message, 'failed', external_error(parsed_result)).perform Rails.logger.info "Facebook::SendOnFacebookService: Error sending message to Facebook : Page - #{channel.page_id} : #{parsed_result}" end @@ -35,11 +35,11 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService result = Facebook::Messenger::Bot.deliver(delivery_params, page_id: channel.page_id) JSON.parse(result) rescue JSON::ParserError - message.update!(status: :failed, external_error: 'Facebook was unable to process this request') + Messages::StatusUpdateService.new(message, 'failed', 'Facebook was unable to process this request').perform Rails.logger.error "Facebook::SendOnFacebookService: Error parsing JSON response from Facebook : Page - #{channel.page_id} : #{result}" nil rescue Net::OpenTimeout - message.update!(status: :failed, external_error: 'Request timed out, please try again later') + Messages::StatusUpdateService.new(message, 'failed', 'Request timed out, please try again later').perform Rails.logger.error "Facebook::SendOnFacebookService: Timeout error sending message to Facebook : Page - #{channel.page_id}" nil end diff --git a/app/services/instagram/base_send_service.rb b/app/services/instagram/base_send_service.rb index dee41c23f..ff5f9216e 100644 --- a/app/services/instagram/base_send_service.rb +++ b/app/services/instagram/base_send_service.rb @@ -61,7 +61,7 @@ class Instagram::BaseSendService < Base::SendOnChannelService else external_error = external_error(parsed_response) Rails.logger.error("Instagram response: #{external_error} : #{message_content}") - message.update!(status: :failed, external_error: external_error) + Messages::StatusUpdateService.new(message, 'failed', external_error).perform nil end end diff --git a/app/services/line/send_on_line_service.rb b/app/services/line/send_on_line_service.rb index f8d704128..03ebe0ab7 100644 --- a/app/services/line/send_on_line_service.rb +++ b/app/services/line/send_on_line_service.rb @@ -14,10 +14,10 @@ class Line::SendOnLineService < Base::SendOnChannelService if response.code == '200' # If the request is successful, update the message status to delivered - message.update!(status: :delivered) + Messages::StatusUpdateService.new(message, 'delivered').perform else # If the request is not successful, update the message status to failed and save the external error - message.update!(status: :failed, external_error: external_error(parsed_json)) + Messages::StatusUpdateService.new(message, 'failed', external_error(parsed_json)).perform end end diff --git a/app/services/messages/status_update_service.rb b/app/services/messages/status_update_service.rb new file mode 100644 index 000000000..4868a201e --- /dev/null +++ b/app/services/messages/status_update_service.rb @@ -0,0 +1,34 @@ +class Messages::StatusUpdateService + attr_reader :message, :status, :external_error + + def initialize(message, status, external_error = nil) + @message = message + @status = status + @external_error = external_error + end + + def perform + return false unless valid_status_transition? + + update_message_status + end + + private + + def update_message_status + # Update status and set external_error only when failed + message.update!( + status: status, + external_error: (status == 'failed' ? external_error : nil) + ) + end + + def valid_status_transition? + return false unless Message.statuses.key?(status) + + # Don't allow changing from 'read' to 'delivered' + return false if message.read? && status == 'delivered' + + true + end +end diff --git a/app/services/twilio/send_on_twilio_service.rb b/app/services/twilio/send_on_twilio_service.rb index 3fc420bb2..5bd262759 100644 --- a/app/services/twilio/send_on_twilio_service.rb +++ b/app/services/twilio/send_on_twilio_service.rb @@ -9,7 +9,7 @@ class Twilio::SendOnTwilioService < Base::SendOnChannelService begin twilio_message = channel.send_message(**message_params) rescue Twilio::REST::TwilioError, Twilio::REST::RestError => e - message.update!(status: :failed, external_error: e.message) + Messages::StatusUpdateService.new(message, 'failed', e.message).perform end message.update!(source_id: twilio_message.sid) if twilio_message end diff --git a/app/views/api/v1/accounts/conversations/messages/update.json.jbuilder b/app/views/api/v1/accounts/conversations/messages/update.json.jbuilder new file mode 100644 index 000000000..3798b6c1f --- /dev/null +++ b/app/views/api/v1/accounts/conversations/messages/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/message', message: @message diff --git a/app/workers/email_reply_worker.rb b/app/workers/email_reply_worker.rb index 20cc70d5e..14b668637 100644 --- a/app/workers/email_reply_worker.rb +++ b/app/workers/email_reply_worker.rb @@ -11,6 +11,6 @@ class EmailReplyWorker ConversationReplyMailer.with(account: message.account).email_reply(message).deliver_now rescue StandardError => e ChatwootExceptionTracker.new(e, account: message.account).capture_exception - message.update!(status: :failed, external_error: e.message) + Messages::StatusUpdateService.new(message, 'failed', e.message).perform end end diff --git a/config/routes.rb b/config/routes.rb index 46199db12..6c71a8cc6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -98,7 +98,7 @@ Rails.application.routes.draw do post :filter end scope module: :conversations do - resources :messages, only: [:index, :create, :destroy] do + resources :messages, only: [:index, :create, :destroy, :update] do member do post :translate post :retry diff --git a/lib/webhooks/trigger.rb b/lib/webhooks/trigger.rb index f2e66f997..41b3a415d 100644 --- a/lib/webhooks/trigger.rb +++ b/lib/webhooks/trigger.rb @@ -42,7 +42,7 @@ class Webhooks::Trigger end def update_message_status(error) - message.update!(status: :failed, external_error: error.message) + Messages::StatusUpdateService.new(message, 'failed', error.message).perform end def message diff --git a/spec/controllers/api/v1/accounts/conversations/messages_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations/messages_controller_spec.rb index 94d4cf272..f7ff042e5 100644 --- a/spec/controllers/api/v1/accounts/conversations/messages_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/conversations/messages_controller_spec.rb @@ -84,7 +84,6 @@ RSpec.describe 'Conversation Messages API', type: :request do context 'when api inbox' do let(:api_channel) { create(:channel_api, account: account) } let(:api_inbox) { create(:inbox, channel: api_channel, account: account) } - let(:inbox_member) { create(:inbox_member, user: agent, inbox: api_inbox) } let(:conversation) { create(:conversation, inbox: api_inbox, account: account) } it 'reopens the conversation with new incoming message' do @@ -294,4 +293,67 @@ RSpec.describe 'Conversation Messages API', type: :request do end end end + + describe 'PATCH /api/v1/accounts/{account.id}/conversations/:conversation_id/messages/:id' do + let(:api_channel) { create(:channel_api, account: account) } + let(:api_inbox) { create(:inbox, channel: api_channel, account: account) } + let(:agent) { create(:user, account: account, role: :agent) } + let!(:conversation) { create(:conversation, inbox: api_inbox, account: account) } + let!(:message) { create(:message, conversation: conversation, account: account, status: :sent) } + + context 'when unauthenticated' do + it 'returns unauthorized' do + patch api_v1_account_conversation_message_url(account_id: account.id, conversation_id: conversation.display_id, id: message.id) + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when authenticated agent' do + context 'when agent has non-API inbox' do + let(:inbox) { create(:inbox, account: account) } + let(:agent) { create(:user, account: account, role: :agent) } + let!(:conversation) { create(:conversation, inbox: inbox, account: account) } + + before { create(:inbox_member, inbox: inbox, user: agent) } + + it 'returns forbidden' do + patch api_v1_account_conversation_message_url( + account_id: account.id, + conversation_id: conversation.display_id, + id: message.id + ), params: { status: 'failed', external_error: 'err' }, headers: agent.create_new_auth_token, as: :json + expect(response).to have_http_status(:forbidden) + end + end + + context 'when agent has API inbox' do + before { create(:inbox_member, inbox: api_inbox, user: agent) } + + it 'uses StatusUpdateService to perform status update' do + service = instance_double(Messages::StatusUpdateService) + expect(Messages::StatusUpdateService).to receive(:new) + .with(message, 'failed', 'err123') + .and_return(service) + expect(service).to receive(:perform) + patch api_v1_account_conversation_message_url( + account_id: account.id, + conversation_id: conversation.display_id, + id: message.id + ), params: { status: 'failed', external_error: 'err123' }, headers: agent.create_new_auth_token, as: :json + end + + it 'updates status to failed with external_error' do + patch api_v1_account_conversation_message_url( + account_id: account.id, + conversation_id: conversation.display_id, + id: message.id + ), params: { status: 'failed', external_error: 'err123' }, headers: agent.create_new_auth_token, as: :json + + expect(response).to have_http_status(:success) + expect(message.reload.status).to eq('failed') + expect(message.reload.external_error).to eq('err123') + end + end + end + end end diff --git a/spec/services/messages/status_update_service_spec.rb b/spec/services/messages/status_update_service_spec.rb new file mode 100644 index 000000000..ce8fc2163 --- /dev/null +++ b/spec/services/messages/status_update_service_spec.rb @@ -0,0 +1,46 @@ +require 'rails_helper' + +describe Messages::StatusUpdateService do + let(:account) { create(:account) } + let(:conversation) { create(:conversation, account: account) } + let(:message) { create(:message, conversation: conversation, account: account) } + + describe '#perform' do + context 'when status is valid' do + it 'updates the status of the message' do + service = described_class.new(message, 'delivered') + service.perform + expect(message.reload.status).to eq('delivered') + end + + it 'clears external_error when status is not failed' do + message.update!(status: 'failed', external_error: 'previous error') + service = described_class.new(message, 'delivered') + service.perform + expect(message.reload.status).to eq('delivered') + expect(message.reload.external_error).to be_nil + end + + it 'updates external_error when status is failed' do + service = described_class.new(message, 'failed', 'some error') + service.perform + expect(message.reload.status).to eq('failed') + expect(message.reload.external_error).to eq('some error') + end + end + + context 'when status is invalid' do + it 'returns false for invalid status' do + service = described_class.new(message, 'invalid_status') + expect(service.perform).to be false + end + + it 'prevents transition from read to delivered' do + message.update!(status: 'read') + service = described_class.new(message, 'delivered') + expect(service.perform).to be false + expect(message.reload.status).to eq('read') + end + end + end +end From fb6409508b916da0e1daff583f0fcfc7c54505b5 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 29 Apr 2025 15:42:15 -0700 Subject: [PATCH 25/82] feat: Allow customizing the responses, flows in Captain (#11385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ability to provide custom instructions to captain Screenshot 2025-04-28 at 6 11 43 PM --- .../dashboard/api/captain/assistant.js | 7 + .../components-next/Accordion/Accordion.vue | 39 +++ .../components-next/captain/PageLayout.vue | 19 +- .../captain/assistant/AssistantCard.vue | 7 +- .../captain/assistant/AssistantPlayground.vue | 111 +++++++ .../captain/assistant/MessageList.vue | 91 ++++++ .../assistant/EditAssistantForm.vue | 306 ++++++++++++++++++ .../i18n/locale/en/integrations.json | 48 ++- .../dashboard/captain/assistants/Edit.vue | 71 ++++ .../dashboard/captain/assistants/Index.vue | 6 +- .../dashboard/captain/captain.routes.js | 14 + config/routes.rb | 3 + .../accounts/captain/assistants_controller.rb | 26 +- enterprise/app/helpers/captain/chat_helper.rb | 27 +- .../conversation/response_builder_job.rb | 2 +- ...ox_pending_conversations_resolution_job.rb | 3 +- .../app/policies/captain/assistant_policy.rb | 4 + .../captain/llm/assistant_chat_service.rb | 2 +- .../captain/llm/system_prompts_service.rb | 4 +- .../captain/assistants_controller_spec.rb | 63 ++++ ...nding_conversations_resolution_job_spec.rb | 2 + 21 files changed, 823 insertions(+), 32 deletions(-) create mode 100644 app/javascript/dashboard/components-next/Accordion/Accordion.vue create mode 100644 app/javascript/dashboard/components-next/captain/assistant/AssistantPlayground.vue create mode 100644 app/javascript/dashboard/components-next/captain/assistant/MessageList.vue create mode 100644 app/javascript/dashboard/components-next/captain/pageComponents/assistant/EditAssistantForm.vue create mode 100644 app/javascript/dashboard/routes/dashboard/captain/assistants/Edit.vue diff --git a/app/javascript/dashboard/api/captain/assistant.js b/app/javascript/dashboard/api/captain/assistant.js index ce636e526..157eba74e 100644 --- a/app/javascript/dashboard/api/captain/assistant.js +++ b/app/javascript/dashboard/api/captain/assistant.js @@ -14,6 +14,13 @@ class CaptainAssistant extends ApiClient { }, }); } + + playground({ assistantId, messageContent, messageHistory }) { + return axios.post(`${this.url}/${assistantId}/playground`, { + message_content: messageContent, + message_history: messageHistory, + }); + } } export default new CaptainAssistant(); diff --git a/app/javascript/dashboard/components-next/Accordion/Accordion.vue b/app/javascript/dashboard/components-next/Accordion/Accordion.vue new file mode 100644 index 000000000..f75a2ef25 --- /dev/null +++ b/app/javascript/dashboard/components-next/Accordion/Accordion.vue @@ -0,0 +1,39 @@ + + + diff --git a/app/javascript/dashboard/components-next/captain/PageLayout.vue b/app/javascript/dashboard/components-next/captain/PageLayout.vue index e9fae9ca7..7355ac616 100644 --- a/app/javascript/dashboard/components-next/captain/PageLayout.vue +++ b/app/javascript/dashboard/components-next/captain/PageLayout.vue @@ -2,6 +2,7 @@ import { computed } from 'vue'; import { usePolicy } from 'dashboard/composables/usePolicy'; import Button from 'dashboard/components-next/button/Button.vue'; +import BackButton from 'dashboard/components/widgets/BackButton.vue'; import PaginationFooter from 'dashboard/components-next/pagination/PaginationFooter.vue'; import Spinner from 'dashboard/components-next/spinner/Spinner.vue'; import Policy from 'dashboard/components/policy.vue'; @@ -23,6 +24,10 @@ const props = defineProps({ type: String, default: '', }, + backUrl: { + type: [String, Object], + default: '', + }, buttonPolicy: { type: Array, default: () => [], @@ -39,6 +44,10 @@ const props = defineProps({ type: Boolean, default: false, }, + showKnowMore: { + type: Boolean, + default: true, + }, isEmpty: { type: Boolean, default: false, @@ -73,19 +82,23 @@ const handlePageChange = event => { class="flex items-start lg:items-center justify-between w-full py-6 lg:py-0 lg:h-20 gap-4 lg:gap-2 flex-col lg:flex-row" >
+ {{ headerTitle }} -
+
@@ -104,7 +117,7 @@ const handlePageChange = event => {
-
+
{