Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Please mark backwards incompatible changes with an exclamation mark at the start
## [Unreleased]

### Added
- The `sort` parameter for Elasticsearch's `TopHits` aggregation.
- The `missing` parameter for Elasticsearch's `Terms` aggregation.

## [29.10.0] - 2026-08-21
Expand Down
4 changes: 2 additions & 2 deletions lib/jay_api/elasticsearch/query_builder/aggregations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
module Elasticsearch
class QueryBuilder
# The list of aggregations to be included in an Elasticsearch query.
class Aggregations

Check warning on line 24 in lib/jay_api/elasticsearch/query_builder/aggregations.rb

View workflow job for this annotation

GitHub Actions / lint

[rubocop] reported by reviewdog 🐶 Class has too many lines. [106/100] Raw Output: lib/jay_api/elasticsearch/query_builder/aggregations.rb:24:7: C: Metrics/ClassLength: Class has too many lines. [106/100]
extend Forwardable

def_delegators :aggregations, :any?, :none?
Expand Down Expand Up @@ -70,8 +70,8 @@

# Adds a +top_hits+ type aggregation. For more information about the parameters
# @see JayAPI::Elasticsearch::QueryBuilder::Aggregations::TopHits#initialize
def top_hits(name, size:)
add(::JayAPI::Elasticsearch::QueryBuilder::Aggregations::TopHits.new(name, size: size))
def top_hits(name, **params)
add(::JayAPI::Elasticsearch::QueryBuilder::Aggregations::TopHits.new(name, **params))
end

# Adds an +scripted_metric+ type aggregation. For information about the parameters
Expand Down
16 changes: 11 additions & 5 deletions lib/jay_api/elasticsearch/query_builder/aggregations/top_hits.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,25 @@ class Aggregations
# Information on this type of aggregation can be found here:
# https://www.elastic.co/docs/reference/aggregations/search-aggregations-metrics-top-hits-aggregation
class TopHits < ::JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation
attr_reader :size
attr_reader :size, :sort

# @param [String] name The name used by Elasticsearch to identify each
# of the aggregations.
# @param [String] size The number of hits that will be returned.
def initialize(name, size:)
# @param [Hash] sort How to sort the documents to determine the top
# hits. When not specified the documents are sorted by the score of
# the main query.
def initialize(name, size:, sort: nil)
super(name)

@size = size
@sort = sort
end

# @return [self] A copy of the receiver.
def clone
copy = self.class.new(name, size: size)
optional_params = { sort: sort.deep_dup }.compact
copy = self.class.new(name, size: size, **optional_params)
copy.aggregations = aggregations.clone
copy
end
Expand All @@ -39,8 +44,9 @@ def to_h
super do
{
top_hits: {
size: size
}
size: size,
sort: sort.deep_dup
}.compact
}
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@
expect(method_call.size).to be(top_hits.size)
end

context "when no 'sort' has been given" do
it "has its 'sort' set to nil" do
expect(method_call.sort).to be_nil
end
end

context "when a 'sort' has been given" do
let(:sort) { { timestamp: :desc } }
let(:constructor_params) { super().merge(sort: sort) }

it "has its 'sort' set to the same value" do
expect(method_call.sort).to eq(sort)
end

it 'is not the same hash' do
expect(method_call.sort).not_to be(top_hits.sort)
end
end

context 'when the original object has nested aggregations' do
let(:cloned_aggregations) { instance_double(JayAPI::Elasticsearch::QueryBuilder::Aggregations) }

Expand All @@ -50,18 +69,46 @@
describe '#to_h' do
subject(:method_call) { top_hits.to_h }

let(:expected_hash) do
{
'an_aggregation_sample' => {
top_hits: { size: 1 }
it_behaves_like 'JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation#to_h'

context "when no 'sort' has been given" do
let(:expected_hash) do
{
'an_aggregation_sample' => {
top_hits: { size: 1 }
}
}
}
end

it 'returns the expected Hash (does not include the :sort key)' do
expect(method_call).to eq(expected_hash)
end
end

it_behaves_like 'JayAPI::Elasticsearch::QueryBuilder::Aggregations::Aggregation#to_h'
context "when a 'sort' has been given" do
let(:sort) { { timestamp: :desc } }
let(:constructor_params) { super().merge(sort: sort) }

let(:expected_hash) do
{
'an_aggregation_sample' => {
top_hits: {
size: 1,
sort: { timestamp: :desc }
}
}
}
end

it 'returns the expected Hash' do
expect(method_call).to eq(expected_hash)
it 'returns the expected Hash (includes the expected :sort key)' do
expect(method_call).to eq(expected_hash)
end

it "does not return a reference to the internal 'sort' Hash" do
sort_hash = method_call.dig('an_aggregation_sample', :top_hits, :sort)
expect(sort_hash).not_to be(top_hits.sort)
expect(sort_hash).to eq(sort)
end
end
end
end
21 changes: 20 additions & 1 deletion spec/jay_api/elasticsearch/query_builder/aggregations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,16 @@

describe '#top_hits' do
subject(:method_call) do
aggregations.top_hits(name, size: size)
aggregations.top_hits(name, **method_params)
end

let(:name) { 'recent_logs' }
let(:size) { 10 }

let(:method_params) do
{ size: }
end

let(:top_hits) do
instance_double(
JayAPI::Elasticsearch::QueryBuilder::Aggregations::TopHits,
Expand All @@ -475,6 +479,21 @@
method_call
end

context "when a 'sort' parameter is given" do
let(:sort) do
{ date: { order: :desc } }
end

let(:method_params) { super().merge(sort:) }

it 'creates the TopHits instance with the expected parameters' do
expect(JayAPI::Elasticsearch::QueryBuilder::Aggregations::TopHits)
.to receive(:new).with(name, size: size, sort: sort)

method_call
end
end

it 'adds the TopHits instance to the array of aggregations' do
expect { method_call }.to change(aggregations, :to_h).to(aggs: { top_hits: 'TopHits#to_h' })
end
Expand Down
Loading