feat: Search improvements
This commit is contained in:
parent
34f7405689
commit
23cd34cbd6
10 changed files with 112 additions and 8 deletions
|
@ -104,11 +104,8 @@ class ConversationFinder
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_by_query
|
def filter_by_query
|
||||||
allowed_message_types = [Message.message_types[:incoming], Message.message_types[:outgoing]]
|
conversation_ids = PgSearch.multisearch("#{params[:q]}%").where(account_id: current_account).pluck(:conversation_id)
|
||||||
@conversations = conversations.joins(:messages).where('messages.content ILIKE :search', search: "%#{params[:q]}%")
|
@conversations = Conversation.where(id: conversation_ids).includes(:messages)
|
||||||
.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
|
end
|
||||||
|
|
||||||
def filter_by_status
|
def filter_by_status
|
||||||
|
|
38
app/models/concerns/multi_searchable_helpers.rb
Normal file
38
app/models/concerns/multi_searchable_helpers.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
module MultiSearchableHelpers
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
PgSearch.multisearch_options = {
|
||||||
|
using: {
|
||||||
|
tsearch: {
|
||||||
|
prefix: true,
|
||||||
|
any_word: true,
|
||||||
|
normalization: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def self.rebuild_pg_search_documents
|
||||||
|
return unless name == 'Contact'
|
||||||
|
|
||||||
|
connection.execute <<~SQL.squish
|
||||||
|
INSERT INTO pg_search_documents (searchable_type, searchable_id, content, account_id, conversation_id, created_at, updated_at)
|
||||||
|
SELECT 'Contact' AS searchable_type,
|
||||||
|
contacts.id AS searchable_id,
|
||||||
|
CONCAT_WS(' ', contacts.email, contacts.name, contacts.phone_number, contacts.id, contacts.account_id) AS content,
|
||||||
|
contacts.account_id::int AS account_id,
|
||||||
|
conversations.id AS conversation_id,
|
||||||
|
now() AS created_at,
|
||||||
|
now() AS updated_at
|
||||||
|
FROM contacts
|
||||||
|
INNER JOIN conversations
|
||||||
|
ON conversations.contact_id = contacts.id
|
||||||
|
SQL
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_contact_search_document
|
||||||
|
PgSearch::Document.create({ searchable_type: 'Contact', searchable_id: contact_id, conversation_id: id, account_id: account_id,
|
||||||
|
content: "#{contact.email} #{contact.name} #{contact.phone_number} #{contact.id}" })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -26,6 +26,13 @@ class Contact < ApplicationRecord
|
||||||
include Avatarable
|
include Avatarable
|
||||||
include AvailabilityStatusable
|
include AvailabilityStatusable
|
||||||
include Labelable
|
include Labelable
|
||||||
|
include PgSearch::Model
|
||||||
|
include MultiSearchableHelpers
|
||||||
|
|
||||||
|
# multisearchable(
|
||||||
|
# against: [:email, :name, :phone_number, :id],
|
||||||
|
# additional_attributes: -> (contact) { { conversation_id: nil, account_id: contact.account_id } }
|
||||||
|
# )
|
||||||
|
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false },
|
validates :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false },
|
||||||
|
|
|
@ -48,7 +48,13 @@ class Conversation < ApplicationRecord
|
||||||
include ActivityMessageHandler
|
include ActivityMessageHandler
|
||||||
include UrlHelper
|
include UrlHelper
|
||||||
include SortHandler
|
include SortHandler
|
||||||
|
include PgSearch::Model
|
||||||
|
include MultiSearchableHelpers
|
||||||
|
|
||||||
|
multisearchable(
|
||||||
|
against: [:display_id],
|
||||||
|
additional_attributes: ->(conversation) { { conversation_id: conversation.id, account_id: conversation.account_id } }
|
||||||
|
)
|
||||||
validates :account_id, presence: true
|
validates :account_id, presence: true
|
||||||
validates :inbox_id, presence: true
|
validates :inbox_id, presence: true
|
||||||
before_validation :validate_additional_attributes
|
before_validation :validate_additional_attributes
|
||||||
|
@ -93,6 +99,7 @@ class Conversation < ApplicationRecord
|
||||||
|
|
||||||
after_update_commit :execute_after_update_commit_callbacks
|
after_update_commit :execute_after_update_commit_callbacks
|
||||||
after_create_commit :notify_conversation_creation
|
after_create_commit :notify_conversation_creation
|
||||||
|
after_create :update_contact_search_document
|
||||||
after_commit :set_display_id, unless: :display_id?
|
after_commit :set_display_id, unless: :display_id?
|
||||||
|
|
||||||
delegate :auto_resolve_duration, to: :account
|
delegate :auto_resolve_duration, to: :account
|
||||||
|
|
|
@ -33,6 +33,14 @@
|
||||||
class Message < ApplicationRecord
|
class Message < ApplicationRecord
|
||||||
include MessageFilterHelpers
|
include MessageFilterHelpers
|
||||||
NUMBER_OF_PERMITTED_ATTACHMENTS = 15
|
NUMBER_OF_PERMITTED_ATTACHMENTS = 15
|
||||||
|
include PgSearch::Model
|
||||||
|
include MultiSearchableHelpers
|
||||||
|
|
||||||
|
multisearchable(
|
||||||
|
against: [:content],
|
||||||
|
if: :allowed_message_types?,
|
||||||
|
additional_attributes: ->(message) { { conversation_id: message.conversation_id, account_id: message.account_id } }
|
||||||
|
)
|
||||||
|
|
||||||
before_validation :ensure_content_type
|
before_validation :ensure_content_type
|
||||||
|
|
||||||
|
@ -274,4 +282,8 @@ class Message < ApplicationRecord
|
||||||
conversation.update_columns(last_activity_at: created_at)
|
conversation.update_columns(last_activity_at: created_at)
|
||||||
# rubocop:enable Rails/SkipsModelValidations
|
# rubocop:enable Rails/SkipsModelValidations
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def allowed_message_types?
|
||||||
|
incoming? || outgoing?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,8 +13,10 @@
|
||||||
# display_name :string
|
# display_name :string
|
||||||
# email :string
|
# email :string
|
||||||
# encrypted_password :string default(""), not null
|
# encrypted_password :string default(""), not null
|
||||||
|
# failed_attempts :integer
|
||||||
# last_sign_in_at :datetime
|
# last_sign_in_at :datetime
|
||||||
# last_sign_in_ip :string
|
# last_sign_in_ip :string
|
||||||
|
# locked_at :datetime
|
||||||
# message_signature :text
|
# message_signature :text
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# provider :string default("email"), not null
|
# provider :string default("email"), not null
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
# ui_settings :jsonb
|
# ui_settings :jsonb
|
||||||
# uid :string default(""), not null
|
# uid :string default(""), not null
|
||||||
# unconfirmed_email :string
|
# unconfirmed_email :string
|
||||||
|
# unlock_token :string
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
#
|
#
|
||||||
|
|
|
@ -13,8 +13,10 @@
|
||||||
# display_name :string
|
# display_name :string
|
||||||
# email :string
|
# email :string
|
||||||
# encrypted_password :string default(""), not null
|
# encrypted_password :string default(""), not null
|
||||||
|
# failed_attempts :integer
|
||||||
# last_sign_in_at :datetime
|
# last_sign_in_at :datetime
|
||||||
# last_sign_in_ip :string
|
# last_sign_in_ip :string
|
||||||
|
# locked_at :datetime
|
||||||
# message_signature :text
|
# message_signature :text
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# provider :string default("email"), not null
|
# provider :string default("email"), not null
|
||||||
|
@ -28,6 +30,7 @@
|
||||||
# ui_settings :jsonb
|
# ui_settings :jsonb
|
||||||
# uid :string default(""), not null
|
# uid :string default(""), not null
|
||||||
# unconfirmed_email :string
|
# unconfirmed_email :string
|
||||||
|
# unlock_token :string
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
#
|
#
|
||||||
|
|
|
@ -10,7 +10,7 @@ json.inbox do
|
||||||
json.channel_type conversation.inbox.channel_type
|
json.channel_type conversation.inbox.channel_type
|
||||||
end
|
end
|
||||||
json.messages do
|
json.messages do
|
||||||
json.array! conversation.messages do |message|
|
json.array! conversation.messages.last(1) do |message|
|
||||||
json.content message.content
|
json.content message.content
|
||||||
json.id message.id
|
json.id message.id
|
||||||
json.sender_name message.sender.name if message.sender
|
json.sender_name message.sender.name if message.sender
|
||||||
|
|
21
db/migrate/20221205081737_create_pg_search_documents.rb
Normal file
21
db/migrate/20221205081737_create_pg_search_documents.rb
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
class CreatePgSearchDocuments < ActiveRecord::Migration[6.1]
|
||||||
|
def up
|
||||||
|
say_with_time('Creating table for pg_search multisearch') do
|
||||||
|
create_table :pg_search_documents do |t|
|
||||||
|
t.text :content
|
||||||
|
t.bigint 'conversation_id'
|
||||||
|
t.bigint 'account_id'
|
||||||
|
t.belongs_to :searchable, polymorphic: true, index: true
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
add_index :pg_search_documents, :account_id
|
||||||
|
add_index :pg_search_documents, :conversation_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
say_with_time('Dropping table for pg_search multisearch') do
|
||||||
|
drop_table :pg_search_documents
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
db/schema.rb
20
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2022_12_19_162759) do
|
ActiveRecord::Schema.define(version: 2022_12_05_081737) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_stat_statements"
|
enable_extension "pg_stat_statements"
|
||||||
|
@ -399,7 +399,7 @@ ActiveRecord::Schema.define(version: 2022_12_19_162759) do
|
||||||
t.datetime "agent_last_seen_at"
|
t.datetime "agent_last_seen_at"
|
||||||
t.jsonb "additional_attributes", default: {}
|
t.jsonb "additional_attributes", default: {}
|
||||||
t.bigint "contact_inbox_id"
|
t.bigint "contact_inbox_id"
|
||||||
t.uuid "uuid", default: -> { "gen_random_uuid()" }, null: false
|
t.uuid "uuid", default: -> { "public.gen_random_uuid()" }, null: false
|
||||||
t.string "identifier"
|
t.string "identifier"
|
||||||
t.datetime "last_activity_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
|
t.datetime "last_activity_at", default: -> { "CURRENT_TIMESTAMP" }, null: false
|
||||||
t.bigint "team_id"
|
t.bigint "team_id"
|
||||||
|
@ -674,6 +674,19 @@ ActiveRecord::Schema.define(version: 2022_12_19_162759) do
|
||||||
t.index ["user_id"], name: "index_notifications_on_user_id"
|
t.index ["user_id"], name: "index_notifications_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "pg_search_documents", force: :cascade do |t|
|
||||||
|
t.text "content"
|
||||||
|
t.bigint "conversation_id"
|
||||||
|
t.bigint "account_id"
|
||||||
|
t.string "searchable_type"
|
||||||
|
t.bigint "searchable_id"
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["account_id"], name: "index_pg_search_documents_on_account_id"
|
||||||
|
t.index ["conversation_id"], name: "index_pg_search_documents_on_conversation_id"
|
||||||
|
t.index ["searchable_type", "searchable_id"], name: "index_pg_search_documents_on_searchable"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "platform_app_permissibles", force: :cascade do |t|
|
create_table "platform_app_permissibles", force: :cascade do |t|
|
||||||
t.bigint "platform_app_id", null: false
|
t.bigint "platform_app_id", null: false
|
||||||
t.string "permissible_type", null: false
|
t.string "permissible_type", null: false
|
||||||
|
@ -836,6 +849,9 @@ ActiveRecord::Schema.define(version: 2022_12_19_162759) do
|
||||||
t.jsonb "custom_attributes", default: {}
|
t.jsonb "custom_attributes", default: {}
|
||||||
t.string "type"
|
t.string "type"
|
||||||
t.text "message_signature"
|
t.text "message_signature"
|
||||||
|
t.datetime "locked_at"
|
||||||
|
t.integer "failed_attempts"
|
||||||
|
t.string "unlock_token"
|
||||||
t.index ["email"], name: "index_users_on_email"
|
t.index ["email"], name: "index_users_on_email"
|
||||||
t.index ["pubsub_token"], name: "index_users_on_pubsub_token", unique: true
|
t.index ["pubsub_token"], name: "index_users_on_pubsub_token", unique: true
|
||||||
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
|
||||||
|
|
Loading…
Reference in a new issue