From dd75db41964ff530948dcf60e203289287cc38f0 Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Mon, 5 Dec 2022 21:38:12 +0530 Subject: [PATCH] feat: Search improvements --- app/finders/conversation_finder.rb | 7 +--- .../concerns/multi_searchable_helpers.rb | 38 +++++++++++++++++++ app/models/contact.rb | 7 ++++ app/models/conversation.rb | 7 ++++ app/models/message.rb | 12 ++++++ app/models/super_admin.rb | 3 ++ app/models/user.rb | 3 ++ .../api/v1/models/_conversation.json.jbuilder | 2 +- ...221205081737_create_pg_search_documents.rb | 21 ++++++++++ db/schema.rb | 20 +++++++++- 10 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 app/models/concerns/multi_searchable_helpers.rb create mode 100644 db/migrate/20221205081737_create_pg_search_documents.rb diff --git a/app/finders/conversation_finder.rb b/app/finders/conversation_finder.rb index b337e8458..7c6b5c725 100644 --- a/app/finders/conversation_finder.rb +++ b/app/finders/conversation_finder.rb @@ -104,11 +104,8 @@ class ConversationFinder end def filter_by_query - 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 }) + conversation_ids = PgSearch.multisearch("#{params[:q]}%").where(account_id: current_account).pluck(:conversation_id) + @conversations = Conversation.where(id: conversation_ids).includes(:messages) end def filter_by_status diff --git a/app/models/concerns/multi_searchable_helpers.rb b/app/models/concerns/multi_searchable_helpers.rb new file mode 100644 index 000000000..ca410c7b0 --- /dev/null +++ b/app/models/concerns/multi_searchable_helpers.rb @@ -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 diff --git a/app/models/contact.rb b/app/models/contact.rb index 2cfcbc3c0..807892b81 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -26,6 +26,13 @@ class Contact < ApplicationRecord include Avatarable include AvailabilityStatusable 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 :email, allow_blank: true, uniqueness: { scope: [:account_id], case_sensitive: false }, diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 6b6c2dc8d..bad4e7851 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -48,7 +48,13 @@ class Conversation < ApplicationRecord include ActivityMessageHandler include UrlHelper 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 :inbox_id, presence: true before_validation :validate_additional_attributes @@ -93,6 +99,7 @@ class Conversation < ApplicationRecord after_update_commit :execute_after_update_commit_callbacks after_create_commit :notify_conversation_creation + after_create :update_contact_search_document after_commit :set_display_id, unless: :display_id? delegate :auto_resolve_duration, to: :account diff --git a/app/models/message.rb b/app/models/message.rb index 2c8827b12..f807cae0f 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -33,6 +33,14 @@ class Message < ApplicationRecord include MessageFilterHelpers 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 @@ -274,4 +282,8 @@ class Message < ApplicationRecord conversation.update_columns(last_activity_at: created_at) # rubocop:enable Rails/SkipsModelValidations end + + def allowed_message_types? + incoming? || outgoing? + end end diff --git a/app/models/super_admin.rb b/app/models/super_admin.rb index f41610ee4..29629f632 100644 --- a/app/models/super_admin.rb +++ b/app/models/super_admin.rb @@ -13,8 +13,10 @@ # display_name :string # email :string # encrypted_password :string default(""), not null +# failed_attempts :integer # last_sign_in_at :datetime # last_sign_in_ip :string +# locked_at :datetime # message_signature :text # name :string not null # provider :string default("email"), not null @@ -28,6 +30,7 @@ # ui_settings :jsonb # uid :string default(""), not null # unconfirmed_email :string +# unlock_token :string # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/user.rb b/app/models/user.rb index 3094990d5..1a78d082d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,8 +13,10 @@ # display_name :string # email :string # encrypted_password :string default(""), not null +# failed_attempts :integer # last_sign_in_at :datetime # last_sign_in_ip :string +# locked_at :datetime # message_signature :text # name :string not null # provider :string default("email"), not null @@ -28,6 +30,7 @@ # ui_settings :jsonb # uid :string default(""), not null # unconfirmed_email :string +# unlock_token :string # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/views/api/v1/models/_conversation.json.jbuilder b/app/views/api/v1/models/_conversation.json.jbuilder index 0951892a6..4159cd374 100644 --- a/app/views/api/v1/models/_conversation.json.jbuilder +++ b/app/views/api/v1/models/_conversation.json.jbuilder @@ -10,7 +10,7 @@ json.inbox do json.channel_type conversation.inbox.channel_type end json.messages do - json.array! conversation.messages do |message| + json.array! conversation.messages.last(1) do |message| json.content message.content json.id message.id json.sender_name message.sender.name if message.sender diff --git a/db/migrate/20221205081737_create_pg_search_documents.rb b/db/migrate/20221205081737_create_pg_search_documents.rb new file mode 100644 index 000000000..1353ac9cc --- /dev/null +++ b/db/migrate/20221205081737_create_pg_search_documents.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 60cfb93e6..6475ab6d2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_11_16_000514) do +ActiveRecord::Schema.define(version: 2022_12_05_081737) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -399,7 +399,7 @@ ActiveRecord::Schema.define(version: 2022_11_16_000514) do t.datetime "agent_last_seen_at" t.jsonb "additional_attributes", default: {} 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.datetime "last_activity_at", default: -> { "CURRENT_TIMESTAMP" }, null: false t.bigint "team_id" @@ -674,6 +674,19 @@ ActiveRecord::Schema.define(version: 2022_11_16_000514) do t.index ["user_id"], name: "index_notifications_on_user_id" 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| t.bigint "platform_app_id", null: false t.string "permissible_type", null: false @@ -836,6 +849,9 @@ ActiveRecord::Schema.define(version: 2022_11_16_000514) do t.jsonb "custom_attributes", default: {} t.string "type" 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 ["pubsub_token"], name: "index_users_on_pubsub_token", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true