From 5f2e0f9bc14b47aa820168a65844d0e0219664b5 Mon Sep 17 00:00:00 2001
From: Pavel Piligrimov <p.piligrimov@iqdev.digital>
Date: Sun, 18 Dec 2022 23:17:21 +0500
Subject: [PATCH] =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=BF?=
 =?UTF-8?q?=D0=BE=D0=BB=D0=BD=D0=BE=D0=B9=20=D1=84=D0=B0=D1=81=D0=B5=D1=82?=
 =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D0=B0?=
 =?UTF-8?q?=D1=86=D0=B8=D0=B8,=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B?=
 =?UTF-8?q?=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=20=D0=B4=D0=BB=D1=8F=20?=
 =?UTF-8?q?=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=B0=D1=86=D0=B8=D0=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Document/ProductDocument.php              |  5 +-
 .../Domain/SearchResultFactory.php            | 26 +++--
 src/ElasticSearch/Domain/SearchService.php    | 98 ++++++++++++-------
 .../Indexer/BaseIndexProvider.php             | 44 +++++++++
 .../Search/Aggs/AggsNumberFacet.php           | 10 +-
 .../Search/Aggs/ExtremumTerms.php             | 15 +--
 src/ElasticSearch/Search/Request.php          |  2 +-
 7 files changed, 139 insertions(+), 61 deletions(-)
 create mode 100644 src/ElasticSearch/Indexer/BaseIndexProvider.php

diff --git a/src/ElasticSearch/Document/ProductDocument.php b/src/ElasticSearch/Document/ProductDocument.php
index 6ae448a..d6071e8 100644
--- a/src/ElasticSearch/Document/ProductDocument.php
+++ b/src/ElasticSearch/Document/ProductDocument.php
@@ -10,14 +10,16 @@ class ProductDocument implements Document
     private FacetCollection $keywordFacets;
     private FacetCollection $numberFacets;
     private ?string $fullSearchContent = null;
+    private array $info;
 
     private FacetCategory $categoryFacet;
 
