diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.story.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.story.vue
index cd6f1d49b..20ab38d58 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.story.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.story.vue
@@ -39,7 +39,6 @@ const policyA = withCount({
description: 'Distributes conversations evenly among available agents',
assignmentOrder: 'round_robin',
conversationPriority: 'high',
- enabled: true,
inboxes: [mockInboxes[0], mockInboxes[1]],
isFetchingInboxes: false,
});
@@ -50,7 +49,6 @@ const policyB = withCount({
description: 'Assigns based on capacity and workload',
assignmentOrder: 'capacity_based',
conversationPriority: 'medium',
- enabled: true,
inboxes: [mockInboxes[2], mockInboxes[3]],
isFetchingInboxes: false,
});
@@ -61,7 +59,6 @@ const emptyPolicy = withCount({
description: 'Policy with no assigned inboxes',
assignmentOrder: 'manual',
conversationPriority: 'low',
- enabled: false,
inboxes: [],
isFetchingInboxes: false,
});
diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.vue
index fe9965777..cedfb0009 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/AssignmentPolicyCard/AssignmentPolicyCard.vue
@@ -15,7 +15,6 @@ const props = defineProps({
assignmentOrder: { type: String, default: '' },
conversationPriority: { type: String, default: '' },
assignedInboxCount: { type: Number, default: 0 },
- enabled: { type: Boolean, default: false },
inboxes: { type: Array, default: () => [] },
isFetchingInboxes: { type: Boolean, default: false },
});
@@ -65,22 +64,6 @@ const handleFetchInboxes = () => {
{{ name }}
-
-
- {{
- enabled
- ? t(
- 'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.ACTIVE'
- )
- : t(
- 'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.INDEX.CARD.INACTIVE'
- )
- }}
-
-
-
+
+
+
-import { ref, onMounted } from 'vue';
+import { ref, computed, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import Input from 'dashboard/components-next/input/Input.vue';
import DurationInput from 'dashboard/components-next/input/DurationInput.vue';
@@ -15,6 +15,9 @@ const fairDistributionLimit = defineModel('fairDistributionLimit', {
},
});
+// The model value is in seconds (for the backend/DB)
+// DurationInput works in minutes internally
+// We need to convert between seconds and minutes
const fairDistributionWindow = defineModel('fairDistributionWindow', {
type: Number,
default: 3600,
@@ -25,6 +28,17 @@ const fairDistributionWindow = defineModel('fairDistributionWindow', {
const windowUnit = ref(DURATION_UNITS.MINUTES);
+// Convert seconds to minutes for DurationInput
+const windowInMinutes = computed({
+ get() {
+ return Math.floor((fairDistributionWindow.value || 0) / 60);
+ },
+ set(minutes) {
+ fairDistributionWindow.value = minutes * 60;
+ },
+});
+
+// Detect unit based on minutes (converted from seconds)
const detectUnit = minutes => {
const m = Number(minutes) || 0;
if (m === 0) return DURATION_UNITS.MINUTES;
@@ -34,7 +48,7 @@ const detectUnit = minutes => {
};
onMounted(() => {
- windowUnit.value = detectUnit(fairDistributionWindow.value);
+ windowUnit.value = detectUnit(windowInMinutes.value);
});
@@ -73,9 +87,9 @@ onMounted(() => {
-
+
+import { useI18n } from 'vue-i18n';
+
const props = defineProps({
id: {
type: String,
@@ -16,12 +18,22 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ disabled: {
+ type: Boolean,
+ default: false,
+ },
+ disabledMessage: {
+ type: String,
+ default: '',
+ },
});
const emit = defineEmits(['select']);
+const { t } = useI18n();
+
const handleChange = () => {
- if (!props.isActive) {
+ if (!props.isActive && !props.disabled) {
emit('select', props.id);
}
};
@@ -29,9 +41,11 @@ const handleChange = () => {
@@ -41,6 +55,7 @@ const handleChange = () => {
:checked="isActive"
:value="id"
:name="id"
+ :disabled="disabled"
type="radio"
class="h-4 w-4 border-n-slate-6 text-n-brand focus:ring-n-brand focus:ring-offset-0"
@change="handleChange"
@@ -49,11 +64,23 @@ const handleChange = () => {
-
- {{ label }}
-
+
+
+ {{ label }}
+
+
+ {{
+ t(
+ 'ASSIGNMENT_POLICY.AGENT_ASSIGNMENT_POLICY.FORM.ASSIGNMENT_ORDER.BALANCED.PREMIUM_BADGE'
+ )
+ }}
+
+
- {{ description }}
+ {{ disabled && disabledMessage ? disabledMessage : description }}
diff --git a/app/javascript/dashboard/components-next/AssignmentPolicy/components/story/BaseInfo.story.vue b/app/javascript/dashboard/components-next/AssignmentPolicy/components/story/BaseInfo.story.vue
index a3bfe9bee..86334a3ce 100644
--- a/app/javascript/dashboard/components-next/AssignmentPolicy/components/story/BaseInfo.story.vue
+++ b/app/javascript/dashboard/components-next/AssignmentPolicy/components/story/BaseInfo.story.vue
@@ -6,7 +6,6 @@ const policyName = ref('Round Robin Policy');
const description = ref(
'Distributes conversations evenly among available agents'
);
-const enabled = ref(true);
@@ -19,13 +18,10 @@ const enabled = ref(true);
diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
index bd13eee30..c90294dc0 100644
--- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
+++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
@@ -8,6 +8,7 @@ import { useStore } from 'vuex';
import { useI18n } from 'vue-i18n';
import { useSidebarKeyboardShortcuts } from './useSidebarKeyboardShortcuts';
import { vOnClickOutside } from '@vueuse/components';
+import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { useWindowSize, useEventListener } from '@vueuse/core';
import { emitter } from 'shared/helpers/mitt';
import { BUS_EVENTS } from 'shared/constants/busEvents';
@@ -50,6 +51,18 @@ const isRTL = useMapGetter('accounts/isRTL');
const { width: windowWidth } = useWindowSize();
const isMobile = computed(() => windowWidth.value < 768);
+const accountId = useMapGetter('getCurrentAccountId');
+const isFeatureEnabledonAccount = useMapGetter(
+ 'accounts/isFeatureEnabledonAccount'
+);
+
+const hasAdvancedAssignment = computed(() => {
+ return isFeatureEnabledonAccount.value(
+ accountId.value,
+ FEATURE_FLAGS.ADVANCED_ASSIGNMENT
+ );
+});
+
const toggleShortcutModalFn = show => {
if (show) {
emit('openKeyShortcutModal');
@@ -584,12 +597,16 @@ const menuItems = computed(() => {
icon: 'i-lucide-users',
to: accountScopedRoute('settings_teams_list'),
},
- {
- name: 'Settings Agent Assignment',
- label: t('SIDEBAR.AGENT_ASSIGNMENT'),
- icon: 'i-lucide-user-cog',
- to: accountScopedRoute('assignment_policy_index'),
- },
+ ...(hasAdvancedAssignment.value
+ ? [
+ {
+ name: 'Settings Agent Assignment',
+ label: t('SIDEBAR.AGENT_ASSIGNMENT'),
+ icon: 'i-lucide-user-cog',
+ to: accountScopedRoute('assignment_policy_index'),
+ },
+ ]
+ : []),
{
name: 'Settings Inboxes',
label: t('SIDEBAR.INBOXES'),
diff --git a/app/javascript/dashboard/featureFlags.js b/app/javascript/dashboard/featureFlags.js
index 279bc1cf6..353bed96e 100644
--- a/app/javascript/dashboard/featureFlags.js
+++ b/app/javascript/dashboard/featureFlags.js
@@ -2,6 +2,7 @@ export const FEATURE_FLAGS = {
AGENT_BOTS: 'agent_bots',
AGENT_MANAGEMENT: 'agent_management',
ASSIGNMENT_V2: 'assignment_v2',
+ ADVANCED_ASSIGNMENT: 'advanced_assignment',
AUTO_RESOLVE_CONVERSATIONS: 'auto_resolve_conversations',
AUTOMATIONS: 'automations',
CAMPAIGNS: 'campaigns',
@@ -56,4 +57,5 @@ export const PREMIUM_FEATURES = [
FEATURE_FLAGS.HELP_CENTER,
FEATURE_FLAGS.SAML,
FEATURE_FLAGS.CONVERSATION_REQUIRED_ATTRIBUTES,
+ FEATURE_FLAGS.ADVANCED_ASSIGNMENT,
];
diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
index 51cae8679..f45e7b4df 100644
--- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
@@ -766,6 +766,53 @@
"MAX_ASSIGNMENT_LIMIT_RANGE_ERROR": "Please enter a value greater than 0",
"MAX_ASSIGNMENT_LIMIT_SUB_TEXT": "Limit the maximum number of conversations from this inbox that can be auto assigned to an agent"
},
+ "ASSIGNMENT": {
+ "TITLE": "Conversation Assignment",
+ "DESCRIPTION": "Automatically assign incoming conversations to available agents based on assignment policies",
+ "ENABLE_AUTO_ASSIGNMENT": "Enable automatic conversation assignment",
+ "DEFAULT_RULES_TITLE": "Default assignment rules",
+ "DEFAULT_RULES_DESCRIPTION": "Using the default assignment behavior for all conversations",
+ "DEFAULT_RULE_1": "Earliest created conversations first",
+ "DEFAULT_RULE_2": "Round robin distribution",
+ "CUSTOMIZE_WITH_POLICY": "Customize with assignment policy",
+ "USING_POLICY": "Using custom assignment policy for this inbox",
+ "CUSTOMIZE_POLICY": "Customize with assignment policy",
+ "DELETE_POLICY": "Delete policy",
+ "POLICY_LABEL": "Assignment policy",
+ "ASSIGNMENT_ORDER_LABEL": "Assignment Order",
+ "ASSIGNMENT_METHOD_LABEL": "Assignment Method",
+ "POLICY_STATUS": {
+ "ACTIVE": "Active",
+ "INACTIVE": "Inactive"
+ },
+ "PRIORITY": {
+ "EARLIEST_CREATED": "Earliest created",
+ "LONGEST_WAITING": "Longest waiting"
+ },
+ "METHOD": {
+ "ROUND_ROBIN": "Round robin",
+ "BALANCED": "Balanced assignment"
+ },
+ "UPGRADE_PROMPT": "Custom assignment policies are available on the Business plan",
+ "UPGRADE_TO_BUSINESS": "Upgrade to Business",
+ "DEFAULT_POLICY_LINKED": "Default policy linked",
+ "DEFAULT_POLICY_DESCRIPTION": "Link a custom assignment policy to customize how conversations are assigned to agents in this inbox.",
+ "LINK_EXISTING_POLICY": "Link existing policy",
+ "CREATE_NEW_POLICY": "Create new policy",
+ "NO_POLICIES": "No assignment policies found",
+ "VIEW_ALL_POLICIES": "View all policies",
+ "CURRENT_BEHAVIOR": "Currently using default assignment behavior:",
+ "LINK_SUCCESS": "Assignment policy linked successfully",
+ "LINK_ERROR": "Failed to link assignment policy"
+ },
+ "ASSIGNMENT_POLICY": {
+ "DELETE_CONFIRM_TITLE": "Delete assignment policy?",
+ "DELETE_CONFIRM_MESSAGE": "Are you sure you want to remove this assignment policy from this inbox? The inbox will revert to default assignment rules.",
+ "CANCEL": "Cancel",
+ "CONFIRM_DELETE": "Delete",
+ "DELETE_SUCCESS": "Assignment policy removed successfully",
+ "DELETE_ERROR": "Failed to remove assignment policy"
+ },
"FACEBOOK_REAUTHORIZE": {
"TITLE": "Reauthorize",
"SUBTITLE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json
index c4131c14a..d885cf8ce 100644
--- a/app/javascript/dashboard/i18n/locale/en/settings.json
+++ b/app/javascript/dashboard/i18n/locale/en/settings.json
@@ -694,7 +694,8 @@
"CREATE_BUTTON": "Create policy",
"API": {
"SUCCESS_MESSAGE": "Assignment policy created successfully",
- "ERROR_MESSAGE": "Failed to create assignment policy"
+ "ERROR_MESSAGE": "Failed to create assignment policy",
+ "INBOX_LINKED": "Inbox has been linked to the policy"
}
},
"EDIT": {
@@ -708,6 +709,12 @@
"CONFIRM_BUTTON_LABEL": "Continue",
"CANCEL_BUTTON_LABEL": "Cancel"
},
+ "INBOX_LINK_PROMPT": {
+ "TITLE": "Link inbox to policy",
+ "DESCRIPTION": "Would you like to link this inbox to the assignment policy?",
+ "LINK_BUTTON": "Link inbox",
+ "CANCEL_BUTTON": "Skip"
+ },
"API": {
"SUCCESS_MESSAGE": "Assignment policy updated successfully",
"ERROR_MESSAGE": "Failed to update assignment policy"
@@ -746,7 +753,9 @@
},
"BALANCED": {
"LABEL": "Balanced",
- "DESCRIPTION": "Assign conversations based on available capacity."
+ "DESCRIPTION": "Assign conversations based on available capacity.",
+ "PREMIUM_MESSAGE": "Upgrade to access balanced assignment and agent capacity management.",
+ "PREMIUM_BADGE": "Premium"
}
},
"ASSIGNMENT_PRIORITY": {
@@ -832,6 +841,20 @@
"SUCCESS_MESSAGE": "Agent removed from policy successfully",
"ERROR_MESSAGE": "Failed to remove agent from policy"
}
+ },
+ "INBOX_LIMIT_API": {
+ "ADD": {
+ "SUCCESS_MESSAGE": "Inbox limit added successfully",
+ "ERROR_MESSAGE": "Failed to add inbox limit"
+ },
+ "UPDATE": {
+ "SUCCESS_MESSAGE": "Inbox limit updated successfully",
+ "ERROR_MESSAGE": "Failed to update inbox limit"
+ },
+ "DELETE": {
+ "SUCCESS_MESSAGE": "Inbox limit deleted successfully",
+ "ERROR_MESSAGE": "Failed to delete inbox limit"
+ }
}
},
"FORM": {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue
index ab9fcf17e..850c55676 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/assignmentPolicy/Index.vue
@@ -1,54 +1,81 @@
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue
index d2ee47e48..c8dc25e0f 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CollaboratorsPage.vue
@@ -1,122 +1,321 @@
-
@@ -138,7 +337,6 @@ export default {
selected-label
:select-label="$t('FORMS.MULTISELECT.ENTER_TO_SELECT')"
:deselect-label="$t('FORMS.MULTISELECT.ENTER_TO_REMOVE')"
- @select="v$.selectedAgents.$touch"
/>
-
diff --git a/app/services/auto_assignment/assignment_service.rb b/app/services/auto_assignment/assignment_service.rb
index 59e7b5a7b..5d75c515f 100644
--- a/app/services/auto_assignment/assignment_service.rb
+++ b/app/services/auto_assignment/assignment_service.rb
@@ -3,6 +3,7 @@ class AutoAssignment::AssignmentService
def perform_bulk_assignment(limit: 100)
return 0 unless inbox.auto_assignment_v2_enabled?
+ return 0 unless inbox.enable_auto_assignment?
assigned_count = 0
@@ -32,7 +33,9 @@ class AutoAssignment::AssignmentService
def unassigned_conversations(limit)
scope = inbox.conversations.unassigned.open
- scope = if assignment_config['conversation_priority'].to_s == 'longest_waiting'
+ # Apply conversation priority using assignment policy if available
+ policy = inbox.assignment_policy
+ scope = if policy&.longest_waiting?
scope.reorder(last_activity_at: :asc, created_at: :asc)
else
scope.reorder(created_at: :asc)
@@ -81,10 +84,6 @@ class AutoAssignment::AssignmentService
def round_robin_selector
@round_robin_selector ||= AutoAssignment::RoundRobinSelector.new(inbox: inbox)
end
-
- def assignment_config
- @assignment_config ||= inbox.auto_assignment_config || {}
- end
end
AutoAssignment::AssignmentService.prepend_mod_with('AutoAssignment::AssignmentService')
diff --git a/app/services/auto_assignment/rate_limiter.rb b/app/services/auto_assignment/rate_limiter.rb
index d22f1f9b7..da8daac9c 100644
--- a/app/services/auto_assignment/rate_limiter.rb
+++ b/app/services/auto_assignment/rate_limiter.rb
@@ -8,8 +8,6 @@ class AutoAssignment::RateLimiter
end
def track_assignment(conversation)
- return unless enabled?
-
assignment_key = build_assignment_key(conversation.id)
Redis::Alfred.set(assignment_key, conversation.id.to_s, ex: window)
end
@@ -24,11 +22,11 @@ class AutoAssignment::RateLimiter
private
def enabled?
- limit.present? && limit.positive?
+ config.present? && limit.positive?
end
def limit
- config&.fair_distribution_limit&.to_i || Math
+ config&.fair_distribution_limit.present? ? config.fair_distribution_limit.to_i : Float::INFINITY
end
def window
diff --git a/config/features.yml b/config/features.yml
index e03690d74..d8378d61c 100644
--- a/config/features.yml
+++ b/config/features.yml
@@ -241,3 +241,7 @@
display_name: Required Conversation Attributes
enabled: false
premium: true
+- name: advanced_assignment
+ display_name: Advanced Assignment
+ enabled: false
+ premium: true
diff --git a/enterprise/app/models/enterprise/account.rb b/enterprise/app/models/enterprise/account.rb
index 0bf93c98b..92038b298 100644
--- a/enterprise/app/models/enterprise/account.rb
+++ b/enterprise/app/models/enterprise/account.rb
@@ -3,6 +3,12 @@ module Enterprise::Account
# this is a temporary method since current administrate doesn't support virtual attributes
def manually_managed_features; end
+ # Auto-sync advanced_assignment with assignment_v2 when features are bulk-updated via admin UI
+ def selected_feature_flags=(features)
+ super
+ sync_assignment_features
+ end
+
def mark_for_deletion(reason = 'manual_deletion')
reason = reason.to_s == 'manual_deletion' ? 'manual_deletion' : 'inactivity'
@@ -31,4 +37,21 @@ module Enterprise::Account
def saml_enabled?
saml_settings&.saml_enabled? || false
end
+
+ private
+
+ def sync_assignment_features
+ if feature_enabled?('assignment_v2')
+ # Enable advanced_assignment for Business/Enterprise plans
+ send('feature_advanced_assignment=', true) if business_or_enterprise_plan?
+ else
+ # Disable advanced_assignment when assignment_v2 is disabled
+ send('feature_advanced_assignment=', false)
+ end
+ end
+
+ def business_or_enterprise_plan?
+ plan_name = custom_attributes['plan_name']
+ %w[Business Enterprise].include?(plan_name)
+ end
end
diff --git a/enterprise/app/services/enterprise/auto_assignment/assignment_service.rb b/enterprise/app/services/enterprise/auto_assignment/assignment_service.rb
index 4f3da08ee..46422f9bc 100644
--- a/enterprise/app/services/enterprise/auto_assignment/assignment_service.rb
+++ b/enterprise/app/services/enterprise/auto_assignment/assignment_service.rb
@@ -19,7 +19,8 @@ module Enterprise::AutoAssignment::AssignmentService
agents = filter_agents_by_capacity(agents) if capacity_filtering_enabled?
return nil if agents.empty?
- selector = policy&.balanced? ? balanced_selector : round_robin_selector
+ # Use balanced selector only if advanced_assignment feature is enabled
+ selector = policy&.balanced? && account.feature_enabled?('advanced_assignment') ? balanced_selector : round_robin_selector
selector.select_agent(agents)
end
@@ -31,7 +32,7 @@ module Enterprise::AutoAssignment::AssignmentService
end
def capacity_filtering_enabled?
- account.feature_enabled?('assignment_v2') &&
+ account.feature_enabled?('advanced_assignment') &&
account.account_users.joins(:agent_capacity_policy).exists?
end
diff --git a/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb b/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb
index a5f03cddf..52a28844f 100644
--- a/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb
+++ b/enterprise/app/services/enterprise/billing/handle_stripe_event_service.rb
@@ -22,7 +22,7 @@ class Enterprise::Billing::HandleStripeEventService
].freeze
# Additional features available starting with the Business plan
- BUSINESS_PLAN_FEATURES = %w[sla custom_roles csat_review_notes conversation_required_attributes].freeze
+ BUSINESS_PLAN_FEATURES = %w[sla custom_roles csat_review_notes conversation_required_attributes advanced_assignment].freeze
# Additional features available only in the Enterprise plan
ENTERPRISE_PLAN_FEATURES = %w[audit_logs disable_branding saml].freeze
diff --git a/spec/enterprise/services/enterprise/auto_assignment/assignment_service_spec.rb b/spec/enterprise/services/enterprise/auto_assignment/assignment_service_spec.rb
index 432f397be..ba1a3eec0 100644
--- a/spec/enterprise/services/enterprise/auto_assignment/assignment_service_spec.rb
+++ b/spec/enterprise/services/enterprise/auto_assignment/assignment_service_spec.rb
@@ -16,8 +16,9 @@ RSpec.describe Enterprise::AutoAssignment::AssignmentService, type: :service do
# Link inbox to assignment policy
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
- allow(account).to receive(:feature_enabled?).and_return(false)
- allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(true)
+ # Enable assignment_v2 (base) and advanced_assignment (premium) features
+ account.enable_features('assignment_v2')
+ account.save!
# Set agents as online
OnlineStatusTracker.update_presence(account.id, 'User', agent1.id)
diff --git a/spec/enterprise/services/enterprise/auto_assignment/capacity_service_spec.rb b/spec/enterprise/services/enterprise/auto_assignment/capacity_service_spec.rb
index 98d7c2172..c34eaad64 100644
--- a/spec/enterprise/services/enterprise/auto_assignment/capacity_service_spec.rb
+++ b/spec/enterprise/services/enterprise/auto_assignment/capacity_service_spec.rb
@@ -52,8 +52,9 @@ RSpec.describe Enterprise::AutoAssignment::CapacityService, type: :service do
agent_at_capacity.id.to_s => 'online'
})
- # Enable assignment_v2 feature
- allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(true)
+ # Enable assignment_v2 (base) and advanced_assignment (premium) features
+ account.enable_features('assignment_v2', 'advanced_assignment')
+ account.save!
# Create existing assignments for agent_at_capacity (at limit)
3.times do
diff --git a/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb b/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb
index 4b06decd0..e281f79f4 100644
--- a/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb
+++ b/spec/jobs/auto_assignment/periodic_assignment_job_spec.rb
@@ -14,13 +14,13 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do
describe '#perform' do
context 'when account has assignment_v2 feature enabled' do
before do
- allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(true)
+ account.enable_features('assignment_v2')
+ account.save!
allow(Account).to receive(:find_in_batches).and_yield([account])
end
- context 'when inbox has auto_assignment_v2 enabled' do
+ context 'when inbox has assignment policy or auto assignment enabled' do
before do
- allow(inbox).to receive(:auto_assignment_v2_enabled?).and_return(true)
inbox_relation = instance_double(ActiveRecord::Relation)
allow(account).to receive(:inboxes).and_return(inbox_relation)
allow(inbox_relation).to receive(:joins).with(:assignment_policy).and_return(inbox_relation)
@@ -41,8 +41,8 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do
policy2 = create(:assignment_policy, account: account2)
create(:inbox_assignment_policy, inbox: inbox2, assignment_policy: policy2)
- allow(account2).to receive(:feature_enabled?).with('assignment_v2').and_return(true)
- allow(inbox2).to receive(:auto_assignment_v2_enabled?).and_return(true)
+ account2.enable_features('assignment_v2')
+ account2.save!
inbox_relation2 = instance_double(ActiveRecord::Relation)
allow(account2).to receive(:inboxes).and_return(inbox_relation2)
@@ -58,9 +58,10 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do
end
end
- context 'when inbox does not have auto_assignment_v2 enabled' do
+ context 'when inbox does not have assignment policy or auto assignment enabled' do
before do
- allow(inbox).to receive(:auto_assignment_v2_enabled?).and_return(false)
+ inbox.update!(enable_auto_assignment: false)
+ InboxAssignmentPolicy.where(inbox: inbox).destroy_all
end
it 'does not queue assignment job' do
@@ -73,7 +74,6 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do
context 'when account does not have assignment_v2 feature enabled' do
before do
- allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(false)
allow(Account).to receive(:find_in_batches).and_yield([account])
end
@@ -90,11 +90,11 @@ RSpec.describe AutoAssignment::PeriodicAssignmentJob, type: :job do
# Create multiple accounts
5.times do |_i|
acc = create(:account)
+ acc.enable_features('assignment_v2')
+ acc.save!
inb = create(:inbox, account: acc, enable_auto_assignment: true)
policy = create(:assignment_policy, account: acc)
create(:inbox_assignment_policy, inbox: inb, assignment_policy: policy)
- allow(acc).to receive(:feature_enabled?).with('assignment_v2').and_return(true)
- allow(inb).to receive(:auto_assignment_v2_enabled?).and_return(true)
inbox_relation = instance_double(ActiveRecord::Relation)
allow(acc).to receive(:inboxes).and_return(inbox_relation)
diff --git a/spec/services/auto_assignment/assignment_service_spec.rb b/spec/services/auto_assignment/assignment_service_spec.rb
index 535277714..2139e5e78 100644
--- a/spec/services/auto_assignment/assignment_service_spec.rb
+++ b/spec/services/auto_assignment/assignment_service_spec.rb
@@ -10,8 +10,9 @@ RSpec.describe AutoAssignment::AssignmentService do
let(:conversation) { create(:conversation, inbox: inbox, assignee: nil) }
before do
- # Enable assignment_v2 feature for the account
- allow(account).to receive(:feature_enabled?).with('assignment_v2').and_return(true)
+ # Enable assignment_v2 feature for the account (basic assignment features)
+ account.enable_features('assignment_v2')
+ account.save!
# Link inbox to assignment policy
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: assignment_policy)
create(:inbox_member, inbox: inbox, user: agent)
diff --git a/spec/services/auto_assignment/rate_limiter_spec.rb b/spec/services/auto_assignment/rate_limiter_spec.rb
index 48a5367e3..d5c3de047 100644
--- a/spec/services/auto_assignment/rate_limiter_spec.rb
+++ b/spec/services/auto_assignment/rate_limiter_spec.rb
@@ -59,8 +59,9 @@ RSpec.describe AutoAssignment::RateLimiter do
allow(inbox).to receive(:assignment_policy).and_return(nil)
end
- it 'does not track the assignment' do
- expect(Redis::Alfred).not_to receive(:set)
+ it 'still tracks the assignment with default window' do
+ expected_key = format(Redis::RedisKeys::ASSIGNMENT_KEY, inbox_id: inbox.id, agent_id: agent.id, conversation_id: conversation.id)
+ expect(Redis::Alfred).to receive(:set).with(expected_key, conversation.id.to_s, ex: 24.hours.to_i)
rate_limiter.track_assignment(conversation)
end
end