From 59b15fc80768d864b138936c2e93e735f94e2ba3 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Lievremont Date: Wed, 4 Feb 2015 14:25:15 +0100 Subject: [PATCH] SONAR-6137 Apply feedback on PR (refactoring of facet processing, tests split) --- .../org/sonar/server/search/FacetValue.java | 10 +- .../java/org/sonar/server/search/Facets.java | 126 +++++++++++++ .../java/org/sonar/server/search/Result.java | 81 +-------- .../issue/index/IssueIndexMediumTest.java | 73 +++++--- .../sonar/server/search/FacetValueTest.java | 13 ++ .../sonar/server/search/FacetsMediumTest.java | 167 ++++++++++++++++++ 6 files changed, 369 insertions(+), 101 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/search/Facets.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/search/FacetsMediumTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/FacetValue.java b/server/sonar-server/src/main/java/org/sonar/server/search/FacetValue.java index ad83b07c369..226ea4b57b6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/FacetValue.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/FacetValue.java @@ -47,7 +47,11 @@ public class FacetValue { } FacetValue that = (FacetValue) o; - return key == null ? that.key == null : key.equals(that.key); + if (key == null) { + return that.key == null; + } else { + return key.equals(that.key); + } } @Override @@ -58,8 +62,8 @@ public class FacetValue { @Override public String toString() { return "FacetValue{" + - "key='" + key + '\'' + - ", value=" + value + + "key='" + getKey() + '\'' + + ", value=" + getValue() + '}'; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java b/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java new file mode 100644 index 00000000000..09b99fc1795 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/search/Facets.java @@ -0,0 +1,126 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.search; + +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; +import org.apache.commons.lang.builder.ReflectionToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.aggregations.Aggregation; +import org.elasticsearch.search.aggregations.HasAggregations; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram; +import org.elasticsearch.search.aggregations.bucket.missing.Missing; +import org.elasticsearch.search.aggregations.bucket.terms.Terms; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +class Facets { + + private static final Logger LOGGER = LoggerFactory.getLogger(Facets.class); + + private final Multimap facetValues; + + public Facets(SearchResponse response) { + facetValues = LinkedHashMultimap.create(); + + if (response.getAggregations() != null) { + for (Aggregation facet : response.getAggregations()) { + this.processAggregation(facet); + } + } + } + + private void processAggregation(Aggregation aggregation) { + if (Missing.class.isAssignableFrom(aggregation.getClass())) { + processMissingAggregation(aggregation); + } else if (Terms.class.isAssignableFrom(aggregation.getClass())) { + processTermsAggregation(aggregation); + } else if (HasAggregations.class.isAssignableFrom(aggregation.getClass())) { + processSubAggregations(aggregation); + } else if (DateHistogram.class.isAssignableFrom(aggregation.getClass())) { + processDateHistogram(aggregation); + } else { + LOGGER.warn("Cannot process {} type of aggregation", aggregation.getClass()); + } + } + + private void processMissingAggregation(Aggregation aggregation) { + Missing missing = (Missing) aggregation; + long docCount = missing.getDocCount(); + if (docCount > 0L) { + this.facetValues.put(aggregation.getName().replace("_missing", ""), new FacetValue("", docCount)); + } + } + + private void processTermsAggregation(Aggregation aggregation) { + Terms termAggregation = (Terms) aggregation; + for (Terms.Bucket value : termAggregation.getBuckets()) { + String facetName = aggregation.getName(); + if (facetName.contains("__") && !facetName.startsWith("__")) { + facetName = facetName.substring(0, facetName.indexOf("__")); + } + facetName = facetName.replace("_selected", ""); + this.facetValues.put(facetName, new FacetValue(value.getKey(), value.getDocCount())); + } + } + + private void processSubAggregations(Aggregation aggregation) { + HasAggregations hasAggregations = (HasAggregations) aggregation; + for (Aggregation internalAggregation : hasAggregations.getAggregations()) { + this.processAggregation(internalAggregation); + } + } + + private void processDateHistogram(Aggregation aggregation) { + DateHistogram dateHistogram = (DateHistogram) aggregation; + for (DateHistogram.Bucket value : dateHistogram.getBuckets()) { + this.facetValues.put(dateHistogram.getName(), new FacetValue(value.getKeyAsText().toString(), value.getDocCount())); + } + } + + public Map> getFacets() { + return this.facetValues.asMap(); + } + + public Collection getFacetValues(String facetName) { + return this.facetValues.get(facetName); + } + + public List getFacetKeys(String facetName) { + List keys = new ArrayList(); + if (this.facetValues.containsKey(facetName)) { + for (FacetValue facetValue : facetValues.get(facetName)) { + keys.add(facetValue.getKey()); + } + } + return keys; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/Result.java b/server/sonar-server/src/main/java/org/sonar/server/search/Result.java index a35fab832a1..55b6faa6e73 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/Result.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/Result.java @@ -20,18 +20,9 @@ package org.sonar.server.search; import com.google.common.base.Preconditions; -import com.google.common.collect.LinkedHashMultimap; -import com.google.common.collect.Multimap; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.aggregations.Aggregation; -import org.elasticsearch.search.aggregations.HasAggregations; -import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram; -import org.elasticsearch.search.aggregations.bucket.missing.Missing; -import org.elasticsearch.search.aggregations.bucket.terms.Terms; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -40,10 +31,8 @@ import java.util.*; public class Result { - private static final Logger LOGGER = LoggerFactory.getLogger(Result.class); - private final List hits; - private final Multimap facets; + private final Facets facets; private final long total; private final String scrollId; private final BaseIndex index; @@ -55,7 +44,7 @@ public class Result { public Result(@Nullable BaseIndex index, SearchResponse response) { this.index = index; this.scrollId = response.getScrollId(); - this.facets = LinkedHashMultimap.create(); + this.facets = new Facets(response); this.total = (int) response.getHits().totalHits(); this.hits = new ArrayList(); if (index != null) { @@ -63,59 +52,6 @@ public class Result { this.hits.add(index.toDoc(hit.getSource())); } } - if (response.getAggregations() != null) { - for (Map.Entry facet : response.getAggregations().asMap().entrySet()) { - this.processAggregation(facet.getValue()); - } - } - } - - private void processAggregation(Aggregation aggregation) { - if (Missing.class.isAssignableFrom(aggregation.getClass())) { - processMissingAggregation(aggregation); - } else if (Terms.class.isAssignableFrom(aggregation.getClass())) { - processTermsAggregation(aggregation); - } else if (HasAggregations.class.isAssignableFrom(aggregation.getClass())) { - processSubAggregations(aggregation); - } else if (DateHistogram.class.isAssignableFrom(aggregation.getClass())) { - processDateHistogram(aggregation); - } else { - LOGGER.warn("Cannot process {} type of aggregation", aggregation.getClass()); - } - } - - private void processMissingAggregation(Aggregation aggregation) { - Missing missing = (Missing) aggregation; - long docCount = missing.getDocCount(); - if (docCount > 0L) { - this.facets.put(aggregation.getName().replace("_missing",""), new FacetValue("", docCount)); - } - } - - private void processTermsAggregation(Aggregation aggregation) { - Terms termAggregation = (Terms) aggregation; - for (Terms.Bucket value : termAggregation.getBuckets()) { - String facetName = aggregation.getName(); - if (facetName.contains("__") && !facetName.startsWith("__")) { - facetName = facetName.substring(0, facetName.indexOf("__")); - } - facetName = facetName.replace("_selected", ""); - this.facets.put(facetName, new FacetValue(value.getKey(), value.getDocCount())); - } - } - - private void processSubAggregations(Aggregation aggregation) { - HasAggregations hasAggregations = (HasAggregations) aggregation; - for (Aggregation internalAggregation : hasAggregations.getAggregations()) { - this.processAggregation(internalAggregation); - } - } - - private void processDateHistogram(Aggregation aggregation) { - DateHistogram dateHistogram = (DateHistogram) aggregation; - for (DateHistogram.Bucket value : dateHistogram.getBuckets()) { - this.facets.put(dateHistogram.getName(), new FacetValue(value.getKeyAsText().toString(), value.getDocCount())); - } } public Iterator scroll() { @@ -132,24 +68,17 @@ public class Result { } public Map> getFacets() { - return this.facets.asMap(); + return this.facets.getFacets(); } @CheckForNull public Collection getFacetValues(String facetName) { - return this.facets.get(facetName); + return this.facets.getFacetValues(facetName); } @CheckForNull public List getFacetKeys(String facetName) { - if (this.facets.containsKey(facetName)) { - List keys = new ArrayList(); - for (FacetValue facetValue : facets.get(facetName)) { - keys.add(facetValue.getKey()); - } - return keys; - } - return null; + return this.facets.getFacetKeys(facetName); } @Override diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java index c56a971ae2a..9da928b740f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexMediumTest.java @@ -652,24 +652,9 @@ public class IssueIndexMediumTest { } @Test - public void facet_on_created_at() throws Exception { + public void facet_on_created_at_with_less_than_20_days() throws Exception { - ComponentDto project = ComponentTesting.newProjectDto(); - ComponentDto file = ComponentTesting.newFileDto(project); - - TimeZone.setDefault(TimeZone.getTimeZone("GMT+04:00")); - - IssueDoc issue0 = IssueTesting.newDoc("ISSUE0", file).setFuncCreationDate(DateUtils.parseDateTime("2011-04-25T01:05:13+0400")); - IssueDoc issue1 = IssueTesting.newDoc("ISSUE1", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T12:34:56+0400")); - IssueDoc issue2 = IssueTesting.newDoc("ISSUE2", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T23:45:60+0400")); - IssueDoc issue3 = IssueTesting.newDoc("ISSUE3", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-02T12:34:56+0400")); - IssueDoc issue4 = IssueTesting.newDoc("ISSUE4", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-05T12:34:56+0400")); - IssueDoc issue5 = IssueTesting.newDoc("ISSUE5", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-20T12:34:56+0400")); - IssueDoc issue6 = IssueTesting.newDoc("ISSUE6", file).setFuncCreationDate(DateUtils.parseDateTime("2015-01-18T12:34:56+0400")); - - indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6); - - QueryContext queryContext = new QueryContext().addFacets(Arrays.asList("createdAt")); + QueryContext queryContext = fixtureForCreatedAtFacet(); Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2014-09-08")).build(), queryContext).getFacets().get("createdAt"); @@ -680,16 +665,28 @@ public class IssueIndexMediumTest { new FacetValue("2014-09-03T04:00:00+0000", 0), new FacetValue("2014-09-04T04:00:00+0000", 0), new FacetValue("2014-09-05T04:00:00+0000", 1)); + } + + @Test + public void facet_on_created_at_with_less_than_20_weeks() throws Exception { + + QueryContext queryContext = fixtureForCreatedAtFacet(); - createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2014-09-21")).build(), + Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2014-09-21")).build(), queryContext).getFacets().get("createdAt"); assertThat(createdAt).hasSize(3) .containsOnly( new FacetValue("2014-09-01T04:00:00+0000", 4), new FacetValue("2014-09-08T04:00:00+0000", 0), new FacetValue("2014-09-15T04:00:00+0000", 1)); + } + + @Test + public void facet_on_created_at_with_less_than_20_months() throws Exception { - createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2015-01-19")).build(), + QueryContext queryContext = fixtureForCreatedAtFacet(); + + Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2014-09-01")).createdBefore(DateUtils.parseDate("2015-01-19")).build(), queryContext).getFacets().get("createdAt"); assertThat(createdAt).hasSize(5) .containsOnly( @@ -698,8 +695,14 @@ public class IssueIndexMediumTest { new FacetValue("2014-11-01T04:00:00+0000", 0), new FacetValue("2014-12-01T04:00:00+0000", 0), new FacetValue("2015-01-01T04:00:00+0000", 1)); + } - createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2011-01-01")).createdBefore(DateUtils.parseDate("2016-01-01")).build(), + @Test + public void facet_on_created_at_with_more_than_20_months() throws Exception { + + QueryContext queryContext = fixtureForCreatedAtFacet(); + + Collection createdAt = index.search(IssueQuery.builder().createdAfter(DateUtils.parseDate("2011-01-01")).createdBefore(DateUtils.parseDate("2016-01-01")).build(), queryContext).getFacets().get("createdAt"); assertThat(createdAt).hasSize(5) .containsOnly( @@ -709,8 +712,14 @@ public class IssueIndexMediumTest { new FacetValue("2014-01-01T04:00:00+0000", 5), new FacetValue("2015-01-01T04:00:00+0000", 1)); - // createdAfter not set: taking min value - createdAt = index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2016-01-01")).build(), + } + + @Test + public void facet_on_created_at_without_start_bound() throws Exception { + + QueryContext queryContext = fixtureForCreatedAtFacet(); + + Collection createdAt = index.search(IssueQuery.builder().createdBefore(DateUtils.parseDate("2016-01-01")).build(), queryContext).getFacets().get("createdAt"); assertThat(createdAt).hasSize(5) .containsOnly( @@ -721,6 +730,26 @@ public class IssueIndexMediumTest { new FacetValue("2015-01-01T04:00:00+0000", 1)); } + protected QueryContext fixtureForCreatedAtFacet() { + ComponentDto project = ComponentTesting.newProjectDto(); + ComponentDto file = ComponentTesting.newFileDto(project); + + TimeZone.setDefault(TimeZone.getTimeZone("GMT+04:00")); + + IssueDoc issue0 = IssueTesting.newDoc("ISSUE0", file).setFuncCreationDate(DateUtils.parseDateTime("2011-04-25T01:05:13+0400")); + IssueDoc issue1 = IssueTesting.newDoc("ISSUE1", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T12:34:56+0400")); + IssueDoc issue2 = IssueTesting.newDoc("ISSUE2", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-01T23:45:60+0400")); + IssueDoc issue3 = IssueTesting.newDoc("ISSUE3", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-02T12:34:56+0400")); + IssueDoc issue4 = IssueTesting.newDoc("ISSUE4", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-05T12:34:56+0400")); + IssueDoc issue5 = IssueTesting.newDoc("ISSUE5", file).setFuncCreationDate(DateUtils.parseDateTime("2014-09-20T12:34:56+0400")); + IssueDoc issue6 = IssueTesting.newDoc("ISSUE6", file).setFuncCreationDate(DateUtils.parseDateTime("2015-01-18T12:34:56+0400")); + + indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6); + + QueryContext queryContext = new QueryContext().addFacets(Arrays.asList("createdAt")); + return queryContext; + } + @Test public void paging() throws Exception { ComponentDto project = ComponentTesting.newProjectDto(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/FacetValueTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/FacetValueTest.java index 8b066698e8b..dd3ad35449d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/search/FacetValueTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/search/FacetValueTest.java @@ -39,5 +39,18 @@ public class FacetValueTest { assertThat(facetValue.equals(withNullKey)).isFalse(); assertThat(withNullKey.equals(withNullKey)).isTrue(); assertThat(withNullKey.equals(facetValue)).isFalse(); + assertThat(withNullKey.equals(new FacetValue(null, 666))).isTrue(); + } + + @Test + public void should_use_key_hashcode() { + assertThat(new FacetValue(null, 42).hashCode()).isZero(); + String key = "polop"; + assertThat(new FacetValue(key, 666).hashCode()).isEqualTo(key.hashCode()); + } + + @Test + public void should_define_toString() { + assertThat(new FacetValue("polop", 42).toString()).isEqualTo("FacetValue{key='polop', value=42}"); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/FacetsMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/FacetsMediumTest.java new file mode 100644 index 00000000000..5d999246f4d --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/search/FacetsMediumTest.java @@ -0,0 +1,167 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.search; + +import com.google.common.collect.ImmutableMap; +import org.elasticsearch.action.search.SearchRequestBuilder; +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram.Interval; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.DateUtils; +import org.sonar.server.es.EsTester; +import org.sonar.server.es.NewIndex.NewIndexType; + +import java.util.Arrays; +import java.util.Date; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class FacetsMediumTest { + + private static final String INDEX = "facetstests"; + private static final String TYPE = "tagsdoc"; + private static final String FIELD_KEY = "key"; + private static final String FIELD_TAGS = "tags"; + private static final String FIELD_CREATED_AT = "createdAt"; + + @Rule + public EsTester esTester = new EsTester().addDefinitions(new FacetsTestDefinition()); + + @Test + public void should_ignore_result_without_aggregations() throws Exception { + Facets facets = new Facets(mock(SearchResponse.class)); + assertThat(facets.getFacets()).isEmpty(); + assertThat(facets.getFacetKeys("polop")).isEmpty(); + assertThat(facets.getFacetValues("polop")).isEmpty(); + } + + @Test + public void should_ignore_unknown_aggregation_type() throws Exception { + esTester.putDocuments(INDEX, TYPE, + newTagsDocument("noTags"), + newTagsDocument("oneTag", "tag1"), + newTagsDocument("twoTags", "tag1", "tag2"), + newTagsDocument("twoTags", "tag1", "tag2", "tag3"), + newTagsDocument("twoTags", "tag1", "tag2", "tag3", "tag4")); + SearchRequestBuilder search = esTester.client().prepareSearch(INDEX).setTypes(TYPE) + .addAggregation(AggregationBuilders.cardinality(FIELD_TAGS).field(FIELD_TAGS)); + + Facets facets = new Facets(search.get()); + assertThat(facets.getFacets()).isEmpty(); + assertThat(facets.getFacetKeys(FIELD_TAGS)).isEmpty(); + } + + @Test + public void should_process_result_with_nested_missing_and_terms_aggregations() throws Exception { + esTester.putDocuments(INDEX, TYPE, + newTagsDocument("noTags"), + newTagsDocument("oneTag", "tag1"), + newTagsDocument("twoTags", "tag1", "tag2"), + newTagsDocument("twoTags", "tag1", "tag2", "tag3"), + newTagsDocument("twoTags", "tag1", "tag2", "tag3", "tag4")); + + SearchRequestBuilder search = esTester.client().prepareSearch(INDEX).setTypes(TYPE) + .addAggregation(AggregationBuilders.global("tags__global") + .subAggregation(AggregationBuilders.missing("tags_missing").field(FIELD_TAGS)) + .subAggregation(AggregationBuilders.terms("tags").field(FIELD_TAGS).size(2)) + .subAggregation(AggregationBuilders.terms("tags__selected").field(FIELD_TAGS).include("tag4")) + .subAggregation(AggregationBuilders.terms("__ignored").field(FIELD_TAGS).include("tag3"))); + + Facets facets = new Facets(search.get()); + assertThat(facets.getFacets()).isNotEmpty(); + assertThat(facets.getFacetKeys(FIELD_TAGS)).containsOnly("", "tag1", "tag2", "tag4"); + assertThat(facets.getFacetKeys(FIELD_CREATED_AT)).isEmpty(); + assertThat(facets.toString()).isEqualTo("{" + + "tags=[" + + "FacetValue{key='tag1', value=2}, " + + "FacetValue{key='tag2', value=1}, " + + "FacetValue{key='tag4', value=1}, " + + "FacetValue{key='', value=1}" + + "], " + + "__ignored=[FacetValue{key='tag3', value=1}]" + + "}"); + } + + @Test + public void should_ignore_empty_missing_aggregation() throws Exception { + esTester.putDocuments(INDEX, TYPE, + newTagsDocument("oneTag", "tag1"), + newTagsDocument("twoTags", "tag1", "tag2"), + newTagsDocument("twoTags", "tag1", "tag2", "tag3"), + newTagsDocument("twoTags", "tag1", "tag2", "tag3", "tag4")); + + SearchRequestBuilder search = esTester.client().prepareSearch(INDEX).setTypes(TYPE) + .addAggregation(AggregationBuilders.global("tags__global") + .subAggregation(AggregationBuilders.missing("tags_missing").field(FIELD_TAGS)) + .subAggregation(AggregationBuilders.terms("tags").field(FIELD_TAGS).size(2)) + .subAggregation(AggregationBuilders.terms("tags__selected").field(FIELD_TAGS).include("tag4")) + .subAggregation(AggregationBuilders.terms("__ignored").field(FIELD_TAGS).include("tag3"))); + + Facets facets = new Facets(search.get()); + assertThat(facets.getFacets()).isNotEmpty(); + assertThat(facets.getFacetKeys(FIELD_TAGS)).containsOnly("tag1", "tag2", "tag4"); + assertThat(facets.getFacetKeys(FIELD_CREATED_AT)).isEmpty(); + } + + @Test + public void should_process_result_with_date_histogram() throws Exception { + esTester.putDocuments(INDEX, TYPE, + newTagsDocument("first"), newTagsDocument("second"), newTagsDocument("third")); + + SearchRequestBuilder search = esTester.client().prepareSearch(INDEX).setTypes(TYPE) + .addAggregation( + AggregationBuilders.dateHistogram(FIELD_CREATED_AT) + .field(FIELD_CREATED_AT) + .interval(Interval.MINUTE) + .format(DateUtils.DATETIME_FORMAT)); + + Facets facets = new Facets(search.get()); + assertThat(facets.getFacets()).isNotEmpty(); + assertThat(facets.getFacetKeys(FIELD_TAGS)).isEmpty(); + assertThat(facets.getFacetKeys(FIELD_CREATED_AT)).hasSize(1); + FacetValue value = facets.getFacetValues(FIELD_CREATED_AT).iterator().next(); + assertThat(DateUtils.parseDateTime(value.getKey()).before(new Date())); + assertThat(value.getValue()).isEqualTo(3L); + } + + private static Map newTagsDocument(String key, String... tags) { + ImmutableMap doc = ImmutableMap.of( + FIELD_KEY, key, + FIELD_TAGS, Arrays.asList(tags), + FIELD_CREATED_AT, new Date()); + return doc; + } + + static class FacetsTestDefinition implements org.sonar.server.es.IndexDefinition { + + @Override + public void define(IndexDefinitionContext context) { + NewIndexType newType = context.create(INDEX).createType(TYPE); + newType.setAttribute("_id", ImmutableMap.of("path", FIELD_KEY)); + newType.stringFieldBuilder(FIELD_KEY).build(); + newType.stringFieldBuilder(FIELD_TAGS).build(); + newType.createDateTimeField(FIELD_CREATED_AT); + } + } +} -- 2.39.5