9
9
use Magento \Catalog \Model \Product \Visibility ;
10
10
use Magento \Framework \Api \SearchCriteriaBuilder ;
11
11
use Magento \Framework \App \Config \Storage \WriterInterface ;
12
+ use Magento \Framework \App \State ;
12
13
use Magento \Framework \Event \Observer ;
13
14
use Magento \Framework \Event \ObserverInterface ;
14
15
use Magento \Framework \Exception \LocalizedException ;
16
+ use Magento \Framework \Message \ManagerInterface as MessageManagerInterface ;
17
+ use Psr \Log \LoggerInterface ;
15
18
16
19
class RecommendSettings implements ObserverInterface
17
20
{
18
21
const QUANTITY_AND_STOCK_STATUS = 'quantity_and_stock_status ' ;
19
22
const STATUS = 'status ' ;
20
23
const VISIBILITY = 'visibility ' ;
21
24
25
+ const ENFORCE_VALIDATION = 0 ;
26
+
22
27
/**
23
28
* @var string
24
29
*/
@@ -36,7 +41,10 @@ public function __construct(
36
41
protected readonly WriterInterface $ configWriter ,
37
42
protected readonly ProductRepositoryInterface $ productRepository ,
38
43
protected readonly RecommendManagementInterface $ recommendManagement ,
39
- protected readonly SearchCriteriaBuilder $ searchCriteriaBuilder
44
+ protected readonly SearchCriteriaBuilder $ searchCriteriaBuilder ,
45
+ protected readonly State $ appState ,
46
+ protected readonly MessageManagerInterface $ messageManager ,
47
+ protected readonly LoggerInterface $ logger
40
48
){}
41
49
42
50
/**
@@ -143,24 +151,122 @@ protected function validateRecommendation(string $changedPath, string $recommend
143
151
{
144
152
try {
145
153
$ recommendations = $ this ->recommendManagement ->$ recommendationMethod ($ this ->getProductId ());
146
- if (empty ($ recommendations ['renderingContent ' ])) {
147
- throw new LocalizedException (__ (
148
- "It appears that there is no trained model available for Algolia application ID %1. " ,
149
- $ this ->configHelper ->getApplicationID ()
150
- ));
151
- }
154
+
155
+ $ this ->validateRecommendApiResponse ($ recommendations , $ modelName );
152
156
} catch (\Exception $ e ) {
153
- $ this ->configWriter ->save ($ changedPath , 0 );
154
- throw new LocalizedException (__ (
155
- "Unable to save %1 Recommend configuration due to the following error: %2 " ,
157
+ $ this ->handleRecommendApiException ($ e , $ modelName , $ changedPath );
158
+ }
159
+ }
160
+
161
+ /**
162
+ * If API does not return a hits response the model may not be configured correctly.
163
+ * Do not hard fail but alert the end user.
164
+ *
165
+ * @throws LocalizedException
166
+ */
167
+ protected function validateRecommendApiResponse (array $ recommendResponse , string $ modelName ): void
168
+ {
169
+ if (!array_key_exists ('hits ' , $ recommendResponse )) {
170
+ $ msg = __ (
171
+ "It appears that there is no trained %1 model available for Algolia application ID %2. "
172
+ . "Please verify your configuration in the Algolia Dashboard before continuing. " ,
173
+ $ modelName ,
174
+ $ this ->configHelper ->getApplicationID ()
175
+ );
176
+
177
+ if ($ this ->shouldDisplayWarning ()) {
178
+ $ this ->messageManager ->addWarningMessage ($ msg );
179
+ }
180
+ else {
181
+ $ this ->logger ->warning ($ msg );
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Handles exceptions on the test request against the Recommend API
188
+ *
189
+ * The goal is to warn the user of potential issues so they do not enable a model on the front end that has not been
190
+ * properly configured in Algolia
191
+ *
192
+ * TODO: Implement store scoped validation
193
+ *
194
+ * @throws LocalizedException
195
+ */
196
+ protected function handleRecommendApiException (\Exception $ e , string $ modelName , string $ changedPath ): void
197
+ {
198
+ $ msg = $ this ->getUserFriendlyRecommendApiErrorMessage ($ e );
199
+
200
+ if (self ::ENFORCE_VALIDATION ) {
201
+ $ this ->rollBack ($ changedPath , __ (
202
+ "Unable to save %1 Recommend configuration due to the following error: %2 " ,
156
203
$ modelName ,
157
- $ e -> getMessage ()
204
+ $ msg
158
205
)
159
206
);
160
207
}
208
+
209
+ $ msg = __ (
210
+ "Error encountered while enabling %1 recommendations: %2 " ,
211
+ $ modelName ,
212
+ $ msg
213
+ );
214
+
215
+ if ($ this ->shouldDisplayWarning ()) {
216
+ $ this ->messageManager ->addWarningMessage (
217
+ $ msg
218
+ . ' Please verify your configuration in the Algolia Dashboard before continuing. ' );
219
+ }
220
+ else {
221
+ $ this ->logger ->warning ($ msg );
222
+ }
223
+ }
224
+
225
+ /*
226
+ * For hard fail only
227
+ */
228
+ protected function rollBack (string $ changedPath , \Magento \Framework \Phrase $ message ): void
229
+ {
230
+ $ this ->configWriter ->save ($ changedPath , 0 );
231
+ throw new LocalizedException ($ message );
232
+ }
233
+
234
+ /**
235
+ * Warnings should only be displayed within the admin panel
236
+ * @throws LocalizedException
237
+ */
238
+ protected function shouldDisplayWarning (): bool
239
+ {
240
+ return $ this ->appState ->getAreaCode () === \Magento \Framework \App \Area::AREA_ADMINHTML
241
+ && php_sapi_name () !== 'cli ' ;
242
+ }
243
+
244
+ /**
245
+ * If there is no model on the index then a 404 error should be returned
246
+ * (which will cause the exception on the API call) because there is no model for that index
247
+ * However errors which say "Index does not exist" are cryptic
248
+ * This function serves to make this clearer to the user while also filtering out the possible
249
+ * "ObjectID does not exist" error which can occur if the model does not contain the test product
250
+ */
251
+ protected function getUserFriendlyRecommendApiErrorMessage (\Exception $ e ): string
252
+ {
253
+ $ msg = $ e ->getMessage ();
254
+ if ($ e ->getCode () === 404 ) {
255
+ if (!!preg_match ('/index.*does not exist/i ' , $ msg )) {
256
+ $ msg = (string ) __ ("A trained model could not be found. " );
257
+ }
258
+ if (!!preg_match ('/objectid does not exist/i ' , $ msg )) {
259
+ $ msg = (string ) __ ("Could not find test product in trained model. " );
260
+ }
261
+ }
262
+ return $ msg ;
161
263
}
162
264
163
265
/**
266
+ * Retrieve a test product for requests against the Recommend API
267
+ *
268
+ * TODO: Implement store scoping and independently address 404 where objectID is not found
269
+ *
164
270
* @return string - Product ID string for use in API calls
165
271
* @throws LocalizedException
166
272
*/
0 commit comments