diff --git a/src/Nest/Aggregations/Aggregate.cs b/src/Nest/Aggregations/Aggregate.cs
index e326f95364f..bc63a1780f6 100644
--- a/src/Nest/Aggregations/Aggregate.cs
+++ b/src/Nest/Aggregations/Aggregate.cs
@@ -3,12 +3,15 @@
namespace Nest
{
///
- /// Represents the result of an aggregation on the response
+ /// Aggregation response for an aggregation request
///
[ExactContractJsonConverter(typeof(AggregateJsonConverter))]
public interface IAggregate
{
//TODO this public set is problematic
+ ///
+ /// Metadata for the aggregation
+ ///
IReadOnlyDictionary Meta { get; set; }
}
}
diff --git a/src/Nest/Aggregations/AggregateDictionary.cs b/src/Nest/Aggregations/AggregateDictionary.cs
index fc5d3b59664..c59b27d8abf 100644
--- a/src/Nest/Aggregations/AggregateDictionary.cs
+++ b/src/Nest/Aggregations/AggregateDictionary.cs
@@ -175,7 +175,17 @@ public TermsAggregate Terms(string key)
public MultiBucketAggregate DateHistogram(string key) => GetMultiBucketAggregate(key);
- public MultiBucketAggregate Composite(string key) => GetMultiBucketAggregate(key);
+ public CompositeBucketAggregate Composite(string key)
+ {
+ var bucket = this.TryGet(key);
+ if (bucket == null) return null;
+ return new CompositeBucketAggregate
+ {
+ Buckets = bucket.Items.OfType().ToList(),
+ Meta = bucket.Meta,
+ AfterKey = new CompositeKey(bucket.AfterKey)
+ };
+ }
public MatrixStatsAggregate MatrixStats(string key) => this.TryGet(key);
diff --git a/src/Nest/Aggregations/AggregateJsonConverter.cs b/src/Nest/Aggregations/AggregateJsonConverter.cs
index b5366cd67b4..2bc12dc7733 100644
--- a/src/Nest/Aggregations/AggregateJsonConverter.cs
+++ b/src/Nest/Aggregations/AggregateJsonConverter.cs
@@ -36,6 +36,7 @@ private static class Parser
public const string Hits = "hits";
public const string Location = "location";
public const string Fields = "fields";
+ public const string AfterKey = "after_key";
public const string Key = "key";
public const string From = "from";
@@ -122,6 +123,16 @@ private IAggregate ReadAggregate(JsonReader reader, JsonSerializer serializer)
case Parser.Value:
aggregate = GetValueAggregate(reader, serializer);
break;
+ case Parser.AfterKey:
+ reader.Read();
+ var afterKeys = serializer.Deserialize>(reader);
+ reader.Read();
+ var bucketAggregate = reader.Value.ToString() == Parser.Buckets
+ ? this.GetMultiBucketAggregate(reader, serializer) as BucketAggregate ?? new BucketAggregate()
+ : new BucketAggregate();
+ bucketAggregate.AfterKey = afterKeys;
+ aggregate = bucketAggregate;
+ break;
case Parser.Buckets:
case Parser.DocCountErrorUpperBound:
aggregate = GetMultiBucketAggregate(reader, serializer);
diff --git a/src/Nest/Aggregations/Bucket/BucketAggregate.cs b/src/Nest/Aggregations/Bucket/BucketAggregate.cs
index 1aa934ab695..40a4c0e256b 100644
--- a/src/Nest/Aggregations/Bucket/BucketAggregate.cs
+++ b/src/Nest/Aggregations/Bucket/BucketAggregate.cs
@@ -14,17 +14,42 @@ public class SingleBucketAggregate : BucketAggregateBase
{
public SingleBucketAggregate(IReadOnlyDictionary aggregations) : base(aggregations) { }
+ ///
+ /// Count of documents in the bucket
+ ///
public long DocCount { get; internal set; }
}
+ ///
+ /// Aggregation response for a bucket aggregation
+ ///
+ ///
public class MultiBucketAggregate : IAggregate
where TBucket : IBucket
{
+ ///
public IReadOnlyDictionary Meta { get; set; }
+ ///
+ /// The buckets into which results are grouped
+ ///
public IReadOnlyCollection Buckets { get; set; } = EmptyReadOnly.Collection;
}
+ ///
+ /// Aggregation response of
+ ///
+ public class CompositeBucketAggregate : MultiBucketAggregate
+ {
+ ///
+ /// The composite key of the last bucket returned
+ /// in the response before any filtering by pipeline aggregations.
+ /// If all buckets are filtered/removed by pipeline aggregations,
+ /// will contain the composite key of the last bucket before filtering.
+ ///
+ /// Valid for Elasticsearch 6.3.0+
+ public CompositeKey AfterKey { get; set; }
+ }
// Intermediate object used for deserialization
public class BucketAggregate : IAggregate
@@ -35,5 +60,6 @@ public class BucketAggregate : IAggregate
public IReadOnlyDictionary Meta { get; set; } = EmptyReadOnly.Dictionary;
public long DocCount { get; set; }
public long BgCount { get; set; }
+ public IReadOnlyDictionary AfterKey { get; set; } = EmptyReadOnly.Dictionary;
}
}
diff --git a/src/Nest/Aggregations/Bucket/Composite/DateHistogramCompositeAggregationSource.cs b/src/Nest/Aggregations/Bucket/Composite/DateHistogramCompositeAggregationSource.cs
index 77fd4cd5f51..fc700b44987 100644
--- a/src/Nest/Aggregations/Bucket/Composite/DateHistogramCompositeAggregationSource.cs
+++ b/src/Nest/Aggregations/Bucket/Composite/DateHistogramCompositeAggregationSource.cs
@@ -22,6 +22,13 @@ public interface IDateHistogramCompositeAggregationSource : ICompositeAggregatio
///
[JsonProperty("time_zone")]
string Timezone { get; set; }
+
+ ///
+ /// Return a formatted date string as the key instead an epoch long
+ ///
+ /// Valid for Elasticsearch 6.3.0+
+ [JsonProperty("format")]
+ string Format { get; set; }
}
///
@@ -35,6 +42,9 @@ public DateHistogramCompositeAggregationSource(string name) : base(name) {}
///
public string Timezone { get; set; }
+ ///
+ public string Format { get; set; }
+
///
protected override string SourceType => "date_histogram";
}
@@ -46,6 +56,7 @@ public class DateHistogramCompositeAggregationSourceDescriptor
{
Union IDateHistogramCompositeAggregationSource.Interval { get; set; }
string IDateHistogramCompositeAggregationSource.Timezone { get; set; }
+ string IDateHistogramCompositeAggregationSource.Format { get; set; }
public DateHistogramCompositeAggregationSourceDescriptor(string name) : base(name, "date_histogram") {}
@@ -58,7 +69,9 @@ public DateHistogramCompositeAggregationSourceDescriptor Interval(Time interv
Assign(a => a.Interval = interval);
///
- public DateHistogramCompositeAggregationSourceDescriptor Timezone(string timezone) =>
- Assign(a => a.Timezone = timezone);
+ public DateHistogramCompositeAggregationSourceDescriptor Timezone(string timezone) => Assign(a => a.Timezone = timezone);
+
+ ///
+ public DateHistogramCompositeAggregationSourceDescriptor Format(string format) => Assign(a => a.Format = format);
}
}
diff --git a/src/Tests/Tests/Aggregations/Bucket/Composite/CompositeAggregationUsageTests.cs b/src/Tests/Tests/Aggregations/Bucket/Composite/CompositeAggregationUsageTests.cs
index cb00d3197b6..0f22d0ebcf7 100644
--- a/src/Tests/Tests/Aggregations/Bucket/Composite/CompositeAggregationUsageTests.cs
+++ b/src/Tests/Tests/Aggregations/Bucket/Composite/CompositeAggregationUsageTests.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using Elastic.Xunit.XunitPlumbing;
using FluentAssertions;
using Nest;
using Newtonsoft.Json;
+using Tests.Configuration;
using Tests.Core.Extensions;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Domain;
@@ -163,6 +165,12 @@ protected override void ExpectResponse(ISearchResponse response)
var composite = response.Aggregations.Composite("my_buckets");
composite.Should().NotBeNull();
composite.Buckets.Should().NotBeNullOrEmpty();
+ composite.AfterKey.Should().NotBeNull();
+ if (TestConfiguration.Instance.InRange(">=6.3.0"))
+ {
+ composite.AfterKey.Should().HaveCount(3)
+ .And.ContainKeys("branches", "started", "branch_count");
+ }
foreach (var item in composite.Buckets)
{
var key = item.Key;
@@ -187,4 +195,118 @@ protected override void ExpectResponse(ISearchResponse response)
}
}
}
+
+
+ //hide
+ [SkipVersion("<6.3.0", "Date histogram source only supports format starting from Elasticsearch 6.3.0+")]
+ public class DateFormatCompositeAggregationUsageTests : ProjectsOnlyAggregationUsageTestBase
+ {
+ public DateFormatCompositeAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }
+
+ protected override object AggregationJson => new
+ {
+ my_buckets = new
+ {
+ composite = new
+ {
+ sources = new object[]
+ {
+ new
+ {
+ started = new
+ {
+ date_histogram = new
+ {
+ field = "startedOn",
+ interval = "month",
+ format = "yyyy-MM-dd"
+ }
+ }
+ },
+ }
+ },
+ aggs = new
+ {
+ project_tags = new
+ {
+ nested = new
+ {
+ path = "tags"
+ },
+ aggs = new
+ {
+ tags = new
+ {
+ terms = new {field = "tags.name"}
+ }
+ }
+ }
+ }
+ }
+ };
+
+ protected override Func, IAggregationContainer> FluentAggs => a => a
+ .Composite("my_buckets", date => date
+ .Sources(s => s
+ .DateHistogram("started", d => d
+ .Field(f => f.StartedOn)
+ .Interval(DateInterval.Month)
+ .Format("yyyy-MM-dd")
+ )
+ )
+ .Aggregations(childAggs => childAggs
+ .Nested("project_tags", n => n
+ .Path(p => p.Tags)
+ .Aggregations(nestedAggs => nestedAggs
+ .Terms("tags", avg => avg.Field(p => p.Tags.First().Name))
+ )
+ )
+ )
+ );
+
+ protected override AggregationDictionary InitializerAggs =>
+ new CompositeAggregation("my_buckets")
+ {
+ Sources = new List
+ {
+ new DateHistogramCompositeAggregationSource("started")
+ {
+ Field = Infer.Field(f => f.StartedOn),
+ Interval = DateInterval.Month,
+ Format = "yyyy-MM-dd"
+ },
+ },
+ Aggregations = new NestedAggregation("project_tags")
+ {
+ Path = Field(p => p.Tags),
+ Aggregations = new TermsAggregation("tags")
+ {
+ Field = Field(p => p.Tags.First().Name)
+ }
+ }
+ };
+
+ /**==== Handling Responses
+ * Each Composite aggregation bucket key is an `CompositeKey`, a specialized
+ * `IReadOnlyDictionary` type with methods to convert values to supported types
+ */
+ protected override void ExpectResponse(ISearchResponse response)
+ {
+ response.ShouldBeValid();
+
+ var composite = response.Aggregations.Composite("my_buckets");
+ composite.Should().NotBeNull();
+ composite.Buckets.Should().NotBeNullOrEmpty();
+ composite.AfterKey.Should().NotBeNull();
+ composite.AfterKey.Should().HaveCount(1).And.ContainKeys("started");
+ foreach (var item in composite.Buckets)
+ {
+ var key = item.Key;
+ key.Should().NotBeNull();
+
+ key.TryGetValue("started", out string startedString).Should().BeTrue();
+ startedString.Should().NotBeNullOrWhiteSpace();
+ }
+ }
+ }
}
diff --git a/src/Tests/Tests/Tests.csproj b/src/Tests/Tests/Tests.csproj
index 9c8ea43f139..3c7b3eaab00 100644
--- a/src/Tests/Tests/Tests.csproj
+++ b/src/Tests/Tests/Tests.csproj
@@ -7,7 +7,7 @@
$(NoWarn);xUnit1013
-
+