Skip to content

Commit

Permalink
Add Parent Aggregation (#3609)
Browse files Browse the repository at this point in the history
Relates: elastic/elasticsearch#34210

This commit adds Parent Aggregation to the high level client.

(cherry picked from commit 1051a57)
  • Loading branch information
russcam committed Apr 3, 2019
1 parent 6645893 commit fcd824a
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/aggregations.asciidoc
Expand Up @@ -136,6 +136,8 @@ In addition to the buckets themselves, the bucket aggregations also compute and

* <<nested-aggregation-usage,Nested Aggregation Usage>>

* <<parent-aggregation-usage,Parent Aggregation Usage>>

* <<range-aggregation-usage,Range Aggregation Usage>>

* <<reverse-nested-aggregation-usage,Reverse Nested Aggregation Usage>>
Expand Down Expand Up @@ -189,6 +191,8 @@ include::aggregations/bucket/missing/missing-aggregation-usage.asciidoc[]

include::aggregations/bucket/nested/nested-aggregation-usage.asciidoc[]

include::aggregations/bucket/parent/parent-aggregation-usage.asciidoc[]

include::aggregations/bucket/range/range-aggregation-usage.asciidoc[]

include::aggregations/bucket/reverse-nested/reverse-nested-aggregation-usage.asciidoc[]
Expand Down
98 changes: 98 additions & 0 deletions docs/aggregations/bucket/parent/parent-aggregation-usage.asciidoc
@@ -0,0 +1,98 @@
:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/6.5

:github: https://github.com/elastic/elasticsearch-net

:nuget: https://www.nuget.org/packages

////
IMPORTANT NOTE
==============
This file has been generated from https://github.com/elastic/elasticsearch-net/tree/6.x/src/Tests/Tests/Aggregations/Bucket/Parent/ParentAggregationUsageTests.cs.
If you wish to submit a PR for any spelling mistakes, typos or grammatical errors for this file,
please modify the original csharp file found at the link and submit the PR with that change. Thanks!
////

[[parent-aggregation-usage]]
=== Parent Aggregation Usage

A special single bucket aggregation that selects parent documents that have the specified type, as defined in a `join` field.

Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-parent-aggregation.html[Parent Aggregation].

==== Fluent DSL example

[source,csharp]
----
a => a
.Parent<Project>("name_of_parent_agg", parent => parent <1>
.Aggregations(parentAggs => parentAggs
.Average("average_commits", avg => avg.Field(p => p.NumberOfCommits))
.Max("max_commits", avg => avg.Field(p => p.NumberOfCommits))
.Min("min_commits", avg => avg.Field(p => p.NumberOfCommits))
)
)
----
<1> sub-aggregations are on the type determined from the generic type parameter. In this example, the search is against `CommitActivity` type and `Project` is a parent of `CommitActivity`

==== Object Initializer syntax example

[source,csharp]
----
new ParentAggregation("name_of_parent_agg", typeof(CommitActivity)) <1>
{
Aggregations =
new AverageAggregation("average_commits", Field<Project>(f => f.NumberOfCommits)) <2>
&& new MaxAggregation("max_commits", Field<Project>(f => f.NumberOfCommits))
&& new MinAggregation("min_commits", Field<Project>(f => f.NumberOfCommits))
}
----
<1> `join` field is determined from the _child_ type. In this example, it is `CommitActivity`

<2> sub-aggregations are on the type determined from the `join` field. In this example, a `Project` is a parent of `CommitActivity`

[source,javascript]
.Example json output
----
{
"size": 0,
"aggs": {
"name_of_parent_agg": {
"parent": {
"type": "commits"
},
"aggs": {
"average_commits": {
"avg": {
"field": "numberOfCommits"
}
},
"max_commits": {
"max": {
"field": "numberOfCommits"
}
},
"min_commits": {
"min": {
"field": "numberOfCommits"
}
}
}
}
}
}
----

==== Handling Responses

[source,csharp]
----
response.ShouldBeValid();
var parentAgg = response.Aggregations.Parent("name_of_parent_agg");
parentAgg.Should().NotBeNull();
parentAgg.DocCount.Should().BeGreaterThan(0);
parentAgg.Min("average_commits").Should().NotBeNull();
parentAgg.Min("min_commits").Should().NotBeNull();
parentAgg.Max("max_commits").Should().NotBeNull();
----

2 changes: 2 additions & 0 deletions src/Nest/Aggregations/AggregateDictionary.cs
Expand Up @@ -115,6 +115,8 @@ public FiltersAggregate Filters(string key)

public SingleBucketAggregate Children(string key) => TryGet<SingleBucketAggregate>(key);

public SingleBucketAggregate Parent(string key) => TryGet<SingleBucketAggregate>(key);

public SingleBucketAggregate Sampler(string key) => TryGet<SingleBucketAggregate>(key);

public GeoCentroidAggregate GeoCentroid(string key) => TryGet<GeoCentroidAggregate>(key);
Expand Down
12 changes: 12 additions & 0 deletions src/Nest/Aggregations/AggregationContainer.cs
Expand Up @@ -189,6 +189,9 @@ public interface IAggregationContainer
[DataMember(Name = "nested")]
INestedAggregation Nested { get; set; }

[DataMember(Name = "parent")]
IParentAggregation Parent { get; set; }

[DataMember(Name = "percentile_ranks")]
IPercentileRanksAggregation PercentileRanks { get; set; }

Expand Down Expand Up @@ -316,6 +319,8 @@ public class AggregationContainer : IAggregationContainer

public INestedAggregation Nested { get; set; }

public IParentAggregation Parent { get; set; }

public IPercentileRanksAggregation PercentileRanks { get; set; }

public IPercentilesAggregation Percentiles { get; set; }
Expand Down Expand Up @@ -452,6 +457,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta

INestedAggregation IAggregationContainer.Nested { get; set; }

IParentAggregation IAggregationContainer.Parent { get; set; }

IPercentileRanksAggregation IAggregationContainer.PercentileRanks { get; set; }

IPercentilesAggregation IAggregationContainer.Percentiles { get; set; }
Expand Down Expand Up @@ -594,6 +601,11 @@ public void Accept(IAggregationVisitor visitor)
) =>
_SetInnerAggregation(name, selector, (a, d) => a.Nested = d);

