feat: New APIs for search (#6564)

- Adding new API endpoints for search
- Migrations to add appropriate indexes
This commit is contained in:
Sojan Jose
2023-02-28 22:00:36 +05:30
committed by GitHub
parent 9bd47588fc
commit d4e7eaecce
18 changed files with 423 additions and 5 deletions

View File

@@ -0,0 +1,28 @@
class Api::V1::Accounts::SearchController < Api::V1::Accounts::BaseController
def index
@result = search('all')
end
def conversations
@result = search('Conversation')
end
def contacts
@result = search('Contact')
end
def messages
@result = search('Message')
end
private
def search(search_type)
SearchService.new(
current_user: Current.user,
current_account: Current.account,
search_type: search_type,
params: params
).perform
end
end

View File

@@ -0,0 +1,17 @@
# Delete migration and spec after 2 consecutive releases.
class Migration::AddSearchIndexesJob < ApplicationJob
queue_as :scheduled_jobs
def perform
ActiveRecord::Migration[6.1].add_index(:messages, [:account_id, :inbox_id], algorithm: :concurrently)
ActiveRecord::Migration[6.1].add_index(:messages, :content, using: 'gin', opclass: :gin_trgm_ops, algorithm: :concurrently)
ActiveRecord::Migration[6.1].add_index(
:contacts,
[:name, :email, :phone_number, :identifier],
using: 'gin',
opclass: :gin_trgm_ops,
name: 'index_contacts_on_name_email_phone_number_identifier',
algorithm: :concurrently
)
end
end

View File

@@ -16,10 +16,11 @@
#
# Indexes
#
# index_contacts_on_account_id (account_id)
# index_contacts_on_phone_number_and_account_id (phone_number,account_id)
# uniq_email_per_account_contact (email,account_id) UNIQUE
# uniq_identifier_per_account_contact (identifier,account_id) UNIQUE
# index_contacts_on_account_id (account_id)
# index_contacts_on_name_email_phone_number_identifier (name,email,phone_number,identifier) USING gin
# index_contacts_on_phone_number_and_account_id (phone_number,account_id)
# uniq_email_per_account_contact (email,account_id) UNIQUE
# uniq_identifier_per_account_contact (identifier,account_id) UNIQUE
#
class Contact < ApplicationRecord

View File

@@ -23,7 +23,9 @@
# Indexes
#
# index_messages_on_account_id (account_id)
# index_messages_on_account_id_and_inbox_id (account_id,inbox_id)
# index_messages_on_additional_attributes_campaign_id (((additional_attributes -> 'campaign_id'::text))) USING gin
# index_messages_on_content (content) USING gin
# index_messages_on_conversation_id (conversation_id)
# index_messages_on_inbox_id (inbox_id)
# index_messages_on_sender_type_and_sender_id (sender_type,sender_id)

View File

@@ -0,0 +1,43 @@
class SearchService
pattr_initialize [:current_user!, :current_account!, :params!, :search_type!]
def perform
case search_type
when 'Message'
{ messages: filter_messages }
when 'Conversation'
{ conversations: filter_conversations }
when 'Contact'
{ contacts: filter_contacts }
else
{ contacts: filter_contacts, messages: filter_messages, conversations: filter_conversations }
end
end
private
def accessable_inbox_ids
@accessable_inbox_ids ||= @current_user.assigned_inboxes.pluck(:id)
end
def filter_conversations
@conversations = current_account.conversations.where(inbox_id: accessable_inbox_ids)
.joins('INNER JOIN contacts ON conversations.contact_id = contacts.id')
.where("cast(conversations.display_id as text) ILIKE :search OR contacts.name ILIKE :search OR contacts.email
ILIKE :search OR contacts.phone_number ILIKE :search OR contacts.identifier ILIKE :search", search: "%#{params[:q]}%")
.limit(10)
end
def filter_messages
@messages = current_account.messages.where(inbox_id: accessable_inbox_ids)
.where('messages.content ILIKE :search', search: "%#{params[:q]}%")
.where('created_at >= ?', 3.months.ago).limit(10)
end
def filter_contacts
@contacts = current_account.contacts.where(
"name ILIKE :search OR email ILIKE :search OR phone_number
ILIKE :search OR identifier ILIKE :search", search: "%#{params[:q]}%"
).limit(10)
end
end

View File

@@ -0,0 +1,5 @@
json.id agent.id
json.available_name agent.available_name
json.email agent.email
json.name agent.name
json.role agent.role

View File

@@ -0,0 +1,5 @@
json.email contact.email
json.id contact.id
json.name contact.name
json.phone_number contact.phone_number
json.identifier contact.identifier

View File

@@ -0,0 +1,4 @@
json.id inbox.id
json.channel_id inbox.channel_id
json.name inbox.name
json.channel_type inbox.channel_type

View File

@@ -0,0 +1,14 @@
json.id message.id
json.content message.content
json.message_type message.message_type_before_type_cast
json.content_type message.content_type
json.source_id message.source_id
json.inbox_id message.inbox_id
json.conversation_id message.conversation.try(:display_id)
json.created_at message.created_at.to_i
json.agent do
json.partial! 'agent', formats: [:json], agent: message.conversation.try(:assignee) if message.conversation.try(:assignee).present?
end
json.inbox do
json.partial! 'inbox', formats: [:json], inbox: message.inbox if message.inbox.present? && message.try(:inbox).present?
end

View File

@@ -0,0 +1,7 @@
json.payload do
json.contacts do
json.array! @result[:contacts] do |contact|
json.partial! 'contact', formats: [:json], contact: contact
end
end
end

View File

@@ -0,0 +1,21 @@
json.payload do
json.conversations do
json.array! @result[:conversations] do |conversation|
json.id conversation.display_id
json.account_id conversation.account_id
json.created_at conversation.created_at.to_i
json.message do
json.partial! 'message', formats: [:json], message: conversation.messages.try(:first)
end
json.contact do
json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present?
end
json.inbox do
json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present?
end
json.agent do
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
end
end
end
end

View File

@@ -0,0 +1,32 @@
json.payload do
json.conversations do
json.array! @result[:conversations] do |conversation|
json.id conversation.display_id
json.account_id conversation.account_id
json.created_at conversation.created_at.to_i
json.message do
json.partial! 'message', formats: [:json], message: conversation.messages.try(:first)
end
json.contact do
json.partial! 'contact', formats: [:json], contact: conversation.contact if conversation.try(:contact).present?
end
json.inbox do
json.partial! 'inbox', formats: [:json], inbox: conversation.inbox if conversation.try(:inbox).present?
end
json.agent do
json.partial! 'agent', formats: [:json], agent: conversation.assignee if conversation.try(:assignee).present?
end
end
end
json.contacts do
json.array! @result[:contacts] do |contact|
json.partial! 'contact', formats: [:json], contact: contact
end
end
json.messages do
json.array! @result[:messages] do |message|
json.partial! 'message', formats: [:json], message: message
end
end
end

View File

@@ -0,0 +1,7 @@
json.payload do
json.messages do
json.array! @result[:messages] do |message|
json.partial! 'message', formats: [:json], message: message
end
end
end