Skip to content

Commit

Permalink
Improve and fix completion suggesters
Browse files Browse the repository at this point in the history
  • Loading branch information
lruozzi9 committed Apr 16, 2024
1 parent d780e23 commit 5833bb7
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 66 deletions.
1 change: 1 addition & 0 deletions src/Builder/QueryBuilderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ public function buildSearchQuery(

public function buildCompletionSuggestersQuery(
string $searchTerm,
?string $source = 'suggest',
): array;
}
4 changes: 4 additions & 0 deletions src/Builder/TwigQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ public function buildSearchQuery(

public function buildCompletionSuggestersQuery(
string $searchTerm,
?string $source = 'suggest',
): array {
$localeCode = $this->localeContext->getLocaleCode();
$query = $this->twig->render('@WebgriffeSyliusElasticsearchPlugin/query/completion-suggesters/query.json.twig', [
Expand All @@ -198,6 +199,9 @@ public function buildCompletionSuggestersQuery(
/** @var array $queryNormalized */
$queryNormalized = json_decode($query, true, 512, JSON_THROW_ON_ERROR);
$completionSuggestersQuery['suggest'] = $queryNormalized;
if ($source !== null) {
$completionSuggestersQuery['_source'] = $source;
}

$this->logger->debug(sprintf(
'Built completion suggesters query: "%s".',
Expand Down
2 changes: 1 addition & 1 deletion src/Client/ClientInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* @psalm-type ESAggregation = array{meta: array{type: string}, doc_count: int, filter_by_key: array{doc_count: int, values: ESValuesAggregation, name: array{doc_count: int, filter_name_by_locale: array{doc_count: int, values: ESValuesAggregation}}}}
* @psalm-type ESHit = array{_index: string, _id: string, score: float, _source: array}
* @psalm-type ESQueryResult = array{took: int, timed_out: bool, _shards: array, hits: array{total: array{value: int, relation: string}, max_score: ?int, hits: array<array-key, ESHit>}, aggregations?: array<string, array|ESAggregation|ESDefaultOptionAggregation>}
* @psalm-type ESSuggestOption = array{text: string, score: float, freq: int}
* @psalm-type ESSuggestOption = array{text: string, _index: string, _type: string, _id: string, _score: float, _source: array{suggest: array<array-key, string>}}
* @psalm-type ESCompletionSuggesters = array<string, array<array-key, array{text: string, offset: int, length: int, options: array<array-key, ESSuggestOption>}>>
*/
interface ClientInterface extends LoggerAwareInterface
Expand Down
21 changes: 8 additions & 13 deletions src/Controller/InstantSearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,33 +61,28 @@ public function __invoke(Request $request, string $query): Response
return $this->render('@WebgriffeSyliusElasticsearchPlugin/InstantSearch/results.html.twig', [
'query' => $query,
'queryResult' => $queryResult,
'completionSuggesters' => $this->buildSuggestions($completionSuggesters),
'completionSuggesters' => $this->buildCompletionSuggesters($completionSuggesters),
]);
}

/**
* @param ESCompletionSuggesters $suggesters
* @param ESCompletionSuggesters $completionSuggesters
*/
private function buildSuggestions(array $suggesters): array
private function buildCompletionSuggesters(array $completionSuggesters): array
{
$suggestions = [];
foreach ($suggesters as $field => $suggestion) {
foreach ($completionSuggesters as $field => $suggestion) {
foreach ($suggestion as $suggestionData) {
if (count($suggestionData['options']) === 0) {
$suggestions[$field][] = $suggestionData['text'];

$options = $suggestionData['options'];
if (count($options) === 0) {
continue;
}
foreach ($suggestionData['options'] as $option) {
$suggestions[$field][] = $option['text'];
foreach ($options as $option) {
$suggestions[$field] = $option['text'];
}
}
}

foreach ($suggestions as $field => $suggestion) {
$suggestions[$field] = implode(' ', $suggestion);
}

return $suggestions;
}
}
31 changes: 0 additions & 31 deletions src/Controller/SearchController.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,43 +103,12 @@ public function __invoke(Request $request, ?string $query = null): Response

return $this->redirectToRoute($result->getRouteName(), $result->getRouteParams());
}
$completionSuggesters = $this->client->completionSuggesters(
$this->queryBuilder->buildCompletionSuggestersQuery($query),
$indexAliasNames,
);

return $this->render('@WebgriffeSyliusElasticsearchPlugin/Search/results.html.twig', [
'query' => $query,
'paginator' => $paginator,
'filters' => $esSearchQueryAdapter->getQueryResult()->getFilters(),
'queryResult' => $esSearchQueryAdapter->getQueryResult(),
'completionSuggesters' => $this->buildSuggestions($completionSuggesters),
]);
}

/**
* @param ESCompletionSuggesters $suggesters
*/
private function buildSuggestions(array $suggesters): array
{
$suggestions = [];
foreach ($suggesters as $field => $suggestion) {
foreach ($suggestion as $suggestionData) {
if (count($suggestionData['options']) === 0) {
$suggestions[$field][] = $suggestionData['text'];

continue;
}
foreach ($suggestionData['options'] as $option) {
$suggestions[$field][] = $option['text'];
}
}
}

foreach ($suggestions as $field => $suggestion) {
$suggestions[$field] = implode(' ', $suggestion);
}

return $suggestions;
}
}
7 changes: 7 additions & 0 deletions src/DocumentType/ProductDocumentType.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ public function getMappings(): array
'include_in_parent' => true,
'properties' => $this->variantProperties(),
],
'suggest' => [
'type' => 'completion',
'search_analyzer' => 'store',
'preserve_separators' => false,
'preserve_position_increments' => true,
'max_input_length' => 50,
],
],
];
$event = new ProductDocumentTypeMappingsEvent($mappings);
Expand Down
20 changes: 13 additions & 7 deletions src/Parser/ElasticsearchProductDocumentParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
*/
final class ElasticsearchProductDocumentParser implements DocumentParserInterface
{
private ?string $defaultLocale = null;
private ?string $defaultLocaleCode = null;

/** @var array<string|int, ProductVariantInterface> */
private array $productVariants = [];
Expand Down Expand Up @@ -68,11 +68,11 @@ public function parse(array $document): ProductResponseInterface
/** @var ChannelInterface $channel */
$channel = $this->channelContext->getChannel();
$defaultLocale = $channel->getDefaultLocale();
$this->defaultLocale = $this->fallbackLocaleCode;
$this->defaultLocaleCode = $this->fallbackLocaleCode;
if ($defaultLocale instanceof LocaleInterface) {
$defaultLocaleCode = $defaultLocale->getCode();
if ($defaultLocaleCode !== null) {
$this->defaultLocale = $defaultLocaleCode;
$this->defaultLocaleCode = $defaultLocaleCode;
}
}
/** @var array{sylius-id: int, code: string, name: LocalizedField, description: LocalizedField, short-description: LocalizedField, taxons: array, main_taxon: array, slug: LocalizedField, images: array, variants: array, product-options: array, translated-attributes: array, attributes: array} $source */
Expand Down Expand Up @@ -122,9 +122,15 @@ public function parse(array $document): ProductResponseInterface
$productAttribute->setCurrentLocale($localeCode);
$productAttribute->setName($this->getValueFromLocalizedField($esTranslatedAttribute['name'], $localeCode));

/** @var array<array-key, array{sylius-id: int|string, code: string, locale: string, values: array<array-key, string>}> $attributeValues */
$attributeValues = $esTranslatedAttribute['values'][$this->defaultLocale];
$usedLocale = $this->defaultLocale;
if (!array_key_exists($this->defaultLocaleCode, $esTranslatedAttribute['values'])) {
/** @var array<array-key, array{sylius-id: int|string, code: string, locale: string, values: array<array-key, string>}> $attributeValues */
$attributeValues = $esTranslatedAttribute['values'][$this->fallbackLocaleCode];
$usedLocale = $this->fallbackLocaleCode;
} else {
/** @var array<array-key, array{sylius-id: int|string, code: string, locale: string, values: array<array-key, string>}> $attributeValues */
$attributeValues = $esTranslatedAttribute['values'][$this->defaultLocaleCode];
$usedLocale = $this->defaultLocaleCode;
}
if (array_key_exists($localeCode, $esTranslatedAttribute['values'])) {
$usedLocale = $localeCode;
/** @var array<array-key, array{sylius-id: int|string, code: string, locale: string, values: array<array-key, string>}> $attributeValues */
Expand Down Expand Up @@ -240,7 +246,7 @@ static function (array $a, array $b): int {
private function getValueFromLocalizedField(array $localizedField, string $localeCode): ?string
{
$fallbackValue = null;
$defaultLocale = $this->defaultLocale;
$defaultLocale = $this->defaultLocaleCode;
Assert::string($defaultLocale);
foreach ($localizedField as $field) {
if (array_key_exists($localeCode, $field)) {
Expand Down
15 changes: 13 additions & 2 deletions src/Serializer/ProductNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ final class ProductNormalizer implements NormalizerInterface
/** @var string[] */
private array $localeCodes = [];

/** @var array<array-key, array{input: string, weight: positive-int}> */
private array $productSuggesters = [];

public function __construct(
private readonly ProductVariantResolverInterface $productVariantResolver,
private readonly EventDispatcherInterface $eventDispatcher,
Expand Down Expand Up @@ -67,6 +70,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
$this->defaultLocaleCode = $channelDefaultLocaleCode;
}
}
$this->productSuggesters = [];

$normalizedProduct = [
'sylius-id' => $product->getId(),
Expand All @@ -90,16 +94,19 @@ public function normalize(mixed $object, ?string $format = null, array $context
'translated-attributes' => [],
'product-options' => [],
'images' => [],
'suggest' => [],
];
$this->productSuggesters[] = ['input' => (string) $product->getCode(), 'weight' => 100];
/** @var ProductTranslationInterface $productTranslation */
foreach ($product->getTranslations() as $productTranslation) {
$localeCode = $productTranslation->getLocale();
Assert::string($localeCode);
$productName = (string) $productTranslation->getName();
$normalizedProduct['name-as-keyword'][] = [
$localeCode => $productTranslation->getName(),
$localeCode => $productName,
];
$normalizedProduct['name'][] = [
$localeCode => $productTranslation->getName(),
$localeCode => $productName,
];
$normalizedProduct['description'][] = [
$localeCode => $productTranslation->getDescription(),
Expand All @@ -116,6 +123,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
$normalizedProduct['meta-description'][] = [
$localeCode => $productTranslation->getMetaDescription(),
];
$this->productSuggesters[] = ['input' => $productName, 'weight' => 50];
}
$defaultVariant = $this->productVariantResolver->getVariant($product);
if ($defaultVariant instanceof ProductVariantInterface) {
Expand Down Expand Up @@ -217,6 +225,8 @@ public function normalize(mixed $object, ?string $format = null, array $context
foreach ($product->getImages() as $image) {
$normalizedProduct['images'][] = $this->normalizeProductImage($image);
}
$normalizedProduct['suggest'] = $this->productSuggesters;

$event = new ProductDocumentTypeProductNormalizeEvent($product, $channel, $normalizedProduct);
$this->eventDispatcher->dispatch($event);

Expand Down Expand Up @@ -250,6 +260,7 @@ private function normalizeTaxon(TaxonInterface $taxon): array
$normalizedTaxon['name'][] = [
$localeCode => $taxonTranslation->getName(),
];
$this->productSuggesters[] = ['input' => (string) $taxonTranslation->getName(), 'weight' => 10];
}

return $normalizedTaxon;
Expand Down
24 changes: 15 additions & 9 deletions templates/InstantSearch/results.html.twig
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
{# queryResult \Webgriffe\SyliusElasticsearchPlugin\Model\QueryResultInterface #}

<div>
Maybe you were looking for:
<ul>
{% for completionSuggest in completionSuggesters %}
<li><a href="{{ path('sylius_shop_search', {'query': completionSuggest}) }}">{{ completionSuggest }}</a></li>
{% endfor %}
</ul>
</div>
{% if completionSuggesters|length > 0 %}
<div>
<ul>
{% for completionSuggest in completionSuggesters %}
<li>
<a href="{{ path('sylius_shop_search', {'query': completionSuggest}) }}">
{{ completionSuggest }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}

<div>
Results for "{{ query }}":
{{ 'webgriffe_sylius_elasticsearch_plugin.ui.results_for_query'|trans({'{query}': query}) }}


{% for result in queryResult.hints(1, 10) %}
{% include '@SyliusShop/Product/_box.html.twig' with {product: result} %}
Expand Down
13 changes: 10 additions & 3 deletions templates/query/completion-suggesters/query.json.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
{
"name": {
"text": "{{ searchTerm }}",
"term": {
"field": "name.{{ localeCode }}"
"prefix": "{{ searchTerm }}",
"completion": {
"field": "suggest",
"size": 5,
"skip_duplicates": true,
"fuzzy": {
"fuzziness": 2,
"transpositions": true,
"min_length": 4
}
}
}
}
2 changes: 2 additions & 0 deletions translations/messages.it.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ webgriffe_sylius_elasticsearch_plugin:
form:
filterable:
label: 'Usa come filtro in pagina categoria e ricerca'
ui:
results_for_query: 'Risultati per {query}:'

0 comments on commit 5833bb7

Please sign in to comment.