public AggregationContainerDescriptor<T> Parent<TParent>(string name,
Func<ParentAggregationDescriptor<T, TParent>, IParentAggregation> selector
) where TParent : class =>
_SetInnerAggregation(name, selector, (a, d) => a.Parent = d);

public AggregationContainerDescriptor<T> ReverseNested(string name,
Func<ReverseNestedAggregationDescriptor<T>, IReverseNestedAggregation> selector
) =>
Expand Down
41 changes: 41 additions & 0 deletions src/Nest/Aggregations/Bucket/Parent/ParentAggregation.cs
@@ -0,0 +1,41 @@
using System.Runtime.Serialization;
using Elasticsearch.Net;

namespace Nest
{
[InterfaceDataContract]
[ReadAs(typeof(ParentAggregation))]
public interface IParentAggregation : IBucketAggregation
{
/// <summary>
/// The type for the child in the parent/child relationship
/// </summary>
[DataMember(Name = "type")]
RelationName Type { get; set; }
}

public class ParentAggregation : BucketAggregationBase, IParentAggregation
{
internal ParentAggregation() { }

public ParentAggregation(string name, RelationName type) : base(name) => Type = type;

public RelationName Type { get; set; }

internal override void WrapInContainer(AggregationContainer c) => c.Parent = this;
}

public class ParentAggregationDescriptor<T, TParent>
: BucketAggregationDescriptorBase<ParentAggregationDescriptor<T, TParent>, IParentAggregation, TParent>, IParentAggregation
where T : class
where TParent : class
{
RelationName IParentAggregation.Type { get; set; } = typeof(T);

public ParentAggregationDescriptor<T, TParent> Type(RelationName type) =>
Assign(a => a.Type = type);

public ParentAggregationDescriptor<T, TParent> Type<TOtherParent>() =>
Assign(a => a.Type = typeof(TOtherParent));
}
}
5 changes: 5 additions & 0 deletions src/Nest/Aggregations/Visitor/AggregationVisitor.cs
Expand Up @@ -66,6 +66,8 @@ public interface IAggregationVisitor

void Visit(INestedAggregation aggregation);

void Visit(IParentAggregation aggregation);

void Visit(IReverseNestedAggregation aggregation);

void Visit(IRangeAggregation aggregation);
Expand Down Expand Up @@ -149,6 +151,7 @@ public class AggregationVisitor : IAggregationVisitor

public virtual void Visit(ITermsAggregation aggregation) { }

// TODO: make virtual
public void Visit(ISignificantTextAggregation aggregation) { }

public virtual void Visit(IPercentileRanksAggregation aggregation) { }
Expand Down Expand Up @@ -195,6 +198,8 @@ public class AggregationVisitor : IAggregationVisitor

public virtual void Visit(INestedAggregation aggregation) { }

public virtual void Visit(IParentAggregation aggregation) { }

public virtual void Visit(ICardinalityAggregation aggregation) { }

