Smile-SA / elasticsuite

Smile ElasticSuite - Magento 2 merchandising and search engine built on ElasticSearch
https://elasticsuite.io
Open Software License 3.0
762 stars 341 forks source link

Custom Category Attribute > Use in Search popup and Search Result #3427

Open mahesh-makwana-web-vision opened 2 weeks ago

mahesh-makwana-web-vision commented 2 weeks ago

Is there a way to use custom category attributes in the search popup and search page, similar to how they are used for products?

Screenshot 2024-10-28 at 16 28 57

I found this file, but I'm not sure how to modify it to include a custom attribute.

Path: Smile\ElasticsuiteCatalog\Model\ResourceModel\Category\Indexer\Fulltext\Action\Full

Can you please help me make categories searchable by their custom attribute?

The custom attribute is of the text datatype.

cc: @romainruaud @vahonc

Thanks.

romainruaud commented 2 weeks ago

Hi @mahesh-makwana-web-vision : the Search Result page is designed to display only products, so you'll not be able to get categories here easily.

But on the autocompletion box, you can easily have category results if you create your category attribute with "is_searchable=1" in your setup script.

This will tell the search engine to index it and to use it when building search results.

Regards

mahesh-makwana-web-vision commented 2 weeks ago

Hello @romainruaud

So, basically, I need to create a new custom category attribute with the value "is_searchable=1," and that's it.

No other file changes are required, right?

Am I correct, or are there any additional steps needed?

Thanks

romainruaud commented 2 weeks ago

Yep, that's it. And after that, you run a full reindex of the "Elasticsuite Categories fulltext" indexer.

Regards

mahesh-makwana-web-vision commented 2 weeks ago

Let me check and update you.

mahesh-makwana-web-vision commented 2 weeks ago

Yes @romainruaud, The search works when a category custom attribute with "is_searchable=1" is added.

Now, we have a requirement: when typing a product name, we want the category containing that product to appear in the search results. Currently, only the product is showing, but not the associated category.

Is there a way to achieve this?

We planned to use a custom attribute to store all product names in a single field and make it searchable. However, this approach may create issues with synchronization when products are added or updated, as the product names would need to be kept in sync.

Do you have any suggestions for a more efficient solution?

cc: @vahonc

Thanks.

mahesh-makwana-web-vision commented 2 weeks ago

Hello @romainruaud

We created an indexer and stored the product name and SKU in that field. After reindexing, the related categories are not showing in the search results.

Can you please help us?

Thanks.

romainruaud commented 2 weeks ago

HI @mahesh-makwana-web-vision we do not provide support for custom development here.

Best regards

mahesh-makwana-web-vision commented 2 weeks ago

Hello @romainruaud and @vahonc

Below is the code for our custom attribute:

<?php
namespace VendoreName\SearchUpdate\Setup\Patch\Data;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchRevertableInterface;
use Magento\Catalog\Model\Category;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;

/**
 * Class AddCategorySearchAttribute
 *
 */
class AddCategorySearchAttribute implements DataPatchInterface, PatchRevertableInterface
{
    /**
     * @var ModuleDataSetupInterface
     */
    private $moduleDataSetup;
    /**
     * @var EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * Constructor
     *
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        EavSetupFactory $eavSetupFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function apply()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->addAttribute(
            Category::ENTITY,
            'search_content',
            [
                'group' => 'general',
                'label' => 'Search Content',
                'type' => 'text',
                'input'  => 'textarea',
                'user_defined' => true,
                'is_user_defined' => true,
                'required' => false,
                'sort_order' => 30,
                'global' => ScopedAttributeInterface::SCOPE_STORE,
                'used_in_product_listing' => true,
                'backend' => '',
                'system' => false,
                'searchable' => true,
                'filterable' => true,
                'default' => null,
            ]
        );

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    public function revert()
    {
        $this->moduleDataSetup->getConnection()->startSetup();
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->eavSetupFactory->create(['setup' => $this->moduleDataSetup]);
        $eavSetup->removeAttribute(\Magento\Catalog\Model\Product::ENTITY, 'search_content');

        $this->moduleDataSetup->getConnection()->endSetup();
    }

    /**
     * {@inheritdoc}
     */
    public function getAliases()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public static function getDependencies()
    {
        return [
        ];
    }
}

We’ve added the attribute to the Category page using the following form:

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <fieldset name="custom_content">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="label" xsi:type="string">Extra Search Content</item>
                <item name="collapsible" xsi:type="boolean">true</item>
                <item name="sortOrder" xsi:type="number">9999</item>
            </item>
        </argument>
        <field name="search_content">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="sortOrder" xsi:type="number">100</item>
                    <item name="dataType" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Product SKU + Name</item>
                    <item name="formElement" xsi:type="string">textarea</item>
                    <item name="disabled" xsi:type="boolean">true</item>
                    <item name="cols" xsi:type="number">10</item>
                    <item name="rows" xsi:type="number">5</item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

