Skip to content

Commit 4e6cf8a

Browse files
authored
Merge pull request #93 from ensi-platform/idbpr-2901-v8
IDBPR-2901 Add filters aggregation v8
2 parents e1d137b + eba12ef commit 4e6cf8a

File tree

6 files changed

+165
-0
lines changed

6 files changed

+165
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Aggregating\Bucket;
4+
5+
use Ensi\LaravelElasticQuery\Aggregating\BucketCollection;
6+
use Ensi\LaravelElasticQuery\Aggregating\FiltersCollection;
7+
use Ensi\LaravelElasticQuery\Aggregating\Result;
8+
use Ensi\LaravelElasticQuery\Contracts\Aggregation;
9+
use Webmozart\Assert\Assert;
10+
11+
class FiltersAggregation implements Aggregation
12+
{
13+
public function __construct(
14+
private string $name,
15+
private FiltersCollection $filters,
16+
private ?Aggregation $composite = null,
17+
private ?string $otherBucketKey = null,
18+
) {
19+
Assert::stringNotEmpty(trim($name));
20+
Assert::greaterThan($filters->count(), 0);
21+
}
22+
23+
public function name(): string
24+
{
25+
return $this->name;
26+
}
27+
28+
public function toDSL(): array
29+
{
30+
$body['filters']['filters'] = $this->filters->toDSL();
31+
32+
if ($this->otherBucketKey != null) {
33+
$body['filters']['other_bucket_key'] = $this->otherBucketKey;
34+
}
35+
36+
if ($this->isComposite()) {
37+
$body['aggs'] = $this->composite->toDSL();
38+
}
39+
40+
return [$this->name => $body];
41+
}
42+
43+
public function parseResults(array $response): array
44+
{
45+
$buckets = array_map(
46+
function (mixed $key, array $bucket) {
47+
$values = $this->isComposite() ? $this->composite->parseResults($bucket) : [];
48+
49+
return Result::parseBucketWithKey($key, $bucket, $values);
50+
},
51+
array_keys($response[$this->name]['buckets'] ?? []),
52+
$response[$this->name]['buckets'] ?? []
53+
);
54+
55+
return [$this->name => new BucketCollection($buckets)];
56+
}
57+
58+
public function isComposite(): bool
59+
{
60+
return isset($this->composite);
61+
}
62+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Aggregating;
4+
5+
use Ensi\LaravelElasticQuery\Contracts\Criteria;
6+
use Ensi\LaravelElasticQuery\Contracts\DSLAware;
7+
use Illuminate\Support\Collection;
8+
9+
class FiltersCollection implements DSLAware
10+
{
11+
private Collection $items;
12+
13+
public function __construct()
14+
{
15+
$this->items = new Collection();
16+
}
17+
18+
public function count(): int
19+
{
20+
return $this->items->count();
21+
}
22+
23+
public function toDSL(): array
24+
{
25+
return $this->items
26+
->mapWithKeys(fn (Criteria $criteria, string $key) => [$key => $criteria->toDSL()])
27+
->all();
28+
}
29+
30+
public function add(string $name, Criteria $criteria): void
31+
{
32+
$this->items->put($name, $criteria);
33+
}
34+
}

src/Aggregating/Result.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ public static function parseBucket(array $source, array $compositeValues = []):
1414
return new Bucket(self::parse($source, 'key'), (int)($source['doc_count'] ?? 0), $compositeValues);
1515
}
1616