-    public function __construct(FacetCategory $categoryFacet)
+    public function __construct(FacetCategory $categoryFacet, array $info = [])
     {
         $this->keywordFacets = new FacetCollection();
         $this->numberFacets = new FacetCollection();
         $this->categoryFacet = $categoryFacet;
+        $this->info = $info;
     }
 
     /**
@@ -60,6 +62,7 @@ class ProductDocument implements Document
                 'keyword_facet' => $this->getKeywordFacets()->es(),
                 'number_facet' => $this->getNumberFacets()->es()
             ],
+            'data' => $this->info
         ];
 
         if ($this->fullSearchContent) {
diff --git a/src/ElasticSearch/Domain/SearchResultFactory.php b/src/ElasticSearch/Domain/SearchResultFactory.php
index a9a132a..363abe4 100644
--- a/src/ElasticSearch/Domain/SearchResultFactory.php
+++ b/src/ElasticSearch/Domain/SearchResultFactory.php
@@ -49,6 +49,20 @@ final class SearchResultFactory
                 }
             }
 
+            foreach ($buckets as $bucket) {
+                $code = $bucket['key'];
+                if (isset($data['aggregations']["keyword_facet_$code"]['agg_special']['agg_keyword_facet_code']['buckets'])) {
+                    $bucketsFiltered[$code] = [];
+                    foreach ($data['aggregations']["keyword_facet_$code"]['agg_special']['agg_keyword_facet_code']['buckets'] as $filtredBucket) {
+                        foreach ($filtredBucket['agg_keyword_facet_value']['buckets'] as $values) {
+                            if ($filtredBucket['key'] === $code) {
+                                $bucketsFiltered[$code][$values['key']] = $values;
+                            }
+                        }
+                    }
+                }
+            }
+
             foreach ($buckets as $bucket) {
                 $code        = $bucket['key'];
                 $valueBucket = $bucket['agg_keyword_facet_value']['buckets'];
@@ -74,8 +88,8 @@ final class SearchResultFactory
                 foreach ($data['aggregations']['number_facet_filtered']['all_number_facet_filtered']['agg_number_facet_code']['buckets'] as $bucket) {
                     $bucketsFiltered[$bucket['key']] = [
                         'data' => $bucket,
-                        'min' => $bucket['agg_number_facet_min_value'],
-                        'max' => $bucket['agg_number_facet_max_value']
+                        'min' => $bucket['agg_number_facet_value']['min'],
+                        'max' => $bucket['agg_number_facet_value']['max']
                     ];
                 }
             }
@@ -88,13 +102,13 @@ final class SearchResultFactory
                 $selectedMin = $selectedMax = null;
                 if (isset($bucketsFiltered[$code])) {
                     $count       = $bucketsFiltered[$code]['data']['doc_count'];
-                    $selectedMin = $bucketsFiltered[$code]['min']['value'];
-                    $selectedMax = $bucketsFiltered[$code]['max']['value'];
+                    $selectedMin = $bucketsFiltered[$code]['min'];
+                    $selectedMax = $bucketsFiltered[$code]['max'];
                 }
 
                 $facet->items->add(FacetItemRange::createFromRange(
-                    $bucket['agg_number_facet_min_value']['value'],
-                    $bucket['agg_number_facet_max_value']['value'],
+                    $bucket['agg_number_facet_value']['min'],
+                    $bucket['agg_number_facet_value']['max'],
                     $count,
                     $selectedMin,
                     $selectedMax
diff --git a/src/ElasticSearch/Domain/SearchService.php b/src/ElasticSearch/Domain/SearchService.php
index 277c171..ffa88f7 100644
--- a/src/ElasticSearch/Domain/SearchService.php
+++ b/src/ElasticSearch/Domain/SearchService.php
@@ -14,6 +14,7 @@ use IQDEV\ElasticSearch\Search\Pagination;
 use IQDEV\ElasticSearch\Search\Request;
 use Elastic\Elasticsearch\Client;
 use IQDEV\Search\{Filter\Filter,
+    Filter\FilterCollection,
     Filter\FilterKeyword,
     Filter\FilterCategory,
     Filter\FilterNumber,
@@ -22,8 +23,7 @@ use IQDEV\Search\{Filter\Filter,
     SearchService as DomainSearchService,
     Sorting\SortingFieldPair as DSortingFieldPair,
     Sorting\SortingPropertyKeywordPair as DSortingPropertyKeywordPair,
-    Sorting\SortingPropertyNumberPair as DSortingPropertyNumberPair
-};
+    Sorting\SortingPropertyNumberPair as DSortingPropertyNumberPair};
 use IQDEV\ElasticSearch\Search\Sorting\SortingCollection;
 use IQDEV\ElasticSearch\Search\Sorting\SortingFieldsPair;
 use IQDEV\ElasticSearch\Search\Sorting\SortingPropertyKeywordPair;
@@ -44,8 +44,6 @@ final class SearchService implements DomainSearchService
     {
         $request       = new Request();
         $commonQuery   = new Query();
-        $filterKeyword = new Query();
-        $filterNumber  = new Query();
 
         if ($q->filters) {
             foreach ($q->filters as $filter) {
@@ -54,23 +52,10 @@ final class SearchService implements DomainSearchService
                     $request->getQuery()->must(
                         (new Terms('category_id', $filter->value))
                     );
-                    continue;
-                }
-
-                if ($filter instanceof FilterNumber) {
-                    $oFacet = new FilterNumberFacet($filter->key, $filter->min, $filter->max);
-                    $filterNumber->filter($oFacet);
-                    $commonQuery->filter($oFacet);
-                    continue;
-                }
-
-                if ($filter instanceof FilterKeyword) {
-                    $oFacet = new FilterKeywordFacet($filter->key, $filter->value);
-                    $filterKeyword->filter($oFacet);
-                    $commonQuery->filter($oFacet);
-                    continue;
+                    break;
                 }
             }
+            $commonQuery = $this->getQuery($q->filters);
         }
         $commonFilter = clone $commonQuery;
         $commonFilter->setType(Query::TYPE_FILTER);
@@ -80,7 +65,7 @@ final class SearchService implements DomainSearchService
                 new Pagination($q->pagination->limit, $q->pagination->page)
             );
         }
-        
+
         if ($q->sorting && !$q->sorting->isEmpty()) {
             $oSortingCollection = new SortingCollection();
             foreach ($q->sorting as $sorting) {
@@ -103,30 +88,57 @@ final class SearchService implements DomainSearchService
             $request->addMatch('full_search_content', ['query' => $q->query]);
         }
 
-        if ($filterKeyword->isEmpty() === false) {
-            $nestedFilterKeyword = new Nested();
-            $nestedFilterKeyword->setPath('search_data')
-                ->setQuery($filterKeyword);
-        }
+        $getKey = static fn (string $type, string $name): string => sprintf('%s_facet_%s', $type, $name);
+        if ($commonQuery->isEmpty() === false) {
+            foreach ($q->filters as $filter) {
+                /** @var Filter $filter */
+                if ($filter instanceof FilterCategory || $filter instanceof FilterNumber) {
+                    continue;
+                }
 
