• 
      

    Search: Add get_es_client() to ES backend and update SignalProcessor

    Review Request #15053 — Created May 13, 2026 and updated

    Information

    Review Board
    release-8.x

    Reviewers

    Two targeted changes to existing files:
    - ElasticsearchBackend.get_es_client() — adds a new method to the
    Elasticsearch search backend that constructs and returns a raw
    elasticsearch.Elasticsearch client using the URL from the current backend
    configuration. Used by FacetedSearchEngine to run direct ES queries outside
    of Haystack. Raises RuntimeError if the installed elasticsearch package
    is unsupported.
    - SignalProcessor — extends handle_save and handle_delete to call
    FacetCache().invalidate_group(group_id) after each successful index update.
    A _MODEL_TO_GROUP mapping at module level translates the model class name
    to its facet cache group ID, covering ReviewRequest, User, Profile,
    Group, and all comment model types (Comment, GeneralComment,
    FileAttachmentComment, ScreenshotComment). Unrecognised model types are
    silently ignored. Cache invalidation is skipped when the index update itself
    raises an exception.

    Group.users M2M changes are also handled: when users are added to or removed
    from a group the affected user records are re-indexed and the user group's
    cache is invalidated.

    Ran full test suite.
    Verified that get_es_client returns a correctly configured Elasticsearch
    client and raises RuntimeError for unsupported package versions. Verified
    that SignalProcessor invalidates the correct facet cache group after save and
    delete for all tracked model types, silently ignores unrecognised models, and
    does not propagate indexing exceptions. Confirmed existing backend and signal
    processor behaviour is unchanged.

    Summary ID
    Search: Add get_es_client() to ES backend and update SignalProcessor
    171b5c9ecbfbe28250b16ea3444851fafde6a582
    Updated styling to match standard
    66c1ba16c101b1723e3803d9784004e01ab8b6c3
    Fixing issues.
    7db8aad14ac062d99258011fcaf23732b5e14caa
    Description From Last Updated

    continuation line over-indented for visual indent Column: 36 Error code: E127

    reviewbot reviewbot

    line too long (82 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (81 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    continuation line over-indented for visual indent Column: 36 Error code: E127

    reviewbot reviewbot

    line too long (82 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    'reviewboard.search.testing.search_enabled' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    'reviewboard.search.search_backend_registry' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    line too long (80 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    'reviewboard.search.testing.search_enabled' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    'reviewboard.search.search_backend_registry' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    For dicts we also need to specify the key and value types, like dict[str, str]. Also if this dict is …

    maubin maubin

    This needs a type hint for the return type.

    maubin maubin

    This should be :py:class: instead of just :class:

    maubin maubin

    While you're here can you get rid of the Python 2-ism here.

    maubin maubin

    Let's call self.validate() here to use the full validation logic, instead of duplicating a part of it.

    maubin maubin

    You should be able to import everything from reviewboard.reviews.models instead of from each individual submodule. So: from reviewboard.reviews.models import ( …

    maubin maubin

    Can you add a "Version Added" to the docstring.

    maubin maubin

    You don't need the \ here, you can do: * Added handlers for :py:data:`~reviewboard.reviews.signals. comment_issue_status_updated` for each concrete comment class.

    maubin maubin

    We could just do self._handlers[(Review, review_published)] = self._handle_review_published There's not really a point to make a _review_signal_classes, if you add …

    maubin maubin

    While here can you get rid of the Python 2-ism: super().handle_save(**kwargs)

    maubin maubin

    Let's move this inside the try block so that if an exception happens while invalidating the cache, it gets logged.

    maubin maubin

    We should always use typing in any of our new code. Can you add type hints to this method and …

    maubin maubin

    Can you update the arguments here so that they're each on their own line. We also prefer doing ... | …

    maubin maubin

    Can you update the arguments here so that they're each on their own line. Also **kwargs and *args don't need …

    maubin maubin

    Can we be more specific about what Exception we're expecting to catch? I don't think an Exception would ever be …

    maubin maubin

    This should be BaseComment type instead of Any.

    maubin maubin

    Same here, can we be more specific about what Exception we're expecting to catch.

    maubin maubin

    I believe you can do self.assertSpyCalledWith( signal_processor.handle_save, instance=review_request) That should check if signal_processor.handle_save has ever been called with the review_request …

    maubin maubin

    ALl the methods here and below need return types.

    maubin maubin

    Arguments need to be on their own line.

    maubin maubin

    Instead of this you can use self.spy_on and kgb.SpyOpReturn to mock this. Same with the tests below.

    maubin maubin

    blank line contains whitespace Column: 1 Error code: W293

    reviewbot reviewbot

    no newline at end of file Column: 9 Error code: W292

    reviewbot reviewbot
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    dan.casares
    Review request changed
    Change Summary:

    Updated signal_processor.py to properly reindex a review request when a comment, review, issue, etc. is published on that review request.
    Updated tests to ensure this is tracked correctly.

    Testing Done:
    ~  

    Verified that get_es_client returns a correctly configured Elasticsearch

      ~

    Ran full test suite.

      + Verified that get_es_client returns a correctly configured Elasticsearch
        client and raises RuntimeError for unsupported package versions. Verified
        that SignalProcessor invalidates the correct facet cache group after save and
        delete for all tracked model types, silently ignores unrecognised models, and
        does not propagate indexing exceptions. Confirmed existing backend and signal
        processor behaviour is unchanged.

    Commits:
    Summary ID Author
    Search: Add get_es_client() to ES backend and update SignalProcessor
    3c33a032f9e0104d5343802ac9a0c8282894ea00 DanielCasaresIglesias
    Search: Add get_es_client() to ES backend and update SignalProcessor
    3343cbafea00a0aba52011b1de6ebda931f40798 Daniel Casares-Iglesias

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    Review request changed
    Commits:
    Summary ID
    Search: Add get_es_client() to ES backend and update SignalProcessor
    3343cbafea00a0aba52011b1de6ebda931f40798
    Search: Add get_es_client() to ES backend and update SignalProcessor
    171b5c9ecbfbe28250b16ea3444851fafde6a582
    Updated styling to match standard
    b508db3f9253792cce09ac01f3144aa0a8ef7277

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    maubin
    1. 
        
    2. Show all issues

      This needs a type hint for the return type.

    3. Show all issues

      This should be :py:class: instead of just :class:

    4. reviewboard/search/search_backends/elasticsearch.py (Diff revision 5)
       
       
       
       
       
       
       
      Show all issues

      Let's call self.validate() here to use the full validation logic, instead of duplicating a part of it.

    5. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
       
       
       
       
       
       
      Show all issues

      You should be able to import everything from reviewboard.reviews.models instead of from each individual submodule. So:

      from reviewboard.reviews.models import (
        Comment,
        FileAttachmentComment,
        GeneralComment,
        Group,
        ...)
      
    6. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
      Show all issues

      Can you add a "Version Added" to the docstring.

    7. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
       
      Show all issues

      You don't need the \ here, you can do:

      * Added handlers for :py:data:`~reviewboard.reviews.signals.
        comment_issue_status_updated` for each concrete comment class.
      
    8. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
       
       
      Show all issues

      We could just do

      self._handlers[(Review, review_published)] = self._handle_review_published
      

      There's not really a point to make a _review_signal_classes, if you add any class to the list it would always map to the review_published signal in the loop, plus I don't know what other classes you would add other than Review.

    9. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
      Show all issues

      While here can you get rid of the Python 2-ism:

      super().handle_save(**kwargs)
      
    10. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
      Show all issues

      Let's move this inside the try block so that if an exception happens while invalidating the cache, it gets logged.

    11. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
      Show all issues

      We should always use typing in any of our new code. Can you add type hints to this method and every other new method you've added in this change.

      You'll also need to add a
      from __future__ import annotations import at the top of this file.

    12. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
      Show all issues

      Can we be more specific about what Exception we're expecting to catch? I don't think an Exception would ever be raised here, if there's no associated review request then review_request = review.review_request would just be None (I think).

    13. reviewboard/search/signal_processor.py (Diff revision 5)
       
       
      Show all issues

      Same here, can we be more specific about what Exception we're expecting to catch.

    14. reviewboard/search/tests/test_search.py (Diff revision 5)
       
       
       
       
       
       
      Show all issues

      I believe you can do

      self.assertSpyCalledWith(
          signal_processor.handle_save,
          instance=review_request)
      

      That should check if signal_processor.handle_save has ever been called with the review_request passed as the instance. Same comment applies to the tests below.

    15. Show all issues

      ALl the methods here and below need return types.

    16. Show all issues

      Arguments need to be on their own line.

    17. Show all issues

      Instead of this you can use self.spy_on and kgb.SpyOpReturn to mock this. Same with the tests below.

    18. 
        
    dan.casares
    Review request changed
    Commits:
    Summary ID
    Search: Add get_es_client() to ES backend and update SignalProcessor
    171b5c9ecbfbe28250b16ea3444851fafde6a582
    Updated styling to match standard
    66c1ba16c101b1723e3803d9784004e01ab8b6c3
    Search: Add get_es_client() to ES backend and update SignalProcessor
    171b5c9ecbfbe28250b16ea3444851fafde6a582
    Updated styling to match standard
    66c1ba16c101b1723e3803d9784004e01ab8b6c3
    Fixing issues.
    b5a9972985176b63976d106717cefad45e839104

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    dan.casares
    Review request changed
    Commits:
    Summary ID
    Search: Add get_es_client() to ES backend and update SignalProcessor
    171b5c9ecbfbe28250b16ea3444851fafde6a582
    Updated styling to match standard
    66c1ba16c101b1723e3803d9784004e01ab8b6c3
    Fixing issues.
    b5a9972985176b63976d106717cefad45e839104
    Search: Add get_es_client() to ES backend and update SignalProcessor
    171b5c9ecbfbe28250b16ea3444851fafde6a582
    Updated styling to match standard
    66c1ba16c101b1723e3803d9784004e01ab8b6c3
    Fixing issues.
    7db8aad14ac062d99258011fcaf23732b5e14caa

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.
    maubin
    1. 
        
    2. Show all issues

      For dicts we also need to specify the key and value types, like dict[str, str]. Also if this dict is read-only in this method, then we should use Mapping instead of dict.

    3. Show all issues

      While you're here can you get rid of the Python 2-ism here.

    4. reviewboard/search/signal_processor.py (Diff revisions 5 - 7)
       
       
      Show all issues

      Can you update the arguments here so that they're each on their own line.

      We also prefer doing ... | None instead of Optional[...], but here the Any type also includes the None type (because Any means it can be any type). So you can remove the Optional.

    5. reviewboard/search/signal_processor.py (Diff revisions 5 - 7)
       
       
      Show all issues

      Can you update the arguments here so that they're each on their own line. Also **kwargs and *args don't need type hints. So this should look like

      def _handle_review_published(
          self,
          review: Review,
          **kwargs,
      ) -> None
      
    6. reviewboard/search/signal_processor.py (Diff revisions 5 - 7)
       
       
      Show all issues

      This should be BaseComment type instead of Any.

    7.