From 84871171bd42229a91d230ccafe5301d2dc70c76 Mon Sep 17 00:00:00 2001 From: Nathan McBride Date: Fri, 14 Apr 2023 20:52:04 +1000 Subject: [PATCH 1/4] Allow users to save configuration and allow autocomplete only 1. Allow users to save Algolia config without error with incorrect credentials 2. Fix category facets 3. Nuke collections and rebuild when configuration changes 4. Allow user to only enable autocomplete --- Helper/ConfigChangeHelper.php | 18 +++++ .../AlgoliaSearch/Model/SaveSettings.php | 41 ++++++++++++ Services/ConfigService.php | 25 +++++-- ViewModel/Adminhtml/Configuration.php | 32 +++++++++ ViewModel/Form.php | 1 + etc/adminhtml/di.xml | 6 +- etc/adminhtml/events.xml | 20 +++++- .../layout/adminhtml_system_config_edit.xml | 11 ++++ view/adminhtml/templates/configuration.phtml | 66 +++++++++++++++++++ 9 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php create mode 100644 ViewModel/Adminhtml/Configuration.php create mode 100644 view/adminhtml/layout/adminhtml_system_config_edit.xml create mode 100644 view/adminhtml/templates/configuration.phtml diff --git a/Helper/ConfigChangeHelper.php b/Helper/ConfigChangeHelper.php index 633e501..f0b8c57 100644 --- a/Helper/ConfigChangeHelper.php +++ b/Helper/ConfigChangeHelper.php @@ -103,6 +103,9 @@ public function __construct( */ public function setCollectionConfig() { + if (!$this->configService->isTypeSenseEnabled()) { + return $this; + } $facets = []; @@ -128,6 +131,11 @@ public function setCollectionConfig() $indexName = $index["indexName"] . "_{$indexToCreate}"; + if (isset($existingCollections[$indexName])) { + $this->typesenseClient->collections[$indexName]->delete(); + unset($existingCollections[$indexName]); + } + if (!isset($existingCollections[$indexName])) { $this->typeSenseCollecitons->create( @@ -186,6 +194,16 @@ public function getFields(array $facets, array $sortingAttributes, string $index ['name' => 'visibility_catalog', 'type' => 'int64', 'facet' => true] ]; + // The hierarchal menu widget expects 10 levels of category. + for ($i = 0; $i < 10; $i++) { + $defaultAttributes[] = [ + 'name' => 'categories.level'.$i, + 'type' => 'string[]', + 'facet' => true, + 'optional' => true + ]; + } + break; case 'categories': $attributes = $this->algoliaConfigHelper->getCategoryAdditionalAttributes(); diff --git a/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php b/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php new file mode 100644 index 0000000..9f0625e --- /dev/null +++ b/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php @@ -0,0 +1,41 @@ +configService = $configService; + } + + public function aroundExecute( + \Algolia\AlgoliaSearch\Model\Observer\SaveSettings $subject, + \Closure $proceed + ) { + if($this->configService->isIndexModeTypeSenseOnly()){ + return true; + } + + $result = $proceed(); + + return $result; + } +} diff --git a/Services/ConfigService.php b/Services/ConfigService.php index a060ef6..b9ef61c 100644 --- a/Services/ConfigService.php +++ b/Services/ConfigService.php @@ -32,7 +32,7 @@ class ConfigService * @var EncryptorInterface $encryptor */ protected EncryptorInterface $encryptor; - + /** * @param EncryptorInterface $encryptor * @param ScopeConfigInterface $scopeConfig @@ -121,13 +121,24 @@ public function getIndexMethod(): ?string } /** - * Check if Typesense Index Mode is TypesenseOnly + * @return bool */ public function isIndexModeTypeSenseOnly(){ - $indexMethod = $this->getIndexMethod(); - if( $indexMethod == TypeSenseIndexMethod::METHOD_TYPESENSE ){ - return true; - } - return false; + return $this->getIndexMethod() === TypeSenseIndexMethod::METHOD_TYPESENSE; + } + + /** + * @return bool + */ + public function isIndexModeBoth(){ + return $this->getIndexMethod() === TypeSenseIndexMethod::METHOD_BOTH; + } + + /** + * @return bool + */ + public function isTypeSenseEnabled() + { + return $this->isEnabled() && ($this->isIndexModeTypeSenseOnly() || $this->isIndexModeBoth()); } } diff --git a/ViewModel/Adminhtml/Configuration.php b/ViewModel/Adminhtml/Configuration.php new file mode 100644 index 0000000..d27506a --- /dev/null +++ b/ViewModel/Adminhtml/Configuration.php @@ -0,0 +1,32 @@ +configService = $configService; + } + + /** + * @return bool + */ + public function isTypeSenseEnabled() + { + return $this->configService->isTypeSenseEnabled(); + } +} diff --git a/ViewModel/Form.php b/ViewModel/Form.php index 0f5e29a..225eeeb 100644 --- a/ViewModel/Form.php +++ b/ViewModel/Form.php @@ -63,6 +63,7 @@ public function getJsConfig(): string public function getAutocompleteScripts() { return $this->json->serialize( [ + $this->getAssetUrl('Hyva_AlgoliaSearch::js/internals/algoliaBundle.min.js'), $this->getAssetUrl('Hyva_AlgoliaSearch::js/internals/autocomplete-js.js'), $this->getAssetUrl('Hyva_AlgoliaSearch::js/internals/algoliasearch.js'), $this->getAssetUrl('Hyva_AlgoliaSearch::js/internals/algoliasearch-query-suggestion-plugin.js') diff --git a/etc/adminhtml/di.xml b/etc/adminhtml/di.xml index 95e348a..4ba2dd6 100644 --- a/etc/adminhtml/di.xml +++ b/etc/adminhtml/di.xml @@ -12,4 +12,8 @@ - \ No newline at end of file + + + + + diff --git a/etc/adminhtml/events.xml b/etc/adminhtml/events.xml index ff83349..7c2fb04 100644 --- a/etc/adminhtml/events.xml +++ b/etc/adminhtml/events.xml @@ -1,6 +1,22 @@ - + - \ No newline at end of file + + + + + + + + + + + + + + + + diff --git a/view/adminhtml/layout/adminhtml_system_config_edit.xml b/view/adminhtml/layout/adminhtml_system_config_edit.xml new file mode 100644 index 0000000..5a79ba3 --- /dev/null +++ b/view/adminhtml/layout/adminhtml_system_config_edit.xml @@ -0,0 +1,11 @@ + + + + + + + Develo\Typesense\ViewModel\Adminhtml\Configuration + + + + diff --git a/view/adminhtml/templates/configuration.phtml b/view/adminhtml/templates/configuration.phtml new file mode 100644 index 0000000..6222727 --- /dev/null +++ b/view/adminhtml/templates/configuration.phtml @@ -0,0 +1,66 @@ +getData('view_model'); +if (!$viewModel instanceof Configuration) { + return ''; +} + +if (!$viewModel->isTypeSenseEnabled()) { + return ''; +} + +?> + + From 73f586410b1ea7c975991681195beedf33497748 Mon Sep 17 00:00:00 2001 From: Nathan McBride Date: Fri, 14 Apr 2023 21:22:30 +1000 Subject: [PATCH 2/4] Allow for facetting --- Adapter/Client.php | 37 +++++++++++++++++++++++++++-------- Helper/ConfigChangeHelper.php | 27 ++++++++++--------------- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/Adapter/Client.php b/Adapter/Client.php index 8355e6e..54dd82e 100644 --- a/Adapter/Client.php +++ b/Adapter/Client.php @@ -2,6 +2,7 @@ namespace Develo\Typesense\Adapter; +use Algolia\AlgoliaSearch\Helper\ConfigHelper as AlgoliaConfigHelper; use Typesense\Client as TypeSenseClient; use Develo\Typesense\Services\ConfigService; use Algolia\AlgoliaSearch\Helper\Data as AlgoliaHelper; @@ -16,20 +17,18 @@ */ class Client { + private ?array $facets = null; + /** * @var ConfigService */ private ConfigService $configService; - /** - * @var TypeSenseClient|null - */ private ?TypeSenseClient $typeSenseClient = null; - /** - * $var AlgoliaHelper - */ - private $algoliaHelper; + private AlgoliaHelper $algoliaHelper; + + private AlgoliaConfigHelper $configHelper; /** * Initialise Typesense Client with Magento config @@ -40,11 +39,13 @@ class Client */ public function __construct( ConfigService $configService, - AlgoliaHelper $algoliaHelper + AlgoliaHelper $algoliaHelper, + AlgoliaConfigHelper $configHelper ) { $this->configService = $configService; $this->algoliaHelper = $algoliaHelper; + $this->configHelper = $configHelper; } /** @@ -63,6 +64,7 @@ public function deleteIndex(string $indexName): array */ public function addData($indexName, $data) { + $facets = []; foreach ($data as &$item) { $item['id'] = (string)$item['objectID']; $item['objectID'] = (string)$item['objectID']; @@ -83,7 +85,14 @@ public function addData($indexName, $data) $price['default'] = number_format($price['default'], 2); } + + foreach ($facets as $facet) { + if (isset($item[$facet]) && !is_array($item[$facet])) { + $item[$facet] = [strval($item[$facet])]; + } + } } + $indexName = rtrim($indexName, "_tmp"); return $this->getTypesenseClient()->collections[$indexName]->getDocuments()->import($data, ['action' => 'upsert']); } @@ -140,6 +149,18 @@ public function getTypesenseClient(): TypeSenseClient return $this->typeSenseClient; } + private function getFacets() + { + if (!is_array($this->facets)) { + $this->facets = []; + foreach ($this->configHelper->getFacets() as $facet) { + $this->facets[] = $facet['attribute']; + } + } + + return $this->facets; + } + } diff --git a/Helper/ConfigChangeHelper.php b/Helper/ConfigChangeHelper.php index f0b8c57..db612ac 100644 --- a/Helper/ConfigChangeHelper.php +++ b/Helper/ConfigChangeHelper.php @@ -235,21 +235,8 @@ public function getFields(array $facets, array $sortingAttributes, string $index $attributeCollection = $this->attributeRepository->getList($entityTypeCode, $searchCriteria->create()); - $backendTypes = [ - 'datetime' => 'string', - 'decimal' => 'float', - 'int' => 'int64', - 'static' => 'string', - 'text' => 'string', - 'varchar' => 'string' - ]; - $fields = []; foreach ($attributeCollection->getItems() as $attribute) { - if (!isset($backendTypes[$attribute->getBackendType()]) || !$attribute->getIsRequired()) { - continue; - } - if ($attribute->getAttributeCode() === 'price') { $fields[] = [ 'name' => $attribute->getAttributeCode(), @@ -276,12 +263,18 @@ public function getFields(array $facets, array $sortingAttributes, string $index continue; } + $isFacet = in_array($attribute->getAttributeCode(), $facets); + + if (!$isFacet) { + continue; + } + $fields[] = [ 'name' => $attribute->getAttributeCode(), - 'type' => $backendTypes[$attribute->getBackendType()], - 'facet' => in_array($attribute->getAttributeCode(), $facets), - 'sort' => in_array($attribute->getAttributeCode(), $sortingAttributes) && - in_array($backendTypes[$attribute->getBackendType()], self::SORTABLE_ATTRIBUTES), + 'type' => 'string[]', + 'facet' => $isFacet, + 'sort' => false, + 'optional' => !$attribute->getIsRequired() ]; } From d23ded0b3c7510c1c11809a3a94bcb31ededc5f7 Mon Sep 17 00:00:00 2001 From: Nathan McBride Date: Fri, 14 Apr 2023 21:26:34 +1000 Subject: [PATCH 3/4] Clean up code slightly --- Helper/ConfigChangeHelper.php | 24 +++++++++---------- .../AlgoliaSearch/Model/SaveSettings.php | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Helper/ConfigChangeHelper.php b/Helper/ConfigChangeHelper.php index db612ac..270e888 100644 --- a/Helper/ConfigChangeHelper.php +++ b/Helper/ConfigChangeHelper.php @@ -136,18 +136,15 @@ public function setCollectionConfig() unset($existingCollections[$indexName]); } - if (!isset($existingCollections[$indexName])) { - $this->typeSenseCollecitons->create( - [ - 'name' => $indexName, - 'enable_nested_fields' => true, - 'fields' => $fields - ] - ); + $this->typeSenseCollecitons->create( + [ + 'name' => $indexName, + 'enable_nested_fields' => true, + 'fields' => $fields + ] + ); - continue; - } } } @@ -182,7 +179,8 @@ private function getMagentoIndexes() return $indexNames; } - public function getFields(array $facets, array $sortingAttributes, string $index) : array { + public function getFields(array $facets, array $sortingAttributes, string $index): array + { switch ($index) { case 'products': $attributes = $this->algoliaConfigHelper->getProductAdditionalAttributes(); @@ -197,7 +195,7 @@ public function getFields(array $facets, array $sortingAttributes, string $index // The hierarchal menu widget expects 10 levels of category. for ($i = 0; $i < 10; $i++) { $defaultAttributes[] = [ - 'name' => 'categories.level'.$i, + 'name' => 'categories.level' . $i, 'type' => 'string[]', 'facet' => true, 'optional' => true @@ -285,7 +283,7 @@ public function getFields(array $facets, array $sortingAttributes, string $index return array_values($fields); } - public function getSearchableAttributes(string $index = self::INDEX_PRODUCTS) : string + public function getSearchableAttributes(string $index = self::INDEX_PRODUCTS): string { $attributes = []; foreach ($this->getFields([], [], $index) as $field) { diff --git a/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php b/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php index 9f0625e..b1b02d0 100644 --- a/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php +++ b/Plugin/Algolia/AlgoliaSearch/Model/SaveSettings.php @@ -31,7 +31,7 @@ public function aroundExecute( \Closure $proceed ) { if($this->configService->isIndexModeTypeSenseOnly()){ - return true; + return; } $result = $proceed(); From 61593f9bd6960e254cdb5e3c46d18ca12a99400f Mon Sep 17 00:00:00 2001 From: Nathan McBride Date: Fri, 21 Apr 2023 20:06:50 +1000 Subject: [PATCH 4/4] Update README.md for Typesense Module --- README.md | 136 ++++++++++++++++++++++-------------------------------- 1 file changed, 54 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 53913fc..d0410ba 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,87 @@ - - +# Magento 2 Typesense Search Integration Module - -
-
-

Magento 2 Typesense Adapter Module

+This module integrates the Typesense search engine with Magento, providing faster and more accurate search results for your customers. -

-
-

This is currently just a proof of concept!

-
- It's an Adapter Client for the main Algolia Magento 2 module. -
- We let the existing Open Source Algolia module handle indexing, queues etc, when the data is ready to be indexed it's pushed Typesense. -
- Why reinvent the wheel? -

-
+## Installation +### Composer Installation +You can install the module via Composer. Run the following command in your Magento 2 root directory: - -
- Table of Contents -
    -
  1. - Getting Started - -
  2. -
  3. Roadmap
  4. -
  5. Contributing
  6. -
  7. License
  8. -
  9. Contact
  10. -
  11. Acknowledgments
  12. -
-
+``` +composer require develodesign/magento2-module-typesense +``` - -## Getting Started +### Copying the Module -Composer install this module, it will include the Algolia module as a dependancy. +Alternatively, you can copy the module files to the `app/code/Develo/Typesense` directory in your Magento 2 installation. -### Installation +``` +php bin/magento module:enable Develo_Typesense +php bin/magento setup:upgrade +php bin/magento setup:di:compile +bin/magento setup:static-content:deploy +``` - ```shell - composer require develodesign/magento2-module-typesense - ``` - - Add Typesene Configuration - - System Config -> Type Sense -> Settings -> General +That's it! The develodesign/magento2-module-typesense module is now installed on your Magento 2 store. -

(back to top)

+## Configuration +System > Configuration > General > Typesense Search: - -## Roadmap +- "Enabled": A yes/no field to enable or disable the Typesense adapter. +- "Cloud ID": A text field to enter the Typesense cloud ID. +- "Admin API Key": A secret key to enter the Typesense admin API key. +- "Search Only Key": A public key to enter the Typesense search only key. +- "Nodes": A text field to enter the Typesense nodes. +- "Port": A text field to enter the Typesense port number. +- "Path": A text field to enter the Typesense path. +- "Protocol": A dropdown field to select the communication protocol. +- "Index Method": A dropdown field to select where the data should be indexed. -- [x] Create Basic module and Config -- [x] Index Product Data -- [ ] Index Categories -- [ ] Admin Facets -- [ ] Loads More .... +These options allow users to configure the Typesense adapter module and customize its behavior according to their needs. -

(back to top)

+***After enabling the Typesense module, if a user makes any changes to the configuration, the module will need to drop and rebuild the collections. As a result, the user will need to perform a full Magento reindex after making any configuration changes. This is important to keep in mind to ensure that the search results are accurate and up-to-date.*** +Note that users also need to configure the Algolia module to fit you requirements. However, live credentials are not needed as our module acts as an adapter. - -## Contributing +The Typesense module uses the Algolia settings, so users should configure Algolia as they normally would. It's important to note that if you set a facet, you must also set it in the product attribute section. -Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. +For more information on customizing the Algolia module, please refer to the following links: -If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". -Don't forget to give the project a star! Thanks again! +- [Customizing Autocomplete Menu](https://www.algolia.com/doc/integration/magento-2/customize/autocomplete-menu/) +- [Customizing Instant Search Page](https://www.algolia.com/doc/integration/magento-2/customize/instant-search-page/) +- [Customizing Custom Front-end Events](https://www.algolia.com/doc/integration/magento-2/customize/custom-front-end-events/) -1. Fork the Project -2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) -3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) -4. Push to the Branch (`git push origin feature/AmazingFeature`) -5. Open a Pull Request +## Documentation -

(back to top)

+For more information about Typesense, check out their [official documentation](https://typesense.org/docs/). +You can also check out [Algolia's Magento 2 module](https://github.com/algolia/algoliasearch-magento-2). +## Contributors - -## License +| Name | Email | Twitter | +| -------------- | ---------------------------------------- | ----------------------------- | +| Luke Collymore | [luke@develodesign.co.uk](mailto:luke@develodesign.co.uk) | [@lukecollymore](https://twitter.com/lukecollymore) | +| Nathan McBride | [nathan@brideo.co.uk](mailto:nathan@brideo.co.uk) | [@brideoweb](https://twitter.com/brideoweb) | -Distributed under the NU General Public License. See `LICENSE.txt` for more information. -

(back to top)

+### How to Contribute +Contributions are always welcome. If you have any suggestions or find any issues, please create a GitHub issue or fork the repository and submit a pull request. +Here's how to contribute: - -## Contact -Luke Collymore - [@lukecollymore](https://twitter.com/lukecollymore) - luke@develodesign.co.uk +1. Fork the project. +2. Create your feature branch (`git checkout -b feature/AmazingFeature`). +3. Commit your changes (`git commit -m 'Add some AmazingFeature'`). +4. Push to the branch (`git push origin feature/AmazingFeature`). +5. Open a pull request. -@Nathan McBride - nathan@brideo.co.uk - -Project Link: [https://github.com/develodesign/magento2-module-typesense](https://github.com/develodesign/magento2-module-typesense) - -

(back to top)

- - - ## Acknowledgments + Algolia for creating a great product indexing and search configuration module + * [Algolia Open Source Module](https://github.com/algolia/algoliasearch-magento-2) -* [Best-README-Template](https://github.com/othneildrew/Best-README-Template) -

(back to top)