feat: time based automation (#15022)

## Description

Add automations that trigger based on how long a conversation has been
in a given state.

## Type of change

- [ ] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

- UI flows 
- Specs (https://github.com/chatwoot/chatwoot/pull/15021) 

## 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: Sony Mathew <sony@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
This commit is contained in:
Tanmay Deep Sharma
2026-07-30 16:04:17 +05:30
committed by GitHub
parent 38a317962b
commit 0c606babea
40 changed files with 2429 additions and 250 deletions

View File

@@ -0,0 +1,5 @@
class AddExecutionDelayToAutomationRules < ActiveRecord::Migration[7.0]
def change
add_column :automation_rules, :execution_delay, :integer
end
end

View File

@@ -0,0 +1,5 @@
class AddStatusChangedAtToConversations < ActiveRecord::Migration[7.0]
def change
add_column :conversations, :status_changed_at, :datetime
end
end

View File

@@ -0,0 +1,21 @@
class CreateAutomationRulePendingExecutions < ActiveRecord::Migration[7.0]
def change
create_table :automation_rule_pending_executions do |t|
t.references :automation_rule, null: false
t.references :conversation, null: false
t.references :account, null: false
t.bigint :message_id
t.datetime :due_at, null: false
t.string :episode_key, null: false
t.integer :status, null: false, default: 0
t.string :skip_reason
t.timestamps
end
add_index :automation_rule_pending_executions, [:status, :due_at]
add_index :automation_rule_pending_executions,
[:automation_rule_id, :conversation_id, :episode_key],
unique: true, name: 'uniq_automation_pending_execution_episode'
end
end

View File

@@ -0,0 +1,8 @@
class AddStatusUpdatedAtIndexToAutomationRulePendingExecutions < ActiveRecord::Migration[7.0]
def change
# Stale reclaim, the abandoned-row alarm and the terminal purge all filter on status + updated_at;
# the (status, due_at) index does not serve them, and terminal rows linger for 30 days.
add_index :automation_rule_pending_executions, [:status, :updated_at],
name: 'index_automation_pending_executions_on_status_and_updated_at'
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2026_07_28_000001) do
ActiveRecord::Schema[7.1].define(version: 2026_07_29_051500) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -278,6 +278,25 @@ ActiveRecord::Schema[7.1].define(version: 2026_07_28_000001) do
t.index ["user_id", "user_type"], name: "user_index"
end
create_table "automation_rule_pending_executions", force: :cascade do |t|
t.bigint "automation_rule_id", null: false
t.bigint "conversation_id", null: false
t.bigint "account_id", null: false
t.bigint "message_id"
t.datetime "due_at", null: false
t.string "episode_key", null: false
t.integer "status", default: 0, null: false
t.string "skip_reason"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_automation_rule_pending_executions_on_account_id"
t.index ["automation_rule_id", "conversation_id", "episode_key"], name: "uniq_automation_pending_execution_episode", unique: true
t.index ["automation_rule_id"], name: "index_automation_rule_pending_executions_on_automation_rule_id"
t.index ["conversation_id"], name: "index_automation_rule_pending_executions_on_conversation_id"
t.index ["status", "due_at"], name: "index_automation_rule_pending_executions_on_status_and_due_at"
t.index ["status", "updated_at"], name: "index_automation_pending_executions_on_status_and_updated_at"
end
create_table "automation_rules", force: :cascade do |t|
t.bigint "account_id", null: false
t.string "name", null: false
@@ -288,6 +307,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_07_28_000001) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "active", default: true, null: false
t.integer "execution_delay"
t.index ["account_id"], name: "index_automation_rules_on_account_id"
end
@@ -794,6 +814,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_07_28_000001) do
t.datetime "waiting_since"
t.text "cached_label_list"
t.bigint "assignee_agent_bot_id"
t.datetime "status_changed_at"
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"