Add support for indexing an entity's Metatag field with Search API.

Issue fork metatag-2901039

Command icon Show commands

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

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

Comments

DamienMcKenna created an issue. See original summary.

tsplash’s picture

Status: Active » Needs review
StatusFileSize
new5.11 KB

This patch adds a new module to support the search_api. It includes a processor plugin which stores the basic metatags saved to an entity. The same principle could be applied to store other metatags if needed, but thought the basics would be a good start.

merilainen’s picture

I don't see any difference in search results after enabling the module. I suppose the processor should not be visible in the processors list because it's marked as hidden.
It would be nice to be able to boost the different properties because they are probably quite relevant for search as they are used for SEO too.

natemow’s picture

StatusFileSize
new89.36 KB

@mErilainen -- I was able to add meta keywords and descriptions as new index fields after enabling the module, then set the type to fulltext and boosted as needed. Thanks for the patch @tsplash!

merilainen’s picture

Status: Needs review » Needs work

Yes, I realized it too that they have to be added as fields! This is perfect, everything can be boosted separately, just what I was looking for.

But the patch has some coding style issues which should be fixed. I was also getting a lot of notices for nodes which don't have any values saved for the metatag field (metatag has been enabled after content was created).

This can be easily fixed by checking if there is something to unsearialize:

<?php
if (isset($metatags[0]['value'])) {
  $metatags = (unserialize($metatags[0]['value']));
}
?>
merilainen’s picture

Assigned: Unassigned » merilainen
Status: Needs work » Needs review
StatusFileSize
new5.27 KB

Here is an updated patch with coding style fixes and the check for values array to avoid notices while indexing.

damienmckenna’s picture

Status: Needs review » Needs work

This is a great step forwards, thank you.

Two requests:

  • Could you please make this a patch to the main module instead of a submodule? I don't think it needs to be a submodule, given it just sits in its own plugin.
  • Could you please use Metatag's APIs to dynamically work out what tags are available instead of hardcoding it to only supporting a few tags?

Thank you both.

merilainen’s picture

Not sure what is the best way to implement this. I managed to get all the tags using the metatag.manager service:

$sorted_tags = \Drupal::service('metatag.manager')->sortedGroupsWithTags();

But there is no information about which fields are probably comma separated like the keywords field is for example.

Also I'm not sure if there are just too many tags which will make the Search API UI bloated. In my current setup I have mobile, open_graph and verification sub-modules enabled in addition to metatag core tags and I get 141 tags. I think most of them are improving machine readability of the content and therefore useless for search index, such as image urls, dimensions etc. Most of the tags which actually have human readable content are usually using field tokens which means they can be indexed to Search API using field properties. So some higher level decision should be done how this could be handled, I guess.

tsplash’s picture

Thank you both for the feedback.

Here is an updated patch so the processor plugin is included in the base metatag module (good point there @DamienMcKenna). It also includes mErilainen's improvements (thank you for those).

@DamienMcKenna regarding you second request around dynamically identifying which tags are available, I agree with mErilainen's points. Unless the metatag api provided information on how the values are entered/stored, we will need to include some hard coded logic for many tags. Additionally as we are including this plugin in the base module, this logic could end up referring to tags which exist in the sub-modules. Also agree on the point around sheer number of available tags.

Should each submodule contain a plugin for the metatags it provides, or is this perhaps a bit overkill? For example from the tags provided by the base module, I could possibly see a need for the 'Geographical place name', 'News Keywords' and perhaps the 'Standout' metatags (which we could add) but not much else.

tsplash’s picture

Assigned: merilainen » Unassigned
Status: Needs work » Needs review
damienmckenna’s picture

Status: Needs review » Needs work

@tsplash: Thanks for the updated patch.

I would honestly take a step back and start over by asking some questions about this proposed functionality:

  • What is the goal of having Metatag integrate with Search API?
  • What are the specific use cases that this functionality would resolve?
tsplash’s picture

Status: Needs work » Needs review
StatusFileSize
new2.79 KB

Attached patch is an entire rewrite, which hopefully resolves the issues at hand.

Initially I had used a processor plugin to create the search_api fields dynamically as the metatag FieldType doesn't have the tags mapped as propertyDefinitions. The search_api looks at the propertyDefinitions on a field and makes them available through the UI in a nice collapsible menu. This would be perfect for the amount of tags available and resolves the UI bloating mErilainen was highlighting with the original solution.

