From d45527b851843f4c40822019eab63c720fd22f14 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:00:15 +0530 Subject: [PATCH] feat: Retry failed messages within 24h (#12436) Fixes [CW-5540](https://linear.app/chatwoot/issue/CW-5540/feat-allow-retry-a-failed-message-sendout), https://github.com/chatwoot/chatwoot/issues/12333 ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Screenshot image ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth --- .../components-next/message/Message.vue | 3 + .../components-next/message/MessageError.vue | 19 ++- .../components-next/message/MessageList.vue | 3 + .../widgets/conversation/MessagesView.vue | 7 + .../shared/helpers/specs/timeHelper.spec.js | 147 ++++++++++++++++++ app/javascript/shared/helpers/timeHelper.js | 23 +++ 6 files changed, 200 insertions(+), 2 deletions(-) diff --git a/app/javascript/dashboard/components-next/message/Message.vue b/app/javascript/dashboard/components-next/message/Message.vue index 586a4b4cb..dd655d0cc 100644 --- a/app/javascript/dashboard/components-next/message/Message.vue +++ b/app/javascript/dashboard/components-next/message/Message.vue @@ -131,6 +131,8 @@ const props = defineProps({ sourceId: { type: String, default: '' }, // eslint-disable-line vue/no-unused-properties }); +const emit = defineEmits(['retry']); + const contextMenuPosition = ref({}); const showBackgroundHighlight = ref(false); const showContextMenu = ref(false); @@ -524,6 +526,7 @@ provideMessageContext({ class="[grid-area:meta]" :class="flexOrientationClass" :error="contentAttributes.externalError" + @retry="emit('retry')" />
diff --git a/app/javascript/dashboard/components-next/message/MessageError.vue b/app/javascript/dashboard/components-next/message/MessageError.vue index d3a03e746..cd17c1e3f 100644 --- a/app/javascript/dashboard/components-next/message/MessageError.vue +++ b/app/javascript/dashboard/components-next/message/MessageError.vue @@ -1,16 +1,22 @@ diff --git a/app/javascript/dashboard/components-next/message/MessageList.vue b/app/javascript/dashboard/components-next/message/MessageList.vue index 44b317c56..4c4fe1a1d 100644 --- a/app/javascript/dashboard/components-next/message/MessageList.vue +++ b/app/javascript/dashboard/components-next/message/MessageList.vue @@ -37,6 +37,8 @@ const props = defineProps({ }, }); +const emit = defineEmits(['retry']); + const allMessages = computed(() => { return useCamelCase(props.messages, { deep: true }); }); @@ -113,6 +115,7 @@ const getInReplyToMessage = parentMessage => { :inbox-supports-reply-to="inboxSupportsReplyTo" :current-user-id="currentUserId" data-clarity-mask="True" + @retry="emit('retry', message)" /> diff --git a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue index 1f850cd74..3cb46c05f 100644 --- a/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue +++ b/app/javascript/dashboard/components/widgets/conversation/MessagesView.vue @@ -4,6 +4,7 @@ import { ref, provide } from 'vue'; import { useConfig } from 'dashboard/composables/useConfig'; import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents'; import { useAI } from 'dashboard/composables/useAI'; +import { useSnakeCase } from 'dashboard/composables/useTransformKeys'; // components import ReplyBox from './ReplyBox.vue'; @@ -437,6 +438,11 @@ export default { makeMessagesRead() { this.$store.dispatch('markMessagesRead', { id: this.currentChat.id }); }, + async handleMessageRetry(message) { + if (!message) return; + const payload = useSnakeCase(message); + await this.$store.dispatch('sendMessageWithData', payload); + }, }, }; @@ -465,6 +471,7 @@ export default { :is-an-email-channel="isAnEmailChannel" :inbox-supports-reply-to="inboxSupportsReplyTo" :messages="getMessages" + @retry="handleMessageRetry" >