Skip to content

New automations - #469

Merged
abhinavxd merged 13 commits into
mainfrom
new-automations
Aug 7, 2026
Merged

abhinavxd merged 13 commits into
mainfrom
new-automations

Conversation

@abhinavxd

@abhinavxd abhinavxd commented Aug 5, 2026 •

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added automation actions for notifying recipients, snoozing conversations, and triggering specific webhooks.
    • Added filters for previous status, priority, assignee, and team values.
    • Added “starts with” text filtering and expanded text, recipient, and webhook controls.
    • Added webhook selection and event configuration in automation setup.
  • Bug Fixes
    • Prevented duplicate automation runs when conversation updates do not change relevant values.
    • Excluded automated messages from triggering additional outgoing-message automations.
    • Added validation for invalid snooze durations and inactive webhook targets.

# Conflicts:
#	i18n/da-DK.json
#	i18n/de-DE.json
#	i18n/es-ES.json
#	i18n/fa-IR.json
#	i18n/fr-FR.json
#	i18n/it-IT.json
#	i18n/ja-JP.json
#	i18n/mr-IN.json
#	i18n/pt-BR.json
#	internal/conversation/conversation.go
An automation rule that listens on an event and then writes the same field
re-fires its own event, so the rule matches again and loops forever. Status
already returned early on a no-op write, but priority and user assignment did
not, so those two could spin a worker doing a DB write per lap. Add the same
unchanged-value guard to both.

Suppression while the engine applies actions was a plain flag in a sync.Map,
so with more than one worker on the same conversation the first one to finish
deleted the key while the other was still applying actions. Make it a refcount
so each worker releases only its own claim.

The starts with operator is only implemented by the automation evaluator, not
the SQL filter builder, so give automation text fields their own operator list
instead of adding it to the shared one.

Also switch the notify action to its own recipients field type, fix the snooze
duration hint since the backend only takes Go duration units, trim and
lowercase notify recipient entries, and treat a missing previous value as no
match rather than an empty string.
@abhinavxd abhinavxd self-assigned this Aug 5, 2026
@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

This change adds previous-value automation filters, starts-with matching, notification, snooze, and targeted webhook actions. It updates conversation event propagation, webhook delivery, frontend automation controls, translations, and evaluator tests.

Changes

Automation extensions

