From 0f04dfe911b14cb4acee4f4185e7c3845cbe1136 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Wed, 6 Jan 2021 16:26:24 +0000 Subject: [PATCH 1/2] Add support for rate aggregation (#5206) * Add support for rate aggregation * Add missing XML comments --- docs/aggregations.asciidoc | 4 + .../rate/rate-aggregation-usage.asciidoc | 94 +++++++++++ src/Nest/Aggregations/AggregateDictionary.cs | 2 + src/Nest/Aggregations/AggregationContainer.cs | 11 ++ .../Metric/Rate/RateAggregation.cs | 78 +++++++++ .../Visitor/AggregationVisitor.cs | 4 + .../Aggregations/Visitor/AggregationWalker.cs | 1 + .../Metric/Rate/RateAggregationUsageTests.cs | 158 ++++++++++++++++++ 8 files changed, 352 insertions(+) create mode 100644 docs/aggregations/metric/rate/rate-aggregation-usage.asciidoc create mode 100644 src/Nest/Aggregations/Metric/Rate/RateAggregation.cs create mode 100644 tests/Tests/Aggregations/Metric/Rate/RateAggregationUsageTests.cs diff --git a/docs/aggregations.asciidoc b/docs/aggregations.asciidoc index a7b5f26fb13..a98bc1844cf 100644 --- a/docs/aggregations.asciidoc +++ b/docs/aggregations.asciidoc @@ -56,6 +56,8 @@ The values are typically extracted from the fields of the document (using the fi * <> +* <> + * <> * <> @@ -100,6 +102,8 @@ include::aggregations/metric/percentile-ranks/percentile-ranks-aggregation-usage include::aggregations/metric/percentiles/percentiles-aggregation-usage.asciidoc[] +include::aggregations/metric/rate/rate-aggregation-usage.asciidoc[] + include::aggregations/metric/scripted-metric/scripted-metric-aggregation-usage.asciidoc[] include::aggregations/metric/stats/stats-aggregation-usage.asciidoc[] diff --git a/docs/aggregations/metric/rate/rate-aggregation-usage.asciidoc b/docs/aggregations/metric/rate/rate-aggregation-usage.asciidoc new file mode 100644 index 00000000000..226cfb9621a --- /dev/null +++ b/docs/aggregations/metric/rate/rate-aggregation-usage.asciidoc @@ -0,0 +1,94 @@ +:ref_current: https://www.elastic.co/guide/en/elasticsearch/reference/7.11 + +: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/7.x/src/Tests/Tests/Aggregations/Metric/Rate/RateAggregationUsageTests.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! +//// + +[[rate-aggregation-usage]] +=== Rate Aggregation Usage + +A rate metrics aggregation can be used only inside a date_histogram and calculates a rate of documents or a field in each +date_histogram bucket. The field values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents. + +Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-metrics-rate-aggregation.html[Rate Aggregation]. + +==== Fluent DSL example + +[source,csharp] +---- +a => a +.DateHistogram("by_date", d => d + .Field(f => f.StartedOn) + .CalendarInterval(DateInterval.Month) + .Aggregations(a => a + .Rate("my_rate", m => m + .Field(p => p.NumberOfCommits) + .Unit(DateInterval.Month) + .Mode(RateMode.Sum) + ))) +---- + +==== Object Initializer syntax example + +[source,csharp] +---- +new DateHistogramAggregation("by_date") +{ + Field = Field(p => p.StartedOn), + CalendarInterval = DateInterval.Month, + Aggregations = new RateAggregation("my_rate", Field(p => p.NumberOfCommits)) + { + Unit = DateInterval.Month, + Mode = RateMode.Sum + } +} +---- + +[source,javascript] +.Example json output +---- +{ + "by_date": { + "date_histogram": { + "field": "startedOn", + "calendar_interval": "month" + }, + "aggs": { + "my_rate": { + "rate": { + "field": "numberOfCommits", + "unit": "month", + "mode": "sum" + } + } + } + } +} +---- + +==== Handling Responses + +[source,csharp] +---- +response.ShouldBeValid(); + +var dateHistogram = response.Aggregations.DateHistogram("by_date"); +dateHistogram.Should().NotBeNull(); +dateHistogram.Buckets.Should().NotBeNull(); +dateHistogram.Buckets.Count.Should().BeGreaterThan(10); +foreach (var item in dateHistogram.Buckets) +{ + var rate = item.Rate("my_rate"); + rate.Should().NotBeNull(); + rate.Value.Should().BeGreaterOrEqualTo(1); +} +---- + diff --git a/src/Nest/Aggregations/AggregateDictionary.cs b/src/Nest/Aggregations/AggregateDictionary.cs index 1311ef54230..49fbb0b5c94 100644 --- a/src/Nest/Aggregations/AggregateDictionary.cs +++ b/src/Nest/Aggregations/AggregateDictionary.cs @@ -205,6 +205,8 @@ public MultiBucketAggregate> RareTerms(string key) }; } + public ValueAggregate Rate(string key) => TryGet(key); + public MultiBucketAggregate> RareTerms(string key) => RareTerms(key); public MultiBucketAggregate Range(string key) => GetMultiBucketAggregate(key); diff --git a/src/Nest/Aggregations/AggregationContainer.cs b/src/Nest/Aggregations/AggregationContainer.cs index 9f458c98e9d..cb7da84492b 100644 --- a/src/Nest/Aggregations/AggregationContainer.cs +++ b/src/Nest/Aggregations/AggregationContainer.cs @@ -232,6 +232,9 @@ public interface IAggregationContainer [DataMember(Name = "rare_terms")] IRareTermsAggregation RareTerms { get; set; } + [DataMember(Name = "rate")] + IRateAggregation Rate { get; set; } + [DataMember(Name = "reverse_nested")] IReverseNestedAggregation ReverseNested { get; set; } @@ -389,6 +392,8 @@ public class AggregationContainer : IAggregationContainer public IRareTermsAggregation RareTerms { get; set; } + public IRateAggregation Rate { get; set; } + public IReverseNestedAggregation ReverseNested { get; set; } public ISamplerAggregation Sampler { get; set; } @@ -551,6 +556,8 @@ public class AggregationContainerDescriptor : DescriptorBase, IRareTermsAggregation> selector ) => _SetInnerAggregation(name, selector, (a, d) => a.RareTerms = d); + public AggregationContainerDescriptor Rate(string name, + Func, IRateAggregation> selector) => + _SetInnerAggregation(name, selector, (a, d) => a.Rate = d); + public AggregationContainerDescriptor Stats(string name, Func, IStatsAggregation> selector ) => diff --git a/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs b/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs new file mode 100644 index 00000000000..134809f66c2 --- /dev/null +++ b/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs @@ -0,0 +1,78 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Runtime.Serialization; +using Elasticsearch.Net; +using Elasticsearch.Net.Utf8Json; + +namespace Nest +{ + [InterfaceDataContract] + [ReadAs(typeof(RateAggregation))] + public interface IRateAggregation : IMetricAggregation + { + /// + /// The to use as the rate unit. + /// + [DataMember(Name = "unit")] + DateInterval? Unit { get; set; } + + /// + /// The mode to use in the rate calculation. By default, "sum" mode is used. + /// + /// + /// The mode may be either "sum", where the rate calculates the sum of field values, or + /// "value_count", where the rate uses the number of values in the field. + /// + [DataMember(Name = "mode")] + RateMode? Mode { get; set; } + } + + public class RateAggregation : MetricAggregationBase, IRateAggregation + { + public RateAggregation(string name) : base(name, null) { } + public RateAggregation(string name, Field field) : base(name, field) { } + + internal override void WrapInContainer(AggregationContainer c) => c.Rate = this; + + /// + public DateInterval? Unit { get; set; } + + /// + public RateMode? Mode { get; set; } + } + + public class RateAggregationDescriptor + : MetricAggregationDescriptorBase, IRateAggregation, T>, IRateAggregation + where T : class + { + DateInterval? IRateAggregation.Unit { get; set; } + + RateMode? IRateAggregation.Mode { get; set; } + + /// + public RateAggregationDescriptor Unit(DateInterval? dateInterval) => + Assign(dateInterval, (a, v) => a.Unit = v); + + /// + public RateAggregationDescriptor Mode(RateMode? mode) => + Assign(mode, (a, v) => a.Mode = v); + } + + [StringEnum] + public enum RateMode + { + /// + /// Rate calculates the sum of field values. + /// + [EnumMember(Value = "sum")] + Sum, + + /// + /// Rate uses the number of values in the field. + /// + [EnumMember(Value = "value_count")] + ValueCount + } +} diff --git a/src/Nest/Aggregations/Visitor/AggregationVisitor.cs b/src/Nest/Aggregations/Visitor/AggregationVisitor.cs index 4d1c16da9be..809f016aaab 100644 --- a/src/Nest/Aggregations/Visitor/AggregationVisitor.cs +++ b/src/Nest/Aggregations/Visitor/AggregationVisitor.cs @@ -82,6 +82,8 @@ public interface IAggregationVisitor void Visit(IRareTermsAggregation aggregation); + void Visit(IRateAggregation aggregation); + void Visit(ITermsAggregation aggregation); void Visit(ISignificantTermsAggregation aggregation); @@ -241,6 +243,8 @@ public virtual void Visit(IRangeAggregation aggregation) { } public virtual void Visit(IRareTermsAggregation aggregation) { } + public virtual void Visit(IRateAggregation aggregation) { } + public virtual void Visit(INestedAggregation aggregation) { } public virtual void Visit(INormalizeAggregation aggregation) { } diff --git a/src/Nest/Aggregations/Visitor/AggregationWalker.cs b/src/Nest/Aggregations/Visitor/AggregationWalker.cs index afe48c1421b..6de8a4cb5a3 100644 --- a/src/Nest/Aggregations/Visitor/AggregationWalker.cs +++ b/src/Nest/Aggregations/Visitor/AggregationWalker.cs @@ -144,6 +144,7 @@ public void Walk(IAggregationContainer aggregation, IAggregationVisitor visitor) v.Visit(d); Accept(v, d.Aggregations); }); + AcceptAggregation(aggregation.Rate, visitor, (v, d) => v.Visit(d)); AcceptAggregation(aggregation.ReverseNested, visitor, (v, d) => { v.Visit(d); diff --git a/tests/Tests/Aggregations/Metric/Rate/RateAggregationUsageTests.cs b/tests/Tests/Aggregations/Metric/Rate/RateAggregationUsageTests.cs new file mode 100644 index 00000000000..e6884e53c8e --- /dev/null +++ b/tests/Tests/Aggregations/Metric/Rate/RateAggregationUsageTests.cs @@ -0,0 +1,158 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using FluentAssertions; +using Nest; +using Tests.Core.Extensions; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Domain; +using Tests.Framework.EndpointTests.TestState; +using static Nest.Infer; + +namespace Tests.Aggregations.Metric.Rate +{ + /** + * A rate metrics aggregation can be used only inside a date_histogram and calculates a rate of documents or a field in each + * date_histogram bucket. The field values can be generated by a provided script or extracted from specific numeric or histogram fields in the documents. + * + * Be sure to read the Elasticsearch documentation on {ref_current}/search-aggregations-metrics-rate-aggregation.html[Rate Aggregation]. + */ + [SkipVersion("<7.11.0", "Mode introduced in 7.11.0")] + public class RateAggregationUsageTests : AggregationUsageTestBase + { + public RateAggregationUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + + protected override object AggregationJson => new + { + by_date = new + { + date_histogram = new + { + field = "startedOn", + calendar_interval = "month" + }, + aggs = new + { + my_rate = new + { + rate = new + { + field = "numberOfCommits", + unit = "month", + mode = "sum" + } + } + } + } + }; + + protected override Func, IAggregationContainer> FluentAggs => a => a + .DateHistogram("by_date", d => d + .Field(f => f.StartedOn) + .CalendarInterval(DateInterval.Month) + .Aggregations(a => a + .Rate("my_rate", m => m + .Field(p => p.NumberOfCommits) + .Unit(DateInterval.Month) + .Mode(RateMode.Sum) + ))); + + protected override AggregationDictionary InitializerAggs => + new DateHistogramAggregation("by_date") + { + Field = Field(p => p.StartedOn), + CalendarInterval = DateInterval.Month, + Aggregations = new RateAggregation("my_rate", Field(p => p.NumberOfCommits)) + { + Unit = DateInterval.Month, + Mode = RateMode.Sum + } + }; + + protected override void ExpectResponse(ISearchResponse response) + { + response.ShouldBeValid(); + + var dateHistogram = response.Aggregations.DateHistogram("by_date"); + dateHistogram.Should().NotBeNull(); + dateHistogram.Buckets.Should().NotBeNull(); + dateHistogram.Buckets.Count.Should().BeGreaterThan(10); + foreach (var item in dateHistogram.Buckets) + { + var rate = item.Rate("my_rate"); + rate.Should().NotBeNull(); + rate.Value.Should().BeGreaterOrEqualTo(1); + } + } + } + + // hide + [SkipVersion("<7.10.0", "Rate aggregation introduced in 7.10.0")] + public class RateAggregationWithoutModeUsageTests : AggregationUsageTestBase + { + public RateAggregationWithoutModeUsageTests(ReadOnlyCluster i, EndpointUsage usage) : base(i, usage) { } + + protected override object AggregationJson => new + { + by_date = new + { + date_histogram = new + { + field = "startedOn", + calendar_interval = "month" + }, + aggs = new + { + my_rate = new + { + rate = new + { + unit = "month" + } + } + } + } + }; + + // We're also testing that we can skip providing the field on this aggregation + + protected override Func, IAggregationContainer> FluentAggs => a => a + .DateHistogram("by_date", d => d + .Field(f => f.StartedOn) + .CalendarInterval(DateInterval.Month) + .Aggregations(a => a + .Rate("my_rate", m => m + .Unit(DateInterval.Month) + ))); + + protected override AggregationDictionary InitializerAggs => + new DateHistogramAggregation("by_date") + { + Field = Field(p => p.StartedOn), + CalendarInterval = DateInterval.Month, + Aggregations = new RateAggregation("my_rate") + { + Unit = DateInterval.Month + } + }; + + protected override void ExpectResponse(ISearchResponse response) + { + response.ShouldBeValid(); + + var dateHistogram = response.Aggregations.DateHistogram("by_date"); + dateHistogram.Should().NotBeNull(); + dateHistogram.Buckets.Should().NotBeNull(); + dateHistogram.Buckets.Count.Should().BeGreaterThan(10); + foreach (var item in dateHistogram.Buckets) + { + var rate = item.Rate("my_rate"); + rate.Should().NotBeNull(); + rate.Value.Should().BeGreaterOrEqualTo(1); + } + } + } +} From 9e98b470bfad37fa76676723f94287f4f6f91eb7 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Mon, 11 Jan 2021 10:17:06 +0000 Subject: [PATCH 2/2] Fix namespace in master --- src/Nest/Aggregations/Metric/Rate/RateAggregation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs b/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs index 134809f66c2..aa21caa08c0 100644 --- a/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs +++ b/src/Nest/Aggregations/Metric/Rate/RateAggregation.cs @@ -4,7 +4,7 @@ using System.Runtime.Serialization; using Elasticsearch.Net; -using Elasticsearch.Net.Utf8Json; +using Nest.Utf8Json; namespace Nest {