Compare commits
18
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', {
|
const excludeOlderThanMinutes = defineModel('excludeOlderThanMinutes', {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 10,
|
default: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Duration limits: 10 minutes to 999 days (in minutes)
|
// Duration limits: 10 minutes to 999 days (in minutes)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ const convertToMinutes = newValue => {
|
|||||||
|
|
||||||
const transformedValue = computed({
|
const transformedValue = computed({
|
||||||
get() {
|
get() {
|
||||||
|
if (duration.value == null) return null;
|
||||||
if (unit.value === DURATION_UNITS.MINUTES) return duration.value;
|
if (unit.value === DURATION_UNITS.MINUTES) return duration.value;
|
||||||
if (unit.value === DURATION_UNITS.HOURS)
|
if (unit.value === DURATION_UNITS.HOURS)
|
||||||
return Math.floor(duration.value / 60);
|
return Math.floor(duration.value / 60);
|
||||||
@@ -41,6 +42,10 @@ const transformedValue = computed({
|
|||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
|
if (newValue == null || newValue === '') {
|
||||||
|
duration.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
let minuteValue = convertToMinutes(newValue);
|
let minuteValue = convertToMinutes(newValue);
|
||||||
|
|
||||||
duration.value = Math.min(Math.max(minuteValue, props.min), props.max);
|
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 might create some confusion, especially when saving
|
||||||
// this watcher fixes it by rounding the duration basically, to the nearest unit value
|
// this watcher fixes it by rounding the duration basically, to the nearest unit value
|
||||||
watch(unit, () => {
|
watch(unit, () => {
|
||||||
|
if (duration.value == null) return;
|
||||||
let adjustedValue = convertToMinutes(transformedValue.value);
|
let adjustedValue = convertToMinutes(transformedValue.value);
|
||||||
duration.value = Math.min(Math.max(adjustedValue, props.min), props.max);
|
duration.value = Math.min(Math.max(adjustedValue, props.min), props.max);
|
||||||
});
|
});
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ const formData = computed(() => ({
|
|||||||
...(selectedPolicy.value?.exclusionRules?.excludedLabels || []),
|
...(selectedPolicy.value?.exclusionRules?.excludedLabels || []),
|
||||||
],
|
],
|
||||||
excludeOlderThanHours:
|
excludeOlderThanHours:
|
||||||
selectedPolicy.value?.exclusionRules?.excludeOlderThanHours || 10,
|
selectedPolicy.value?.exclusionRules?.excludeOlderThanHours ?? null,
|
||||||
},
|
},
|
||||||
inboxCapacityLimits:
|
inboxCapacityLimits:
|
||||||
selectedPolicy.value?.inboxCapacityLimits?.map(limit => ({
|
selectedPolicy.value?.inboxCapacityLimits?.map(limit => ({
|
||||||
|
|||||||
+3
-3
@@ -17,7 +17,7 @@ const props = defineProps({
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
exclusionRules: {
|
exclusionRules: {
|
||||||
excludedLabels: [],
|
excludedLabels: [],
|
||||||
excludeOlderThanHours: 10,
|
excludeOlderThanHours: null,
|
||||||
},
|
},
|
||||||
inboxCapacityLimits: [],
|
inboxCapacityLimits: [],
|
||||||
}),
|
}),
|
||||||
@@ -84,7 +84,7 @@ const state = reactive({
|
|||||||
description: '',
|
description: '',
|
||||||
exclusionRules: {
|
exclusionRules: {
|
||||||
excludedLabels: [],
|
excludedLabels: [],
|
||||||
excludeOlderThanHours: 10,
|
excludeOlderThanHours: null,
|
||||||
},
|
},
|
||||||
inboxCapacityLimits: [],
|
inboxCapacityLimits: [],
|
||||||
});
|
});
|
||||||
@@ -120,7 +120,7 @@ const resetForm = () => {
|
|||||||
description: '',
|
description: '',
|
||||||
exclusionRules: {
|
exclusionRules: {
|
||||||
excludedLabels: [],
|
excludedLabels: [],
|
||||||
excludeOlderThanHours: 10,
|
excludeOlderThanHours: null,
|
||||||
},
|
},
|
||||||
inboxCapacityLimits: [],
|
inboxCapacityLimits: [],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ const showWebhookSection = computed(
|
|||||||
|
|
||||||
const webhookUrl = computed(
|
const webhookUrl = computed(
|
||||||
() =>
|
() =>
|
||||||
|
props.healthData?.webhook_configuration?.phone_number ||
|
||||||
props.healthData?.webhook_configuration?.whatsapp_business_account ||
|
props.healthData?.webhook_configuration?.whatsapp_business_account ||
|
||||||
props.healthData?.webhook_configuration?.application
|
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
|
# Auto-sync advanced_assignment with assignment_v2 when features are bulk-updated via admin UI
|
||||||
def selected_feature_flags=(features)
|
def selected_feature_flags=(features)
|
||||||
|
was_assignment_v2 = feature_enabled?('assignment_v2')
|
||||||
super
|
super
|
||||||
sync_assignment_features
|
sync_assignment_features
|
||||||
|
@assignment_v2_just_enabled = !was_assignment_v2 && feature_enabled?('assignment_v2')
|
||||||
end
|
end
|
||||||
|
|
||||||
def mark_for_deletion(reason = 'manual_deletion')
|
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_many :voice_channels, dependent: :destroy_async, class_name: '::Channel::Voice'
|
||||||
|
|
||||||
has_one :saml_settings, dependent: :destroy_async, class_name: 'AccountSamlSettings'
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -222,6 +222,41 @@ RSpec.describe Account, type: :model do
|
|||||||
end
|
end
|
||||||
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
|
describe 'account deletion' do
|
||||||
let(:account) { create(:account) }
|
let(:account) { create(:account) }
|
||||||
let(:admin) { create(:user, account: account, role: :administrator) }
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
||||||
|
|||||||
Reference in New Issue
Block a user