Problem/Motivation

While developing a site, and trying to setup breadcrumbs for facets, we found this issue.
\Drupal\search_api\Plugin\search_api\display\ViewsBlock::isRenderedInCurrentRequest() relies on the existing block config entities, but those didn't exist when a block is placed using layout builder.
Therefore, it should be a mechanism to detect if the block is placed using layout builder in the current page, with fallback to the existing detection mechanism if not.

Steps to reproduce

  • Create a search api block view an a facet
  • Place the block and the facet into a content entity using layout builder
  • Setup the breadcrumb for facets
  • The breadcrumb won't work

Proposed resolution

Add support for layout builder to the isRenderedInCurrentRequest() method (if the module is installed).
Fallback to the existing detection method if the module is not installed or the block component was not detected in the current page.

Remaining tasks

  • Patch
  • Review
  • Test

Issue fork search_api-3226415

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

akalam created an issue. See original summary.

akalam’s picture

Status: Active » Needs review

I'm changing this issue to "Needs review" although the CI reports a error on require-dev because I'm not modifying the dependencies, so I wonder there is an error with CI not related with the MR

drunken monkey’s picture

Component: General code » Facets
Issue tags: +Needs tests

Thanks a lot for reporting this problem and already providing code to address it!

Please don’t use issue forks in this project, though, as the d.o tech team hasn’t been able to get automated tests working for them correctly yet. (Cf. #3190024: Problem with test dependencies when testing issue forks.)
I’m reposting your code as a patch, along with some modifications by me. (I prefer a slightly different style of DI in this module, which I like to keep consistent, and I also think the much “quicker” check for whether blocks are displayed should probably go before the more complex Layout Builder checks.

In any case, as I’m not using Layout Builder I cannot verify whether this issue a) actually exists and is b) fixed by your MR.
Would you be able to provide automated test coverage for this bug, so we can also make sure this keeps on working in the future?

Also, input from others would be very much appreciated, too.
Finally, do you think this could negatively impact performance, or should those additional checks go reasonably quickly?

drunken monkey’s picture

drunken monkey’s picture

Status: Needs review » Needs work

NW for the tests.

miteshmap’s picture

Status: Needs work » Needs review
StatusFileSize
new5.24 KB
new4.75 KB

The patch works fine if an entity has overridden layout builder, it doesn't work for default layout builder. However, the layout builder is only one scenario here, there are cases where the block is placed through the layout paragraphs or block reference field on entity and it doesn't work well. I think we should provide a hook to allow other modules (custom or contrib) to modify the status based on the logic.

miteshmap’s picture

StatusFileSize
new5.27 KB
new4.78 KB

Updated patch

drunken monkey’s picture

Status: Needs review » Needs work

@ miteshmap: Thanks for the suggestion, this also sounds sensible. However, I’m not sure whether to move this issue to that approach, or whether to first fix it for the main use case and then provide more flexibility in a follow-up. Does anyone else have an opinion on this?

