Skip to content

Commit 2f1e155

Browse files
authored
Merge pull request #1786 from algolia/feat/MAGE-1281-replica-settings-forward
MAGE-1281 Replica settings forwarding
2 parents 251aa61 + d8347aa commit 2f1e155

File tree

11 files changed

+1033
-701
lines changed

11 files changed

+1033
-701
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- The indexing queue cron job can now be configured from the Magento admin.
1313
- Dynamic faceting through Algolia merchandising rules is now supported in Magento via an opt-in feature flag.
1414
- No code redirects via merchandising rules in Algolia are now supported in Magento for both Autocomplete and InstantSearch. Support is enabled by default for Autocomplete.
15+
- Settings updates are now automatically forwarded to replicas (if this behavior is not desirable it can be disabled in the admin)
1516
- Integration tests and unit tests added
1617

1718
### Bug Fixes

Helper/ConfigHelper.php

Lines changed: 724 additions & 666 deletions
Large diffs are not rendered by default.

Helper/Configuration/InstantSearchHelper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ public function setSorting(
132132
);
133133
}
134134

135+
public function shouldShowSuggestionsOnNoResultsPage(?int $storeId = null): bool
136+
{
137+
return $this->configInterface->isSetFlag(
138+
self::SHOW_SUGGESTIONS_NO_RESULTS,
139+
ScopeInterface::SCOPE_STORE,
140+
$storeId
141+
);
142+
}
143+
135144
public function isSearchBoxEnabled(?int $storeId = null): bool
136145
{
137146
return $this->isEnabled($storeId)

Helper/Entity/ProductHelper.php

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Algolia\AlgoliaSearch\Service\IndexOptionsBuilder;
1313
use Algolia\AlgoliaSearch\Service\Product\FacetBuilder;
1414
use Algolia\AlgoliaSearch\Service\Product\RecordBuilder as ProductRecordBuilder;
15+
use Algolia\AlgoliaSearch\Service\IndexSettingsHandler;
1516
use Magento\Catalog\Api\Data\ProductInterfaceFactory;
1617
use Magento\Catalog\Model\Product;
1718
use Magento\Catalog\Model\Product\Attribute\Source\Status;
@@ -87,6 +88,7 @@ public function __construct(
8788
protected ProductInterfaceFactory $productFactory,
8889
protected ProductRecordBuilder $productRecordBuilder,
8990
protected FacetBuilder $facetBuilder,
91+
protected IndexSettingsHandler $indexSettingsHandler,
9092
)
9193
{
9294
parent::__construct($indexNameFetcher);
@@ -316,21 +318,13 @@ public function setSettings(
316318
): void {
317319
$indexSettings = $this->getIndexSettings($storeId);
318320

319-
$this->algoliaConnector->setSettings(
320-
$indexOptions,
321-
$indexSettings,
322-
false,
323-
true
324-
);
321+
$this->indexSettingsHandler->setSettings($indexOptions, $indexSettings);
325322

326323
$this->logger->log('Settings: ' . json_encode($indexSettings));
327324
if ($saveToTmpIndicesToo) {
328-
329-
$this->algoliaConnector->setSettings(
325+
$this->indexSettingsHandler->setSettings(
330326
$indexTmpOptions,
331327
$indexSettings,
332-
false,
333-
true,
334328
$indexOptions->getIndexName()
335329
);
336330

Model/IndicesConfigurator.php

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Algolia\AlgoliaSearch\Service\Category\IndexOptionsBuilder as CategoryIndexOptionsBuilder;
1818
use Algolia\AlgoliaSearch\Service\Page\IndexOptionsBuilder as PageIndexOptionsBuilder;
1919
use Algolia\AlgoliaSearch\Service\Product\IndexOptionsBuilder as ProductIndexOptionsBuilder;
20+
use Algolia\AlgoliaSearch\Service\IndexSettingsHandler;
2021
use Algolia\AlgoliaSearch\Service\Suggestion\IndexOptionsBuilder as SuggestionIndexOptionsBuilder;
2122
use Magento\Framework\Exception\NoSuchEntityException;
2223

@@ -37,6 +38,7 @@ public function __construct(
3738
protected SuggestionHelper $suggestionHelper,
3839
protected AdditionalSectionHelper $additionalSectionHelper,
3940
protected AlgoliaCredentialsManager $algoliaCredentialsManager,
41+
protected IndexSettingsHandler $indexSettingsHandler,
4042
protected DiagnosticsLogger $logger
4143
) {}
4244

@@ -108,12 +110,7 @@ protected function setCategoriesSettings(int $storeId): void
108110
$settings = $this->categoryHelper->getIndexSettings($storeId);
109111
$indexOptions = $this->categoryIndexOptionsBuilder->buildEntityIndexOptions($storeId);
110112

111-
$this->algoliaConnector->setSettings(
112-
$indexOptions,
113-
$settings,
114-
false,
115-
true
116-
);
113+
$this->indexSettingsHandler->setSettings($indexOptions, $settings);
117114

118115
$this->logger->log('Index name: ' . $indexOptions->getIndexName());
119116
$this->logger->log('Settings: ' . json_encode($settings));
@@ -133,12 +130,7 @@ protected function setPagesSettings(int $storeId): void
133130
$settings = $this->pageHelper->getIndexSettings($storeId);
134131
$indexOptions = $this->pageIndexOptionsBuilder->buildEntityIndexOptions($storeId);
135132

136-
$this->algoliaConnector->setSettings(
137-
$indexOptions,
138-
$settings,
139-
false,
140-
true
141-
);
133+
$this->indexSettingsHandler->setSettings($indexOptions, $settings);
142134

143135
$this->logger->log('Index name: ' . $indexOptions->getIndexName());
144136
$this->logger->log('Settings: ' . json_encode($settings));
@@ -158,12 +150,7 @@ protected function setQuerySuggestionsSettings(int $storeId): void
158150
$settings = $this->suggestionHelper->getIndexSettings($storeId);
159151
$indexOptions = $this->suggestionIndexOptionsBuilder->buildEntityIndexOptions($storeId);
160152

161-
$this->algoliaConnector->setSettings(
162-
$indexOptions,
163-
$settings,
164-
false,
165-
true
166-
);
153+
$this->indexSettingsHandler->setSettings($indexOptions, $settings);
167154

168155
$this->logger->log('Index name: ' . $indexOptions->getIndexName());
169156
$this->logger->log('Settings: ' . json_encode($settings));
@@ -192,7 +179,7 @@ protected function setAdditionalSectionsSettings(int $storeId): void
192179
$settings = $this->additionalSectionHelper->getIndexSettings($storeId);
193180
$indexOptions = $this->indexOptionsBuilder->buildWithEnforcedIndex($indexName, $storeId);
194181

195-
$this->algoliaConnector->setSettings($indexOptions, $settings);
182+
$this->indexSettingsHandler->setSettings($indexOptions, $settings);
196183

197184
$this->logger->log('Index name: ' . $indexName);
198185
$this->logger->log('Settings: ' . json_encode($settings));

Service/IndexSettingsHandler.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Service;
4+
5+
use Algolia\AlgoliaSearch\Api\Data\IndexOptionsInterface;
6+
use Algolia\AlgoliaSearch\Exceptions\AlgoliaException;
7+
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
8+
use Magento\Framework\Exception\NoSuchEntityException;
9+
10+
/**
11+
* This class will proxy the settings save operations and forward to replicas based on user configuration
12+
* excluding attributes which should never be forwarded
13+
*/
14+
class IndexSettingsHandler
15+
{
16+
/**
17+
* As replicas are used for sorting we do not want to override these replicas specific configurations
18+
*/
19+
protected const FORWARDING_EXCLUDED_ATTRIBUTES = [
20+
'customRanking',
21+
'ranking'
22+
];
23+
24+
public function __construct(
25+
protected AlgoliaConnector $connector,
26+
protected ConfigHelper $config,
27+
) {}
28+
29+
/**
30+
* @throws NoSuchEntityException
31+
* @throws AlgoliaException
32+
*/
33+
public function setSettings(
34+
IndexOptionsInterface $indexOptions,
35+
array $indexSettings,
36+
string $mergeSettingsFrom = ''
37+
): void
38+
{
39+
if (!$this->config->shouldForwardPrimaryIndexSettingsToReplicas($indexOptions->getStoreId())) {
40+
$this->connector->setSettings(
41+
$indexOptions,
42+
$indexSettings,
43+
false,
44+
true,
45+
$mergeSettingsFrom
46+
);
47+
return;
48+
}
49+
50+
[$forward, $noForward] = $this->splitSettings($indexSettings);
51+
if ($forward) {
52+
$this->connector->setSettings(
53+
$indexOptions,
54+
$forward,
55+
true,
56+
false
57+
);
58+
}
59+
if ($noForward) {
60+
$this->connector->setSettings(
61+
$indexOptions,
62+
$noForward,
63+
false,
64+
true,
65+
$mergeSettingsFrom
66+
);
67+
}
68+
}
69+
70+
/**
71+
* Split settings based on whether they should be forwarded to the replicas
72+
* @return array Tuple of settings (to forward and not to forward)
73+
*/
74+
protected function splitSettings(array $settings): array
75+
{
76+
$excludedKeys = array_flip(self::FORWARDING_EXCLUDED_ATTRIBUTES);
77+
return [
78+
array_diff_key($settings, $excludedKeys),
79+
array_intersect_key($settings, $excludedKeys)
80+
];
81+
}
82+
83+
}

Setup/Patch/Schema/ConfigPatch.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ class ConfigPatch implements SchemaPatchInterface
8585
'algoliasearch_advanced/advanced/remove_words_if_no_result' => 'allOptional',
8686
'algoliasearch_advanced/advanced/partial_update' => '0',
8787
'algoliasearch_advanced/advanced/customer_groups_enable' => '0',
88-
'algoliasearch_advanced/advanced/make_seo_request' => '1',
8988
'algoliasearch_advanced/advanced/remove_branding' => '0',
9089
'algoliasearch_autocomplete/autocomplete/autocomplete_selector' => '.algolia-search-input',
9190
'algoliasearch_advanced/advanced/index_product_on_category_products_update' => '1',
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
namespace Algolia\AlgoliaSearch\Test\Unit\Service;
4+
5+
use Algolia\AlgoliaSearch\Api\Data\IndexOptionsInterface;
6+
use Algolia\AlgoliaSearch\Helper\ConfigHelper;
7+
use Algolia\AlgoliaSearch\Service\AlgoliaConnector;
8+
use Algolia\AlgoliaSearch\Service\IndexSettingsHandler;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class IndexSettingsHandlerTest extends TestCase
12+
{
13+
protected ?AlgoliaConnector $connector = null;
14+
15+
protected ?ConfigHelper $config = null;
16+
17+
protected ?IndexOptionsInterface $indexOptions = null;
18+
19+
private ?IndexSettingsHandler $handler = null;
20+
21+
protected function setUp(): void
22+
{
23+
$this->connector = $this->createMock(AlgoliaConnector::class);
24+
$this->config = $this->createMock(ConfigHelper::class);
25+
$this->indexOptions = $this->createMock(IndexOptionsInterface::class);
26+
27+
$this->handler = new IndexSettingsHandlerTestable($this->connector, $this->config);
28+
}
29+
30+
public function testSetSettingsWithForwardingEnabledAndMixedSettings(): void
31+
{
32+
$storeId = 1;
33+
$settings = [
34+
'customRanking' => ['desc(price)'],
35+
'attributesToRetrieve' => ['name', 'price']
36+
];
37+
38+
$this->indexOptions->method('getStoreId')->willReturn($storeId);
39+
$this->config->method('shouldForwardPrimaryIndexSettingsToReplicas')
40+
->with($storeId)
41+
->willReturn(true);
42+
43+
$invocationCount = 0;
44+
$this->connector->expects($this->exactly(2))
45+
->method('setSettings')
46+
->willReturnCallback(
47+
function($indexOptions, $indexSettings, $forwardToReplicas, $mergeSettings, $mergeFrom) use (&$invocationCount) {
48+
$invocationCount++;
49+
50+
switch ($invocationCount) {
51+
case 1:
52+
$this->assertEquals(['attributesToRetrieve' => ['name', 'price']], $indexSettings);
53+
$this->assertTrue($forwardToReplicas);
54+
$this->assertFalse($mergeSettings);
55+
break;
56+
case 2:
57+
$this->assertEquals(['customRanking' => ['desc(price)']], $indexSettings);
58+
$this->assertFalse($forwardToReplicas);
59+
$this->assertTrue($mergeSettings);
60+
break;
61+
}
62+
});
63+
64+
$this->handler->setSettings($this->indexOptions, $settings);
65+
}
66+
67+
public function testSetSettingsWithForwardingEnabledOnlyExcludedSettings(): void
68+
{
69+
$storeId = 1;
70+
$settings = [
71+
'ranking' => ['asc(name)'],
72+
'customRanking' => ['desc(price)']
73+
];
74+
75+
$this->indexOptions->method('getStoreId')->willReturn($storeId);
76+
$this->config->method('shouldForwardPrimaryIndexSettingsToReplicas')
77+
->willReturn(true);
78+
79+
// Only one call expected (no forwarded settings since they are sorts)
80+
$this->connector->expects($this->once())
81+
->method('setSettings')
82+
->with(
83+
$this->indexOptions,
84+
$settings,
85+
false,
86+
true,
87+
''
88+
);
89+
90+
$this->handler->setSettings($this->indexOptions, $settings);
91+
}
92+
93+
public function testSetSettingsWithForwardingEnabledOnlyForwardableSettings(): void
94+
{
95+
$storeId = 1;
96+
$settings = [
97+
'attributesToHighlight' => ['title'],
98+
'attributesToRetrieve' => ['name']
99+
];
100+
101+
$this->indexOptions->method('getStoreId')->willReturn($storeId);
102+
$this->config->method('shouldForwardPrimaryIndexSettingsToReplicas')
103+
->willReturn(true);
104+
105+
// Only one call expected (all forwarded - no excluded settings)
106+
$this->connector->expects($this->once())
107+
->method('setSettings')
108+
->with(
109+
$this->indexOptions,
110+
$settings,
111+
true,
112+
false
113+
);
114+
115+
$this->handler->setSettings($this->indexOptions, $settings);
116+
}
117+
118+
public function testSetSettingsWithForwardingDisabled(): void
119+
{
120+
$storeId = 1;
121+
$settings = [
122+
'customRanking' => ['desc(price)'],
123+
'attributesToRetrieve' => ['name', 'price']
124+
];
125+
126+
$this->indexOptions->method('getStoreId')->willReturn($storeId);
127+
$this->config->method('shouldForwardPrimaryIndexSettingsToReplicas')
128+
->willReturn(false);
129+
130+
$this->connector->expects($this->once())
131+
->method('setSettings')
132+
->with(
133+
$this->indexOptions,
134+
$settings,
135+
false,
136+
true,
137+
''
138+
);
139+
140+
$this->handler->setSettings($this->indexOptions, $settings);
141+
}
142+
143+
public function testForwardSettingsWithEmptyInput(): void
144+
{
145+
$storeId = 1;
146+
$settings = [];
147+
148+
$this->indexOptions->method('getStoreId')->willReturn($storeId);
149+
$this->config->method('shouldForwardPrimaryIndexSettingsToReplicas')
150+
->willReturn(true);
151+
152+
// Connector should not be called
153+
$this->connector->expects($this->never())->method('setSettings');
154+
155+
$this->handler->setSettings($this->indexOptions, $settings);
156+
}
157+
158+
159+
public function testSplitSettings(): void
160+
{
161+
$settings = [
162+
'customRanking' => ['desc(price)'],
163+
'ranking' => ['asc(name)'],
164+
'attributesToRetrieve' => ['name']
165+
];
166+
167+
[$forward, $noForward] = $this->handler->splitSettings($settings);
168+
169+
$this->assertEquals(['attributesToRetrieve' => ['name']], $forward);
170+
$this->assertEquals([
171+
'customRanking' => ['desc(price)'],
172+
'ranking' => ['asc(name)']
173+
], $noForward);
174+
}
175+
}

0 commit comments

Comments
 (0)