diff --git a/CHANGELOG.md b/CHANGELOG.md index e82f41c6e..cbfb41567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added support for the multi-match query type `bool_prefix` [#2220](https://github.com/ruflin/Elastica/pull/2220) * Supported PHP 8.4 [#2221](https://github.com/ruflin/Elastica/pull/2221) * Added support for custom key to IpRange and GeoDistance `addRange` using a common trait [#2227](https://github.com/ruflin/Elastica/pull/2227) +* Added bucket sort aggregation [#2229](https://github.com/ruflin/Elastica/pull/2229) ### Changed diff --git a/src/Aggregation/BucketSort.php b/src/Aggregation/BucketSort.php new file mode 100644 index 000000000..5d404cfa8 --- /dev/null +++ b/src/Aggregation/BucketSort.php @@ -0,0 +1,52 @@ +hasParam('sort') && !$this->hasParam('size') && !$this->hasParam('from')) { + throw new InvalidException('Either the sort param, the size param or the from param should be set'); + } + + return parent::toArray(); + } + + /** + * The number of buckets to return. Defaults to all buckets of the parent aggregation. + * + * @return $this + */ + public function setSize(int $size): self + { + return $this->setParam('size', $size); + } + + /** + * Buckets in positions prior to the set value will be truncated. + * + * @return $this + */ + public function setFrom(int $from): self + { + return $this->setParam('from', $from); + } + + /** + * How the top matching hits should be sorted. By default the hits are sorted by the score of the main query. + * + * @param string $aggregationName the name of an aggregation + * @param string $direction "asc" or "desc" + */ + public function addSort(string $aggregationName, string $direction): self + { + return $this->addParam('sort', [$aggregationName => ['order' => $direction]]); + } +} diff --git a/tests/Aggregation/BucketSortTest.php b/tests/Aggregation/BucketSortTest.php new file mode 100644 index 000000000..30d899a51 --- /dev/null +++ b/tests/Aggregation/BucketSortTest.php @@ -0,0 +1,85 @@ +addSort('sum_metric_a', 'desc'); + $agg = $this->getAggregation(); + $agg->addAggregation($bucketSortAggregation); + + $query = new Query(); + $query->addAggregation($agg); + $results = $this->_getIndexForTest()->search($query)->getAggregations(); + + $this->assertSame('bar', $results['terms']['buckets'][0]['key']); + + $bucketSortAggregation = new BucketSort('sort_by_bucket'); + $bucketSortAggregation->addSort('sum_metric_a', 'asc'); + $agg = $this->getAggregation(); + $agg->addAggregation($bucketSortAggregation); + + $query = new Query(); + $query->addAggregation($agg); + $results = $this->_getIndexForTest()->search($query)->getAggregations(); + + $this->assertSame('foo', $results['terms']['buckets'][0]['key']); + } + + protected function _getIndexForTest(): Index + { + $index = $this->_createIndex(); + $index->setMapping(new Mapping([ + 'field_a' => ['type' => 'keyword'], + 'metric_a' => ['type' => 'integer'], + ])); + + $index->addDocuments([ + new Document('1', ['field_a' => 'foo', 'metric_a' => 1]), + new Document('2', ['field_a' => 'foo', 'metric_a' => 1]), + new Document('3', ['field_a' => 'foo', 'metric_a' => 1]), + new Document('4', ['field_a' => 'bar', 'metric_a' => 10]), + new Document('5', ['field_a' => 'bar', 'metric_a' => 10]), + new Document('6', ['field_a' => 'bar', 'metric_a' => 10]), + ]); + + $index->refresh(); + + return $index; + } + + private function getAggregation(): Terms + { + $agg = new Terms('terms'); + $agg->setField('field_a'); + + $subAgg = new Sum('sum_metric_a'); + $subAgg->setField('metric_a'); + $agg->addAggregation($subAgg); + + $subAgg = new Sum('sum_metric_b'); + $subAgg->setField('metric_b'); + $agg->addAggregation($subAgg); + + return $agg; + } +}