17+
public static function parseBucketWithKey(mixed $key, array $source, array $compositeValues = []): Bucket
18+
{
19+
return new Bucket($key, (int)($source['doc_count'] ?? 0), $compositeValues);
20+
}
21+
1722
public static function parse(array $source, string $key): mixed
1823
{
1924
$stringValue = $source["{$key}_as_string"] ?? null;

src/Concerns/ConstructsAggregations.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
use Closure;
66
use Ensi\LaravelElasticQuery\Aggregating\AggregationCollection;
77
use Ensi\LaravelElasticQuery\Aggregating\Bucket\FilterAggregation;
8+
use Ensi\LaravelElasticQuery\Aggregating\Bucket\FiltersAggregation;
89
use Ensi\LaravelElasticQuery\Aggregating\Bucket\NestedAggregation;
910
use Ensi\LaravelElasticQuery\Aggregating\Bucket\TermsAggregation;
1011
use Ensi\LaravelElasticQuery\Aggregating\CompositeAggregationBuilder;
12+
use Ensi\LaravelElasticQuery\Aggregating\FiltersCollection;
1113
use Ensi\LaravelElasticQuery\Aggregating\Metrics\CardinalityAggregation;
1214
use Ensi\LaravelElasticQuery\Aggregating\Metrics\MinMaxAggregation;
1315
use Ensi\LaravelElasticQuery\Aggregating\Metrics\ValueCountAggregation;
@@ -43,6 +45,17 @@ public function filter(string $name, Criteria $criteria, AggregationCollection $
4345
return $this;
4446
}
4547

48+
public function filters(
49+
string $name,
50+
FiltersCollection $filters,
51+
?Aggregation $composite = null,
52+
?string $otherBucketKey = null,
53+
): static {
54+
$this->aggregations->add(new FiltersAggregation($name, $filters, $composite, $otherBucketKey));
55+
56+
return $this;
57+
}
58+
4659
public function minmax(string $name, string $field): static
4760
{
4861
$this->aggregations->add(new MinMaxAggregation($name, $this->absolutePath($field)));

tests/IntegrationTests/AggregationQueryIntegrationTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<?php
22

33
use Ensi\LaravelElasticQuery\Aggregating\Bucket;
4+
use Ensi\LaravelElasticQuery\Aggregating\FiltersCollection;
45
use Ensi\LaravelElasticQuery\Aggregating\Metrics\MinMaxScoreAggregation;
56
use Ensi\LaravelElasticQuery\Aggregating\Metrics\TopHitsAggregation;
67
use Ensi\LaravelElasticQuery\Aggregating\MinMax;
78
use Ensi\LaravelElasticQuery\Contracts\AggregationsBuilder;
9+
use Ensi\LaravelElasticQuery\Filtering\Criterias\RangeBound;
10+
use Ensi\LaravelElasticQuery\Filtering\Criterias\Term;
811
use Ensi\LaravelElasticQuery\Search\Sorting\Sort;
912
use Ensi\LaravelElasticQuery\Tests\Data\Models\ProductsIndex;
1013
use Ensi\LaravelElasticQuery\Tests\IntegrationTestCase;
@@ -139,3 +142,39 @@
139142
assertCount(2, $results->get('codes'));
140143
assertGreaterThanOrEqual($scores->first(), $scores->last());
141144
});
145+
146+
test('aggregation query filters', function (?string $defaultBucket) {
147+
/** @var IntegrationTestCase $this */
148+
149+
$filters = new FiltersCollection();
150+
$filters->add('filter_tags', new Term('tags', 'video'));
151+
$filters->add('filter_rating', new RangeBound('rating', '>=', 7));
152+
153+
$topHits = new TopHitsAggregation('top_hits');
154+
155+
$results = ProductsIndex::aggregate()
156+
->filters('group_by_filters', $filters, $topHits, otherBucketKey: $defaultBucket)
157+
->get()
158+
->get('group_by_filters')
159+
->keyBy(fn (Bucket $bucket) => $bucket->key);
160+
161+
$additionResult = $defaultBucket !== null ? 1 : 0;
162+
assertCount($filters->count() + $additionResult, $results);
163+
164+
assertEqualsCanonicalizing(
165+
[1, 328],
166+
extractBucketValues($results, 'filter_tags', $topHits->name(), 'product_id')
167+
);
168+
169+
assertEqualsCanonicalizing(
170+
[1, 150, 405],
171+
extractBucketValues($results, 'filter_rating', $topHits->name(), 'product_id')
172+
);
173+
174+
if ($defaultBucket != null) {
175+
assertEqualsCanonicalizing(
176+
[319, 471],
177+
extractBucketValues($results, $defaultBucket, $topHits->name(), 'product_id')
178+
);
179+
}
180+
})->with([null, 'default_bucket']);

tests/Pest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
use Ensi\LaravelElasticQuery\Aggregating\Bucket;
4+
use Illuminate\Support\Collection;
35
use Illuminate\Testing\AssertableJsonString;
46

57
/*
@@ -58,3 +60,13 @@ function makeAssertableArray(array $source): AssertableJsonString
5860
{
5961
return new AssertableJsonString($source);
6062
}
63+
64+
function extractBucketValues(Collection $result, string $bucketName, string $aggregationName, string $key): array
65+
{
66+
/** @var Bucket $bucket */
67+
$bucket = $result->get($bucketName);
68+
69+
$hits = $bucket->getCompositeValue($aggregationName);
70+
71+
return array_map(fn (array $hit) => $hit['_source'][$key], $hits);
72+
};

0 commit comments

Comments
 (0)