In any case, we definitely won’t introduce a new hook, but an event, as we’re currently moving away from hooks (see #3023704: Convert hooks to events).
Also, if we add an event for this in this issue, we should then also provide a listener that fixes the actual problem discussed here. (Or keep the code solving this for layout builder right in that method, as done in #5 – but I think I’d then tend to add the event in a separate issue.)

shani maurya’s picture

Hi @miteshmap:

Thank You for the patch, but patch #8 is conflicting with the latest Security Update for the Search API module https://www.drupal.org/sa-contrib-2022-059

After Updating the security update I'm getting this Fatal Error throughout the site:

Fatal error: Cannot use Symfony\Component\DependencyInjection\ContainerInterface as ContainerInterface because the name is already in use in /var/www/html/docroot/modules/contrib/search_api/src/Plugin/search_api/display/ViewsBlock.php on line 13

Thank You!

mkalkbrenner’s picture

Priority: Normal » Major

Coming from #3315228: ViewsBlock::isRenderedInCurrentRequest() fails for Layout Builder .

I think that there're multiple issues in the facets issue queue that might be related as layout builder is used more and more.

I second drunken monkey to directly add the layout builder detection logic as a function instead of using a hook or event.

miteshmap’s picture

Thanks @drunken-monkey and @mkalkbrenner

I agree with adding the logic function to detect layout builder. But, we will also need Events (As we are moving away from hooks) to allow other modules to customise the logic.

There are other contrib modules that allows users to add blocks to layout builder. We may not be able to cover all the possible ways of how those module handles it. That's why I think we definitely need events (that can be a separate task).

mkalkbrenner’s picture

Adding the event for 3rd party makes sense. But we should not subscribe to our own events.

drunken monkey’s picture

Status: Needs work » Needs review
StatusFileSize
new3.46 KB

OK, then let’s go ahead with this. I’m fine with adding the event in this issue, too, if it doesn’t delay the commit. Otherwise we’ll do that in a separate issue.
However, in any case, this still needs tests before I can commit it. Is anyone able to write some? As I have no experience with Layout Builder, I wouldn’t quite know where to start.

Attaching a re-roll of #5.

drunken monkey’s picture

Status: Needs review » Needs work

NW for the tests.

mkalkbrenner’s picture

I got bug reports of a massive performance issue on one of our production sites. It tuned out that the reason was a facet_summary added as block via layout builder. Basically that block gets partly rendered on every page. So this patch is one part required of the solution.

But it turns out that the event is also required, because that logic is too simple:

        if ($param instanceof ContentEntityInterface
            && $param->hasField(OverridesSectionStorage::FIELD_NAME)
            && !$param->get(OverridesSectionStorage::FIELD_NAME)->isEmpty()) {

In our case the referenced entity in $param is not the one being rendered, but a referenced one. This happens through custom code. So we need the event to add our logic here, too.

mkalkbrenner’s picture

StatusFileSize
new4.94 KB
new7.3 KB

I added the event. Implementing the subscriber gave our site a massive performance boost by avoiding to build our complex facets on every page.

mkalkbrenner’s picture

Status: Needs work » Needs review
drunken monkey’s picture

Thanks for the additional code, looks pretty good. And good to know the event is also pretty important, then of course let’s add that, too.

  1. +++ b/src/Plugin/search_api/display/ViewsBlock.php
    @@ -107,12 +111,13 @@
           if ($block->access('view')) {
    -        return TRUE;
    +        $is_rendered_in_current_request = TRUE;
    +        break;
           }
    

    Do we really also need the event in case of a visible block? Shouldn’t that always be accurate?

    Similarly, do we also want to send the event if Layout Builder module isn’t present? It seems to me like this would add needless overhead for simple sites, where we’d now dispatch one event for each facet (based on a Views block display) on every page.

  2. +++ b/src/Event/SearchApiEvents.php
    @@ -209,4 +209,16 @@ final class SearchApiEvents {
    +   * Event fired when detecting if display is rendered in current request.
    +   *
    +   * When detecting if a display is rendered in current request the decision
    +   * could be overridden within this event..
    

    I think this is too unspecific, if we’re only gonna fire it for Views blocks (or, worse, only for Views blocks under certain circumstances). People might wonder why the event never fires for them.

  3. +++ b/src/Event/IsRenderedInCurrentRequestEvent.php
    @@ -0,0 +1,87 @@
    +  /**
    +   * The display plugin ID.
    +   *
    +   * @var string
    +   */
    +  protected $pluginId;
    

    The way you currently initialize this, it is not the display plugin ID, but the block plugin ID. However, I don’t think we should include that, as it would make the event specific for Views blocks, with no clean way of later extending it to other use cases. Including the display plugin ID, on the other hand, would be pretty pointless as you can get that with $event->getDisplay()->getPluginId().

These and a few code style nitpicks would be changed/fixed by the attached patch revision.

drunken monkey’s picture

Status: Needs review » Needs work

Still needs tests, either way.

mkalkbrenner’s picture

+++ b/src/Plugin/search_api/display/ViewsBlock.php
@@ -107,12 +111,13 @@
if ($block->access('view')) {
- return TRUE;
+ $is_rendered_in_current_request = TRUE;
+ break;
}

Do we really also need the event in case of a visible block? Shouldn’t that always be accurate?

Similarly, do we also want to send the event if Layout Builder module isn’t present? It seems to me like this would add needless overhead for simple sites, where we’d now dispatch one event for each facet (based on a Views block display) on every page.

I'm of the opinion that an event should always be fired. The way I implemented it you can use the event to overwrite the "TRUE" decision, too.
And I think that this is a valid use case for a search result block. In opposite to a page, such a block can be added on different pages and some might contain facets while others don't. Think about search results as part of simple news newsletters or RSS feeds. I know that these aren't common use-cases. But if you do such things, there's no block layout where you can place a condition to omit the facet. For sure it won't be rendered, but it will be populated with results! And if you also take facets features like combined facets into account, a "complete" API becomes valuable.

+++ b/src/Event/SearchApiEvents.php
@@ -209,4 +209,16 @@ final class SearchApiEvents {
+ * Event fired when detecting if display is rendered in current request.
+ *
+ * When detecting if a display is rendered in current request the decision
+ * could be overridden within this event..

I think this is too unspecific, if we’re only gonna fire it for Views blocks (or, worse, only for Views blocks under certain circumstances). People might wonder why the event never fires for them.

As described above, firing the event only under some circumstances in not a good design.
But I agree that we should limit it to blocks as the problem is specific to block displays.

The way you currently initialize this, it is not the display plugin ID, but the block plugin ID. However, I don’t think we should include that, as it would make the event specific for Views blocks, with no clean way of later extending it to other use cases. Including the display plugin ID, on the other hand, would be pretty pointless as you can get that with $event->getDisplay()->getPluginId().

OK, I agree to remove the pluginId.

drunken monkey’s picture

I'm of the opinion that an event should always be fired. The way I implemented it you can use the event to overwrite the "TRUE" decision, too.
And I think that this is a valid use case for a search result block. In opposite to a page, such a block can be added on different pages and some might contain facets while others don't. Think about search results as part of simple news newsletters or RSS feeds. I know that these aren't common use-cases. But if you do such things, there's no block layout where you can place a condition to omit the facet. For sure it won't be rendered, but it will be populated with results! And if you also take facets features like combined facets into account, a "complete" API becomes valuable.

If I understand you correctly, you’re arguing that even though the search block is displayed, a user might still want to hide facets?
In that case, I’d argue that this is the wrong place to implement that functionality. The method is called isRenderedInCurrentRequest(), not shouldFacetsBeRendered() – if the search block will be displayed, this method should return TRUE. The method can, after all, also be used by other modules than Facets. If a user doesn’t want to display a facet for a search that is present on the page, they should implement that functionality using Facets, not by “hiding” the search.
Is there no way to just hide a facet for specific pages, or alter whether a facet is computed in a given request?

In any case, I’m not against also firing the event when the result is TRUE, but against firing it when we are sure about the result. And I think if we detect that a block is displayed using Drupal’s normal functionality, then that result will be reliable.

mkalkbrenner’s picture

The issue in facets is that facets are always filled with results before any decision takes place. Depending on the facet, that could be very expensive.
At the moment isRenderedInCurrentRequest() returning FALSE is the only way to terminate facets processing early.

But I agree that this issue should be solved in facets itself, but it might require architectual changes. Maybe we should introduce a "terminate early" event in facets.

drunken monkey’s picture

Issue tags: +Needs tests

OK, then I’d suggest trying that approach, within the Facets module, first, and only resolving it via isRenderedInCurrentRequest() as a last ressort, in case implementing this in Facets would not be feasible for some reason. Is there already an issue in the Facets queue?

How do you propose we proceed in this issue, then? I guess the patch in #19 still makes sense?

mkalkbrenner’s picture

Yes, we should go with #19.

There're multiple issues within the facets queue. But we need to consolidate them.

shani maurya’s picture

Version: 8.x-1.x-dev » 8.x-1.28
StatusFileSize
new5.41 KB

Re-roll the patch for #8 as previous patch conflict with Latest version 8.x-1.28

drunken monkey’s picture

Version: 8.x-1.28 » 8.x-1.x-dev
Status: Needs work » Needs review
StatusFileSize
new4.53 KB
new13.09 KB

I finally got round to writing a test. Please test/review so we can finally resolve this issue, at least from the Search API point of view.
(Tests-only patch doubles as the interdiff, I didn’t change any non-test code.)

drunken monkey’s picture

Issue tags: -Needs tests
drunken monkey’s picture

Long time since I added a whole new test class, apparently …

Status: Needs review » Needs work

The last submitted patch, 29: 3226415-29--views_block_display_layout_builder_compat--tests_only.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

drunken monkey’s picture

Status: Needs work » Needs review
drunken monkey’s picture

Does anyone time to test/review?

miteshmap’s picture

Status: Needs review » Reviewed & tested by the community

Verified the patch and works fine as expected, Good to merge!

  • drunken monkey committed 526cbe50 on 8.x-1.x authored by akalam
    Issue #3226415 by akalam, drunken monkey, mkalkbrenner, miteshmap: Fixed...
drunken monkey’s picture

Status: Reviewed & tested by the community » Fixed

Good to hear, thanks a lot for testing and reporting back!
Committed. Thanks again, everyone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.