From 1e6291616dd493c3ff07ddb40b37b4ca574551df Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Wed, 15 Jul 2026 13:00:03 +0530 Subject: [PATCH] fix(automations): harden delayed rules against races, stale edits, and unsupported configs - episode_key_for falls back to the incoming message's created_at when waiting_since is still nil (it is written after MESSAGE_CREATED dispatches), so awaiting-agent episodes no longer arm as awaiting_agent:0 and get skipped as episode_moved on the first customer message after an agent reply. - Editing a rule's trigger, conditions, or actions (not just the delay) now discards its armed pending rows so they can't fire against a definition they were never armed under; deleting rather than skipping frees the episode slot so the new definition re-arms on the next matching event. - The rule form only offers a delay when the config supports it: the delayed option is disabled with an explanation for attribute_changed conditions and for non-status conditions on conversation events, and the backend's specific error is surfaced on save instead of a generic message. --- .../dashboard/i18n/locale/en/automation.json | 6 +- .../automation/AutomationRuleForm.vue | 59 ++++++++++++++++++- .../dashboard/settings/automation/Index.vue | 4 +- app/models/automation_rule.rb | 15 +++-- .../automation_rule_pending_execution.rb | 6 +- 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/app/javascript/dashboard/i18n/locale/en/automation.json b/app/javascript/dashboard/i18n/locale/en/automation.json index 805674eba..7faf5d71f 100644 --- a/app/javascript/dashboard/i18n/locale/en/automation.json +++ b/app/javascript/dashboard/i18n/locale/en/automation.json @@ -38,7 +38,11 @@ "DAYS": "Days" }, "ERROR": "Delay must be between 10 minutes and 30 days", - "HELP_TEXT": "Delayed rules run only if the conditions still hold when the delay ends, and apply to conversations with activity after the rule is created." + "HELP_TEXT": "Delayed rules run only if the conditions still hold when the delay ends, and apply to conversations with activity after the rule is created.", + "RESTRICTED": { + "ATTRIBUTE_CHANGED": "A delay can't be used with an \"attribute changed\" condition, because the change can't be re-checked when the delay ends.", + "CONVERSATION_NON_STATUS": "For conversation events, a delay is only available when every condition uses the Status attribute." + } }, "CONDITIONS": { "LABEL": "Conditions" diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue b/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue index 3a6cc8a8b..c8370f6c8 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/AutomationRuleForm.vue @@ -197,6 +197,30 @@ const allowsDelayedExecution = computed(() => isCloudFeatureEnabled(FEATURE_FLAGS.DELAYED_AUTOMATIONS) ); +// Mirrors the backend's execution_delay validations so we never offer an unsupported delay. +const delayRestrictionReason = computed(() => { + const conditions = automation.value?.conditions || []; + if ( + conditions.some( + condition => condition.filter_operator === 'attribute_changed' + ) + ) { + return 'ATTRIBUTE_CHANGED'; + } + if ( + eventName.value !== 'message_created' && + conditions.some( + condition => + condition.attribute_key && condition.attribute_key !== 'status' + ) + ) { + return 'CONVERSATION_NON_STATUS'; + } + return null; +}); + +const isDelaySupported = computed(() => delayRestrictionReason.value === null); + const executeMode = ref('immediate'); const delayValue = ref(4); const delayUnit = ref('HOURS'); @@ -237,6 +261,13 @@ watch([executeMode, delayInMinutes], () => { executeMode.value === 'delayed' ? delayInMinutes.value : null; }); +// Editing the event/conditions into an unsupported shape reverts the delay to immediate. +watch(isDelaySupported, supported => { + if (!supported && executeMode.value === 'delayed') { + executeMode.value = 'immediate'; + } +}); + watch( () => automation.value, () => { @@ -361,11 +392,23 @@ defineExpose({ open, close }); {{ $t('AUTOMATION.ADD.FORM.EXECUTE.IMMEDIATELY') }} -