From 6644249530d73530f81279f97b9e180621440e92 Mon Sep 17 00:00:00 2001 From: Will Rossiter Date: Thu, 13 Jul 2023 15:01:34 +1200 Subject: [PATCH] feat: add includeFilter option for segmented indexing (fixes #33) --- README.md | 233 ++++++++++++++++++---- src/Extensions/AlgoliaObjectExtension.php | 15 +- src/Jobs/AlgoliaReindexAllJob.php | 15 +- src/Service/AlgoliaIndexer.php | 12 +- src/Service/AlgoliaService.php | 58 ++++-- src/Tasks/AlgoliaConfigure.php | 14 +- src/Tasks/AlgoliaReindex.php | 114 +++++------ tests/AlgoliaIndexerTest.php | 3 +- tests/AlgoliaServiceTest.php | 107 ++++++++++ 9 files changed, 432 insertions(+), 139 deletions(-) create mode 100644 tests/AlgoliaServiceTest.php diff --git a/README.md b/README.md index 58073c9..316a2b2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Status](http://img.shields.io/travis/wilr/silverstripe-algolia.svg?style=flat-sq ## Maintainer Contact -* Will Rossiter (@wilr) +- Will Rossiter (@wilr) ## Installation @@ -23,7 +23,7 @@ multiple indexes. :ballot_box_with_check: Integrates into existing versioned workflow. -:ballot_box_with_check: No dependancies on the CMS, supports any DataObject +:ballot_box_with_check: No dependencies on the CMS, supports any DataObject subclass. :ballot_box_with_check: Queued job support for offloading operations to Algolia. @@ -52,25 +52,26 @@ DataObjects. First, sign up for Algolia.com account and install this module. Once installed, Configure the API keys via YAML (environment variables recommended). -*app/_config/algolia.yml* +_app/\_config/algolia.yml_ + ```yml --- Name: algolia After: silverstripe-algolia --- SilverStripe\Core\Injector\Injector: - Wilr\SilverStripe\Algolia\Service\AlgoliaService: - properties: - adminApiKey: '`ALGOLIA_ADMIN_API_KEY`' - searchApiKey: '`ALGOLIA_SEARCH_API_KEY`' - applicationId: '`ALGOLIA_SEARCH_APP_ID`' - indexes: - IndexName: - includeClasses: - - SilverStripe\CMS\Model\SiteTree - indexSettings: - attributesForFaceting: - - 'filterOnly(objectClassName)' + Wilr\SilverStripe\Algolia\Service\AlgoliaService: + properties: + adminApiKey: "`ALGOLIA_ADMIN_API_KEY`" + searchApiKey: "`ALGOLIA_SEARCH_API_KEY`" + applicationId: "`ALGOLIA_SEARCH_APP_ID`" + indexes: + IndexName: + includeClasses: + - SilverStripe\CMS\Model\SiteTree + indexSettings: + attributesForFaceting: + - "filterOnly(objectClassName)" ``` Once the indexes and API keys are configured, run a `dev/build` to update the @@ -92,7 +93,6 @@ ALGOLIA_PREFIX_INDEX_NAME='dev_will' Or for testing with live data on dev use `ALGOLIA_PREFIX_INDEX_NAME='live'` - ### Defining Replica Indexes If your search form provides a sort option (e.g latest or relevance) then you @@ -107,31 +107,31 @@ Name: algolia After: silverstripe-algolia --- SilverStripe\Core\Injector\Injector: - Wilr\SilverStripe\Algolia\Service\AlgoliaService: - properties: - adminApiKey: '`ALGOLIA_ADMIN_API_KEY`' - searchApiKey: '`ALGOLIA_SEARCH_API_KEY`' - applicationId: '`ALGOLIA_SEARCH_APP_ID`' - indexes: - IndexName: - includeClasses: - - SilverStripe\CMS\Model\SiteTree - indexSettings: - attributesForFaceting: - - 'filterOnly(ObjectClassName)' - replicas: - - IndexName_Latest - IndexName_Latest: - indexSettings: - ranking: - - 'desc(objectCreated)' - - 'typo' - - 'words' - - 'filters' - - 'proximity' - - 'attribute' - - 'exact' - - 'custom' + Wilr\SilverStripe\Algolia\Service\AlgoliaService: + properties: + adminApiKey: "`ALGOLIA_ADMIN_API_KEY`" + searchApiKey: "`ALGOLIA_SEARCH_API_KEY`" + applicationId: "`ALGOLIA_SEARCH_APP_ID`" + indexes: + IndexName: + includeClasses: + - SilverStripe\CMS\Model\SiteTree + indexSettings: + attributesForFaceting: + - "filterOnly(ObjectClassName)" + replicas: + - IndexName_Latest + IndexName_Latest: + indexSettings: + ranking: + - "desc(objectCreated)" + - "typo" + - "words" + - "filters" + - "proximity" + - "attribute" + - "exact" + - "custom" ``` ## Indexing @@ -219,7 +219,7 @@ class MyPage extends Page { } ``` -### Customising the indexed relationships +### Customizing the indexed relationships Out of the box, the default is to push the ID and Title fields of any relationships (`$has_one`, `$has_many`, `$many_many`) into a field @@ -255,7 +255,7 @@ operations. The queuing feature can be disabled via the Config YAML. ```yaml Wilr\SilverStripe\Algolia\Extensions\AlgoliaObjectExtension: - use_queued_indexing: false + use_queued_indexing: false ``` ## Displaying and fetching results @@ -316,7 +316,7 @@ it in a `objectForTemplate` field in Algolia. This content is parsed via the ```html
- $ElementalArea + $ElementalArea
``` @@ -336,3 +336,148 @@ Wilr\SilverStripe\Algolia\Service\AlgoliaPageCrawler: content_xpath_selector: '//[data-index]' ``` +## Subsite support + +If you use the Silverstripe Subsite module to run multiple websites you can +handle indexing in a couple ways: + +- Use separate indexes per site. +- Use a single index, but add a `SubsiteID` field in Algolia. + +The decision to go either way depends on the nature of the websites and how +related they are but separate indexes are highly recommended to prevent leaking +information between websites and mucking up analytics and query suggestions. + +### Subsite support with a single index + +If subsites are frequently being created then you may choose to prefer a single +index since index names need to be controlled via YAML so any new subsite would +require a code change. + +The key to this approach is added `SubsiteID` to the attributes for faceting +and at the query time. + +Step 1. Add the field to Algolia + +``` +SilverStripe\Core\Injector\Injector: + Wilr\SilverStripe\Algolia\Service\AlgoliaService: + properties: + adminApiKey: "`ALGOLIA_ADMIN_API_KEY`" + searchApiKey: "`ALGOLIA_SEARCH_API_KEY`" + applicationId: "`ALGOLIA_SEARCH_APP_ID`" + indexes: + index_main_site: + includeClasses: + - SilverStripe\CMS\Model\SiteTree + indexSettings: + distinct: true + attributeForDistinct: "objectLink" + searchableAttributes: + - objectTitle + - objectContent + - objectLink + - Summary + - objectForTemplate + attributesForFaceting: + - "filterOnly(objectClassName)" + ***- "filterOnly(SubsiteID)"*** +``` + +Step 2. Expose the field on `SiteTree` via a DataExtension (make sure to apply the extension) + +``` +request->getVar('start') / $hitsPerPage); + + $results = Injector::inst()->get(AlgoliaQuerier::class)->fetchResults( + 'indexName', + $this->request->getVar('search'), [ + 'page' => $this->request->getVar('start') ? $paginatedPageNum : 0, + 'hitsPerPage' => $hitsPerPage, + 'facetFilters' => [ + 'SubsiteID' => SubsiteState::singleton()->getSubsiteId() + ] + ] + ); + + return [ + 'Title' => 'Search Results', + 'Results' => $results + ]; + } +} +``` + +### Subsite support with separate indexes + +Create multiple indexes in your config and use the `includeFilter` parameter to +filter the records per index. + +The `includeFilter` should be in the format `{$Class}`: `{$WhereQuery}` where +the `$WhereQuery` is a basic SQL statement performed by the ORM on the given +class. + +``` +SilverStripe\Core\Injector\Injector: + Wilr\SilverStripe\Algolia\Service\AlgoliaService: + properties: + adminApiKey: "`ALGOLIA_ADMIN_API_KEY`" + searchApiKey: "`ALGOLIA_SEARCH_API_KEY`" + applicationId: "`ALGOLIA_SEARCH_APP_ID`" + indexes: + index_main_site: + includeClasses: + - SilverStripe\CMS\Model\SiteTree + includeFilter: + "SilverStripe\\CMS\\Model\\SiteTree": "SubsiteID = 0" + indexSettings: + distinct: true + attributeForDistinct: "objectLink" + searchableAttributes: + - objectTitle + - objectContent + - objectLink + - Summary + - objectForTemplate + attributesForFaceting: + - "filterOnly(objectClassName)" + index_subsite_pages: + includeClasses: + - SilverStripe\CMS\Model\SiteTree + includeFilter: + "SilverStripe\\CMS\\Model\\SiteTree": "SubsiteID > 0" + indexSettings: + distinct: true + attributeForDistinct: "objectLink" + searchableAttributes: + - objectTitle + - objectContent + - objectLink + - Summary + - objectForTemplate + attributesForFaceting: + - "filterOnly(objectClassName)" +``` diff --git a/src/Extensions/AlgoliaObjectExtension.php b/src/Extensions/AlgoliaObjectExtension.php index 28f6875..09dcd61 100644 --- a/src/Extensions/AlgoliaObjectExtension.php +++ b/src/Extensions/AlgoliaObjectExtension.php @@ -79,9 +79,9 @@ public function updateSettingsFields(FieldList $fields) $fields->addFieldsToTab( 'Root.Search', [ - ReadonlyField::create('AlgoliaIndexed', _t(__CLASS__.'.LastIndexed', 'Last indexed in Algolia')) + ReadonlyField::create('AlgoliaIndexed', _t(__CLASS__ . '.LastIndexed', 'Last indexed in Algolia')) ->setDescription($this->owner->AlgoliaError), - ReadonlyField::create('AlgoliaUUID', _t(__CLASS__.'.UUID', 'Algolia UUID')) + ReadonlyField::create('AlgoliaUUID', _t(__CLASS__ . '.UUID', 'Algolia UUID')) ] ); } @@ -99,7 +99,9 @@ public function requireDefaultRecords() $this->ranSync = true; $algolia = Injector::inst()->create(AlgoliaService::class); - if(!$this->indexEnabled()) return; + if (!$this->indexEnabled()) { + return; + } try { $algolia->syncSettings(); @@ -145,6 +147,8 @@ public function onAfterPublish() public function markAsRemovedFromAlgoliaIndex() { $this->touchAlgoliaIndexedDate(true); + + return $this->owner; } /** @@ -152,11 +156,14 @@ public function markAsRemovedFromAlgoliaIndex() */ public function touchAlgoliaIndexedDate($isDeleted = false) { - $newValue = $isDeleted ? 'null' : 'NOW()'; + $newValue = $isDeleted ? 'null' : DB::get_conn()->now(); + $this->updateAlgoliaFields([ 'AlgoliaIndexed' => $newValue, 'AlgoliaUUID' => "'" . $this->owner->AlgoliaUUID . "'", ]); + + return $this->owner; } /** diff --git a/src/Jobs/AlgoliaReindexAllJob.php b/src/Jobs/AlgoliaReindexAllJob.php index 2898e2f..2130ca7 100644 --- a/src/Jobs/AlgoliaReindexAllJob.php +++ b/src/Jobs/AlgoliaReindexAllJob.php @@ -66,13 +66,14 @@ public function setup() // and process simply handles one batch at a time. foreach ($algoliaService->indexes as $index) { $classes = (isset($index['includeClasses'])) ? $index['includeClasses'] : null; + $indexFilters = (isset($index['includeFilters'])) ? $index['includeFilters'] : null; if ($classes) { foreach ($classes as $candidate) { $filter = (isset($filters[$candidate])) ? $filters[$candidate] : ''; $count = 0; - foreach ($task->getItems($candidate, $filter)->column('ID') as $id) { + foreach ($task->getItems($candidate, $filter, $indexFilters)->column('ID') as $id) { $count++; if (!isset($this->indexData[$candidate])) { @@ -83,7 +84,7 @@ public function setup() $this->totalSteps++; } - $this->addMessage('Indexing '. $count . ' '. $candidate . ' instances with filters '. $filter); + $this->addMessage('Indexing ' . $count . ' ' . $candidate . ' instances with filters ' . $filter); } } } @@ -118,19 +119,19 @@ public function process() try { if ($batching) { - if ($task->indexItems($class, '', DataObject::get($class)->filter('ID', $take), false)) { - $this->addMessage('Successfully indexing '. $class . ' ['. implode(', ', $take) . ']'); + if ($task->indexItems($class, DataObject::get($class)->filter('ID', $take), false)) { + $this->addMessage('Successfully indexing ' . $class . ' [' . implode(', ', $take) . ']'); } else { - $this->addMessage('Error indexing '. $class . ' ['. implode(', ', $take) . ']'); + $this->addMessage('Error indexing ' . $class . ' [' . implode(', ', $take) . ']'); } } else { $items = DataObject::get($class)->filter('ID', $take); foreach ($items as $item) { if ($task->indexItem($item)) { - $this->addMessage('Successfully indexed '. $class . ' ['. $item->ID . ']'); + $this->addMessage('Successfully indexed ' . $class . ' [' . $item->ID . ']'); } else { - $this->addMessage('Error indexing '. $class . ' ['. $item->ID . ']'); + $this->addMessage('Error indexing ' . $class . ' [' . $item->ID . ']'); } } } diff --git a/src/Service/AlgoliaIndexer.php b/src/Service/AlgoliaIndexer.php index 8fda0ba..641a778 100644 --- a/src/Service/AlgoliaIndexer.php +++ b/src/Service/AlgoliaIndexer.php @@ -241,7 +241,7 @@ public function exportAttributesFromObject($item) ); if ($block) { - $attributes->push($attributeName .'_Block'. $i, $block); + $attributes->push($attributeName . '_Block' . $i, $block); } else { $hasContent = false; } @@ -331,10 +331,14 @@ public function deleteItem($itemClass, $itemUUID) return false; } - $searchIndexes = $this->getService()->initIndexes(); + $searchIndexes = $this->getService()->initIndexes($itemClass); foreach ($searchIndexes as $key => $searchIndex) { - $searchIndex->deleteObject($itemUUID); + try { + $searchIndex->deleteObject($itemUUID); + } catch (Throwable $e) { + // do nothing + } } return true; @@ -352,7 +356,7 @@ public function deleteItem($itemClass, $itemUUID) */ public function generateUniqueID($item) { - return strtolower(str_replace('\\', '_', get_class($item)) . '_'. $item->ID); + return strtolower(str_replace('\\', '_', get_class($item)) . '_' . $item->ID); } /** diff --git a/src/Service/AlgoliaService.php b/src/Service/AlgoliaService.php index 802a480..60846fa 100644 --- a/src/Service/AlgoliaService.php +++ b/src/Service/AlgoliaService.php @@ -27,6 +27,8 @@ class AlgoliaService protected $client; + protected $preloadedIndexes = []; + /** * @return \Algolia\AlgoliaSearch\SearchClient */ @@ -34,11 +36,11 @@ public function getClient() { if (!$this->client) { if (!$this->adminApiKey) { - throw new Exception('No adminApiKey configured for '. self::class); + throw new Exception('No adminApiKey configured for ' . self::class); } if (!$this->applicationId) { - throw new Exception('No applicationId configured for '. self::class); + throw new Exception('No applicationId configured for ' . self::class); } $this->client = SearchClient::create( @@ -57,7 +59,6 @@ public function getIndexes($excludeReplicas = true) return $this->indexes; } - $replicas = []; $output = []; @@ -81,6 +82,21 @@ public function getIndexes($excludeReplicas = true) } + public function getIndexByName($name) + { + $indexes = $this->initIndexes(); + + if (!isset($indexes[$name])) { + throw new Exception(sprintf( + 'Index ' . $name . ' not found, must be one of [%s]', + implode(', ', array_keys($indexes)) + )); + } + + return $indexes[$name]; + } + + /** * Returns an array of all the indexes which need the given item or item * class. If no item provided, returns a list of all the indexes defined. @@ -112,14 +128,19 @@ public function initIndexes($item = null, $excludeReplicas = true) return []; } if (!$item) { + if ($this->preloadedIndexes) { + return $this->preloadedIndexes; + } + $indexes = $this->getIndexes($excludeReplicas); - return array_map( - function ($indexName) use ($client) { - return $client->initIndex($this->environmentizeIndex($indexName)); - }, - array_keys($indexes) - ); + $this->preloadedIndexes = []; + + foreach ($indexes as $indexName => $data) { + $this->preloadedIndexes[$indexName] = $client->initIndex($this->environmentizeIndex($indexName)); + } + + return $this->preloadedIndexes; } if (is_string($item)) { @@ -134,10 +155,26 @@ function ($indexName) use ($client) { foreach ($this->indexes as $indexName => $data) { $classes = (isset($data['includeClasses'])) ? $data['includeClasses'] : null; + $filter = (isset($data['includeFilter'])) ? $data['includeFilter'] : null; if ($classes) { foreach ($classes as $candidate) { if ($item instanceof $candidate) { + if (method_exists($item, 'shouldIncludeInIndex') && !$item->shouldIncludeInIndex($indexName)) { + continue; + } + + if ($filter && isset($filter[$candidate])) { + // check to see if this item matches the filter. + $check = $candidate::get()->filter([ + 'ID' => $item->ID, + ])->where($filter[$candidate])->first(); + + if (!$check) { + continue; + } + } + $matches[] = $indexName; break; @@ -220,9 +257,6 @@ function ($replica) { } catch (Throwable $e) { Injector::inst()->create(LoggerInterface::class)->error($e); - if (Director::isDev()) { - throw $e; - } return false; } diff --git a/src/Tasks/AlgoliaConfigure.php b/src/Tasks/AlgoliaConfigure.php index 0d8cf05..e301284 100644 --- a/src/Tasks/AlgoliaConfigure.php +++ b/src/Tasks/AlgoliaConfigure.php @@ -24,12 +24,16 @@ public function run($request) { $service = Injector::inst()->get(AlgoliaService::class); - if ($service->syncSettings()) { - echo 'Success.' . PHP_EOL; - } else { - echo 'An error occurred while syncing the settings. Please check your error logs.'. PHP_EOL; + try { + if ($service->syncSettings()) { + echo 'Success.' . PHP_EOL; + } else { + echo 'An error occurred while syncing the settings. Please check your error logs.' . PHP_EOL; + } + } catch (\Exception $e) { + echo 'An error occurred while syncing the settings. Please check your error logs.' . PHP_EOL; + echo $e->getMessage() . PHP_EOL; } - echo 'Done.'; } } diff --git a/src/Tasks/AlgoliaReindex.php b/src/Tasks/AlgoliaReindex.php index 9ba995c..55597e9 100644 --- a/src/Tasks/AlgoliaReindex.php +++ b/src/Tasks/AlgoliaReindex.php @@ -75,54 +75,49 @@ public function run($request) } } - if ($targetClass) { - $items = $this->getItems($targetClass, $filter); + // find all classes we have to index and do so + foreach ($algoliaService->indexes as $indexName => $index) { + echo 'Updating index ' . $indexName . PHP_EOL; + + $classes = (isset($index['includeClasses'])) ? $index['includeClasses'] : null; + $indexFilters = (isset($index['includeFilter'])) ? $index['includeFilter'] : []; + + if ($classes) { + foreach ($classes as $candidate) { + if ($targetClass && $targetClass !== $candidate) { + continue; + } - if ($items->exists()) { - $this->indexItems($targetClass, $filter, $items); - } else { - echo sprintf( - 'Found 0 %s remaining to index which match filter (%s)%s', - $targetClass, - $filter, - PHP_EOL - ); - } - } else { - // find all classes we have to index and do so - foreach ($algoliaService->indexes as $index) { - $classes = (isset($index['includeClasses'])) ? $index['includeClasses'] : null; - - if ($classes) { - foreach ($classes as $candidate) { - $items = $this->getItems($candidate, $filter); - - if ($items->exists()) { - $this->indexItems($candidate, $filter, $items); - } else { - echo sprintf( - 'Found 0 %s remaining to index which match filter (%s)%s', - $targetClass, - $filter, - PHP_EOL - ); - } + $items = $this->getItems($candidate, $filter, $indexFilters); + echo sprintf( + '| Found %s %s remaining to index which match filter (%s)%s', + $items->count(), + $targetClass, + implode(',', array_merge([$filter], [$indexFilters[$candidate]] ?? [])), + PHP_EOL + ); + + if ($items->exists()) { + $this->indexItems($indexName, $items); } } } } + echo 'Done'; } + /** * @param string $targetClass * @param string $filter + * @param string[] $indexFilters * * @return \SilverStripe\ORM\DataList */ - public function getItems($targetClass, $filter = '') + public function getItems($targetClass, $filter = '', $indexFilters = []) { $inst = $targetClass::create(); @@ -136,11 +131,16 @@ public function getItems($targetClass, $filter = '') } } + if (isset($indexFilters[$targetClass])) { + $items = $items->where($indexFilters[$targetClass]); + } + $items = $items->setDataQueryParam('Subsite.filter', false); return $items; } + /** * @param DataObject $obj * @@ -167,14 +167,13 @@ public function indexItem($obj = null): bool /** - * @param string $targetClass - * @param string $filter + * @param string $indexName * @param DataList? $items * @param bool $output; * * @return bool|string */ - public function indexItems($targetClass, $filter = '', $items = null, $output = true) + public function indexItems($indexName, $items = null, $output = true) { $algoliaService = Injector::inst()->get(AlgoliaService::class); $count = 0; @@ -183,19 +182,6 @@ public function indexItems($targetClass, $filter = '', $items = null, $output = $batchSize = $this->config()->get('batch_size') ?? 25; $batchesTotal = ($total > 0) ? (ceil($total / $batchSize)) : 0; $indexer = Injector::inst()->get(AlgoliaIndexer::class); - - if ($output) { - echo sprintf( - 'Found %s %s remaining to index which match filter (%s), export in batches of %s, %s batches total %s', - $total, - $targetClass, - $filter, - $batchSize, - $batchesTotal, - PHP_EOL - ); - } - $pos = 0; if ($total < 1) { @@ -253,7 +239,7 @@ public function indexItems($targetClass, $filter = '', $items = null, $output = } if (count($currentBatches[$batchKey]) >= $batchSize) { - $this->indexBatch($currentBatches[$batchKey]); + $this->indexBatch($indexName, $currentBatches[$batchKey]); unset($currentBatches[$batchKey]); } @@ -266,7 +252,7 @@ public function indexItems($targetClass, $filter = '', $items = null, $output = foreach ($currentBatches as $class => $records) { if (count($currentBatches[$class]) > 0) { - $this->indexbatch($currentBatches[$class]); + $this->indexBatch($indexName, $currentBatches[$class]); if ($output) { sleep(1); @@ -275,21 +261,24 @@ public function indexItems($targetClass, $filter = '', $items = null, $output = } $summary = sprintf( - "Number of objects indexed: %s, Skipped %s", + "%s| Number of objects indexed in %s: %s, Skipped %s", + PHP_EOL, + $indexName, $count, $skipped ); if ($output) { - Debug::message($summary); - - Debug::message( - sprintf( - "See index at ". - "algolia.com/apps/%s/explorer/indices", - $algoliaService->applicationId, - $algoliaService->applicationId - ) + echo $summary; + + echo sprintf( + "%s| See index at " . + "algolia.com/apps/%s/explorer/indices%s---%s", + PHP_EOL, + $algoliaService->applicationId, + $algoliaService->applicationId, + PHP_EOL, + PHP_EOL ); } @@ -303,9 +292,10 @@ public function indexItems($targetClass, $filter = '', $items = null, $output = * * @return bool */ - public function indexBatch($items): bool + public function indexBatch($indexName, $items): bool { - $indexes = Injector::inst()->create(AlgoliaService::class)->initIndexes($items[0]); + $service = Injector::inst()->create(AlgoliaService::class); + $indexes = $service->getIndexByName($indexName); try { foreach ($indexes as $index) { diff --git a/tests/AlgoliaIndexerTest.php b/tests/AlgoliaIndexerTest.php index 968b699..dae19af 100644 --- a/tests/AlgoliaIndexerTest.php +++ b/tests/AlgoliaIndexerTest.php @@ -74,6 +74,7 @@ public function testDeleteNonExistentItem() $indexer = Injector::inst()->get(AlgoliaIndexer::class); $deleted = $indexer->deleteItem(AlgoliaTestObject::class, 9999999); - return $this->assertFalse($deleted); + return $this->assertTrue($deleted); } + } diff --git a/tests/AlgoliaServiceTest.php b/tests/AlgoliaServiceTest.php new file mode 100644 index 0000000..9759315 --- /dev/null +++ b/tests/AlgoliaServiceTest.php @@ -0,0 +1,107 @@ + [ + AlgoliaObjectExtension::class + ] + ]; + + public static function setUpBeforeClass(): void + { + parent::setUpBeforeClass(); + + // mock AlgoliaService + Injector::inst()->get(DataObjectSchema::class)->reset(); + Injector::inst()->registerService(new TestAlgoliaService(), AlgoliaService::class); + } + + + public function testInitIndexes() + { + $service = Injector::inst()->create(AlgoliaService::class); + + $service->indexes = [ + 'testIndexTestObjects' => [ + 'includeClasses' => [ + AlgoliaTestObject::class + ], + ], + 'testIndexCustomTestObjects' => [ + 'includeClasses' => [ + AlgoliaCustomTestObject::class + ], + ], + ]; + + $testObj = new AlgoliaTestObject(); + $testObj->Title = 'Test'; + $testObj->Active = 1; + $testObj->write(); + + $testObj2 = new AlgoliaCustomTestObject(); + $testObj2->Title = 'Test'; + $testObj2->Active = 1; + + $this->assertEquals(['testIndexTestObjects'], array_keys($service->initIndexes($testObj))); + $this->assertEquals(['testIndexCustomTestObjects'], array_keys($service->initIndexes($testObj2))); + } + + + public function testInitIndexesWithFilter() + { + $service = Injector::inst()->create(AlgoliaService::class); + + $service->indexes = [ + 'testIndexTestObjects' => [ + 'includeClasses' => [ + AlgoliaTestObject::class + ], + 'includeFilter' => [ + AlgoliaTestObject::class => "Title != 'Ted'" + ] + ], + 'testIndexTestObjectsNamedTed' => [ + 'includeClasses' => [ + AlgoliaTestObject::class + ], + 'includeFilter' => [ + AlgoliaTestObject::class => "Title = 'Ted'" + ] + ], + ]; + + $testObj = new AlgoliaTestObject(); + $testObj->Title = 'Test'; + $testObj->Active = 1; + $testObj->write(); + + + $testObj2 = new AlgoliaTestObject(); + $testObj2->Title = 'Ted'; + $testObj2->Active = 1; + $testObj2->write(); + + $this->assertEquals(['testIndexTestObjects'], array_keys($service->initIndexes($testObj))); + $this->assertEquals(['testIndexTestObjectsNamedTed'], array_keys($service->initIndexes($testObj2))); + } + +}