From a bit of research other modules have solved this by defining computed properties. This patch does this is a dynamic manner, making all tags available as computed properties.

This does leave open the point around comma delimited tags, as each tag is indexed as a single value. However these could then be split with an additional processor or preprocess function. My gut feeling is that this wouldn't be the responsibility of our integration.

What do you guys think?

tsplash’s picture

Couple mistakes in the previous patch's comments, apologies.

merilainen’s picture

As for use cases, I was about to add a hidden field for "Search result description" to get some text into the search index which would not be visible in the content entity at all. Then I remembered that several pages already had a meta title + description which Google for example displays as the summary when you see the page in the results. And then I found this issue which would solve my problem and didn't need to add another field for metadata.

Also as a special case, we have some pages which have React components which render content from external API or elasticsearch, so they don't actually have much Drupal content other than a title, so this solves also that case to get something into the search index without adding hidden fields.

merilainen’s picture

I think the new approach looks very nice. Here is a patch with some code style fixes.

Status: Needs review » Needs work

The last submitted patch, 15: 2901039-add-support-for-search-api--15.patch, failed testing. View results

merilainen’s picture

Fixed patch format.

merilainen’s picture

Status: Needs work » Needs review
mpp’s picture

Thanks for exposing the fields to Search API.

What about providing deeper integration with Search API, like excluding content from the index when the metatag noindex is checked?

merilainen’s picture

There are other ways to exclude items and I would not want to assume that noindex should also exclude content automatically from Search API without control which might be used for internal searches, for authenticated users etc.

mpp’s picture

In that case I'd say this is RTBC.

I do have these small remarks:

/**
+ * Cached processed value.
+ *
+ * @var string|null
+ */
+ protected $value = NULL;

=> no need to indicate null or set a variable to NULL.

