Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a53c2dd687 | ||
|
|
5bf5c88075 | ||
|
|
2f6da8545f | ||
|
|
8852e87a87 | ||
|
|
b0d11b5c1d | ||
|
|
1206cbb08c | ||
|
|
aead51cbde | ||
|
|
93ea9ecc9f | ||
|
|
a36dd4bcda | ||
|
|
fc83fd39ca | ||
|
|
2cacb0868c | ||
|
|
b8d35e8749 | ||
|
|
3a99d7fad6 | ||
|
|
d210f61855 | ||
|
|
b15f60755d | ||
|
|
e5229802df | ||
|
|
8c0655d618 | ||
|
|
9f345c8d48 |
+1
-1
@@ -20,7 +20,7 @@ const excludedLabels = defineModel('excludedLabels', {
|
||||
|
||||
const excludeOlderThanMinutes = defineModel('excludeOlderThanMinutes', {
|
||||
type: Number,
|
||||
default: 10,
|
||||
default: null,
|
||||
});
|
||||
|
||||
// Duration limits: 10 minutes to 999 days (in minutes)
|
||||
|
||||
@@ -32,6 +32,7 @@ const convertToMinutes = newValue => {
|
||||
|
||||
const transformedValue = computed({
|
||||
get() {
|
||||
if (duration.value == null) return null;
|
||||
if (unit.value === DURATION_UNITS.MINUTES) return duration.value;
|
||||
if (unit.value === DURATION_UNITS.HOURS)
|
||||
return Math.floor(duration.value / 60);
|
||||
@@ -41,6 +42,10 @@ const transformedValue = computed({
|
||||
return 0;
|
||||
},
|
||||
set(newValue) {
|
||||
if (newValue == null || newValue === '') {
|
||||
duration.value = null;
|
||||
return;
|
||||
}
|
||||
let minuteValue = convertToMinutes(newValue);
|
||||
|
||||
duration.value = Math.min(Math.max(minuteValue, props.min), props.max);
|
||||
@@ -53,6 +58,7 @@ const transformedValue = computed({
|
||||
// this might create some confusion, especially when saving
|
||||
// this watcher fixes it by rounding the duration basically, to the nearest unit value
|
||||
watch(unit, () => {
|
||||
if (duration.value == null) return;
|
||||
let adjustedValue = convertToMinutes(transformedValue.value);
|
||||
duration.value = Math.min(Math.max(adjustedValue, props.min), props.max);
|
||||
});
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ const formData = computed(() => ({
|
||||
...(selectedPolicy.value?.exclusionRules?.excludedLabels || []),
|
||||
],
|
||||
excludeOlderThanHours:
|
||||
selectedPolicy.value?.exclusionRules?.excludeOlderThanHours || 10,
|
||||
selectedPolicy.value?.exclusionRules?.excludeOlderThanHours ?? null,
|
||||
},
|
||||
inboxCapacityLimits:
|
||||
selectedPolicy.value?.inboxCapacityLimits?.map(limit => ({
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ const props = defineProps({
|
||||
enabled: false,
|
||||
exclusionRules: {
|
||||
excludedLabels: [],
|
||||
excludeOlderThanHours: 10,
|
||||
excludeOlderThanHours: null,
|
||||
},
|
||||
inboxCapacityLimits: [],
|
||||
}),
|
||||
@@ -84,7 +84,7 @@ const state = reactive({
|
||||
description: '',
|
||||
exclusionRules: {
|
||||
excludedLabels: [],
|
||||
excludeOlderThanHours: 10,
|
||||
excludeOlderThanHours: null,
|
||||
},
|
||||
inboxCapacityLimits: [],
|
||||
});
|
||||
@@ -120,7 +120,7 @@ const resetForm = () => {
|
||||
description: '',
|
||||
exclusionRules: {
|
||||
excludedLabels: [],
|
||||
excludeOlderThanHours: 10,
|
||||
excludeOlderThanHours: null,
|
||||
},
|
||||
inboxCapacityLimits: [],
|
||||
});
|
||||
|
||||
@@ -146,6 +146,7 @@ const showWebhookSection = computed(
|
||||
|
||||
const webhookUrl = computed(
|
||||
() =>
|
||||
props.healthData?.webhook_configuration?.phone_number ||
|
||||
props.healthData?.webhook_configuration?.whatsapp_business_account ||
|
||||
props.healthData?.webhook_configuration?.application
|
||||
);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
class Migration::AccountAssignmentPolicyJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
INT_MAX = (2**31) - 1
|
||||
|
||||
def perform(account)
|
||||
inboxes_with_limit = account.inboxes
|
||||
.where("auto_assignment_config->>'max_assignment_limit' ~ '[1-9]'")
|
||||
|
||||
return if inboxes_with_limit.empty?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
policy = AgentCapacityPolicy.find_or_create_by!(account: account, name: 'Auto Assignment Capacity') do |p|
|
||||
p.description = 'Migrated from inbox settings'
|
||||
end
|
||||
|
||||
create_inbox_limits(policy, inboxes_with_limit)
|
||||
assign_policy_to_inbox_members(account, policy, inboxes_with_limit)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_inbox_limits(policy, inboxes)
|
||||
inboxes.each do |inbox|
|
||||
next if InboxCapacityLimit.exists?(agent_capacity_policy_id: policy.id, inbox_id: inbox.id)
|
||||
|
||||
limit = [inbox.auto_assignment_config['max_assignment_limit'].to_i, INT_MAX].min
|
||||
|
||||
InboxCapacityLimit.create!(
|
||||
agent_capacity_policy: policy,
|
||||
inbox: inbox,
|
||||
conversation_limit: limit
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def assign_policy_to_inbox_members(account, policy, inboxes)
|
||||
member_user_ids = InboxMember.where(inbox_id: inboxes.select(:id)).distinct.pluck(:user_id)
|
||||
|
||||
account.account_users
|
||||
.where(user_id: member_user_ids, agent_capacity_policy_id: nil)
|
||||
.find_each { |account_user| account_user.update!(agent_capacity_policy_id: policy.id) }
|
||||
end
|
||||
end
|
||||
@@ -5,8 +5,10 @@ module Enterprise::Account
|
||||
|
||||
# Auto-sync advanced_assignment with assignment_v2 when features are bulk-updated via admin UI
|
||||
def selected_feature_flags=(features)
|
||||
was_assignment_v2 = feature_enabled?('assignment_v2')
|
||||
super
|
||||
sync_assignment_features
|
||||
@assignment_v2_just_enabled = !was_assignment_v2 && feature_enabled?('assignment_v2')
|
||||
end
|
||||
|
||||
def mark_for_deletion(reason = 'manual_deletion')
|
||||
|
||||
@@ -19,5 +19,18 @@ module Enterprise::Concerns::Account
|
||||
has_many :voice_channels, dependent: :destroy_async, class_name: '::Channel::Voice'
|
||||
|
||||
has_one :saml_settings, dependent: :destroy_async, class_name: 'AccountSamlSettings'
|
||||
|
||||
after_commit :migrate_assignment_policies, if: :assignment_v2_just_enabled?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assignment_v2_just_enabled?
|
||||
!!@assignment_v2_just_enabled
|
||||
end
|
||||
|
||||
def migrate_assignment_policies
|
||||
@assignment_v2_just_enabled = false
|
||||
Migration::AccountAssignmentPolicyJob.perform_later(self)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -222,6 +222,41 @@ RSpec.describe Account, type: :model do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'after_commit :migrate_assignment_policies' do
|
||||
let(:account) { create(:account) }
|
||||
|
||||
context 'when assignment_v2 is toggled on via admin UI' do
|
||||
it 'enqueues Migration::AccountAssignmentPolicyJob after commit' do
|
||||
enabled_flags = account.enabled_features.keys.map { |f| "feature_#{f}".to_sym } + [:feature_assignment_v2]
|
||||
|
||||
expect do
|
||||
account.update!(selected_feature_flags: enabled_flags)
|
||||
end.to have_enqueued_job(Migration::AccountAssignmentPolicyJob).with(account)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assignment_v2 is already enabled and another flag changes' do
|
||||
it 'does not enqueue the job' do
|
||||
account.enable_features!('assignment_v2')
|
||||
clear_enqueued_jobs
|
||||
|
||||
enabled_flags = account.enabled_features.keys.map { |f| "feature_#{f}".to_sym }
|
||||
|
||||
expect do
|
||||
account.update!(selected_feature_flags: enabled_flags)
|
||||
end.not_to have_enqueued_job(Migration::AccountAssignmentPolicyJob)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assignment_v2 is enabled via enable_features!' do
|
||||
it 'does not enqueue the job' do
|
||||
expect do
|
||||
account.enable_features!('assignment_v2')
|
||||
end.not_to have_enqueued_job(Migration::AccountAssignmentPolicyJob)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'account deletion' do
|
||||
let(:account) { create(:account) }
|
||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||
|
||||
Reference in New Issue
Block a user