diff --git a/.env_sample b/.env_sample index 1a9fcf4c3..0645a5ca0 100644 --- a/.env_sample +++ b/.env_sample @@ -33,9 +33,6 @@ REDIS_SIDEKIQ_URL="redis://localhost:6379" # Redis Devices/Rpush storage REDIS_RPUSH_URL="redis://localhost:6379" -# Elasticsearch -ELASTICSEARCH_URL="http://localhost:9200" - # OpenResty OPENRESTY_URL="" @@ -164,4 +161,4 @@ SENDGRID_API_KEY="Optional, Production only" # Disable simplecov coverage testing when running rspec locally -COVERAGE="false" \ No newline at end of file +COVERAGE="false" diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile index a8a6f369d..c05178468 100644 --- a/.gitpod.dockerfile +++ b/.gitpod.dockerfile @@ -18,12 +18,3 @@ RUN sudo apt-get update \ && sudo apt-get install -y \ redis-server \ && sudo rm -rf /var/lib/apt/lists/* - -# Install Elasticsearch -ARG ES_REPO=https://artifacts.elastic.co/downloads/elasticsearch -ARG ES_ARCHIVE=elasticsearch-oss-7.5.2-linux-x86_64.tar.gz -RUN wget "${ES_REPO}/${ES_ARCHIVE}" \ - && wget "${ES_REPO}/${ES_ARCHIVE}.sha512" \ - && shasum -a 512 -c ${ES_ARCHIVE}.sha512 \ - && tar -xzf ${ES_ARCHIVE} \ - && rm ${ES_ARCHIVE} ${ES_ARCHIVE}.sha512 diff --git a/.gitpod.yml b/.gitpod.yml index 3e081f489..181d298fc 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -10,16 +10,11 @@ ports: onOpen: ignore - port: 6379 onOpen: ignore - - port: 9200 - onOpen: ignore - - port: 9300 - onOpen: ignore tasks: - name: Forem Server before: | redis-server & - /home/gitpod/elasticsearch-7.5.2/bin/elasticsearch & init: > cp .env_sample .env && gem install solargraph && diff --git a/.travis.yml b/.travis.yml index 3321bfce3..d1b273bcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,20 +39,11 @@ env: - KNAPSACK_PRO_CI_NODE_TOTAL=3 - COVERAGE_REPORTS_TOTAL=4 - FOREM_OWNER_SECRET="secret" # test secret so e2e tests can run properly. - - ELASTICSEARCH_URL="http://localhost:9200" before_install: - gem install bundler:"<2.3" install: - date --rfc-3339=seconds - nvm install - - pkill -9 -f elasticsearch || true - - curl -s -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.5.2-amd64.deb - - sudo dpkg -i --force-confnew elasticsearch-7.5.2-amd64.deb - - sudo sed -i.old 's/-Xms1g/-Xms128m/' /etc/elasticsearch/jvm.options - - sudo sed -i.old 's/-Xmx1g/-Xmx128m/' /etc/elasticsearch/jvm.options - - echo -e '-XX:+DisableExplicitGC\n-Djdk.io.permissionsUseCanonicalPath=true\n-Dlog4j.skipJansi=true\n-server\n' | sudo tee -a /etc/elasticsearch/jvm.options - - sudo chown -R elasticsearch:elasticsearch /etc/default/elasticsearch - - sudo systemctl start elasticsearch - bundle config set path 'vendor/bundle' - cp .env_sample .env - bin/ci-bundle diff --git a/Gemfile b/Gemfile index e521a9b11..9197f872c 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,6 @@ gem "devise" # Flexible authentication solution for Rails gem "devise_invitable", "~> 2.0.5" # Allows invitations to be sent for joining gem "dogstatsd-ruby", "~> 4.8" # A client for DogStatsD, an extension of the StatsD metric server for Datadog gem "doorkeeper", "~> 5.5" # Oauth 2 provider -gem "elasticsearch", "~> 7.12" # Powers DEVs core search functionality gem "email_validator", "~> 2.2" # Email validator for Rails and ActiveModel gem "emoji_regex", "~> 3.2" # A pair of Ruby regular expressions for matching Unicode Emoji symbols gem "fastly", "~> 3.0" # Client library for the Fastly acceleration system @@ -65,7 +64,7 @@ gem "omniauth-github", "~> 2.0" # OmniAuth strategy for GitHub gem "omniauth-rails_csrf_protection", "~> 1.0" # Provides CSRF protection on OmniAuth request endpoint on Rails application. gem "omniauth-twitter", "~> 1.4" # OmniAuth strategy for Twitter gem "parallel", "~> 1.20" # Run any kind of code in parallel processes -gem "patron", "~> 0.13.3" # HTTP client library based on libcurl, used with Elasticsearch to support http keep-alive connections +gem "patron", "~> 0.13.3" # HTTP client library based on libcurl, used with GitHub OAuth client gem "pg", "~> 1.2" # Pg is the Ruby interface to the PostgreSQL RDBMS gem "pg_search", "~> 2.3.5" # PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search gem "puma", "~> 5.2.2" # Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server diff --git a/Gemfile.lock b/Gemfile.lock index 9799ecc0c..097690b86 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -223,14 +223,6 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - elasticsearch (7.12.0) - elasticsearch-api (= 7.12.0) - elasticsearch-transport (= 7.12.0) - elasticsearch-api (7.12.0) - multi_json - elasticsearch-transport (7.12.0) - faraday (~> 1) - multi_json em-websocket (0.5.2) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) @@ -880,7 +872,6 @@ DEPENDENCIES dogstatsd-ruby (~> 4.8) doorkeeper (~> 5.5) dotenv-rails (~> 2.7.6) - elasticsearch (~> 7.12) email_validator (~> 2.2) emoji_regex (~> 3.2) erb_lint (~> 0.0.37) diff --git a/README.md b/README.md index 83def2cda..e0dcfe3ec 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,6 @@ A more complete overview of our stack is available in - [ImageMagick](https://imagemagick.org/): please refer to ImageMagick's [installation instructions](https://imagemagick.org/script/download.php). - [Redis](https://redis.io/) 4 or higher. -- [Elasticsearch](https://www.elastic.co) 7 or higher. #### Containers diff --git a/app/controllers/api/v0/health_checks_controller.rb b/app/controllers/api/v0/health_checks_controller.rb index 26a5abc3e..31e1b719f 100644 --- a/app/controllers/api/v0/health_checks_controller.rb +++ b/app/controllers/api/v0/health_checks_controller.rb @@ -7,14 +7,6 @@ module Api render json: { message: "App is up!" }, status: :ok end - def search - if Search::Client.ping - render json: { message: "Search ping succeeded!" }, status: :ok - else - render json: { message: "Search ping failed!" }, status: :internal_server_error - end - end - def database if ActiveRecord::Base.connected? render json: { message: "Database connected" }, status: :ok diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb index ecc56f786..91e153160 100644 --- a/app/controllers/chat_channels_controller.rb +++ b/app/controllers/chat_channels_controller.rb @@ -159,7 +159,6 @@ class ChatChannelsController < ApplicationController authorize chat_channel chat_channel.status = "blocked" chat_channel.save - chat_channel.chat_channel_memberships.map(&:index_to_elasticsearch) render json: { status: "success", message: "chat channel blocked" }, status: :ok end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 27ff8f0af..1cd2fd519 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -93,10 +93,6 @@ class SearchController < ApplicationController render json: { result: result } end - def users - render json: { result: user_search } - end - def usernames result = Search::Postgres::Username.search_documents(params[:username]) @@ -197,10 +193,6 @@ class SearchController < ApplicationController ) end - def user_search - Search::User.search_documents(params: user_params.to_h) - end - def chat_channel_params params.permit(CHAT_CHANNEL_PARAMS) end diff --git a/app/lib/acts_as_taggable_on/tag.rb b/app/lib/acts_as_taggable_on/tag.rb deleted file mode 100644 index 715cb1eee..000000000 --- a/app/lib/acts_as_taggable_on/tag.rb +++ /dev/null @@ -1,11 +0,0 @@ -module ActsAsTaggableOn - class Tag - after_commit on: %i[create update] do - ::Tag.find(id).index_to_elasticsearch - end - - after_commit on: :update do - DataSync::Elasticsearch::Tag.new(self).call - end - end -end diff --git a/app/lib/forem_stats_drivers/datadog_driver.rb b/app/lib/forem_stats_drivers/datadog_driver.rb index ab4280196..cba2c38fa 100644 --- a/app/lib/forem_stats_drivers/datadog_driver.rb +++ b/app/lib/forem_stats_drivers/datadog_driver.rb @@ -9,7 +9,6 @@ module ForemStatsDrivers c.tracer enabled: ENV["DD_API_KEY"].present? c.tracer partial_flush: true c.tracer priority_sampling: true - c.use :elasticsearch c.use :sidekiq c.use :redis, service_name: "redis", describes: { url: ENV["REDIS_URL"] } c.use :redis, service_name: "redis-sessions", describes: { url: ENV["REDIS_SESSIONS_URL"] } diff --git a/app/models/article.rb b/app/models/article.rb index 098ba2997..cc840b6ea 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -3,14 +3,9 @@ class Article < ApplicationRecord include ActionView::Helpers include Storext.model include Reactable - include Searchable include UserSubscriptionSourceable include PgSearch::Model - SEARCH_SERIALIZER = Search::ArticleSerializer - SEARCH_CLASS = Search::FeedContent - DATA_SYNC_CLASS = DataSync::Elasticsearch::Article - acts_as_taggable_on :tags resourcify @@ -112,9 +107,6 @@ class Article < ApplicationRecord } after_commit :async_score_calc, :touch_collection, on: %i[create update] - after_commit :index_to_elasticsearch, on: %i[create update] - after_commit :sync_related_elasticsearch_docs, on: %i[update] - after_commit :remove_from_elasticsearch, on: [:destroy] # The trigger `update_reading_list_document` is used to keep the `articles.reading_list_document` column updated. # @@ -361,7 +353,6 @@ class Article < ApplicationRecord def touch_by_reaction async_score_calc - index_to_elasticsearch end def comments_blob diff --git a/app/models/chat_channel_membership.rb b/app/models/chat_channel_membership.rb index 83d8220bf..6396dd36c 100644 --- a/app/models/chat_channel_membership.rb +++ b/app/models/chat_channel_membership.rb @@ -1,10 +1,6 @@ class ChatChannelMembership < ApplicationRecord attr_accessor :invitation_usernames - include Searchable - SEARCH_SERIALIZER = Search::ChatChannelMembershipSerializer - SEARCH_CLASS = Search::ChatChannelMembership - ROLES = %w[member mod].freeze STATUSES = %w[active inactive pending rejected left_channel removed_from_channel joining_request].freeze @@ -18,9 +14,6 @@ class ChatChannelMembership < ApplicationRecord validate :permission - after_commit :index_to_elasticsearch, on: %i[create update] - after_commit :remove_from_elasticsearch, on: [:destroy] - delegate :channel_type, to: :chat_channel scope :eager_load_serialized_data, -> { includes(:user, :channel) } diff --git a/app/models/comment.rb b/app/models/comment.rb index 4d5c0fda6..b395a0197 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -4,10 +4,6 @@ class Comment < ApplicationRecord include PgSearch::Model include Reactable - include Searchable - - SEARCH_SERIALIZER = Search::CommentSerializer - SEARCH_CLASS = Search::FeedContent BODY_MARKDOWN_SIZE_RANGE = (1..25_000).freeze @@ -63,18 +59,9 @@ class Comment < ApplicationRecord after_create_commit :send_to_moderator after_commit :calculate_score, on: %i[create update] - after_commit :index_to_elasticsearch, on: %i[create update] after_update_commit :update_notifications, if: proc { |comment| comment.saved_changes.include? "body_markdown" } - after_commit :remove_from_elasticsearch, on: [:destroy] - - # [@atsmith813] this is adapted from the `search_field` property in - # `config/elasticsearch/mappings/feed_content.json` and - # `app/serializers/search/comment_serializer.rb` - # - # highlighter settings are taken from - # Search::QueryBuildersFeedContent#add_highlight_fields pg_search_scope :search_comments, against: %i[body_markdown], using: { diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb deleted file mode 100644 index fb0dc774d..000000000 --- a/app/models/concerns/searchable.rb +++ /dev/null @@ -1,32 +0,0 @@ -module Searchable - def search_id - id - end - - def index_to_elasticsearch - Search::IndexWorker.perform_async(self.class.name, id) - end - - def index_to_elasticsearch_inline - self.class::SEARCH_CLASS.index(search_id, serialized_search_hash) - end - - def remove_from_elasticsearch - # Callbacks can cause index and removal jobs to be enqueued at the same time - # to avoid indexing a document after removing it we delay the removal job by 5 seconds to - # ensure it is run last - Search::RemoveFromIndexWorker.perform_in(5.seconds, self.class::SEARCH_CLASS.to_s, search_id) - end - - def serialized_search_hash - self.class::SEARCH_SERIALIZER.new(self).serializable_hash.dig(:data, :attributes) - end - - def elasticsearch_doc - self.class::SEARCH_CLASS.find_document(search_id) - end - - def sync_related_elasticsearch_docs - self.class::DATA_SYNC_CLASS.new(self).call - end -end diff --git a/app/models/listing.rb b/app/models/listing.rb index eee54211c..adadc1170 100644 --- a/app/models/listing.rb +++ b/app/models/listing.rb @@ -3,12 +3,8 @@ class Listing < ApplicationRecord # We standardized on the latter, but keeping the table name was easier. self.table_name = "classified_listings" - include Searchable include PgSearch::Model - SEARCH_SERIALIZER = Search::ListingSerializer - SEARCH_CLASS = Search::Listing - attr_accessor :action # NOTE: categories were hardcoded at first and the model was only added later. @@ -19,8 +15,6 @@ class Listing < ApplicationRecord before_validation :modify_inputs before_save :evaluate_markdown before_create :create_slug - after_commit :index_to_elasticsearch, on: %i[create update] - after_commit :remove_from_elasticsearch, on: [:destroy] acts_as_taggable_on :tags has_many :credits, as: :purchase, inverse_of: :purchase, dependent: :nullify @@ -33,8 +27,6 @@ class Listing < ApplicationRecord validate :restrict_markdown_input validate :validate_tags - # [@atsmith813] this is adapted from the `listing_search` property in - # `config/elasticsearch/mappings/listings.json` pg_search_scope :search_listings, against: %i[body_markdown cached_tag_list location slug title], using: { tsearch: { prefix: true } } diff --git a/app/models/message.rb b/app/models/message.rb index 1f964156e..6a6848f44 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -31,7 +31,6 @@ class Message < ApplicationRecord def update_chat_channel_last_message_at chat_channel.touch(:last_message_at) - ChatChannels::IndexesMembershipsWorker.perform_async(chat_channel.id) end def update_all_has_unopened_messages_statuses diff --git a/app/models/organization.rb b/app/models/organization.rb index 5ab97ddb7..0e77ea9dd 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -30,7 +30,6 @@ class Organization < ApplicationRecord # This callback will eventually invoke Article.update_cached_user to update the organization.name # only when it has been changed, thus invoking the trigger on Article.reading_list_document after_update_commit :update_articles_cached_organization - after_update_commit :sync_related_elasticsearch_docs after_destroy_commit :bust_cache, :article_sync has_many :articles, dependent: :nullify @@ -173,10 +172,6 @@ class Organization < ApplicationRecord errors.add(:slug, "is taken.") if slug_taken end - def sync_related_elasticsearch_docs - DataSync::Elasticsearch::Organization.new(self).call - end - def article_sync # Syncs article cached organization and updates Elasticsearch docs Article.where(id: cached_article_ids).find_each(&:save) diff --git a/app/models/podcast_episode.rb b/app/models/podcast_episode.rb index 464c93b35..78accaab6 100644 --- a/app/models/podcast_episode.rb +++ b/app/models/podcast_episode.rb @@ -4,10 +4,6 @@ class PodcastEpisode < ApplicationRecord ] include PgSearch::Model - include Searchable - - SEARCH_SERIALIZER = Search::PodcastEpisodeSerializer - SEARCH_CLASS = Search::FeedContent acts_as_taggable @@ -38,13 +34,6 @@ class PodcastEpisode < ApplicationRecord after_destroy :purge, :purge_all after_save :bust_cache - after_commit :index_to_elasticsearch, on: %i[update] - after_commit :remove_from_elasticsearch, on: [:destroy] - - # [@atsmith813] this is adapted from the `search_field` property in - # `config/elasticsearch/mappings/feed_content.json` and - # `app/serializers/search/podcast_episode_serializer.rb` with a couple of - # extras pg_search_scope :search_podcast_episodes, against: %i[body subtitle title], using: { tsearch: { prefix: true } } diff --git a/app/models/tag.rb b/app/models/tag.rb index 0842898d6..07e0d7d40 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -1,5 +1,3 @@ -require_relative "../lib/acts_as_taggable_on/tag" - class Tag < ActsAsTaggableOn::Tag attr_accessor :points, :tag_moderator_id, :remove_moderator_id @@ -8,8 +6,6 @@ class Tag < ActsAsTaggableOn::Tag # This model doesn't inherit from ApplicationRecord so this has to be included include Purgeable - include Searchable - include PgSearch::Model ALLOWED_CATEGORIES = %w[uncategorized language library tool site_mechanic location subcommunity].freeze @@ -39,9 +35,6 @@ class Tag < ActsAsTaggableOn::Tag before_save :mark_as_updated after_commit :bust_cache - after_commit :index_to_elasticsearch, on: %i[create update] - after_commit :sync_related_elasticsearch_docs, on: [:update] - after_commit :remove_from_elasticsearch, on: [:destroy] pg_search_scope :search_by_name, against: :name, @@ -49,10 +42,6 @@ class Tag < ActsAsTaggableOn::Tag scope :eager_load_serialized_data, -> {} - SEARCH_SERIALIZER = Search::TagSerializer - SEARCH_CLASS = Search::Tag - DATA_SYNC_CLASS = DataSync::Elasticsearch::Tag - # possible social previews templates for articles with a particular tag def self.social_preview_templates Rails.root.join("app/views/social_previews/articles").children.map { |ch| File.basename(ch, ".html.erb") } diff --git a/app/models/user.rb b/app/models/user.rb index d4b47bc89..8b27e6e0b 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,8 +1,8 @@ class User < ApplicationRecord resourcify + rolify include CloudinaryHelper - include Searchable include Storext.model # @citizen428 Preparing to drop profile columns from the users table @@ -100,12 +100,6 @@ class User < ApplicationRecord :add_credits, :remove_credits, :add_org_credits, :remove_org_credits, :ip_address, :current_password - rolify after_add: :index_roles, after_remove: :index_roles - - SEARCH_SERIALIZER = Search::UserSerializer - SEARCH_CLASS = Search::User - DATA_SYNC_CLASS = DataSync::Elasticsearch::User - acts_as_followable acts_as_follower @@ -310,9 +304,6 @@ class User < ApplicationRecord after_create_commit :send_welcome_notification after_commit :bust_cache - after_commit :index_to_elasticsearch, on: %i[create update] - after_commit :sync_related_elasticsearch_docs, on: %i[create update] - after_commit :remove_from_elasticsearch, on: [:destroy] def self.dev_account find_by(id: Settings::Community.staff_user_id) @@ -676,14 +667,6 @@ class User < ApplicationRecord follows.destroy_all end - def index_roles(_role) - return unless persisted? - - index_to_elasticsearch_inline - rescue StandardError => e - Honeybadger.notify(e, context: { user_id: id }) - end - def can_send_confirmation_email return if changes[:email].blank? || id.blank? diff --git a/app/services/data_sync/elasticsearch/article.rb b/app/services/data_sync/elasticsearch/article.rb deleted file mode 100644 index abc03c709..000000000 --- a/app/services/data_sync/elasticsearch/article.rb +++ /dev/null @@ -1,16 +0,0 @@ -module DataSync - module Elasticsearch - class Article < Base - RELATED_DOCS = %i[ - comments - ].freeze - - SHARED_FIELDS = %i[ - published - title - ].freeze - - delegate :comments, to: :@updated_record - end - end -end diff --git a/app/services/data_sync/elasticsearch/base.rb b/app/services/data_sync/elasticsearch/base.rb deleted file mode 100644 index b20054a97..000000000 --- a/app/services/data_sync/elasticsearch/base.rb +++ /dev/null @@ -1,33 +0,0 @@ -module DataSync - module Elasticsearch - class Base - attr_reader :updated_record - - def initialize(updated_record) - @updated_record = updated_record - end - - def call - return unless sync_needed? - - sync_related_documents - end - - private - - def sync_needed? - updated_fields.slice(*self.class::SHARED_FIELDS).any? - end - - def sync_related_documents - self.class::RELATED_DOCS.each do |relation_name| - __send__(relation_name).find_each(&:index_to_elasticsearch) - end - end - - def updated_fields - updated_record.saved_changes - end - end - end -end diff --git a/app/services/data_sync/elasticsearch/organization.rb b/app/services/data_sync/elasticsearch/organization.rb deleted file mode 100644 index 94e06e684..000000000 --- a/app/services/data_sync/elasticsearch/organization.rb +++ /dev/null @@ -1,17 +0,0 @@ -module DataSync - module Elasticsearch - class Organization < Base - RELATED_DOCS = %i[ - articles - ].freeze - - SHARED_FIELDS = %i[ - slug - name - profile_image - ].freeze - - delegate :articles, to: :@updated_record - end - end -end diff --git a/app/services/data_sync/elasticsearch/tag.rb b/app/services/data_sync/elasticsearch/tag.rb deleted file mode 100644 index 2b486856d..000000000 --- a/app/services/data_sync/elasticsearch/tag.rb +++ /dev/null @@ -1,30 +0,0 @@ -module DataSync - module Elasticsearch - class Tag < Base - RELATED_DOCS = %i[ - articles - podcast_episodes - ].freeze - - SHARED_FIELDS = %i[ - keywords_for_search - ].freeze - - private - - def articles - ::Article.cached_tagged_with(updated_record.name) - end - - def reactions - ::Reaction.readinglist.where(reactable: articles) - end - - def podcast_episodes - ::PodcastEpisode.where( - id: updated_record.taggings.where(taggable_type: "PodcastEpisode").select(:taggable_id), - ) - end - end - end -end diff --git a/app/services/data_sync/elasticsearch/user.rb b/app/services/data_sync/elasticsearch/user.rb deleted file mode 100644 index de33ef574..000000000 --- a/app/services/data_sync/elasticsearch/user.rb +++ /dev/null @@ -1,30 +0,0 @@ -module DataSync - module Elasticsearch - class User < Base - RELATED_DOCS = %i[ - articles - podcast_episodes - chat_channel_memberships - comments - ].freeze - - SHARED_FIELDS = %i[ - username - name - profile_image_url - ].freeze - - delegate :articles, :chat_channel_memberships, :comments, to: :@updated_record - - private - - def reactions - updated_record.reactions.readinglist - end - - def podcast_episodes - PodcastEpisode.for_user(updated_record) - end - end - end -end diff --git a/app/services/edge_cache/bust_commentable.rb b/app/services/edge_cache/bust_commentable.rb index 08a24fdb4..5887c3716 100644 --- a/app/services/edge_cache/bust_commentable.rb +++ b/app/services/edge_cache/bust_commentable.rb @@ -7,7 +7,6 @@ module EdgeCache cache_bust = EdgeCache::Bust.new cache_bust.call("#{commentable.path}/comments") - commentable.index_to_elasticsearch_inline end end end diff --git a/app/services/podcasts/create_episode.rb b/app/services/podcasts/create_episode.rb index 531188fd4..71995b2aa 100644 --- a/app/services/podcasts/create_episode.rb +++ b/app/services/podcasts/create_episode.rb @@ -69,7 +69,6 @@ module Podcasts def finalize(episode) episode.purge_all - episode.index_to_elasticsearch episode.save if episode.processed_html.blank? end end diff --git a/app/services/search/base.rb b/app/services/search/base.rb deleted file mode 100644 index 16d7ab4ce..000000000 --- a/app/services/search/base.rb +++ /dev/null @@ -1,143 +0,0 @@ -module Search - class Base - class << self - def index(doc_id, serialized_data) - Search::Client.index( - id: doc_id, - index: self::INDEX_ALIAS, - body: serialized_data.merge(last_indexed_at: Time.current), - ) - end - - def bulk_index(data_hashes) - indexing_hashes = data_hashes.map do |data_hash| - model_hash = data_hash.with_indifferent_access - model_hash[:last_indexed_at] = Time.current - { - index: { - _index: self::INDEX_ALIAS, - _id: model_hash[:id], - data: model_hash - } - } - end - - process_hashes(indexing_hashes) - end - - def find_document(doc_id) - Search::Client.get(id: doc_id, index: self::INDEX_ALIAS) - end - - def delete_document(doc_id) - Search::Client.delete(id: doc_id, index: self::INDEX_ALIAS) - end - - def index_exists?(index_name: self::INDEX_NAME) - Search::Client.indices.exists(index: index_name) - end - - def create_index(index_name: self::INDEX_NAME) - Search::Client.indices.create(index: index_name, body: settings) - end - - def update_index(index_name: self::INDEX_NAME) - return unless dynamic_index_settings.any? - - Search::Client.indices.put_settings(index: index_name, body: dynamic_settings) - end - - def delete_index(index_name: self::INDEX_NAME) - Search::Client.indices.delete(index: index_name) - end - - def refresh_index(index_name: self::INDEX_ALIAS) - Search::Client.indices.refresh(index: index_name) - end - - def add_alias(index_name: self::INDEX_NAME, index_alias: self::INDEX_ALIAS) - Search::Client.indices.put_alias(index: index_name, name: index_alias) - end - - def update_mappings(index_alias: self::INDEX_ALIAS) - Search::Client.indices.put_mapping(index: index_alias, body: self::MAPPINGS) - end - - def document_count - Search::Client.count(index: self::INDEX_ALIAS)["count"] - end - - def search_documents(params:) - set_query_size(params) - query_hash = "Search::QueryBuilders::#{name.demodulize}".safe_constantize.new(params: params).as_hash - - results = search(body: query_hash) - hits = results.dig("hits", "hits").map { |hit| prepare_doc(hit) } - paginate_hits(hits, params) - end - - private - - def prepare_doc(hit) - hit["_source"] - end - - def search(body:) - Search::Client.search(index: self::INDEX_ALIAS, body: body) - end - - def set_query_size(params) - params[:page] ||= self::DEFAULT_PAGE - params[:per_page] ||= self::DEFAULT_PER_PAGE - - # pages start at 0 - params[:size] = params[:per_page].to_i * (params[:page].to_i + 1) - end - - def paginate_hits(hits, params) - start = params[:per_page] * params[:page] - hits[start, params[:per_page]] || [] - end - - def settings - { settings: { index: index_settings } } - end - - def index_settings - raise "Search classes must implement their own index settings" - end - - def dynamic_settings - { settings: { index: dynamic_index_settings } } - end - - def dynamic_index_settings - {} - end - - def process_hashes(indexing_hashes) - indexing_hashes.in_groups_of(1000, false).flat_map do |hashes| - indexing_chunks(hashes).filter_map { |chunk| Search::Client.bulk(body: chunk) } - end - end - - def indexing_chunks(hashes) - return [] unless hashes.any? - return to_enum(__method__, hashes) unless block_given? - - size = 0 - chunk = [] - hashes.each do |hash| - chunk << hash - size += hash.to_s.bytesize - next unless size > 5.megabytes - - yield chunk - size = 0 - chunk = [] - end - yield chunk - end - end - end -end diff --git a/app/services/search/chat_channel_membership.rb b/app/services/search/chat_channel_membership.rb deleted file mode 100644 index ef6533332..000000000 --- a/app/services/search/chat_channel_membership.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Search - class ChatChannelMembership < Base - INDEX_NAME = "chat_channel_memberships_#{Rails.env}".freeze - INDEX_ALIAS = "chat_channel_memberships_#{Rails.env}_alias".freeze - MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/chat_channel_memberships.json"), - symbolize_names: true).freeze - DEFAULT_PAGE = 0 - DEFAULT_PER_PAGE = 30 - - class << self - private - - def index_settings - if Rails.env.production? - { - number_of_shards: 3, - number_of_replicas: 1 - } - else - { - number_of_shards: 1, - number_of_replicas: 0 - } - end - end - end - end -end diff --git a/app/services/search/client.rb b/app/services/search/client.rb deleted file mode 100644 index 985ea3b11..000000000 --- a/app/services/search/client.rb +++ /dev/null @@ -1,65 +0,0 @@ -module Search - # Search client (uses Elasticsearch as a backend) - class Client - class << self - # adapted from https://api.rubyonrails.org/classes/Module.html#method-i-delegate_missing_to - def method_missing(method, *args, &block) - return super unless target.respond_to?(method, false) - - # define for re-use - self.class.define_method(method) do |*new_args, &new_block| - request do - target.public_send(method, *new_args, &new_block) - end - end - - # call the original method, this will only be called the first time - # as in subsequent calls, the newly defined method will prevail - request do - target.public_send(method, *args, &block) - end - end - - # adapted from https://api.rubyonrails.org/classes/Module.html#method-i-delegate_missing_to - def respond_to_missing?(method, _include_all = false) - target.respond_to?(method, false) || super - end - - private - - TRANSPORT_EXCEPTIONS = [ - Elasticsearch::Transport::Transport::Errors::BadRequest, - Elasticsearch::Transport::Transport::Errors::NotFound, - ].freeze - - def request - yield - rescue *TRANSPORT_EXCEPTIONS => e - class_name = e.class.name.demodulize - record_error(e.message, class_name) - - # raise specific error if known, generic one if unknown - error_class = "::Search::Errors::Transport::#{class_name}".safe_constantize - raise error_class, e.message if error_class - - raise ::Search::Errors::TransportError, e.message - end - - def record_error(error_message, class_name) - Honeycomb.add_field("elasticsearch.result", "error") - Honeycomb.add_field("elasticsearch.error", class_name) - ForemStatsClient.increment("elasticsearch.errors", tags: ["error:#{class_name}", "message:#{error_message}"]) - end - - def target - @target ||= Elasticsearch::Client.new( - url: ApplicationConfig["ELASTICSEARCH_URL"], - retry_on_failure: 5, - request_timeout: 30, - adapter: :patron, - log: Rails.env.development?, - ) - end - end - end -end diff --git a/app/services/search/cluster.rb b/app/services/search/cluster.rb deleted file mode 100644 index f1457be23..000000000 --- a/app/services/search/cluster.rb +++ /dev/null @@ -1,72 +0,0 @@ -module Search - class Cluster - SEARCH_CLASSES = [ - Search::ChatChannelMembership, - Search::Listing, - Search::FeedContent, - Search::Tag, - Search::User, - ].freeze - - class << self - def recreate_indexes - delete_indexes - setup_indexes - end - - def setup_indexes - update_settings - create_indexes - update_indexes - add_aliases - update_mappings - end - - def update_settings - Search::Client.cluster.put_settings(body: default_settings) - end - - def create_indexes - SEARCH_CLASSES.each do |search_class| - next if search_class.index_exists? - - search_class.create_index - end - end - - def update_indexes - SEARCH_CLASSES.each(&:update_index) - end - - def add_aliases - SEARCH_CLASSES.each(&:add_alias) - end - - def update_mappings - SEARCH_CLASSES.each(&:update_mappings) - end - - def delete_indexes - return if Rails.env.production? - - SEARCH_CLASSES.each do |search_class| - next unless Search::Client.indices.exists(index: search_class::INDEX_NAME) - - search_class.delete_index - end - end - - private - - def default_settings - { - persistent: { - action: { - auto_create_index: false - } - } - } - end - end - end -end diff --git a/app/services/search/feed_content.rb b/app/services/search/feed_content.rb deleted file mode 100644 index f098202de..000000000 --- a/app/services/search/feed_content.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Search - class FeedContent < Base - INDEX_NAME = "feed_content_#{Rails.env}".freeze - INDEX_ALIAS = "feed_content_#{Rails.env}_alias".freeze - MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/feed_content.json"), symbolize_names: true).freeze - DEFAULT_PAGE = 0 - DEFAULT_PER_PAGE = 60 - - INCLUDED_CLASS_NAMES = %w[Article Comment PodcastEpisode].freeze - - class << self - INCLUDED_CLASS_NAMES.each do |class_name| - define_method("#{class_name.underscore.pluralize}_document_count") do - Search::Client.count( - index: self::INDEX_ALIAS, body: count_filter(class_name), - )["count"] - end - end - - private - - def prepare_doc(hit) - source = hit["_source"] - source["tag_list"] = hit["tags"]&.map { |t| t["name"] } || [] - source["id"] = source["id"].split("_").last.to_i - source["tag_list"] = source["tags"]&.map { |t| t["name"] } || [] - source["flare_tag"] = source["flare_tag_hash"] - source["user_id"] = source.dig("user", "id") - source["highlight"] = hit["highlight"] - source["readable_publish_date"] = source["readable_publish_date_string"] - source["podcast"] = { - "slug" => source["slug"], - "image_url" => source["main_image"], - "title" => source["title"] - } - source["_score"] = hit["_score"] - - source.merge(timestamps_hash(hit)) - end - - def timestamps_hash(hit) - published_at = hit.dig("_source", "published_at") - published_at_timestamp = Time.zone.parse(published_at || "") - { - "published_at_int" => published_at_timestamp.to_i, - "published_timestamp" => published_at - } - end - - def count_filter(class_name) - { - query: { - bool: { - filter: { term: { class_name: class_name } } - } - } - } - end - - def index_settings - if Rails.env.production? - { - number_of_shards: 10, - number_of_replicas: 1 - } - else - { - number_of_shards: 1, - number_of_replicas: 0 - } - end - end - - def dynamic_index_settings - if Rails.env.production? - { refresh_interval: "5s" } - else - { refresh_interval: "1s" } - end - end - end - end -end diff --git a/app/services/search/listing.rb b/app/services/search/listing.rb deleted file mode 100644 index a93d6aaad..000000000 --- a/app/services/search/listing.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Search - class Listing < Base - # We used to use both "classified listing" and "listing" throughout the app. - # We standardized on the latter in most places, but kept the index name here. - INDEX_NAME = "classified_listings_#{Rails.env}".freeze - INDEX_ALIAS = "classified_listings_#{Rails.env}_alias".freeze - MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/listings.json"), symbolize_names: true).freeze - DEFAULT_PAGE = 0 - DEFAULT_PER_PAGE = 75 - - class << self - private - - def index_settings - if Rails.env.production? - { - number_of_shards: 2, - number_of_replicas: 1 - } - else - { - number_of_shards: 1, - number_of_replicas: 0 - } - end - end - end - end -end diff --git a/app/services/search/query_builders/chat_channel_membership.rb b/app/services/search/query_builders/chat_channel_membership.rb deleted file mode 100644 index 5158d6429..000000000 --- a/app/services/search/query_builders/chat_channel_membership.rb +++ /dev/null @@ -1,57 +0,0 @@ -module Search - module QueryBuilders - class ChatChannelMembership < QueryBase - FILTER_KEYS = %i[ - channel_status - channel_type - status - viewable_by - ].freeze - - TERMS_FILTER_KEYS = %i[viewable_by status].freeze - - QUERY_KEYS = %i[ - channel_text - ].freeze - - DEFAULT_PARAMS = { - sort_by: "channel_last_message_at", - sort_direction: "desc", - size: 0 - }.freeze - - def initialize(params:) - super() - - @params = params.deep_symbolize_keys - @params[:viewable_by] = @params[:user_id] - - # TODO: @mstruve: When we want to allow people like admins to - # search ALL memberships this will need to change - @params[:status] = %w[active joining_request] - - build_body - end - - private - - def build_queries - @body[:query] = {} - @body[:query][:bool] = { filter: filter_conditions } - @body[:query][:bool][:must] = query_conditions if query_keys_present? - end - - def filter_conditions - FILTER_KEYS.filter_map do |filter_key| - next if @params[filter_key].blank? || @params[filter_key] == "all" - - if TERMS_FILTER_KEYS.include?(filter_key) - { terms: { filter_key => @params[filter_key] } } - else - { term: { filter_key => @params[filter_key] } } - end - end - end - end - end -end diff --git a/app/services/search/query_builders/feed_content.rb b/app/services/search/query_builders/feed_content.rb deleted file mode 100644 index f81bec168..000000000 --- a/app/services/search/query_builders/feed_content.rb +++ /dev/null @@ -1,202 +0,0 @@ -module Search - module QueryBuilders - class FeedContent < QueryBase - # In order for highlighting to work properly we have to search the fields we want to highlight - QUERY_KEYS = { - search_fields: [ - "tags.keywords_for_search", - "tags.name^3", - "body_text^2", - "title^6", - "user.name", - "user.username", - "organization.name", - ] - }.freeze - - # Search keys from our controllers may not match what we have stored in Elasticsearch so we map them here, - # this allows us to change our Elasticsearch docs without worrying about the frontend - TERM_KEYS = { - id: "id", # NOTE: FeedContent ids are formatted article_#, podcast_episode_#, comment_# - tag_names: "tags.name", - approved: "approved", - user_id: "user.id", - class_name: "class_name", - published: "published", - organization_id: "organization.id" - }.freeze - - RANGE_KEYS = %i[ - published_at - ].freeze - - DEFAULT_PARAMS = { - sort: [ - :_score, - { score: "desc" }, - { hotness_score: "desc" }, - { comments_count: "desc" }, - ], - size: 0 - }.freeze - - HIGHLIGHT_FIELDS = %w[ - body_text - ].freeze - - SOURCE = %i[ - id - class_name - cloudinary_video_url - comments_count - flare_tag_hash - main_image - path - public_reactions_count - published_at - readable_publish_date_string - reading_time - slug - tags - title - video_duration_in_minutes - video_duration_string - user - organization - ].freeze - - attr_accessor :params, :body - - def initialize(params:) - super() - - @params = params.deep_symbolize_keys - - # Default to only showing published articles to start - @params[:published] = true - - build_body - add_function_scoring unless sort_params_present? - end - - private - - def add_sort - return @body[:sort] = DEFAULT_PARAMS[:sort] unless sort_params_present? - - @body[:sort] = { @params[:sort_by] => @params[:sort_direction] } - end - - def add_highlight_fields - highlight_fields = { encoder: "html", pre_tags: "", post_tags: "", fields: {} } - HIGHLIGHT_FIELDS.each do |field_name| - # This hash can be filled with options to further customize our highlighting - # https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-highlighting - highlight_fields[:fields][field_name] = { order: :score, number_of_fragments: 2, fragment_size: 75 } - end - @body[:highlight] = highlight_fields - end - - def filter_source - @body[:_source] = SOURCE - end - - def build_queries - @body[:query] = { bool: {} } - @body[:query][:bool][:filter] = filter_conditions if filter_keys_present? - return unless query_keys_present? - - @body[:query][:bool][:must] = query_conditions - # Boost the score of queries that match these conditions but if they dont match any, - # minimum_should_match: 0, then that is OK - @body[:query][:bool][:should] = match_phrase_conditions - @body[:query][:bool][:minimum_should_match] = 0 - end - - def filter_conditions - filter_conditions = [] - - filter_conditions.concat(term_keys) if terms_keys_present? - filter_conditions.concat(range_keys) if range_keys_present? - - filter_conditions - end - - def filter_keys_present? - range_keys_present? || terms_keys_present? - end - - def terms_keys_present? - TERM_KEYS.detect { |key, _| @params[key].present? } - end - - def range_keys_present? - RANGE_KEYS.detect { |key| @params[key].present? } - end - - def term_keys - TERM_KEYS.filter_map do |term_key, search_key| - next unless @params.key? term_key - - values = Array.wrap(@params[term_key]) - - if params[:tag_boolean_mode] == "all" && term_key == :tag_names - values.map { |val| { term: { search_key => val } } } - else - { terms: { search_key => values } } - end - end.compact - end - - def range_keys - RANGE_KEYS.filter_map do |range_key| - next unless @params.key? range_key - - { range: { range_key => @params[range_key] } } - end - end - - def query_hash(key, fields) - { - simple_query_string: { - query: key.downcase, - fields: fields, - lenient: true, - analyze_wildcard: true, - minimum_should_match: 2 - } - } - end - - def add_function_scoring - @body[:query] = { function_score: { query: @body[:query] } } - @body[:query][:function_score][:functions] = scoring_functions - end - - def scoring_functions - [ - scoring_filter(1000, 2.5), - scoring_filter(500, 2), - scoring_filter(100, 1.5), - ] - end - - def scoring_filter(score, weight) - { - filter: { - range: { - score: { - gte: score - } - } - }, - weight: weight - } - end - - def sort_params_present? - @params[:sort_by] && @params[:sort_direction] - end - end - end -end diff --git a/app/services/search/query_builders/listing.rb b/app/services/search/query_builders/listing.rb deleted file mode 100644 index 4b5332fdf..000000000 --- a/app/services/search/query_builders/listing.rb +++ /dev/null @@ -1,91 +0,0 @@ -module Search - module QueryBuilders - class Listing < QueryBase - TERM_KEYS = %i[ - category - contact_via_connect - location - published - slug - tags - title - user_id - ].freeze - - RANGE_KEYS = %i[ - bumped_at - expires_at - ].freeze - - QUERY_KEYS = %i[ - listing_search - ].freeze - - DEFAULT_PARAMS = { - published: true, - sort_by: "bumped_at", - sort_direction: "desc", - size: 0 - }.freeze - - def initialize(params:) - super() - - @params = params.deep_symbolize_keys - - # For now, we're not allowing searches for Listings that are - # not published. If we want to change this in the future we can do: - # @params[:published] = DEFAULT_PARAMS[:published] unless @params.key?(:published) - @params[:published] = DEFAULT_PARAMS[:published] - - build_body - end - - private - - def build_queries - @body[:query] = { bool: {} } - @body[:query][:bool][:filter] = filter_conditions - @body[:query][:bool][:must] = query_conditions if query_keys_present? - end - - def filter_conditions - filter_conditions = [] - - # term_keys are always present because we are setting a filter of - # published: true by default - filter_conditions.concat term_keys - - filter_conditions.concat range_keys if range_keys_present? - - filter_conditions - end - - def term_keys - TERM_KEYS.flat_map do |term_key| - next unless @params.key? term_key - - values = Array.wrap(@params[term_key]) - - if params[:tag_boolean_mode] == "all" && term_key == :tags - values.map { |tag| { terms: { term_key => Array.wrap(tag) } } } - else - { terms: { term_key => values } } - end - end.compact - end - - def range_keys - RANGE_KEYS.filter_map do |range_key| - next unless @params.key? range_key - - { range: { range_key => @params[range_key] } } - end - end - - def range_keys_present? - RANGE_KEYS.detect { |key| @params[key].present? } - end - end - end -end diff --git a/app/services/search/query_builders/query_base.rb b/app/services/search/query_builders/query_base.rb deleted file mode 100644 index 9f9600ef7..000000000 --- a/app/services/search/query_builders/query_base.rb +++ /dev/null @@ -1,80 +0,0 @@ -module Search - module QueryBuilders - class QueryBase - attr_accessor :params, :body - - def as_hash - @body - end - - private - - def build_body - @body = ActiveSupport::HashWithIndifferentAccess.new - build_queries - add_sort - set_size - add_highlight_fields - filter_source - end - - def add_highlight_fields; end - - def filter_source; end - - def add_sort - sort_key = @params[:sort_by] || self.class::DEFAULT_PARAMS[:sort_by] - sort_direction = @params[:sort_direction] || self.class::DEFAULT_PARAMS[:sort_direction] - @body[:sort] = { - sort_key => sort_direction - } - end - - def set_size - # By default we will return 0 documents if size is not specified - @body[:size] = @params[:size] || self.class::DEFAULT_PARAMS[:size] - end - - def query_keys_present? - self.class::QUERY_KEYS.detect { |key, _| @params[key].present? } - end - - def query_conditions - self.class::QUERY_KEYS.filter_map do |query_key, query_fields| - next if @params[query_key].blank? - - fields = query_fields.presence || [query_key] - - query_hash(@params[query_key], fields) - end - end - - def match_phrase_conditions - self.class::QUERY_KEYS.filter_map do |query_key, _fields| - next if @params[query_key].blank? - - match_phrase(@params[query_key], query_key) - end - end - - def match_phrase(phrase, query_key) - { - match_phrase: { - query_key => { query: phrase, slop: 0 } - } - } - end - - def query_hash(key, fields) - { - simple_query_string: { - query: "#{key}*", - fields: fields, - lenient: true, - analyze_wildcard: true - } - } - end - end - end -end diff --git a/app/services/search/query_builders/user.rb b/app/services/search/query_builders/user.rb deleted file mode 100644 index e832edba0..000000000 --- a/app/services/search/query_builders/user.rb +++ /dev/null @@ -1,52 +0,0 @@ -module Search - module QueryBuilders - class User < QueryBase - QUERY_KEYS = %i[ - search_fields - ].freeze - - # In the event we want to search for documents that do NOT contain certain values - EXCLUDED_TERM_KEYS = { - exclude_roles: "roles" - }.freeze - - DEFAULT_PARAMS = { - sort_by: "hotness_score", - sort_direction: "desc", - size: 0 - }.freeze - - def initialize(params:) - super() - - @params = params.deep_symbolize_keys - - # default to excluding users who are suspended - # TODO: [@jacobherrington] banned can be removed once the data scripts have succesfully run on all Forems - @params[:exclude_roles] = %w[suspended banned] - - build_body - end - - private - - def build_queries - @body[:query] = { bool: {} } - @body[:query][:bool][:must] = query_conditions if query_keys_present? - @body[:query][:bool][:must_not] = excluded_term_keys if excluded_term_keys_present? - end - - def excluded_term_keys_present? - self.class::EXCLUDED_TERM_KEYS.detect { |key, _| @params[key].present? } - end - - def excluded_term_keys - EXCLUDED_TERM_KEYS.filter_map do |term_key, search_key| - next unless @params.key? term_key - - { terms: { search_key => Array.wrap(@params[term_key]) } } - end - end - end - end -end diff --git a/app/services/search/reading_list.rb b/app/services/search/reading_list.rb deleted file mode 100644 index cc696a438..000000000 --- a/app/services/search/reading_list.rb +++ /dev/null @@ -1,78 +0,0 @@ -module Search - # This class does not inherit from Search::Base like our other search classes bc it - # is using the FeedContent index to search for ReadingList reaction articles rather than its - # own separate index. The primary function of this class is to help combine and parse reaction - # data with article Elasticsearch data to populate the Reading List view. - class ReadingList - DEFAULT_PAGE = 0 - DEFAULT_PER_PAGE = 60 - DEFAULT_STATUS = %w[valid confirmed].freeze - - # Using a class method here to follow the pattern of the other Search classes - def self.search_documents(params:, user:) - new(params: params, user: user).reading_list_reactions - end - - def initialize(params:, user:) - self.status = params.delete(:status) || DEFAULT_STATUS - self.view_page = params.delete(:page) || DEFAULT_PAGE - self.view_per_page = params.delete(:per_page) || DEFAULT_PER_PAGE - self.user = user - self.search_params = params - end - - def reading_list_reactions - ordered_articles = parse_and_order_articles(article_docs) - { "reactions" => paginate_articles(ordered_articles), "total" => total } - end - - private - - attr_accessor :search_params, :user, :status, :view_page, :view_per_page, :total - - def paginate_articles(ordered_articles) - start = view_per_page * view_page - ordered_articles[start, view_per_page] || [] - end - - def article_docs - return @article_docs if @article_docs - - # Gather articles from Elasticsearch based on search criteria containing - # tags, text search, status, and the list of IDs of all articles in a user's - # reading list - docs = FeedContent.search_documents( - params: search_params.merge( - id: search_ids, - class_name: "Article", - page: 0, - per_page: reading_list_article_ids.count, - ), - ) - self.total = docs.count - @article_docs = docs.index_by { |doc| doc["id"] } - end - - def reading_list_article_ids - # Collect all reading list IDs and article IDs for a user - @reading_list_article_ids ||= user.reactions.readinglist.where(status: status).order(id: :desc).pluck( - :reactable_id, :id - ).to_h - end - - def search_ids - reading_list_article_ids.keys.map { |id| "article_#{id}" } - end - - def parse_and_order_articles(articles) - # Combines reaction and article data to create hashes that contain the fields - # the reading list view needs. Ensures articles are returned in order of reaction ID - reading_list_article_ids.filter_map do |article_id, reaction_id| - found_article_doc = articles[article_id] - next unless found_article_doc - - { "id" => reaction_id, "user_id" => user.id, "reactable" => articles[article_id] } - end - end - end -end diff --git a/app/services/search/tag.rb b/app/services/search/tag.rb deleted file mode 100644 index 53c965b45..000000000 --- a/app/services/search/tag.rb +++ /dev/null @@ -1,45 +0,0 @@ -module Search - class Tag < Base - INDEX_NAME = "tags_#{Rails.env}".freeze - INDEX_ALIAS = "tags_#{Rails.env}_alias".freeze - MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/tags.json"), symbolize_names: true).freeze - - class << self - def search_documents(query_string) - results = search(body: query(query_string)) - results.dig("hits", "hits").map { |tag_doc| tag_doc["_source"] } - end - - private - - def query(query_string) - { - query: { - query_string: { - query: query_string, - analyze_wildcard: true, - allow_leading_wildcard: false - } - }, - sort: { - hotness_score: "desc" - } - } - end - - def index_settings - if Rails.env.production? - { - number_of_shards: 1, - number_of_replicas: 1 - } - else - { - number_of_shards: 1, - number_of_replicas: 0 - } - end - end - end - end -end diff --git a/app/services/search/user.rb b/app/services/search/user.rb deleted file mode 100644 index ea9ce87b4..000000000 --- a/app/services/search/user.rb +++ /dev/null @@ -1,80 +0,0 @@ -module Search - class User < Base - INDEX_NAME = "users_#{Rails.env}".freeze - INDEX_ALIAS = "users_#{Rails.env}_alias".freeze - MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/users.json"), symbolize_names: true).freeze - DEFAULT_PAGE = 0 - DEFAULT_PER_PAGE = 20 - - class << self - def search_usernames(username) - results = search(body: username_query(username)) - results.dig("hits", "hits").map do |doc| - source = doc["_source"] - { - "username" => source["username"], - "name" => source["name"], - "profile_image_90" => source["profile_image_90"] - } - end - end - - private - - def username_query(username) - { - query: { - query_string: { - query: "username:#{username}*", - analyze_wildcard: true, - allow_leading_wildcard: false - } - }, - size: Search::Postgres::Username::MAX_RESULTS # Limit the number of results we return to the frontend - } - end - - def prepare_doc(hit) - source = hit["_source"] - { - "user" => { - "username" => source["username"], - "name" => source["username"], - "profile_image_90" => source["profile_image_90"] - }, - "title" => source["name"], - "path" => source["path"], - "id" => source["id"], - "class_name" => "User", - "public_reactions_count" => source["public_reactions_count"], - "comments_count" => source["comments_count"], - "badge_achievements_count" => source["badge_achievements_count"], - "last_comment_at" => source["last_comment_at"], - "user_id" => source["id"] - } - end - - def index_settings - if Rails.env.production? - { - number_of_shards: 10, - number_of_replicas: 1 - } - else - { - number_of_shards: 1, - number_of_replicas: 0 - } - end - end - - def dynamic_index_settings - if Rails.env.production? - { refresh_interval: "10s" } - else - { refresh_interval: "1s" } - end - end - end - end -end diff --git a/app/services/users/delete_activity.rb b/app/services/users/delete_activity.rb index cb6f9dd24..83054c027 100644 --- a/app/services/users/delete_activity.rb +++ b/app/services/users/delete_activity.rb @@ -2,8 +2,6 @@ module Users module DeleteActivity module_function - # If you're removing data that is in Elasticsearch, make sure to use - # .destroy_all to trigger the callback to remove the document(s) def call(user) delete_social_media(user) delete_profile_info(user) diff --git a/app/services/users/delete_articles.rb b/app/services/users/delete_articles.rb index 33d8945e1..64853fa2b 100644 --- a/app/services/users/delete_articles.rb +++ b/app/services/users/delete_articles.rb @@ -12,10 +12,8 @@ module Users comment.reactions.delete_all EdgeCache::BustComment.call(comment.commentable) EdgeCache::BustUser.call(comment.user) - comment.remove_from_elasticsearch comment.delete end - article.remove_from_elasticsearch article.delete article.purge end diff --git a/app/services/users/delete_comments.rb b/app/services/users/delete_comments.rb index 7659dfc77..273d30bed 100644 --- a/app/services/users/delete_comments.rb +++ b/app/services/users/delete_comments.rb @@ -9,7 +9,6 @@ module Users comment.reactions.delete_all EdgeCache::BustComment.call(comment.commentable) comment.remove_notifications - comment.remove_from_elasticsearch comment.delete end EdgeCache::BustUser.call(user) diff --git a/app/workers/articles/score_calc_worker.rb b/app/workers/articles/score_calc_worker.rb index b6fa3c573..dddf0cce3 100644 --- a/app/workers/articles/score_calc_worker.rb +++ b/app/workers/articles/score_calc_worker.rb @@ -9,7 +9,6 @@ module Articles return unless article article.update_score - article.index_to_elasticsearch_inline end end end diff --git a/app/workers/chat_channels/indexes_memberships_worker.rb b/app/workers/chat_channels/indexes_memberships_worker.rb deleted file mode 100644 index 31f0fbcea..000000000 --- a/app/workers/chat_channels/indexes_memberships_worker.rb +++ /dev/null @@ -1,12 +0,0 @@ -module ChatChannels - class IndexesMembershipsWorker - include Sidekiq::Worker - - sidekiq_options queue: :high_priority, lock: :until_executing - - def perform(chat_channel_id) - chat_channel = ChatChannel.find(chat_channel_id) - chat_channel.chat_channel_memberships.each(&:index_to_elasticsearch) - end - end -end diff --git a/app/workers/metrics/record_data_counts_worker.rb b/app/workers/metrics/record_data_counts_worker.rb index 2e900646f..575932fe2 100644 --- a/app/workers/metrics/record_data_counts_worker.rb +++ b/app/workers/metrics/record_data_counts_worker.rb @@ -14,16 +14,6 @@ module Metrics Rails.logger.info(message: "db_table_size", table_info: { table_name: model.table_name, table_size: db_count }) ForemStatsClient.gauge("postgres.db_table_size", db_count, tags: ["table_name:#{model.table_name}"]) - - next unless model.const_defined?(:SEARCH_CLASS) - - document_count = if model::SEARCH_CLASS.respond_to?("#{model.to_s.underscore.pluralize}_document_count") - model::SEARCH_CLASS.public_send("#{model.to_s.underscore.pluralize}_document_count") - else - model::SEARCH_CLASS.document_count - end - ForemStatsClient.gauge("elasticsearch.document_count", document_count, - tags: ["table_name:#{model.table_name}"]) end end end diff --git a/app/workers/rating_votes/assign_rating_worker.rb b/app/workers/rating_votes/assign_rating_worker.rb index 2a1a55167..9a13a54e0 100644 --- a/app/workers/rating_votes/assign_rating_worker.rb +++ b/app/workers/rating_votes/assign_rating_worker.rb @@ -16,7 +16,6 @@ module RatingVotes experience_level_rating_distribution: ratings.max - ratings.min, last_experience_level_rating_at: Time.current, ) - article.index_to_elasticsearch_inline end end end diff --git a/app/workers/search/bulk_index_worker.rb b/app/workers/search/bulk_index_worker.rb deleted file mode 100644 index 9d81ba0f1..000000000 --- a/app/workers/search/bulk_index_worker.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Search - class BulkIndexWorker - include Sidekiq::Worker - - sidekiq_options queue: :high_priority, lock: :until_executing - - def perform(object_class, ids) - data_hashes = object_class.constantize - .eager_load_serialized_data - .where(id: ids) - .find_each.map(&:serialized_search_hash) - - search_class = object_class.constantize::SEARCH_CLASS - - search_class.bulk_index(data_hashes) - end - end -end diff --git a/app/workers/search/index_worker.rb b/app/workers/search/index_worker.rb deleted file mode 100644 index 247f7f846..000000000 --- a/app/workers/search/index_worker.rb +++ /dev/null @@ -1,12 +0,0 @@ -module Search - class IndexWorker - include Sidekiq::Worker - - sidekiq_options queue: :high_priority, lock: :until_executing - - def perform(object_class, id) - object = object_class.constantize.find_by(id: id) - object&.index_to_elasticsearch_inline - end - end -end diff --git a/app/workers/search/remove_from_index_worker.rb b/app/workers/search/remove_from_index_worker.rb deleted file mode 100644 index fecf9db5d..000000000 --- a/app/workers/search/remove_from_index_worker.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Search - class RemoveFromIndexWorker - include Sidekiq::Worker - - sidekiq_options queue: :medium_priority, lock: :until_executing - - def perform(search_class, id) - search_class.safe_constantize.delete_document(id) - rescue Search::Errors::Transport::NotFound - # Often race conditions cause us never to index a document and - # we end up with an error when trying to remove it. Because - # we have count checks that run every hour to track the counts in - # Elasticsearch and db to ensure they match we can ignore this error - nil - end - end -end diff --git a/bin/setup b/bin/setup index 98ecea7bf..e0fb2b846 100755 --- a/bin/setup +++ b/bin/setup @@ -30,9 +30,6 @@ FileUtils.chdir APP_ROOT do FileUtils.cp "config/database.yml.sample", "config/database.yml" end - puts "\n== Preparing Test Elasticsearch ==" - system! 'RAILS_ENV="test" bin/rails search:setup' - puts "\n== Initializing the application ==" system! "bin/rails app_initializer:setup forem:setup" diff --git a/config/elasticsearch/mappings/chat_channel_memberships.json b/config/elasticsearch/mappings/chat_channel_memberships.json deleted file mode 100644 index 1e188ea99..000000000 --- a/config/elasticsearch/mappings/chat_channel_memberships.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "status": { - "type": "keyword" - }, - "viewable_by": { - "type": "keyword" - }, - "chat_channel_id": { - "type": "keyword" - }, - "last_opened_at": { - "type": "date" - }, - "channel_text": { - "type": "text" - }, - "channel_last_message_at": { - "type": "date" - }, - "channel_status": { - "type": "keyword" - }, - "channel_status": { - "type": "keyword" - }, - "channel_type": { - "type": "keyword" - }, - "channel_username": { - "type": "keyword" - }, - "channel_name": { - "type": "text" - }, - "channel_image": { - "type": "keyword" - }, - "channel_modified_slug": { - "type": "text", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "channel_messages_count": { - "type": "integer" - }, - "channel_discoverable": { - "type": "boolean" - }, - "last_indexed_at": { - "type": "date" - } - } -} diff --git a/config/elasticsearch/mappings/feed_content.json b/config/elasticsearch/mappings/feed_content.json deleted file mode 100644 index 04124a792..000000000 --- a/config/elasticsearch/mappings/feed_content.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "approved": { - "type": "boolean" - }, - "body_text": { - "type": "text", - "copy_to": "search_fields" - }, - "class_name": { - "type": "keyword" - }, - "cloudinary_video_url": { - "type": "keyword" - }, - "comments_count": { - "type": "integer" - }, - "experience_level_rating": { - "type": "integer" - }, - "experience_level_rating_distribution": { - "type": "integer" - }, - "featured": { - "type": "boolean" - }, - "featured_number": { - "type": "integer" - }, - "flare_tag_hash": { - "dynamic": "strict", - "properties": { - "name": { - "type": "keyword" - }, - "bg_color_hex": { - "type": "keyword" - }, - "text_color_hex": { - "type": "keyword" - } - } - }, - "hotness_score": { - "type": "integer" - }, - "last_indexed_at": { - "type": "date" - }, - "main_image": { - "type": "keyword" - }, - "organization": { - "dynamic": "strict", - "properties": { - "slug": { - "type": "keyword" - }, - "name": { - "type": "text", - "copy_to": "search_fields", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "id": { - "type": "keyword" - }, - "profile_image_90": { - "type": "keyword" - } - } - }, - "path": { - "type": "keyword" - }, - // deprecated in favor of public_reactions_count, to be removed - "positive_reactions_count": { - "type": "integer" - }, - "public_reactions_count": { - "type": "integer" - }, - "published": { - "type": "boolean" - }, - "published_at": { - "type": "date" - }, - "quote": { - "type": "text" - }, - "reactions_count": { - "type": "integer" - }, - "readable_publish_date": { - "type": "date" - }, - "readable_publish_date_string": { - "type": "keyword" - }, - "reading_time": { - "type": "integer" - }, - "score": { - "type": "integer" - }, - "search_fields": { - "type": "text" - }, - "search_score": { - "type": "integer" - }, - "slug": { - "type": "keyword" - }, - "subtitle": { - "type": "text" - }, - "summary": { - "type": "text" - }, - "tags": { - "dynamic": "strict", - "properties": { - "name": { - "type": "keyword", - "copy_to": "search_fields" - }, - "keywords_for_search": { - "type": "keyword", - "copy_to": "search_fields" - } - } - }, - "title": { - "type": "text", - "copy_to": "search_fields", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "user": { - "dynamic": "strict", - "properties": { - "username": { - "type": "keyword", - "copy_to": "search_fields" - }, - "name": { - "type": "text", - "copy_to": "search_fields", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "id": { - "type": "keyword" - }, - "profile_image_90": { - "type": "keyword" - } - } - }, - "video_duration_in_minutes": { - "type": "integer" - }, - "video_duration_string": { - "type": "keyword" - }, - "website_url": { - "type": "keyword" - } - } -} diff --git a/config/elasticsearch/mappings/listings.json b/config/elasticsearch/mappings/listings.json deleted file mode 100644 index e832e30af..000000000 --- a/config/elasticsearch/mappings/listings.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "author": { - "dynamic": "strict", - "properties": { - "username": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "profile_image_90": { - "type": "keyword" - } - } - }, - "body_markdown": { - "type": "text", - "copy_to": "listing_search" - }, - "originally_published_at": { - "type": "date" - }, - "bumped_at": { - "type": "date" - }, - "category": { - "type": "keyword" - }, - "listing_search": { - "type": "text" - }, - "contact_via_connect": { - "type": "boolean" - }, - "expires_at": { - "type": "date" - }, - "last_indexed_at": { - "type": "date" - }, - "location": { - "type": "text", - "copy_to": "listing_search", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "processed_html": { - "type": "keyword" - }, - "published": { - "type": "boolean" - }, - "slug": { - "type": "text", - "copy_to": "listing_search", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "tags": { - "type": "keyword", - "copy_to": "listing_search" - }, - "title": { - "type": "text", - "copy_to": "listing_search", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "user_id": { - "type": "keyword" - } - } -} diff --git a/config/elasticsearch/mappings/reactions.json b/config/elasticsearch/mappings/reactions.json deleted file mode 100644 index 30dab30fd..000000000 --- a/config/elasticsearch/mappings/reactions.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "category": { - "type": "keyword" - }, - "created_at": { - "type": "date" - }, - "last_indexed_at": { - "type": "date" - }, - "reactable": { - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "body_text": { - "type": "text", - "copy_to": "search_fields" - }, - "class_name": { - "type": "keyword" - }, - "path": { - "type": "keyword" - }, - "published_date_string": { - "type": "keyword" - }, - "reading_time": { - "type": "integer" - }, - "tags": { - "dynamic": "strict", - "properties": { - "name": { - "type": "keyword", - "copy_to": "search_fields" - }, - "keywords_for_search": { - "type": "keyword", - "copy_to": "search_fields" - } - } - }, - "title": { - "type": "text", - "copy_to": "search_fields", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "user": { - "dynamic": "strict", - "properties": { - "username": { - "type": "keyword", - "copy_to": "search_fields" - }, - "name": { - "type": "text", - "copy_to": "search_fields", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "id": { - "type": "keyword" - }, - "profile_image_90": { - "type": "keyword" - } - } - } - } - }, - "search_fields": { - "type": "text" - }, - "status": { - "type": "keyword" - }, - "user_id": { - "type": "keyword" - } - } -} diff --git a/config/elasticsearch/mappings/tags.json b/config/elasticsearch/mappings/tags.json deleted file mode 100644 index ce5374ef4..000000000 --- a/config/elasticsearch/mappings/tags.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "last_indexed_at": { - "type": "date" - }, - "name": { - "type": "text", - "fields": { - "raw": { - "type": "keyword" - } - } - }, - "hotness_score": { - "type": "integer" - }, - "supported": { - "type": "boolean" - }, - "short_summary": { - "type": "text" - }, - "rules_html": { - "type": "text" - } - } -} diff --git a/config/elasticsearch/mappings/users.json b/config/elasticsearch/mappings/users.json deleted file mode 100644 index c7f6e594a..000000000 --- a/config/elasticsearch/mappings/users.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "dynamic": "strict", - "properties": { - "id": { - "type": "keyword" - }, - "available_for": { - "type": "keyword", - "copy_to": "search_fields" - }, - "badge_achievements_count": { - "type": "integer" - }, - "comments_count": { - "type": "integer" - }, - "employer_name": { - "type": "keyword", - "copy_to": "search_fields" - }, - "hotness_score": { - "type": "integer" - }, - "last_comment_at": { - "type": "date" - }, - "last_indexed_at": { - "type": "date" - }, - "mostly_work_with": { - "type": "keyword", - "copy_to": "search_fields" - }, - "name": { - "type": "text", - "copy_to": "search_fields" - }, - "path": { - "type": "keyword" - }, - // deprecated in favor of public_reactions_count, to be removed - "positive_reactions_count": { - "type": "integer" - }, - "public_reactions_count": { - "type": "integer" - }, - "profile_image_90": { - "type": "keyword" - }, - "reactions_count": { - "type": "integer" - }, - "roles": { - "type": "keyword" - }, - "search_fields": { - "type": "text" - }, - "username": { - "type": "keyword", - "copy_to": "search_fields" - }, - "profile_fields": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - }, - "custom_profile_fields": { - "type": "nested", - "properties": { - "name": { - "type": "keyword" - }, - "value": { - "type": "keyword" - } - } - } - } -} diff --git a/config/initializers/cypress_rails.rb b/config/initializers/cypress_rails.rb index 14f2b96a7..857fb06ea 100644 --- a/config/initializers/cypress_rails.rb +++ b/config/initializers/cypress_rails.rb @@ -22,7 +22,6 @@ end CypressRails.hooks.before_server_stop do # Called once, at_exit puts "Cleaning up and stopping server for end to end tests." - Rake::Task["search:destroy"].invoke Rake::Task["db:truncate_all"].invoke puts "The end to end test server shutdown gracefully." end diff --git a/config/routes.rb b/config/routes.rb index 6834c380b..5095553f4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,7 +89,6 @@ Rails.application.routes.draw do resources :health_checks, only: [] do collection do get :app - get :search get :database get :cache end @@ -212,7 +211,6 @@ Rails.application.routes.draw do get "/search/tags", to: "search#tags" get "/search/chat_channels", to: "search#chat_channels" get "/search/listings", to: "search#listings" - get "/search/users", to: "search#users" get "/search/usernames", to: "search#usernames" get "/search/feed_content", to: "search#feed_content" get "/search/reactions", to: "search#reactions" diff --git a/container-compose.yml b/container-compose.yml index f21c99aca..00d9d608f 100644 --- a/container-compose.yml +++ b/container-compose.yml @@ -8,13 +8,11 @@ services: depends_on: - bundle - db - - elasticsearch - redis - yarn environment: RAILS_ENV: development DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 REDIS_SESSIONS_URL: redis://redis:6379 REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 @@ -24,7 +22,7 @@ services: APP_DOMAIN: rails volumes: - .:/opt/apps/forem:z - entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "http://elasticsearch:9200", "-wait", "tcp://redis:6379", "-wait", "file:///opt/apps/forem/vendor/bundle/.bundle_finished", "-timeout", "2700s"] + entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "tcp://redis:6379", "-wait", "file:///opt/apps/forem/vendor/bundle/.bundle_finished", "-timeout", "2700s"] command: [ "bash", "-c", "./scripts/entrypoint.sh bootstrap && bundle exec rails server -b 0.0.0.0 -p 3000"] bundle: @@ -36,7 +34,6 @@ services: REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:z command: ["./scripts/bundle.sh"] @@ -50,7 +47,6 @@ services: REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:z command: [ "bash", "-c", "yarn install --dev"] @@ -67,7 +63,6 @@ services: REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:z entrypoint: ["dockerize", "-wait", "file:///opt/apps/forem/node_modules/.bin/webpack-dev-server", "-timeout", "300s"] @@ -80,17 +75,15 @@ services: - rails - redis - db - - elasticsearch environment: RAILS_ENV: development REDIS_SESSIONS_URL: redis://redis:6379 REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:z - entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "http://elasticsearch:9200", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "300s"] + entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "300s"] command: ["bundle", "exec", "rake","db:seed"] sidekiq: @@ -100,17 +93,15 @@ services: - rails - redis - db - - elasticsearch environment: RAILS_ENV: development REDIS_SESSIONS_URL: redis://redis:6379 REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:z - entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "http://elasticsearch:9200", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "300s"] + entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "300s"] command: ["bundle", "exec", "sidekiq","-c","2"] db: @@ -131,23 +122,5 @@ services: ports: - "6379:6379" - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:7.5.2 - container_name: forem_elasticsearch - environment: - - cluster.name=forem - - bootstrap.memory_lock=true - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - "discovery.type=single-node" - - xpack.security.enabled=false - - xpack.monitoring.enabled=false - - xpack.graph.enabled=false - - xpack.watcher.enabled=false - ports: - - "9200:9200" - # volumes: - # - es_data:/usr/share/elasticsearch/data:Z - volumes: db_data: - # es_data: diff --git a/docker-compose.yml b/docker-compose.yml index 9fd4a60c9..6f9ecdc1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,13 +9,11 @@ services: depends_on: - bundle - db - - elasticsearch - redis - yarn environment: RAILS_ENV: development DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 REDIS_SESSIONS_URL: redis://redis:6379 REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 @@ -25,7 +23,7 @@ services: APP_DOMAIN: rails volumes: - .:/opt/apps/forem:delegated - entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "http://elasticsearch:9200", "-wait", "tcp://redis:6379", "-wait", "file:///opt/apps/forem/vendor/bundle/.bundle_finished", "-timeout", "2700s", "-wait-retry-interval", "10s"] + entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "tcp://redis:6379", "-wait", "file:///opt/apps/forem/vendor/bundle/.bundle_finished", "-timeout", "2700s", "-wait-retry-interval", "10s"] command: [ "bash", "-c", "./scripts/entrypoint.sh bootstrap && bundle exec rails server -b 0.0.0.0 -p 3000"] bundle: @@ -38,7 +36,6 @@ services: REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:delegated command: ["./scripts/bundle.sh"] @@ -53,7 +50,6 @@ services: REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:delegated command: yarn install --dev @@ -72,7 +68,6 @@ services: REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:delegated entrypoint: ["dockerize", "-wait", "file:///opt/apps/forem/node_modules/.bin/webpack-dev-server", "-wait", "file:///opt/apps/forem/vendor/bundle/.bundle_finished", "-timeout", "2700s", "-wait-retry-interval", "10s"] @@ -86,17 +81,15 @@ services: - rails - redis - db - - elasticsearch environment: RAILS_ENV: development REDIS_SESSIONS_URL: redis://redis:6379 REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:delegated - entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "http://elasticsearch:9200", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "2700s", "-wait-retry-interval", "20s"] + entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "2700s", "-wait-retry-interval", "20s"] command: ["bundle", "exec", "rake","db:seed"] sidekiq: @@ -107,17 +100,15 @@ services: - rails - redis - db - - elasticsearch environment: RAILS_ENV: development REDIS_SESSIONS_URL: redis://redis:6379 REDIS_SIDEKIQ_URL: redis://redis:6379 REDIS_URL: redis://redis:6379 DATABASE_URL: postgresql://forem:forem@db:5432/PracticalDeveloper_development - ELASTICSEARCH_URL: http://elasticsearch:9200 volumes: - .:/opt/apps/forem:delegated - entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "http://elasticsearch:9200", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "2700s", "-wait-retry-interval", "20s"] + entrypoint: ["dockerize", "-wait", "tcp://db:5432", "-wait", "tcp://redis:6379", "-wait", "http://rails:3000", "-timeout", "2700s", "-wait-retry-interval", "20s"] command: ["bundle", "exec", "sidekiq","-c","2"] db: @@ -138,28 +129,5 @@ services: ports: - "6379:6379" - elasticsearch: - image: docker.elastic.co/elasticsearch/elasticsearch:7.5.2 - container_name: forem_elasticsearch - environment: - - cluster.name=forem - - bootstrap.memory_lock=true - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - "discovery.type=single-node" - - xpack.security.enabled=false - - xpack.monitoring.enabled=false - - xpack.graph.enabled=false - - xpack.watcher.enabled=false - volumes: - - es_data:/usr/share/elasticsearch/data:delegated - ports: - - "9200:9200" - ulimits: - memlock: - soft: -1 - hard: -1 - volumes: db_data: - es_data: - diff --git a/docs/admin/admin-search.md b/docs/admin/admin-search.md index 2beff7180..3d671f795 100644 --- a/docs/admin/admin-search.md +++ b/docs/admin/admin-search.md @@ -16,9 +16,9 @@ The view responsible for managing user reports can be found at action of the [`admin/feedback_messages_controller`][feedback_messages]. For Forem, Ransack is being used exclusively in admin, for search problems in -other parts of the app we use [Elasticsearch][elasticsearch]. +other parts of the app we use [PostgreSQL Full Text Search][postgres_fts]. [feedback_messages]: https://github.com/forem/forem/blob/4e41e4a2ac893fa2a6c36990cfe475858ffb086a/app/controllers/admin/feedback_messages_controller.rb#L4 [ransack]: https://github.com/activerecord-hackery/ransack -[elasticsearch]: /backend/elasticsearch +[postgres_fts]: https://www.postgresql.org/docs/11/textsearch.html diff --git a/docs/backend/data-update-scripts.md b/docs/backend/data-update-scripts.md index 8e78d5f8f..7143f8676 100644 --- a/docs/backend/data-update-scripts.md +++ b/docs/backend/data-update-scripts.md @@ -8,9 +8,7 @@ Data Update Scripts were introduced in [this PR](https://github.com/forem/forem/pull/6025) and allow us to run any data updates we might need. For example, if we added a column to the database and then wanted to backfill that column with data, rather than going and manually -doing it in a console, we would use a DataUpdateScript. Another example might be -adding a new attribute to Elasticsearch. We could then use a DataUpdateScript to -reindex all of our models. +doing it in a console, we would use a DataUpdateScript. ## How it works diff --git a/docs/backend/elasticsearch.md b/docs/backend/elasticsearch.md deleted file mode 100644 index 6a68c6735..000000000 --- a/docs/backend/elasticsearch.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Elasticsearch ---- - -# What is [Elasticsearch](https://www.elastic.co/what-is/elasticsearch)? - -> Elasticsearch is a distributed, open-source search and analytics engine for -> all types of data, including textual, numerical, geospatial, structured, and -> unstructured. Elasticsearch is built on Apache Lucene and was first released -> in 2010 by Elastic. - -# How Forem uses Elasticsearch - -### Searchable Models - -At Forem we use Elasticsearch for all of our user-facing searching needs. Models -that are searched using Elasticsearch are: - -- Tags -- Listings -- Chat Channels -- Users -- Podcast Episodes -- Comments -- Articles - -### Index Setup - -The above models are organized in Elasticsearch in different -[indexes](https://www.elastic.co/blog/what-is-an-elasticsearch-index) to make -searching more accurate and performant. The index breakdown is: - -- Tags index -> Tags -- Listings index -> Listings -- Chat Channel Membership index -> Chat Channels -- User's index -> Users -- Feed Content index -> Podcast Episodes, Comments, and Articles - -You will notice that Podcast Episodes, Comments, and Articles all share the same -index. This allows us to search and aggregate across all of these documents at -the same time. These documents also share a lot of similar fields so putting -them in the same index made sense. In the future, depending on our search needs -this index structure might change. - -Each index that is listed above has a corresponding -[mapping](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html) -that defines what fields can and cannot be indexed to it. These mappings are -similar to a db schema. In our case, all of our index mappings are defined as -[`strict`](https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html#dynamic) -which means if we try to index a field that is not defined it will throw an -error. Mappings for each index can be found in -`app/config/elasticsearch/mappings`. - -### Indexing documents into Elasticsearch - -In order to index the data from Postgres into Elasticsearch, we rely on -callbacks in our ActiveRecord models. Whenever a model is created or updated we -use an `after_commit` callback to enqueue a `Search::IndexWorker` which handles -indexing the document into Elasticsearch. In order to translate our ActiveRecord -data to Elasticsearch we use serializers. Each model has its own serializer -which can be found in `app/serializers/search` - -### Searching Elasticsearch - -Once the documents are in Elasticsearch then we can search for them. This is -handled by code in our `app/services/search` directory. Here you will see a -collection of classes used to help us index and search documents in -Elasticsearch. The nested `query_builders` directory contains all the logic we -need to help us build complex search queries to send to Elasticsearch. - -# Working With Elasticsearch - -## How to add a new field to Elasticsearch - -When adding a new field to Elasticsearch here are the steps you have to follow: - -1. Add field to mapping by editing the appropriate JSON file in - `app/config/elasticsearch/mappings`. If you plan to test locally you will - need to run `bin/setup` to update your local Elasticsearch mappings. -2. Add field to serializer for model located in `app/serializers/search`. -3. If the new field needs to be backfilled write a DataUpdateScript to reindex - all affected models so the field is available and populated in Elasticsearch -4. DEPLOY - Before you can start using a new field you need to ensure that it is - added to Elasticsearch and populated properly. The worker that runs the - DataUpdateScript will run 5 min after a deploy completes. This means you have - to deploy the code for adding the field separate from the code that will use - it. -5. Add code to search for your new field. You likely will need to update the - allowed params in the searches controller as well as the pertinent query - builder. -6. DEPLOY new search code -7. DONE! diff --git a/docs/backend/readme.md b/docs/backend/readme.md index a1b917644..553924150 100644 --- a/docs/backend/readme.md +++ b/docs/backend/readme.md @@ -10,7 +10,6 @@ items: - authorization.md - configuration.md - data-update-scripts.md - - elasticsearch.md - emails.md - fastly.md - internationalization.md diff --git a/docs/installation/containers.md b/docs/installation/containers.md index b784e479d..2d4891b16 100644 --- a/docs/installation/containers.md +++ b/docs/installation/containers.md @@ -108,12 +108,6 @@ The script executes the following steps: ### Docker on Mac -- Should you experience problems with the Elasticsearch container, try to - increase the memory and/or swap allocation for Docker. On macOS this can be - done via the GUI: - - ![docker gui](https://user-images.githubusercontent.com/47985/74210448-b63b7c80-4c83-11ea-959b-02249b2a6952.png) - - In case `rails server` starts with the following message: ```shell diff --git a/docs/installation/linux.md b/docs/installation/linux.md index d20f7fb69..aeaf0ae64 100644 --- a/docs/installation/linux.md +++ b/docs/installation/linux.md @@ -66,21 +66,6 @@ We recommend following Digital Ocean's extensive guides [How to Install and Secure Redis](https://www.digitalocean.com/community/tutorial_collections/how-to-install-and-secure-redis) to setup Redis. -### Elasticsearch - -Forem requires a version of Elasticsearch between 7.1 and 7.5. Version 7.6 is -not supported. We recommend version 7.5.2. - -We recommend following -[Elasticsearch's guide for installing on Linux](https://www.elastic.co/guide/en/elasticsearch/reference/7.5/targz.html#install-linux). - -Elasticsearch is also available as as -[Debian package](https://www.elastic.co/guide/en/elasticsearch/reference/7.5/deb.html) -or a -[RPM package](https://www.elastic.co/guide/en/elasticsearch/reference/7.5/rpm.html). - -NOTE: Make sure to download **the OSS version**, `elasticsearch-oss`. - ## Installing Forem 1. Fork Forem's repository, e.g. diff --git a/docs/installation/mac.md b/docs/installation/mac.md index 19f0ddb33..471a38a27 100644 --- a/docs/installation/mac.md +++ b/docs/installation/mac.md @@ -8,18 +8,25 @@ title: macOS ### Ruby -1. **Note:** MacOS ships with a version of Ruby, needed for various operating systems. - To avoid causing an issue with your operating system you should use a version manager for Ruby. +1. **Note:** MacOS ships with a version of Ruby, needed for various operating + systems. To avoid causing an issue with your operating system you should use + a version manager for Ruby. + + If you don't already have a Ruby version manager, we highly recommend + [rbenv](https://github.com/rbenv/rbenv). This will allow you to have + different versions running on a per project basis. The MacOS system version + of Ruby will stay intact while giving you the ability to use the version + needed for this Forem project. Please follow their + [installation guide](https://github.com/rbenv/rbenv#installation). - If you don't already have a Ruby version manager, we highly recommend [rbenv](https://github.com/rbenv/rbenv). This will allow you to have different versions running on a per project basis. The MacOS system version of Ruby will stay intact while giving you the ability to use the version needed for this Forem project. -Please follow their [installation guide](https://github.com/rbenv/rbenv#installation). 2. With the Ruby version manager, install the Ruby version listed on our badge. (i.e. with rbenv: `rbenv install $(cat .ruby-version)`) - - **Note:** The repository must be forked and cloned before running the `rbenv install $(cat .ruby-version)` command. - + + **Note:** The repository must be forked and cloned before running the + `rbenv install $(cat .ruby-version)` command. ### Yarn + Please refer to their [installation guide](https://yarnpkg.com/en/docs/install). ### PostgreSQL @@ -63,146 +70,6 @@ You can test if it's up and running by issuing the following command: redis-cli ping ``` -### Elasticsearch - -Forem requires Elasticsearch 7.x to run. We recommend version 7.5.2. - -You have the option of installing Elasticsearch with Homebrew or through an -archive. We **recommend** installing from archive on Mac. - -### Installing Elasticsearch from the archive - -We recommend that you **do not** install Elasticsearch in the app directory. -Instead, we recommend installing it in your home directory (for example, -`cd $HOME`). (This also ensures that we don't accidentally commit Elasticsearch -code to the project's repository!) - -The following directions were -[taken from the Elasticsearch docs themselves](https://www.elastic.co/guide/en/elasticsearch/reference/7.5/targz.html#install-macos), -so check those out if you run into any issues or want further information. Make -sure to download **the OSS version** of Elasticsearch, `elasticsearch-oss`. - -Please note that you will need `wget` in order to proceed with this installation -(`brew install wget`). - -```shell -wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-oss-7.5.2-darwin-x86_64.tar.gz -wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-oss-7.5.2-darwin-x86_64.tar.gz.sha512 -shasum -a 512 -c elasticsearch-oss-7.5.2-darwin-x86_64.tar.gz.sha512 -tar -xzf elasticsearch-oss-7.5.2-darwin-x86_64.tar.gz -``` - -To start elasticsearch, make sure you are in the correct directory: - -```shell -cd elasticsearch-7.5.2 -``` - -You can then start it by running: - -```shell -./bin/elasticsearch -``` - -To start elasticsearch as a daemonized process: - -```shell -./bin/elasticsearch -d -``` - -### Installing Elasticsearch with Homebrew - -To install Elasticsearch with Homebrew we will use the following commands to: - -- tap the Elastic Homebrew repository -- install the latest OSS distribution -- pin the latest OSS distribution. - -```shell -brew tap elastic/tap -brew install elastic/tap/elasticsearch-oss -brew pin elasticsearch-oss -``` - -After installation you can manually test if the Elasticsearch server starts by -issuing the command `elasticsearch` in the shell. You can then start the server -as a service with `brew services start elasticsearch-oss`. - -You can find further info on your local Elasticsearch installation by typing -`brew info elastic/tap/elasticsearch-oss`. - -#### Troubleshooting startup issues - -Two possible startup issues you might encounter: - -- `java.nio.file.FileSystemLoopException`: - -```text -Exception in thread "main" org.elasticsearch.bootstrap.BootstrapException: java.nio.file.FileSystemLoopException: /usr/local/etc/elasticsearch/elasticsearch -Likely root cause: java.nio.file.FileSystemLoopException: /usr/local/etc/elasticsearch/elasticsearch -``` - -This happens because the installation of Elasticsearch might have a recursive -link in the configuration directory causing the infinite loop: - -```shell -> ll /usr/local/etc/elasticsearch -elasticsearch -> /usr/local/etc/elasticsearch -``` - -By manually removing the link with -`rm -i /usr/local/etc/elasticsearch/elasticsearch` the issue should be fixed. - -- `java.lang.IllegalStateException`: - -```text -java.lang.IllegalStateException: Could not load plugin descriptor for plugin directory [plugins] -Likely root cause: java.nio.file.NoSuchFileException: /usr/local/Cellar/elasticsearch-oss/7.6.0/libexec/plugins/plugins/plugin-descriptor.properties -``` - -This happens for a similar reason as the previous error, the installation might -create a recursive link in the plugins directory. - -```shell -> ll /usr/local/var/elasticsearch/plugins -plugins -> /usr/local/var/elasticsearch/plugins -``` - -By manually removing the link with -`rm -i /usr/local/var/elasticsearch/plugins/plugins` the issue should be fixed. - -### Testing if Elasticsearch is running - -Once installed and started you can test if it's up and running correctly by -issuing the following command: - -```shell -curl http://localhost:9200 -``` - -You should receive in response a JSON document containing some information about -your local Elasticsearch installation, for example: - -```json -{ - "name": "hostname", - "cluster_name": "elasticsearch_...", - "cluster_uuid": "...", - "version": { - "number": "7.5.2", - "build_flavor": "oss", - "build_type": "tar", - "build_hash": "8bec50e1e0ad29dad5653712cf3bb580cd1afcdf", - "build_date": "2020-01-15T12:11:52.313576Z", - "build_snapshot": false, - "lucene_version": "8.3.0", - "minimum_wire_compatibility_version": "6.8.0", - "minimum_index_compatibility_version": "6.0.0-beta1" - }, - "tagline": "You Know, for Search" -} -``` - ## Installing Forem 1. Fork Forem's repository, e.g. @@ -240,8 +107,7 @@ your local Elasticsearch installation, for example: ### Possible error messages -**Error:** -`rbenv install hangs at ruby-build: using readline from homebrew` +**Error:** `rbenv install hangs at ruby-build: using readline from homebrew` **_Solution:_** [Stackoverflow answer](https://stackoverflow.com/questions/63599818/rbenv-install-hangs-at-ruby-build-using-readline-from-homebrew) diff --git a/docs/installation/windows.md b/docs/installation/windows.md index 686675c7d..e849d83f9 100644 --- a/docs/installation/windows.md +++ b/docs/installation/windows.md @@ -176,60 +176,6 @@ We recommend to follow [this guide](https://redislabs.com/blog/redis-on-windows-10/) to run Redis under WSL. -### Elasticsearch - -Forem requires a version of Elasticsearch between 7.1 and 7.5. Version 7.6 is -not supported. We recommend version 7.5.2. - -We recommend that you **do not** install Elasticsearch in the app directory. -Instead, we recommend installing it in your home directory (for example, -`cd $HOME`). (This also ensures that we don't accidentally commit Elasticsearch -code to the project's repository!) - -The following directions were taken from -[the Elasticsearch docs](https://www.elastic.co/guide/en/elasticsearch/reference/7.5/targz.html#install-linux), -check them out to learn more about the installation process and troubleshooting -issues. Make sure to refer to **the OSS version**, `elasticsearch-oss` while -going through the Elasticsearch docs. - -To install Elasticsearch perform the following steps: - -1. Execute the following commands: - - ```shell - cd - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-oss-7.5.2-linux-x86_64.tar.gz - wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-oss-7.5.2-linux-x86_64.tar.gz.sha512 - shasum -a 512 -c elasticsearch-oss-7.5.2-linux-x86_64.tar.gz.sha512 - tar -xzf elasticsearch-oss-7.5.2-linux-x86_64.tar.gz - ``` - -2. Next, switch to the correct directory with: - - ```shell - cd elasticsearch-7.5.2/ - ``` - -3. To start Elasticsearch, run the following command: - - ```shell - ./bin/elasticsearch - ``` - - or, start it as a daemonized process with: - - ```shell - ./bin/elasticsearch -d - ``` - -4. Once Elasticsearch is running, - [verify Elasticsearch's installation](https://www.elastic.co/guide/en/elasticsearch/reference/7.5/targz.html#_checking_that_elasticsearch_is_running) - by executing the `cURL` command as follows: - - ```shell - curl -X GET "localhost:9200/?pretty" - ``` - ## Installing Forem 1. Fork Forem's repository, eg. @@ -277,8 +223,8 @@ To install Elasticsearch perform the following steps: - You do not need "real" keys for basic development. Some features require certain keys, so you may be able to add them as you go. -1. After ensuring that Elasticsearch, the PostgreSQL server, and the Redis - server are running, run `bin/setup`. +1. After ensuring that the PostgreSQL server and the Redis server are running, + run `bin/setup`. > The `bin/setup` script is responsible for installing a varienty of > dependencies. One can find it inside the `bin` folder by the name of @@ -290,8 +236,8 @@ To install Elasticsearch perform the following steps: > - It then installs JavaScript dependencies using the script in `bin/yarn` > file. These dependencies are located in `package.json` in the root of the > repository. - > - Next, it uses various Rake files located inside the `lib` folder to setup - > ElasticSearch environment, PostgreSQL database creation and updation. + > - Next, it uses various Rake files located inside the `lib` folder for the + > PostgreSQL database creation and updation. > - Finally it cleans up all the log files and restarts the Puma server. ### Possible error messages diff --git a/docs/maintainers/deploying.md b/docs/maintainers/deploying.md index ad0449d65..35e50e207 100644 --- a/docs/maintainers/deploying.md +++ b/docs/maintainers/deploying.md @@ -57,10 +57,10 @@ stage will do nothing. If the branch has been merged into main, then this stage will kick off a deploy. The deploy will run in its own job deploying our application to Heroku. -Prior to deploying the code, Heroku will run database migrations, Elasticsearch -updates, and do some final checks (more information on that below) to make sure -everything is working as expected. If these all succeed, then the deploy -completes and our team is notified. +Prior to deploying the code, Heroku will run database migrations and do some +final checks (more information on that below) to make sure everything is working +as expected. If these all succeed, then the deploy completes and our team is +notified. ## Deploying to Heroku @@ -84,13 +84,8 @@ directory. During this release step we do a few checks. successfully before the code that uses it goes live. 3. We run any data update scripts that need to be run. A data update script is one that allows us to update data in the background separate from a - migration. For example, if we add a new field to Elasticsearch and need to - reindex all of our documents we would use a data update script. -4. We update Elasticsearch. Elasticsearch contains indexes which have mappings. - Mappings are similar to database schema. The same way we run a migration to - update our database we have to run a setup task to update any Elasticsearch - mappings. -5. Following updating all of our datastores we use the Rails runner to output a + migration. +4. Following updating all of our datastores we use the Rails runner to output a simple string. Executing a Rails runner command ensures that we can boot up the entire app successfully before it is deployed. We deploy asynchronously, so the website is running the new code a few minutes after deploy. A new diff --git a/docs/technical-overview/architecture.md b/docs/technical-overview/architecture.md index c6ed9a2fa..6c0bee3cf 100644 --- a/docs/technical-overview/architecture.md +++ b/docs/technical-overview/architecture.md @@ -72,10 +72,10 @@ Rails][fastly_rails] docs, but we bust specific URLs via `EdgeCache::Bust#call`. The home feed is based on a combination of recent collective posts that are cached and delivered the same to everyone in the HTML, and additional articles -fetched from an Elasticsearch index after page load. To determine which posts a -user sees, they are ranked based on the user's followed tags, followed users, -and relative weights for each tag. Additional fetched articles also follow this -general pattern. +fetched from a PostgreSQL Full Text Search index after page load. To determine +which posts a user sees, they are ranked based on the user's followed tags, +followed users, and relative weights for each tag. Additional fetched articles +also follow this general pattern. Currently, the top post on the home feed, which must have a cover image, is shared among all users. diff --git a/docs/technical-overview/stack.md b/docs/technical-overview/stack.md index 20b4bf877..78a0a406d 100644 --- a/docs/technical-overview/stack.md +++ b/docs/technical-overview/stack.md @@ -7,7 +7,8 @@ title: Stack For the Forem tech stack we use: - [_Puma_](https://github.com/puma/puma) as the web server -- [_PostgreSQL_](https://www.postgresql.org/) as the primary database +- [_PostgreSQL_](https://www.postgresql.org/) as the primary database and for + Full Text Search - [_Redis_](https://redis.io/) to store cached data - [_Fastly_](https://www.fastly.com/) for [edge caching](https://dev.to/ben/making-devto-insanely-fast) @@ -18,7 +19,6 @@ For the Forem tech stack we use: - [_Sidekiq_](https://github.com/mperham/sidekiq) and [_Active Job_](https://guides.rubyonrails.org/active_job_basics.html) for background workers -- [_Elasticsearch_](https://www.elastic.co/elasticsearch/) for in app searching - [Ransack](https://github.com/activerecord-hackery/ransack) for internal search - [_Redcarpet_](https://github.com/vmg/redcarpet) and [_Rouge_](https://github.com/jneen/rouge) to parse Markdown diff --git a/docs/tests/readme.md b/docs/tests/readme.md index b4b59aad2..efae0084e 100644 --- a/docs/tests/readme.md +++ b/docs/tests/readme.md @@ -69,8 +69,3 @@ retry the individual job that failed rather than the entire suite in order to save some time. When you retry the individual job, make sure to also retry the Deploy job. Even though you may not be deploying that job must complete for the entire build to pass. - -Please note that you will need to have Elasticsearch installed and running for -certain tests in our test suite. You can find instructions on how to install and -run Elasticsearch specific to your environment in the -[Installation Guide](/installation). diff --git a/docs/tests/test-flags.md b/docs/tests/test-flags.md index 93f1f6774..6342ee4f2 100644 --- a/docs/tests/test-flags.md +++ b/docs/tests/test-flags.md @@ -11,59 +11,15 @@ test example. Some flags that we use are: - `js: true` -- `elasticsearch_reset: true` -- `elasticsearch: ` -- `stub_elasticsearch: true` - `throttle: true` - `type: ` -### Elasticsearch Flags - -Two Elasticsearch flags that can be used when dealing with specs that will be -hitting Elasticsearch. When running a spec that is going to interact with -Elasticsearch, you want Elasticsearch to be clean like your database. Since we -don't have a "database cleaner" for Elasticsearch, we have to do it manually. -There are two ways to do this: - -1. `elasticsearch: reset` - This will trigger a complete tear down and rebuild - of Elasticsearch via a block. This takes time so it's not something you want - to be doing unless you absolutely have to. - -```ruby - config.around(:each, elasticsearch_reset: true) do |example| - Search::Cluster.recreate_indexes - example.run - Search::Cluster.recreate_indexes - end -``` - -2. `elasticsearch: ` - This will clear the data for the given - search class index. For example, if you passed "User" as your search class - name it would clear out the user index data using code from this block. - -```ruby - config.around(:each, :elasticsearch) do |ex| - klasses = Array.wrap(ex.metadata[:elasticsearch]).map do |search_class| - Search.const_get(search_class) - end - klasses.each { |klass| clear_elasticsearch_data(klass) } - ex.run - end -``` - ### `js: true` Flag `js: true` indicates that we want the JavaScript on the page to be executed when the page is rendered, and a headless chrome instance will be initialized to do so (instead of the default -[rack_test](https://github.com/teamcapybara/capybara#racktest) driver). One side -effect of running our JavaScript in our specs is that a lot of pages will hit -Elasticsearch. Since we don't clean out Elasticsearch between every single spec -(because it is very costly) this can lead to unexpected data being loaded for a -spec. To prevent this from happening, we can use the `:stub_elasticsearch` flag. -The `:stub_elasticsearch` flag will stub all index and search requests made to -Elasticsearch and return an empty response. This will ensure that no unwanted -data shows up on your spec's page. +[rack_test](https://github.com/teamcapybara/capybara#racktest) driver). If you are debugging a `js: true` spec and want to see the browser, you can set `HEADLESS=false` before running a spec: diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 60c1700cb..cf550dbfe 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -33,66 +33,6 @@ The first command creates the user **ec2-user** and the second one creates the database for this user because every user needs its database. Even if the first command fails, run the second command to create the missing database. -## Elasticsearch - -### Index read-only error - -If you encounter an error similar to the following: - -```shell -{"error":{"root_cause":[{"type":"cluster_block_exception","reason":"index [tags_development] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"}],"type":"cluster_block_exception","reason":"index [tags_development] blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"},"status":403} -``` - -it means that Elasticsearch went into read only mode because its disk allocator -noticed how the disk drive is nearly full: - -> Elasticsearch enforces a read-only index block -> (`index.blocks.read_only_allow_delete`) on every index that has one or more -> shards allocated on the node that has at least one disk exceeding the flood -> stage. - -This is an indication that you might not have enough space for ES to work -correctly. - -If you want to disable the threshold check on your local machine you can open a -Rails console (with `rails console`) and issue the following command: - -```ruby -Search::Client.cluster.put_settings(body: { - persistent: { - "cluster.routing.allocation.disk.threshold_enabled" => false, - } -}) -``` - -Alternatively you can use the following command in a terminal session: - -```shell -curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_all/_settings -d '{"index.blocks.read_only_allow_delete": null}' -``` - -To disable the "read only" mode to allow operations on the Elasticsearch indexes -you can issue the following command, similary in the Rails console: - -```ruby -Search::Client.indices.get(index: "*").keys.each do |index_name| - Search::Client.indices.put_settings( - index: index_name, - body: { "index.blocks.read_only_allow_delete" => nil } - ) -end -``` - -Or alternatively: - -```shell -curl -XPUT -H "Content-Type: application/json" http://localhost:9200/_cluster/settings -d '{ "transient": { "cluster.routing.allocation.disk.threshold_enabled": false } }' -``` - -If instead you want to tune the Elasticsearch disk allocator's settings, please -refer to -[Disk-based shard allocation](https://www.elastic.co/guide/en/elasticsearch/reference/current/disk-allocator.html#disk-allocator). - ## CORS If you are experiencing CORS issues locally or need to display more information diff --git a/lib/data_update_scripts/20200218195023_index_chat_channel_memberships_to_elasticsearch.rb b/lib/data_update_scripts/20200218195023_index_chat_channel_memberships_to_elasticsearch.rb index 73d5d464e..35fa1babb 100644 --- a/lib/data_update_scripts/20200218195023_index_chat_channel_memberships_to_elasticsearch.rb +++ b/lib/data_update_scripts/20200218195023_index_chat_channel_memberships_to_elasticsearch.rb @@ -1,7 +1,7 @@ module DataUpdateScripts class IndexChatChannelMembershipsToElasticsearch def run - ChatChannelMembership.find_each(&:index_to_elasticsearch_inline) + # ChatChannelMembership.find_each(&:index_to_elasticsearch_inline) end end end diff --git a/lib/data_update_scripts/20200410152018_resync_elasticsearch_documents.rb b/lib/data_update_scripts/20200410152018_resync_elasticsearch_documents.rb index 2adea1ffc..dd1c1afae 100644 --- a/lib/data_update_scripts/20200410152018_resync_elasticsearch_documents.rb +++ b/lib/data_update_scripts/20200410152018_resync_elasticsearch_documents.rb @@ -2,13 +2,13 @@ module DataUpdateScripts class ResyncElasticsearchDocuments def run # Previous bug: Not getting removed properly - sync_docs(Article.ids, "Article") - sync_docs(Comment.ids, "Comment") - sync_docs(User.ids, "User") + # sync_docs(Article.ids, "Article") + # sync_docs(Comment.ids, "Comment") + # sync_docs(User.ids, "User") # Previous bug: Not getting indexed on creation properly - index_docs(PodcastEpisode.ids, "PodcastEpisode") - index_docs(Tag.ids, "Tag") + # index_docs(PodcastEpisode.ids, "PodcastEpisode") + # index_docs(Tag.ids, "Tag") end private diff --git a/lib/data_update_scripts/20200519142908_re_index_feed_content_and_users_to_elasticsearch.rb b/lib/data_update_scripts/20200519142908_re_index_feed_content_and_users_to_elasticsearch.rb index 2131cc3cf..bb54a90a5 100644 --- a/lib/data_update_scripts/20200519142908_re_index_feed_content_and_users_to_elasticsearch.rb +++ b/lib/data_update_scripts/20200519142908_re_index_feed_content_and_users_to_elasticsearch.rb @@ -1,21 +1,21 @@ module DataUpdateScripts class ReIndexFeedContentAndUsersToElasticsearch def run - Article.select(:id).in_batches(of: 100) do |batch| - Search::BulkIndexWorker.set(queue: :default).perform_async( - "Article", batch.ids - ) - end - Comment.select(:id).in_batches(of: 100) do |batch| - Search::BulkIndexWorker.set(queue: :default).perform_async( - "Comment", batch.ids - ) - end - PodcastEpisode.select(:id).in_batches(of: 100) do |batch| - Search::BulkIndexWorker.set(queue: :default).perform_async( - "PodcastEpisode", batch.ids - ) - end + # Article.select(:id).in_batches(of: 100) do |batch| + # Search::BulkIndexWorker.set(queue: :default).perform_async( + # "Article", batch.ids + # ) + # end + # Comment.select(:id).in_batches(of: 100) do |batch| + # Search::BulkIndexWorker.set(queue: :default).perform_async( + # "Comment", batch.ids + # ) + # end + # PodcastEpisode.select(:id).in_batches(of: 100) do |batch| + # Search::BulkIndexWorker.set(queue: :default).perform_async( + # "PodcastEpisode", batch.ids + # ) + # end # See: https://github.com/forem/forem/pull/10313#discussion_r487646864 # User.select(:id).in_batches(of: 200) do |batch| diff --git a/lib/data_update_scripts/20200803142830_reindex_listing_search_column.rb b/lib/data_update_scripts/20200803142830_reindex_listing_search_column.rb index 0e9a8c4dc..2ac808345 100644 --- a/lib/data_update_scripts/20200803142830_reindex_listing_search_column.rb +++ b/lib/data_update_scripts/20200803142830_reindex_listing_search_column.rb @@ -3,7 +3,7 @@ module DataUpdateScripts def run # Choose to do inline so development envs are ready immediately after # this is run - Listing.find_each(&:index_to_elasticsearch_inline) + # Listing.find_each(&:index_to_elasticsearch_inline) end end end diff --git a/lib/data_update_scripts/20200904132553_remove_draft_articles_with_duplicate_feed_source_url.rb b/lib/data_update_scripts/20200904132553_remove_draft_articles_with_duplicate_feed_source_url.rb index 742792069..3b0ad601f 100644 --- a/lib/data_update_scripts/20200904132553_remove_draft_articles_with_duplicate_feed_source_url.rb +++ b/lib/data_update_scripts/20200904132553_remove_draft_articles_with_duplicate_feed_source_url.rb @@ -5,76 +5,76 @@ module DataUpdateScripts # This statement deletes all draft articles in excess found to be duplicate over feed_source_url, # excluding those whose body_markdown is different from the other duplicate occurrences - result_same_body = ActiveRecord::Base.connection.execute( - <<-SQL, - WITH duplicates_draft_articles AS - (SELECT id - FROM - (SELECT id, - published, - body_markdown, - LAG(body_markdown, 1) OVER(PARTITION BY feed_source_url - ORDER BY id ASC) AS previous_body_markdown, - ROW_NUMBER() OVER(PARTITION BY feed_source_url - ORDER BY id ASC) AS row_number - FROM articles - WHERE feed_source_url IS NOT NULL ) duplicates - WHERE duplicates.row_number > 1 - AND published = 'f' -- drafts - AND body_markdown = previous_body_markdown -- with the same body - ) - DELETE - FROM articles - WHERE id IN (SELECT id FROM duplicates_draft_articles) RETURNING id; - SQL - ) - + # result_same_body = ActiveRecord::Base.connection.execute( + # <<-SQL, + # WITH duplicates_draft_articles AS + # (SELECT id + # FROM + # (SELECT id, + # published, + # body_markdown, + # LAG(body_markdown, 1) OVER(PARTITION BY feed_source_url + # ORDER BY id ASC) AS previous_body_markdown, + # ROW_NUMBER() OVER(PARTITION BY feed_source_url + # ORDER BY id ASC) AS row_number + # FROM articles + # WHERE feed_source_url IS NOT NULL ) duplicates + # WHERE duplicates.row_number > 1 + # AND published = 'f' -- drafts + # AND body_markdown = previous_body_markdown -- with the same body + # ) + # DELETE + # FROM articles + # WHERE id IN (SELECT id FROM duplicates_draft_articles) RETURNING id; + # SQL + # ) + # # Now that all duplicates with the same body are gone, we need to deal with duplicate feed source URLs # with different bodies. # We thus select the oldest for removal preserving the most recent one - result_different_bodies = ActiveRecord::Base.connection.execute( - <<-SQL, - WITH duplicates_draft_articles AS - (SELECT id - FROM - (SELECT id, - published, - body_markdown, - LAG(body_markdown, 1) OVER(PARTITION BY feed_source_url - ORDER BY created_at DESC) AS previous_body_markdown, - ROW_NUMBER() OVER(PARTITION BY feed_source_url - ORDER BY created_at DESC) AS row_number - FROM articles - WHERE feed_source_url IS NOT NULL ) duplicates - WHERE duplicates.row_number > 1 - AND published = 'f' -- drafts - AND body_markdown != previous_body_markdown -- with different bodies - ) - DELETE - FROM articles - WHERE id IN (SELECT id FROM duplicates_draft_articles) RETURNING id; - SQL - ) - - result_same_body_ids = result_same_body.map { |row| row["id"] } - result_different_bodies_ids = result_different_bodies.map { |row| row["id"] } + # result_different_bodies = ActiveRecord::Base.connection.execute( + # <<-SQL, + # WITH duplicates_draft_articles AS + # (SELECT id + # FROM + # (SELECT id, + # published, + # body_markdown, + # LAG(body_markdown, 1) OVER(PARTITION BY feed_source_url + # ORDER BY created_at DESC) AS previous_body_markdown, + # ROW_NUMBER() OVER(PARTITION BY feed_source_url + # ORDER BY created_at DESC) AS row_number + # FROM articles + # WHERE feed_source_url IS NOT NULL ) duplicates + # WHERE duplicates.row_number > 1 + # AND published = 'f' -- drafts + # AND body_markdown != previous_body_markdown -- with different bodies + # ) + # DELETE + # FROM articles + # WHERE id IN (SELECT id FROM duplicates_draft_articles) RETURNING id; + # SQL + # ) + # + # result_same_body_ids = result_same_body.map { |row| row["id"] } + # result_different_bodies_ids = result_different_bodies.map { |row| row["id"] } # Remove all articles from Elasticsearch - (result_same_body_ids + result_different_bodies_ids).each do |id| - Search::RemoveFromIndexWorker.perform_in(5.seconds, "Search::FeedContent", "article_#{id}") - end + # (result_same_body_ids + result_different_bodies_ids).each do |id| + # Search::RemoveFromIndexWorker.perform_in(5.seconds, "Search::FeedContent", "article_#{id}") + # end # Store deleted IDs temporarily in Redis for safe keeping - Rails.cache.write( - "DataUpdateScripts::RemoveDraftArticlesWithDuplicateFeedSourceUrl::SameBody", - result_same_body_ids, - expires_in: 2.weeks, - ) - Rails.cache.write( - "DataUpdateScripts::RemoveDraftArticlesWithDuplicateFeedSourceUrl::DifferentBodies", - result_same_body_ids, - expires_in: 2.weeks, - ) + # Rails.cache.write( + # "DataUpdateScripts::RemoveDraftArticlesWithDuplicateFeedSourceUrl::SameBody", + # result_same_body_ids, + # expires_in: 2.weeks, + # ) + # Rails.cache.write( + # "DataUpdateScripts::RemoveDraftArticlesWithDuplicateFeedSourceUrl::DifferentBodies", + # result_same_body_ids, + # expires_in: 2.weeks, + # ) end end end diff --git a/lib/data_update_scripts/20200910140109_cleanup_published_articles_with_duplicate_user_id_title_body_markdown.rb b/lib/data_update_scripts/20200910140109_cleanup_published_articles_with_duplicate_user_id_title_body_markdown.rb index 23ff5a6f1..0a938f6db 100644 --- a/lib/data_update_scripts/20200910140109_cleanup_published_articles_with_duplicate_user_id_title_body_markdown.rb +++ b/lib/data_update_scripts/20200910140109_cleanup_published_articles_with_duplicate_user_id_title_body_markdown.rb @@ -34,7 +34,7 @@ module DataUpdateScripts # save all the magic article.save - article.index_to_elasticsearch_inline + # article.index_to_elasticsearch_inline articles_to_delete_ids += articles_to_graft.map { |a| a["id"] } end diff --git a/lib/data_update_scripts/20200911045602_reindex_articles_with_videos.rb b/lib/data_update_scripts/20200911045602_reindex_articles_with_videos.rb index 1ec9484aa..31e7caa4f 100644 --- a/lib/data_update_scripts/20200911045602_reindex_articles_with_videos.rb +++ b/lib/data_update_scripts/20200911045602_reindex_articles_with_videos.rb @@ -1,8 +1,8 @@ module DataUpdateScripts class ReindexArticlesWithVideos def run - articles = Article.where.not(video: nil).or(Article.where.not(video: "")) - articles.find_each(&:index_to_elasticsearch_inline) + # articles = Article.where.not(video: nil).or(Article.where.not(video: "")) + # articles.find_each(&:index_to_elasticsearch_inline) end end end diff --git a/lib/data_update_scripts/20200924140813_remove_reaction_index_by_name.rb b/lib/data_update_scripts/20200924140813_remove_reaction_index_by_name.rb index 77e46414e..9e5415954 100644 --- a/lib/data_update_scripts/20200924140813_remove_reaction_index_by_name.rb +++ b/lib/data_update_scripts/20200924140813_remove_reaction_index_by_name.rb @@ -1,10 +1,10 @@ module DataUpdateScripts class RemoveReactionIndexByName def run - index_name = "reactions_#{Rails.env}" - return unless Search::Client.indices.exists(index: index_name) + # index_name = "reactions_#{Rails.env}" + # return unless Search::Client.indices.exists(index: index_name) - Search::Client.indices.delete(index: index_name) + # Search::Client.indices.delete(index: index_name) end end end diff --git a/lib/data_update_scripts/20201030134117_reindex_users_for_username_search.rb b/lib/data_update_scripts/20201030134117_reindex_users_for_username_search.rb index e131bef58..47d03cd2c 100644 --- a/lib/data_update_scripts/20201030134117_reindex_users_for_username_search.rb +++ b/lib/data_update_scripts/20201030134117_reindex_users_for_username_search.rb @@ -1,9 +1,9 @@ module DataUpdateScripts class ReindexUsersForUsernameSearch def run - User.select(:id).in_batches(of: 200) do |batch| - Search::BulkIndexWorker.set(queue: :default).perform_async("User", batch.ids) - end + # User.select(:id).in_batches(of: 200) do |batch| + # Search::BulkIndexWorker.set(queue: :default).perform_async("User", batch.ids) + # end end end end diff --git a/lib/data_update_scripts/20210118194138_resync_unpublished_articles_comments_elasticsearch_document.rb b/lib/data_update_scripts/20210118194138_resync_unpublished_articles_comments_elasticsearch_document.rb index 5160419ff..4c102122b 100644 --- a/lib/data_update_scripts/20210118194138_resync_unpublished_articles_comments_elasticsearch_document.rb +++ b/lib/data_update_scripts/20210118194138_resync_unpublished_articles_comments_elasticsearch_document.rb @@ -1,9 +1,9 @@ module DataUpdateScripts class ResyncUnpublishedArticlesCommentsElasticsearchDocument def run - Article.unpublished.where.not(comments_count: 0).each do |article| - article.comments.each(&:index_to_elasticsearch) - end + # Article.unpublished.where.not(comments_count: 0).each do |article| + # article.comments.each(&:index_to_elasticsearch) + # end end end end diff --git a/lib/tasks/app_initializer.rake b/lib/tasks/app_initializer.rake index bbc000885..fb7d6f333 100644 --- a/lib/tasks/app_initializer.rake +++ b/lib/tasks/app_initializer.rake @@ -1,9 +1,6 @@ namespace :app_initializer do desc "Prepare Application on Boot Up" task setup: :environment do - puts "\n== Preparing Elasticsearch ==" - Rake::Task["search:setup"].execute - puts "\n== Preparing database ==" system("bin/rails db:prepare") || exit!(1) Rake::Task["db:migrate"].execute # it'll re-alphabetize the columns in `schema.rb` diff --git a/lib/tasks/search.rake b/lib/tasks/search.rake deleted file mode 100644 index 5c7abd766..000000000 --- a/lib/tasks/search.rake +++ /dev/null @@ -1,26 +0,0 @@ -namespace :search do - desc "set up Elasticsearch indexes" - task setup: :environment do - Search::Cluster.setup_indexes - end - - desc "update Elasticsearch index mappings" - task update_mappings: :environment do - Search::Cluster.update_mappings - end - - desc "tear down Elasticsearch indexes" - task destroy: :environment do - if Rails.env.production? - puts "Will NOT destroy indexes in production" - next - end - - Search::Cluster.delete_indexes - end -end - -if Rails.env.development? - Rake::Task["db:create"].enhance(["search:setup"]) - Rake::Task["db:drop"].enhance(["search:destroy"]) -end diff --git a/release-tasks.sh b/release-tasks.sh index f76338ad4..f77240619 100755 --- a/release-tasks.sh +++ b/release-tasks.sh @@ -14,8 +14,7 @@ set -Eex # abort release if deploy status equals "blocked" [[ $DEPLOY_STATUS = "blocked" ]] && echo "Deploy blocked" && exit 1 -# runs migration for Postgres, setups/updates Elasticsearch -# and boots the app to check there are no errors +# runs migration for Postgres and boots the app to check there are no errors STATEMENT_TIMEOUT=4500000 bundle exec rails app_initializer:setup bundle exec rake fastly:update_configs bundle exec rails runner "puts 'app load success'" diff --git a/spec/factories/tags.rb b/spec/factories/tags.rb index 5c395d5f2..9e82608b8 100644 --- a/spec/factories/tags.rb +++ b/spec/factories/tags.rb @@ -4,9 +4,5 @@ FactoryBot.define do factory :tag do name { generate :name } supported { true } - - trait :search_indexed do - after(:create, &:index_to_elasticsearch_inline) - end end end diff --git a/spec/initializers/rack/attack_spec.rb b/spec/initializers/rack/attack_spec.rb index d02eb099f..9102d14f4 100644 --- a/spec/initializers/rack/attack_spec.rb +++ b/spec/initializers/rack/attack_spec.rb @@ -11,12 +11,13 @@ describe Rack::Attack, type: :request, throttle: true do describe "search_throttle" do it "throttles /search endpoints based on IP" do Timecop.freeze do - allow(Search::User).to receive(:search_documents).and_return({}) + allow(Search::Postgres::Username).to receive(:search_documents).and_return({}) + valid_responses = Array.new(5).map do - get "/search/users", headers: { "HTTP_FASTLY_CLIENT_IP" => "5.6.7.8" } + get "/search/usernames", headers: { "HTTP_FASTLY_CLIENT_IP" => "5.6.7.8" } end - throttled_response = get "/search/users", headers: { "HTTP_FASTLY_CLIENT_IP" => "5.6.7.8" } - new_ip_response = get "/search/users", headers: { "HTTP_FASTLY_CLIENT_IP" => "1.1.1.1" } + throttled_response = get "/search/usernames", headers: { "HTTP_FASTLY_CLIENT_IP" => "5.6.7.8" } + new_ip_response = get "/search/usernames", headers: { "HTTP_FASTLY_CLIENT_IP" => "1.1.1.1" } valid_responses.each { |r| expect(r).not_to eq(429) } expect(throttled_response).to eq(429) diff --git a/spec/lib/acts_as_taggable_on/tag_spec.rb b/spec/lib/acts_as_taggable_on/tag_spec.rb deleted file mode 100644 index 312d47309..000000000 --- a/spec/lib/acts_as_taggable_on/tag_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require "rails_helper" - -RSpec.describe ActsAsTaggableOn::Tag, type: :lib do - describe "#after_commit" do - it "on create indexes tag to elasticsearch" do - tag_name = "muffintag" - create(:article, body_markdown: "---\ntitle: Me#{rand(1000)}\ntags: #{tag_name}\n---\n\nMeMeMe") - sidekiq_perform_enqueued_jobs - tag = Tag.find_by(name: tag_name) - expect(tag.elasticsearch_doc).not_to be_nil - end - - it "syncs related elasticsearch documents" do - article = create(:article) - podcast_episode = create(:podcast_episode) - tag = article.tags.first - podcast_episode.tags << tag - new_keywords = "keyword1, keyword2, keyword3" - sidekiq_perform_enqueued_jobs - - tag.update(keywords_for_search: new_keywords) - sidekiq_perform_enqueued_jobs - expect(collect_keywords(article)).to include(new_keywords) - expect(collect_keywords(podcast_episode)).to include(new_keywords) - end - end - - def collect_keywords(record) - record.elasticsearch_doc.dig("_source", "tags").flat_map { |t| t["keywords_for_search"] } - end -end diff --git a/spec/lib/data_update_scripts/index_chat_channel_memberships_to_elasticsearch_spec.rb b/spec/lib/data_update_scripts/index_chat_channel_memberships_to_elasticsearch_spec.rb deleted file mode 100644 index 137af509c..000000000 --- a/spec/lib/data_update_scripts/index_chat_channel_memberships_to_elasticsearch_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "rails_helper" -require Rails.root.join("lib/data_update_scripts/20200218195023_index_chat_channel_memberships_to_elasticsearch.rb") - -describe DataUpdateScripts::IndexChatChannelMembershipsToElasticsearch, elasticsearch: "ChatChannelMembership" do - it "indexes chat channel memberships to Elasticsearch" do - chat_channel_membership = FactoryBot.create(:chat_channel_membership) - expect { chat_channel_membership.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - described_class.new.run - expect(chat_channel_membership.elasticsearch_doc).not_to be_nil - end -end diff --git a/spec/lib/data_update_scripts/re_index_feed_content_and_users_to_elasticsearch_spec.rb b/spec/lib/data_update_scripts/re_index_feed_content_and_users_to_elasticsearch_spec.rb deleted file mode 100644 index 98bf55ca9..000000000 --- a/spec/lib/data_update_scripts/re_index_feed_content_and_users_to_elasticsearch_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "rails_helper" -require Rails.root.join("lib/data_update_scripts/20200519142908_re_index_feed_content_and_users_to_elasticsearch.rb") - -describe DataUpdateScripts::ReIndexFeedContentAndUsersToElasticsearch do - after do - Search::FeedContent.refresh_index - end - - it "indexes feed content(articles, comments, podcast episodes) and users to Elasticsearch", :aggregate_failures do - article = create(:article) - podcast_episode = create(:podcast_episode) - comment = create(:comment) - - expect { article.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - expect { podcast_episode.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - expect { comment.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - - sidekiq_perform_enqueued_jobs { described_class.new.run } - - expect(article.elasticsearch_doc).not_to be_nil - expect(podcast_episode.elasticsearch_doc).not_to be_nil - expect(comment.elasticsearch_doc).not_to be_nil - - expect(article.elasticsearch_doc["_source"].keys).to include "public_reactions_count" - expect(podcast_episode.elasticsearch_doc["_source"].keys).to include "public_reactions_count" - expect(comment.elasticsearch_doc["_source"].keys).to include "public_reactions_count" - end -end diff --git a/spec/lib/data_update_scripts/reindex_listing_search_column_spec.rb b/spec/lib/data_update_scripts/reindex_listing_search_column_spec.rb deleted file mode 100644 index 967558231..000000000 --- a/spec/lib/data_update_scripts/reindex_listing_search_column_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "rails_helper" -require Rails.root.join("lib/data_update_scripts/20200803142830_reindex_listing_search_column.rb") - -describe DataUpdateScripts::ReindexListingSearchColumn, elasticsearch: "Listing" do - it "indexes listings to Elasticsearch" do - listing = create(:listing) - expect { listing.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - described_class.new.run - expect(listing.elasticsearch_doc).not_to be_nil - end -end diff --git a/spec/lib/data_update_scripts/reindex_users_for_username_search_spec.rb b/spec/lib/data_update_scripts/reindex_users_for_username_search_spec.rb deleted file mode 100644 index bd9826833..000000000 --- a/spec/lib/data_update_scripts/reindex_users_for_username_search_spec.rb +++ /dev/null @@ -1,12 +0,0 @@ -require "rails_helper" -require Rails.root.join("lib/data_update_scripts/20201030134117_reindex_users_for_username_search.rb") - -describe DataUpdateScripts::ReindexUsersForUsernameSearch do - let(:user) { create :user } - - it "reindexes users" do - sidekiq_assert_enqueued_with(job: Search::BulkIndexWorker, args: ["User", [user.id]], queue: "default") do - described_class.new.run - end - end -end diff --git a/spec/lib/data_update_scripts/remove_reaction_index_by_name_spec.rb b/spec/lib/data_update_scripts/remove_reaction_index_by_name_spec.rb deleted file mode 100644 index 48fb7cb15..000000000 --- a/spec/lib/data_update_scripts/remove_reaction_index_by_name_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "rails_helper" -require Rails.root.join("lib/data_update_scripts/20200924140813_remove_reaction_index_by_name.rb") - -describe DataUpdateScripts::RemoveReactionIndexByName do - it "removes reaction index" do - index_name = "reactions_#{Rails.env}" - Search::Client.indices.create(index: index_name) - expect(Search::Client.indices.get(index: "*").keys).to include(index_name) - - described_class.new.run - expect(Search::Client.indices.get(index: "*").keys).not_to include(index_name) - end -end diff --git a/spec/lib/data_update_scripts/resync_elasticsearch_documents_spec.rb b/spec/lib/data_update_scripts/resync_elasticsearch_documents_spec.rb deleted file mode 100644 index 89063cfef..000000000 --- a/spec/lib/data_update_scripts/resync_elasticsearch_documents_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require "rails_helper" -require Rails.root.join("lib/data_update_scripts/20200410152018_resync_elasticsearch_documents.rb") - -describe DataUpdateScripts::ResyncElasticsearchDocuments, elasticsearch: %w[FeedContent User Tag] do - after do - Article::SEARCH_CLASS.refresh_index - User::SEARCH_CLASS.refresh_index - end - - it "indexes podcast episodes and tags to Elasticsearch" do - tag = create(:tag) - podcast_episode = create(:podcast_episode) - Sidekiq::Worker.clear_all - - expect { tag.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - expect { podcast_episode.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - - sidekiq_perform_enqueued_jobs { described_class.new.run } - expect(tag.elasticsearch_doc).not_to be_nil - expect(podcast_episode.elasticsearch_doc).not_to be_nil - end - - it "syncs articles, comments, and users to Elasticsearch", :aggregate_failures do - article = create(:article) - comment = create(:comment) - user = create(:user) - index_real_and_mock_documents(article, comment, user) - - expect(Article::SEARCH_CLASS.articles_document_count).not_to eq(Article.count) - expect(Comment::SEARCH_CLASS.comments_document_count).not_to eq(Comment.count) - expect(User::SEARCH_CLASS.document_count).not_to eq(User.count) - - sidekiq_perform_enqueued_jobs { described_class.new.run } - refresh_indexes - - expect(article.elasticsearch_doc).not_to be_nil - expect(user.elasticsearch_doc).not_to be_nil - expect(comment.elasticsearch_doc).not_to be_nil - expect(Article::SEARCH_CLASS.articles_document_count).to eq(Article.count) - expect(Comment::SEARCH_CLASS.comments_document_count).to eq(Comment.count) - expect(User::SEARCH_CLASS.document_count).to eq(User.count) - end - - def index_real_and_mock_documents(article, comment, user) - sidekiq_perform_enqueued_jobs - Article::SEARCH_CLASS.index("article_#{article.id + 100}", class_name: "Article") - Comment::SEARCH_CLASS.index("comment_#{comment.id + 100}", class_name: "Comment") - User::SEARCH_CLASS.index(user.id + 100, name: "User") - refresh_indexes - end - - def refresh_indexes - User::SEARCH_CLASS.refresh_index - Article::SEARCH_CLASS.refresh_index - end -end diff --git a/spec/lib/data_update_scripts/resync_unpublished_articles_comments_elasticsearch_document_spec.rb b/spec/lib/data_update_scripts/resync_unpublished_articles_comments_elasticsearch_document_spec.rb deleted file mode 100644 index 60c5705f3..000000000 --- a/spec/lib/data_update_scripts/resync_unpublished_articles_comments_elasticsearch_document_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require "rails_helper" -require Rails.root.join( - "lib/data_update_scripts/20210118194138_resync_unpublished_articles_comments_elasticsearch_document.rb", -) - -describe DataUpdateScripts::ResyncUnpublishedArticlesCommentsElasticsearchDocument do - it "works" do - article = create(:article) - comment = create(:comment, commentable: article) - sidekiq_perform_enqueued_jobs - article.update_column(:published, false) - - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: ["Comment", comment.id]) do - described_class.new.run - end - end -end diff --git a/spec/models/article_spec.rb b/spec/models/article_spec.rb index 7cfa6d9fa..df51653d2 100644 --- a/spec/models/article_spec.rb +++ b/spec/models/article_spec.rb @@ -137,30 +137,6 @@ RSpec.describe Article, type: :model do end end - describe "#after_commit" do - it "on update enqueues job to index article to elasticsearch" do - article.save - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, article.id]) do - article.save - end - end - - it "on destroy enqueues job to delete article from elasticsearch" do - article = create(:article) - - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, article.search_id]) do - article.destroy - end - end - - it "on update syncs elasticsearch data" do - allow(article).to receive(:sync_related_elasticsearch_docs) - article.save - expect(article).to have_received(:sync_related_elasticsearch_docs) - end - end - context "when published" do before do # rubocop:disable RSpec/NamedSubject @@ -1204,14 +1180,6 @@ RSpec.describe Article, type: :model do end end - describe "#touch_by_reaction" do - it "reindexes elasticsearch doc" do - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, article.id]) do - article.touch_by_reaction - end - end - end - describe "co_author_ids_list=" do it "correctly sets co author ids from a comma separated list of ids" do co_author1 = create(:user) diff --git a/spec/models/chat_channel_membership_spec.rb b/spec/models/chat_channel_membership_spec.rb index 204a30d51..1b3f60e11 100644 --- a/spec/models/chat_channel_membership_spec.rb +++ b/spec/models/chat_channel_membership_spec.rb @@ -34,22 +34,4 @@ RSpec.describe ChatChannelMembership, type: :model do expect(chat_channel_membership.channel_text).to eq(expected_text) end end - - describe "#after_commit" do - it "on update enqueues job to index chat_channel_membership to elasticsearch" do - chat_channel_membership.save - sidekiq_assert_enqueued_with(job: Search::IndexWorker, - args: [described_class.to_s, chat_channel_membership.id]) do - chat_channel_membership.save - end - end - - it "on destroy enqueues job to delete chat_channel_membership from elasticsearch" do - chat_channel_membership.save - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, chat_channel_membership.id]) do - chat_channel_membership.destroy - end - end - end end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index b5063531e..049a9a484 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -118,23 +118,6 @@ RSpec.describe Comment, type: :model do end # rubocop:enable RSpec/NamedSubject - describe "#after_commit" do - it "on update enqueues job to index comment to elasticsearch" do - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, comment.id]) do - comment.save - end - end - - it "on destroy enqueues job to delete comment from elasticsearch" do - comment = create(:comment) - - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, comment.search_id]) do - comment.destroy - end - end - end - describe "#search_id" do it "returns comment_ID" do expect(comment.search_id).to eq("comment_#{comment.id}") diff --git a/spec/models/concerns/searchable_spec.rb b/spec/models/concerns/searchable_spec.rb deleted file mode 100644 index 6c6f77483..000000000 --- a/spec/models/concerns/searchable_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require "rails_helper" - -class SearchableModel - include Searchable - SEARCH_CLASS = Search::Tag - SEARCH_SERIALIZER = Search::TagSerializer - - def id - 1 - end -end - -RSpec.describe Searchable do - let(:model_class) { SearchableModel } - let(:searchable_model) { model_class.new } - let(:serialized_hash) { { data: { attributes: { id: searchable_model.search_id } } } } - - before do - mock_serializer = instance_double("MockSerializer", :serializable_hash) - allow(model_class::SEARCH_SERIALIZER).to receive(:new).and_return(mock_serializer) - allow(mock_serializer).to receive(:serializable_hash).and_return( - serialized_hash, - ) - end - - describe "#search_id" do - it "defaults to id" do - expect(searchable_model.search_id).to equal(searchable_model.id) - end - end - - describe "#remove_from_elasticsearch" do - it "enqueues job to delete model document from elasticsearch" do - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [SearchableModel::SEARCH_CLASS.to_s, searchable_model.search_id]) do - searchable_model.remove_from_elasticsearch - end - end - end - - describe "#index_to_elasticsearch" do - it "enqueues job to index document to elasticsearch" do - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: ["SearchableModel", searchable_model.id]) do - searchable_model.index_to_elasticsearch - end - end - end - - describe "#index_to_elasticsearch_inline" do - it "indexes a document to elasticsearch inline" do - allow(model_class::SEARCH_CLASS).to receive(:index) - searchable_model.index_to_elasticsearch_inline - expect(model_class::SEARCH_CLASS).to have_received(:index).with(searchable_model.search_id, - id: searchable_model.search_id) - end - end - - describe "#serialized_search_hash" do - it "creates a valid serialized hash to send to elasticsearch" do - expect(searchable_model.serialized_search_hash.symbolize_keys.keys).to eq([:id]) - end - end - - describe "#elasticsearch_doc" do - it "finds document in elasticsearch" do - allow(model_class::SEARCH_CLASS).to receive(:find_document) - searchable_model.elasticsearch_doc - expect(model_class::SEARCH_CLASS).to have_received(:find_document) - end - end -end diff --git a/spec/models/listing_spec.rb b/spec/models/listing_spec.rb index 98eeb193a..248082e64 100644 --- a/spec/models/listing_spec.rb +++ b/spec/models/listing_spec.rb @@ -71,22 +71,4 @@ RSpec.describe Listing, type: :model do expect(credit.reload.purchase).to be_nil end end - - describe "#after_commit" do - it "on update enqueues worker to index tag to elasticsearch" do - listing.save - - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, listing.id]) do - listing.save - end - end - - it "on destroy enqueues job to delete listing from elasticsearch" do - listing.save - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, listing.id]) do - listing.destroy - end - end - end end diff --git a/spec/models/message_spec.rb b/spec/models/message_spec.rb index c58d3c807..625dfde2f 100644 --- a/spec/models/message_spec.rb +++ b/spec/models/message_spec.rb @@ -158,15 +158,6 @@ RSpec.describe Message, type: :model do end describe "#after_create" do - it "enqueues ChatChannels::IndexesMembershipsWorker" do - chat_channel.add_users([user]) - allow(ChatChannels::IndexesMembershipsWorker).to receive(:perform_async) - - create(:message, chat_channel: chat_channel, user: user) - - expect(ChatChannels::IndexesMembershipsWorker).to have_received(:perform_async) - end - context "when chat_action is left_channel" do it "does not update unopened message statuses" do chat_channel.add_users([user, user2]) diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index f9e1e44f1..bfac8f1ae 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -72,22 +72,9 @@ RSpec.describe Organization, type: :model do end context "when callbacks are triggered after commit" do - it "on update syncs elasticsearch data" do - article = create(:article, organization: organization) - sidekiq_perform_enqueued_jobs - new_org_name = "#{organization.name}+NEW" - organization.update(name: new_org_name) - sidekiq_perform_enqueued_jobs - expect(article.elasticsearch_doc.dig("_source", "organization", "name")).to eq(new_org_name) - end - it "on destroy updates related article data" do article = create(:article, organization: organization) - drain_all_sidekiq_jobs - expect(article.elasticsearch_doc.dig("_source", "organization", "id")).to eq(organization.id) organization.destroy - sidekiq_perform_enqueued_jobs - expect(article.elasticsearch_doc.dig("_source", "organization")).to be_nil expect(article.reload.cached_organization).to be_nil end end diff --git a/spec/models/podcast_episode_spec.rb b/spec/models/podcast_episode_spec.rb index 1431a52f4..cb657b273 100644 --- a/spec/models/podcast_episode_spec.rb +++ b/spec/models/podcast_episode_spec.rb @@ -20,11 +20,6 @@ RSpec.describe PodcastEpisode, type: :model do it { is_expected.to validate_presence_of(:title) } end - # Couldn't use shoulda matchers for these tests because: - # Shoulda uses `save(validate: false)` which skips validations, but runs callbacks - # So an invalid record is saved and the elasticsearch callback fails because there's no associated podcast - # https://git.io/fjg2g - it "validates guid uniqueness" do ep2 = build(:podcast_episode, guid: podcast_episode.guid) @@ -40,23 +35,6 @@ RSpec.describe PodcastEpisode, type: :model do end end - describe "#after_commit" do - it "on update enqueues job to index podcast_episode to elasticsearch" do - podcast_episode.save - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, podcast_episode.id]) do - podcast_episode.save - end - end - - it "on destroy enqueues job to delete podcast_episode from elasticsearch" do - podcast_episode.save - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, podcast_episode.search_id]) do - podcast_episode.destroy - end - end - end - describe "#search_id" do it "returns podcast_episode_ID" do expect(podcast_episode.search_id).to eq("podcast_episode_#{podcast_episode.id}") diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 0b0d7c656..b5ca689a7 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -128,37 +128,6 @@ RSpec.describe Tag, type: :model do expect(tag.mod_chat_channel).to eq(channel) end - describe "#after_commit" do - it "on update enqueues job to index tag to elasticsearch" do - tag.save - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, tag.id]) do - tag.save - end - end - - it "on destroy enqueues job to delete tag from elasticsearch" do - tag.save - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, tag.id]) do - tag.destroy - end - end - - it "syncs related elasticsearch documents" do - article = create(:article) - podcast_episode = create(:podcast_episode) - tag = described_class.find(article.tags.first.id) - podcast_episode.tags << tag - new_keywords = "keyword1, keyword2, keyword3" - sidekiq_perform_enqueued_jobs - - tag.update(keywords_for_search: new_keywords) - sidekiq_perform_enqueued_jobs - expect(collect_keywords(article)).to include(new_keywords) - expect(collect_keywords(podcast_episode)).to include(new_keywords) - end - end - describe "::aliased_name" do it "returns the preferred alias tag" do preferred_tag = create(:tag, name: "rails") @@ -187,8 +156,4 @@ RSpec.describe Tag, type: :model do expect(described_class.find_preferred_alias_for("something")).to eq("something") end end - - def collect_keywords(record) - record.elasticsearch_doc.dig("_source", "tags").flat_map { |t| t["keywords_for_search"] } - end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index d39b50c55..ddcf21998 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -298,29 +298,6 @@ RSpec.describe User, type: :model do end end - describe "#after_commit" do - it "on update enqueues job to index user to elasticsearch" do - user.save - sidekiq_assert_enqueued_with(job: Search::IndexWorker, args: [described_class.to_s, user.id]) do - user.save - end - end - - it "on update syncs elasticsearch data" do - allow(user).to receive(:sync_related_elasticsearch_docs) - user.save - expect(user).to have_received(:sync_related_elasticsearch_docs) - end - - it "on destroy enqueues job to delete user from elasticsearch" do - user.save - sidekiq_assert_enqueued_with(job: Search::RemoveFromIndexWorker, - args: [described_class::SEARCH_CLASS.to_s, user.id]) do - user.destroy - end - end - end - context "when callbacks are triggered before validation" do let(:user) { build(:user) } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index a48ff8378..d3b87d759 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -48,7 +48,6 @@ allowed_sites = [ "selenium-release.storage.googleapis.com", "developer.microsoft.com/en-us/microsoft-edge/tools/webdriver", "api.knapsackpro.com", - "elasticsearch", ] WebMock.disable_net_connect!(allow_localhost: true, allow: allowed_sites) @@ -74,7 +73,6 @@ RSpec.configure do |config| config.include FactoryBot::Syntax::Methods config.include OmniauthHelpers config.include SidekiqTestHelpers - config.include ElasticsearchHelpers config.after(:each, type: :system) do Warden::Manager._on_request.clear @@ -89,8 +87,6 @@ RSpec.configure do |config| # which we can then use to properly set the browser time for Capybara specs ENV["TZ"] = Time.zone.tzinfo.name - Search::Cluster.recreate_indexes - # NOTE: @citizen428 needed while we delegate from User to Profile to keep # spec changes limited for the time being. csv = Rails.root.join("lib/data/dev_profile_fields.csv") @@ -109,30 +105,10 @@ RSpec.configure do |config| # rubocop:enable RSpec/AnyInstance end - config.before(:each, stub_elasticsearch: true) do |_example| - stubbed_search_response = { "hits" => { "hits" => [] } } - allow(Search::Client).to receive(:search).and_return(stubbed_search_response) - allow(Search::Client).to receive(:index).and_return({ "_source" => {} }) - end - config.around(:each, :flaky) do |ex| ex.run_with_retry retry: 3 end - config.around(:each, elasticsearch_reset: true) do |example| - Search::Cluster.recreate_indexes - example.run - Search::Cluster.recreate_indexes - end - - config.around(:each, :elasticsearch) do |ex| - klasses = Array.wrap(ex.metadata[:elasticsearch]).map do |search_class| - Search.const_get(search_class) - end - klasses.each { |klass| clear_elasticsearch_data(klass) } - ex.run - end - config.around(:each, throttle: true) do |example| Rack::Attack.enabled = true example.run diff --git a/spec/requests/api/v0/health_checks_spec.rb b/spec/requests/api/v0/health_checks_spec.rb index b6df5a71e..6d7a5e811 100644 --- a/spec/requests/api/v0/health_checks_spec.rb +++ b/spec/requests/api/v0/health_checks_spec.rb @@ -22,21 +22,6 @@ RSpec.describe "HealthCheck", type: :request do end end - describe "GET /api/health_checks/search" do - it "returns json success if ping succeeds" do - get search_api_health_checks_path, headers: headers - expect(response.status).to eq(200) - expect(response.parsed_body["message"]).to eq("Search ping succeeded!") - end - - it "returns json failure if ping fails" do - allow(Search::Client).to receive(:ping).and_return(false) - get search_api_health_checks_path, headers: headers - expect(response.status).to eq(500) - expect(response.parsed_body["message"]).to eq("Search ping failed!") - end - end - describe "GET /api/health_checks/database" do it "returns json success if connection check succeeds" do get database_api_health_checks_path, headers: headers diff --git a/spec/requests/search_spec.rb b/spec/requests/search_spec.rb index 27d40aa33..f6bff8bdd 100644 --- a/spec/requests/search_spec.rb +++ b/spec/requests/search_spec.rb @@ -59,18 +59,6 @@ RSpec.describe "Search", type: :request, proper_status: true do end end - describe "GET /search/users" do - let(:mock_documents) { [{ "username" => "firstlast" }] } - - it "returns json" do - allow(Search::User).to receive(:search_documents).and_return( - mock_documents, - ) - get "/search/users" - expect(response.parsed_body).to eq("result" => mock_documents) - end - end - describe "GET /search/usernames" do before do sign_in authorized_user diff --git a/spec/serializers/search/article_serializer_spec.rb b/spec/serializers/search/article_serializer_spec.rb index d6b2fb08d..5cb168596 100644 --- a/spec/serializers/search/article_serializer_spec.rb +++ b/spec/serializers/search/article_serializer_spec.rb @@ -16,12 +16,6 @@ RSpec.describe Search::ArticleSerializer do expect(data_hash.keys).to include(:id, :body_text, :hotness_score) end - it "creates valid json for Elasticsearch", elasticsearch: "FeedContent" do - data_hash = described_class.new(article).serializable_hash.dig(:data, :attributes) - result = Article::SEARCH_CLASS.index(article.id, data_hash) - expect(result["result"]).to eq("created") - end - it "correctly serializes video duration in minutes when video_duration_in_seconds is nil" do data_hash = described_class.new(article).serializable_hash.dig(:data, :attributes) expect(data_hash[:video_duration_in_minutes]).to eq(0) diff --git a/spec/serializers/search/comment_serializer_spec.rb b/spec/serializers/search/comment_serializer_spec.rb index 10a624e43..65b81694f 100644 --- a/spec/serializers/search/comment_serializer_spec.rb +++ b/spec/serializers/search/comment_serializer_spec.rb @@ -11,10 +11,4 @@ RSpec.describe Search::CommentSerializer do expect(data_hash[:user]).to eq(user_data) expect(data_hash.keys).to include(:id, :body_text, :hotness_score, :title) end - - it "creates valid json for Elasticsearch", elasticsearch: "FeedContent" do - data_hash = described_class.new(comment).serializable_hash.dig(:data, :attributes) - result = Comment::SEARCH_CLASS.index(comment.id, data_hash) - expect(result["result"]).to eq("created") - end end diff --git a/spec/serializers/search/user_serializer_spec.rb b/spec/serializers/search/user_serializer_spec.rb index 12451dd47..efa891ba8 100644 --- a/spec/serializers/search/user_serializer_spec.rb +++ b/spec/serializers/search/user_serializer_spec.rb @@ -7,26 +7,4 @@ RSpec.describe Search::UserSerializer do data_hash = described_class.new(user).serializable_hash.dig(:data, :attributes) expect(data_hash.keys).to include(:id, :name, :path, :username, :roles) end - - it "creates valid json for Elasticsearch", elasticsearch: "User" do - data_hash = described_class.new(user).serializable_hash.dig(:data, :attributes) - result = User::SEARCH_CLASS.index(user.id, data_hash) - expect(result["result"]).to eq("created") - end - - it "indexes profile fields as a nested field", elasticsearch: "User", aggregate_failures: true do - data_hash = described_class.new(user).serializable_hash.dig(:data, :attributes) - result = User::SEARCH_CLASS.index(user.id, data_hash) - expect(result["result"]).to eq("created") - indexed_profile_fields = user.reload.elasticsearch_doc.dig("_source", "profile_fields") - expect(indexed_profile_fields).to be_an_instance_of(Array) - end - - it "indexes custom profile fields as a nested field", elasticsearch: "User", aggregate_failures: true do - data_hash = described_class.new(user).serializable_hash.dig(:data, :attributes) - result = User::SEARCH_CLASS.index(user.id, data_hash) - expect(result["result"]).to eq("created") - indexed_custom_fields = user.reload.elasticsearch_doc.dig("_source", "custom_profile_fields") - expect(indexed_custom_fields).to be_an_instance_of(Array) - end end diff --git a/spec/services/data_sync/elasticsearch/article_spec.rb b/spec/services/data_sync/elasticsearch/article_spec.rb deleted file mode 100644 index f28ae30b8..000000000 --- a/spec/services/data_sync/elasticsearch/article_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "rails_helper" - -RSpec.describe DataSync::Elasticsearch::Article, type: :service do - it "defines necessary constants" do - expect(described_class::RELATED_DOCS).not_to be_nil - expect(described_class::SHARED_FIELDS).not_to be_nil - end -end diff --git a/spec/services/data_sync/elasticsearch/base_spec.rb b/spec/services/data_sync/elasticsearch/base_spec.rb deleted file mode 100644 index e965793d6..000000000 --- a/spec/services/data_sync/elasticsearch/base_spec.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "rails_helper" - -RSpec.describe DataSync::Elasticsearch::Base, type: :service do - let!(:updated_record) { instance_double("Record", saved_changes: { name: %w[name1 name2] }) } - let(:syncer) { described_class.new(updated_record) } - - describe "#call" do - before do - stub_const("#{described_class}::SHARED_FIELDS", %i[name]) - allow(syncer).to receive(:sync_related_documents) - end - - it "syncs related_documents when sync is needed " do - syncer.call - expect(syncer).to have_received(:sync_related_documents) - end - - it "does not sync related_documents when sync is not needed" do - allow(updated_record).to receive(:saved_changes).and_return(twitter_url: %w[url1 url2]) - syncer.call - expect(syncer).not_to have_received(:sync_related_documents) - end - end -end diff --git a/spec/services/data_sync/elasticsearch/organization_spec.rb b/spec/services/data_sync/elasticsearch/organization_spec.rb deleted file mode 100644 index cab5d2e6d..000000000 --- a/spec/services/data_sync/elasticsearch/organization_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "rails_helper" - -RSpec.describe DataSync::Elasticsearch::Organization, type: :service do - it "defines necessary constants" do - expect(described_class::RELATED_DOCS).not_to be_nil - expect(described_class::SHARED_FIELDS).not_to be_nil - end -end diff --git a/spec/services/data_sync/elasticsearch/tag_spec.rb b/spec/services/data_sync/elasticsearch/tag_spec.rb deleted file mode 100644 index 589f39743..000000000 --- a/spec/services/data_sync/elasticsearch/tag_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "rails_helper" - -RSpec.describe DataSync::Elasticsearch::Tag, type: :service do - it "defines necessary constants" do - expect(described_class::RELATED_DOCS).not_to be_nil - expect(described_class::SHARED_FIELDS).not_to be_nil - end -end diff --git a/spec/services/data_sync/elasticsearch/user_spec.rb b/spec/services/data_sync/elasticsearch/user_spec.rb deleted file mode 100644 index 3320d7a93..000000000 --- a/spec/services/data_sync/elasticsearch/user_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require "rails_helper" - -RSpec.describe DataSync::Elasticsearch::User, type: :service do - it "defines necessary constants" do - expect(described_class::RELATED_DOCS).not_to be_nil - expect(described_class::SHARED_FIELDS).not_to be_nil - end -end diff --git a/spec/services/edge_cache/bust_commentable_spec.rb b/spec/services/edge_cache/bust_commentable_spec.rb index 71e3ddfaa..2fbcc2a7f 100644 --- a/spec/services/edge_cache/bust_commentable_spec.rb +++ b/spec/services/edge_cache/bust_commentable_spec.rb @@ -15,10 +15,4 @@ RSpec.describe EdgeCache::BustCommentable, type: :service do expect(EdgeCache::BustComment).to have_received(:call).with(commentable).once expect(cache_bust).to have_received(:call).with("#{commentable.path}/comments").once end - - it "indexes commentable to Elasticsearch" do - allow(commentable).to receive(:index_to_elasticsearch_inline) - described_class.call(commentable) - expect(commentable).to have_received(:index_to_elasticsearch_inline).once - end end diff --git a/spec/services/moderator/banish_user_spec.rb b/spec/services/moderator/banish_user_spec.rb index 2f615417b..d2464ae82 100644 --- a/spec/services/moderator/banish_user_spec.rb +++ b/spec/services/moderator/banish_user_spec.rb @@ -27,7 +27,7 @@ RSpec.describe Moderator::BanishUser, type: :service do create(:comment, user: user, commentable: article) sidekiq_perform_enqueued_jobs - sidekiq_perform_enqueued_jobs(except: Search::IndexWorker) do + sidekiq_perform_enqueued_jobs do described_class.call(user: user, admin: admin) end expect(user.comments.count).to eq 0 diff --git a/spec/services/moderator/merge_user_spec.rb b/spec/services/moderator/merge_user_spec.rb index 1d6f08498..c27c5e4a9 100644 --- a/spec/services/moderator/merge_user_spec.rb +++ b/spec/services/moderator/merge_user_spec.rb @@ -23,20 +23,6 @@ RSpec.describe Moderator::MergeUser, type: :service do expect(User.find_by(id: keep_user.id)).not_to be_nil end - it "updates documents in Elasticsearch" do - related_records - drain_all_sidekiq_jobs - expect(article.elasticsearch_doc.dig("_source", "user", "id")).to eq(delete_user_id) - expect(comment.elasticsearch_doc.dig("_source", "user", "id")).to eq(delete_user_id) - - sidekiq_perform_enqueued_jobs do - described_class.call(admin: admin, keep_user: keep_user, delete_user_id: delete_user.id) - end - drain_all_sidekiq_jobs - expect(article.reload.elasticsearch_doc.dig("_source", "user", "id")).to eq(keep_user.id) - expect(comment.reload.elasticsearch_doc.dig("_source", "user", "id")).to eq(keep_user.id) - end - it "updates badge_achievements_count" do create_list(:badge_achievement, 2, user: delete_user) diff --git a/spec/services/podcasts/create_episode_spec.rb b/spec/services/podcasts/create_episode_spec.rb index 58939b85d..ef2319169 100644 --- a/spec/services/podcasts/create_episode_spec.rb +++ b/spec/services/podcasts/create_episode_spec.rb @@ -19,11 +19,6 @@ RSpec.describe Podcasts::CreateEpisode, type: :service do end.to change(PodcastEpisode, :count).by(1) end - it "indexes the episode" do - sidekiq_perform_enqueued_jobs { described_class.call(podcast.id, item) } - expect { podcast.podcast_episodes.each(&:elasticsearch_doc) }.not_to raise_error - end - it "creates an episode with correct data" do episode = described_class.call(podcast.id, item) expect(episode.title).to eq("Individual Contributor Career Growth w/ Matt Klein (part 1)") diff --git a/spec/services/search/base_spec.rb b/spec/services/search/base_spec.rb deleted file mode 100644 index 47a971f8c..000000000 --- a/spec/services/search/base_spec.rb +++ /dev/null @@ -1,170 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::Base, type: :service do - let(:document_id) { 123 } - - def recreate_tag_index - Search::Tag.delete_index if described_class.index_exists? - Search::Tag.create_index - Search::Tag.add_alias - Search::Tag.update_mappings - end - - before do - # Need to use an existing index name to ensure proper data cleanup - stub_const("#{described_class}::INDEX_NAME", "tags_#{Rails.env}") - stub_const("#{described_class}::INDEX_ALIAS", "tags_#{Rails.env}_alias") - stub_const("#{described_class}::MAPPINGS", Search::Tag::MAPPINGS) - Search::Tag.refresh_index - clear_elasticsearch_data(Search::Tag) - allow(described_class).to receive(:index_settings).and_return({}) - end - - describe "::index" do - it "indexes a document to elasticsearch" do - expect { described_class.find_document(document_id) }.to raise_error(Search::Errors::Transport::NotFound) - described_class.index(document_id, id: document_id) - expect(described_class.find_document(document_id)).not_to be_nil - end - - it "sets last_indexed_at field" do - Timecop.freeze(Time.current) do - described_class.index(document_id, id: document_id) - last_indexed_at = described_class.find_document(document_id).dig("_source", "last_indexed_at") - expect(Time.zone.parse(last_indexed_at).to_i).to eq(Time.current.to_i) - end - end - end - - describe "::bulk_index" do - it "indexes a set of data hashes to Elasticsearch" do - id_list = [123, 456, 789] - id_list.each do |document_id| - expect { described_class.find_document(document_id) }.to raise_error(Search::Errors::Transport::NotFound) - end - data_hashes = id_list.map { |id| { id: id, name: "i_am_a_tag" } } - described_class.bulk_index(data_hashes) - - id_list.each do |document_id| - doc = described_class.find_document(document_id) - expect(doc.dig("_source", "id")).to eql(document_id) - end - end - - it "sets last_indexed_at field" do - Timecop.freeze(Time.current) do - described_class.bulk_index([{ id: document_id, name: "i_am_a_tag" }]) - last_indexed_at = described_class.find_document(document_id).dig("_source", "last_indexed_at") - expect(Time.zone.parse(last_indexed_at).to_i).to eq(Time.current.to_i) - end - end - end - - describe "::find_document" do - it "fetches a document for a given ID from elasticsearch" do - described_class.index(document_id, id: document_id) - expect { described_class.find_document(document_id) }.not_to raise_error - end - end - - describe "::delete_document" do - it "deletes a document for a given ID from elasticsearch" do - described_class.index(document_id, id: document_id) - expect { described_class.find_document(document_id) }.not_to raise_error - described_class.delete_document(document_id) - expect { described_class.find_document(document_id) }.to raise_error(Search::Errors::Transport::NotFound) - end - end - - describe "::create_index" do - before { recreate_tag_index } - - after { recreate_tag_index } - - it "creates an elasticsearch index with INDEX_NAME" do - described_class.delete_index - expect(Search::Client.indices.exists(index: described_class::INDEX_NAME)).to eq(false) - described_class.create_index - expect(Search::Client.indices.exists(index: described_class::INDEX_NAME)).to eq(true) - end - - it "creates an elasticsearch index with name argument" do - other_name = "random" - expect(Search::Client.indices.exists(index: other_name)).to eq(false) - described_class.create_index(index_name: other_name) - expect(Search::Client.indices.exists(index: other_name)).to eq(true) - - # Have to cleanup index since it wont automatically be handled by our cluster class bc of the unexpected name - described_class.delete_index(index_name: other_name) - end - end - - describe "::update_index" do - before do - recreate_tag_index - allow(described_class).to receive(:dynamic_index_settings).and_return( - refresh_interval: "10s", - ) - end - - after { recreate_tag_index } - - it "updates elasticsearch index settings with INDEX_NAME" do - index_info = Search::Client.indices.get(index: described_class::INDEX_NAME) - expect(index_info.values.first.dig("settings", "index", "refresh_interval")).to be_nil - described_class.update_index - - new_index_info = Search::Client.indices.get(index: described_class::INDEX_NAME) - expect(new_index_info.values.first.dig("settings", "index", "refresh_interval")).to eq("10s") - end - end - - describe "::delete_index" do - before { recreate_tag_index } - - after { recreate_tag_index } - - it "deletes an elasticsearch index with INDEX_NAME" do - expect(Search::Client.indices.exists(index: described_class::INDEX_NAME)).to eq(true) - described_class.delete_index - expect(Search::Client.indices.exists(index: described_class::INDEX_NAME)).to eq(false) - end - - it "deletes an elasticsearch index with name argument" do - other_name = "random" - described_class.create_index(index_name: other_name) - expect(Search::Client.indices.exists(index: other_name)).to eq(true) - - described_class.delete_index(index_name: other_name) - expect(Search::Client.indices.exists(index: other_name)).to eq(false) - end - end - - describe "::add_alias" do - it "adds alias INDEX_ALIAS to elasticsearch index with INDEX_NAME" do - Search::Client.indices.delete_alias(index: described_class::INDEX_NAME, name: described_class::INDEX_ALIAS) - expect(Search::Client.indices.exists(index: described_class::INDEX_ALIAS)).to eq(false) - described_class.add_alias - expect(Search::Client.indices.exists(index: described_class::INDEX_ALIAS)).to eq(true) - end - - it "adds custom alias to elasticsearch index with INDEX_NAME" do - other_alias = "random" - expect(Search::Client.indices.exists(index: other_alias)).to eq(false) - described_class.add_alias(index_name: described_class::INDEX_NAME, index_alias: other_alias) - expect(Search::Client.indices.exists(index: other_alias)).to eq(true) - end - end - - describe "::update_mappings" do - it "updates index mappings" do - other_name = "index_name" - allow(Search::Client.indices).to receive(:put_mapping) - - described_class.update_mappings(index_alias: other_name) - expect(Search::Client.indices).to have_received(:put_mapping).with( - index: other_name, body: described_class::MAPPINGS, - ) - end - end -end diff --git a/spec/services/search/chat_channel_membership_spec.rb b/spec/services/search/chat_channel_membership_spec.rb deleted file mode 100644 index 3914ff364..000000000 --- a/spec/services/search/chat_channel_membership_spec.rb +++ /dev/null @@ -1,148 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::ChatChannelMembership, type: :service do - describe "::search_documents", elasticsearch: "ChatChannelMembership" do - let(:user) { create(:user) } - let(:chat_channel_membership1) { create(:chat_channel_membership, user_id: user.id) } - let(:chat_channel_membership2) { create(:chat_channel_membership, user_id: user.id) } - - it "parses chat_channel_membership document hits from search response" do - mock_search_response = { "hits" => { "hits" => {} } } - allow(described_class).to receive(:search) { mock_search_response } - described_class.search_documents(params: { user_id: 1 }) - expect(described_class).to have_received(:search).with(body: a_kind_of(Hash)) - end - - context "with a query" do - it "searches by channel_text" do - allow(chat_channel_membership1).to receive(:channel_text).and_return("a name") - allow(chat_channel_membership2).to receive(:channel_text).and_return("another name and slug") - index_documents([chat_channel_membership1, chat_channel_membership2]) - name_params = { size: 5, channel_text: "name", user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: name_params) - expect(chat_channel_membership_docs.count).to eq(2) - doc_ids = chat_channel_membership_docs.map { |t| t["id"] } - expect(doc_ids).to include(chat_channel_membership1.id, chat_channel_membership2.id) - end - end - - context "with a filter" do - it "searches by viewable_by" do - new_user = create(:user) - chat_channel_membership3 = create(:chat_channel_membership, user_id: new_user.id) - index_documents([chat_channel_membership1, chat_channel_membership2, chat_channel_membership3]) - params = { size: 5, user_id: [new_user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(1) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership3.id) - end - - it "searches by channel_status" do - allow(chat_channel_membership1).to receive(:channel_status).and_return("popping") - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { size: 5, channel_status: "popping", user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(1) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership1.id) - end - - it "searches by channel_type" do - allow(chat_channel_membership2).to receive(:channel_type).and_return("invite_only") - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { size: 5, channel_type: "invite_only", user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(1) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership2.id) - end - - it "only returns active status memberships" do - chat_channel_membership1.update(status: "inactive") - chat_channel_membership2.update(status: "active") - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { size: 5, user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(1) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership2.id) - end - end - - context "with a query and filter" do - it "searches by channel_text and status" do - allow(chat_channel_membership1).to receive(:channel_text).and_return("a name") - allow(chat_channel_membership2).to receive(:channel_text).and_return("another name") - chat_channel_membership1.update(status: "active") - chat_channel_membership2.update(status: "inactive") - index_documents([chat_channel_membership1, chat_channel_membership2]) - name_params = { size: 5, channel_text: "name", status: "active", user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: name_params) - expect(chat_channel_membership_docs.count).to eq(1) - doc_ids = chat_channel_membership_docs.map { |t| t["id"] } - expect(doc_ids).to include(chat_channel_membership1.id) - end - end - - it "sorts documents for given field" do - allow(chat_channel_membership1).to receive(:channel_type).and_return("not_direct") - allow(chat_channel_membership2).to receive(:channel_type).and_return("direct") - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { size: 5, sort_by: "channel_type", sort_direction: "asc", user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(2) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership2.id) - expect(chat_channel_membership_docs.last["id"]).to eq(chat_channel_membership1.id) - end - - it "sorts documents by channel_last_message_at by default" do - allow(chat_channel_membership1).to receive(:channel_last_message_at).and_return(Time.current) - allow(chat_channel_membership2).to receive(:channel_last_message_at).and_return(1.year.ago) - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { size: 5, user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(2) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership1.id) - expect(chat_channel_membership_docs.last["id"]).to eq(chat_channel_membership2.id) - end - - it "will return a set number of docs based on pagination params" do - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { page: 0, per_page: 1, user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs.count).to eq(1) - end - - it "paginates the results" do - allow(chat_channel_membership1).to receive(:channel_last_message_at).and_return(Time.current) - allow(chat_channel_membership2).to receive(:channel_last_message_at).and_return(1.year.ago) - index_documents([chat_channel_membership1, chat_channel_membership2]) - first_page_params = { page: 0, per_page: 1, sort_by: "channel_last_message_at", order: "dsc", user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: first_page_params) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership1.id) - - second_page_params = { page: 1, per_page: 1, sort_by: "channel_last_message_at", order: "dsc", - user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: second_page_params) - expect(chat_channel_membership_docs.first["id"]).to eq(chat_channel_membership2.id) - end - - it "returns an empty Array if no results are found" do - allow(chat_channel_membership1).to receive(:channel_last_message_at).and_return(Time.current) - allow(chat_channel_membership2).to receive(:channel_last_message_at).and_return(1.year.ago) - index_documents([chat_channel_membership1, chat_channel_membership2]) - params = { page: 3, per_page: 1, user_id: [user.id] } - - chat_channel_membership_docs = described_class.search_documents(params: params) - expect(chat_channel_membership_docs).to eq([]) - end - end -end diff --git a/spec/services/search/cluster_spec.rb b/spec/services/search/cluster_spec.rb deleted file mode 100644 index 6ae6433b4..000000000 --- a/spec/services/search/cluster_spec.rb +++ /dev/null @@ -1,88 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::Cluster, type: :service do - describe "::recreate_indexes" do - it "destroys and sets up indexes" do - allow(described_class).to receive(:delete_indexes) - allow(described_class).to receive(:setup_indexes) - - described_class.recreate_indexes - expect(described_class).to have_received(:delete_indexes) - expect(described_class).to have_received(:setup_indexes) - end - end - - describe "::setup_indexes" do - it "creates, adds aliases and updates mappings for indexes" do - allow(described_class).to receive(:create_indexes) - allow(described_class).to receive(:add_aliases) - allow(described_class).to receive(:update_mappings) - - described_class.setup_indexes - expect(described_class).to have_received(:create_indexes) - expect(described_class).to have_received(:add_aliases) - expect(described_class).to have_received(:update_mappings) - end - end - - describe "::create_indexes" do - it "calls create_index for each search class" do - allow(Search::Client.indices).to receive(:exists).and_return(false) - described_class::SEARCH_CLASSES.each do |search_class| - allow(search_class).to receive(:create_index) - end - described_class.create_indexes - expect(described_class::SEARCH_CLASSES).to all(have_received(:create_index)) - end - end - - describe "::update_indexes" do - it "calls update_index for each search class" do - described_class::SEARCH_CLASSES.each do |search_class| - allow(search_class).to receive(:update_index) - end - described_class.update_indexes - expect(described_class::SEARCH_CLASSES).to all(have_received(:update_index)) - end - end - - describe "::delete_indexes" do - it "calls delete_index for each search class" do - allow(Search::Client.indices).to receive(:exists).and_return(true) - described_class::SEARCH_CLASSES.each do |search_class| - allow(search_class).to receive(:delete_index) - end - described_class.delete_indexes - expect(described_class::SEARCH_CLASSES).to all(have_received(:delete_index)) - end - end - - describe "::add_aliases" do - it "calls add_alias for each search class" do - described_class::SEARCH_CLASSES.each do |search_class| - allow(search_class).to receive(:add_alias) - end - described_class.add_aliases - expect(described_class::SEARCH_CLASSES).to all(have_received(:add_alias)) - end - end - - describe "::update_mappings" do - it "calls update_mappings for each search class" do - described_class::SEARCH_CLASSES.each do |search_class| - allow(search_class).to receive(:update_mappings) - end - described_class.update_mappings - expect(described_class::SEARCH_CLASSES).to all(have_received(:update_mappings)) - end - end - - describe "::update_settings" do - it "updates cluster settings" do - described_class.update_settings - cluster_settings = Search::Client.cluster.get_settings - auto_create_setting = cluster_settings.dig("persistent", "action", "auto_create_index") - expect(auto_create_setting).to eq("false") - end - end -end diff --git a/spec/services/search/feed_content_spec.rb b/spec/services/search/feed_content_spec.rb deleted file mode 100644 index 1670603f1..000000000 --- a/spec/services/search/feed_content_spec.rb +++ /dev/null @@ -1,224 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::FeedContent, type: :service do - it "defines INDEX_NAME, INDEX_ALIAS, and MAPPINGS", :aggregate_failures do - expect(described_class::INDEX_NAME).not_to be_nil - expect(described_class::INDEX_ALIAS).not_to be_nil - expect(described_class::MAPPINGS).not_to be_nil - end - - describe "::search_documents", elasticsearch: "FeedContent" do - let(:article1) { create(:article, published_at: 1.day.ago) } - let(:article2) { create(:article, published_at: 2.days.ago) } - let(:article3) { create(:article, published_at: Time.current) } - - it "parses feed content document hits from search response" do - mock_search_response = { "hits" => { "hits" => {} } } - allow(described_class).to receive(:search) { mock_search_response } - described_class.search_documents(params: {}) - expect(described_class).to have_received(:search).with(body: a_kind_of(Hash)) - end - - it "returns highlighted fields" do - allow(article1).to receive(:body_text).and_return("I love ruby") - allow(article2).to receive(:body_text).and_return("Ruby Tuesday is love") - index_documents([article1, article2]) - query_params = { size: 5, search_fields: "love ruby" } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(2) - doc_highlights = feed_docs.map { |t| t.dig("highlight", "body_text") }.flatten - expect(doc_highlights).to include("I love ruby", - "Ruby Tuesday is love") - end - - it "returns fields necessary for the view" do - allow(article1).to receive(:flare_tag).and_return(name: "help", bg_color_hex: nil, text_color_hex: nil) - view_keys = %w[ - id title path class_name cloudinary_video_url comments_count flare_tag tag_list user_id user - published_at_int published_timestamp readable_publish_date - ] - flare_tag_keys = %w[name bg_color_hex text_color_hex] - user_keys = %w[username name profile_image_90] - podcast_keys = %w[slug image_url title] - index_documents([article1]) - - feed_doc = described_class.search_documents(params: { size: 1 }).first - expect(feed_doc.keys).to include(*view_keys) - expect(feed_doc["user"].keys).to include(*user_keys) - expect(feed_doc["flare_tag"].keys).to include(*flare_tag_keys) - expect(feed_doc["podcast"].keys).to include(*podcast_keys) - end - - context "with chronological sorting specified" do - before do - allow(article1).to receive(:title).and_return("Ruby Slippers") - allow(article2).to receive(:title).and_return("Ruby Tuesday") - allow(article3).to receive(:title).and_return("Just Ruby") - end - - it "sorts articles from newest to oldest" do - index_documents([article1, article2, article3]) - query_params = { size: 5, search_fields: "ruby", sort_by: "published_at", sort_direction: "desc" } - - titles_in_order = described_class.search_documents(params: query_params).map { |doc| doc["title"] } - - expect(titles_in_order).to eq ["Just Ruby", "Ruby Slippers", "Ruby Tuesday"] - end - - it "sorts articles from oldest to newest" do - index_documents([article1, article2, article3]) - query_params = { size: 5, search_fields: "ruby", sort_by: "published_at", sort_direction: "asc" } - - titles_in_order = described_class.search_documents(params: query_params).map { |doc| doc["title"] } - - expect(titles_in_order).to eq ["Ruby Tuesday", "Ruby Slippers", "Just Ruby"] - end - end - - context "with a query" do - it "searches by search_fields" do - allow(article1).to receive(:title).and_return("ruby") - allow(article2).to receive(:body_text).and_return("Ruby Tuesday") - index_documents([article1, article2]) - query_params = { size: 5, search_fields: "ruby" } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(2) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article1.id, article2.id) - end - - # Skipped because this seems inconsistent - xit "bumps scores for words closer together" do - allow(article1).to receive(:body_text).and_return("Ruby I dont know maybe is cool") - allow(article2).to receive(:body_text).and_return("Ruby is cool I dont know maybe") - index_documents([article1, article2]) - query_params = { size: 5, search_fields: "ruby is cool" } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(2) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to eq([article2.id, article1.id]) - end - end - - context "with a filter term" do - it "filters by tag names" do - article1.tags << create(:tag, name: "ruby") - article2.tags << create(:tag, name: "python") - index_documents([article1, article2]) - query_params = { size: 5, tag_names: "ruby" } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article1.id) - end - - it "filters by multiple tag names when tag_boolean_mode is set to all" do - tag_one = create(:tag) - tag_two = create(:tag) - article1.tags << tag_one - article2.tags << tag_two - article2.tags << tag_one - index_documents([article1, article2]) - query_params = { - tag_names: [tag_one.name, tag_two.name], - tag_boolean_mode: "all" - } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article2.id) - end - - it "filters by user_id" do - index_documents([article1, article2]) - query_params = { size: 5, user_id: article1.user_id } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article1.id) - end - - it "filters by approved" do - article1.update(approved: false) - article2.update(approved: true) - index_documents([article1, article2]) - query_params = { size: 5, approved: true } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article2.id) - end - - it "filters by class_name" do - pde = create(:podcast_episode) - index_documents([pde, article1, article2]) - query_params = { size: 5, class_name: "PodcastEpisode" } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(pde.id) - end - - it "filters by id" do - index_documents([article1, article2]) - query_params = { size: 5, id: ["article_#{article1.id}"] } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article1.id) - expect(doc_ids).not_to include(article2.id) - end - end - - context "with range keys" do - it "searches by published_at" do - article1.update(published_at: 1.year.ago) - article2.update(published_at: 1.month.ago) - index_documents([article1, article2]) - query_params = { size: 5, published_at: { gte: 2.months.ago.iso8601 } } - - feed_docs = described_class.search_documents(params: query_params) - expect(feed_docs.count).to eq(1) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to include(article2.id) - end - end - - context "with default sorting" do - it "sorts by Elasticsearch _score which is weighted based on article score" do - ruby_tag = create(:tag, name: "ruby") - allow(article1).to receive(:score).and_return(200) - article1.tags << ruby_tag - allow(article2).to receive(:score).and_return(1500) - article2.tags << ruby_tag - index_documents([article1, article2]) - query_params = { size: 5, search_fields: "ruby" } - - feed_docs = described_class.search_documents(params: query_params) - doc_ids = feed_docs.map { |t| t["id"] } - expect(doc_ids).to eq([article2.id, article1.id]) - end - end - end - - describe "document counts", elasticsearch: "FeedContent" do - it "returns counts for each document class" do - article = create(:article) - comment = create(:comment) - pde = create(:podcast_episode) - index_documents([article, comment, pde]) - described_class::INCLUDED_CLASS_NAMES.each do |class_name| - expect(described_class.public_send("#{class_name.underscore.pluralize}_document_count")).to eq(1) - end - end - end -end diff --git a/spec/services/search/listing_spec.rb b/spec/services/search/listing_spec.rb deleted file mode 100644 index c19865167..000000000 --- a/spec/services/search/listing_spec.rb +++ /dev/null @@ -1,178 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::Listing, type: :service do - describe "::search_documents", elasticsearch: "Listing" do - let(:listing) { create(:listing) } - - it "parses listing document hits from search response" do - mock_search_response = { "hits" => { "hits" => {} } } - allow(described_class).to receive(:search) { mock_search_response } - described_class.search_documents(params: {}) - expect(described_class).to have_received(:search).with(body: a_kind_of(Hash)) - end - - context "with a query" do - # listing_search is a copy_to field including: - # body_markdown, location, slug, tags, and title - it "searches by listing_search" do - listing1 = create(:listing, body_markdown: "# body_markdown with test") - listing2 = create(:listing, location: "a test location") - listing3 = create(:listing, title: "this test title is testing slug") - listing4 = create(:listing, tag_list: ["test"]) - listing5 = create(:listing, title: "a test title") - listings = [listing1, listing2, listing3, listing4, listing5] - index_documents(listings) - - listing_docs = described_class.search_documents(params: { size: 5, listing_search: "test" }) - expect(listing_docs.count).to eq(5) - expect(listing_docs.map { |t| t["id"] }).to match_array(listings.map(&:id)) - end - end - - context "with a term filter" do - it "searches by category" do - new_category = create(:listing_category, :cfp) - listing.update(listing_category_id: new_category.id) - index_documents(listing) - params = { size: 5, category: new_category.slug } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by contact_via_connect" do - listing.update(contact_via_connect: true) - index_documents(listing) - params = { size: 5, contact_via_connect: true } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by location" do - listing.update(location: "a location") - index_documents(listing) - params = { size: 5, location: "location" } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by slug" do - slug_listing = create(:listing, title: "A slug is created from this title in a callback") - index_documents(slug_listing) - params = { size: 5, slug: "slug" } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(slug_listing.id) - end - - it "searches by tags" do - listing.update(tag_list: %w[beginners career]) - index_documents(listing) - params = { size: 5, tags: "career" } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by title" do - listing.update(title: "An Amazing Title") - index_documents(listing) - params = { size: 5, title: "amazing" } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by user_id" do - index_documents(listing) - params = { size: 5, user_id: listing.user_id } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by bumped_at" do - listing.update(bumped_at: 1.day.from_now) - index_documents(listing) - params = { size: 5, bumped_at: { gt: Time.current } } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "searches by expires_at" do - listing.update(expires_at: 1.day.ago) - index_documents(listing) - params = { size: 5, expires_at: { lt: Time.current } } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(1) - expect(listing_docs.first["id"]).to eq(listing.id) - end - end - - it "sorts documents for a given field" do - listing = create(:listing) - cfp = create(:listing_category, :cfp) - listing2 = create(:listing, listing_category_id: cfp.id) - index_documents([listing, listing2]) - params = { size: 5, sort_by: "category", sort_direction: "asc" } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(2) - expect(listing_docs.first["id"]).to eq(listing2.id) - expect(listing_docs.last["id"]).to eq(listing.id) - end - - it "sorts documents by bumped_at by default" do - listing.update(bumped_at: 1.year.ago) - listing2 = create(:listing, bumped_at: Time.current) - index_documents([listing, listing2]) - params = { size: 5 } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs.count).to eq(2) - expect(listing_docs.first["id"]).to eq(listing2.id) - expect(listing_docs.last["id"]).to eq(listing.id) - end - - it "paginates the results" do - listing.update(bumped_at: 1.year.ago) - listing2 = create(:listing, bumped_at: Time.current) - index_documents([listing, listing2]) - first_page_params = { page: 0, per_page: 1, sort_by: "bumped_at", order: "dsc" } - - listing_docs = described_class.search_documents(params: first_page_params) - expect(listing_docs.first["id"]).to eq(listing2.id) - - second_page_params = { page: 1, per_page: 1, sort_by: "bumped_at", order: "dsc" } - - listing_docs = described_class.search_documents(params: second_page_params) - expect(listing_docs.first["id"]).to eq(listing.id) - end - - it "returns an empty Array if no results are found" do - jobs_category = create(:listing_category, :jobs) - listing.update(listing_category: jobs_category) - - cfp_category = create(:listing_category, :cfp) - listing2 = create(:listing, - listing_category: cfp_category) - index_documents([listing, listing2]) - params = { page: 3, per_page: 1 } - - listing_docs = described_class.search_documents(params: params) - expect(listing_docs).to eq([]) - end - end -end diff --git a/spec/services/search/query_builders/chat_channel_membership_spec.rb b/spec/services/search/query_builders/chat_channel_membership_spec.rb deleted file mode 100644 index 26afaaeda..000000000 --- a/spec/services/search/query_builders/chat_channel_membership_spec.rb +++ /dev/null @@ -1,103 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::QueryBuilders::ChatChannelMembership, type: :service do - describe "::initialize" do - it "sets params" do - filter_params = { foo: "bar" } - filter = described_class.new(params: filter_params) - expect(filter.params).to include(filter_params) - end - - it "builds query body" do - filter = described_class.new(params: {}) - expect(filter.body).not_to be_nil - end - end - - describe "#as_hash" do - it "applies FILTER_KEYS from params" do - params = { channel_status: "active", channel_type: "direct", user_id: 1 } - filter = described_class.new(params: params) - expected_filters = [ - { "term" => { "channel_status" => "active" } }, - { "term" => { "channel_type" => "direct" } }, - { "terms" => { "status" => %w[active joining_request] } }, - { "terms" => { "viewable_by" => 1 } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "applies QUERY_KEYS from params" do - params = { channel_text: "a_name" } - query = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "a_name*", "fields" => [:channel_text], "lenient" => true, "analyze_wildcard" => true - } - }] - expect(query.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - end - - it "applies QUERY_KEYS and FILTER_KEYS from params" do - params = { channel_text: "a_name", channel_status: "active", user_id: 1 } - query = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "a_name*", "fields" => [:channel_text], "lenient" => true, "analyze_wildcard" => true - } - }] - expected_filters = [ - { "term" => { "channel_status" => "active" } }, - { "terms" => { "status" => %w[active joining_request] } }, - { "terms" => { "viewable_by" => 1 } }, - ] - expect(query.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - expect(query.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "always applies viewable_by and status params" do - params = { user_id: 1 } - filter = described_class.new(params: params) - expected_filters = [ - { "terms" => { "status" => %w[active joining_request] } }, - { "terms" => { "viewable_by" => 1 } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "always applies channel discoverable and status params" do - allow(Settings::Mascot).to receive(:mascot_user_id).and_return(2) - - params = { user_id: Settings::Mascot.mascot_user_id } - filter = described_class.new(params: params) - expected_filters = [ - { "terms" => { "status" => %w[active joining_request] } }, - { "terms" => { "viewable_by" => Settings::Mascot.mascot_user_id } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "ignores params we dont support" do - params = { not_supported: "direct", user_id: 1 } - filter = described_class.new(params: params) - expected_filters = [ - { "terms" => { "status" => %w[active joining_request] } }, - { "terms" => { "viewable_by" => 1 } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "sets default params when not present" do - filter = described_class.new(params: {}) - expect(filter.as_hash["sort"]).to eq("channel_last_message_at" => "desc") - expect(filter.as_hash["size"]).to eq(0) - end - - it "allows default params to be overriden" do - params = { sort_by: "status", sort_direction: "asc", size: 20 } - filter = described_class.new(params: params) - expect(filter.as_hash["sort"]).to eq("status" => "asc") - expect(filter.as_hash["size"]).to eq(20) - end - end -end diff --git a/spec/services/search/query_builders/feed_content_spec.rb b/spec/services/search/query_builders/feed_content_spec.rb deleted file mode 100644 index 57bdf848e..000000000 --- a/spec/services/search/query_builders/feed_content_spec.rb +++ /dev/null @@ -1,130 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::QueryBuilders::FeedContent, type: :service do - describe "::initialize" do - it "sets params" do - filter_params = { foo: "bar" } - filter = described_class.new(params: filter_params) - expect(filter.params).to include(filter_params) - end - - it "builds query body" do - filter = described_class.new(params: {}) - expect(filter.body).not_to be_nil - end - - it "builds query body with html encoder" do - filter = described_class.new(params: {}) - expect(filter.body).to include("highlight" => hash_including("encoder" => "html")) - end - - it "sets published to true" do - filter = described_class.new(params: {}) - expect(filter.params).to include(published: true) - end - end - - describe "#as_hash" do - let(:query_fields) { described_class::QUERY_KEYS[:search_fields] } - let(:expected_query) do - [{ - "simple_query_string" => { - "query" => "test", - "fields" => query_fields, - "lenient" => true, - "analyze_wildcard" => true, - "minimum_should_match" => 2 - } - }] - end - let(:expected_match_phrase) do - [{ - "match_phrase" => { - "search_fields" => { - "query" => "test", - "slop" => 0 - } - } - }] - end - - it "applies QUERY_KEYS from params" do - params = { search_fields: "test" } - filter = described_class.new(params: params) - expect(search_bool_clause(filter)["must"]).to match_array(expected_query) - expect(search_bool_clause(filter)["should"]).to match_array(expected_match_phrase) - expect(search_bool_clause(filter)["minimum_should_match"]).to eq(0) - end - - it "applies TERM_KEYS from params" do - params = { approved: true, tag_names: "beginner", user_id: 777, class_name: "Article" } - filter = described_class.new(params: params) - expected_filters = [ - { "terms" => { "approved" => [true] } }, - { "terms" => { "tags.name" => ["beginner"] } }, - { "terms" => { "user.id" => [777] } }, - { "terms" => { "class_name" => ["Article"] } }, - { "terms" => { "published" => [true] } }, - ] - expect(search_bool_clause(filter)["filter"]).to match_array(expected_filters) - end - - it "applies RANGE_KEYS from params" do - Timecop.freeze(Time.current) do - params = { published_at: { lte: Time.current } } - filter = described_class.new(params: params) - expected_filters = [ - { "range" => { "published_at" => { lte: Time.current } } }, - { "terms" => { "published" => [true] } }, - ] - expect(search_bool_clause(filter)["filter"]).to match_array(expected_filters) - end - end - - it "applies QUERY_KEYS, TERM_KEYS, and RANGE_KEYS from params" do - Timecop.freeze(Time.current) do - params = { search_fields: "ruby", published_at: { lte: Time.current }, tag_names: "cfp" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { "query" => "ruby", "fields" => query_fields, "lenient" => true, - "analyze_wildcard" => true, "minimum_should_match" => 2 } - }] - expected_filters = [ - { "range" => { "published_at" => { lte: Time.current } } }, - { "terms" => { "tags.name" => ["cfp"] } }, - { "terms" => { "published" => [true] } }, - ] - expect(search_bool_clause(filter)["must"]).to match_array(expected_query) - expect(search_bool_clause(filter)["filter"]).to match_array(expected_filters) - end - end - - it "ignores params we don't support" do - params = { not_supported: "trash", search_fields: "cfp" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "cfp", "fields" => query_fields, "lenient" => true, - "analyze_wildcard" => true, "minimum_should_match" => 2 - } - }] - expect(search_bool_clause(filter)["must"]).to match_array(expected_query) - end - - it "allows default params to be overriden" do - params = { sort_by: "published_at", sort_direction: "asc", size: 20 } - filter = described_class.new(params: params).as_hash - expect(filter["sort"]).to eq("published_at" => "asc") - expect(filter["size"]).to eq(20) - end - - it "correctly sets default sort" do - filter = described_class.new(params: {}).as_hash - expect(filter["sort"]).to eq(described_class::DEFAULT_PARAMS[:sort]) - end - end - - def search_bool_clause(query_builder) - query_builder.as_hash.dig("query", "function_score", "query", "bool") - end -end diff --git a/spec/services/search/query_builders/listing_spec.rb b/spec/services/search/query_builders/listing_spec.rb deleted file mode 100644 index 911d50ec0..000000000 --- a/spec/services/search/query_builders/listing_spec.rb +++ /dev/null @@ -1,114 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::QueryBuilders::Listing, type: :service do - describe "::intialize" do - it "sets params" do - filter_params = { foo: "bar" } - filter = described_class.new(params: filter_params) - expect(filter.params).to include(filter_params) - end - - it "builds query body" do - filter = described_class.new(params: {}) - expect(filter.body).not_to be_nil - end - end - - describe "#as_hash" do - it "applies TERM_KEYS from params" do - params = { category: "cfp", tags: %w[beginner intermediate professional], contact_via_connect: false } - filter = described_class.new(params: params) - exepcted_filters = [ - { "terms" => { "category" => ["cfp"] } }, - { "terms" => { "tags" => %w[beginner intermediate professional] } }, - { "terms" => { "contact_via_connect" => [false] } }, - { "terms" => { "published" => [true] } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(exepcted_filters) - end - - it "applies TERM_KEYS from params with boolean mode" do - params = { category: "cfp", tags: %w[beginner intermediate professional], contact_via_connect: false, - tag_boolean_mode: "all" } - filter = described_class.new(params: params) - expected_filters = [ - { "terms" => { "category" => ["cfp"] } }, - { "terms" => { "tags" => ["beginner"] } }, - { "terms" => { "tags" => ["intermediate"] } }, - { "terms" => { "tags" => ["professional"] } }, - { "terms" => { "contact_via_connect" => [false] } }, - { "terms" => { "published" => [true] } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "applies RANGE_KEYS from params" do - Timecop.freeze(Time.current) do - params = { bumped_at: Time.current, expires_at: 1.day.from_now } - filter = described_class.new(params: params) - expected_filters = [ - { "range" => { "bumped_at" => Time.current } }, - { "range" => { "expires_at" => 1.day.from_now } }, - { "terms" => { "published" => [true] } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - end - - it "applies QUERY_KEYS from params" do - params = { listing_search: "test" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "test*", - "fields" => [:listing_search], - "lenient" => true, - "analyze_wildcard" => true - } - }] - expect(filter.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - end - - it "applies QUERY_KEYS, TERM_KEYS, and RANGE_KEYS from params" do - Timecop.freeze(Time.current) do - params = { listing_search: "test", bumped_at: Time.current, category: "cfp" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { "query" => "test*", "fields" => [:listing_search], "lenient" => true, - "analyze_wildcard" => true } - }] - expected_filters = [ - { "range" => { "bumped_at" => Time.current } }, - { "terms" => { "category" => ["cfp"] } }, - { "terms" => { "published" => [true] } }, - ] - expect(filter.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - end - - it "ignores params we don't support" do - params = { not_supported: "trash", category: "cfp" } - filter = described_class.new(params: params) - expected_filters = [ - { "terms" => { "category" => ["cfp"] } }, - { "terms" => { "published" => [true] } }, - ] - expect(filter.as_hash.dig("query", "bool", "filter")).to match_array(expected_filters) - end - - it "sets default params when not present" do - filter = described_class.new(params: {}).as_hash - expect(filter["sort"]).to eq("bumped_at" => "desc") - expect(filter["size"]).to eq(0) - expect(filter.dig("query", "bool", "filter")).to match_array([{ "terms" => { "published" => [true] } }]) - end - - it "allows default params to be overriden" do - params = { sort_by: "category", sort_direction: "asc", size: 20 } - filter = described_class.new(params: params).as_hash - expect(filter["sort"]).to eq("category" => "asc") - expect(filter["size"]).to eq(20) - end - end -end diff --git a/spec/services/search/query_builders/user_spec.rb b/spec/services/search/query_builders/user_spec.rb deleted file mode 100644 index 363aa907e..000000000 --- a/spec/services/search/query_builders/user_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::QueryBuilders::User, type: :service do - describe "::intialize" do - it "sets params" do - filter_params = { foo: "bar" } - filter = described_class.new(params: filter_params) - expect(filter.params).to include(filter_params) - end - - it "builds query body" do - filter = described_class.new(params: {}) - expect(filter.body).not_to be_nil - end - end - - describe "#as_hash" do - it "applies QUERY_KEYS from params" do - params = { search_fields: "test" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "test*", "fields" => [:search_fields], "lenient" => true, "analyze_wildcard" => true - } - }] - expect(filter.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - end - - it "applies EXCLUDED_TERM_KEYS by default" do - filter = described_class.new(params: {}) - expected_filters = [ - { "terms" => { "roles" => %w[suspended banned] } }, - ] - expect(filter.as_hash.dig("query", "bool", "must_not")).to match_array(expected_filters) - end - - it "applies EXCLUDED_TERM_KEYS and QUERY_KEYS" do - params = { search_fields: "test" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "test*", "fields" => [:search_fields], "lenient" => true, "analyze_wildcard" => true - } - }] - expected_filters = [ - { "terms" => { "roles" => %w[suspended banned] } }, - ] - expect(filter.as_hash.dig("query", "bool", "must_not")).to match_array(expected_filters) - expect(filter.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - end - - it "ignores params we don't support" do - params = { not_supported: "trash", search_fields: "cfp" } - filter = described_class.new(params: params) - expected_query = [{ - "simple_query_string" => { - "query" => "cfp*", "fields" => [:search_fields], "lenient" => true, "analyze_wildcard" => true - } - }] - expect(filter.as_hash.dig("query", "bool", "must")).to match_array(expected_query) - end - - it "allows default params to be overriden" do - params = { sort_by: "name", sort_direction: "asc", size: 20 } - filter = described_class.new(params: params).as_hash - expect(filter["sort"]).to eq("name" => "asc") - expect(filter["size"]).to eq(20) - end - end -end diff --git a/spec/services/search/reading_list_spec.rb b/spec/services/search/reading_list_spec.rb deleted file mode 100644 index 2295199a5..000000000 --- a/spec/services/search/reading_list_spec.rb +++ /dev/null @@ -1,115 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::ReadingList, type: :service do - describe "::search_documents", elasticsearch: "FeedContent" do - let(:user) { create(:user) } - let(:article0) { create(:article) } - let(:article1) { create(:article) } - let(:article2) { create(:article) } - let(:reaction1) { create(:reaction, user: user, category: "readinglist", reactable: article1) } - let(:reaction2) { create(:reaction, user: user, category: "readinglist", reactable: article2) } - let(:reaction3) { create(:reaction, user: user, category: "readinglist", reactable: article0) } - let(:query_params) { {} } - - before do - reaction1 - reaction2 - end - - def index_documents(docs) - index_documents_for_search_class(Array.wrap(docs), Search::FeedContent) - end - - it "returns fields necessary for the view" do - view_keys = %w[id reactable user_id] - reactable_keys = %w[title path readable_publish_date_string tags user] - user_keys = %w[username name profile_image_90] - index_documents(article1) - - result = described_class.search_documents(params: {}, user: user) - reaction_doc = result["reactions"].first - expect(result["total"]).to be_present - expect(reaction_doc.keys).to include(*view_keys) - expect(reaction_doc["reactable"].keys).to include(*reactable_keys) - expect(reaction_doc.dig("reactable", "user").keys).to include(*user_keys) - end - - context "with a query" do - it "searches by search_fields" do - allow(article1).to receive(:title).and_return("ruby") - allow(article2).to receive(:body_text).and_return("Ruby Tuesday") - index_documents([article1, article2]) - query_params[:search_fields] = "ruby" - - reaction_docs = described_class.search_documents(params: query_params, user: user)["reactions"] - expect(reaction_docs.count).to eq(2) - doc_ids = reaction_docs.map { |t| t["id"] } - expect(doc_ids).to include(reaction1.id, reaction2.id) - end - end - - context "with a filter term" do - let(:tag_one) { create(:tag) } - let(:tag_two) { create(:tag) } - - it "filters by tag names" do - article1.tags << tag_one - article2.tags << tag_two - index_documents([article1, article2]) - query_params[:tag_names] = [tag_one.name] - - reaction_docs = described_class.search_documents(params: query_params, user: user)["reactions"] - expect(reaction_docs.count).to eq(1) - doc_ids = reaction_docs.map { |t| t["id"] } - expect(doc_ids).to include(reaction1.id) - expect(doc_ids).not_to include(reaction2.id) - end - - it "filters by multiple tag names when tag_boolean_mode is set to all" do - article1.tags << tag_one - article2.tags << tag_two - article2.tags << tag_one - index_documents([article1, article2]) - query_params[:tag_names] = [tag_one.name, tag_two.name] - query_params[:tag_boolean_mode] = "all" - - reaction_docs = described_class.search_documents(params: query_params, user: user)["reactions"] - expect(reaction_docs.count).to eq(1) - doc_ids = reaction_docs.map { |t| t["id"] } - expect(doc_ids).to include(reaction2.id) - expect(doc_ids).not_to include(reaction1.id) - end - - it "filters by status" do - reaction1.update(status: "invalid") - index_documents([article1, article2]) - query_params[:status] = %w[valid confirmed] - - reaction_docs = described_class.search_documents(params: query_params, user: user)["reactions"] - expect(reaction_docs.count).to eq(1) - doc_ids = reaction_docs.map { |t| t["id"] } - expect(doc_ids).to include(reaction2.id) - end - - it "only returns readinglist reactions" do - reaction1.update(category: "like") - index_documents([article1, article2]) - - reaction_docs = described_class.search_documents(params: query_params, user: user)["reactions"] - expect(reaction_docs.count).to eq(1) - doc_ids = reaction_docs.map { |t| t["id"] } - expect(doc_ids).to include(reaction2.id) - expect(doc_ids).not_to include(reaction1.id) - end - end - - it "sorts by reaction ID DESC" do - reaction3 - index_documents([article0, article1, article2]) - - reaction_docs = described_class.search_documents(params: query_params, user: user)["reactions"] - doc_ids = reaction_docs.map { |t| t["id"] } - expect(doc_ids).to eq([reaction3.id, reaction2.id, reaction1.id]) - end - end -end diff --git a/spec/services/search/tag_spec.rb b/spec/services/search/tag_spec.rb deleted file mode 100644 index 753b98424..000000000 --- a/spec/services/search/tag_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::Tag, type: :service do - describe "::search_documents", elasticsearch: "Tag" do - let(:tag_doc_1) { { "name" => "tag1" } } - let(:tag_doc_2) { { "name" => "tag2" } } - let(:mock_search_response) do - { - "hits" => { - "hits" => [ - { "_source" => tag_doc_1 }, - { "_source" => tag_doc_2 }, - ] - } - } - end - - it "searches with name:tag" do - tag = create(:tag, :search_indexed, name: "tag1") - - described_class.refresh_index - tag_docs = described_class.search_documents("name:#{tag.name}") - expect(tag_docs.count).to eq(1) - expect(tag_docs).to match([ - a_hash_including("name" => tag.name), - ]) - end - - it "analyzes wildcards" do - tag1 = create(:tag, :search_indexed, name: "tag1") - tag2 = create(:tag, :search_indexed, name: "tag2") - tag3 = create(:tag, :search_indexed, name: "3tag") - - described_class.refresh_index - - tag_docs = described_class.search_documents("name:tag*") - expect(tag_docs).to match([ - a_hash_including("name" => tag1.name), - a_hash_including("name" => tag2.name), - ]) - expect(tag_docs).not_to match(a_hash_including("name" => tag3.name)) - end - - it "parses tag document hits from search response" do - allow(Search::Client).to receive(:search) { mock_search_response } - tag_docs = described_class.search_documents("query") - expect(tag_docs.count).to eq(2) - expect(tag_docs).to include(tag_doc_1, tag_doc_2) - end - - it "does not allow leading wildcards" do - expect { described_class.search_documents("name:*tag") }.to raise_error(Search::Errors::Transport::BadRequest) - end - end -end diff --git a/spec/services/search/user_spec.rb b/spec/services/search/user_spec.rb deleted file mode 100644 index 731142747..000000000 --- a/spec/services/search/user_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::User, type: :service do - it "defines INDEX_NAME, INDEX_ALIAS, and MAPPINGS", :aggregate_failures do - expect(described_class::INDEX_NAME).not_to be_nil - expect(described_class::INDEX_ALIAS).not_to be_nil - expect(described_class::MAPPINGS).not_to be_nil - end - - describe "::search_documents", elasticsearch: "User" do - let(:user1) { create(:user) } - let(:user2) { create(:user) } - - it "parses user document hits from search response" do - mock_search_response = { "hits" => { "hits" => {} } } - allow(described_class).to receive(:search) { mock_search_response } - described_class.search_documents(params: {}) - expect(described_class).to have_received(:search).with(body: a_kind_of(Hash)) - end - - context "with a query" do - it "searches by search_fields" do - allow(user1).to receive(:available_for).and_return("ruby") - allow(user2).to receive(:employer_name).and_return("Ruby Tuesday") - index_documents([user1, user2]) - query_params = { size: 5, search_fields: "ruby" } - - user_docs = described_class.search_documents(params: query_params) - expect(user_docs.count).to eq(2) - doc_ids = user_docs.map { |t| t["id"] } - expect(doc_ids).to include(user1.id, user2.id) - end - - it "searches by a username" do - allow(user1).to receive(:username).and_return("kyloren") - index_documents([user1]) - query_params = { size: 5, search_fields: "kyloren" } - - user_docs = described_class.search_documents(params: query_params) - expect(user_docs.count).to eq(1) - doc_ids = user_docs.map { |t| t["id"] } - expect(doc_ids).to include(user1.id) - end - end - - context "with a filter" do - it "searches by excluding roles" do - user1.add_role(:admin) - user2.add_role(:suspended) - index_documents([user1, user2]) - query_params = { size: 5, exclude_roles: %w[suspended banned] } - - user_docs = described_class.search_documents(params: query_params) - expect(user_docs.count).to eq(1) - doc_ids = user_docs.map { |t| t["id"] } - expect(doc_ids).to match_array([user1.id]) - end - end - end - - describe "::search_usernames", elasticsearch: "User" do - let(:user1) { create(:user, username: "star_wars_is_the_best") } - let(:user2) { create(:user, username: "star_trek_is_the_best") } - - before do - index_documents([user1, user2]) - end - - it "searches with username" do - usernames = described_class.search_usernames(user1.username) - expect(usernames.count).to eq(1) - expect(usernames.map { |u| u["username"] }).to match([user1.username]) - end - - it "analyzes wildcards" do - user3 = create(:user, username: "does_not_start_with_a_star") - index_documents([user3]) - - usernames = described_class.search_usernames("star*") - expect(usernames.map { |u| u["username"] }).to match( - [user1.username, user2.username], - ) - expect(usernames.map { |u| u["username"] }).not_to include(user3.username) - end - - it "does not allow leading wildcards" do - expect { described_class.search_usernames("*star") }.to raise_error(Search::Errors::Transport::BadRequest) - end - - it "limits the number of results to the value of MAX_RESULTS" do - max_results = 1 - stub_const("Search::Postgres::Username::MAX_RESULTS", max_results) - - usernames = described_class.search_usernames("star*") - expect(usernames.size).to eq(max_results) - expect([user1.username, user2.username]).to include(usernames.first["username"]) - end - end -end diff --git a/spec/services/users/delete_articles_spec.rb b/spec/services/users/delete_articles_spec.rb index dc2ff7156..60d4584f9 100644 --- a/spec/services/users/delete_articles_spec.rb +++ b/spec/services/users/delete_articles_spec.rb @@ -14,14 +14,6 @@ RSpec.describe Users::DeleteArticles, type: :service do expect(Article.find(article3.id)).to be_present end - it "removes article from Elasticsearch" do - sidekiq_perform_enqueued_jobs - expect(article.elasticsearch_doc).not_to be_nil - - sidekiq_perform_enqueued_jobs { described_class.call(user) } - expect { article.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - end - context "with comments" do before do allow(EdgeCache::BustComment).to receive(:call) @@ -42,18 +34,5 @@ RSpec.describe Users::DeleteArticles, type: :service do expect(EdgeCache::BustUser).to have_received(:call).with(user2).at_least(:once) expect(EdgeCache::BustArticle).to have_received(:call).with(article) end - - it "removes comments from Elasticsearch", :aggregate_failures do - sidekiq_perform_enqueued_jobs - comments = article.comments - comments.each { |comment| expect(comment.elasticsearch_doc).not_to be_nil } - sidekiq_perform_enqueued_jobs(only: Search::RemoveFromIndexWorker) do - described_class.call(user) - end - - comments.each do |comment| - expect { comment.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - end - end end end diff --git a/spec/services/users/delete_comments_spec.rb b/spec/services/users/delete_comments_spec.rb index 9d1090f8b..438b39618 100644 --- a/spec/services/users/delete_comments_spec.rb +++ b/spec/services/users/delete_comments_spec.rb @@ -19,14 +19,6 @@ RSpec.describe Users::DeleteComments, type: :service do expect(Comment.where(user_id: user.id).any?).to be false end - it "removes comments from Elasticsearch" do - comment - sidekiq_perform_enqueued_jobs - expect(comment.elasticsearch_doc).not_to be_nil - sidekiq_perform_enqueued_jobs { described_class.call(user) } - expect { comment.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - end - it "busts cache" do described_class.call(user) expect(EdgeCache::BustComment).to have_received(:call).with(article).at_least(:once) diff --git a/spec/services/users/delete_spec.rb b/spec/services/users/delete_spec.rb index 10e093cec..2f2f2a9e5 100644 --- a/spec/services/users/delete_spec.rb +++ b/spec/services/users/delete_spec.rb @@ -60,25 +60,6 @@ RSpec.describe Users::Delete, type: :service do expect(audit_log.reload.user_id).to be(nil) end - it "removes user from Elasticsearch" do - sidekiq_perform_enqueued_jobs { user } - expect(user.elasticsearch_doc).not_to be_nil - sidekiq_perform_enqueued_jobs do - described_class.call(user) - end - expect { user.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - end - - it "removes articles from Elasticsearch" do - article = create(:article, user: user) - sidekiq_perform_enqueued_jobs - expect(article.elasticsearch_doc).not_to be_nil - sidekiq_perform_enqueued_jobs do - described_class.call(user) - end - expect { article.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - end - it "deletes field tests memberships" do create(:field_test_membership, participant_id: user.id) diff --git a/spec/support/elasticsearch_helpers.rb b/spec/support/elasticsearch_helpers.rb deleted file mode 100644 index 128eb05b2..000000000 --- a/spec/support/elasticsearch_helpers.rb +++ /dev/null @@ -1,19 +0,0 @@ -# Only included in specs with elasticsearch: true -module ElasticsearchHelpers - def index_documents(resources) - index_documents_for_search_class(Array.wrap(resources), described_class) - end - - def index_documents_for_search_class(records, search_class) - records.each(&:index_to_elasticsearch_inline) - search_class.refresh_index - end - - def clear_elasticsearch_data(search_class) - search_class.refresh_index - Search::Client.delete_by_query( - index: search_class::INDEX_ALIAS, body: { query: { match_all: {} } }, - ) - search_class.refresh_index - end -end diff --git a/spec/support/initializers/vcr.rb b/spec/support/initializers/vcr.rb index 164bbae94..1802e6fe7 100644 --- a/spec/support/initializers/vcr.rb +++ b/spec/support/initializers/vcr.rb @@ -13,8 +13,7 @@ VCR.configure do |config| "github.com/mozilla/geckodriver/releases", "selenium-release.storage.googleapis.com", "developer.microsoft.com/en-us/microsoft-edge/tools/webdriver", - "api.knapsackpro.com", "localhost", "127.0.0.1", "0.0.0.0", - "elasticsearch" + "api.knapsackpro.com", "localhost", "127.0.0.1", "0.0.0.0" ) # Removes all private data (Basic Auth, Set-Cookie headers...) diff --git a/spec/system/articles/user_visits_articles_by_tag_spec.rb b/spec/system/articles/user_visits_articles_by_tag_spec.rb index 9ffd0d4ab..1547f2bc4 100644 --- a/spec/system/articles/user_visits_articles_by_tag_spec.rb +++ b/spec/system/articles/user_visits_articles_by_tag_spec.rb @@ -18,16 +18,16 @@ RSpec.describe "User visits articles by tag", type: :system do visit "/t/javascript" end - it "shows the header", js: true, stub_elasticsearch: true do + it "shows the header", js: true do within("h1") { expect(page).to have_text("javascript") } end - it "shows the follow button", js: true, stub_elasticsearch: true do + it "shows the follow button", js: true do within("header.tag-header") { expect(page).to have_button("Follow") } end # Regression test for https://github.com/forem/forem/pull/12724 - it "does not display a comment count of 0", js: true, stub_elasticsearch: true do + it "does not display a comment count of 0", js: true do expect(page).to have_text("Add Comment") expect(page).not_to have_text("0 comments") end @@ -62,7 +62,7 @@ RSpec.describe "User visits articles by tag", type: :system do visit "/t/functional" end - it "shows the following button", js: true, stub_elasticsearch: true do + it "shows the following button", js: true do wait_for_javascript within("header.tag-header") { expect(page).to have_button("Following") } diff --git a/spec/system/articles/user_visits_articles_by_timeframe_spec.rb b/spec/system/articles/user_visits_articles_by_timeframe_spec.rb index e0f84c18b..944ec8926 100644 --- a/spec/system/articles/user_visits_articles_by_timeframe_spec.rb +++ b/spec/system/articles/user_visits_articles_by_timeframe_spec.rb @@ -105,7 +105,7 @@ RSpec.describe "User visits articles by timeframe", type: :system do end end - context "when user has logged in", js: true, stub_elasticsearch: true do + context "when user has logged in", js: true do let(:user) { create(:user) } before do diff --git a/spec/system/comments/user_delete_a_comment_spec.rb b/spec/system/comments/user_delete_a_comment_spec.rb index 1758e88bd..0b3feb23a 100644 --- a/spec/system/comments/user_delete_a_comment_spec.rb +++ b/spec/system/comments/user_delete_a_comment_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Deleting Comment", type: :system, js: true, elasticsearch: "FeedContent" do +RSpec.describe "Deleting Comment", type: :system, js: true do let(:user) { create(:user) } let(:raw_comment) { Faker::Lorem.paragraph } let(:article) do diff --git a/spec/system/homepage/user_visits_homepage_articles_spec.rb b/spec/system/homepage/user_visits_homepage_articles_spec.rb index b701d2526..a88967a5c 100644 --- a/spec/system/homepage/user_visits_homepage_articles_spec.rb +++ b/spec/system/homepage/user_visits_homepage_articles_spec.rb @@ -20,7 +20,7 @@ RSpec.describe "User visits a homepage", type: :system do end # Regression test for https://github.com/forem/forem/pull/12724 - it "does not display a comment count of 0", js: true, stub_elasticsearch: true do + it "does not display a comment count of 0", js: true do expect(page).to have_text("Add Comment") expect(page).not_to have_text("0 comments") article.update_column(:comments_count, 50) @@ -28,7 +28,7 @@ RSpec.describe "User visits a homepage", type: :system do expect(page).to have_text(/50\s*comments/) end - it "shows the main article readable date and time", js: true, stub_elasticsearch: true do + it "shows the main article readable date and time", js: true do expect(page).to have_selector(".crayons-story--featured time", text: published_date) selector = ".crayons-story--featured time[datetime='#{timestamp}']" expect(page).to have_selector(selector) @@ -48,7 +48,7 @@ RSpec.describe "User visits a homepage", type: :system do expect(page).to have_text(article2.title) end - it "shows all articles' dates and times", js: true, stub_elasticsearch: true do + it "shows all articles' dates and times", js: true do expect(page).to have_selector(".crayons-story time", text: published_date, count: 2) selector = ".crayons-story time[datetime='#{timestamp}']" expect(page).to have_selector(selector, count: 2) diff --git a/spec/system/homepage/user_visits_homepage_spec.rb b/spec/system/homepage/user_visits_homepage_spec.rb index d7fa07416..6832f5cf7 100644 --- a/spec/system/homepage/user_visits_homepage_spec.rb +++ b/spec/system/homepage/user_visits_homepage_spec.rb @@ -105,7 +105,7 @@ RSpec.describe "User visits a homepage", type: :system do end end - it "shows followed tags ordered by weight and name", js: true, elasticsearch: "FeedContent" do + it "shows followed tags ordered by weight and name", js: true do # Need to ensure the user data is loaded before doing any checks find("body")["data-user"] diff --git a/spec/system/homepage/user_visits_homepage_with_announcement_spec.rb b/spec/system/homepage/user_visits_homepage_with_announcement_spec.rb index a9cf6df17..d7ac189a9 100644 --- a/spec/system/homepage/user_visits_homepage_with_announcement_spec.rb +++ b/spec/system/homepage/user_visits_homepage_with_announcement_spec.rb @@ -13,7 +13,7 @@ def expect_no_broadcast_data(page) expect(page).not_to have_text("Hello, World!") end -RSpec.describe "User visits a homepage", stub_elasticsearch: true, type: :system do +RSpec.describe "User visits a homepage", type: :system do context "when user hasn't logged in" do context "with an active announcement" do before do diff --git a/spec/system/organization/user_views_an_organization_spec.rb b/spec/system/organization/user_views_an_organization_spec.rb index cffec23a0..0f6f3dc48 100644 --- a/spec/system/organization/user_views_an_organization_spec.rb +++ b/spec/system/organization/user_views_an_organization_spec.rb @@ -12,7 +12,7 @@ RSpec.describe "Organization index", type: :system do context "when 2 articles" do before { visit "/#{organization.slug}" } - it "shows the header", js: true, stub_elasticsearch: true do + it "shows the header", js: true do within("h1") { expect(page).to have_content(organization.name) } within("div.profile-header__actions") do expect(page).to have_button("Follow") @@ -36,7 +36,7 @@ RSpec.describe "Organization index", type: :system do end context "when more articles" do - it "visits ok", js: true, stub_elasticsearch: true do + it "visits ok", js: true do create_list(:article, 3, organization: organization) visit "/#{organization.slug}" end @@ -51,7 +51,7 @@ RSpec.describe "Organization index", type: :system do user.follows.create(followable: organization) end - it "shows the correct button", js: true, stub_elasticsearch: true do + it "shows the correct button", js: true do visit "/#{organization.slug}" within(".profile-header__actions") do diff --git a/spec/system/search/display_articles_search_spec.rb b/spec/system/search/display_articles_search_spec.rb index 9570e6afb..f8bfdc19c 100644 --- a/spec/system/search/display_articles_search_spec.rb +++ b/spec/system/search/display_articles_search_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Display articles search spec", type: :system, js: true, elasticsearch: "FeedContent" do +RSpec.describe "Display articles search spec", type: :system, js: true do it "returns correct results for a search" do found_article_one = create(:article) found_article_one.update_columns(cached_tag_list: "ruby") diff --git a/spec/system/search/display_comments_search_spec.rb b/spec/system/search/display_comments_search_spec.rb index 72ff64037..37e850aea 100644 --- a/spec/system/search/display_comments_search_spec.rb +++ b/spec/system/search/display_comments_search_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "Display articles search spec", type: :system, js: true, elasticsearch: "FeedContent" do +RSpec.describe "Display articles search spec", type: :system, js: true do it "returns correct results for a search" do query = "XSS" create(:comment, body_markdown: query) diff --git a/spec/system/search/display_users_search_spec.rb b/spec/system/search/display_users_search_spec.rb index e531a1b0f..e113e1f54 100644 --- a/spec/system/search/display_users_search_spec.rb +++ b/spec/system/search/display_users_search_spec.rb @@ -1,14 +1,16 @@ require "rails_helper" -RSpec.describe "Display users search spec", type: :system, js: true, elasticsearch: "User" do +RSpec.describe "Display users search spec", type: :system, js: true do let(:current_user) { create(:user, username: "ironman", name: "Iron Man") } let(:found_user) { create(:user, username: "janedoe", name: "Jane Doe") } let(:found_two_user) { create(:user, username: "doejane", name: "Doe Jane") } let(:not_found_user) { create(:user, username: "batman", name: "Batman") } it "returns correct results for name search" do - users = [current_user, found_user, found_two_user, not_found_user] - index_documents_for_search_class(users, Search::User) + current_user + found_user + found_two_user + not_found_user visit "/search?q=jane&filters=class_name:User" expect(page).to have_content(found_user.name) @@ -18,8 +20,9 @@ RSpec.describe "Display users search spec", type: :system, js: true, elasticsear end it "returns all expected user fields" do + current_user + found_user sign_in current_user - index_documents_for_search_class([found_user], Search::User) visit "/search?q=jane&filters=class_name:User" expect(page).to have_content(found_user.name) diff --git a/spec/system/search/user_searches_users_spec.rb b/spec/system/search/user_searches_users_spec.rb index 06f4f459b..e6f6ef058 100644 --- a/spec/system/search/user_searches_users_spec.rb +++ b/spec/system/search/user_searches_users_spec.rb @@ -10,11 +10,10 @@ RSpec.describe "User searches users", type: :system do sign_in current_user current_user.follow(followed_user) follow_back_user.follow(current_user) - [current_user, followed_user, not_followed_user, follow_back_user].each(&:index_to_elasticsearch_inline) - Search::User.refresh_index + not_followed_user end - it "shows the correct follow buttons", js: true, elasticsearch: "User" do + it "shows the correct follow buttons", js: true do visit "/search?q=&filters=class_name:User" expect(JSON.parse(find_button("Edit profile")["data-info"])["id"]).to eq(current_user.id) diff --git a/spec/system/user/view_user_comments_spec.rb b/spec/system/user/view_user_comments_spec.rb index b64205868..c06710b68 100644 --- a/spec/system/user/view_user_comments_spec.rb +++ b/spec/system/user/view_user_comments_spec.rb @@ -15,7 +15,7 @@ RSpec.describe "User comments", type: :system do end end - it "shows user's comments", js: true, stub_elasticsearch: true do + it "shows user's comments", js: true do within("#substories div.profile-comment-card") do expect(page).to have_content("All 2 comments") expect(page).to have_link(nil, href: comment.path) @@ -25,7 +25,7 @@ RSpec.describe "User comments", type: :system do end context "when user has too many comments" do - it "show user's last comments ", js: true, stub_elasticsearch: true do + it "show user's last comments ", js: true do stub_const("CommentsHelper::MAX_COMMENTS_TO_RENDER", 1) visit "/user3000/comments" within("#substories div.profile-comment-card") do diff --git a/spec/system/user/view_user_index_spec.rb b/spec/system/user/view_user_index_spec.rb index cc7b94aff..fe3341bca 100644 --- a/spec/system/user/view_user_index_spec.rb +++ b/spec/system/user/view_user_index_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe "User index", type: :system, stub_elasticsearch: true do +RSpec.describe "User index", type: :system do let!(:user) { create(:user) } let!(:article) { create(:article, user: user) } let!(:other_article) { create(:article) } diff --git a/spec/system/user_uses_the_editor_spec.rb b/spec/system/user_uses_the_editor_spec.rb index d0858f940..258ba89fe 100644 --- a/spec/system/user_uses_the_editor_spec.rb +++ b/spec/system/user_uses_the_editor_spec.rb @@ -93,7 +93,7 @@ RSpec.describe "Using the editor", type: :system do end end - describe "using v2 editor", js: true, stub_elasticsearch: true do + describe "using v2 editor", js: true do before { user.update(editor_version: "v2") } it "fill out form with rich content and click publish" do diff --git a/spec/workers/articles/score_calc_worker_spec.rb b/spec/workers/articles/score_calc_worker_spec.rb index e26edae93..7e72e1256 100644 --- a/spec/workers/articles/score_calc_worker_spec.rb +++ b/spec/workers/articles/score_calc_worker_spec.rb @@ -31,13 +31,6 @@ RSpec.describe Articles::ScoreCalcWorker, type: :worker do expect(article.hotness_score).to be(373) expect(article.spaminess_rating).to be(2) end - - it "indexes the article to Elasticsearch" do - allow(Article).to receive(:find_by).and_return(article) - allow(article).to receive(:index_to_elasticsearch_inline) - worker.perform(article.id) - expect(article).to have_received(:index_to_elasticsearch_inline).once - end end context "without article" do diff --git a/spec/workers/chat_channels/indexes_memberships_worker_spec.rb b/spec/workers/chat_channels/indexes_memberships_worker_spec.rb deleted file mode 100644 index e7d146877..000000000 --- a/spec/workers/chat_channels/indexes_memberships_worker_spec.rb +++ /dev/null @@ -1,18 +0,0 @@ -require "rails_helper" - -RSpec.describe ChatChannels::IndexesMembershipsWorker, type: :worker do - describe "#perform" do - let(:chat_channel) { create(:chat_channel) } - let(:chat_channel_membership) { create(:chat_channel_membership) } - - it "indexes chat channel memberships" do - allow(ChatChannel).to receive(:find).and_return(chat_channel) - allow(chat_channel).to receive(:chat_channel_memberships).and_return([chat_channel_membership]) - allow(chat_channel_membership).to receive(:index_to_elasticsearch) - - described_class.new.perform(chat_channel.id) - - expect(chat_channel_membership).to have_received(:index_to_elasticsearch) - end - end -end diff --git a/spec/workers/metrics/record_data_counts_worker_spec.rb b/spec/workers/metrics/record_data_counts_worker_spec.rb index 833cfa106..9d60d0523 100644 --- a/spec/workers/metrics/record_data_counts_worker_spec.rb +++ b/spec/workers/metrics/record_data_counts_worker_spec.rb @@ -28,14 +28,5 @@ RSpec.describe Metrics::RecordDataCountsWorker, type: :worker do ForemStatsClient, ).to have_received(:gauge).with("postgres.db_table_size", 0, tags: Array).at_least(1) end - - it "logs index counts in Datadog" do - allow(ForemStatsClient).to receive(:gauge) - described_class.new.perform - - expect( - ForemStatsClient, - ).to have_received(:gauge).with("elasticsearch.document_count", Integer, tags: Array).at_least(1) - end end end diff --git a/spec/workers/rating_votes/assign_rating_worker_spec.rb b/spec/workers/rating_votes/assign_rating_worker_spec.rb index fbb5d37e7..ff84e4694 100644 --- a/spec/workers/rating_votes/assign_rating_worker_spec.rb +++ b/spec/workers/rating_votes/assign_rating_worker_spec.rb @@ -37,12 +37,5 @@ RSpec.describe RatingVotes::AssignRatingWorker, type: :worker do expect(article.reload.experience_level_rating).to eq(2.5) expect(article.reload.experience_level_rating_distribution).to eq(3.0) end - - it "updates article in Elasticsearch" do - create(:rating_vote, article_id: article.id, user_id: user.id, rating: 4.0) - create(:rating_vote, article_id: article.id, user_id: second_user.id, rating: 1.0, context: "comment") - worker.perform(article.id) - expect(article.elasticsearch_doc.dig("_source", "experience_level_rating")).to eq(2.5) - end end end diff --git a/spec/workers/search/bulk_index_worker_spec.rb b/spec/workers/search/bulk_index_worker_spec.rb deleted file mode 100644 index 3db204bb5..000000000 --- a/spec/workers/search/bulk_index_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::BulkIndexWorker, type: :worker do - let(:worker) { subject } - let(:article) { create(:article) } - - include_examples "#enqueues_on_correct_queue", "high_priority", ["Tag", 1] - - it "indexes documents for a set of given ids and object class", elasticsearch: "Tag" do - tags = [create(:tag), create(:tag), create(:tag)] - Sidekiq::Worker.clear_all - - tags.each do |tag| - expect { tag.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - end - worker.perform("Tag", tags.map(&:id)) - - tags.each do |tag| - expect(tag.elasticsearch_doc.dig("_source", "id")).to eql(tag.id) - end - end -end diff --git a/spec/workers/search/index_worker_spec.rb b/spec/workers/search/index_worker_spec.rb deleted file mode 100644 index ff8688d7a..000000000 --- a/spec/workers/search/index_worker_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::IndexWorker, type: :worker, elasticsearch: "Tag" do - let(:worker) { subject } - - include_examples "#enqueues_on_correct_queue", "high_priority", ["Tag", 1] - - it "indexes document" do - tag = FactoryBot.create(:tag) - expect { tag.elasticsearch_doc }.to raise_error(Search::Errors::Transport::NotFound) - worker.perform(tag.class.name, tag.id) - - expect(tag.elasticsearch_doc.dig("_source", "id")).to eql(tag.id) - end - - it "does not raise an error if record is not found" do - expect { worker.perform("Tag", 1234) }.not_to raise_error - end -end diff --git a/spec/workers/search/remove_from_index_worker_spec.rb b/spec/workers/search/remove_from_index_worker_spec.rb deleted file mode 100644 index 6b60e0545..000000000 --- a/spec/workers/search/remove_from_index_worker_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -require "rails_helper" - -RSpec.describe Search::RemoveFromIndexWorker, type: :worker do - let(:worker) { subject } - let(:search_class) { Search::Tag } - - include_examples "#enqueues_on_correct_queue", "medium_priority", ["SearchClass", 1] - - it "deletes document for given search class" do - allow(search_class).to receive(:delete_document) - described_class.new.perform(search_class.to_s, 1) - expect(search_class).to have_received(:delete_document).with(1) - end - - context "when document is not found" do - it "does not raise error" do - expect { described_class.new.perform(search_class.to_s, 1) }.not_to raise_error - end - end -end diff --git a/vendor/cache/elasticsearch-7.12.0.gem b/vendor/cache/elasticsearch-7.12.0.gem deleted file mode 100644 index 0e23fe4f1..000000000 Binary files a/vendor/cache/elasticsearch-7.12.0.gem and /dev/null differ diff --git a/vendor/cache/elasticsearch-api-7.12.0.gem b/vendor/cache/elasticsearch-api-7.12.0.gem deleted file mode 100644 index e26a933c5..000000000 Binary files a/vendor/cache/elasticsearch-api-7.12.0.gem and /dev/null differ diff --git a/vendor/cache/elasticsearch-transport-7.12.0.gem b/vendor/cache/elasticsearch-transport-7.12.0.gem deleted file mode 100644 index 0c2a31813..000000000 Binary files a/vendor/cache/elasticsearch-transport-7.12.0.gem and /dev/null differ