discourse/spec/services
Joffrey JAFFEUX 7ecb945ec4
FEATURE: Add full-text search for chat messages (#34704)
## Overview

This PR introduces comprehensive search functionality for chat messages,
enabling users to search through their chat history both globally across
all accessible channels and within specific channels.

### Search Capabilities

**All-Channel Search**: When no channel is specified, users can search
across all channels they have access to. The search respects channel
permissions through `ChannelFetcher.all_secured_channel_ids`, ensuring
users only see results from channels they can view.

**Per-Channel Search**: Users can scope their search to a specific
channel by providing a `channel_id` parameter, useful for finding
messages within a particular conversation context.

**Search Features**:
- Full-text search using PostgreSQL's tsvector/tsquery
- Advanced filters: `@username` to filter by author, `#channel` to
filter by channel slug
- Sort options: relevance (default) or latest
- Pagination support
- Search data weighted by relevance

## Site Setting: `chat_search_enabled`

This feature is gated behind the `chat_search_enabled` site setting,
which is currently:
- **Default**: `false`
- **Hidden**: `true`
- **Client-accessible**: `true`

### Deployment Strategy

Due to the need for chat messages to be indexed before search becomes
useful, we're implementing a two-phase deployment:

**Phase 1 (Initial Merge)**:
- `chat_search_enabled` remains `false` and hidden
- The `register_search_index` uses default (true) instead of `chat_search_enabled` value
- This allows the reindexing infrastructure to begin indexing existing
chat messages even if we don't show the UI yet

**Wait Period**:
- Wait at least one week after Phase 1 deployment
- `Jobs::ReindexSearch` runs every 2 hours and will progressively index
all chat messages
- This ensures most sites have a significant part of their chat history indexed

**Phase 2 (Follow-up Merge)**:
- Set `chat_search_enabled` default to `true` and unhide it
- Update the `register_search_index` enabled proc uses the default
(true) instead of using the `chat_search_enabled` setting
- Users can now access search with pre-indexed data

**Rationale**: Without this phased approach, users would see the search
UI immediately but receive no results until the reindexing job runs,
creating a confusing experience. By pre-indexing while the UI is hidden,
we ensure search works immediately when enabled.

## New Plugin API: `register_search_index`

This PR introduces a new plugin API that allows plugins to register
custom search indexes that integrate seamlessly with Discourse's search
infrastructure.

### API Signature

```ruby
register_search_index(
  model_class:,              # The ActiveRecord model to index
  search_data_class:,        # The model for storing search data
  index_version:,            # Version number for re-indexing
  search_data:,              # Proc that returns weighted search data
  load_unindexed_record_ids:,# Proc that finds records needing indexing
  enabled:                   # Optional proc to enable/disable (default: -> { true })
)
```

### How It Works

**Integration with SearchIndexer**: When `SearchIndexer.index(obj)` is
called, it checks registered search handlers for the object's type. If a
handler matches, it:
1. Calls the `search_data` proc with the object and an `IndexerHelper`
instance
2. Receives weighted search data (`:a_weight`, `:b_weight`, `:c_weight`,
`:d_weight`)
3. Updates the corresponding search data table with PostgreSQL's
tsvector

**Integration with Jobs::ReindexSearch**: The scheduled job (runs every
2 hours) calls `rebuild_registered_search_handlers`, which:
1. Iterates through all registered search handlers
2. Skips handlers where `enabled` proc returns `false`
3. Calls `load_unindexed_record_ids` to find records needing indexing
4. Indexes up to `limit` records per handler (default: 10,000)

### Chat Implementation Example

```ruby
register_search_index(
  model_class: Chat::Message,
  search_data_class: Chat::MessageSearchData,
  index_version: 1,
  search_data: proc { |message, indexer_helper|
    {
      a_weight: message.message,
      d_weight: indexer_helper.scrub_html(message.cooked)[0..600_000]
    }
  },
  load_unindexed_record_ids: proc { |limit:, index_version:|
    Chat::Message
      .joins("LEFT JOIN chat_message_search_data ON chat_message_id = chat_messages.id")
      .where(
        "chat_message_search_data.locale IS NULL OR 
         chat_message_search_data.locale != ? OR 
         chat_message_search_data.version != ?",
        SiteSetting.default_locale,
        index_version
      )
      .order("chat_messages.id ASC")
      .limit(limit)
      .pluck(:id)
  }
)
```

Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Loïc Guitaut <5648+Flink@users.noreply.github.com>
2025-10-22 11:30:35 +02:00
..
admin_notices DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
discourse_id DEV: Update rubocop-discourse to 3.13 and autofix issues (#35073) 2025-10-06 16:11:01 +02:00
experiments DEV: Provide user input to services using params key 2024-10-25 09:57:59 +02:00
flags DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
notifications FEATURE: Consolidate link notifications (#26567) 2024-04-09 11:53:37 -06:00
problem_check DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
site_setting DEV: Move backfill into SiteSetting::Update service (#32037) 2025-03-28 12:01:56 +08:00
themes FIX: Add delete button to themes grid (#34606) 2025-08-29 10:09:23 +08:00
user FIX: bug when silence user and do nothing to post (#33819) 2025-07-25 10:31:22 +08:00
video_conversion DEV: Have media convert service set s3 output permissions (#35392) 2025-10-15 12:38:25 -06:00
anonymous_shadow_creator_spec.rb UX: Improve naming for anonymous mode settings (#31832) 2025-03-21 04:54:06 +03:00
auto_silence_spec.rb FIX: Moderator notifications when new post auto-silences a user (#35403) 2025-10-15 16:07:56 +08:00
badge_granter_spec.rb FIX: error when trying to un-favorite badge (#32369) 2025-04-22 15:36:48 +08:00
base_bookmarkable_spec.rb FIX: Show deleted bookmark reminders in user bookmarks menu (#25905) 2024-02-29 09:03:49 +10:00
category_hashtag_data_source_spec.rb UX: Show parent category name for category hashtags (#31188) 2025-02-05 12:31:50 +10:00
color_scheme_revisor_spec.rb FEATURE: Ability to set palettes as default on theme (#34480) 2025-08-27 09:09:34 +08:00
destroy_task_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
email_settings_exception_handler_spec.rb FIX: Show the SMTP authentication error for group UI (#27914) 2024-07-16 09:14:17 +10:00
email_settings_validator_spec.rb UX: Use a dropdown for SSL mode for group SMTP (#27932) 2024-07-18 10:33:14 +10:00
email_style_updater_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
external_upload_manager_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
flag_sockpuppets_spec.rb DEV: Fix Lint/BooleanSymbol (#24747) 2023-12-06 13:19:09 +01:00
group_action_logger_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
group_mentions_updater_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
group_message_spec.rb FIX: crawler view with unicode usernames (#27051) 2024-05-16 17:11:24 +02:00
hashtag_autocomplete_service_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
heat_settings_updater_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
inline_uploads_multisite_spec.rb DEV: Fix various rubocop lints (#24749) 2023-12-06 23:25:00 +01:00
inline_uploads_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
locale_normalizer_spec.rb FIX: Show localization for regionless locale if they exist (#33702) 2025-07-21 15:45:14 +08:00
notification_emailer_spec.rb FIX: Avoid sending user emails if @ mentioning a staged user in a topic (#26102) 2024-03-13 11:05:34 +08:00
post_action_notifier_spec.rb DEV: Convert min_trust_to_create_topic to groups (#24740) 2023-12-13 14:50:13 +11:00
post_alerter_spec.rb FEATURE: disable link notification user preference (#35352) 2025-10-14 10:53:05 +02:00
post_bookmarkable_spec.rb DEV: Remove unnecessary rails_helper requiring (#26364) 2024-03-26 11:32:01 +01:00
post_owner_changer_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
problem_check_spec.rb DEV: Allow disabling problem checks programatically (#28440) 2024-08-20 16:42:06 +02:00
push_notification_pusher_spec.rb DEV: Migrate from sprockets to propshaft for assets (#32475) 2025-04-30 08:59:32 +01:00
random_topic_selector_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
search_indexer_spec.rb FEATURE: Add full-text search for chat messages (#34704) 2025-10-22 11:30:35 +02:00
sidebar_site_settings_backfiller_spec.rb DEV: Hand-pick Rails/WhereNot autofixes (#35117) 2025-10-03 13:29:22 +02:00
site_settings_spec.rb DEV: Fix constant redefinition warnings when running specs (#29837) 2024-11-20 15:17:36 +11:00
staff_action_logger_spec.rb UX: show SCSS files in theme admin "extra files" section (#35300) 2025-10-09 16:18:03 -04:00
tag_hashtag_data_source_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
theme_settings_migrations_runner_spec.rb FIX: Make getCategoryIdByName theme migration helper case insensitive (#26878) 2024-05-06 12:42:58 +08:00
themes_spec.rb DEV: Fix constant redefinition warnings when running specs (#29837) 2024-11-20 15:17:36 +11:00
topic_bookmarkable_spec.rb DEV: Remove unnecessary rails_helper requiring (#26364) 2024-03-26 11:32:01 +01:00
topic_status_updater_spec.rb DEV: don't mark messages as unread when they are closed (#35451) 2025-10-16 19:21:32 +02:00
topic_timestamp_changer_spec.rb DEV: Update rubocop-discourse to 3.13 and autofix issues (#35073) 2025-10-06 16:11:01 +02:00
trust_level_granter_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
user_activator_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
user_anonymizer_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
user_authenticator_spec.rb FIX: do not enforce 2fa when an account is created with OAuth (#28625) 2024-08-29 11:19:04 +10:00
user_destroyer_spec.rb FIX: Staff action log logs in default locale when a user deletes themselves (#32503) 2025-04-29 12:32:29 +08:00
user_merger_spec.rb DEV: Merge bookmarks when merging two users (#34567) 2025-08-29 09:25:05 +08:00
user_notification_schedule_processor_spec.rb DEV: Allow fab! without block (#24314) 2023-11-09 16:47:59 -06:00
user_password_expirer_spec.rb DEV: Migrate user passwords data to UserPassword table (#28746) 2024-10-10 09:23:06 +08:00
user_silencer_spec.rb DEV: update syntax tree to latest (#24623) 2023-11-29 16:38:07 +11:00
user_stat_count_updater_spec.rb DEV: Don’t replace Rails logger in specs (#29721) 2024-11-13 08:47:39 +08:00
user_suspender_spec.rb SECURITY: Don't allow suspending staff users via other_user_ids param 2024-07-03 20:49:29 +08:00
user_updater_spec.rb DEV: add shortcut fab!(:variable, :fabricator) to specs (#33577) 2025-07-11 11:16:34 -03:00
username_changer_spec.rb UX: improve quote title alignment, wrapping (#33796) 2025-07-23 16:21:55 -04:00
username_checker_service_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
wildcard_domain_checker_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
wildcard_url_checker_spec.rb DEV: Apply syntax_tree formatting to spec/* 2023-01-09 11:49:28 +00:00
word_watcher_spec.rb FIX: wildcard watched word and regexps (#35217) 2025-10-06 20:49:02 +02:00