Merge branch 'develop' into feat/reporting-events-rollup

This commit is contained in:
Shivam Mishra
2026-03-09 19:32:03 +05:30
committed by GitHub
12 changed files with 49 additions and 41 deletions
@@ -1,4 +1,5 @@
<script setup>
import { vOnClickOutside } from '@vueuse/components';
import Button from 'dashboard/components-next/button/Button.vue';
defineProps({
@@ -28,7 +29,11 @@ const handleButtonClick = () => {
{{ headerTitle }}
</span>
<div
v-on-clickaway="() => emit('close')"
v-on-click-outside="[
() => emit('close'),
// This will prevent closing the modal when the editor Create link popup is open
{ ignore: ['dialog.ProseMirror-prompt-backdrop'] },
]"
class="relative group/campaign-button"
>
<Button
@@ -277,7 +277,7 @@ useKeyboardEvents(keyboardEvents);
handleClickOutside,
// Fixed and edge case https://github.com/chatwoot/chatwoot/issues/10785
// This will prevent closing the compose conversation modal when the editor Create link popup is open
{ ignore: ['div.ProseMirror-prompt'] },
{ ignore: ['dialog.ProseMirror-prompt-backdrop'] },
]"
class="relative"
:class="{
@@ -96,6 +96,17 @@ const close = () => {
isOpen.value = false;
};
// Only close if the close event originated from this dialog,
// not from a child dialog (e.g. ProseMirror prompt) bubbling up.
const handleDialogClose = e => e.target === dialogRef.value && close();
// Only close on click-outside if this dialog is the topmost one.
// If another dialog (e.g. ProseMirror prompt) is open on top, ignore.
const handleClickOutside = () => {
const dialogs = document.querySelectorAll('dialog[open]');
if (dialogs[dialogs.length - 1] === dialogRef.value) close();
};
const confirm = () => {
emit('confirm');
};
@@ -113,9 +124,9 @@ defineExpose({ open, close });
positionClass,
overflowYAuto ? 'overflow-y-auto' : 'overflow-visible',
]"
@close="close"
@close.prevent="handleDialogClose"
>
<OnClickOutside @trigger="close">
<OnClickOutside @trigger="handleClickOutside">
<form
ref="dialogContentRef"
class="flex flex-col w-full h-auto gap-6 p-6 overflow-visible text-start align-middle transition-all duration-300 ease-in-out transform bg-n-alpha-3 backdrop-blur-[100px] shadow-xl rounded-xl"
@@ -978,11 +978,15 @@ useEmitter(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, insertContentIntoEditor);
@apply overflow-auto min-h-[5rem] max-h-[7.5rem];
}
.ProseMirror-prompt-backdrop::backdrop {
@apply bg-n-alpha-black1 backdrop-blur-[4px];
}
.ProseMirror-prompt {
@apply z-[9999] bg-n-alpha-3 backdrop-blur-[100px] border border-n-strong p-6 shadow-xl rounded-xl;
@apply bg-n-alpha-3 border border-n-strong p-6 shadow-xl rounded-xl w-96 !important;
h5 {
@apply text-n-slate-12 mb-1.5;
@apply text-n-slate-12 mb-3;
}
.ProseMirror-prompt-buttons {
@@ -326,26 +326,4 @@ export default {
max-height: 7.5rem;
overflow: auto;
}
.ProseMirror-prompt {
@apply z-[9999] bg-n-alpha-3 min-w-80 backdrop-blur-[100px] border border-n-strong p-6 shadow-xl rounded-xl;
h5 {
@apply text-n-slate-12 mb-1.5;
}
.ProseMirror-prompt-buttons {
button {
@apply h-8 px-3;
&[type='submit'] {
@apply bg-n-brand text-white hover:bg-n-brand/90;
}
&[type='button'] {
@apply bg-n-slate-9/10 text-n-slate-12 hover:bg-n-slate-9/20;
}
}
}
}
</style>
@@ -6,7 +6,12 @@ const RECONNECT_INTERVAL = 1000;
class BaseActionCableConnector {
static isDisconnected = false;
constructor(app, pubsubToken, websocketHost = '') {
constructor(
app,
pubsubToken,
websocketHost = '',
presenceInterval = PRESENCE_INTERVAL
) {
const websocketURL = websocketHost ? `${websocketHost}/cable` : undefined;
this.consumer = createConsumer(websocketURL);
@@ -37,7 +42,7 @@ class BaseActionCableConnector {
setTimeout(() => {
this.subscription.updatePresence();
this.triggerPresenceInterval();
}, PRESENCE_INTERVAL);
}, presenceInterval);
};
this.triggerPresenceInterval();
}
+3 -1
View File
@@ -13,9 +13,11 @@ const isMessageInActiveConversation = (getters, message) => {
return activeConversationId && conversationId !== activeConversationId;
};
const WIDGET_PRESENCE_INTERVAL = 60000;
class ActionCableConnector extends BaseActionCableConnector {
constructor(app, pubsubToken) {
super(app, pubsubToken);
super(app, pubsubToken, '', WIDGET_PRESENCE_INTERVAL);
this.events = {
'message.created': this.onMessageCreated,
'message.updated': this.onMessageUpdated,
@@ -3,7 +3,7 @@ class Internal::RemoveStaleRedisKeysService
def perform
Rails.logger.info "Removing redis stale keys for account #{@account_id}"
range_start = (Time.zone.now - OnlineStatusTracker::PRESENCE_DURATION).to_i
range_start = (Time.zone.now - OnlineStatusTracker::CONTACT_PRESENCE_DURATION).to_i
# exclusive minimum score is specified by prefixing (
# we are clearing old records because this could clogg up the sorted set
::Redis::Alfred.zremrangebyscore(
+5 -2
View File
@@ -1,6 +1,8 @@
class OnlineStatusTracker
# NOTE: You can customise the environment variable to keep your agents/contacts as online for longer
PRESENCE_DURATION = ENV.fetch('PRESENCE_DURATION', 20).to_i.seconds
# Widget pings every 60s, so contacts need a longer presence window
CONTACT_PRESENCE_DURATION = ENV.fetch('CONTACT_PRESENCE_DURATION', 90).to_i.seconds
# presence : sorted set with timestamp as the score & object id as value
@@ -11,7 +13,8 @@ class OnlineStatusTracker
def self.get_presence(account_id, obj_type, obj_id)
connected_time = ::Redis::Alfred.zscore(presence_key(account_id, obj_type), obj_id)
connected_time && connected_time > (Time.zone.now - PRESENCE_DURATION).to_i
duration = obj_type == 'Contact' ? CONTACT_PRESENCE_DURATION : PRESENCE_DURATION
connected_time && connected_time > (Time.zone.now - duration).to_i
end
def self.presence_key(account_id, type)
@@ -39,7 +42,7 @@ class OnlineStatusTracker
end
def self.get_available_contact_ids(account_id)
range_start = (Time.zone.now - PRESENCE_DURATION).to_i
range_start = (Time.zone.now - CONTACT_PRESENCE_DURATION).to_i
# exclusive minimum score is specified by prefixing (
# we are clearing old records because this could clogg up the sorted set
::Redis::Alfred.zremrangebyscore(presence_key(account_id, 'Contact'), '-inf', "(#{range_start}")
+1 -1
View File
@@ -34,7 +34,7 @@
"@amplitude/analytics-browser": "^2.11.10",
"@breezystack/lamejs": "^1.2.7",
"@chatwoot/ninja-keys": "1.2.3",
"@chatwoot/prosemirror-schema": "1.3.6",
"@chatwoot/prosemirror-schema": "1.3.7",
"@chatwoot/utils": "^0.0.52",
"@formkit/core": "^1.6.7",
"@formkit/vue": "^1.6.7",
+5 -5
View File
@@ -23,8 +23,8 @@ importers:
specifier: 1.2.3
version: 1.2.3
'@chatwoot/prosemirror-schema':
specifier: 1.3.6
version: 1.3.6
specifier: 1.3.7
version: 1.3.7
'@chatwoot/utils':
specifier: ^0.0.52
version: 0.0.52
@@ -454,8 +454,8 @@ packages:
'@chatwoot/ninja-keys@1.2.3':
resolution: {integrity: sha512-xM8d9P5ikDMZm2WbaCTk/TW5HFauylrU3cJ75fq5je6ixKwyhl/0kZbVN/vbbZN4+AUX/OaSIn6IJbtCgIF67g==}
'@chatwoot/prosemirror-schema@1.3.6':
resolution: {integrity: sha512-sHRtWqbtiow9mVF1ixim0eGUXfhGK5tuLOdF9Vf53aepjJ+ngEiNVkxQT6FohlEOd886ZsdQxMvmI92IDaUXAQ==}
'@chatwoot/prosemirror-schema@1.3.7':
resolution: {integrity: sha512-N+Gicecp18TSEJQoRtGZXkp8R+kC0iPSms8ezu1k8U+ySY9FAENzFQQ1rBVSSC4hDFwb9/EbSI9IFqDjHGds7g==}
'@chatwoot/utils@0.0.52':
resolution: {integrity: sha512-e57uVqyVW4tj1gql4YJPNMykqMJPkETn5Y9AmHdhc6Y7oxDXfRXBq27fZrrDadLkZdn5RYVCZjfIhXOumyYv2Q==}
@@ -4999,7 +4999,7 @@ snapshots:
hotkeys-js: 3.8.7
lit: 2.2.6
'@chatwoot/prosemirror-schema@1.3.6':
'@chatwoot/prosemirror-schema@1.3.7':
dependencies:
markdown-it-sup: 2.0.0
prosemirror-commands: 1.6.0
+1 -1
View File
@@ -42,7 +42,7 @@ describe OnlineStatusTracker do
described_class.update_presence(account.id, 'Contact', online_contact.id)
# creating a stale record for offline contact presence
Redis::Alfred.zadd(format(Redis::Alfred::ONLINE_PRESENCE_CONTACTS, account_id: account.id),
(Time.zone.now - (OnlineStatusTracker::PRESENCE_DURATION + 20)).to_i, offline_contact.id)
(Time.zone.now - (OnlineStatusTracker::CONTACT_PRESENCE_DURATION + 20)).to_i, offline_contact.id)
end
it 'returns only the online contact ids with presence' do