feat: Add support for minutes in auto resolve feature (#11269)
### Summary - Converts conversation auto-resolution duration from days to minutes for more granular control - Updates validation to allow values from 10 minutes (minimum) to 999 days (maximum) - Implements smart messaging to show appropriate time units in activity messages ### Changes - Created migration to convert existing durations from days to minutes (x1440) - Updated conversation resolver to use minutes instead of days - Added dynamic translation key selection based on duration value - Updated related specs and documentation - Added support for displaying durations in days, hours, or minutes based on value ### Test plan - Verify account validation accepts new minute-based ranges - Confirm existing account settings are correctly migrated - Test auto-resolution works properly with minute values - Ensure proper time unit display in activity messages --------- Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
# limits :jsonb
|
||||
# locale :integer default("en")
|
||||
# name :string not null
|
||||
# settings :jsonb
|
||||
# status :integer default("active")
|
||||
# support_email :string(100)
|
||||
# created_at :datetime not null
|
||||
@@ -28,13 +29,28 @@ class Account < ApplicationRecord
|
||||
include Featurable
|
||||
include CacheKeys
|
||||
|
||||
SETTINGS_PARAMS_SCHEMA = {
|
||||
'type': 'object',
|
||||
'properties':
|
||||
{
|
||||
'auto_resolve_after': { 'type': %w[integer null], 'minimum': 10, 'maximum': 1_439_856 },
|
||||
'auto_resolve_message': { 'type': %w[string null] }
|
||||
},
|
||||
'required': [],
|
||||
'additionalProperties': false
|
||||
}.to_json.freeze
|
||||
|
||||
DEFAULT_QUERY_SETTING = {
|
||||
flag_query_mode: :bit_operator,
|
||||
check_for_column: false
|
||||
}.freeze
|
||||
|
||||
validates :auto_resolve_duration, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 999, allow_nil: true }
|
||||
validates :domain, length: { maximum: 100 }
|
||||
validates_with JsonSchemaValidator,
|
||||
schema: SETTINGS_PARAMS_SCHEMA,
|
||||
attribute_resolver: ->(record) { record.settings }
|
||||
|
||||
store_accessor :settings, :auto_resolve_after, :auto_resolve_message
|
||||
|
||||
has_many :account_users, dependent: :destroy_async
|
||||
has_many :agent_bot_inboxes, dependent: :destroy_async
|
||||
@@ -83,6 +99,8 @@ class Account < ApplicationRecord
|
||||
enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h
|
||||
enum status: { active: 0, suspended: 1 }
|
||||
|
||||
scope :with_auto_resolve, -> { where("(settings ->> 'auto_resolve_after')::int IS NOT NULL") }
|
||||
|
||||
before_validation :validate_limit_keys
|
||||
after_create_commit :notify_creation
|
||||
after_destroy :remove_account_sequences
|
||||
|
||||
@@ -56,13 +56,24 @@ module ActivityMessageHandler
|
||||
::Conversations::ActivityMessageJob.perform_later(self, activity_message_params(content)) if content
|
||||
end
|
||||
|
||||
def auto_resolve_message_key(minutes)
|
||||
if minutes >= 1440 && (minutes % 1440).zero?
|
||||
{ key: 'auto_resolved_days', count: minutes / 1440 }
|
||||
elsif minutes >= 60 && (minutes % 60).zero?
|
||||
{ key: 'auto_resolved_hours', count: minutes / 60 }
|
||||
else
|
||||
{ key: 'auto_resolved_minutes', count: minutes }
|
||||
end
|
||||
end
|
||||
|
||||
def user_status_change_activity_content(user_name)
|
||||
if user_name
|
||||
I18n.t("conversations.activity.status.#{status}", user_name: user_name)
|
||||
elsif Current.contact.present? && resolved?
|
||||
I18n.t('conversations.activity.status.contact_resolved', contact_name: Current.contact.name.capitalize)
|
||||
elsif resolved?
|
||||
I18n.t('conversations.activity.status.auto_resolved', duration: auto_resolve_duration)
|
||||
message_data = auto_resolve_message_key(auto_resolve_after || 0)
|
||||
I18n.t("conversations.activity.status.#{message_data[:key]}", count: message_data[:count])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ class JsonSchemaValidator < ActiveModel::Validator
|
||||
|
||||
def format_and_append_error(error, record)
|
||||
return handle_required(error, record) if error['type'] == 'required'
|
||||
return handle_minimum(error, record) if error['type'] == 'minimum'
|
||||
return handle_maximum(error, record) if error['type'] == 'maximum'
|
||||
|
||||
type = error['type'] == 'object' ? 'hash' : error['type']
|
||||
|
||||
@@ -74,6 +76,16 @@ class JsonSchemaValidator < ActiveModel::Validator
|
||||
record.errors.add(data, "must be of type #{expected_type}")
|
||||
end
|
||||
|
||||
def handle_minimum(error, record)
|
||||
data = get_name_from_data_pointer(error)
|
||||
record.errors.add(data, "must be greater than or equal to #{error['schema']['minimum']}")
|
||||
end
|
||||
|
||||
def handle_maximum(error, record)
|
||||
data = get_name_from_data_pointer(error)
|
||||
record.errors.add(data, "must be less than or equal to #{error['schema']['maximum']}")
|
||||
end
|
||||
|
||||
def get_name_from_data_pointer(error)
|
||||
data = error['data_pointer']
|
||||
|
||||
|
||||
@@ -76,10 +76,10 @@ class Conversation < ApplicationRecord
|
||||
scope :assigned, -> { where.not(assignee_id: nil) }
|
||||
scope :assigned_to, ->(agent) { where(assignee_id: agent.id) }
|
||||
scope :unattended, -> { where(first_reply_created_at: nil).or(where.not(waiting_since: nil)) }
|
||||
scope :resolvable, lambda { |auto_resolve_duration|
|
||||
return none if auto_resolve_duration.to_i.zero?
|
||||
scope :resolvable, lambda { |auto_resolve_after|
|
||||
return none if auto_resolve_after.to_i.zero?
|
||||
|
||||
open.where('last_activity_at < ? ', Time.now.utc - auto_resolve_duration.days)
|
||||
open.where('last_activity_at < ? AND waiting_since IS NULL', Time.now.utc - auto_resolve_after.minutes)
|
||||
}
|
||||
|
||||
scope :last_user_message_at, lambda {
|
||||
@@ -112,7 +112,7 @@ class Conversation < ApplicationRecord
|
||||
after_create_commit :notify_conversation_creation
|
||||
after_create_commit :load_attributes_created_by_db_triggers
|
||||
|
||||
delegate :auto_resolve_duration, to: :account
|
||||
delegate :auto_resolve_after, to: :account
|
||||
|
||||
def can_reply?
|
||||
Conversations::MessageWindowService.new(self).can_reply?
|
||||
|
||||
Reference in New Issue
Block a user