package eu.luminis.elastic.search; import eu.luminis.elastic.IndexDocumentHelper; import eu.luminis.elastic.SingleClusterRestClientConfig; import eu.luminis.elastic.TestConfig; import eu.luminis.elastic.document.helpers.MessageEntity; import eu.luminis.elastic.document.helpers.MessageEntityTypeReference; import eu.luminis.elastic.index.IndexService; import eu.luminis.elastic.search.response.HitsAggsResponse; import eu.luminis.elastic.search.response.aggregations.Aggregation; import eu.luminis.elastic.search.response.aggregations.bucket.DateHistogramAggregation; import eu.luminis.elastic.search.response.aggregations.bucket.DateHistogramBucket; import eu.luminis.elastic.search.response.aggregations.bucket.HistogramAggregation; import eu.luminis.elastic.search.response.aggregations.bucket.HistogramBucket; import eu.luminis.elastic.search.response.aggregations.bucket.TermsAggregation; import eu.luminis.elastic.search.response.aggregations.metric.SingleValueMetricsAggregation; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.Date; import static eu.luminis.elastic.SingleClusterRestClientFactoryBean.DEFAULT_CLUSTER_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SingleClusterRestClientConfig.class, TestConfig.class}) public class SearchServiceAggsIT { public static final String TEST_AGGS = "test_aggs"; public static final int NUM_DAYS_TO_SUBTRACT_1 = 1; public static final int NUM_DAYS_TO_SUBTRACT_2 = 2; public static final int NUM_DAYS_TO_SUBTRACT_5 = 5; public static final ZoneId ZONE_ID = ZoneId.of("Europe/Amsterdam"); @Autowired private IndexService indexService; @Autowired private IndexDocumentHelper indexDocumentHelper; @Autowired private SingleClusterSearchService searchService; @Before public void setUp() throws Exception { Boolean test_aggs = indexService.indexExist(DEFAULT_CLUSTER_NAME, "test_aggs"); if (test_aggs) { indexService.dropIndex(DEFAULT_CLUSTER_NAME, TEST_AGGS); } String indexProps = "{\n" + " \"settings\": {\n" + " \"number_of_replicas\": 0,\n" + " \"number_of_shards\": 1\n" + " },\n" + " \"mappings\": {\n" + " \"test_agg\": {\n" + " \"properties\": {\n" + " \"message\": {\n" + " \"type\": \"text\"\n" + " },\n" + " \"tags\": {\n" + " \"type\": \"keyword\"\n" + " },\n" + " \"year\": {\n" + " \"type\": \"long\"\n" + " },\n" + " \"created\": {\n" + " \"type\": \"date\"\n" + " }\n" + " }\n" + " }\n" + " }\n" + "}"; indexService.createIndex(DEFAULT_CLUSTER_NAME, TEST_AGGS, indexProps); test_aggs = indexService.indexExist(DEFAULT_CLUSTER_NAME, "test_aggs"); assertTrue("The index should now exist", test_aggs); indexDocumentHelper.indexDocument(TEST_AGGS, "test_agg", "one", "message one", Collections.singletonList("news"), 1970L, createDate(NUM_DAYS_TO_SUBTRACT_1)); indexDocumentHelper.indexDocument(TEST_AGGS, "test_agg", "two", "message two", Collections.singletonList("blog"), 1980L, createDate(NUM_DAYS_TO_SUBTRACT_1)); indexDocumentHelper.indexDocument(TEST_AGGS, "test_agg", "three", "message three", Collections.singletonList("news"), 1985L, createDate(NUM_DAYS_TO_SUBTRACT_2)); indexDocumentHelper.indexDocument(TEST_AGGS, "test_agg", "four", "message four", Collections.singletonList("twitter"), 2000, createDate(NUM_DAYS_TO_SUBTRACT_5)); indexService.refreshIndexes(DEFAULT_CLUSTER_NAME, TEST_AGGS); } @Test public void findTerms() { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName("find_message_aggs.twig") .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(4, response.getHits().size()); Aggregation byTags = response.getAggregations().get("byTags"); assertNotNull(byTags); assertTrue(byTags instanceof TermsAggregation); TermsAggregation termsbyTags = (TermsAggregation) byTags; assertEquals(3, termsbyTags.getBuckets().size()); } @Test public void testFindTermsNoAggs() { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName("find_message.twig") .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(4, response.getHits().size()); assertNull(response.getAggregations()); } @Test public void testFindTermsNoHits() { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName("find_message_aggs_no_hits.twig") .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(0, response.getHits().size()); Aggregation byTags = response.getAggregations().get("byTags"); assertNotNull(byTags); assertTrue(byTags instanceof TermsAggregation); TermsAggregation termsbyTags = (TermsAggregation) byTags; assertEquals(3, termsbyTags.getBuckets().size()); } @Test public void testCalculateAvgYearPerTag() { doCalculateYearPerTag((key, value) -> { switch (key) { case "news": assertEquals((1970D + 1985D) / 2D, value, 0.01); break; case "blog": assertEquals(1980D, value, 0.01); break; case "twitter": assertEquals(2000D, value, 0.01); break; } }, "terms_calculated_avg.twig"); } @Test public void testCalculateSumPerYearTag() { doCalculateYearPerTag((key, value) -> { switch (key) { case "news": assertEquals(1970D + 1985D, value, 0.01); break; case "blog": assertEquals(1980D, value, 0.01); break; case "twitter": assertEquals(2000D, value, 0.01); break; } }, "terms_calculated_sum.twig"); } @Test public void testCalculateMinPerYearTag() { doCalculateYearPerTag((key, value) -> { switch (key) { case "news": assertEquals(1970D, value, 0.01); break; case "blog": assertEquals(1980D, value, 0.01); break; case "twitter": assertEquals(2000D, value, 0.01); break; } }, "terms_calculated_min.twig"); } @Test public void testCalculateMaxPerYearTag() { doCalculateYearPerTag((key, value) -> { switch (key) { case "news": assertEquals(1985D, value, 0.01); break; case "blog": assertEquals(1980D, value, 0.01); break; case "twitter": assertEquals(2000D, value, 0.01); break; } }, "terms_calculated_max.twig"); } @Test public void testCalculateCardinalityPerYearTag() { doCalculateYearPerTag((key, value) -> { switch (key) { case "news": assertEquals(2, value, 0.01); break; case "blog": assertEquals(1, value, 0.01); break; case "twitter": assertEquals(1, value, 0.01); break; } }, "terms_calculated_cardinality.twig"); } @Test public void testCalculateValueCountPerYearTag() { doCalculateYearPerTag((key, value) -> { switch (key) { case "news": assertEquals(2, value, 0.01); break; case "blog": assertEquals(1, value, 0.01); break; case "twitter": assertEquals(1, value, 0.01); break; } }, "terms_calculated_value_count.twig"); } private void doCalculateYearPerTag(CheckCalculaterAggregation check, String template) { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName(template) .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(0, response.getHits().size()); Aggregation termsCalcTerms = response.getAggregations().get("calcTerms"); assertNotNull(termsCalcTerms); assertTrue(termsCalcTerms instanceof TermsAggregation); TermsAggregation terms = (TermsAggregation) termsCalcTerms; terms.getBuckets().forEach(bucket -> { Aggregation aggregation = bucket.getAggregations().get("calculated"); assertTrue(aggregation instanceof SingleValueMetricsAggregation); SingleValueMetricsAggregation singleValueMetricsAggregation = (SingleValueMetricsAggregation) aggregation; Double value = singleValueMetricsAggregation.getValue(); String key = bucket.getKey(); check.check(key, value); }); } @Test public void testFindHistogramByYear() { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName("find_message_range_aggs_no_hits.twig") .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(0, response.getHits().size()); Aggregation byHistogram = response.getAggregations().get("byHistogram"); assertNotNull(byHistogram); assertTrue(byHistogram instanceof HistogramAggregation); HistogramAggregation histoAggs = (HistogramAggregation) byHistogram; assertEquals(4, histoAggs.getBuckets().size()); checkHistoBucket(histoAggs.getBuckets().get(0), 1, "1970.0"); checkHistoBucket(histoAggs.getBuckets().get(1), 2, "1980.0"); checkHistoBucket(histoAggs.getBuckets().get(2), 0, "1990.0"); checkHistoBucket(histoAggs.getBuckets().get(3), 1, "2000.0"); } @Test public void testFindDateHistogramByDay() { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName("find_message_date_histo_no_hits.twig") .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(0, response.getHits().size()); assertEquals(4, response.getTotalHits()); Aggregation byDateHistogram = response.getAggregations().get("byDateHistogram"); assertNotNull(byDateHistogram); assertTrue(byDateHistogram instanceof DateHistogramAggregation); DateHistogramAggregation histoAggs = (DateHistogramAggregation) byDateHistogram; assertEquals(5, histoAggs.getBuckets().size()); checkDateHistoBucket(histoAggs.getBuckets().get(0), 1, expectedMilis(NUM_DAYS_TO_SUBTRACT_5), expectKeyAsString(NUM_DAYS_TO_SUBTRACT_5)); checkDateHistoBucket(histoAggs.getBuckets().get(1), 0, expectedMilis(4), expectKeyAsString(4)); checkDateHistoBucket(histoAggs.getBuckets().get(2), 0, expectedMilis(3), expectKeyAsString(3)); checkDateHistoBucket(histoAggs.getBuckets().get(3), 1, expectedMilis(NUM_DAYS_TO_SUBTRACT_2), expectKeyAsString(NUM_DAYS_TO_SUBTRACT_2)); checkDateHistoBucket(histoAggs.getBuckets().get(4), 2, expectedMilis(NUM_DAYS_TO_SUBTRACT_1), expectKeyAsString(NUM_DAYS_TO_SUBTRACT_1)); } @Test public void testFindDateHistogramByDayNestedTerms() { SearchByTemplateRequest request = SearchByTemplateRequest.create() .setIndexName(TEST_AGGS) .setTemplateName("date_histo_nested_terms.twig") .setAddId(true) .setTypeReference(new MessageEntityTypeReference()); HitsAggsResponse<MessageEntity> response = searchService.aggsByTemplate(request); assertEquals(0, response.getHits().size()); assertEquals(4, response.getTotalHits()); Aggregation byDateHistogram = response.getAggregations().get("byDateHistogram"); assertNotNull(byDateHistogram); assertTrue(byDateHistogram instanceof DateHistogramAggregation); DateHistogramAggregation histoAggs = (DateHistogramAggregation) byDateHistogram; assertEquals(5, histoAggs.getBuckets().size()); DateHistogramBucket bucket = histoAggs.getBuckets().get(4); checkDateHistoBucket(bucket, 2, expectedMilis(NUM_DAYS_TO_SUBTRACT_1), expectKeyAsString(NUM_DAYS_TO_SUBTRACT_1)); Aggregation byTags = bucket.getAggregations().get("byTags"); assertTrue(byTags instanceof TermsAggregation); TermsAggregation byTagsTerms = (TermsAggregation) byTags; assertEquals(2, byTagsTerms.getBuckets().size()); byTagsTerms.getBuckets().forEach(termsBucket -> { switch (termsBucket.getKey()) { case "news": case "blog": break; default: fail("tag should have been news or blog"); } }); } private void checkHistoBucket(HistogramBucket bucket, long expectedCount, String expectedKey) { assertEquals(expectedCount, bucket.getDocCount().longValue()); assertEquals(expectedKey, bucket.getKey()); } private void checkDateHistoBucket(DateHistogramBucket bucket, long expectedCount, long expectedKey, String expectedKeyAsString) { assertEquals(expectedCount, bucket.getDocCount().longValue()); assertEquals(expectedKey, bucket.getKey().longValue()); assertEquals(expectedKeyAsString, bucket.getKeyAsString()); } private Date createDate(int numDaysToSubtract) { LocalDateTime date = LocalDateTime.now(); LocalDateTime newDate = date.minusDays(numDaysToSubtract); return Date.from(newDate.atZone(ZoneId.systemDefault()).toInstant()); } private long expectedMilis(int numDaysToSubtract) { LocalDate date = LocalDate.now(); LocalDateTime newDate = LocalDateTime.from(date.minusDays(numDaysToSubtract).atStartOfDay(ZONE_ID)); return Date.from(newDate.atZone(ZoneId.systemDefault()).toInstant()).getTime(); } private String expectKeyAsString(int numDaysToSubtract) { LocalDate date = LocalDate.now(); LocalDate newDate = date.minusDays(numDaysToSubtract); DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; return newDate.format(formatter); } private interface CheckCalculaterAggregation { void check(String key, Double value); } }