Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the auto_date_histogram aggregations #3521

Merged
merged 1 commit into from
Jan 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Nest/Aggregations/AggregateDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,20 @@ public TermsAggregate<TKey> Terms<TKey>(string key)

public MultiBucketAggregate<DateHistogramBucket> DateHistogram(string key) => GetMultiBucketAggregate<DateHistogramBucket>(key);

public AutoDateHistogramAggregate AutoDateHistogram(string key)
{
var bucket = TryGet<BucketAggregate>(key);
if (bucket == null) return null;

return new AutoDateHistogramAggregate
{
Buckets = bucket.Items.OfType<DateHistogramBucket>().ToList(),
Meta = bucket.Meta,
Interval = bucket.Interval
};
}


public CompositeBucketAggregate Composite(string key)
{
var bucket = TryGet<BucketAggregate>(key);
Expand Down
7 changes: 7 additions & 0 deletions src/Nest/Aggregations/AggregateJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,12 @@ private IAggregate GetMultiBucketAggregate(JsonReader reader, JsonSerializer ser
} while (reader.TokenType != JsonToken.EndArray);
bucket.Items = items;
reader.Read();
if (reader.TokenType == JsonToken.PropertyName && (string)reader.Value == Parser.Interval)
{
var interval = reader.ReadAsString();
bucket.Interval = new Time(interval);
}

return bucket;
}

Expand Down Expand Up @@ -755,6 +761,7 @@ private static class Parser
public const string DocCountErrorUpperBound = "doc_count_error_upper_bound";
public const string Fields = "fields";
public const string From = "from";
public const string Interval = "interval";

public const string FromAsString = "from_as_string";
public const string Hits = "hits";
Expand Down
14 changes: 14 additions & 0 deletions src/Nest/Aggregations/AggregationContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ public interface IAggregationContainer
[JsonProperty("date_histogram")]
IDateHistogramAggregation DateHistogram { get; set; }

[JsonProperty("auto_date_histogram")]
IAutoDateHistogramAggregation AutoDateHistogram { get; set; }

[JsonProperty("date_range")]
IDateRangeAggregation DateRange { get; set; }

Expand Down Expand Up @@ -253,11 +256,15 @@ public class AggregationContainer : IAggregationContainer
public ICompositeAggregation Composite { get; set; }

public ICumulativeSumAggregation CumulativeSum { get; set; }

public IDateHistogramAggregation DateHistogram { get; set; }

public IAutoDateHistogramAggregation AutoDateHistogram { get; set; }

public IDateRangeAggregation DateRange { get; set; }

public IDerivativeAggregation Derivative { get; set; }

public IExtendedStatsAggregation ExtendedStats { get; set; }

public IExtendedStatsBucketAggregation ExtendedStatsBucket { get; set; }
Expand Down Expand Up @@ -386,6 +393,8 @@ public class AggregationContainerDescriptor<T> : DescriptorBase<AggregationConta

IDateHistogramAggregation IAggregationContainer.DateHistogram { get; set; }

IAutoDateHistogramAggregation IAggregationContainer.AutoDateHistogram { get; set; }

IDateRangeAggregation IAggregationContainer.DateRange { get; set; }

IDerivativeAggregation IAggregationContainer.Derivative { get; set; }
Expand Down Expand Up @@ -483,6 +492,11 @@ Func<DateHistogramAggregationDescriptor<T>, IDateHistogramAggregation> selector
) =>
_SetInnerAggregation(name, selector, (a, d) => a.DateHistogram = d);

public AggregationContainerDescriptor<T> AutoDateHistogram(string name,
Func<AutoDateHistogramAggregationDescriptor<T>, IAutoDateHistogramAggregation> selector
) =>
_SetInnerAggregation(name, selector, (a, d) => a.AutoDateHistogram = d);

public AggregationContainerDescriptor<T> Percentiles(string name,
Func<PercentilesAggregationDescriptor<T>, IPercentilesAggregation> selector
) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Newtonsoft.Json;

