Restructure omni services and add Chatwoot research snapshot
This commit is contained in:
216
research/chatwoot/app/finders/conversation_finder.rb
Normal file
216
research/chatwoot/app/finders/conversation_finder.rb
Normal file
@@ -0,0 +1,216 @@
|
||||
class ConversationFinder
|
||||
attr_reader :current_user, :current_account, :params
|
||||
|
||||
DEFAULT_STATUS = 'open'.freeze
|
||||
SORT_OPTIONS = {
|
||||
'last_activity_at_asc' => %w[sort_on_last_activity_at asc],
|
||||
'last_activity_at_desc' => %w[sort_on_last_activity_at desc],
|
||||
'created_at_asc' => %w[sort_on_created_at asc],
|
||||
'created_at_desc' => %w[sort_on_created_at desc],
|
||||
'priority_asc' => %w[sort_on_priority asc],
|
||||
'priority_desc' => %w[sort_on_priority desc],
|
||||
'waiting_since_asc' => %w[sort_on_waiting_since asc],
|
||||
'waiting_since_desc' => %w[sort_on_waiting_since desc],
|
||||
|
||||
# To be removed in v3.5.0
|
||||
'latest' => %w[sort_on_last_activity_at desc],
|
||||
'sort_on_created_at' => %w[sort_on_created_at asc],
|
||||
'sort_on_priority' => %w[sort_on_priority desc],
|
||||
'sort_on_waiting_since' => %w[sort_on_waiting_since asc]
|
||||
}.with_indifferent_access
|
||||
# assumptions
|
||||
# inbox_id if not given, take from all conversations, else specific to inbox
|
||||
# assignee_type if not given, take 'all'
|
||||
# conversation_status if not given, take 'open'
|
||||
|
||||
# response of this class will be of type
|
||||
# {conversations: [array of conversations], count: {open: count, resolved: count}}
|
||||
|
||||
# params
|
||||
# assignee_type, inbox_id, :status
|
||||
|
||||
def initialize(current_user, params)
|
||||
@current_user = current_user
|
||||
@current_account = current_user.account
|
||||
@is_admin = current_account.account_users.find_by(user_id: current_user.id)&.administrator?
|
||||
@params = params
|
||||
end
|
||||
|
||||
def perform
|
||||
set_up
|
||||
|
||||
mine_count, unassigned_count, all_count, = set_count_for_all_conversations
|
||||
assigned_count = all_count - unassigned_count
|
||||
|
||||
filter_by_assignee_type
|
||||
|
||||
{
|
||||
conversations: conversations,
|
||||
count: {
|
||||
mine_count: mine_count,
|
||||
assigned_count: assigned_count,
|
||||
unassigned_count: unassigned_count,
|
||||
all_count: all_count
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def perform_meta_only
|
||||
set_up
|
||||
|
||||
mine_count, unassigned_count, all_count, = set_count_for_all_conversations
|
||||
assigned_count = all_count - unassigned_count
|
||||
|
||||
{
|
||||
count: {
|
||||
mine_count: mine_count,
|
||||
assigned_count: assigned_count,
|
||||
unassigned_count: unassigned_count,
|
||||
all_count: all_count
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_up
|
||||
set_inboxes
|
||||
set_team
|
||||
set_assignee_type
|
||||
|
||||
find_all_conversations
|
||||
filter_by_status unless params[:q]
|
||||
filter_by_team
|
||||
filter_by_labels
|
||||
filter_by_query
|
||||
filter_by_source_id
|
||||
end
|
||||
|
||||
def set_inboxes
|
||||
@inbox_ids = if params[:inbox_id]
|
||||
@current_user.assigned_inboxes.where(id: params[:inbox_id])
|
||||
else
|
||||
@current_user.assigned_inboxes.pluck(:id)
|
||||
end
|
||||
end
|
||||
|
||||
def set_assignee_type
|
||||
@assignee_type = params[:assignee_type]
|
||||
end
|
||||
|
||||
def set_team
|
||||
@team = current_account.teams.find(params[:team_id]) if params[:team_id]
|
||||
end
|
||||
|
||||
def find_conversation_by_inbox
|
||||
@conversations = current_account.conversations
|
||||
|
||||
return unless params[:inbox_id]
|
||||
|
||||
@conversations = @conversations.where(inbox_id: @inbox_ids)
|
||||
end
|
||||
|
||||
def find_all_conversations
|
||||
find_conversation_by_inbox
|
||||
# Apply permission-based filtering
|
||||
@conversations = Conversations::PermissionFilterService.new(
|
||||
@conversations,
|
||||
current_user,
|
||||
current_account
|
||||
).perform
|
||||
filter_by_conversation_type if params[:conversation_type]
|
||||
@conversations
|
||||
end
|
||||
|
||||
def filter_by_assignee_type
|
||||
case @assignee_type
|
||||
when 'me'
|
||||
@conversations = @conversations.assigned_to(current_user)
|
||||
when 'unassigned'
|
||||
@conversations = @conversations.unassigned
|
||||
when 'assigned'
|
||||
@conversations = @conversations.assigned
|
||||
end
|
||||
@conversations
|
||||
end
|
||||
|
||||
def filter_by_conversation_type
|
||||
case @params[:conversation_type]
|
||||
when 'mention'
|
||||
conversation_ids = current_account.mentions.where(user: current_user).pluck(:conversation_id)
|
||||
@conversations = @conversations.where(id: conversation_ids)
|
||||
when 'participating'
|
||||
@conversations = current_user.participating_conversations.where(account_id: current_account.id)
|
||||
when 'unattended'
|
||||
@conversations = @conversations.unattended
|
||||
end
|
||||
@conversations
|
||||
end
|
||||
|
||||
def filter_by_query
|
||||
return unless params[:q]
|
||||
|
||||
allowed_message_types = [Message.message_types[:incoming], Message.message_types[:outgoing]]
|
||||
@conversations = conversations.joins(:messages).where('messages.content ILIKE :search', search: "%#{params[:q]}%")
|
||||
.where(messages: { message_type: allowed_message_types }).includes(:messages)
|
||||
.where('messages.content ILIKE :search', search: "%#{params[:q]}%")
|
||||
.where(messages: { message_type: allowed_message_types })
|
||||
end
|
||||
|
||||
def filter_by_status
|
||||
return if params[:status] == 'all'
|
||||
|
||||
@conversations = @conversations.where(status: params[:status] || DEFAULT_STATUS)
|
||||
end
|
||||
|
||||
def filter_by_team
|
||||
return unless @team
|
||||
|
||||
@conversations = @conversations.where(team: @team)
|
||||
end
|
||||
|
||||
def filter_by_labels
|
||||
return unless params[:labels]
|
||||
|
||||
@conversations = @conversations.tagged_with(params[:labels], any: true)
|
||||
end
|
||||
|
||||
def filter_by_source_id
|
||||
return unless params[:source_id]
|
||||
|
||||
@conversations = @conversations.joins(:contact_inbox)
|
||||
@conversations = @conversations.where(contact_inboxes: { source_id: params[:source_id] })
|
||||
end
|
||||
|
||||
def set_count_for_all_conversations
|
||||
[
|
||||
@conversations.assigned_to(current_user).count,
|
||||
@conversations.unassigned.count,
|
||||
@conversations.count
|
||||
]
|
||||
end
|
||||
|
||||
def current_page
|
||||
params[:page] || 1
|
||||
end
|
||||
|
||||
def conversations_base_query
|
||||
@conversations.includes(
|
||||
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :contact_inbox
|
||||
)
|
||||
end
|
||||
|
||||
def conversations
|
||||
@conversations = conversations_base_query
|
||||
|
||||
sort_by, sort_order = SORT_OPTIONS[params[:sort_by]] || SORT_OPTIONS['last_activity_at_desc']
|
||||
@conversations = @conversations.send(sort_by, sort_order)
|
||||
|
||||
if params[:updated_within].present?
|
||||
@conversations.where('conversations.updated_at > ?', Time.zone.now - params[:updated_within].to_i.seconds)
|
||||
else
|
||||
@conversations.page(current_page).per(ENV.fetch('CONVERSATION_RESULTS_PER_PAGE', '25').to_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
ConversationFinder.prepend_mod_with('ConversationFinder')
|
||||
59
research/chatwoot/app/finders/email_channel_finder.rb
Normal file
59
research/chatwoot/app/finders/email_channel_finder.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
class EmailChannelFinder
|
||||
include EmailHelper
|
||||
|
||||
def initialize(email_object)
|
||||
@email_object = email_object
|
||||
end
|
||||
|
||||
def perform
|
||||
channel_from_primary_recipients || channel_from_bcc_recipients
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def channel_from_primary_recipients
|
||||
primary_recipient_emails.each do |email|
|
||||
channel = channel_from_email(email)
|
||||
return channel if channel.present?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def channel_from_bcc_recipients
|
||||
bcc_recipient_emails.each do |email|
|
||||
channel = channel_from_email(email)
|
||||
|
||||
# Skip if BCC processing is disabled for this account
|
||||
next if channel && !allow_bcc_processing?(channel.account_id)
|
||||
|
||||
return channel if channel.present?
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def primary_recipient_emails
|
||||
(@email_object.to.to_a + @email_object.cc.to_a + [@email_object['X-Original-To'].try(:value)]).flatten.compact
|
||||
end
|
||||
|
||||
def bcc_recipient_emails
|
||||
@email_object.bcc.to_a.flatten.compact
|
||||
end
|
||||
|
||||
def channel_from_email(email)
|
||||
normalized_email = normalize_email_with_plus_addressing(email)
|
||||
Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', normalized_email, normalized_email)
|
||||
end
|
||||
|
||||
def bcc_processing_skipped_accounts
|
||||
config_value = GlobalConfigService.load('SKIP_INCOMING_BCC_PROCESSING', '')
|
||||
return [] if config_value.blank?
|
||||
|
||||
config_value.split(',').map(&:to_i)
|
||||
end
|
||||
|
||||
def allow_bcc_processing?(account_id)
|
||||
bcc_processing_skipped_accounts.exclude?(account_id)
|
||||
end
|
||||
end
|
||||
50
research/chatwoot/app/finders/message_finder.rb
Normal file
50
research/chatwoot/app/finders/message_finder.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
class MessageFinder
|
||||
def initialize(conversation, params)
|
||||
@conversation = conversation
|
||||
@params = params
|
||||
end
|
||||
|
||||
def perform
|
||||
current_messages
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conversation_messages
|
||||
@conversation.messages.includes(:attachments, :sender, sender: { avatar_attachment: [:blob] })
|
||||
end
|
||||
|
||||
def messages
|
||||
return conversation_messages if @params[:filter_internal_messages].blank?
|
||||
|
||||
conversation_messages.where.not('private = ? OR message_type = ?', true, 2)
|
||||
end
|
||||
|
||||
def current_messages
|
||||
if @params[:after].present? && @params[:before].present?
|
||||
messages_between(@params[:after].to_i, @params[:before].to_i)
|
||||
elsif @params[:before].present?
|
||||
messages_before(@params[:before].to_i)
|
||||
elsif @params[:after].present?
|
||||
messages_after(@params[:after].to_i)
|
||||
else
|
||||
messages_latest
|
||||
end
|
||||
end
|
||||
|
||||
def messages_after(after_id)
|
||||
messages.reorder('created_at asc').where('id > ?', after_id).limit(100)
|
||||
end
|
||||
|
||||
def messages_before(before_id)
|
||||
messages.reorder('created_at desc').where('id < ?', before_id).limit(20).reverse
|
||||
end
|
||||
|
||||
def messages_between(after_id, before_id)
|
||||
messages.reorder('created_at asc').where('id >= ? AND id < ?', after_id, before_id).limit(1000)
|
||||
end
|
||||
|
||||
def messages_latest
|
||||
messages.reorder('created_at desc').limit(20).reverse
|
||||
end
|
||||
end
|
||||
62
research/chatwoot/app/finders/notification_finder.rb
Normal file
62
research/chatwoot/app/finders/notification_finder.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
class NotificationFinder
|
||||
attr_reader :current_user, :current_account, :params
|
||||
|
||||
RESULTS_PER_PAGE = 15
|
||||
|
||||
def initialize(current_user, current_account, params = {})
|
||||
@current_user = current_user
|
||||
@current_account = current_account
|
||||
@params = params
|
||||
set_up
|
||||
end
|
||||
|
||||
def notifications
|
||||
@notifications.page(current_page).per(RESULTS_PER_PAGE).order(last_activity_at: sort_order)
|
||||
end
|
||||
|
||||
def unread_count
|
||||
if type_included?('read')
|
||||
# If we're including read notifications, filter to unread
|
||||
@notifications.where(read_at: nil).count
|
||||
else
|
||||
# Already filtered to unread notifications, just count
|
||||
@notifications.count
|
||||
end
|
||||
end
|
||||
|
||||
def count
|
||||
@notifications.count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_up
|
||||
find_all_notifications
|
||||
filter_snoozed_notifications
|
||||
filter_read_notifications
|
||||
end
|
||||
|
||||
def find_all_notifications
|
||||
@notifications = current_user.notifications.where(account_id: @current_account.id)
|
||||
end
|
||||
|
||||
def filter_snoozed_notifications
|
||||
@notifications = @notifications.where(snoozed_until: nil) unless type_included?('snoozed')
|
||||
end
|
||||
|
||||
def filter_read_notifications
|
||||
@notifications = @notifications.where(read_at: nil) unless type_included?('read')
|
||||
end
|
||||
|
||||
def type_included?(type)
|
||||
(params[:includes] || []).include?(type)
|
||||
end
|
||||
|
||||
def current_page
|
||||
params[:page] || 1
|
||||
end
|
||||
|
||||
def sort_order
|
||||
params[:sort_order] || :desc
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user