Skip to content

Commit e380f7d

Browse files
authored
feat(search): Allow aggregating on facets that are not explicitly part of default filter set (#8540)
1 parent ace74fa commit e380f7d

File tree

2 files changed

+88
-11
lines changed

2 files changed

+88
-11
lines changed

metadata-io/src/main/java/com/linkedin/metadata/search/elasticsearch/query/request/AggregationQueryBuilder.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
public class AggregationQueryBuilder {
2222

2323
private final SearchConfiguration _configs;
24-
private final Set<String> _facetFields;
24+
private final Set<String> _defaultFacetFields;
25+
private final Set<String> _allFacetFields;
2526

2627
public AggregationQueryBuilder(
2728
@Nonnull final SearchConfiguration configs,
2829
@Nonnull final List<SearchableAnnotation> annotations) {
2930
this._configs = Objects.requireNonNull(configs, "configs must not be null");
30-
this._facetFields = getFacetFields(annotations);
31+
this._defaultFacetFields = getDefaultFacetFields(annotations);
32+
this._allFacetFields = getAllFacetFields(annotations);
3133
}
3234

3335
/**
@@ -44,27 +46,36 @@ public List<AggregationBuilder> getAggregations(@Nullable List<String> facets) {
4446
final Set<String> facetsToAggregate;
4547
if (facets != null) {
4648
facets.stream().filter(f -> !isValidAggregate(f)).forEach(facet -> {
47-
log.warn(String.format("Provided facet for search filter aggregations that doesn't exist. Provided: %s; Available: %s", facet, _facetFields));
49+
log.warn(String.format("Requested facet for search filter aggregations that isn't part of the default filters. Provided: %s; Available: %s", facet,
50+
_defaultFacetFields));
4851
});
4952
facetsToAggregate = facets.stream().filter(this::isValidAggregate).collect(Collectors.toSet());
5053
} else {
51-
facetsToAggregate = _facetFields;
54+
facetsToAggregate = _defaultFacetFields;
5255
}
5356
return facetsToAggregate.stream().map(this::facetToAggregationBuilder).collect(Collectors.toList());
5457
}
5558

5659

57-
private Set<String> getFacetFields(final List<SearchableAnnotation> annotations) {
60+
private Set<String> getDefaultFacetFields(final List<SearchableAnnotation> annotations) {
5861
Set<String> facets = annotations.stream()
59-
.flatMap(annotation -> getFacetFieldsFromAnnotation(annotation).stream())
62+
.flatMap(annotation -> getDefaultFacetFieldsFromAnnotation(annotation).stream())
63+
.collect(Collectors.toSet());
64+
facets.add(INDEX_VIRTUAL_FIELD);
65+
return facets;
66+
}
67+
68+
private Set<String> getAllFacetFields(final List<SearchableAnnotation> annotations) {
69+
Set<String> facets = annotations.stream()
70+
.flatMap(annotation -> getAllFacetFieldsFromAnnotation(annotation).stream())
6071
.collect(Collectors.toSet());
6172
facets.add(INDEX_VIRTUAL_FIELD);
6273
return facets;
6374
}
6475

6576
private boolean isValidAggregate(final String inputFacet) {
6677
Set<String> facets = Set.of(inputFacet.split(AGGREGATION_SEPARATOR_CHAR));
67-
return facets.size() > 0 && _facetFields.containsAll(facets);
78+
return facets.size() > 0 && _allFacetFields.containsAll(facets);
6879
}
6980

7081
private AggregationBuilder facetToAggregationBuilder(final String inputFacet) {
@@ -97,7 +108,7 @@ private String getAggregationField(final String facet) {
97108
return ESUtils.toKeywordField(facet, false);
98109
}
99110

100-
List<String> getFacetFieldsFromAnnotation(final SearchableAnnotation annotation) {
111+
List<String> getDefaultFacetFieldsFromAnnotation(final SearchableAnnotation annotation) {
101112
final List<String> facetsFromAnnotation = new ArrayList<>();
102113
if (annotation.isAddToFilters()) {
103114
facetsFromAnnotation.add(annotation.getFieldName());
@@ -107,4 +118,13 @@ List<String> getFacetFieldsFromAnnotation(final SearchableAnnotation annotation)
107118
}
108119
return facetsFromAnnotation;
109120
}
110-
}
121+
122+
List<String> getAllFacetFieldsFromAnnotation(final SearchableAnnotation annotation) {
123+
final List<String> facetsFromAnnotation = new ArrayList<>();
124+
facetsFromAnnotation.add(annotation.getFieldName());
125+
if (annotation.getHasValuesFieldName().isPresent()) {
126+
facetsFromAnnotation.add(annotation.getHasValuesFieldName().get());
127+
}
128+
return facetsFromAnnotation;
129+
}
130+
}

metadata-io/src/test/java/com/linkedin/metadata/search/elasticsearch/query/request/AggregationQueryBuilderTest.java

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.linkedin.metadata.search.elasticsearch.query.request;
22

3+
import com.google.common.collect.ImmutableSet;
34
import com.linkedin.metadata.config.search.SearchConfiguration;
45
import com.google.common.collect.ImmutableList;
56
import com.linkedin.metadata.models.annotation.SearchableAnnotation;
67
import java.util.Collections;
78
import java.util.List;
89
import java.util.Optional;
10+
import java.util.Set;
11+
import java.util.stream.Collectors;
912
import org.elasticsearch.search.aggregations.AggregationBuilder;
1013
import org.testng.Assert;
1114
import org.testng.annotations.Test;
@@ -14,7 +17,7 @@
1417
public class AggregationQueryBuilderTest {
1518

1619
@Test
17-
public void testGetAggregationsHasFields() {
20+
public void testGetDefaultAggregationsHasFields() {
1821

1922
SearchableAnnotation annotation = new SearchableAnnotation(
2023
"test",
@@ -43,7 +46,7 @@ public void testGetAggregationsHasFields() {
4346
}
4447

4548
@Test
46-
public void testGetAggregationsFields() {
49+
public void testGetDefaultAggregationsFields() {
4750

4851
SearchableAnnotation annotation = new SearchableAnnotation(
4952
"test",
@@ -70,4 +73,58 @@ public void testGetAggregationsFields() {
7073

7174
Assert.assertTrue(aggs.stream().anyMatch(agg -> agg.getName().equals("test")));
7275
}
76+
77+
@Test
78+
public void testGetSpecificAggregationsHasFields() {
79+
80+
SearchableAnnotation annotation1 = new SearchableAnnotation(
81+
"test1",
82+
SearchableAnnotation.FieldType.KEYWORD,
83+
true,
84+
true,
85+
false,
86+
false,
87+
Optional.empty(),
88+
Optional.of("Has Test"),
89+
1.0,
90+
Optional.of("hasTest1"),
91+
Optional.empty(),
92+
Collections.emptyMap()
93+
);
94+
95+
SearchableAnnotation annotation2 = new SearchableAnnotation(
96+
"test2",
97+
SearchableAnnotation.FieldType.KEYWORD,
98+
true,
99+
true,
100+
false,
101+
false,
102+
Optional.of("Test Filter"),
103+
Optional.empty(),
104+
1.0,
105+
Optional.empty(),
106+
Optional.empty(),
107+
Collections.emptyMap()
108+
);
109+
110+
SearchConfiguration config = new SearchConfiguration();
111+
config.setMaxTermBucketSize(25);
112+
113+
AggregationQueryBuilder builder = new AggregationQueryBuilder(
114+
config, ImmutableList.of(annotation1, annotation2));
115+
116+
// Case 1: Ask for fields that should exist.
117+
List<AggregationBuilder> aggs = builder.getAggregations(
118+
ImmutableList.of("test1", "test2", "hasTest1")
119+
);
120+
Assert.assertEquals(aggs.size(), 3);
121+
Set<String> facets = aggs.stream().map(AggregationBuilder::getName).collect(Collectors.toSet());
122+
Assert.assertEquals(ImmutableSet.of("test1", "test2", "hasTest1"), facets);
123+
124+
// Case 2: Ask for fields that should NOT exist.
125+
aggs = builder.getAggregations(
126+
ImmutableList.of("hasTest2")
127+
);
128+
Assert.assertEquals(aggs.size(), 0);
129+
}
73130
}

0 commit comments

Comments
 (0)