From 20dc4f169c4b38d319171b1aebecf1b7e95cd946 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Wed, 2 Jul 2025 03:40:39 +0530 Subject: [PATCH 1/6] fix: Do not enforce max_limits if inbox_auto_assignment is disabled (#11849) # Pull Request Template ## Description The `auto_assignment` max_limits were being enforced even if the inbox level `auto_assign` feature was disabled. This was because the enterprise method was not verifying the feature status before returning the available agents. ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? - Tested locally - Added Specs ## 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 - [x] 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 - [x] Any dependent changes have been merged and published in downstream modules --- enterprise/app/models/enterprise/inbox.rb | 2 ++ spec/enterprise/models/conversation_spec.rb | 31 +++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/enterprise/app/models/enterprise/inbox.rb b/enterprise/app/models/enterprise/inbox.rb index 4cd627e41..0ae21ce00 100644 --- a/enterprise/app/models/enterprise/inbox.rb +++ b/enterprise/app/models/enterprise/inbox.rb @@ -1,5 +1,7 @@ module Enterprise::Inbox def member_ids_with_assignment_capacity + return super unless enable_auto_assignment? + max_assignment_limit = auto_assignment_config['max_assignment_limit'] overloaded_agent_ids = max_assignment_limit.present? ? get_agent_ids_over_assignment_limit(max_assignment_limit) : [] super - overloaded_agent_ids diff --git a/spec/enterprise/models/conversation_spec.rb b/spec/enterprise/models/conversation_spec.rb index 9585274dd..50d9ee993 100644 --- a/spec/enterprise/models/conversation_spec.rb +++ b/spec/enterprise/models/conversation_spec.rb @@ -74,4 +74,35 @@ RSpec.describe Conversation, type: :model do end end end + + describe 'assignment capacity limits' do + describe 'team assignment with inbox auto-assignment disabled' do + let(:account) { create(:account) } + let(:inbox) { create(:inbox, account: account, enable_auto_assignment: false, auto_assignment_config: { max_assignment_limit: 1 }) } + let(:team) { create(:team, account: account, allow_auto_assign: true) } + let!(:agent1) { create(:user, account: account, role: :agent, auto_offline: false) } + let!(:agent2) { create(:user, account: account, role: :agent, auto_offline: false) } + + before do + create(:inbox_member, inbox: inbox, user: agent1) + create(:inbox_member, inbox: inbox, user: agent2) + create(:team_member, team: team, user: agent1) + create(:team_member, team: team, user: agent2) + # Both agents are over the limit (simulate by assigning open conversations) + create_list(:conversation, 2, inbox: inbox, assignee: agent1, status: :open) + create_list(:conversation, 2, inbox: inbox, assignee: agent2, status: :open) + end + + it 'does not enforce max_assignment_limit for team assignment when inbox auto-assignment is disabled' do + conversation = create(:conversation, inbox: inbox, account: account, assignee: nil, status: :open) + + # Assign to team to trigger the assignment logic + conversation.update!(team: team) + + # Should assign to a team member even if they are over the limit + expect(conversation.reload.assignee).to be_present + expect([agent1, agent2]).to include(conversation.reload.assignee) + end + end + end end From ef591f848aa3a886773b8fb64f3acf6984a39f88 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 1 Jul 2025 21:31:50 -0700 Subject: [PATCH 2/6] chore: Add open conversation option (#11828) Added conversation_status, assignee_id, team_id, and priority to the message_created event to allow users to build automations based on conversation details. Also introduced a new open_conversation action. --------- Co-authored-by: Muhsin Keloth --- .../dashboard/helper/validations.js | 1 + .../dashboard/i18n/locale/en/automation.json | 3 +- .../settings/automation/constants.js | 34 ++++++++++++++++++- app/models/automation_rule.rb | 2 +- app/services/action_service.rb | 4 +++ .../automation_rule_listener_spec.rb | 10 ++++++ spec/services/action_service_spec.rb | 11 ++++++ .../conditions_filter_service_spec.rb | 23 +++++++++++++ 8 files changed, 85 insertions(+), 3 deletions(-) diff --git a/app/javascript/dashboard/helper/validations.js b/app/javascript/dashboard/helper/validations.js index 58f3faa1a..edebc4656 100644 --- a/app/javascript/dashboard/helper/validations.js +++ b/app/javascript/dashboard/helper/validations.js @@ -126,6 +126,7 @@ const validateSingleAction = action => { 'snooze_conversation', 'resolve_conversation', 'remove_assigned_team', + 'open_conversation', ]; if ( diff --git a/app/javascript/dashboard/i18n/locale/en/automation.json b/app/javascript/dashboard/i18n/locale/en/automation.json index 86ec0b58b..c5dd4a514 100644 --- a/app/javascript/dashboard/i18n/locale/en/automation.json +++ b/app/javascript/dashboard/i18n/locale/en/automation.json @@ -147,7 +147,8 @@ "SEND_ATTACHMENT": "Send Attachment", "SEND_MESSAGE": "Send a Message", "CHANGE_PRIORITY": "Change Priority", - "ADD_SLA": "Add SLA" + "ADD_SLA": "Add SLA", + "OPEN_CONVERSATION": "Open conversation" }, "ATTRIBUTES": { "MESSAGE_TYPE": "Message Type", diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js index 18468e3ad..1b0f0599f 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js @@ -32,6 +32,30 @@ export const AUTOMATIONS = { inputType: 'multi_select', filterOperators: OPERATOR_TYPES_1, }, + { + key: 'status', + name: 'STATUS', + inputType: 'multi_select', + filterOperators: OPERATOR_TYPES_1, + }, + { + key: 'assignee_id', + name: 'ASSIGNEE_NAME', + inputType: 'search_select', + filterOperators: OPERATOR_TYPES_3, + }, + { + key: 'team_id', + name: 'TEAM_NAME', + inputType: 'search_select', + filterOperators: OPERATOR_TYPES_3, + }, + { + key: 'priority', + name: 'PRIORITY', + inputType: 'multi_select', + filterOperators: OPERATOR_TYPES_1, + }, { key: 'conversation_language', name: 'CONVERSATION_LANGUAGE', @@ -82,7 +106,10 @@ export const AUTOMATIONS = { key: 'snooze_conversation', name: 'SNOOZE_CONVERSATION', }, - + { + key: 'open_conversation', + name: 'OPEN_CONVERSATION', + }, { key: 'resolve_conversation', name: 'RESOLVE_CONVERSATION', @@ -508,6 +535,11 @@ export const AUTOMATION_ACTION_TYPES = [ label: 'RESOLVE_CONVERSATION', inputType: null, }, + { + key: 'open_conversation', + label: 'OPEN_CONVERSATION', + inputType: null, + }, { key: 'send_webhook_event', label: 'SEND_WEBHOOK_EVENT', diff --git a/app/models/automation_rule.rb b/app/models/automation_rule.rb index 258c5c0a3..c8f5a180d 100644 --- a/app/models/automation_rule.rb +++ b/app/models/automation_rule.rb @@ -41,7 +41,7 @@ class AutomationRule < ApplicationRecord def actions_attributes %w[send_message add_label remove_label send_email_to_team assign_team assign_agent send_webhook_event mute_conversation - send_attachment change_status resolve_conversation snooze_conversation change_priority send_email_transcript].freeze + send_attachment change_status resolve_conversation open_conversation snooze_conversation change_priority send_email_transcript].freeze end def file_base_data diff --git a/app/services/action_service.rb b/app/services/action_service.rb index 4f33a302b..a50b11193 100644 --- a/app/services/action_service.rb +++ b/app/services/action_service.rb @@ -18,6 +18,10 @@ class ActionService @conversation.resolved! end + def open_conversation(_params) + @conversation.open! + end + def change_status(status) @conversation.update!(status: status[0]) end diff --git a/spec/listeners/automation_rule_listener_spec.rb b/spec/listeners/automation_rule_listener_spec.rb index e0103b19a..c08b6e402 100644 --- a/spec/listeners/automation_rule_listener_spec.rb +++ b/spec/listeners/automation_rule_listener_spec.rb @@ -171,6 +171,16 @@ describe AutomationRuleListener do listener.message_created(event) expect(AutomationRules::ActionService).not_to have_received(:new).with(automation_rule, account, conversation) end + + it 'passes conversation attributes to conditions filter service' do + conversation.update!(status: :open, priority: :high) + listener.message_created(event) + expect(AutomationRules::ConditionsFilterService).to have_received(:new).with( + automation_rule, + conversation, + { message: message, changed_attributes: { content: %w[nil Hi] } } + ) + end end end end diff --git a/spec/services/action_service_spec.rb b/spec/services/action_service_spec.rb index c28761844..95b1e6ecf 100644 --- a/spec/services/action_service_spec.rb +++ b/spec/services/action_service_spec.rb @@ -14,6 +14,17 @@ describe ActionService do end end + describe '#open_conversation' do + let(:conversation) { create(:conversation, status: :resolved) } + let(:action_service) { described_class.new(conversation) } + + it 'opens the conversation' do + expect(conversation.status).to eq('resolved') + action_service.open_conversation(nil) + expect(conversation.reload.status).to eq('open') + end + end + describe '#change_priority' do let(:conversation) { create(:conversation) } let(:action_service) { described_class.new(conversation) } diff --git a/spec/services/automation_rules/conditions_filter_service_spec.rb b/spec/services/automation_rules/conditions_filter_service_spec.rb index ec9f52062..7082d31b1 100644 --- a/spec/services/automation_rules/conditions_filter_service_spec.rb +++ b/spec/services/automation_rules/conditions_filter_service_spec.rb @@ -110,6 +110,29 @@ RSpec.describe AutomationRules::ConditionsFilterService do expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(false) end end + + context 'when filtering messages based on conversation attributes' do + let(:conversation) { create(:conversation, account: account, status: :open, priority: :high) } + let(:message) do + create(:message, account: account, conversation: conversation, content: 'Test message', + inbox: conversation.inbox, message_type: :incoming) + end + + it 'will return true when conversation status matches' do + rule.update(conditions: [{ 'values': ['open'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' }]) + expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(true) + end + + it 'will return false when conversation status does not match' do + rule.update(conditions: [{ 'values': ['resolved'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' }]) + expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(false) + end + + it 'will return true when conversation priority matches' do + rule.update(conditions: [{ 'values': ['high'], 'attribute_key': 'priority', 'query_operator': nil, 'filter_operator': 'equal_to' }]) + expect(described_class.new(rule, conversation, { message: message, changed_attributes: {} }).perform).to be(true) + end + end end end end From cdb1dc6714ea718b1b6186e0d6416c4edb949a3e Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:59:15 +0530 Subject: [PATCH 3/6] chore: Disable drag on macro item when preview is open (#11847) --- .../dashboard/routes/dashboard/conversation/Macros/List.vue | 1 - .../dashboard/routes/dashboard/conversation/Macros/MacroItem.vue | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue b/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue index 9e7f92ae1..7903ae751 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/Macros/List.vue @@ -104,7 +104,6 @@ onMounted(() => { :key="element.id" :macro="element" :conversation-id="conversationId" - class="drag-handle cursor-grab" /> diff --git a/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue b/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue index 2b7637ee2..b6de05d51 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/Macros/MacroItem.vue @@ -54,6 +54,7 @@ const closeMacroPreview = () => {