-        if ($filterNumber->isEmpty() === false) {
-            $nestedFilterNumber = new Nested();
-            $nestedFilterNumber->setPath('search_data')
-                ->setQuery($filterNumber);
-        }
+                $oFilters = new FilterCollection();
+
+                if ($filter instanceof FilterKeyword) {
+                    foreach ($q->filters as $filter2) {
+                        if (!($filter2 instanceof Filter) || $filter2 instanceof FilterCategory) {
+                            continue;
+                        }
+
+                        if ($filter->key() === $filter2->key()) {
+                            continue;
+                        }
+                        $oFilters->add($filter2);
+                    }
+                }
+
+                $aggsFiltered = new Aggs($getKey('keyword', $filter->key()));
+                $aggsFiltered->addAggs(
+                    AggsKeyWordFacet::create(
+                        'agg_special',
+                        'keyword_facet'
+                    )
+                );
+
+                if (false === $oFilters->isEmpty()) {
+                    $aggsFiltered->setQuery($this->getQuery($oFilters));
+                } else {
+                    $aggsFiltered->setNested((new Nested())->setPath('search_data'));
+                }
+
+                $request->getAggs()->add($aggsFiltered);
+            }
 
-        if ($commonQuery->isEmpty() === false) {
             $nestedFilter = new Nested();
             $nestedFilter->setPath('search_data')
                 ->setQuery($commonQuery);
-            
+
             $request->getPostFilter()->filter($nestedFilter);
 
             $aggsKeywordFiltered = new Aggs('keyword_facet_filtered');
             $aggsKeywordFiltered->addAggs(AggsKeyWordFacet::create('all_keyword_facet_filtered', 'keyword_facet'))
                 ->setQuery($commonFilter);
             $request->getAggs()->add($aggsKeywordFiltered);
-            
+
             $aggsNumberFiltered = new Aggs('number_facet_filtered');
             $aggsNumberFiltered->addAggs(AggsNumberFacet::create('all_number_facet_filtered', 'number_facet'))
                 ->setQuery($commonFilter);
@@ -148,4 +160,24 @@ final class SearchService implements DomainSearchService
 
         return SearchResultFactory::createFromResponse($response, $request);
     }
+
+    private function getQuery(FilterCollection $filters): Query
+    {
+        $commonQuery = new Query();
+        foreach ($filters as $filter) {
+            if ($filter instanceof FilterNumber) {
+                $oFacet = new FilterNumberFacet($filter->key, $filter->min, $filter->max);
+                $commonQuery->filter($oFacet);
+                continue;
+            }
+
+            if ($filter instanceof FilterKeyword) {
+                $oFacet = new FilterKeywordFacet($filter->key, $filter->value);
+                $commonQuery->filter($oFacet);
+                continue;
+            }
+        }
+
+        return $commonQuery;
+    }
 }
