From 39f0dd59c18be966df81ec2604b318a5febad9ba Mon Sep 17 00:00:00 2001 From: Caleb Champlin Date: Sat, 17 Oct 2020 13:42:50 -0600 Subject: [PATCH] Add support for nested aggregations and filtered aggregations --- aggregations_test.go | 34 ++++++++++++++++++++++++++++ aggs_filter.go | 49 ++++++++++++++++++++++++++++++++++++++++ aggs_filter_test.go | 42 +++++++++++++++++++++++++++++++++++ aggs_nested.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ aggs_nested_test.go | 34 ++++++++++++++++++++++++++++ 5 files changed, 212 insertions(+) create mode 100644 aggs_filter.go create mode 100644 aggs_filter_test.go create mode 100644 aggs_nested.go create mode 100644 aggs_nested_test.go diff --git a/aggregations_test.go b/aggregations_test.go index d113f2d..39eeaa3 100644 --- a/aggregations_test.go +++ b/aggregations_test.go @@ -58,5 +58,39 @@ func TestAggregations(t *testing.T) { }, }, }, + { + "a complex, multi-aggregation, nested", + Aggregate( + NestedAgg("categories","categories"). + Aggs(TermsAgg("type","outdoors")), + FilterAgg("filtered", + Term("type", "t-shirt")), + ), + map[string]interface{}{ + "aggs": map[string]interface{}{ + "categories": map[string]interface{}{ + "nested": map[string]interface{}{ + "path": "categories", + }, + "aggs": map[string]interface{} { + "type": map[string]interface{} { + "terms": map[string]interface{} { + "field": "outdoors", + }, + }, + }, + }, + "filtered": map[string]interface{}{ + "filter": map[string]interface{}{ + "term": map[string]interface{}{ + "type": map[string]interface{} { + "value": "t-shirt", + }, + }, + }, + }, + }, + }, + }, }) } diff --git a/aggs_filter.go b/aggs_filter.go new file mode 100644 index 0000000..9927f98 --- /dev/null +++ b/aggs_filter.go @@ -0,0 +1,49 @@ +package esquery + +type FilterAggregation struct { + name string + filter Mappable + aggs []Aggregation +} + +// Filter creates a new aggregation of type "filter". The method name includes +// the "Agg" suffix to prevent conflict with the "filter" query. +func FilterAgg(name string, filter Mappable) *FilterAggregation { + return &FilterAggregation{ + name: name, + filter: filter, + } +} + +// Name returns the name of the aggregation. +func (agg *FilterAggregation) Name() string { + return agg.name +} + +// Filter sets the filter items +func (agg *FilterAggregation) Filter(filter Mappable) *FilterAggregation { + agg.filter = filter + return agg +} + +// Aggs sets sub-aggregations for the aggregation. +func (agg *FilterAggregation) Aggs(aggs ...Aggregation) *FilterAggregation { + agg.aggs = aggs + return agg +} + +func (agg *FilterAggregation) Map() map[string]interface{} { + outerMap := map[string]interface{}{ + "filter": agg.filter.Map(), + } + + if len(agg.aggs) > 0 { + subAggs := make(map[string]map[string]interface{}) + for _, sub := range agg.aggs { + subAggs[sub.Name()] = sub.Map() + } + outerMap["aggs"] = subAggs + } + + return outerMap +} diff --git a/aggs_filter_test.go b/aggs_filter_test.go new file mode 100644 index 0000000..1ec30aa --- /dev/null +++ b/aggs_filter_test.go @@ -0,0 +1,42 @@ +package esquery + +import "testing" + +func TestFilterAggs(t *testing.T) { + runMapTests(t, []mapTest{ + { + "filter agg: simple", + FilterAgg("filtered", Term("type","t-shirt")), + map[string]interface{}{ + "filter": map[string]interface{}{ + "term": map[string]interface{} { + "type": map[string]interface{} { + "value": "t-shirt", + }, + }, + }, + }, + }, + { + "filter agg: with aggs", + FilterAgg("filtered", Term("type","t-shirt")). + Aggs(Avg("avg_price","price")), + map[string]interface{}{ + "filter": map[string]interface{}{ + "term": map[string]interface{} { + "type": map[string]interface{} { + "value": "t-shirt", + }, + }, + }, + "aggs": map[string]interface{} { + "avg_price": map[string]interface{} { + "avg": map[string]interface{}{ + "field": "price", + }, + }, + }, + }, + }, + }) +} diff --git a/aggs_nested.go b/aggs_nested.go new file mode 100644 index 0000000..888f819 --- /dev/null +++ b/aggs_nested.go @@ -0,0 +1,53 @@ +package esquery + +type NestedAggregation struct { + name string + path string + aggs []Aggregation +} + +// NestedAgg creates a new aggregation of type "nested". The method name includes +// the "Agg" suffix to prevent conflict with the "nested" query. +func NestedAgg(name string, path string) *NestedAggregation { + return &NestedAggregation{ + name: name, + path: path, + } +} + +// Name returns the name of the aggregation. +func (agg *NestedAggregation) Name() string { + return agg.name +} + +// NumberOfFragments sets the aggregations path +func (agg *NestedAggregation) Path(p string) *NestedAggregation { + agg.path = p + return agg +} + +// Aggs sets sub-aggregations for the aggregation. +func (agg *NestedAggregation) Aggs(aggs ...Aggregation) *NestedAggregation { + agg.aggs = aggs + return agg +} + +func (agg *NestedAggregation) Map() map[string]interface{} { + innerMap := map[string]interface{}{ + "path": agg.path, + } + + outerMap := map[string]interface{}{ + "nested": innerMap, + } + + if len(agg.aggs) > 0 { + subAggs := make(map[string]map[string]interface{}) + for _, sub := range agg.aggs { + subAggs[sub.Name()] = sub.Map() + } + outerMap["aggs"] = subAggs + } + + return outerMap +} diff --git a/aggs_nested_test.go b/aggs_nested_test.go new file mode 100644 index 0000000..7384e6c --- /dev/null +++ b/aggs_nested_test.go @@ -0,0 +1,34 @@ +package esquery + +import "testing" + +func TestNestedAggs(t *testing.T) { + runMapTests(t, []mapTest{ + { + "nested agg: simple", + NestedAgg("simple", "categories"), + map[string]interface{}{ + "nested": map[string]interface{}{ + "path": "categories", + }, + }, + }, + { + "nested agg: with aggs", + NestedAgg("more_nested", "authors"). + Aggs(TermsAgg("authors","name")), + map[string]interface{}{ + "nested": map[string]interface{}{ + "path": "authors", + }, + "aggs": map[string]interface{} { + "authors": map[string]interface{} { + "terms": map[string]interface{} { + "field": "name", + }, + }, + }, + }, + }, + }) +}