+ if ($definition->getSetting('tag name') === NULL) {
=> why not use is_null()?

AMDandy’s picture

This patch seems to have broken on the latest dev release. It worked for our needs and I look forward to it being merged so I can update metatag.

damienmckenna’s picture

FYI the tests are currently failing because of #3043263: Missing getActiveDefinition on EntityManagerWrapper due to changes in 8.7, when Devel gets a new release we'll be able to check the tests again and see if maybe something changed somewhere.

damienmckenna’s picture

I changed the default tests to work against 8.6.x (#3042739: Fix tests on Metatag 8.x-1.x branch), so lets see what the testbot says.

jenniferhoude’s picture

Tried to use patch 2901039-add-support-for-search-api--16.patch. Can now see the meta tag field available in the search api fields listing. Rebuilt the index and re-indexed. But the the search results are not showing anything when searching for that keyword.

mpp’s picture

Hi @jenniferaube, welcome to drupal.org. I have confirmed your account so you no longer need to enter capcha's when posting comments.

Did you add the field as a full text field?
Have a look at the search view and make sure you checked the field in list of fields to use for the fulltext filter.

wannesdr’s picture

Assigned: Unassigned » wannesdr
Status: Needs review » Needs work
wannesdr’s picture

I applied the feedback from @mpp in comment #21. But I will leave the === NULL since this seems the preferred way in Core. As long as the check is strict it is good.

I also changed the setting tag name to tag_name (without the space). We don't like seeing spaces in setting keys.

Up next we will add a kernel test for the typedData.

Status: Needs review » Needs work

The last submitted patch, 28: 2901039-28.patch, failed testing. View results

wannesdr’s picture

Status: Needs work » Needs review
StatusFileSize
new655 bytes
new1.7 KB

Missed one of the tag name setting keys.  🙈

mpp’s picture

Looks like part of the patch is missing :-)

wannesdr’s picture

StatusFileSize
new2.69 KB

I uploaded the wrong file.
(Guess I need coffee...)

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

I spent the last 4 hours working on this with @wannesderoy and we can't figure out how to write a kernel test for this, especially not because most of things we would be testing would be things that test the typed data layer. We could add a test to check that if we pass in a value to setValue that it gets returned from getValue.

That is again a way to test the typed data api's, and I don't think it makes sense to do that.
We manually tested that this at least exposes typed data and that seemed to be working as expected.

Based on that I'm setting this to RTBC - at least to surface the problem to @DamienMcKenna, but it might be that this is already good enough without testcoverage.

borisson_’s picture

Title: Add support for Search API » Add a TypedData plugin to support Search API

Retitling as well to be more explicit.

mpp’s picture

#34: @wannesderoy, @borisson_, great work!

#32: @wannesderoy, Happens to the best ;-)

In fact if I had been paying some more attention in #21, I would have noticed this earlier:
+ ->setLabel(t('@label', ['@label' => $tag['label']]))

This feels wrong. I don't have an IDE at hand so I can't immediately see what class the $tag is but we should probably get the label somewhere up in the stack from calling sortedGroupsWithTags (I assume it's the plugin annotation's label?).

damienmckenna’s picture

Assigned: wannesdr » Unassigned
damienmckenna’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

We could add a test to check that if we pass in a value to setValue that it gets returned from getValue.

That is again a way to test the typed data api's, and I don't think it makes sense to do that.

While yes, test coverage for this would (in part) test the typed data system itself, it would also test how the values from Metatag are output.

->setLabel(t('@label', ['@label' => $tag['label']]))

I agree with mpp in #35, this doesn't feel right; at the very least it should be $this->t() instead of just t().

+      throw new \InvalidArgumentException("The definition's 'tag_name' key has to specify the name of the metatag to be processed.");

The spelling should be "meta tag", two separate words.

klmd302’s picture

Thank you for this patch! However I encountered a couple of issues with it that I wasn't sure how to solve.
1. If a meta tag on a node is using the default value configured at /admin/config/search/metatag, no value is indexed for that field.
2. If a meta tag contains tokens, only the token name is indexed, rather than the content it refers to.

damienmckenna’s picture

#38 indicates that the meta tags need to be rendered prior to indexing, the Metatag field should not be just included as-is.

rcodina’s picture

Status: Needs work » Needs review
StatusFileSize
new2.75 KB
new1.24 KB

I upload a new version of patch on #32. Changes:

  1. I try to solve problems described on #38 driven by #39
  2. I fix the typo described on #37. But $this->t() cannot be done because that method is static.
rcodina’s picture

StatusFileSize
new2.69 KB
new396 bytes

I upload a new patch to remove a debugging line I missed to remove.

rcodina’s picture

@DamienMcKenna Is there a way to render metatags in a more performant way? After my patch, I think the indexing performs much worse.

damienmckenna’s picture

Is there a better way of doing it? It's doing a lot of extra processing, and it'd only get slower as more meta tags are added by installing more submodules.

damienmckenna’s picture

Would it be better to process all of the tags in one go rather than processing them individually?

rcodina’s picture

StatusFileSize
new3.87 KB
new1.13 KB

@DamienMcKenna I tried a cache system approach given I need this feature ASAP and I think the approach you suggested would take me much more time.

rcodina’s picture

damienmckenna’s picture

Status: Needs review » Needs work

Let's put this back on the "needs work" pile as it feels like it'd be a massive performance hit.

rcodina’s picture

Status: Needs work » Needs review

@DamienMcKenna With patch on #45, the performance is back to normal. With my test data, with patch on #41 it indexed on 6 minutes and with patch on #45 it indexed on 4 minutes (which is normal).

rcodina’s picture

Status: Needs review » Needs work

@DamienMcKenna You are right, there are still performance issues. I just did a performance test and these are the results:

1 minute 12 seconds (patch #32)
3 minutes 50 seconds (patch #45)

rcodina’s picture

@DamienMcKenna Please, could you do your own performance tests? Sometimes, patch on #45 performs much faster. I don't know if there is a special method to measure performance on Search API indexing (maybe I'm doing something wrong).

On the other hand, any clue about how to modify the patch to process all of the tags in one go?

rcodina’s picture

@DamienMcKenna I've repeated performance tests and it seems that performance is better than I thought:

My measurements on #49 were wrong because I did them on a local vagrant multisite environment and I measured times via UI indexing. Now I modified the same multisite environment to run via DDEV (Docker) and I measured times via terminal. Also, In previous analysis I didn't cleared the cache and the index before every measurement (now I do).

Before each run I do:

drush cr
drush search-api-clear

Time measuring command:
drush search-api-index multisite_search_index

Results:

1. 2660 elements to index (same site as #49):

patch #32, 59.28 seconds
patch #45, 1min and 1.15 seconds

2. 10266 elements to index (another site)

patch #32, 1min and 20.58 seconds
patch #45, 1min and 25.79 seconds

rcodina’s picture

Status: Needs work » Needs review
StatusFileSize
new2.89 KB
new1.67 KB

Changes from patch on #45:
- Removed cache code on metatag_generate_entity_metatags method. This code was introduced in #45 and produces a language bug from which metatags can be rendered in another language!
- Removed usage of metatag_generate_entity_metatags method to render metatags, custom code is used instead (only renders the desired metatag)

Please, review this new version!

weekbeforenext’s picture

StatusFileSize
new4.59 KB
new1.93 KB
  • Added conditions to allow the "href" attribute to be used as the value for the metatag when "content" doesn't exist.
  • Added some logging and conditions to catch undefined index notices and improve the troubleshooting experience.
damienmckenna’s picture

Thanks April!
BTW did you mean to include the metatag_async.js file?

weekbeforenext’s picture

StatusFileSize
new4.05 KB
new1.87 KB

Oops! Tested this patch before upload.

rcodina’s picture

@DamienMcKenna @weekbeforenext Patch #55 works fine!

damienmckenna’s picture

Status: Needs review » Needs work
Related issues: +#3302969: Add refactored MetaNameBase changes from test refactoring

I think this should follow the new API from #3302969, specifically the htmlValueAttribute attribute, to determine what element to use from the HTML tag.

damienmckenna’s picture

Assigned: Unassigned » damienmckenna

I'm working on this.

damienmckenna’s picture

I manually tested this. It appears to work well.

Suggested improvements:
* Documentation is needed for the README.md file.
* Rework to use the tag plugin's htmlValueAttribute; this is a protected attribute so maybe add a getter?
* As a separate issue, maybe add a way of getting all meta tag values in one go?

damienmckenna’s picture

Status: Needs work » Needs review
StatusFileSize
new2.94 KB
new4.96 KB

This reworks the logic to use a new method that uses the tag's htmlValueAttribute value.

damienmckenna’s picture

Status: Needs review » Needs work

Still to do:
* Test coverage.
* Documentation is needed for the README.md file.
* Create a separate issue for improving the UI so that each meta tag doesn't have to be individually added as a separate item.

I also wonder if this would work for complex data structures, like the Schema Metatag values? It might need further testing.

damienmckenna’s picture

Status: Needs work » Needs review
StatusFileSize
new5 KB

I renamed the class to Drupal\metatag\TypedData\IndividualTag instead of Drupal\metatag\TypedData\Metatags to make it more clear what it's for.

damienmckenna’s picture

Status: Needs review » Needs work
damienmckenna’s picture

Status: Needs work » Needs review
Related issues: +#3224311: Make string fields default to "fulltext" field type

Now with added documentation.

I could not see how to make Search API use the "fulltext" field type for Metatag values, it defaults to "string" which is not what most people will want, see #3224311: Make string fields default to "fulltext" field type for details; I noted this in the docs.

damienmckenna’s picture

StatusFileSize
new2.64 KB
new7.64 KB

Oh yeah, the patch file.

damienmckenna’s picture

I spun off a separate issue to look into changing the default data type to improve the UX: #3311389

  • DamienMcKenna committed 5476285 on 8.x-1.x
    Issue #2901039 by rcodina, tsplash, DamienMcKenna, Wannes DR, mErilainen...
damienmckenna’s picture

Status: Needs review » Fixed
Issue tags: -Needs tests
Parent issue: » #3296327: Plan for Metatag 8.x-1.22

I looked at the test coverage and it's a bit tricker because of the AJAX bit to load the form, so let's do the test coverage in #3311544.

Committed. Thanks everyone!

damienmckenna’s picture

Assigned: damienmckenna » Unassigned

Status: Fixed » Closed (fixed)

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

matt_zimo’s picture

I'm getting this deprecation warning when I drush cr:
Deprecated: Drupal\metatag\TypedData\Metatags::__construct(): Implicitly marking parameter $parent as nullable is deprecated, the explicit nullable type must be used instead in /var/www/html/docroot/modules/contrib/metatag/src/TypedData/Metatags.php on line 31

damienmckenna’s picture

Please open a new bug report for that. Thank you.

matt_zimo’s picture

OK. I made it here. It's my first time making a bug report here, so apologies if its formatted incorrectly.