OUTPUT Category Page:

MZ-Analysentechnik-GmbH-ID-402-Categories-Inventory-Catalog-Magento-Admin-10-30-2024_08_57_AM

OUTPUT Catalog Search Index: Screenshot 2024-10-30 at 08 49 57

Database Table Data: Select-eav_attribute-Adminer-10-30-2024_09_14_AM

Select-catalog_eav_attribute-Adminer-10-30-2024_09_16_AM

Debug Log:

[2024-10-30T08:23:33.995178+00:00] main.INFO: Request Success: {"method":"HEAD","uri":"http://localhost:9200/xtz_de_catalog_product","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.00187} []
[2024-10-30T08:23:33.995583+00:00] main.INFO: curl -XHEAD 'http://localhost:9200/xtz_de_catalog_product?pretty=true' [] []
[2024-10-30T08:23:34.007846+00:00] main.INFO: Request Success: {"method":"POST","uri":"http://localhost:9200/xtz_de_thesaurus/_analyze","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.001145} []
[2024-10-30T08:23:34.008053+00:00] main.INFO: curl -XPOST 'http://localhost:9200/xtz_de_thesaurus/_analyze?pretty=true' -d '{"text":"5020 01802","analyzer":"synonym"}' [] []
[2024-10-30T08:23:34.008735+00:00] main.INFO: Request Success: {"method":"POST","uri":"http://localhost:9200/xtz_de_thesaurus/_analyze","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.000372} []
[2024-10-30T08:23:34.008898+00:00] main.INFO: curl -XPOST 'http://localhost:9200/xtz_de_thesaurus/_analyze?pretty=true' -d '{"text":"5020 01802","analyzer":"expansion"}' [] []
[2024-10-30T08:23:34.074397+00:00] main.INFO: Request Success: {"method":"GET","uri":"http://localhost/","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.000605} []
[2024-10-30T08:23:34.074609+00:00] main.INFO: curl -XGET 'http://localhost/?pretty=true' [] []
[2024-10-30T08:23:34.113484+00:00] main.INFO: Request Success: {"method":"POST","uri":"http://localhost:9200/xtz_de_catalog_product/_search","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.037828} []
[2024-10-30T08:23:34.113798+00:00] main.INFO: curl -XPOST 'http://localhost:9200/xtz_de_catalog_product/_search?pretty=true' -d '{"size":5,"sort":[{"search_query.position":{"order":"ASC","missing":"_last","unmapped_type":"keyword","nested":{"path":"search_query","filter":{"terms":{"search_query.query_id":["17707"],"boost":1}}},"mode":"min"}},{"_score":{"order":"desc"}},{"entity_id":{"order":"desc","missing":"_first","unmapped_type":"keyword"}}],"from":0,"query":{"bool":{"filter":{"bool":{"must":[{"term":{"stock.is_in_stock":{"value":true,"boost":1}}},{"terms":{"visibility":[3,4],"boost":1}},{"bool":{"must_not":[{"nested":{"path":"search_query","score_mode":"none","query":{"bool":{"must":[{"term":{"search_query.query_id":{"value":17707,"boost":1}}},{"term":{"search_query.is_blacklisted":{"value":true,"boost":1}}}],"must_not":[],"should":[],"boost":1}},"boost":1}}],"boost":1}}],"must_not":[],"should":[],"boost":1}},"must":{"bool":{"must":[],"must_not":[],"should":[{"bool":{"filter":{"multi_match":{"query":"5020-01802","fields":["search^1"],"minimum_should_match":"95%","tie_breaker":1,"boost":1,"type":"best_fields"}},"must":{"multi_match":{"query":"5020-01802","fields":["search^1","name.standard^5","sku.reference^10","description.standard^9","short_description.standard^9","option_text_manufacturer.standard^10","option_text_length.standard^8","option_text_inner_diameter.standard^8","option_text_packing_brand.standard^10","search.whitespace^10","name.whitespace^50","sku.whitespace^100","description.whitespace^90","short_description.whitespace^90","option_text_manufacturer.whitespace^100","option_text_length.whitespace^80","option_text_inner_diameter.whitespace^80","option_text_packing_brand.whitespace^100","name.sortable^100","sku.sortable^200","option_text_manufacturer.sortable^200","option_text_length.sortable^160","option_text_bet_surface_area_in_m2g.sortable^20","option_text_endcapping.sortable^20","option_text_carbon_load_in_percent_c.sortable^20"],"minimum_should_match":1,"tie_breaker":1.0,"boost":1,"type":"best_fields"}},"_name":"EXACT","boost":1}}],"minimum_should_match":1,"boost":1}},"boost":1}},"track_total_hits":0}' [] []
[2024-10-30T08:23:34.660982+00:00] main.INFO: Request Success: {"method":"HEAD","uri":"http://localhost:9200/xtz_de_catalog_category","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.000948} []
[2024-10-30T08:23:34.661192+00:00] main.INFO: curl -XHEAD 'http://localhost:9200/xtz_de_catalog_category?pretty=true' [] []
[2024-10-30T08:23:34.662509+00:00] main.INFO: Request Success: {"method":"POST","uri":"http://localhost:9200/xtz_de_thesaurus/_analyze","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.000667} []
[2024-10-30T08:23:34.662688+00:00] main.INFO: curl -XPOST 'http://localhost:9200/xtz_de_thesaurus/_analyze?pretty=true' -d '{"text":"5020 01802","analyzer":"synonym"}' [] []
[2024-10-30T08:23:34.663443+00:00] main.INFO: Request Success: {"method":"POST","uri":"http://localhost:9200/xtz_de_thesaurus/_analyze","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.000395} []
[2024-10-30T08:23:34.663605+00:00] main.INFO: curl -XPOST 'http://localhost:9200/xtz_de_thesaurus/_analyze?pretty=true' -d '{"text":"5020 01802","analyzer":"expansion"}' [] []
[2024-10-30T08:23:34.671286+00:00] main.INFO: Request Success: {"method":"POST","uri":"http://localhost:9200/xtz_de_catalog_category/_search","port":"9200","headers":{"Host":["localhost"],"Content-Type":["application/json"],"Accept":["application/json"],"User-Agent":["opensearch-php/2.3.1 (Linux 4.18.0; PHP 8.2.25)"]},"HTTP code":200,"duration":0.004866} []
[2024-10-30T08:23:34.671471+00:00] main.INFO: curl -XPOST 'http://localhost:9200/xtz_de_catalog_category/_search?pretty=true' -d '{"size":3,"sort":[{"_score":{"order":"desc"}},{"entity_id":{"order":"desc","missing":"_first","unmapped_type":"keyword"}}],"from":0,"query":{"bool":{"filter":{"terms":{"is_displayed_in_autocomplete":[true],"boost":1}},"must":{"bool":{"must":[],"must_not":[],"should":[{"bool":{"filter":{"multi_match":{"query":"5020-01802","fields":["search^1"],"minimum_should_match":"95%","tie_breaker":1,"boost":1,"type":"best_fields"}},"must":{"multi_match":{"query":"5020-01802","fields":["search^1","search.whitespace^10"],"minimum_should_match":1,"tie_breaker":1.0,"boost":1,"type":"best_fields"}},"_name":"EXACT","boost":1}},{"bool":{"filter":{"multi_match":{"query":"5020-01802","fields":["search^1"],"minimum_should_match":"95%","tie_breaker":1,"boost":1,"type":"best_fields"}},"must":{"multi_match":{"query":"5020-01802","fields":["search^1","search.whitespace^10"],"minimum_should_match":1,"tie_breaker":1.0,"boost":1,"type":"best_fields"}},"_name":"EXACT","boost":1}}],"minimum_should_match":1,"boost":1}},"boost":1}},"track_total_hits":100000}' [] []

ISSUE:

The content is visible on the Category page and is indexed, but when we perform a search, the related categories are not appear in the search results.

We are using Magento 2.4.7-p2 with ElasticSuite 2.11.9 on Elasticsearch 8.15.3. We’ve implemented the is_searchable=1 attribute as per your prior confirmation, but we’re still facing issues.

Could you please help us troubleshoot this?

Thank you.

romainruaud commented 1 week ago

@mahesh-makwana-web-vision if you are willing to display Categories on the search result page, you'll have to implement this yourself :

mahesh-makwana-web-vision commented 1 week ago

@romainruaud

When we search by SKU or product name, we want the related category to appear in the search results. Currently, if the search string matches, only the category name is displayed.

To address this, I added a custom attribute to the category and stored all product names and SKUs within this attribute. I also set this custom field to is_searchable=1, but the category name still doesn’t appear based on the custom attribute value.

I’ve added the code that was previously shared. Could you please guide me on where I might be going wrong?

Thanks.

romainruaud commented 1 week ago

Hi, I've explained what you should try to do.

You can take inspiration from this module : https://github.com/Smile-SA/magento2-module-elasticsuite-cms-search

Just replace the frontend part which is fetching "PageCollection" with a "CategoryCollection" and you should be all good.

Best regards

mahesh-makwana-web-vision commented 1 week ago

Okay @romainruaud

Let me check and update you.

Thanks.