public virtual void Visit(IGlobalAggregation aggregation) { }
Expand Down
5 changes: 5 additions & 0 deletions src/Nest/Aggregations/Visitor/AggregationWalker.cs
Expand Up @@ -111,6 +111,11 @@ public void Walk(IAggregationContainer aggregation, IAggregationVisitor visitor)
v.Visit(d);
Accept(v, d.Aggregations);
});
AcceptAggregation(aggregation.Parent, visitor, (v, d) =>
{
v.Visit(d);
Accept(v, d.Aggregations);
});
AcceptAggregation(aggregation.PercentileRanks, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.Percentiles, visitor, (v, d) => v.Visit(d));
AcceptAggregation(aggregation.Range, visitor, (v, d) =>
Expand Down
@@ -0,0 +1,130 @@
using System;
using System.Threading.Tasks;
using Elastic.Xunit.XunitPlumbing;
using Elasticsearch.Net;
using FluentAssertions;
using Nest;
using Tests.Core.Client;
using Tests.Core.Extensions;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Core.ManagedElasticsearch.NodeSeeders;
using Tests.Domain;
using Tests.Framework;
using Tests.Framework.Integration;
using static Nest.Infer;

namespace Tests.Aggregations.Bucket.Parent
{
/**
* A special single bucket aggregation that selects parent documents that have the specified type, as defined in a `join` field.
*
* Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-parent-aggregation.html[Parent Aggregation].
*/
public class ParentAggregationUsageTests : ApiIntegrationTestBase<ReadOnlyCluster, ISearchResponse<CommitActivity>, ISearchRequest, SearchDescriptor<CommitActivity>, SearchRequest<CommitActivity>>
{
public ParentAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }

protected override bool ExpectIsValid => true;

protected sealed override object ExpectJson => new
{
size = 0,
aggs = new
{
name_of_parent_agg = new
{
parent = new { type = "commits" },
aggs = new
{
average_commits = new
{
avg = new { field = "numberOfCommits" }
},
max_commits = new
{
max = new { field = "numberOfCommits" }
},
min_commits = new
{
min = new { field = "numberOfCommits" }
}
}
}
}
};

protected override int ExpectStatusCode => 200;

// hide
protected override Func<SearchDescriptor<CommitActivity>, ISearchRequest> Fluent => s => s
.Size(0)
.Index(DefaultSeeder.CommitsAliasFilter)
.TypedKeys(TestClient.Configuration.Random.TypedKeys)
.Aggregations(FluentAggs);

protected override HttpMethod HttpMethod => HttpMethod.POST;

// hide
protected override SearchRequest<CommitActivity> Initializer =>
new SearchRequest<CommitActivity>(DefaultSeeder.CommitsAliasFilter)
{
Size = 0,
TypedKeys = TestClient.Configuration.Random.TypedKeys,
Aggregations = InitializerAggs
};

protected override string UrlPath => $"/commits-only/_search";

// https://youtrack.jetbrains.com/issue/RIDER-19912
[U] protected override Task HitsTheCorrectUrl() => base.HitsTheCorrectUrl();

[U] protected override Task UsesCorrectHttpMethod() => base.UsesCorrectHttpMethod();

[U] protected override void SerializesInitializer() => base.SerializesInitializer();

[U] protected override void SerializesFluent() => base.SerializesFluent();

[I] public override Task ReturnsExpectedStatusCode() => base.ReturnsExpectedResponse();

[I] public override Task ReturnsExpectedIsValid() => base.ReturnsExpectedIsValid();

[I] public override Task ReturnsExpectedResponse() => base.ReturnsExpectedResponse();

protected override LazyResponses ClientUsage() => Calls(
(client, f) => client.Search<CommitActivity>(f),
(client, f) => client.SearchAsync<CommitActivity>(f),
(client, r) => client.Search<CommitActivity>(r),
(client, r) => client.SearchAsync<CommitActivity>(r)
);

protected Func<AggregationContainerDescriptor<CommitActivity>, IAggregationContainer> FluentAggs => a => a
.Parent<Project>("name_of_parent_agg", parent => parent // <1> sub-aggregations are on the type determined from the generic type parameter. In this example, the search is against `CommitActivity` type and `Project` is a parent of `CommitActivity`
.Aggregations(parentAggs => parentAggs
.Average("average_commits", avg => avg.Field(p => p.NumberOfCommits))
.Max("max_commits", avg => avg.Field(p => p.NumberOfCommits))
.Min("min_commits", avg => avg.Field(p => p.NumberOfCommits))
)
);

protected AggregationDictionary InitializerAggs =>
new ParentAggregation("name_of_parent_agg", typeof(CommitActivity)) // <1> `join` field is determined from the _child_ type. In this example, it is `CommitActivity`
{
Aggregations =
new AverageAggregation("average_commits", Field<Project>(f => f.NumberOfCommits)) // <2> sub-aggregations are on the type determined from the `join` field. In this example, a `Project` is a parent of `CommitActivity`
&& new MaxAggregation("max_commits", Field<Project>(f => f.NumberOfCommits))
&& new MinAggregation("min_commits", Field<Project>(f => f.NumberOfCommits))
};

protected override void ExpectResponse(ISearchResponse<CommitActivity> response)
{
response.ShouldBeValid();

var parentAgg = response.Aggregations.Parent("name_of_parent_agg");
parentAgg.Should().NotBeNull();
parentAgg.DocCount.Should().BeGreaterThan(0);
parentAgg.Min("average_commits").Should().NotBeNull();
parentAgg.Min("min_commits").Should().NotBeNull();
parentAgg.Max("max_commits").Should().NotBeNull();
}
}
}

0 comments on commit fcd824a

Please sign in to comment.