namespace Nest
{
[JsonObject(MemberSerialization = MemberSerialization.OptIn)]
[ContractJsonConverter(typeof(AggregationJsonConverter<AutoDateHistogramAggregation>))]
public interface IAutoDateHistogramAggregation : IBucketAggregation
{
[JsonProperty("field")]
Field Field { get; set; }

[JsonProperty("format")]
string Format { get; set; }

[JsonProperty("missing")]
DateTime? Missing { get; set; }

[JsonProperty("offset")]
string Offset { get; set; }

[JsonProperty("params")]
IDictionary<string, object> Params { get; set; }

[JsonProperty("script")]
IScript Script { get; set; }

[JsonProperty("time_zone")]
string TimeZone { get; set; }
}

public class AutoDateHistogramAggregation : BucketAggregationBase, IAutoDateHistogramAggregation
{
private string _format;

internal AutoDateHistogramAggregation() { }

public AutoDateHistogramAggregation(string name) : base(name) { }

public Field Field { get; set; }

//see: https://github.com/elastic/elasticsearch/issues/9725
public string Format
{
get => !string.IsNullOrEmpty(_format) &&
!_format.Contains("date_optional_time") &&
(Missing.HasValue)
? _format + "||date_optional_time"
: _format;
set => _format = value;
}

public DateTime? Missing { get; set; }
public string Offset { get; set; }
public IDictionary<string, object> Params { get; set; }
public IScript Script { get; set; }
public string TimeZone { get; set; }

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

public class AutoDateHistogramAggregationDescriptor<T>
: BucketAggregationDescriptorBase<AutoDateHistogramAggregationDescriptor<T>, IAutoDateHistogramAggregation, T>
, IAutoDateHistogramAggregation
where T : class
{
private string _format;

Field IAutoDateHistogramAggregation.Field { get; set; }

//see: https://github.com/elastic/elasticsearch/issues/9725
string IAutoDateHistogramAggregation.Format
{
get => !string.IsNullOrEmpty(_format) &&
!_format.Contains("date_optional_time") &&
(Self.Missing.HasValue)
? _format + "||date_optional_time"
: _format;
set => _format = value;
}

DateTime? IAutoDateHistogramAggregation.Missing { get; set; }

string IAutoDateHistogramAggregation.Offset { get; set; }

IDictionary<string, object> IAutoDateHistogramAggregation.Params { get; set; }

IScript IAutoDateHistogramAggregation.Script { get; set; }

string IAutoDateHistogramAggregation.TimeZone { get; set; }

public AutoDateHistogramAggregationDescriptor<T> Field(Field field) => Assign(a => a.Field = field);

public AutoDateHistogramAggregationDescriptor<T> Field(Expression<Func<T, object>> field) => Assign(a => a.Field = field);

public AutoDateHistogramAggregationDescriptor<T> Script(string script) => Assign(a => a.Script = (InlineScript)script);

public AutoDateHistogramAggregationDescriptor<T> Script(Func<ScriptDescriptor, IScript> scriptSelector) =>
Assign(a => a.Script = scriptSelector?.Invoke(new ScriptDescriptor()));

public AutoDateHistogramAggregationDescriptor<T> Format(string format) => Assign(a => a.Format = format);

public AutoDateHistogramAggregationDescriptor<T> TimeZone(string timeZone) => Assign(a => a.TimeZone = timeZone);

public AutoDateHistogramAggregationDescriptor<T> Offset(string offset) => Assign(a => a.Offset = offset);

public AutoDateHistogramAggregationDescriptor<T> Missing(DateTime? missing) => Assign(a => a.Missing = missing);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Nest
{
public class AutoDateHistogramAggregate : MultiBucketAggregate<DateHistogramBucket>
{
public Time Interval { get; internal set; }
}
}
1 change: 1 addition & 0 deletions src/Nest/Aggregations/Bucket/BucketAggregate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ public class BucketAggregate : IAggregate
public IReadOnlyCollection<IBucket> Items { get; set; } = EmptyReadOnly<IBucket>.Collection;
public IReadOnlyDictionary<string, object> Meta { get; set; } = EmptyReadOnly<string, object>.Dictionary;
public long? SumOtherDocCount { get; set; }
public Time Interval { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public class DateHistogramAggregationDescriptor<T>
ExtendedBounds<DateMath> IDateHistogramAggregation.ExtendedBounds { get; set; }
Field IDateHistogramAggregation.Field { get; set; }

//see: https://github.com/elastic/elasticsearch/issues/9725
string IDateHistogramAggregation.Format
{
get => !string.IsNullOrEmpty(_format) &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System;
using System.Linq;
using FluentAssertions;
using Nest;
using Tests.Core.Extensions;
using Tests.Core.ManagedElasticsearch.Clusters;
using Tests.Domain;
using Tests.Framework.Integration;
using static Nest.Infer;
using static Tests.Domain.Helpers.TestValueHelper;

namespace Tests.Aggregations.Bucket.AutoDateHistogram
{
/**
* A multi-bucket aggregation similar to the Date Histogram Aggregation except instead of providing an interval to
* use as the width of each bucket, a target number of buckets is provided indicating the number of buckets needed
* and the interval of the buckets is automatically chosen to best achieve that target. The number of buckets
* returned will always be less than or equal to this target number.
*
* NOTE: When specifying a `format` **and** `extended_bounds` or `missing`, in order for Elasticsearch to be able to parse
* the serialized `DateTime` of `extended_bounds` or `missing` correctly, the `date_optional_time` format is included
* as part of the `format` value.
*
* Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-bucket-autodatehistogram-aggregation.html[Auto Date Histogram Aggregation].
*/
public class AutoDateHistogramAggregationUsageTests : ProjectsOnlyAggregationUsageTestBase
{
public AutoDateHistogramAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { }

protected override object AggregationJson => new
{
projects_started_per_month = new
{
auto_date_histogram = new
{
field = "startedOn",
format = "yyyy-MM-dd'T'HH:mm:ss||date_optional_time", //<1> Note the inclusion of `date_optional_time` to `format`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

++

missing = FixedDate
},
aggs = new
{
project_tags = new
{
nested = new
{
path = "tags"
},
aggs = new
{
tags = new
{
terms = new { field = "tags.name" }
}
}
}
}
}
};

protected override Func<AggregationContainerDescriptor<Project>, IAggregationContainer> FluentAggs => a => a
.AutoDateHistogram("projects_started_per_month", date => date
.Field(p => p.StartedOn)
.Format("yyyy-MM-dd'T'HH:mm:ss")
.Missing(FixedDate)
.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 AutoDateHistogramAggregation("projects_started_per_month")
{
Field = Field<Project>(p => p.StartedOn),
Format = "yyyy-MM-dd'T'HH:mm:ss",
Missing = FixedDate,
Aggregations = new NestedAggregation("project_tags")
{
Path = Field<Project>(p => p.Tags),
Aggregations = new TermsAggregation("tags")
{
Field = Field<Project>(p => p.Tags.First().Name)
}
}
};

protected override void ExpectResponse(ISearchResponse<Project> response)
{
/** ==== Handling responses
* The `AggregateDictionary found on `.Aggregations` on `ISearchResponse<T>` has several helper methods
* so we can fetch our aggregation results easily in the correct type.
* <<handling-aggregate-response, Be sure to read more about these helper methods>>
*/
response.ShouldBeValid();

var dateHistogram = response.Aggregations.AutoDateHistogram("projects_started_per_month");
dateHistogram.Should().NotBeNull();
dateHistogram.Interval.Should().NotBeNull();
dateHistogram.Buckets.Should().NotBeNull();
dateHistogram.Buckets.Count.Should().BeGreaterThan(1);
foreach (var item in dateHistogram.Buckets)
{
item.Date.Should().NotBe(default);
item.DocCount.Should().BeGreaterThan(0);

var nested = item.Nested("project_tags");
nested.Should().NotBeNull();

var nestedTerms = nested.Terms("tags");
nestedTerms.Buckets.Count.Should().BeGreaterThan(0);
}
}
}
}