Layer / File(s) Summary
Previous-value rule evaluation
internal/automation/models/models.go, internal/automation/automation.go, internal/automation/evaluator.go, internal/automation/evaluator_test.go
Automation rules receive previous status, priority, assignment, and team values. The evaluator supports starts_with and suppresses recursive automation execution. Tests cover previous values, missing values, transitions, and action dispatch.
Targeted webhook delivery
internal/webhook/..., cmd/handlers.go, cmd/webhooks.go, frontend/apps/main/src/api/index.js, frontend/apps/main/src/stores/webhook.js
The API and frontend store expose compact webhook data. Webhook delivery can target one webhook by ID. Untargeted tasks retain event-based fan-out.
Conversation action execution
internal/conversation/conversation.go, internal/conversation/message.go, internal/conversation/models/models.go
Conversation events propagate previous values. Snooze, notification, and targeted webhook actions validate inputs and execute through conversation services. Automated messages do not trigger outgoing-message automation.
Automation configuration UI
frontend/apps/main/src/constants/*, frontend/apps/main/src/composables/*, frontend/apps/main/src/features/admin/automation/*, frontend/apps/main/src/views/admin/automations/*, i18n/en-US.json
The frontend adds automation text operators, previous-value filters, recipient inputs, snooze controls, webhook selection, text inputs, webhook loading, notification validation, and related translations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Conversation
  participant AutomationEngine
  participant Evaluator
  participant WebhookManager
  participant NotificationDispatcher
  Conversation->>AutomationEngine: submit event and previous values
  AutomationEngine->>Evaluator: evaluate conversation rules
  Evaluator->>Conversation: execute snooze action
  Evaluator->>WebhookManager: trigger selected webhook
  WebhookManager-->>Conversation: deliver webhook event
  Conversation->>NotificationDispatcher: send resolved recipients notification
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the main change: adding new automation capabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch new-automations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
internal/automation/evaluator_test.go (1)

1454-1479: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the suppression refcount.

The new tests cover previous values, the starts-with operator, and action passthrough. They do not cover suppress, unsuppress, or isSuppressed. That logic is the riskiest part of this change, because a leaked refcount silently disables automation for a conversation.

Add a test that asserts isSuppressed returns true during ApplyAction and false after evalConversationRules returns. A mock ApplyAction that calls engine.isSuppressed(conversation.UUID) makes this assertion direct.

Also consider one case for RuleOperatorStartsWith with CaseSensitiveMatch: true, since only the default case-insensitive path is covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/automation/evaluator_test.go` around lines 1454 - 1479, Extend
TestStartsWithOperator coverage with a mock ApplyAction that records
engine.isSuppressed(conversation.UUID), then assert suppression is true during
action execution and false after evalConversationRules returns. Also add a
RuleOperatorStartsWith case using CaseSensitiveMatch: true to verify
case-sensitive matching behavior.
internal/automation/automation.go (1)

306-309: 🎯 Functional Correctness | 🔵 Trivial | 🏗️ Heavy lift

Do not drop non-automation update events behind per-conversation suppression.

EvaluateConversationUpdateRules is called from conversation status, assignment, and priority writes; each of those paths is skipped while any automation worker holds conversations/[uuid]. Those legitimate concurrent updates are lost instead of deferred.

Restrict the suppression to automation-originated action context. Since ApplyAction falls back to umodels.User{}, the automation actor is identifiable at the update site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/automation/automation.go` around lines 306 - 309, Update
EvaluateConversationUpdateRules so per-conversation suppression applies only to
automation-originated updates, not ordinary status, assignment, or priority
writes. Use the actor context at the update site to identify automation actions,
including the umodels.User{} fallback produced by ApplyAction, and bypass
isSuppressed for non-automation actors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/conversation/conversation.go`:
- Around line 1513-1524: Reject non-positive webhook IDs in the targeted
delivery validation around strconv.Atoi in internal/conversation/conversation.go
lines 1513-1524 by returning an error unless webhookID > 0. Add defense-in-depth
handling in internal/webhook/webhook.go lines 263-279 to reject or log and drop
non-positive IDs before delivery; both sites require changes, while preserving
valid targeted delivery.

In `@internal/conversation/models/models.go`:
- Around line 347-355: Update the automation path in ApplyAction for
ActionSendPrivateNote so the created private note includes metadata with
is_automated set to true, or extend SendPrivateNote to accept and apply that
flag. Ensure Message.IsAutomated() recognizes these notes so the existing
EvaluateConversationUpdateRulesByID gate excludes them.

---

Nitpick comments:
In `@internal/automation/automation.go`:
- Around line 306-309: Update EvaluateConversationUpdateRules so
per-conversation suppression applies only to automation-originated updates, not
ordinary status, assignment, or priority writes. Use the actor context at the
update site to identify automation actions, including the umodels.User{}
fallback produced by ApplyAction, and bypass isSuppressed for non-automation
actors.

In `@internal/automation/evaluator_test.go`:
- Around line 1454-1479: Extend TestStartsWithOperator coverage with a mock
ApplyAction that records engine.isSuppressed(conversation.UUID), then assert
suppression is true during action execution and false after
evalConversationRules returns. Also add a RuleOperatorStartsWith case using
CaseSensitiveMatch: true to verify case-sensitive matching behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 40ded46c-9cfb-4e08-acae-1702e6b2f534

📥 Commits

Reviewing files that changed from the base of the PR and between a482f07 and aa0deed.

📒 Files selected for processing (13)
  • frontend/apps/main/src/composables/useConversationFilters.js
  • frontend/apps/main/src/constants/filterConfig.js
  • frontend/apps/main/src/features/admin/automation/ActionBox.vue
  • frontend/apps/main/src/stores/webhook.js
  • i18n/en-US.json
  • internal/automation/automation.go
  • internal/automation/evaluator.go
  • internal/automation/evaluator_test.go
  • internal/automation/models/models.go
  • internal/conversation/conversation.go
  • internal/conversation/message.go
  • internal/conversation/models/models.go
  • internal/webhook/webhook.go

Comment thread internal/conversation/conversation.go
Comment thread internal/conversation/models/models.go
strconv.Atoi accepts "0" and negative values, and the delivery worker treats a
non-positive WebhookID as a fan out to every subscriber of the event. So a
malformed target value sent the conversation payload to unintended webhooks.
Reject it in the action and drop it in TriggerWebhook as well.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/webhook/webhook.go (1)

338-351: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: External

Reachability path
● Entry
  cmd/webhooks.go:19
  GetAllCompact
│
▼
● Sink
  internal/webhook/webhook.go

Block redirect hops for signed webhook requests.

Manager.httpClient has no CheckRedirect, so a 307/308 webhook response can resend the POST payload to another host. The signature is added manually in deliverSingleWebhook, so it is not automatically stripped across origins; a redirect policy should either disable redirects for these POSTs or validate every hop before allowing it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/webhook/webhook.go` around lines 338 - 351, Update the HTTP client
used by deliverSingleWebhook to prevent signed webhook POST requests from
following redirects, preferably by configuring Manager.httpClient with a
CheckRedirect policy that returns http.ErrUseLastResponse or otherwise rejects
redirect hops. Preserve normal delivery and error handling while ensuring
307/308 responses cannot resend the signed payload to another host.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/webhook/webhook.go`:
- Around line 338-351: Update the HTTP client used by deliverSingleWebhook to
prevent signed webhook POST requests from following redirects, preferably by
configuring Manager.httpClient with a CheckRedirect policy that returns
http.ErrUseLastResponse or otherwise rejects redirect hops. Preserve normal
delivery and error handling while ensuring 307/308 responses cannot resend the
signed payload to another host.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 780fceae-d7a3-42f8-9c08-6dc60decbebe

📥 Commits

Reviewing files that changed from the base of the PR and between c4270f4 and 89b40f9.

📒 Files selected for processing (11)
  • cmd/handlers.go
  • cmd/webhooks.go
  • frontend/apps/main/src/api/index.js
  • frontend/apps/main/src/features/admin/automation/ActionBox.vue
  • frontend/apps/main/src/stores/webhook.js
  • i18n/en-US.json
  • internal/automation/automation.go
  • internal/conversation/conversation.go
  • internal/webhook/models/models.go
  • internal/webhook/queries.sql
  • internal/webhook/webhook.go
💤 Files with no reviewable changes (1)
  • internal/automation/automation.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • i18n/en-US.json
  • frontend/apps/main/src/features/admin/automation/ActionBox.vue
  • internal/conversation/conversation.go

The notify action only created a fixed in-app notification and recipients
were typed as a raw team:<id> / user:<id> DSL where typos silently notified
nobody. Now:

- recipients are picked by name (assignee, assigned team, any team or agent)
- admin writes the subject and message; both show in the bell notification
  and go out as an email (message plus conversation link, wrapped in the
  default outgoing email template, so nothing is hardcoded in English)
- the action serializes as typed fields {subject, message, recipients}
  instead of a positional value array

Also: previous_* condition fields are hidden for time triggers and populated
on message events (previous = current) so those rules can actually match,
action row widths now follow the RuleBox pattern (fixed type select, value
widgets fill the row), and three new i18n keys merged into existing globals.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/conversation/message.go (1)

1391-1396: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Capture the previous conversation before hook updates.

ReOpenConversation runs before this fetch. PreviousValues(conversation) can therefore contain status=open instead of the prior status. An incoming-message rule cannot match previous_status=resolved after a customer reply reopens the conversation.

Fetch a pre-hook snapshot before UpdateConversationWaitingSince and ReOpenConversation. Pass amodels.PreviousValues(previousConversation) at Line 1396. Add a regression test for a resolved conversation that receives an incoming message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/conversation/message.go` around lines 1391 - 1396, Capture the
conversation snapshot before UpdateConversationWaitingSince and
ReOpenConversation execute, retain it through the incoming-message hook flow,
and pass amodels.PreviousValues(previousConversation) to
EvaluateConversationUpdateRules instead of deriving previous values from the
post-reopen conversation. Add a regression test covering a resolved conversation
receiving an incoming message and matching previous_status=resolved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/conversation/message.go`:
- Around line 1391-1396: Capture the conversation snapshot before
UpdateConversationWaitingSince and ReOpenConversation execute, retain it through
the incoming-message hook flow, and pass
amodels.PreviousValues(previousConversation) to EvaluateConversationUpdateRules
instead of deriving previous values from the post-reopen conversation. Add a
regression test covering a resolved conversation receiving an incoming message
and matching previous_status=resolved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c75e6264-dc88-46f5-a849-73605d4fbd4a

📥 Commits

Reviewing files that changed from the base of the PR and between 89b40f9 and ae0b6ee.

📒 Files selected for processing (10)
  • frontend/apps/main/src/composables/useConversationFilters.js
  • frontend/apps/main/src/features/admin/automation/ActionBox.vue
  • frontend/apps/main/src/features/admin/automation/RuleBox.vue
  • frontend/apps/main/src/views/admin/automations/CreateOrEditRule.vue
  • i18n/en-US.json
  • internal/automation/automation.go
  • internal/automation/evaluator_test.go
  • internal/automation/models/models.go
  • internal/conversation/conversation.go
  • internal/conversation/message.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/automation/automation.go
  • frontend/apps/main/src/composables/useConversationFilters.js
  • internal/automation/evaluator_test.go

Automation update events now carry the actor. A suppressed conversation drops only system-user events (the engine's own in-flight actions), so an agent updating the conversation during that window is evaluated instead of silently dropped. Autoassigner events still trigger automations since they arrive outside the suppress window.
@abhinavxd

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/automation/automation.go`:
- Around line 333-340: Update EvaluateConversationUpdateRulesByID so it does not
call models.PreviousValues(conversation) on the post-update conversation; pass
nil when no pre-update snapshot is available, or extend the method to accept and
forward the actual pre-update values map when callers provide one. Preserve
conversation fetching and rule evaluation, and add a regression test covering
previous_status, previous_priority, or assignment filters through this method.

In `@internal/conversation/message.go`:
- Line 1396: Update the incoming-message flow around ReOpenConversation and
EvaluateConversationUpdateRules to load or retain the conversation snapshot
before reopening it, then pass that pre-reopen snapshot to
amodels.PreviousValues at the rule-evaluation call. Keep the reopened
conversation for current-state processing while ensuring previous values reflect
the state before reopening.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c1443f04-81e8-454b-82ac-17920df17bb7

📥 Commits

Reviewing files that changed from the base of the PR and between a482f07 and e67aa28.

📒 Files selected for processing (21)
  • cmd/handlers.go
  • cmd/main.go
  • cmd/webhooks.go
  • frontend/apps/main/src/api/index.js
  • frontend/apps/main/src/composables/useConversationFilters.js
  • frontend/apps/main/src/constants/filterConfig.js
  • frontend/apps/main/src/features/admin/automation/ActionBox.vue
  • frontend/apps/main/src/features/admin/automation/RuleBox.vue
  • frontend/apps/main/src/stores/webhook.js
  • frontend/apps/main/src/views/admin/automations/CreateOrEditRule.vue
  • i18n/en-US.json
  • internal/automation/automation.go
  • internal/automation/evaluator.go
  • internal/automation/evaluator_test.go
  • internal/automation/models/models.go
  • internal/conversation/conversation.go
  • internal/conversation/message.go
  • internal/conversation/models/models.go
  • internal/webhook/models/models.go
  • internal/webhook/queries.sql
  • internal/webhook/webhook.go

Comment thread internal/automation/automation.go
Comment thread internal/conversation/message.go Outdated
@abhinavxd
abhinavxd merged commit 64e7430 into main Aug 7, 2026
5 checks passed
@abhinavxd
abhinavxd deleted the new-automations branch August 7, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant