Problem/Motivation

The help system needs search capability.

Proposed resolution

Probably, using the existing Drupal core Search module to build the search functionality would make the most sense. But build it in such a way that Search API contrib module, or other search modules, could easily grab the help text to index and make it searchable.

A note about caching:
- We're really only concerned here about caching for the search results page.
- The Search module already puts cache tags in based on the search index (which we have a test assert for). So that means whenever the search index changes (i.e. for whatever reason we have decided to add, delete, or update an entry for a help item), the search results page cache is invalidated. That should be sufficient to take into account the idea that if the list of (and contents of) help topics that are searchable changes for any reason, we should invalidate the search results page (because you would get different search results potentially).
- We do also want to let each item in the results, as it renders itself, to declare cache stuff (this is also in the patch/test).
- We also need a cache context for user permissions, because which items can be searched depends on user permissions.
- We should also restrict the search to only look for topics in the current page language, and add a context for the interface language.

Remaining tasks

  1. [Done] Implement search indexing and searching, using the core Search module. The latest patch (#54) has this working. Here is what the search results look like:
    Search results output screenshot
  2. [Done] Once it's finalized, we'll need to update its hook_help, and also add a Help Topic for it. NOTE: The help topic could use some links to help topics that don't yet exist about creating search pages and block placement. We'll have to do those later when we clean everything up in the topics on #2687107: Reorganize topics into sensible outline, and/or write more topics so I'll add a note to that issue.
  3. [Done] Add a search box to the main Help page. This depended on #3067943: Add capability for search blocks on non-default search and looks like this:
    Help page screenshot showing help search block
  4. [Not doing at least now] Possibly add search block to other admin pages?
  5. [Not doing at least now] We discussed having a keyword functionality -- a field added to the HelpTopic plugin that would define keywords that could be emphasized in the search, but decided that would not be part of this patch.
  6. [Done] Write a test. Things to test:
    - [Done] If you enable Seven theme, you get the search block present on the help page.
    - [Done] Search works for some keywords. Probably best to use the test module HelpSection plugin for that.
    - [Done] Search permissions -- both the overall permission and the one that is used for a given help section. The existing help tests verify this with a section that has permissions, so we could make that HelpSection plugin searchable for further testing.
    - [Done] Internationalization of search -- again with that test plugin... the HelpSection plugins have control over how to render items for indexing/results in a given language, so it would not be too hard to fake the internationalization bit in the test.
    - [Done] Also add tests in the Help Topics module that verify that searching works with Help Topics in particular, and the internationalization of search works for Help Topics. But best to have the main tests independent of Help Topics I think?
    - [Done] Cache tags/contexts/etc.

User interface changes

Help topics provided by the Help Topics module will be searchable. There is a new block on the main Help page that lets you search help.

API changes

No changes to existing APIs.

New API: HelpSection plugins that also implement the new interface \Drupal\help_topics\SearchableHelpInterface will make their topics searchable. So far, only the HelpTopicsSection in the help_topics module implements this interface.

Data model changes

A new database table help_search_items is added to keep track of the IDs of help topics that are searchable.

Steps to test

  1. Apply latest patch
  2. Enable help_topics module
  3. Go to admin/config/search/pages and click "Re-index site"
  4. Run the cron
  5. Go to /admin/help and search for a keyword. e.g. shortcuts and make sure there is at least a result

Release notes snippet

We have added a new search type (that is, a new @SearchPlugin plugin) to Core, to go along with the Node and User search types that already existed. This search type is for searching help, and its plugin is called \Drupal\help_topics\Plugin\Search\HelpSearch (it is in the experimental Help Topics module). Using this type of search, administrators can define search pages that will provide help search to site users with permission to view help.

Modules that provide @HelpSection plugins (which define sections for the admin/help page) can make their help text searchable by implementing a new interface \Drupal\help_topics\SearchableHelpInterface on their HelpSection class, which requires implementing two methods: listSearchableTopics() to list the IDs of help topics that should be searchable, and renderTopicForSearch(), which renders a single ID for either help indexing or help search results. As an example, see the \Drupal\help_topics\Plugin\HelpSection\HelpTopicsSection plugin, which is currently the only HelpSection plugin that implements this interface. (So, currently if you search help, you are only searching Help Topics managed by the Help Topics experimental module.)

CommentFileSizeAuthor
#213 2664830-3-213.patch62.25 KBalexpott
#213 207-213-interdiff.txt2.36 KBalexpott
#207 2664830-3-207.patch64.04 KBalexpott
#207 203-207-interdiff.txt1.12 KBalexpott
#203 interdiff.txt1.84 KBjhodgdon
#203 2664830-203.patch63.38 KBjhodgdon
#201 interdiff.txt771 bytesjhodgdon
#201 2664830-201.patch61.55 KBjhodgdon
#200 2664830-200.patch61.5 KBandypost
#200 interdiff.txt866 bytesandypost
#192 2664830-3-192.patch61.45 KBalexpott
#192 187-192-interdiff.txt13.32 KBalexpott
#192 2664830-3-192.patch61.45 KBalexpott
#192 187-192-interdiff.txt13.32 KBalexpott
#187 2664830-187.patch63.8 KBandypost
#187 interdiff.txt10.18 KBandypost
#185 2664830-184.patch60.91 KBjhodgdon
#185 interdiff.txt4.11 KBjhodgdon
#183 2664830-183.patch59.97 KBjhodgdon
#179 2664830-179.patch59.96 KBandypost
#179 interdiff.txt1.61 KBandypost
#179 search_pages.png16.62 KBandypost
#172 2664830-172.patch59.67 KBjhodgdon
#170 2664830-170.patch60.37 KBjhodgdon
#170 interdiff.txt8.72 KBjhodgdon
#169 2664830-reroll.patch57.93 KBjhodgdon
#166 2664830-2-166.patch57.88 KBalexpott
#166 153-166-interdiff.txt625 bytesalexpott
#153 2664830-153.patch57.8 KBalexpott
#153 152-153-interdiff.txt1.73 KBalexpott
#152 2664830-152.patch57.43 KBalexpott
#152 147-152-interdiff.txt3.59 KBalexpott
#148 2664830-148-search-result.png216.17 KBvijaycs85
#148 2664830-148-search-index-admin.png361.84 KBvijaycs85
#148 2664830-148-no-result.png169.71 KBvijaycs85
#148 2664830-148-index-page.png444.23 KBvijaycs85
#147 interdiff-142-147.txt1.09 KBGhost of Drupal Past
#147 2664830_147.patch57.95 KBGhost of Drupal Past
#144 3084526_143.patch57.91 KBGhost of Drupal Past
#144 interdiff-142-143.txt869 bytesGhost of Drupal Past
#142 interdiff-137-142.txt17.86 KBGhost of Drupal Past
#142 2664830-142.patch57.4 KBGhost of Drupal Past
#140 interdiff-137-140.txt8.14 KBGhost of Drupal Past
#140 2664830-140.patch57.36 KBGhost of Drupal Past
#137 2664830-137.patch57.13 KBoknate
#137 2664830--interdiff-136-137.txt675 bytesoknate
#136 2664830-136.patch57.1 KBoknate
#134 interdiff.txt2.36 KBandypost
#134 2664830-134.patch54.43 KBandypost
#133 Screen Shot 2019-09-16 at 8.07.30 am.png178.63 KBlarowlan
#133 Screen Shot 2019-09-16 at 8.08.48 am.png51.33 KBlarowlan
#133 Screen Shot 2019-09-16 at 8.10.05 am.png213.21 KBlarowlan
#131 2664830-131.patch54.53 KBjhodgdon
#125 2664830-125.patch54.54 KBjhodgdon
#125 interdiff.txt3.65 KBjhodgdon
#121 interdiff.txt4.29 KBjhodgdon
#121 2664830-121.patch53.62 KBjhodgdon
#119 2664830-119.patch53.32 KBjhodgdon
#119 interdiff.txt11.03 KBjhodgdon
#114 2664830-114.patch50.38 KBandypost
#114 interdiff.txt9.24 KBandypost
#113 2664830-113.patch49.92 KBjhodgdon
#113 interdiff.txt1.05 KBjhodgdon
#109 2664830-109.patch50.01 KBjhodgdon
#109 interdiff.txt1.1 KBjhodgdon
#108 2664830-108.patch50.01 KBjhodgdon
#108 interdiff.txt17.19 KBjhodgdon
#100 interdiff.txt1.41 KBjhodgdon
#100 2664830-100.patch49.4 KBjhodgdon
#96 2664830-96.patch48.97 KBjhodgdon
#96 interdiff.txt762 bytesjhodgdon
#87 interdiff83.txt1.3 KBjhodgdon
#87 2664830-83.patch48.23 KBjhodgdon
#86 interdiff82.txt591 bytesjhodgdon
#86 2664830-82.patch48.23 KBjhodgdon
#86 interdiff78.txt7.48 KBjhodgdon
#86 2664830-78.patch48.23 KBjhodgdon
#77 interdiff.txt4.23 KBjhodgdon
#77 2664830-77.patch48.13 KBjhodgdon
#69 interdiff.txt4.08 KBjhodgdon
#69 2664830-69.patch49.33 KBjhodgdon
#68 interdiff.txt955 bytesjhodgdon
#68 2664830-68.patch46.78 KBjhodgdon
#67 interdiff.txt7.42 KBjhodgdon
#67 2664830-67.patch46.67 KBjhodgdon
#64 interdiff2.txt1.36 KBjhodgdon
#64 interdiff.txt3.74 KBjhodgdon
#64 2664830-64.patch46.93 KBjhodgdon
#61 interdiff.txt14.06 KBjhodgdon
#61 2664830-61.patch45.46 KBjhodgdon
#59 interdiff.txt1.75 KBjhodgdon
#59 2664830-59-fix-test-fails.patch32.49 KBjhodgdon
#54 interdiff.txt15.24 KBjhodgdon
#54 2664830-54.patch32.48 KBjhodgdon
#54 searchresults.png37.48 KBjhodgdon
#54 help_page_with_search.png47.65 KBjhodgdon
#53 interdiff.txt47.97 KBjhodgdon
#53 2664830-move-stuff-45.patch31.98 KBjhodgdon
#53 2664830-patch43-rerolled.patch31.59 KBjhodgdon
#43 Screenshot from 2019-07-24 07-15-57.png366.94 KBscott_euser
#43 core-add-search-to-help-topics-2664830-43.patch31.59 KBscott_euser
#43 interdiff-2664830-41-43.txt4.33 KBscott_euser
#41 interdiff.txt13.83 KBjhodgdon
#41 2664830-41.patch31.1 KBjhodgdon
#35 2664830-35.patch28.19 KBjhodgdon
#34 2664830-34.patch27.85 KBjhodgdon
#32 2664830-32.patch27.85 KBjhodgdon
#32 interdiff.txt1.46 KBjhodgdon
#29 2664830-29.patch27.41 KBjhodgdon
#29 interdiff.txt8.26 KBjhodgdon
#29 helpsearch.png40.35 KBjhodgdon
#28 interdiff2.txt625 bytesjhodgdon
#19 interdiff-2664830-18-19.txt6.55 KBscott_euser
#28 interdiff.txt7.95 KBjhodgdon
#19 screenshot-drupal8.ddev_.site-2019.07.12-09_00_28.png59.15 KBscott_euser
#28 2664830-27.patch23.92 KBjhodgdon
#18 2664830-closer.patch20.08 KBjhodgdon
#19 core-add-search-to-help-topics-2664830-19.patch20.99 KBscott_euser
#8 2664830-unfinished.patch11.91 KBjhodgdon
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

jhodgdon created an issue. See original summary.

gnuget’s picture

I want to work on this but not sure to understand what do you mean with "index"

Also, I think Search API already provide a way to search for any custom entity and it is already stable, should we let seach api to handle the search instead to create our own?

https://www.drupal.org/project/search_api

jhodgdon’s picture

The Search API project is not an OK plan for this, if we are going to put this in Core. We would instead need to use the generic Drupal core "Search" module.

So there are two possible strategies here:

a) Search -- add the text of topic pages to the Drupal Core search index, and then when someone does a help search, that index is used to find if there are matching pages.

b) Index -- each help topic would define its own entries for the help index (kind of like keywords). For instance, a topic about how to add fields to a content type might say that in the Help index, it wants to have entries for
Content type
Field
etc.

Then the Configurable Help module would build an index by collecting all the help topics' index entries and combining them alphabetically, and it would provide a page that would list the index entries, similar to the index you would find in the back of a programming book.

So... These two strategies are a bit different. With the Search strategy, each word in a topic would be indexed. With the Index strategy, topic authors would manually define what the important keywords were for indexing.

gnuget’s picture

Thanks for the explanation.

I haven't experience with any of the two approaches, so I couldn't say which one is the most ideal for this project.

I can help building them though.

Let me know if you decide which one is the most ideal for this.

Thanks!

(I had a very long month, sorry for disappear!)

jhodgdon’s picture

It seems to me that probably the search approach is probably best for this project.

The way the core Search API works in Drupal 8 is this... To tell the core Search module that you have some content to search, you define a config entity plugin of type SearchPage:
https://api.drupal.org/api/drupal/core%21modules%21search%21src%21Entity...

There's documentation on how to do D8 plugins here:
https://api.drupal.org/api/drupal/core%21core.api.php/group/plugin_api/8...
See the "Creating a plugin of an existing type" documentation. There are also two examples in Core. The most relevant is probably:
https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%2...

You'll probably want to extend this class
https://api.drupal.org/api/drupal/core%21modules%21search%21src%21Plugin...

And you'll need to implement this interface, so that the help content can be indexed:
https://api.drupal.org/api/drupal/core%21modules%21search%21src%21Plugin...

That's actually basically it, except that it would probably be helpful to add a link somewhere on the Help page to the search page.

Let me know if you need more advice. I was heavily involved in setting up the D8 Search API, and of course I also wrote this module, so I can most likely answer any questions that come up. Thanks!

jhodgdon’s picture

Title: Search or index? » Add search capability
Priority: Normal » Major
Issue summary: View changes
Related issues: +#2592487: [meta] Help system overhaul, +#2920309: Add experimental module for Help Topics

From discussions on #2920309: Add experimental module for Help Topics and #2592487: [meta] Help system overhaul, it is looking like what we really want is a search system. So, I'm changing the title here. Also it is higher priority than just "Normal".

Editing the issue summary also.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

I'm going to take this on... Even though it doesn't need to be done before we can get #2920309: Add experimental module for Help Topics into Core. There's not much else waiting to be done. :)

jhodgdon’s picture

I'm realizing that this should probably be a separate module:
- To use the core Search module's index/search capability (which seems like a good idea!), we would need to provide numeric IDs for each topic/page for the index. So, we'd need to have a database table to save a correspondence between a numeric ID and the config ID for the topic.
- It seems like we should make both the hook_help() module overviews and the configurable help topics searchable, plus (in principle) any other topics provided by other modules on the main Help page (admin/help).
- If we're doing that, we probably need either a set of hooks or a plugin or something that will allow each module to tell the search system about its topics.
- All of that just seems to say to put it in a separate module "Help Search" that depends on Search and Help, and then let each provider of help topics (including Config Help) implement the plugin/hook(s) so that if this help search module is enabled, searching will be there.

Anyway... I don't think I'm going to continue to work on this right now (seems better to wait until Config Help is in Core first), but I'm attaching the SearchPage plugin I started writing so I don't lose that work.

jhodgdon’s picture

Title: Add search capability » Add search capability to help topics
Project: Configurable Help » Drupal core
Version: » 8.8.x-dev
Component: Code » help.module
geek-merlin’s picture

#3048650: Add "Relevant help topics" block (or re-purpose help block) would benefit from a link index. I don't know though if core search provides this.

jhodgdon’s picture

I am not sure what you mean by a link index?

geek-merlin’s picture

An index for all the links on a help topic page. See the related issue. (Think "What links here" block on some sites.)

jhodgdon’s picture

If you mark topic A as related to B, by using the 'related' meta-tag in the Twig file, then the link between A and B is automatically bi-directional. The Help Topics module takes care of that.

But the search module doesn't create an index of links.

geek-merlin’s picture

Maybe the screenshot on the Help Topics Backlinks page clarifies: Think about a backlink from the "Basic site settings" page to the "Configuring basic site settings" help page.

And yes, it'd need a different plugin than full text search.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

I'm going to get back to working on this now that Help Topics are in Core.

vacho’s picture

Hi here. it is an interesting and very useful feature for Drupal users. Congrats and thanks a lot @jhodgdon for your contributions.

Some aspects that I can recommend(IMHO):

  1. Due to module help_topics is here (8.8.x branch) we need to move this searcher inside this module
  2. Now, helps came in twig files; so is needed that the plugin searches inside these too. I am unsure about how to do it because twig content is not an entity where we can consult by a query. Maybe this is related https://api.drupal.org/api/drupal/core%21modules%21search%21templates%21...
jhodgdon’s picture

Hi!

1. There is no component in the issue queue for help_topics, because we have plans to eventually merge help_topics into help when it gets stable. Check this issue for more information about the plans. #3027054: Help Topics module roadmap: the path to beta and stable
2. This issue is currently assigned to me and I plan to work on it next week. But yes, I will need to render the topic Twig templates and index the resulting HTML content. The patch here was from before. And no, it's not really related to that search result Twig template.

If you're looking for something to help with, please check #3027054: Help Topics module roadmap: the path to beta and stable for an issue that is not yet assigned to anyone. Thanks for your interest!

jhodgdon’s picture

Here's a preliminary patch. It's totally untested, and not finished, but uploading here as a start. Things to do:
a) Write the findResults() method in the HelpSearch class (currently empty, see NodeSearch for example of how it should work).
b) Implement HelpSearchInterface in at least one HelpSection plugin (presumably the one in the Help Topics module).
c) Add configuration for a help search page, and put a search form at top of admin/help.
d) Does it need its own permission? I don't think so. There are already permissions for viewing help; presumably if you have this module enabled that is good enough to know you want people who can see help to be able to search it?
e) Test manually and get it working
f) Write a test for it
g) Write a help topic and update the hook_help for this new module based on how it actually works, once it's working. A rough idea is in the hook_help now but it may need revising.

A few notes on how it is supposed to work:

1. Modules that want to list their help on admin/help already create HelpSection plugins to tell the Help module about their topics.
2. Now they can also make that same plugin implement HelpSearchInterface. There are 3 methods to implement, which list and render topics.
3. If they do that and the Search, Help, and Help Search modules are installed, their help will be searchable. Site builders will need to create a "search page" (one will be provided by default) for searching help, and put a block on admin/help or wherever for searching help. (Or maybe this module will add this to the page already? that would be better.) Also they'll need to run cron to index stuff.

scott_euser’s picture

Just a bit more progress on this. Fixed a few fatal errors and got the search index running (not sure if the config install is the correct way, but this was how node and user core modules appear to do it).

jhodgdon’s picture

Thanks... I guess? This issue was assigned to me and I'm working on it.

The usual etiquette in such a case would be to wait at least a week to see if someone is still working on an issue, and then ask if you can take it over. In this case, I made the last patch 4 days ago and was planning to get back to it today. Now I don't know if you plan to take it to completion (you didn't assign it to yourself) or if I can get back to working on it. Please clarify! This is a high-priority issue and I have time to work on it today.

A few notes, looking at your interdiff -- which mostly looks good!

a) I am pretty sure we don't normally add @throws to doc blocks, unless that method explicitly throws an exception itself, which these ones don't. We don't document when a method is simply not handling exceptions that are thrown by things it calls. (Check examples in Core to confirm this, but I'm pretty sure.) If we did need a @throws, it would need an explanation as to why/when, I think? It's a pretty generic exception title. See
https://www.drupal.org/docs/develop/standards/api-documentation-and-comm...

b) Converting simple queries to using select() is not really ideal. See
https://www.drupal.org/docs/8/api/database-api/static-queries
https://www.drupal.org/docs/8/api/database-api/dynamic-queries/introduct...
The queries in my patch that were ideal were copy/pasted nearly verbatim from
https://api.drupal.org/api/drupal/core%21modules%21node%21src%21Plugin%2...
so some of them should probably be restored.

c) You asked about config/install -- yes, that is correct!

Anyway, let me know if you're planning to take this to completion, and if so, assign it to yourself. Thanks!

jhodgdon’s picture

Assigned: jhodgdon » Unassigned

I'm going to work on something else today... Anyway let me know if you plan to continue or if you want me to get back to this issue. Thanks!

scott_euser’s picture

Sorry I did not see it, was just trying to help! Feel free to ignore.

scott_euser’s picture

If you do decide not to just discard the patch, re the database queries static vs dynamic, I had changed them because they were causing my fatal errors when the search index tried to install the config, but for no reason other than that.

If you do want any help on this in the future let me know. I will be away next week but could help the following week before/after work.

Again very sorry for not seeing the assignment! Really did not intend to get in your way.

jhodgdon’s picture

No worries, and I definitely don't want to discard the patch (which was great work)! I was mildly annoyed (since I had planned to work on this today), but I'm happy to have someone so enthusiastic about contributing to this effort, so I'm over it. Feel free to take this on now -- I have plenty of other things to do. :)

Good explanation re dynamic queries. I personally prefer them anyway, it's just that I've heard we aren't supposed to use them unless we need them. But let's leave them in, and please go ahead and keep working on this issue. :)

scott_euser’s picture

Okay thanks for understanding! I'll pick this up when I am back then if nobody else is working on it (I'll check this time haha!)

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

Ah, I missed that in your last comment. If you're going to be away for a week, then I think I'll go ahead and make the next patch, since the other issue I was working on turned out to be pretty trivial. I am working on this today/now for sure, and then I'll unassign if I'm unavailable for a few days, and you can work on it again.

Sorry for being annoyed. It is never a great idea to get annoyed when people make good code contributions. :)

scott_euser’s picture

Was justified :) Sounds good! I've got a reminder set to look at this ticket a week from Monday whatever the status is, even if to test or add tests.

jhodgdon’s picture

OK, here's the next patch!

Things I did:
a) Removed the @throws from the doc blocks (as discussed above). We definitely can't have inheritdoc + extra stuff in that one doc block (that doesn't work at all!), and I don't think we need them in the other doc blocks.

b) Changed one if() statement -- if we check that we implement the HelpSearch interface, then we don't also need to check that the methods exist (PHP will complain if you say you implement an interface but miss methods).

c) Implemented HelpSearchInterface for help topics, so they will get indexed. There are some @todo comments in HelpTopicSection about how to deal with languages... I am not sure how to do that.

d) Worked on the code until the indexing actually worked, and I could see that the help topics had their words in the search index.

Sorry, 2 interdiff files (in reviewing the patch, I found 1 more thing to change).

Still to do:

1. Write the findResults() method in the HelpSearch class (currently empty, see NodeSearch for example of how it should work). This will actually let the search part work.
2. Put a search form at top of admin/help.
3. Test searching manually and get it working
4. Write a test for it
5. Write a help topic and update the hook_help for this new module based on how it actually works, once it's working. A rough idea is in the hook_help now but it may need revising.

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
40.35 KB
8.26 KB
27.41 KB

Wooot!

Here's a patch that gets search actually working! Setting this to Needs Review for the first time.
Screenshot of help search

Still to do:
1. Fix the @todo items in the patch, mostly having to do with caching and translation.
2. Put a search form at top of admin/help (currently you have to go to search/help in order to search).
3. Write a test for it.
4. Write a help topic and update the hook_help for this new module based on how it actually works, once it's working. A rough idea is in the hook_help now but it may need revising.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes

Well, here's a problem... The URL for searching help is configurable (that is how the core Search module's Search Pages work). Also, the Search module only provides a block for using the default type of search that is configured on the site. So... We don't really have a way to set up a block with a search form in it for help, or to add a form that will search help from admin/search, because someone could go to the Search Settings page and edit the HelpSearch page that is provided in this patch to change the URL. Or even delete it.

I think the only way around this would be to make it so that the Search module provides a more configurable block, which allows you to specify which search page you want the search to go to. We'll probably need to spin this off to a different issue... unless we can think of another way to do it.

Anyway, updating the issue summary, and I'm probably done working on this for the moment.

Status: Needs review » Needs work

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

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
1.46 KB
27.85 KB

The test errors seem to be:
- We forgot to add new module help_search to the core/composer.json file
- The dependencies might be incorrect, not sure about that? Maybe the composer fix will fix that other error
- There is something wrong with the search page config [although I can't figure out what?]
- 2 coding standards messages

So... let's try this...

Status: Needs review » Needs work

The last submitted patch, 32: 2664830-32.patch, failed testing. View results

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
27.85 KB

Hm. That fixed one test fail, but the module install/uninstall test is still failing, as is the config test. I am going to try moving the config to the config/optional directory instead of config/install and see if that helps. Interdiff is difficult, but all I did was move that one file to a new location.

jhodgdon’s picture

FileSize
28.19 KB

Oh, duh! We need a config schema file. Adding that, and I'll put the config item back where it was.

Status: Needs review » Needs work

The last submitted patch, 35: 2664830-35.patch, failed testing. View results

jhodgdon’s picture

Status: Needs work » Needs review

Hm, there was a test failure that looks unrelated. Hitting retest...

jhodgdon’s picture

Here's the issue I created for the unrelated test fail, if anyone is interested (nothing to do with this patch):
#3067768: Random test fail in ManageGitIgnoreTest

So, I talked to @alexpott and @pwolanin (who is the Search module maintainer) in Slack today, and they said we should spin off a new issue to deal with the search block stuff. I created:
#3067943: Add capability for search blocks on non-default search

We can work on the To Dos here in the meantime, but we will not be able to complete this issue until that one is finished. I'll add it to the roadmap.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon
Issue summary: View changes

So, the @todo items in this patch. There are basically 3:

a) Cache tag stuff for the search page. Look at NodeSearch and do something similar.

b) Permissions. There are notes in that @todo on how to do that.

c) Language stuff when indexing and making snippets for search results... Thoughts:
- The titles of the HelpTopic topics are detected in HelpTopicDiscovery, and shoved into a TranslatableMarkup object (without the language being set).
- The body of HelpTopic topics is wrapped in {% trans %} in Twig (no language being set there).
- To render these into a desired language, it looks like we need to set the default language on the language.default service, and then all rendering/translation will happen in that language.

So, I think I'll work on (b) and (c) today. Also, I don't think we need keywords -- that could be a later enhancement -- so removing that from the issue summary.

jhodgdon’s picture

Issue summary: View changes
jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes
FileSize
31.1 KB
13.83 KB

OK, here's a new patch. I've taken care of two of the @todo items in the code (permission checking, and language stuff). One of them involved a change to HelpSearchInterface, as I decided it made more sense to render the item in the HelpSection plugin and not in HelpSearch. Now HelpSearch doesn't need the Renderer object.

The remaining @todo in the code (cache tags) -- I moved it around to 2 places and added some notes on how to do it. Decided to leave that to the next iteration.

Adding some notes about things to test to the issue summary.

scott_euser’s picture

Assigned: Unassigned » scott_euser

Trying to review and progress with this a bit now.

scott_euser’s picture

Great progress! All seems to be mostly working for me! I tested this out with and without help_topics enabled and tested out modifying permissions to allow other roles to use the search to see how that behaved with only a couple minor issues (below).

I tried to take care of the two TODOs in HelpSearch but I am not sure if I did them right. When I reviewed the cache tags I see we already have `search_index:help_search` cache tag. I was not sure if that was sufficiently like `node_list` in terms of being a general cache tag that is cleared on search (re)indexing, but put it in for now as the cache tags in HelpSearch::findResults() and similar approach for via HelpTopicSection::prepareSearchResult().
Help search cache tags

A couple other things I did here that I came across when testing it out:

  • Added check that permissions exists when checking permissions in HelpSearch. If help_topics module is not enabled, it throws an error as there are then no help_search_items.
  • Fix search index cron error in HelpSearch::updateIndex() "Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42000]: Syntax error or access violation: 1055 'db.hsi.plugin_type' isn't in GROUP BY: SELECT hsi.sid AS sid, hsi.plugin_type AS plugin_type, hsi.id AS id FROM {help_search_items} hsi LEFT OUTER JOIN {search_dataset} sd ON sd.sid = hsi.sid AND sd.type = :type WHERE (sd.sid IS NULL) GROUP BY hsi.sid LIMIT 100 OFFSET 0; Array ( [:type] => help_search ) in Drupal\help_search\Plugin\Search\HelpSearch->updateIndex() (line 304 of /var/www/html/core/modules/help_search/src/Plugin/Search/HelpSearch.php)."
jhodgdon’s picture

Yeah, some of the To Dos will need to wait for #3067943: Add capability for search blocks on non-default search... interdiff looks good I think. I'm still not sure about the cache tags. Will need to think more on that.

jhodgdon’s picture

Actually, I do have some thoughts about cache tags.

For Nodes (this may not apply to help topics), there are 2 relevant types of cache tags:

a) There's one that says "this is a list of nodes, so cache is invalid whenever any node is added, deleted, updated". That needs to be present on any search results page.

b) There's one that says "this page's content includes data from this particular node, so cache is invalid if this node changes in any way". That needs to be present on any search results page that includes that node.

The Search plugins have the capability to take care of both types of cache tags. For instance, the NodeSearch plugin does this in its constructor:

$this->addCacheTags([ 'node_list', ]);

(that's the type A cache tag), and then when building search results pages, the Search module adds the NodeSearch plugin as a cacheable dependency on the search results page.

And then NodeSearch, when building the snippet for the search results containing a given node, does this:

$this->addCacheableDependency(CacheableMetadata::createFromRenderArray($build));

So... It looks to me as though your patch adds the type A cache tags, but not in the same manner as was done on NodeSearch. And the type B ones are missing. I think it needs more work.

andypost’s picture

btw In related #2966607: Invalidating 'node_list' and other broad cache tags early in a transaction severely increases lock wait time and probability of deadlock cache tags may get split

but for topics search not sure how to calculate "hash" of rendered topics (they may change with code deploy - no hooks)
That could help to clear cache on re-indexing of topics

jhodgdon’s picture

Well, presumably the caches are cleared when code is deployed, right? And we have set up the Help Search (or at least the Help Topic Search) to re-index when the cache is cleared. So... I think we are OK.

jhodgdon’s picture

Status: Needs review » Needs work

Setting to Needs Work for #45 (need to redo the cache tags stuff from previous patch).

andypost’s picture

Blocker is in #3067943: Add capability for search blocks on non-default search
So remaining tasks
- adopt new block
- fix cache to tags & cover with tests

Probably it needs release manager to approve new module, I gonna check a way to keep it in help topics:
- schema could be created on demand (ensureTable() & check that search module enabled)
- HelpSearchInterface looks wrong because it is not a plugin's responsibility to be searcheable (contrib may need to use search_api so render and other methods could be different)

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

I do think that a provider of help (as evidenced by having a section on admin/help and hence a HelpSection plugin) will need to declare that by implementing basically these methods. They would be just as useful for search_api contrib module as they would for the core Search module, although the return values might be used differently. But additional methods are definitely needed ... For instance, the list of topics to display on admin/help, in the case of Help Topics, is only the "top level" topics, but all topics should be searchable.

I do think it can be rearranged though. We could call it SearchableHelpInterface, put it in the Help module directly, and make it a bit more generic, such as not asking the plugins to prepare the search result, but instead to return just the information about the URL, title, cache tags, and text. Then the HelpSearch plugin could do the excerpt stuff (which uses the search_excerpt function).

Anyway, I'll work on this -- that might not make total sense in this comment but I have it in my head... I was going to make the next patch here anyway, and I have time today and the next few days.

andypost’s picture

Btw what about "boost" for search results?
For example topics could be ordered, meta data hits surely should give priority to its topics

jhodgdon’s picture

I think that would be a possible future feature, but I don't think the base functionality requires it.

The base Search module (as implemented here) will automatically give more preference to higher headers, and some other HTML tags. So since we put the title in an H1, that will have highest priority, and headers will have next highest. It works pretty well. Right now we don't have any other relevant metadata, like keywords defined by the topic...

jhodgdon’s picture

Before I get too far, I'm uploading some intermediate patches:
1. The patch from #43, rerolled so it applies cleanly.
2. An interdiff/patch from that that only moves some stuff around, so that we don't have a help_search module at all. I put all the help search stuff into help_topics for now, because adding it to the core Help module would be weird (since it doesn't itself have anything that is searchable).

Note that the interdiff is not all that useful or easy to see, because... well I'm not great at making interdiffs I guess. I made two patches and used the interdiff utility, but it didn't recognize that files were moved and instead said "you deleted this one and created this one". Anyway, I didn't really do anything new in this patch, just moved things around, combined the hook_help, and fixed namespaces.

Anyway. This will not work yet. I need to figure out the schema install stuff, plus the other To Dos. Right now the tables will not be installed.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes
Status: Needs work » Needs review
Issue tags: +Needs tests
FileSize
47.65 KB
37.48 KB
32.48 KB
15.24 KB

OK!! Here's a new patch:
- I fixed up the cache stuff (I think?)
- I added the search block. I made it only visible on admin/help. Here is what it looks like:
Help page screenshot showing help search block

- All the code is now in the help_search module (see previous patch/comment). Database tables are installed/uninstalled as needed when help_search and search are installed/uninstalled.
- I changed the name of the interface to SearchableHelpInterface, and made it more generic. Now it should be sufficient for both core Search and (hypothetically) Search API contrib module, or anything else that wants to index search. There are now only 2 methods: one to list the IDs, and the other to render a given ID into title, body, etc. (with no dependency on the Search module per se in the code there) (and less code duplication than in the old system).
- It works!!
Search results output screenshot

We still need tests. Updating summary, which lists what tests are needed. But first lets see what I screwed up in the existing tests (hopefully not too much).

Another question (see summary): Do we want this search block visible on other admin pages besides the main admin/help page? I kind of think that is sufficient.

Status: Needs review » Needs work

The last submitted patch, 54: 2664830-54.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

andypost’s picture

Looking on placement of block after related issue I wonder if we can display search form via special help section with weight -100

jhodgdon’s picture

I don't think that is what we want to do. I put it into the Help region in the theme, with a weight that put it above the regular Help block. That region is all above the main page content. All the help sections would be in Main page content. So, I think it's correct as it is.

I'll take a look at fixing the test errors tomorrow, unless someone else wants to take the next steps. Basically, to get to Beta now on Help Topics, we just need to get this patch passing, and then write a lot of tests (on this issue and 2 others). I'll pick up this one or one of the other ones, doesn't matter too much to me. I should have some time to work on these issues for the next few days. Anyway, if someone else wants to take this one for a while, please assign it to yourself. Otherwise, I'll assign it to myself and work on it next.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

I'm going to work on this today: fixing the test results problems (failed tests and 1 coding standards message), and adding tests.

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
32.49 KB
1.75 KB

First step: this should fix the test fails and coder errors, hopefully.

andypost’s picture

While you on it, consider to use service - I could make it later (and unit test for service logic)

+++ b/core/modules/help_topics/help_topics.module
@@ -51,3 +54,88 @@ function help_topics_theme() {
+function help_topics_rebuild() {
...
+  if (\Drupal::moduleHandler()->moduleExists('search')) {
+    Drupal::state()->set('help_search_reindex_needed', TRUE);
...
+function help_topics_modules_installed(array $modules) {
...
+  if (!\Drupal::moduleHandler()->moduleExists('search') ||
+    \Drupal::database()->schema()->tableExists('help_search_items')) {
...
+function help_topics_modules_uninstalled(array $modules) {
...
+  if (!\Drupal::moduleHandler()->moduleExists('search') &&
+    \Drupal::database()->schema()->tableExists('help_search_items')) {

This hooks "content" needs new service to encapsulate

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes
Issue tags: -Needs tests
FileSize
45.46 KB
14.06 KB

Since I'm not sure what you mean by comment #60, I think I will leave that to you. :) It is probably easier to communicate what you mean in a patch.

Meanwhile, here is a functional test that verifies:
- Search works in English and translated when searching for help topics from the test module
- Search works when searching for the fake help items from the test help section, English and translated.
- Help view permissions are enforced when searching
- Search block is installed automatically when you enable Seven theme, and works

As a note, I added a new HelpSection plugin to the help_topics_test module, for testing purposes. It returns information independent of the Twig/Topic plugin system, so it's simpler for testing. It may be useful for unit testing as well? It's pretty simple.

Updated the issue summary regarding things that are done/working/tested.

Anyway, I'll let you take a go at the service/test you are talking about in #60.

jhodgdon’s picture

Issue summary: View changes

Whoever takes this next -- note there are 3 small coder problems on the last patch test results page. Other than that, tests pass.

One thing that I didn't test was the cache tags, so adding that to the summary. Also see #60.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

I'm going to go ahead and do the coder fixes and hopefully the cache test too, if I can get it done in next 2 hours.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes
FileSize
46.93 KB
3.74 KB
1.36 KB

Here's a patch that adds a test for cache tags, and fixes the coder errors. Sorry, 2 interdiffs -- I made one and then thought of something else.

The test is failing locally to find the cache tag for the user who is searching. I'm not sure why that is, because in HelpSearch::findResults(), we are doing:

    $this->addCacheableDependency($this->account);
    if (!$this->account->hasPermission('access administration pages')) {
      return NULL;
    }

It seems like this should work... but maybe the AccountInterface object is not enough for addCacheableDependency?

Anyway. I'm out of time for today.

Status: Needs review » Needs work

The last submitted patch, 64: 2664830-64.patch, failed testing. View results

jhodgdon’s picture

Assigned: Unassigned » jhodgdon
Status: Needs work » Needs review

I'm going to work on the cache stuff, got some hints from @dpl.

Also I think we want to restrict the search by language -- node search does this. So if you are currently viewing the admin page you searched from in German, you should only see German results. I noticed when looking at some of the HTML debug output in the tests that there were duplicated links, and I am pretty sure that is why. Anyway, assigning back to me... should have a new patch shortly.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Issue summary: View changes
FileSize
46.67 KB
7.42 KB

Here is my thought on caching (I'll add this to the issue summary too):
- We're really only concerned about when a search results page needs to be invalidated.
- The Search module already puts cache tags in based on the search index (which I have a test assert for already). So that means whenever the search index changes (i.e. for whatever reason we have decided to add, delete, or update an entry for a help item), the search results page cache is invalidated.
- Because of that, I don't think we need to worry about some of the caching stuff from the previous patch, such as making sure that each help section that might declare itself to be searchable has its plugin manager added to cache tags.
- We do also want to let each item in the results, as it renders itself, to declare cache stuff (which we're already doing in the previous patch).
- As per discussion with @dpl in slack, rather than adding a cache tag for the user who is searching, we should instead add a cache context for user.permissions.
- We should also restrict the search by the current language, and add a context for that. (languages:language_interface context)

As a note, I looked into changing the translation test base class in this patch into a trait, but I think it's better as an actual class that can be extended. This will allow the setUp() method to call parent::setUp(), which is a normal pattern, easily recognized... would be messier code to have a trait, alias that method, etc.

Anyway. Here's a new patch. The tests pass now, and I think the caching stuff is all good.

jhodgdon’s picture

FileSize
46.78 KB
955 bytes

I ran into a problem when trying to get Configurable Help to work (I had this patch installed). In one place we are trying to call search_index_clear() passing in a list of IDs. That is incorrect. Patch/interdiff...

jhodgdon’s picture

Issue summary: View changes
FileSize
49.33 KB
4.08 KB

I am very happy to report that since I got #3072353: Update Configurable Help module so it works with Core Help Topics finished, with the above patch on #68, the configurable help topics also appear in search along with the Twig-provided topics. Woot!

So... I think this is about ready for RTBC. I am still not sure what was meant on #60, but as far as I can tell, that is the only To Do left for this patch.

Oh, we need a hook_help update and a new help topic too. Here is a patch for that, and updating issue summary for that being done. Here's a new patch and interdiff.

andypost’s picture

On one hand - core can manage this table for us with hook_schema() so updates are easy to manage
On other hand if no search module installed is there reason to have this empty table in DB

Other then that it perfectly RTBC

+++ b/core/modules/help_topics/help_topics.module
@@ -51,3 +54,88 @@ function help_topics_theme() {
+function help_topics_modules_installed(array $modules) {
+  // If the search module and this module are both installed, make sure
+  // the help_search_items table is present.
+  if (!\Drupal::moduleHandler()->moduleExists('search') ||
+    \Drupal::database()->schema()->tableExists('help_search_items')) {
+    return;

I still not sure that we should manage schema this way instead of hook_schema, otherwise it is ready, if we wanna manage schema inside of service that's what about #60 was about

jhodgdon’s picture

Yeah, I don't know what the right answer is. I thought we should not have that table present unless both the help_topics and search modules are installed. But maybe it is not so terrible to just use hook_schema, and know that it will be empty unless search is installed. Do we have other examples of this situation in Core?

andypost’s picture

Yes, the example is \Drupal\Core\Menu\MenuTreeStorage::schemaDefinition() and other could be found with git grep ensureTableExists

jhodgdon’s picture

I looked at other implementations of hook_modules_installed and hook_modules_uninstalled in Core and there isn't anyone doing schema installs. But what I don't know is if any modules are declaring schema that only applies of a certain other module is installed.

andypost’s picture

Found related, that's why I'm confused between hook_schema and "on demand"

jhodgdon’s picture

Regarding #72, it looks like all the Core examples that have defined an ensureTableExists() function [there should be a trait! They just all defined them separately] are in core/lib/Drupal/Core. Meaning they are outside the control of any module, so they had to take care of their own schema outside of hook_schema(), and only if their class/service/etc. was being used. And they are all doing that "on-demand" schema.

This situation is a bit different, because we do have hook_schema available.

Probably really help_search should be its own module... then we wouldn't have this trouble. The module would just depend on Search and Help and voila.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon
Issue summary: View changes

OK, we discussed this on Slack and decided the best alternative was probably just to use hook_schema() and live with an empty table in the case that the Search module is not enabled. I'm making a new patch that does that.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
FileSize
48.13 KB
4.23 KB

OK, here's the patch (that was easy). I verified that the functional test HelpTopicsSearchPatch at least still passes, so it should be fine.

andypost’s picture

FileSize
7.48 KB
48.23 KB

Did a bit of clean-up for CS and method should check only for one interface - only its 2 methods used (listSearchableTopics and renderTopicForSearch)

I did not dig tests a lot but why translation removed? Otherwise it RTBC for me

+++ b/core/modules/help_topics/tests/src/Functional/HelpTopicTranslationTest.php
@@ -66,32 +38,4 @@ public function testHelpTopicTranslations() {
-  protected function installParameters() {
-    $parameters = parent::installParameters();
-    // Install in German. This will ensure the language and locale modules are
-    // installed.
-    $parameters['parameters']['langcode'] = 'de';
-    // Create a po file so we don't attempt to download one from
-    // localize.drupal.org and to have a test translation that will not change.
-    \Drupal::service('file_system')->mkdir($this->publicFilesDirectory . '/translations', NULL, TRUE);
-    $contents = <<<ENDPO
-msgid ""
-msgstr ""
-
-msgid "ABC Help Test module"
-msgstr "ABC-Hilfetestmodul"

No idea why this part removed, I mean translations import...

jhodgdon’s picture

It was put into the HelpTopicTranslationTestBase class, not removed. :)

jhodgdon’s picture

Interdiff looks fine to me if test bot agrees. Thanks!

Status: Needs review » Needs work

The last submitted patch, 78: 2664830-78.patch, failed testing. View results

andypost’s picture

Status: Needs work » Needs review
FileSize
591 bytes
48.23 KB

Interesting how it passed before

andypost’s picture

FileSize
1.3 KB
48.23 KB

sorted usage

jhodgdon’s picture

This is all looking good to me. +1 for RTBC. Thanks!

jhodgdon’s picture

I added a change record for this; putting the release notes into the issue summary here too.

jhodgdon’s picture

FileSize
48.23 KB
7.48 KB
48.23 KB
591 bytes

Ugh. Somehow I managed to delete a bunch of files from this issue. They are all still in the file system, but unlinked from the issue. So, I'm re-uploading the files I messed up. I think I must have had a stale form... sorry about that!

Actually I will do them in 2 comments so that the latest patch is alone for testing. Anyway here are the oldest ones first. These are all andypost's patches.

jhodgdon’s picture

FileSize
48.23 KB
1.3 KB

And here's the latest one, also from andypost. I'll try to cancel the other two tests that just launched from the previous comment.

pwolanin’s picture

Quick first look - seems like a bunch of the code is duplicated from node search. Not sure if that's avoidable.

jhodgdon’s picture

I do not think much is a direct duplicate. Some code was certainly adapted, but the queries and results are different, plus rendering is different. I definitely didn't copy/paste very much without changing it...

What did you see that you thought we could maybe avoid duplicating? Because we could move stuff to a base class... but I don't think there is much if anything.

pwolanin’s picture

Overall this looks good.

Confused by deleting from the DB in batches of 100 in protected function removeItemsFromIndex()

jhodgdon’s picture

I've run into limits with how many placeholders a query can have in the past. This query uses

->condition('sid', $this_list, 'IN')

so each SID that is being deleted will have a placeholder in the query, because we're deleting specific item(s) from the table.

I didn't run into the placeholder limit in this case in my testing, but knowing I've run into it before, my general practice in situations where I'm doing an insert, update, or delete query that uses placeholders that grow as the size of what is being inserted/updated/deleted, is to chunk it. 100 could be overly cautious, but it should prevent problems down the line. I think in most circumstances, there will only be a few items deleted (e.g. because someone removed a module/theme that had a few topics that had been indexed and don't exist any more), so we won't run into the chunking very often.

pwolanin’s picture

Status: Needs review » Reviewed & tested by the community

Ok, I realized last night that's probably why.

I think this is good to go forward.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 87: 2664830-83.patch, failed testing. View results

jhodgdon’s picture

Status: Needs work » Needs review

Hm. Something has changed recently in rendering, and we're getting a strange test error about body not being a valid render key:

Drupal\Tests\help_topics\Functional\HelpTopicSearchTest::testHelpSearch
Exception: User error: "body" is an invalid render array key
Drupal\Core\Render\Element::children()() (Line: 97)

Going to hit retest just in case. If it still fails, I will investigate today or tomorrow.

Status: Needs review » Needs work

The last submitted patch, 87: 2664830-83.patch, failed testing. View results

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
762 bytes
48.97 KB

Ah. What happened is that on #3072519: Help Topics discovery cannot be decorated easily we added some YAML-based topics and a TestHelpTopicPlugin class. That class's getBody() method should have been returning a render array, but was instead returning as string. So in the test for searching, we were trying to render/index these new topics from the test class, and they wouldn't render (which we never tested in the other patch that added these fake topics -- they were added to test *discovery* but we didn't actually look at the topic pages).

So. Here's a patch that fixes this method on the test class, so that the fake YAML-based topics can now be search indexed in the test. This is a pretty trivial interdiff (4-line change), and I don't think it really affects the earlier RTBC... but I'll let someone else (and the bot) review the interdiff and new patch.

andypost’s picture

No need for #type in case of markup, otoh it makes be think about to return renderable

jhodgdon’s picture

Yeah, I usually prefer to put #type => 'markup' in. For one thing, on api.drupal.org, it links to the Markup element class. For another thing, for people new to the render element stuff in Drupal, I think it's clearer to always have a #type instead of having this exception.

jhodgdon’s picture

Status: Needs review » Needs work

So... Do you want me to remove the #type -> 'markup', or is this patch OK to get back to RTBC?

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
49.4 KB
1.41 KB

I just remembered that the other classes within core/modules/help_topics are listed as @internal for now. Adding that to the docs header of the new classes in this patch. See interdiff. (I copy/pasted the @internal notation from the other classes that already have it. They all have the same typo about "Help Topic" being internal. Oh well. At least they are consistent.)

Since anything under tests/ is automatically internal, I didn't add that notation there.

Status: Needs review » Needs work

The last submitted patch, 100: 2664830-100.patch, failed testing. View results

jhodgdon’s picture

Status: Needs work » Needs review

That fail was totally unrelated to this patch. Hitting retest. I'll check and see if I need to file a random test fail issue or if it's a rare glitch or already known or what.

jhodgdon’s picture

andypost’s picture

Status: Needs review » Reviewed & tested by the community

Awesome idea with internal!

jhodgdon Sorry for misread interface

+++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpTopic/TestHelpTopicPlugin.php
@@ -14,7 +14,10 @@ class TestHelpTopicPlugin extends HelpTopicPluginBase {
   public function getBody() {
...
+    return [
+      '#type' => 'markup',
+      '#markup' => $this->pluginDefinition['body'],

Let's keep it with @type, \Drupal\help_topics\HelpTopicPluginInterface::getBody() require render array

larowlan’s picture

Status: Reviewed & tested by the community » Needs work

This is great work - some observations below

  1. +++ b/core/modules/help_topics/help_topics/help_topics.help_topic_search.html.twig
    @@ -0,0 +1,17 @@
    +  <li>{% trans %}From the <em>Extend</em> administrative page (<em>admin/modules</em>), verify that the Search, Help, Help Topics, and Block modules are installed (or install them if they are not already installed).{% endtrans %}</li>
    

    and help_topics just paid for itself

    (sorry for the cheesy Australian cultural references)

  2. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -57,10 +84,19 @@ class HelpTopicSection extends HelpSectionPluginBase implements ContainerFactory
    -  public function __construct(array $configuration, $plugin_id, $plugin_definition, HelpTopicPluginManagerInterface $plugin_manager) {
    +  public function __construct(array $configuration, $plugin_id, $plugin_definition, HelpTopicPluginManagerInterface $plugin_manager, RendererInterface $renderer, LanguageDefault $default_language, LanguageManagerInterface $language_manager) {
    

    this hasn't been in a tagged release, is internal and in an experimental module, so on that basis, no need for a BC layer ✅

  3. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -138,6 +177,66 @@ protected function getPlugins() {
    +    $definitions = $this->pluginManager->getDefinitions();
    +    $ids = [];
    +    foreach ($definitions as $definition) {
    +      $ids[] = $definition['id'];
    +    }
    +    return $ids;
    

    this could be rewritten as

    return array_column($this->pluginManager->getDefinitions(), 'id');
    
  4. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -138,6 +177,66 @@ protected function getPlugins() {
    +    // then reset the default language so we do not screw up other cron things.
    

    I'm not sure 'screw up' meets the normal standards for non-test code. Suggest 'interfere with' instead?

  5. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -138,6 +177,66 @@ protected function getPlugins() {
    +    $old_language = $this->defaultLanguage->get();
    +    $this->defaultLanguage->set($language);
    +    $topic = [];
    

    On the basis that this could cause exceptions that would bubble up to core's Pokemon exception handling (gotta catch em all) in cron without returning the language to the previous value, I think we should wrap this whole block in a try/catch with a finally block that resets the language back to the previous value, thus ensuring that if things go sideways, we still clean up after ourselves

  6. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -138,6 +177,66 @@ protected function getPlugins() {
    +    $context = new RenderContext();
    +    $build = [
    +      'body' => $this->renderer->executeInRenderContext($context, [$plugin, 'getBody']),
    +    ];
    +    $topic['text'] = $this->renderer->renderPlain($build);
    +    $cache_meta->addCacheableDependency(CacheableMetadata::createFromRenderArray($build));
    +    $cache_meta->addCacheableDependency($plugin);
    

    I think we're missing a $context->pop() here, which would yield a new cacheable metadata object that we'd merge with the existing $cache_meta

    But I'm not 100% - perhaps ping @WimLeers for a review to make sure we're doing this bit right. edit: I've pinged Wim

  7. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -138,6 +177,66 @@ protected function getPlugins() {
    +    // Reset the language.
    +    $this->defaultLanguage->set($old_language);
    

    this would go into the finally block with the try/catch approach from above

  8. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +    // could get returned by search. Each entry in the help_search_items
    

    nit: could be instead of could get?

  9. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +      foreach ($permissions as $permission) {
    +        if ($permission && !$this->account->hasPermission($permission)) {
    

    calling array_filter on the permissions could avoid the need for the if ($permission

  10. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +      $result = $this->database->select('help_search_items', 'hsi')
    +        ->condition('sid', $item->sid)
    +        ->fields('hsi', ['plugin_type', 'id'])
    +        ->execute();
    

    could these be fetched in a single query using fetchAssoc outside the foreach for performance sake?

  11. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +      foreach ($result as $record) {
    +        $type = $record->plugin_type;
    

    this implies more than one result for a given plugin - is that the case or just the fact that the query is multi-return? We'd only ever be showing in the one language right?

    Would it be simplified if we used fetchRow or something?

  12. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +          $language = $this->languageManager->getLanguage($item->langcode);
    

    we limit the query above to a single language (in findResults) so could this be put outside the foreach for performance?

  13. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +              'snippet' => search_excerpt($keys, $topic['title'] . ' ' . $topic['text'], $item->langcode),
    ...
    +      search_index_clear($this->getPluginId(), $item->sid);
    ...
    +          search_index($this->getPluginId(), $item->sid, $langcode, $text);
    ...
    +    search_index_clear($this->getPluginId());
    

    such a shame there's no OO equivalent of these - follow up?

  14. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +    if (count($items) < $limit) {
    +      $query = $this->database->select('help_search_items', 'hsi');
    +      $query->fields('hsi', ['sid', 'plugin_type', 'id']);
    

    I think this is indexing items that have been indexed before but are stale - could we add a comment to make it clear?

  15. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +      if ($item->sid > $max_sid) {
    +        $max_sid = $item->sid;
    +      }
    

    this could be written as

    $max_sid = max($item->sid, $max_sid);
  16. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +      $permission = (isset($plugin_definition['permission']) ? $plugin_definition['permission'] : '');
    

    we can use null coalesce operator (??) here

  17. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +            unset($sids_to_remove[$old_item->sid]);
    

    we could add a continue after this line and avoid the else to improve readability.

  18. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +            $this->database->update('help_search_items')
    

    same here for the outer else

  19. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +              'sid' => $max_sid + 1,
    

    any reason we don't make this column a SERIAL and just let the DB handle sequencing? we can use the return from ->insert() to get the inserted ID.

  20. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +    if (!is_array($sids)) {
    +      $sids = [$sids];
    +    }
    

    this can be written as $sids = (array) $sids, i.e a cast operation

  21. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +    for ($start = 0; $start < count($sids); $start += 100) {
    

    is there a reason we use batches here, the DB should handle bulk deletes fairly easily?

    if there is, any reason not to use array_chunk instead, e.g.

    foreach (array_chunk($sids, 100) as $this_list) {
    }
    
  22. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,508 @@
    +      search_index_clear($this->getPluginId(), $sid);
    

    such a shame there is no bulk-operation for this - follow-up?

  23. +++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpSection/TestHelpSection.php
    @@ -0,0 +1,86 @@
    +        else {
    ...
    +        else {
    

    no need for the else here, we return above

  24. +++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpSection/TestHelpSection.php
    @@ -0,0 +1,86 @@
    +        trigger_error('Unexpected ID encountered', E_USER_ERROR);
    

    I think we should throw \InvalidArgumentException here as that's what's going on

  25. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,148 @@
    +    $this->drupalPostForm('admin/config/development/performance', [], 'Clear all caches');
    

    Why is this needed?
    If it is, we can use the $this->resetAll() instead of doing it over HTTP

  26. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,148 @@
    +    $this->drupalPostForm('admin/config/system/cron', [], 'Run cron');
    

    similarly, we can use a while loop here with CronRunTrait and $this->cronRun() - where the while loop checks that items remaining is zero.

    choosing 4 cron runs arbitrarily makes the test brittle, using while makes it more robust

  27. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,148 @@
    +    $session->addressMatches('|search/help|');
    

    nice, hadn't seen this method before 🤓

  28. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,148 @@
    +    $session->responseHeaderContains('X-Drupal-Cache-Tags', 'config:search.page.help_search');
    +    $session->responseHeaderContains('X-Drupal-Cache-Tags', 'search_index:help_search');
    

    ❤️

  29. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,148 @@
    +    $session->linkNotExists('Foo in English title');
    

    😎

  30. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,148 @@
    +    $links = $this->xpath('//a[contains(text(), :label)]',
    +      [':label' => $label]);
    

    this could use $this->getSession()->getPage()->findAll('named', ['link', $label])

Berdir’s picture

+++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
@@ -138,6 +177,66 @@ protected function getPlugins() {
+
+    // Render the body in this language. For this, we need to set up a render
+    // context, because the Twig plugins that provide the body assume one
+    // is present.
+    $context = new RenderContext();
+    $build = [
+      'body' => $this->renderer->executeInRenderContext($context, [$plugin, 'getBody']),
+    ];
+    $topic['text'] = $this->renderer->renderPlain($build);
+    $cache_meta->addCacheableDependency(CacheableMetadata::createFromRenderArray($build));
+    $cache_meta->addCacheableDependency($plugin);
+
+    // Add the other information.
+    $topic['url'] = $plugin->toUrl();
+    $topic['cache_dependency'] = $cache_meta;
+

I'm not Wim, but commenting anyway ;)

Yeah, since you actually collect the cacheable metadata, I think you want to add something like this:$cache_meta->addCacheableDependency($context->pop());

But not 100% sure, also not if there always is a frame on the render context, it does assert that there isn't more than one, but maybe if nothing actually gets rendered..?

Another option would be to call getBody() through a #pre_render() callback, then it takes care of everything for you.

That's a bit like entities are rendered too, $view_builder->view($node) actually just sets up a minimal render array with meta info and a #pre_render callback. In your case, you'd set #pre_render + maybe #plugin_id or so. Then you'd do all inside renderPlain().

Another note, I find the naming a bit confusing.

It's cache_dependency, but stores on CacheableMetadata object, which is not what the documention of the method says. Yes, it could could hold a different object hat implements CacheableDependency but we know the implementation really has multiple things to consider.

Also, $cache_meta doesn't exist once as a variable name in core, although we use many different names for it, often $(something_)cacheability or $cache(able)_metadata. Although CacheableMetadata::createFromObject() uses just $meta. So pretty inconsistent, but lets not add a new version to the mix. Maybe cacheable_metadata for both key and variable?

What I I also find somewhat confusing is how searching and indexing goes through the same method. Does anything actually happen with cacheablity metadata while indexing? Does display-as-search-result actually need to support rendering in a different language? As far as I see, the answer to both is no. findResults() hardcodes the langcode to the current language and then selects it again, updateIndex() just uses title + text. If we'd have two methods, then render-for-output wouldn't need to care about renderPlain() and render contexts, it could maybe just render a return array. And the other way round, indexing wouldn't have to worry about returning cacheable metadata, it could just drop it.

I'm also surprised that different-language indexing like that works. We just change the *default* language? But \Drupal\language\ConfigurableLanguageManager::getCurrentLanguage() statically caches the result of language negotiation?

Wim Leers’s picture

#106: thanks :D

What you want is

        if (!$context->isEmpty()) {
          …->addCacheableDependency($context->pop());
        }

(which is mostly what @Berdir wrote in #106, but that also answers his also not if there always is a frame on the render context question.)

This already happens in a few places, for example:

  • \Drupal\jsonapi\Controller\EntityResource::executeQueryInRenderContext()
  • \Drupal\rest\EventSubscriber\ResourceResponseSubscriber::renderResponseBody()
  • \Drupal\views_ui\Form\Ajax\ViewsFormBase::ajaxFormWrapper()
  • \Drupal\views\Controller\ViewAjaxController::ajaxView()

I didn't dig into the code in detail since @Berdir already did that! 👏

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
17.19 KB
50.01 KB

Thanks for the reviews and suggestions! Some of the items in the last 3 comments were more "observations" than suggestions for changes. So... Here are some notes... Responding to comment #105 first:

3 / 4 / 5 / 7 / 8 / 9 / 11 / 14 / 15 / 16 / 17 / 18 / 19 / 20 / 23 / 24 / 26 : Made suggested minor improvements to the code/comments. Hopefully changes are clear and what was desired.

6 [will be addressing that later]

10 : I am not sure about this. The code in question is:

  protected function prepareResults(StatementInterface $found) {
...
    foreach ($found as $item) {
      $result = $this->database->select('help_search_items', 'hsi')
        ->condition('sid', $item->sid)
        ->fields('hsi', ['plugin_type', 'id'])
        ->execute();
...

And the suggestion is to do this database query outside of the foreach. ...

So... The problem is that I would still (I think?) need to do some kind of a foreach on $found to extract the IDs from the StatementInterface object (it's not really an array), so that I could extract the plugin types and IDs in a single select. Then the code in the foreach would also be a bit harder to understand... So I would replace having 1 foreach loop and let's say about 10 queries, and fairly clear code, with having 2 foreach loops and 1 query with an IN condition in it, and I think harder-to-understand code. Is that really a lot more efficient? I'm not convinced it's worth it. I have left this as it is for now.

12 : Hmm... well the langcode gets to this function inside $item->langcode... anyway I changed the code so we are only getting the language once and saving it in an array, like the code was already doing for plugins around there.

13 / 22 : Yeah, both of these could be follow-ups. The search API has been stable in Core since about 8.0.

21 : I discussed this with pwolanin above in comments 90 and 91. I did replace the code with array_chunk though, and updated the comment to explain why I did it that way.

25 : I tried replacing this line in the test

    $this->drupalPostForm('admin/config/development/performance', [], 'Clear all caches');

with using $this->resetAll(). However, the test failed on this line because it got a 404 error visiting the page:

    $this->drupalPostForm('de/search/help', ['keys' => 'foomm'], 'Search');

So, I put it back the way it was, with a comment as to why it is necessary. This is not a crucial thing to debug and figure out the difference between going to that page vs. calling the resetAll() method, I think? If so, it's probably a follow-up? Suffice it to say the test does not pass (at least locally) unless it is done this way.

30. Well, maybe? I am way more familiar with xpath than with this "named" Behat thing. I did not make this change, because I couldn't quickly find documentation on what "named" does, or why putting "link" in there would work.

OK, back to item 6 from comment 105, and comments 106/107:
- I added the context pop thing to the cache thing.
- I changed the name of the key/variable as suggested in #106.

And one more thing from comment #106 -- regarding having one method for both indexing and results:
I started out with two methods, but there were very few differences between the two. I felt that rather than duplicating a *lot* of code, the most sensible thing to do was just to have a single method that HelpSection plugins wanting to make their items searchable would need to implement (see SearchableHelpInterface in this patch) in order to render an item.

You're right that we don't use the cache meta-data when indexing, so that is a bit of extra overhead during cron runs. But the language could be important for some types of help items that could be displayed in search results. For instance, look at what NodeSearch does when it is displaying search results -- you need the language code in order to render the item at that time.

I can split them up, but really I think it's a lot simpler this way. Let me know if you want to insist.

Anyway... Phew! Here's a new patch and interdiff. The tests still pass locally.

jhodgdon’s picture

FileSize
1.1 KB
50.01 KB

3 whitespace coding standards messages from that last test. Apparently coder wants the break statements indented the same as the case statements in a switch. ?!? Anyway. Whatever.

andypost’s picture

When language changed the whole container got rebuild in child site, that's why call $plugin = HelpSearch::create($this->container, [], 'foo', []); will fail - this container no longer valid

jhodgdon’s picture

OK... so what do you think we need to do for this patch?

andypost’s picture

Can't check right now, surely it needs follow-up to debug and fix
Meantime changes addressing all feedback, just not sure break makes sense after thrown exception in TestHelpSection::renderTopicForSearch() (sorry, trying to review ftom phone)

+      default:
+        throw new \InvalidArgumentException('Unexpected ID encountered');
+      break;
jhodgdon’s picture

FileSize
1.05 KB
49.92 KB

OK. Well regarding #110, the way the test is set up currently, it is working... I guess we could debug it further but it's not all that relevant for the test or this patch?

Regarding breaks, yeah I guess after a return or a throw we do not need a break. I will fix that. Here's a new patch. Hopefully Coder doesn't require break statements.

andypost’s picture

FileSize
9.24 KB
50.38 KB

Patch rework language related tests and fix minor nits

When test needs to visit language related page the language => $language_object should be used.
As English set default after install I added $german where it needed.

I replaced assertLinkExistsOnce() with assertSearchResultsCount() because test should count results and then check titles

$session->addressMatches() removed because assertSearchResultsCount() using CSS selector specific to help search

andypost’s picture

+++ b/core/modules/help_topics/tests/modules/help_topics_test/src/Plugin/HelpSection/TestHelpSection.php
@@ -53,7 +53,7 @@ public function renderTopicForSearch($id, LanguageInterface $language) {
-          'text' => 'Fake foreign foomm text',
+          'text' => 'Fake foreign foo text',

and this change is needed to test for case when searching for "foo" in English

jhodgdon’s picture

I don't agree with this change in TestHelpSection:

-          'text' => 'Fake foreign foomm text',
+          'text' => 'Fake foreign foo text',

We should have different text in English and German so we can verify the search indexing works correctly in both languages, and the correct text is stored in the index and searched for in both languages.

Then I also don't agree with this change:

-    $this->drupalPostForm('de/search/help', ['keys' => 'foomm'], 'Search');

+    $this->drupalPostForm('search/help', ['keys' => 'foo'], 'Search', [
+      'language' => $german,
+    ]);

We need to verify that the correct text is getting into the search index for the correct language. So we should be putting the word "foomm" in for German, and then testing that you can search for "foomm" in German. The test as it is currently written doesn't really verify that the translations are handled correctly in search indexing, because you are searching for "foo" and only verifying that the title on the search result page shows the German title.

I also don't think this is a good practice in the test:

     $this->drupalPostForm('search/help', ['keys' => 'ABC'], 'Search');
-    $session->addressMatches('|search/help|');
-    $this->assertLinkExistsOnce('ABC Help Test module');
+    $this->assertSearchResultsCount(2);
+    $this->assertLink('Writing good help');

The problem here is, we *currently* have a topic called "Writing good help". But this is a production topic, not a topic from a test module, so if that topic's title or content changes, the test will fail. We should only search for test topic text and test topic results. Hm... Actually this whole thing looks wrong? Why is "Writing good help" coming up in a search for ABC at all???

andypost’s picture

@jhodgdon I also wondered to see "writing good help" when started to check browser's output for en/de URLs

The reason to change foomm to foo in text was to make sure that same text in en/de is not mixed in results page (same text in body is indexed in different languages and returning only in requested language).

Probably it needs to make texts more unique to have no collision with existing topics... But have no idea how to prevent it in future when more topics will be added to core.

jhodgdon’s picture

Probably using nonsense words as the search terms and the title/body text is the best way to make sure that we do not get real topics in the results.

So it seems like we need to verify:
a) When we search in English for a word that is in the English text, we get the correct English results.
b) When we search in English for a word that is in the German but not English text, we get no results.
c) When we search in English for a word that is in both English and German texts, we get only English results. It would be best to search for a word that is in one topic in English and in a different topic in German, and verify we only get the right topic, and it is in English.
d) Same as above a-c but with English/German reversed.
e) Searching works both with Help Topics and with the test fake Help Section plugin.

So... What we currently have for topics in the test module + current patch is:
1. A topic called "ABC Help Test Module" with body (partial) saying "Test translation", with fake German translation of these two strings as "ABC-Hilfetestmodul" and "Übersetzung testen.". This topic is in tests/modules/help_topics_test/help_topics, and the translation comes from HelpTopicTranslatedTestBase [we could add more translations to that test pretty easily].

2. Two other topics "Additional topic" and "Linked topic", plus the rest of the "ABC Help Test Module" that do not have translations for their strings. These are in the same directory.

3. A deriver class TestHelpTopicDeriver that creates a topic with title "Label for help_topics_derivatives" and body "Body for help_topics_derivatives". No translations.

4. A topic defined in the help_topics_test.help_topics.yml file with label "Test direct yaml topic label" and body "Test direct yaml body". No translations.

5. Two topics from the TestHelpSection with English titles "Foo in English title" and "Bar in English title", and German text that we're discussing what it should be in the previous two comments. Both the text and translations are provided in that class.

So.... I think we have enough topics and translations to test all of the above if we are careful, and we should avoid using real words as search terms that might pull in real topics. So, we might want to rewrite some of the topic text so it is full of enough fake words that we can do all the tests.

You are probably/hopefully asleep (??) so I will make a new patch in a few minutes...

jhodgdon’s picture

FileSize
11.03 KB
53.32 KB

I have to run out but here's a test that does all of that... it is not passing yet, and I am not sure why... I can look at it again tomorrow or you can andypost...

Status: Needs review » Needs work

The last submitted patch, 119: 2664830-119.patch, failed testing. View results

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
53.62 KB
4.29 KB

As far as I can tell, this is a real bug, either in the test or in the code. When I debugged this test, I could see that during the cron runs,although we asked for the DE translation of the test topic, the text from the Twig template was not actually translated using the PO file that is in the translation test.

Something is going wrong... I'm not sure what. The HelpTopicTranslationTest, which uses the same PO file and the same topic file, works -- you can see it in the browser output if you run interactively -- the test topic is definitely translated there. But not when we try to render it for searching. So, something is definitely wrong with either the test environment or the code that is rendering topics in different languages to send to the search index.

Meanwhile, here is an updated patch with a test that we need to get to work. I think this is now a very thorough test, but it isn't passing, so...

Status: Needs review » Needs work

The last submitted patch, 121: 2664830-121.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Berdir’s picture

> So, something is definitely wrong with either the test environment or the code that is rendering topics in different languages to send to the search index.

Isn't that what I said in #106? :)

> I'm also surprised that different-language indexing like that works. We just change the *default* language? But \Drupal\language\ConfigurableLanguageManager::getCurrentLanguage() statically caches the result of language negotiation?

You need to set the current language instead.

I wasn't even sure that we have a way to do that. We do have \Drupal\Core\Language\LanguageManagerInterface::setConfigOverrideLanguage(), which is for example used in user_mail(), but that doesn't actually cover string/interface translation. That does seem to be set in \Drupal\language\EventSubscriber\LanguageRequestSubscriber::setLanguageOverrides() for example ($this->translation->setDefaultLangcode($langcode);), but that method isn't actually on an interface. It is on \Drupal\Core\StringTranslation\TranslationManager::setDefaultLangcode, and we obviously expect it to exist. Fun stuff...

jhodgdon’s picture

Assigned: Unassigned » jhodgdon
Status: Needs work » Needs review

Obviously, I didn't get what you said in #106. Thanks for posting it again. :) I'll see what I can do...

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
FileSize
3.65 KB
54.54 KB

Thanks again for your help @Berdir -- that did it. Test now passes locally.

After I made this interdiff, I removed a blank line in the test file that Coder complained about in the previous test also. Hope that is OK not to get it into the interdiff.

I wasn't sure if it was OK to specify a class passed into the constructor for the TranslationManager, but as pointed out by Berdir, the method we need to call isn't on an interface...

jhodgdon’s picture

Oh, and I figured out why the "Writing good help" topic showed up in the earlier tests when we searched for "ABC". The reason is that the test topic adds the Writing good help topic as Related, which makes the title of the test topic show up on the Writing good help topic page. So when it is indexed for searching, the text "ABC" is included.

We probably shouldn't do that. I think we should refactor the help topic tests so that the tests are not using production topics at all, and the test topics don't link to production topics. Created an issue for that. Meanwhile, these new tests are only relying on the test topics and test classes.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
jhodgdon’s picture

Can we get this to RTBC? I don't think we should postpone it on #3075695: Move search_index() and related functions to a service, which should not be a prerequisite to getting this done, as it affects NodeSearch too. Thoughts?

larowlan’s picture

I don't think we should postpone it on #3075695: Move search_index() and related functions to a service

100% agree

andypost’s picture

Status: Needs review » Reviewed & tested by the community

I think it really for commiter's eyes and surely tests get changes in future.
The scope of issue to add search which could be polished endlessly. Current tests coverage is enough.

jhodgdon’s picture

FileSize
54.53 KB

Reroll of previous patch due to #3076348: Refactor help topic tests so they do not use production topics. Only change was in the context of one of the help topics in the test module. Here's a straight diff of the two patch files for reference:

947c947
< index 2e76aa0cb3..43b95d91a9 100644
---
> index eaf23b14d2..40b7cc9228 100644
952,953c952,953
<  {% set help_topic_url = render_var(url('help_topics.help_topic', {id: 'help_topics.help_topic_writing'})) %}
<  <p>{% trans %}This is a test. It should <a href="{{ help_topic_url }}">link to the writing good help topic</a>. Also there should be a related topic link below to the Help module topic page and the linked topic.{% endtrans %}</p>
---
>  {% set help_topic_url = render_var(url('help_topics.help_topic', {id: 'help_topics_test.additional'})) %}
>  <p>{% trans %}This is a test. It should <a href="{{ help_topic_url }}">link to the additional topic</a>. Also there should be a related topic link below to the Help module topic page and the linked topic.{% endtrans %}</p>
larowlan’s picture

Status: Reviewed & tested by the community » Needs review

This is looking great, and its exciting to see it nearly ready and the amazing progress help_topics has made in this cycle 🥇
Couple of observations.

  1. +++ b/core/modules/help_topics/config/schema/help_topics.schema.yml
    @@ -0,0 +1,3 @@
    +search.plugin.help_search:
    +  type: sequence
    +  label: 'Help search'
    

    is this needed? It doesn't look like we have any config to go with this plugin - the node one does, but I don't see any here?

  2. +++ b/core/modules/help_topics/help_topics.module
    @@ -27,6 +29,8 @@ function help_topics_help($route_name, RouteMatchInterface $route_match) {
    +      $output .= '<dd>' . t('To search help, you will need to install the core Search module, configure a search page, and add a search block to the Help page or another administrative page. (A search page is provided automatically, and if you use the core Seven administrative theme, a help search block is shown on the main Help page.) Then users with search permissions, and permission to view help, will be able to search help. See the <a href=":search_help">Search module help page</a> for more information.', [':search_help' => $search_help]) . '</dd>';
    

    nit: is the comma required after permissions in this sentence?

    Then users with search permissions, and permission to view help
  3. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -137,6 +191,79 @@ protected function getPlugins() {
    +      // context, because the Twig plugins that provide the body assume one
    

    nit: assumes not assume?

  4. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,503 @@
    +      ->groupBy('hsi.permission')
    ...
    +      $query->condition('hsi.permission', $denied_permissions, 'NOT IN');
    

    We don't have an index on this column in the table schema. Should we be adding one given we're using it in a groupBy and a where clause?

  5. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,503 @@
    +    foreach ($permissions as $permission) {
    +      if (!$this->account->hasPermission($permission)) {
    +        $denied_permissions[] = $permission;
    +      }
    +    }
    

    we could do this inside the existing array_filter with a custom callback.

    array_filter(/* ... existing code for query */, function ($permission) {
    return $permission && !$this->account->hasPermission($permission);
    });
    
  6. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,503 @@
    +      $record = $this->database->select('help_search_items', 'hsi')
    +        ->condition('sid', $item->sid)
    +        ->fields('hsi', ['plugin_type', 'id'])
    +        ->execute()->fetchObject();
    

    this is executing this for every $item in $found performant, can we fetch the full result set in one query for all items in found? Looking back at earlier comments, I asked for this before and you replied

    The problem is that I would still (I think?) need to do some kind of a foreach on $found to extract the IDs from the StatementInterface object (it's not really an array), so that I could extract the plugin types and IDs in a single select. Then the code in the foreach would also be a bit harder to understand... So I would replace having 1 foreach loop and let's say about 10 queries, and fairly clear code, with having 2 foreach loops and 1 query with an IN condition in it, and I think harder-to-understand code. Is that really a lot more efficient? I'm not convinced it's worth it. I have left this as it is for now.

    From what I can gather, the query is just to retrieve the plugin ID and type for each sid - so we could conceivably replace it with something like this outside the foreach loop.

    $sids = array_unique(array_map(function ($item) { return $item->sid;{, iterator_to_array($found))), 
    $pluginInfo = $this->database->select('help_search_items', 'hsi')
            ->condition('sid', $sids, 'IN')
            ->fields('hsi', ['sid', 'plugin_type', 'id'])
            ->execute();
    
    

    and then loop over that or call ->fetchAllAssoc('sid'); on it if we need it keyed by sid
    Then we'd have plugin_type and ID available in an array keyed by sid. I think 🤔

  7. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,503 @@
    +        $plugins[$type] = $this->getSectionPlugin($type);
    

    is it possible that the search results table in the database could contain references to help topic plugins that no longer exist (e.g. are from modules that have since been uninstalled, or in the case of the contrib module - have been deleted? If so, we should wrap either this, or the code inside ::getSectionPlugin in a try/catch that catches a PluginException. The same applies to the call to ::getSectionPlugin from within ::updateIndex

  8. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,503 @@
    +            $this->addCacheableDependency($topic['cacheable_metadata']);
    

    should we put an assert call here to ensure that $topic['cacheable_metadata'] is an instance of an appropriate class for this method call/typehint?

  9. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,503 @@
    +    if ($this->state->get('help_search_reindex_needed', TRUE)) {
    +      $this->markForReindex();
    +    }
    

    is there an alternate code path here when the state flag is not set where we do nothing and return early?

  10. +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
    @@ -0,0 +1,206 @@
    +    $session->responseHeaderContains('X-Drupal-Cache-Tags', 'config:search.page.help_search');
    +    $session->responseHeaderContains('X-Drupal-Cache-Tags', 'search_index:help_search');
    +    $session->responseHeaderContains('X-Drupal-Cache-Contexts', 'user.permissions');
    +    $session->responseHeaderContains('X-Drupal-Cache-Contexts', 'languages:language_interface');
    

    should we use \Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait here?

larowlan’s picture

Manual testing:

After installing standard profile and then enabling help_topics, I was unable to search for any content until I ran 'reindex site' from admin/config/search/pages - does that mean we're missing a hook_install or similar to prime the database when search module is enabled?

After doing that and then running cron, got search results.

Looks great 🎉

andypost’s picture

Feedback on #133 - I bet running indexing on install is bad idea, moreover that's a search module territory so install of help topic module should not check that search installed and try to re-index

Feedback on #132

1. it is required because each search plugin use own settings, which require schema https://git.drupalcode.org/project/drupal/blob/8.8.x/core/modules/search...

2-3 fixed 3 and no sure about 2

4. not sure because I expect that amount of topics will not be huge, and I can't see difference on "tens of records"
5. fixed
6... TBD will try to check tomorrow

jhodgdon’s picture

RE #133 -- we are using the core Search module.

What should happens is that after enabling all the modules, you would need to (a) do a cache clear and (b) run cron. Then you should be able to search. Otherwise nothing is in the index. That is just how the Search module works.

I'm out of Drupal-land until probably November. Sorry...

oknate’s picture

I'm not 100% familiar with this new functionality, but I tested it out AFAIK and it works. I was able to run cron and then search for the help topics.

Notes:
1. It's not clear why search isn't working before cron has been run. If there are no indexed items, it would be good to display a warning that search items haven't been indexed. This could be follow-up material, so as not to block this issue.
2. There's an assertion for counting elements:

-    $this->assertEqual($count, count($this->cssSelect('.help_search-results > li')));
+    $this->assertSession()->elementsCount('css', '.help_search-results > li', $count);

3. $this->assertLink() is deprecated, should be replaced with $session->linkExists().

-    $this->assertLink('ABC Help Test module');
+    $session->linkExists('ABC Help Test module');

4. Adding a few coding standard fixes.

oknate’s picture

Another small change:
$this->assertText() is deprecated

-    $this->assertText('100% of the site has been indexed');
+    $this->assertSession()->pageTextContains('100% of the site has been indexed');
jhodgdon’s picture

Regarding #136 -- that is just how the core Search module works. You need to run cron in order to index items for searching, whether they are Node module content items or (now) Help items. We never had a warning message about the Node search, so I don't think it is necessary to have one for help search either. If you go to the Search settings page, you will see the indexing status and it will tell you cron needs to be run (possibly several times if there is a lot to index).

jhodgdon’s picture

You could also look at the Status Report page and see if, before you run cron, there is a notice there about the search index not being fully indexed. I think if there is not something there already, the Search module should be updated.

Ghost of Drupal Past’s picture

tl;dr: I renamed plugin_type to plugin_id.

This issue is monumental, it has the potential to change the very perception of Drupal as it will basically ship with the manual built in. Huge congrats to everyone getting it this far, let me see whether I can further it a little bit.

I was nitpicking at first and made small fixes (in findResults the indentation was telling it's slightly unreadable, I made the query a bit better while at it and moved the not empty condition into the query, I renamed a join to innerJoin for easier understanding, in markForReindex I dropped an unnecessary fetchAll, and an unnecessary empty test because foreach over an empty array is just as good, another empty test in removeItemsFromIndex because instanceof works with any type of variable, simplified and fixed the loop in HelpTopicSearchTest::setup it wasn't incrementing $num_runs, all nitpicks) until I arrived to 'plugin_type' => $plugin_id

Wait a minute, what? 'plugin_type' => $plugin_id?? I look more and see $plugin = $this->helpManager->createInstance($plugin_type); and I am like, OK that's not a plugin type, that's a plugin id. So I did a rename with git diff 8.8.x |sed s/plugin_type/plugin_id/g so I can guarantee nothing was left out of the renaming, submitted it into a testing issue and it came back green. So I am submitting it for consideration.

oknate’s picture

Nice catch on \s\plugin_type\plugin_id. This should change also:

Type of plugin to instantiate.
-   * @param string $plugin_type
+   * @param string $plugin_id
    *   Type of plugin to instantiate.

I recently added a related issue #3083507: Standardize "plugin ID" in doc comments instead of "plugin_id", so I would suggest calling it "plugin ID" in the comment.

Ghost of Drupal Past’s picture

How deep does the rabbit hole go? There are three plugin types and associated IDs in this patch and boy, are their names confusing. (Reminder: a plugin type is like migration source or help section.)

  1. Search calls its plugin IDs type for legacy reasons. The HelpSearch search plugin already had the SearchInterface::getType function implemented as $this->getPluginId() but it still used to call $this->getPluginId() everywhere instead of the more semantic $this->getType(). Old version: ':type' => $this->getPluginId(), new version ':type' => $this->getType(). That certainly looks better and I didn't plan on it, I was just doing some search-and-replace, all the methods were there and I didn't touch the method names. Same is true for the other two. (Note this issue is working on renaming some $type to $plugin_id but the database columns and the method name stays so there is no conflict.)
  2. Help sections have their plugin IDs. For some reason these were called plugin_type. New name: section_plugin_id. Results in $this->getSectionPlugin($section_plugin_id) and again I was not planning on the new variable matching up with the relevant method name.
  3. And help topics also have their plugin IDs but since they are coming from files they were just called ID. New name: topic_id. Results in renderTopicForSearch($topic_id. Isn't it nice how these align?

As visible from above, I am really happy how this turned out, I hope others will be just as happy. Interdiff is against 137 (140 is just me confused, ignore that).

And since I am making a list, let me make a nice formatted list of the small fixes in #140 and of course here as well:

  1. Made the permissions query in findResults a little more readable (the indentation was telling it's slightly unreadable) and I moved the not empty condition into the query
  2. I renamed a join in there to innerJoin for easier understanding
  3. in markForReindex I dropped an unnecessary fetchAll,
  4. also in markForReindex I dropped unnecessary empty test because foreach over an empty array is just as good,
  5. another empty test in removeItemsFromIndex because instanceof works with any type of variable
  6. simplified the loop in HelpTopicSearchTest::setup.
  7. One extra which wasn't in #140, the constructor had a = NULL for $account in the middle of the argument list, I removed it.
Ghost of Drupal Past’s picture

One more. Why does HelpTopicSearchTest::setup compare the remaining to total? It should test for zeroness. Now it does. With an assert.

Ghost of Drupal Past’s picture

*frown* where are my files?

Status: Needs review » Needs work

The last submitted patch, 144: 3084526_143.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

andypost’s picture

+++ b/core/modules/help_topics/tests/src/Functional/HelpTopicSearchTest.php
@@ -59,8 +59,9 @@ protected function setUp() {
+    $this->assertEmpty($remaining);;

nit, two `;;`

Ghost of Drupal Past’s picture

Status: Needs work » Needs review
FileSize
57.95 KB
1.09 KB

That fail is fixed, interdiff is against #142. Patches up to #137 were looking for search type "foo" items in the database of which there were zero so the remaining and the total were always equal... because the search type is always help_search (mayhaps someone mixed up search type with topic id which now hopefully won't happen because I renamed things). So the loop was testing $num_runs < 100 which always succeeded and then $indexed was always TRUE after the first loop so only one iteration ran and that was enough since cron allows 100 items to be indexed and we only had 19. But, you know, it was not testing the indexStatus() method much. And then I fixed the condition was actually made cron run 100 times because there were always 19 items remaining as the search_dataset didn't contain any "foo" items... Now that's fixed and I added two asserts to see the it finishes in less than 100 iterations and it actually didn't leave anything behind.

vijaycs85’s picture

Overall the changes look great(+1 to RTBC). I did manual testing and didn't see anything unexpected.

Few observations:
1. search is somewhat slow for like 13 items on a standard profile installed core-only site: after a cache clear it takes about 5sec and with cache, it takes 2.1 sec.
2. Small alignment issue with the help link on the empty search result page.

Here are a few screenshots of the UI changes introduced by this issue.

jhodgdon’s picture

The speed and alignment issues are coming from the core Search module, not from this patch.

I have carefully reviewed the code and I am +1 for RTBC. I don't think I can set it to RTBC though, since I wrote a lot of the current patch.

Amber Himes Matz’s picture

Status: Needs review » Reviewed & tested by the community

I've also reviewed and tested the module. I only have a couple of UX nits which I don't think are necessarily blockers. They are:

1. It is somewhat jarring to enter a search at admin/help in the Seven (or administrative theme), then have the search results show up in the default theme (e.g. Bartik), then click on a help topics result going back to Seven (admin theme).

2. If a user has the search block enabled and placed in the default front-facing theme and they perform a search, the linked text "Search help" will appear near the form. (See also this screenshot from #148 https://www.drupal.org/files/issues/2019-10-01/2664830-148-search-result....) This text in this context means "Read some help documentation about searching on this site." But now we have a search form on admin/help would could quite conceivably be titled "Search help". And in this context it would mean, "Search for help topics." Having these two meanings of "Search help" co-exist on a site could lead to confusion. (It certainly tripped me up when I was testing.)

These 2 UX issues notwithstanding, the code and functionality looks fine to me and in light of the other recent in-depth reviews, I am in favor of an RTBC, using the patch in #147.

I can also open follow-up issues on the 2 UX issues I mentioned above.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -137,6 +191,79 @@ protected function getPlugins() {
    +  public function renderTopicForSearch($topic_id, LanguageInterface $language) {
    +    $plugin = $this->pluginManager->createInstance($topic_id);
    +    if (!$plugin) {
    +      return [];
    +    }
    +
    +    // We are rendering this topic for search indexing or search results,
    +    // possibly in a different language than the current language. The topic
    +    // title and body come from translatable things in the Twig template, so we
    +    // need to set the default language to the desired language, render them,
    +    // then restore the default language so we do not affect other cron
    +    // processes. Also, just in case there is an exception, wrap the whole
    +    // thing in a try/finally block, and reset the language in the finally part.
    +    $old_language = $this->defaultLanguage->get();
    +    try {
    +      if ($old_language->getId() !== $language->getId()) {
    +        $this->defaultLanguage->set($language);
    +        $this->translationManager->setDefaultLangcode($language->getId());
    +        $this->languageManager->reset();
    +      }
    

    I've had a long discussion with @chx and @larowlan about whether this is the right place to be doing rendering and language switching.

    This all feels very tricky to me. My original thought was that language setting and rendering for search should be the responsibility of the HelpSearch plugin but @chx pointed out that if this prepping content entities for help search then the language switching would be different. I think this is a good point and means we probably have this on the correct level. (As much as it pains me to see the renderer and language manager and everything injected here).

    I think the language swapping here might need to borrow more from \Drupal\commerce\MailHandler::changeActiveLanguage - it does this:

      protected function changeActiveLanguage($langcode) {
        if (!$this->languageManager->isMultilingual()) {
          return;
        }
        $language = $this->languageManager->getLanguage($langcode);
        if (!$language) {
          return;
        }
        // The language manager has no method for overriding the default
        // language, like it does for config overrides. We have to change the
        // default language service's current language.
        // @see https://www.drupal.org/project/drupal/issues/3029010
        $this->languageDefault->set($language);
        $this->languageManager->setConfigOverrideLanguage($language);
        $this->languageManager->reset();
    
        // The default string_translation service, TranslationManager, has a
        // setDefaultLangcode method. However, this method is not present on
        // either of its interfaces. Therefore we check for the concrete class
        // here so that any swapped service does not break the application.
        // @see https://www.drupal.org/project/drupal/issues/3029003
        $string_translation = $this->getStringTranslation();
        if ($string_translation instanceof TranslationManager) {
          $string_translation->setDefaultLangcode($language->getId());
          $string_translation->reset();
        }
      }
    

    Not sure though. It's a shame that there isn't code on the language manager to handle swapping the default language. There are some interesting protections in that method where $langcode is not valid and the services are not quite as we expected them.

  2. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -137,6 +191,79 @@ protected function getPlugins() {
    +      // Render the body in this language. For this, we need to set up a render
    +      // context, because the Twig plugins that provide the body assumes one
    +      // is present.
    +      $context = new RenderContext();
    +      $build = [
    +        'body' => $this->renderer->executeInRenderContext($context, [$plugin, 'getBody']),
    ...
    +      $topic['text'] = $this->renderer->renderPlain($build);
    +      $cacheable_metadata->addCacheableDependency(CacheableMetadata::createFromRenderArray($build));
    +      $cacheable_metadata->addCacheableDependency($plugin);
    +      if (!$context->isEmpty()) {
    +        $cacheable_metadata->addCacheableDependency($context->pop());
    +      }
    

    Are we sure that the complexity here is necessary? I've changed this to

          // Render the body in this language.
          $build = [
            'body' => $plugin->getBody(),
          ];
          $topic['text'] = $this->renderer->renderPlain($build);
          $cacheable_metadata->addCacheableDependency(CacheableMetadata::createFromRenderArray($build));
          $cacheable_metadata->addCacheableDependency($plugin);
    

    and Drupal\Tests\help_topics\Functional\HelpTopicSearchTest passes just fine.

    We lose the double render and renderPlain does executeInRenderContext() already.

  3. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -137,6 +191,79 @@ protected function getPlugins() {
    +    return $topic;
    
    +++ b/core/modules/help_topics/src/SearchableHelpInterface.php
    @@ -0,0 +1,46 @@
    +   * @return array
    +   *   An array of information about the topic, with elements:
    +   *   - title: The title of the topic in this language.
    +   *   - text: The text of the topic in this language.
    +   *   - url: The URL of the topic as a \Drupal\Core\Url object.
    +   *   - cacheable_metadata: (optional) An object to add as a cache dependency
    +   *     if this topic is shown in search results.
    

    I'm not a huge fan of returning arrays with magical keys on an interface based API. Normally I'd reach for a value object for this. @chx pointed out that these values are consumed immediately and therefore he feels a value object is not needed.

  4. +++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
    @@ -0,0 +1,492 @@
    +    $find = $query
    +      ->fields('i', ['langcode'])
    +      // Since SearchQuery makes these into GROUP BY queries, if we add
    +      // a field, for PostgreSQL we also need to make it an aggregate or a
    +      // GROUP BY. In this case, we want GROUP BY.
    +      ->groupBy('i.langcode')
    +      ->limit(10)
    +      ->execute();
    ...
    +      $record = $this->database->select('help_search_items', 'hsi')
    +        ->condition('sid', $item->sid)
    +        ->fields('hsi', ['section_plugin_id', 'topic_id'])
    +        ->execute()->fetchObject();
    

    If we add ->fields('hsi', ['section_plugin_id', 'topic_id']) to the find query then we don't need to query the same table again. Less queries ftw.

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.59 KB
57.43 KB

AS I have it locally here's #151.2 and #151.4 addressed.

Which only leaves the #151.1 questions about whether we are doing language swapping correctly and safely to do.

alexpott’s picture

Lolz lolz lolz. Learnt a new thing today. So when you run cron in test or via the browser (same thing)... you're executing cron in a render context so that means the point in #151.2 is valid - however if you run cron via drush you are not. Therefore it fails. So probably do need the RenderContext code. We also need to open an issue to somehow execute cron via drush and via the browser in the same environment.

alexpott’s picture

So here's a funny problem. If you install

  1. help_topics with this patch applied
  2. run cron
  3. search help topics for "basic" everything works
  4. uninstall help_topics
  5. install help_topics
  6. run cron
  7. search help topics for "basic" can't find a thing
alexpott’s picture

I think #154 shows that we need a hook_modules_installed() as hook_rebuild is not triggered when installing a module (even though its docs kinda imply it is. It's not called on uninstall either. This all implies the patch is missing test coverage.

jhodgdon’s picture

I am +1 on the changes in the latest patch (#153).

Regarding #154... It seems like the bug is that hook_rebuild is not triggered when you install a module? But we could definitely add hook_modules_installed() and add a test here that replicates the steps in #154.

I remember discussing this, I guess in just Slack and the discussion didn't get into the issue, when I was developing this patch, and I don't remember with whom. I was concerned because there are several cases where we would need to reindex the help topics, because the content of topics might have changed:
- Install/uninstall a module/theme
- Add/remove a language
- Update a module/theme
- Update/import/remove translations
- Update config [if someone is using the contrib config_help module]

The consensus of that discussion was that we could not possibly detect all of these cases, so we should just trigger reindex whenever there is a cache rebuild, and if someone does any of these things and the system doesn't automatically trigger a cache rebuild, most likely the person who did the thing would rebuild the cache anyway.

So... I think we have 3 options:

a) Decide that a cache rebuild should be happening whenever a new module is installed, and make that happen in Core.
b) Implement hook_modules_installed() with the same body as hook_rebuild() we have in the current patch.
c) Leave it as it is and assume someone who found things not working on their site, or made any major changes, would probably rebuild the cache.

Thoughts?

alexpott’s picture

I think we need to do (b) since the behaviour in #154 is very unexpected and we can't do (a) because doing a full cache flush on module install /uninstall is extremely expensive and we've not done it since before 8.0.0.

jhodgdon’s picture

It sounds like we need both hook_modules_installed() and hook_modules_uninstalled() then? Because if a module is uninstalled and the cache isn't flushed then (really???), we definitely need to at least flush out the topics that were provided by the module.

alexpott’s picture

We don't call drupal_flush_all_caches() nope. Just checked D7 too. drupal_flush_all_caches() is only called for module enable and disable when it's done via the UI so in a way D7 is not different either.

And yes we definitely need a test to show that once a module is uninstalled it's help topics are no longer been displayed - even if cron has not been run.

jhodgdon’s picture

Ah, so if you install/uninstall a module *in the UI*, then caches are rebuilt, but not if you use Drush or another programmatic method. That makes sense. That is probably also true for some of the other things that need to trigger reindexing help, such as adding/removing a language or updating translations... and I don't think we can even really detect every possible thing that should trigger a reindex.

And as a note, your idea of adding a test to verify topics are removed even if cron hasn't been run will not pass with the current code -- the list of topics that should be included in the index is only currently updated during cron runs.

So... I think we need to do something else here, rather than trying to add more hook implementations, and testing for all those cases. I'm going to open a discussion in Slack, and then report back when we've decided something...

jhodgdon’s picture

We had a rather long discussion about this on Slack, with andypost, wimleers, pwolanin, and jhodgdon participating.

Some thoughts:

There are several reasons that the list of which topics/languages need to be in the Help Topics Search Index could change. It is probably possible to detect all of them (if we're sure we've figured out all the reasons), but would require quite a bit of code complexity to do so. We are currently only recalculating this when hook_rebuild() is triggered.

There are also several reasons that the indexed text for a given topic/language could change, including updating the Twig file source, adding/updating/removing translation strings, and (if the config_help contrib module is being used) updating configuration. Again, these could probably all be detected, but it would require a bit of code complexity to do so. We are currently triggering a reindex of topic text on hook_rebuild() and when the "Rebuild search index" button on admin/config/search/pages is clicked.

What I think we should do:

a) Rebuild the list of topics on each cron run. This should not be very expensive, as it's mostly asking for cached things (like lists of help topic plugins). That will ensure that if modules are added/removed, we detect their topics even if a cache rebuild is not done.

b) Also trigger full text reindex when the plugin cache is cleared for the Help Section plugins.

c) Document for admins that they should click the "Rebuild search index" button on admin/config/search/pages if they have updated translations or made other changes that they think might affect help topic text.

d) [Maybe??] Consider adding to the end of each cron run that we cycle through topics that were indexed the farthest in the past, and reindex them. This way, all the stale stuff in the help topic index would eventually be reindexed.

jhodgdon’s picture

In order to implement this, we would need to:

1. Separate HelpSearch::markForReindex() into two parts. One would recalculate the list of topics [call this updateTopicList()], and the other would mark all existing topics for reindex.

2. In HelpSearch::updateIndex(), always call updateTopicList(), but only call markForReindex() if the state variable is set.

3. Think about whether we should call updateTopicList() from hook_modules_uninstalled() so that the list of available topics is immediately reduced when a module goes away. And is there a hook_themes_uninstalled() too? Should be done there too. Can we make this method static, so it can be called more easily from this hook? HelpSearch is a plugin...

4. Add to some documentation.

alexpott’s picture

So with the current patch nothing shows up until you run cron. This kinda feels okay but at the very least I think we need a hook_modules_uninstalled() to remove topics that are no longer available. We don't want to be able to search and then end up with help for things that we can't do. This is inline with what node does on node create and node delete. Node create - it's not searchable until cron has run but it is removed the moment you delete it.

One thing I wonder is does the current code already cope with this - it doesn't remove it from the search index but I think in order to display a result it has to be able to load the topic which it will no longer be able to do.

I think we need to start with testing our assumptions. I.e. add the following sort of test to HelpTopicSearchTest

  1. install a module - assert one of its topic is not there
  2. run cron - assert one its topic is there
  3. uninstall the same module -assert its topic is not there even though cron has not been run after uninstalling.
andypost’s picture

As there's no way to display custom message (from plugin for example to report why search cannot be done) https://git.drupalcode.org/project/drupal/blob/8.8.x/core/modules/search...
I think it only doable when building/preprocessing results https://git.drupalcode.org/project/drupal/blob/8.8.x/core/modules/search...

Another idea is do additional check in \Drupal\help_topics\Plugin\HelpSection\HelpTopicSection::renderTopicForSearch() or override \Drupal\help_topics\HelpTopicPluginManager::createInstance() to exclude unavailable topics from build (as they remain searchable)

jhodgdon’s picture

That is true that the topic will not be displayed on a search (RE #163). However, you would get fewer results per page than expected, and could get a problem where all 10 of the first results were non-existent topics and thus a "no results" result even though the next result would have been valid. So I think you are right, we need to remove topics when modules are uninstalled. This would be implemented by #163 item 3.

Regarding #164, I don't really think it's necessary... or if it is, it's needed in core Search in general and should be a follow-up issue.

alexpott’s picture

@Charlie ChX Negyesi pointed out in Slack I broke postgres by not reading the great comment. This patch fixes that.

jhodgdon’s picture

Postgres passing ++

So it looks like we still need to take care of the module install/uninstall stuff. In particular, I think we should do the following [slightly different from my last suggestion, and incorporating some other comments]:

1. Separate HelpSearch::markForReindex() into two public methods. One would recalculate the list of topics [call this updateTopicList()], and the other would mark all existing topics for reindex [markForReindex()]. Note that markForReindex() is a public method called by the core Search module when the user clicks the "Rebuild search index" button, and it needs to also call updateTopicList().

2. In HelpSearch::updateIndex(), always call updateTopicList() [every cron run we will recalculate the list of valid topics, no matter what].

3. Hooks/events/etc.:
- hook_rebuild() should call the public markForReindex() method directly (let's not use a state variable unless we have to).
- hook_modules_uninstalled() should call updateTopicList(), so that invalid topics are immediately removed
- when plugin cache is cleared for the Help Section plugins, call markForReindex()

4. Add to the docs so admins know about the "Rebuild search index" button and/or clearing the cache.

5. Add to the tests:

  1. install a module - assert one of its topic is not there
  2. run cron - assert one its topic is there
  3. uninstall the same module -assert its topic is not there even though cron has not been run after uninstalling.
jhodgdon’s picture

Assigned: Unassigned » jhodgdon
Status: Needs review » Needs work

Updating status for accuracy (see previous comment). I'll go ahead and make a patch for this later today.

jhodgdon’s picture

To start with, here is a reroll of the previous patch, which did not apply cleanly for me on the latest Core.

jhodgdon’s picture

Status: Needs work » Needs review
FileSize
8.72 KB
60.37 KB

OK, here's a new patch that takes care of #167. Search test passes locally. A couple of questions/notes:

a) In the previous patch, we used a state variable to keep track of whether we needed to update the topic list and/or mark for reindex. I have eliminated that, because we need to have an immediate action if a module/theme is uninstalled, and it didn't make sense to me to have the state variable sometimes and not use it at other times.

b) I'm not quite sure about what I did to implement this part of #167:

- when plugin cache is cleared for the Help Section plugins, call markForReindex()

I implemented it like this, in the HelpTopicSection plugin class:

  public function clearCachedDefinitions() {
    parent::clearCachedDefinitions();
    help_topics_update_help_search_index('reindex');
  }

(calling a new function in help_topics.module). Also added docs to the Searchable interface so it tells implementers they should do something similar...

Two questions about this:
1. Is this the right place to respond to "plugin cache is cleared"? I think it is.
2. Is it OK to call this function? I know we generally want to avoid calling functions like this, but doing it with proper dependency injection would I think not work, because some of that function only executes if the Search module is enabled. Here's the function body:

  if (!\Drupal::moduleHandler()->moduleExists('search')) {
    return;
  }

  $search_page_repository = \Drupal::service('search.search_page_repository');
  /** @var \Drupal\help_topics\Plugin\Search\HelpSearch $plugin */
  $plugin = NULL;
  foreach ($search_page_repository->getIndexableSearchPages() as $entity) {
    if ($entity->get('plugin') == 'help_search') {
      $plugin = $entity->getPlugin();
      break;
    }
  }

  if ($plugin) {
    if ($action === 'list') {
      $plugin->updateTopicList();
    }
    elseif ($action === 'reindex') {
      $plugin->markForReindex();
    }
  }

I just don't think this can be done via dependency injection, since the search page repository service only exists if the Search module is enabled.

Thoughts?

jhodgdon’s picture

The topic in this patch is going to need a reformat for #3069109: Replace help_topic meta tags with front matter. Rest of the patch can still be reviewed. I'll take care of that shortly...

jhodgdon’s picture

FileSize
59.67 KB

Here is a straight reroll of the previous patch, with just the front-matter change made to that one topic about how to set up searching for help. I haven't tried to run the tests locally; let's see what the bot says instead...

Here's the section that changed:

+++ b/core/modules/help_topics/help_topics/help_topics.help_topic_search.html.twig
@@ -0,0 +1,20 @@
+---
+label: 'Configuring help search'
+top_level: true
+---

Everything else should be the same, except a few parts of files that were removed in that other issue and patched here are gone.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned
Amber Himes Matz’s picture

I'm putting the UI through its paces. If I uninstall help_topics via Drush and refresh admin/help, I get a "The website encountered an unexpected error. Please try again later."

1. Install Drupal 8.8-dev
2. Apply patch in #172
3. Enable help_topics
4. Re-index site
5. Run cron
6. Test search box in admin/help -- everything is fine up to this point.
7. Uninstall help_topics
8. Refresh admin/help
9. White screen with "The website encountered an unexpected error..."
10. Log message immediately after uninstall:

Symfony\Component\Routing\Exception\RouteNotFoundException: Route "search.view_help_search" does not exist. in Drupal\Core\Routing\RouteProvider->getRouteByName() (line 201 of /var/www/html/core/lib/Drupal/Core/Routing/RouteProvider.php).
Amber Himes Matz’s picture

Same result uninstalling via admin/modules/uninstall UI.

andypost’s picture

  1. +++ b/core/modules/help_topics/help_topics.module
    @@ -64,10 +64,57 @@ function help_topics_rebuild() {
    +  help_topics_update_help_search_index('reindex');
    ...
    +  help_topics_update_help_search_index('list');
    ...
    +  help_topics_update_help_search_index('list');
    ...
    +function help_topics_update_help_search_index($action = 'list') {
    
    +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -150,6 +150,14 @@ public function getCacheMaxAge() {
    +    help_topics_update_help_search_index('reindex');
    

    I think it is no go to use function within object's method

  2. +++ b/core/modules/help_topics/help_topics.module
    @@ -64,10 +64,57 @@ function help_topics_rebuild() {
    +  if ($plugin) {
    +    if ($action === 'list') {
    +      $plugin->updateTopicList();
    +    }
    +    elseif ($action === 'reindex') {
    +      $plugin->markForReindex();
    

    This code bit confusing me because both actions needs search page entity (plugin) to exists but /admin/config/search/pages allows to disable search page as well so
    - makes sense to move this to plugin.manager.help_topic as 2 methods for example to separate plugin lookup from actions

jhodgdon’s picture

Regarding #174... It is not this module's fault if Drupal doesn't automatically rebuild the routing tables after a module is uninstalled or a search page is deleted.... Definitely the route to the search page would not exist if the module is uninstalled and therefore the search page doesn't exist. That seems like a problem in the Search module or Drupal Core.

Regarding #176 -- I don't know what else to do. We cannot inject the service into these classes unless the Search module is installed, which it may not be.. See comment #170. No one bothered to answer that question... but I don't think there is any way around having some procedural code... I guess we could use $this->container->service() but it will still be lame, and I would rather contain the lameness in a central function call than scatter it in 2 or more classes? Not sure. Anyway... If you have a better idea I am open to it.

It is probably too late to get this done by Wednesday now. Not sure what to do but with the time zone difference between Russia and western US, I don't think we will have time to finish this to everyone's satisfaction. :(

Amber Himes Matz’s picture

Ok, regarding #174-5, I will add this to my list of follow-up issues to open.

andypost’s picture

If the search page (config entity) is disabled then search admin page shows "Does not use index"

So the helper function should check the status (added comment)
Also this function only depends on presence of storage handler for pages to access plugin & state of config

Implementation for "immediately re-index" could use suggestion from #123 and implement batching like locale module doing (to import translations) could be done in follow-up because of complexity and tests (let's keep scope)

I think it ready to go

Amber Himes Matz’s picture

I've tested the latest patch (#179) in the UI and it functions just fine. I will be opening follow-up issues for the UX issues I raised in #174 and #150.

Just waiting for the testing to complete the check on #179, but pending a final code review (maybe from @alexpott?) I would be +1 in favor of RTBC.

vacho’s picture

I review this issue after long months. After review, I see that the main last issues were solved.

1. Install - uninstall - install problem doesn't exist anymore.
I test it manually

2. Indexes, re-index
Works as I expected, a hook re-index all searchable pages.

3. Search result
After manual test works fine with olds and a new module

RTBC +1 for this issue.

Note:
- After module help_topics install is needed to run the cron.

Amber Himes Matz’s picture

Status: Needs review » Reviewed & tested by the community

Thank you for the review @vacho and for the new patches @andypost and @jhodgdon!

I think the functionality is there. We've had several pairs of eyes on this throughout. And we can open follow-up issues as needed to refine the UX. Those issues are is in the search module anyway. As for what help topics module is doing, we have a functioning search for help topics.

I'm setting this to RTBC and we'll see if we can get this in by the deadline.

jhodgdon’s picture

RE the UI in #179, User search simply doesn't use the index. It is not because it is disabled, it is because User search just doesn't use the index even if it is enabled. But the interdiff in #179 looks OK to me, aside from a nitpick in the grammar of a new comment:

+      // Disabled pages is not indexed by search module.

I edited the patch file directly to change this one comment to
"Only update the search index if there is an enabled help search plugin."

Leaving at RTBC since this is just a simple nitpick.

jhodgdon’s picture

Status: Reviewed & tested by the community » Needs review

Actually, though, with the latest patch we can inject the necessary services into a class so we don't have to call the function in help_topics.module from within a class. Temporarily bumping this back to Needs Review. I'll see if I can make a new patch within an hour or two that will do just that.

jhodgdon’s picture

OK, here's a patch that removes the call to the function in help.module in the HelpTopicSection plugin -- the function calls in help_topics.module are still there (and I think they are appropriate). In HelpTopicSection, this is replaced by a dependency-injected 10 lines or so of code. Thoughts?

jhodgdon’s picture

As a note, if we wanted to make this a little more compatible with other search back ends (like Search API module), I think we would want to define some kind of an event or hook that could be triggered to say "The searchable topics list needs to be rebuilt" or "The text of topics need to be reindexed". Other than that, Search API should be able to use the methods on SearchableHelpInterface to discover and index the topics.

The reason this hasn't been an issue for Node searching in Search API is that there are already hooks/events/whatever that say "A node has been created/updated/deleted", so Search API can respond. But in the case of help topics, they aren't entities that are being indexed for searching, so there aren't clear hooks/events when they need updates.

So... I think such an event or hook could be defined later.

andypost’s picture

FileSize
10.18 KB
63.8 KB

I think that's most clean approach

Each plugin "knows" own plugin manager and interface declares search support now

  1. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -103,14 +111,17 @@ class HelpTopicSection extends HelpSectionPluginBase implements ContainerFactory
    +    $this->entityTypeManager = $entity_type_manager;
    

    moved the interaction to search to topic plugin manager interface

  2. +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
    @@ -155,7 +167,18 @@ public function getCacheMaxAge() {
    +        if ($page->status() && $page->getPlugin()->getPluginId() === 'help_search') {
    +          $plugin->markForReindex();
    

    it was broken - plugin should be taken from page

jhodgdon’s picture

That seems better indeed. +1 for RTBC if the bot agrees.

andypost’s picture

Symfony\Component\Routing\Exception\RouteNotFoundException: Route "search.view_help_search" does not exist. in Drupal\Core\Routing\RouteProvider->getRouteByName() (line 201 of /var/www/html/web/core/lib/Drupal/Core/Routing/RouteProvider.php).

Reproducible error when I disable->enable any search page at /admin/config/search/pages

jhodgdon’s picture

Amber saw that in comment #174. I think it is a core search module problem?

jhodgdon’s picture

Unrelated: I was looking for help_search in the patch (to see if we are doing something obviously wrong), and found this comment

+   * Constructs a \Drupal\help_search\Plugin\Search\HelpSearch object.

The namespace here should be help_topics not help_search. [doc block for constructor in the HelpSearch plugin class]

alexpott’s picture

+++ b/core/modules/help_topics/help_topics.module
@@ -51,3 +56,30 @@ function help_topics_theme() {
+/**
+ * Implements hook_rebuild().
+ */
+function help_topics_rebuild() {
+  // If the Search module is also enabled, we need to trigger a search reindex
+  // when a module or theme is installed, uninstalled, or updated; and when
+  // languages are added, translations are changed, or string overrides are
+  // changed. These situations can cause an indexed help item to have different
+  // text, or for help items to be added or removed. We cannot detect all of
+  // these cases, so instead at least trigger a reindex on cache rebuild.
+  \Drupal::service('plugin.manager.help_topic')->updateSearchIndex('reindex');
+}

+++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php
@@ -96,6 +151,16 @@ public function getCacheMaxAge() {
+  /**
+   * {@inheritdoc}
+   */
+  public function clearCachedDefinitions() {
+    parent::clearCachedDefinitions();
+    // Mark the help search index for reindexing, if there is an active
+    // help search page.
+    $this->pluginManager->updateSearchIndex('reindex');
+  }

The hook_rebuild only gets called on drupal_flsuh_all_caches()... that's supposed to call the clearCachedDefintions... it not currently happening because this method only exists on plugin managers not plugins (which this is)

Here's a patch that has tests and works. It also reduces the complexity (a little) compared to #187 and #185. The one bit off additional complexity is that we need to decorate the Help section plugin manager from the help module in order to mark search for re-index on the right level.

alexpott’s picture

No idea why I uploaded the patch twice... but they are the same :)

jhodgdon’s picture

New patch/interdiff looks good to me, assuming bot agrees. New test lines look good too.

jhodgdon’s picture

Also as a note about #174/#189, @andypost found this related issue.

andypost’s picture

@alexpott thanks a lot, it looks much cleaner!

But ... it does not respect "status" of search page (core search is not supposed to reindex if no search pages enabled (see #179 screenshot)

jhodgdon’s picture

I don't think that matters, actually. It is possibly a bit inefficient to reindex if help search is not enabled, but it won't actually hurt anything.

The screenshot in #179 actually shows something else: that User search never uses the Search index (it does its own direct queries on the user module database tables). It is not necessarily wrong, just a bit inefficient, to have Help indexed or the Help topics table populated, without a search page for help being enabled.

Amber Himes Matz’s picture

Applying the patch from @alexpott in #192, I get the following error when I try to uninstall help_topics.

ambermatz@drupal-web:/var/www/html$ drush pm-uninstall help_topics
The following extensions will be uninstalled: help_topics -y

PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'db.help_search_items' doesn't  [error]
exist in /var/www/html/core/lib/Drupal/Core/Database/Statement.php:59

Steps to reproduce:

1. Install Drupal 8.8-dev
2. Apply patch in #192
3. Enable help_topics
4. Re-index site
5. Run cron
6. Test search box in admin/help -- everything is fine up to this point.
7. Uninstall help_topics with drush pm-uninstall help_topics. Error.

jhodgdon’s picture

Looks like it could maybe be related to #2901590: Investigate route rebuilding problem wrt Search module ? Or... Oh! I think it's probably because we have a hook_modules_uninstalled(). We should probably put an if() in there and only rebuild the index if help_topics is not in the list of modules that is uninstalled. Doh!

andypost’s picture

At least it fixes uninstall

#8 /var/www/html/web/core/lib/Drupal/Core/Plugin/CachedDiscoveryClearer.php(31): Drupal\help_topics\HelpSectionManager->clearCachedDefinitions()
#9 /var/www/html/web/core/lib/Drupal/Core/ProxyClass/Plugin/CachedDiscoveryClearer.php(83): Drupal\Core\Plugin\CachedDiscoveryClearer->clearCachedDefinitions()
#10 /var/www/html/web/core/lib/Drupal/Core/Extension/ModuleInstaller.php(483): Drupal\Core\ProxyClass\Plugin\CachedDiscoveryClearer->clearCachedDefinitions()
#11 /var/www/html/web/core/lib/Drupal/Core/ProxyClass/Extension/ModuleInstaller.php(91): Drupal\Core\Extension\ModuleInstaller->uninstall(Array, true)
jhodgdon’s picture

FileSize
61.55 KB
771 bytes

Wow, that is weird. It seems like a plugin provided by help_topics module should not have any of its methods executed on the cache clear that happens when help_topics module is being uninstalled. ?!? That seems like kind of a core bug, really. But anyway.

It seems like we might still need/want to do something similar in the hook_uninstall()? Something like this...

andypost’s picture

jhodgdon’s picture

FileSize
63.38 KB
1.84 KB

OK, this is totally untested... Amber keeps coming across that WSOD from routing if you disable the help_topics module and then try to go to admin/help. This is bad. We are pretty sure it is coming from the Help Search block. So, here's a patch that hopefully fixes the problem, by making sure that the search form in the search block is not trying to search disabled or nonexistent search pages.

I haven't even tried this out but here it is... will maybe work...

Amber Himes Matz’s picture

Ok, on this latest patch in #203, I tested it and both uninstall errors are resolved! (Reported in comments #174 and #198.)

With a code review on the latest changes, I would be +1 RTBC. And will be willing to RTBC with 1 more code review please.

andypost’s picture

Status: Needs review » Reviewed & tested by the community

that's fixes uninstall, the remains will be addressed in #2901590: Investigate route rebuilding problem wrt Search module
Looking on #3075695: Move search_index() and related functions to a service

PS: I don't think we need install/uninstall test here

alexpott’s picture

I think we need test coverage for this. I'm in process of adding it.

alexpott’s picture

The test added by the patch catches both #198 and #203.

andypost’s picture

@alexpott Thanks for test! let's get this in)

jhodgdon’s picture

+1!

alexpott’s picture

In #150 @Amber Himes Matz discussed 2 UX concerns - before we can commit this we need those issues created and on the roadmap for stable - so we don't forgot about them.

jhodgdon’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/search/src/Form/SearchBlockForm.php
@@ -77,6 +77,7 @@ public function getFormId() {
   public function buildForm(array $form, FormStateInterface $form_state, $entity_id = NULL) {
     // Set up the form to submit using GET to the correct search page.
+    // But first, make sure this search page is active.
     if (!$entity_id) {
       $entity_id = $this->searchPageRepository->getDefaultSearchPage();
       // SearchPageRepository::getDefaultSearchPage() depends on
@@ -87,7 +88,12 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_i

@@ -87,7 +88,12 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_i
       $this->renderer->addCacheableDependency($form, $this->configFactory->get('search.settings'));
     }
 
-    if (!$entity_id) {
+    if ($entity_id) {
+      $pages = $this->searchPageRepository->getActiveSearchPages();
+      $entity = $pages[$entity_id] ?? NULL;
+    }
+
+    if (!isset($entity)) {
       $form['message'] = [
         '#markup' => $this->t('Search is currently disabled'),
       ];
diff --git a/core/modules/search/src/SearchPageRepositoryInterface.php b/core/modules/search/src/SearchPageRepositoryInterface.php

diff --git a/core/modules/search/src/SearchPageRepositoryInterface.php b/core/modules/search/src/SearchPageRepositoryInterface.php
index 6169fbcfe9..7ed5d43a82 100644

index 6169fbcfe9..7ed5d43a82 100644
--- a/core/modules/search/src/SearchPageRepositoryInterface.php

--- a/core/modules/search/src/SearchPageRepositoryInterface.php
+++ b/core/modules/search/src/SearchPageRepositoryInterface.php

+++ b/core/modules/search/src/SearchPageRepositoryInterface.php
+++ b/core/modules/search/src/SearchPageRepositoryInterface.php
@@ -34,8 +34,8 @@ public function getIndexableSearchPages();

@@ -34,8 +34,8 @@ public function getIndexableSearchPages();
   /**
    * Returns the default search page.
    *
-   * @return \Drupal\search\SearchPageInterface|bool
-   *   The search page entity, or FALSE if no pages are active.
+   * @return string|false
+   *   The default search page entity ID, or FALSE if no pages are active.
    */
   public function getDefaultSearchPage();

Oh noes... I've just realised we have change outside of the help topics module here. :(

This either needs to go in blocking issue or be resolved differently. Patches against experimental modules shouldn't make changes to non-experimental code (unless it's part of removing the experimental ness).

Let's see if there is another way.

alexpott’s picture

Yay for having test coverage. So here's an acceptable work-around for now. But the problem is caused by the fact that search_form_block plugins should add a config dependency on their page_id configuration entity. So we should create a search issue for that and then we can remove the enforced dependency.

alexpott’s picture

jhodgdon’s picture

Status: Needs review » Reviewed & tested by the community

Interdiff looks good, assuming your uninstall test still passes. Back to RTBC (provisionally). I (obviously) wasn't aware that we couldn't make changes outside of help_topics, but that does indeed make sense!

I will also add a note about the doc block I fixed in that other issue.

jhodgdon’s picture

Actually created #3086832: SearchPageRepositoryInterface doc block error for the doc block issue.

jhodgdon’s picture

Actually, this patch will not work for all cases.... It takes care of the case of uninstalling Help Topics module, but it doesn't take care of the case of disabling the search page. We need to fix core Search module for that. I added a note to the other issue. I don't think the config dependency is the right/only fix -- we need what was in my earlier patch.

But if we can't take care of the real problem in this patch... not sure if we can have this as RTBC with this bug outstanding?

jhodgdon’s picture

Discussed this with alexpott in Slack.

The conclusion:
- Having a WSOD on admin/help when you disable the Help Topics module was a blocker. That is fixed by this patch.
- Having a problem when you disable the help search page on admin/search/pages is not a blocker. So, created this follow-up issue for the core Search module: #3086846: Deprecate ability to disable search pages (no reason for it and causes problems and code complexity)

Amber Himes Matz’s picture

Ok looks like @jhodgdon had the same idea. I closed my follow-up issues as duplicates. The active follow-up UX issues are already set up as child issues of this one.

larowlan’s picture

+++ b/core/modules/help_topics/help_topics.install
@@ -0,0 +1,51 @@
+    'indexes' => [
+      'section_plugin_id' => ['section_plugin_id'],
+      'topic_id' => ['topic_id'],

+++ b/core/modules/help_topics/src/Plugin/Search/HelpSearch.php
@@ -0,0 +1,489 @@
+      ->condition('permission', '', '<>')
...
+      $query->condition('hsi.permission', $denied_permissions, 'NOT IN');

should we have an index on the permissions column?

larowlan’s picture

Discussed #221 with @Charlie ChX Negyesi in slack who indicated that the query wasn't going to be helped by an index, given its current nature

larowlan’s picture

  • larowlan committed f702e8c on 8.8.x
    Issue #2664830 by jhodgdon, alexpott, andypost, Charlie ChX Negyesi,...
larowlan’s picture

Status: Reviewed & tested by the community » Fixed

Committed f702e8c and pushed to 8.8.x. Thanks!

Published the change record.

Creating a 'mark help topics module beta stable' issue

Status: Fixed » Closed (fixed)

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