diff --git a/src/ElasticSearch/Indexer/BaseIndexProvider.php b/src/ElasticSearch/Indexer/BaseIndexProvider.php
new file mode 100644
index 0000000..d8bafd1
--- /dev/null
+++ b/src/ElasticSearch/Indexer/BaseIndexProvider.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace IQDEV\ElasticSearch\Indexer;
+
+use IQDEV\ElasticSearch\Configuration;
+use IQDEV\ElasticSearch\Document\ProductDocument;
+use IQDEV\ElasticSearch\Facet\FacetCategory;
+use IQDEV\ElasticSearch\Facet\FacetKeyword;
+use IQDEV\ElasticSearch\Facet\FacetNumber;
+
+class BaseIndexProvider implements IndexProvider
+{
+    private array $products;
+    private Configuration $configuration;
+
+    public function __construct($products, $configuration)
+    {
+        $this->configuration = $configuration;
+        $this->products = $products;
+    }
+
+    public function get(): \Generator
+    {
+        foreach ($this->products as $product) {
+            $document = new ProductDocument(new FacetCategory($product['category']), $product['data'] ?? []);
+            foreach ($product['properties'] as $type => $values) {
+                foreach ($values as $key => $prop) {
+                    if ($type === 'number') {
+                        $document->getNumberFacets()->add(new FacetNumber($key, $prop));
+                    } else {
+                        $document->getKeywordFacets()->add(new FacetKeyword($key, $prop));
+                    }
+                }
+            }
+            $document->setFullSearchContent($product['name']);
+
+            yield new Index(
+                $this->configuration->getIndexName(),
+                $document,
+                $product['id']
+            );
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/ElasticSearch/Search/Aggs/AggsNumberFacet.php b/src/ElasticSearch/Search/Aggs/AggsNumberFacet.php
index 2c67ab8..16d0b01 100644
--- a/src/ElasticSearch/Search/Aggs/AggsNumberFacet.php
+++ b/src/ElasticSearch/Search/Aggs/AggsNumberFacet.php
@@ -18,17 +18,9 @@ final class AggsNumberFacet
             (new Terms("{$path}.{$facet}.facet_code"))
         );
 
-        $aggFacetValue = new Aggs("agg_{$facet}_min_value");
+        $aggFacetValue = new Aggs("agg_{$facet}_value");
         $aggFacetValue->setExtremumTerms(
             (new ExtremumTerms("{$path}.{$facet}.facet_value"))
-            ->setOperation('min')
-        );
-        $aggFacetCode->addAggs($aggFacetValue);
-
-        $aggFacetValue = new Aggs("agg_{$facet}_max_value");
-        $aggFacetValue->setExtremumTerms(
-            (new ExtremumTerms("{$path}.{$facet}.facet_value"))
-                ->setOperation('max')
         );
         $aggFacetCode->addAggs($aggFacetValue);
 
diff --git a/src/ElasticSearch/Search/Aggs/ExtremumTerms.php b/src/ElasticSearch/Search/Aggs/ExtremumTerms.php
index 629ef18..9de2a5c 100644
--- a/src/ElasticSearch/Search/Aggs/ExtremumTerms.php
+++ b/src/ElasticSearch/Search/Aggs/ExtremumTerms.php
@@ -7,7 +7,6 @@ use IQDEV\ElasticSearch\Esable;
 
 final class ExtremumTerms implements Esable
 {
-    private string $sOperation;
     private string $field;
 
     /**
@@ -18,18 +17,12 @@ final class ExtremumTerms implements Esable
         $this->field = $field;
     }
 
-    public function setOperation($sOperation): self
-    {
-        $this->sOperation = $sOperation;
-        return $this;
-    }
-
     public function es(): array
     {
-        $data = [
-            'field' => $this->field
+        return [
+            'stats' => [
+                'field' => $this->field,
+            ],
         ];
-
-        return [$this->sOperation => $data];
     }
 }
\ No newline at end of file
diff --git a/src/ElasticSearch/Search/Request.php b/src/ElasticSearch/Search/Request.php
index 19d8e84..ab423a9 100644
--- a/src/ElasticSearch/Search/Request.php
+++ b/src/ElasticSearch/Search/Request.php
@@ -94,7 +94,7 @@ final class Request implements Esable
     public function es(): array
     {
         $request = [
-            '_source' => ['id']
+            '_source' => ['id', 'data.*']
         ];
 
         if (isset($this->postFilter) && $this->postFilter->isEmpty() === false) {
-- 
GitLab