From 8c5f68aaed010f7eee089c6502879b6234474837 Mon Sep 17 00:00:00 2001 From: jdotcms Date: Tue, 17 Sep 2024 15:08:18 -0600 Subject: [PATCH] #29865 adding the filters parser --- .../analytics/query/AnalyticsQueryParser.java | 29 ++++++++++ .../dotcms/analytics/query/OrderParser.java | 53 +++++++++++++++++++ .../analytics/query/TimeDimensionParser.java | 52 ++++++++++++++++++ .../java/com/dotcms/cube/CubeJSQuery.java | 24 ++++++++- .../query/AnalyticsQueryParserTest.java | 49 +++++++++++++++++ 5 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 dotCMS/src/main/java/com/dotcms/analytics/query/OrderParser.java create mode 100644 dotCMS/src/main/java/com/dotcms/analytics/query/TimeDimensionParser.java diff --git a/dotCMS/src/main/java/com/dotcms/analytics/query/AnalyticsQueryParser.java b/dotCMS/src/main/java/com/dotcms/analytics/query/AnalyticsQueryParser.java index 21f854dc0e02..92424412e8c8 100644 --- a/dotCMS/src/main/java/com/dotcms/analytics/query/AnalyticsQueryParser.java +++ b/dotCMS/src/main/java/com/dotcms/analytics/query/AnalyticsQueryParser.java @@ -14,6 +14,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class AnalyticsQueryParser { @@ -58,10 +60,37 @@ public CubeJSQuery parseQueryToCubeQuery(final AnalyticsQuery query) { builder.filters(parseFilters(query.getFilters())); } + builder.limit(query.getLimit()).offset(query.getOffset()); + + if (UtilMethods.isSet(query.getOrders())) { + builder.orders(parseOrders(query.getOrders())); + } + + if (UtilMethods.isSet(query.getTimeDimensions())) { + builder.timeDimensions(parseTimeDimensions(query.getTimeDimensions())); + } return builder.build(); } + private Collection parseTimeDimensions(final String timeDimensions) { + final TimeDimensionParser.TimeDimension parsedTimeDimension = TimeDimensionParser.parseTimeDimension(timeDimensions); + return Stream.of( + new CubeJSQuery.TimeDimension(parsedTimeDimension.getTerm(), + parsedTimeDimension.getField()) + ).collect(Collectors.toList()); + } + + private Collection parseOrders(final String orders) { + + final OrderParser.ParsedOrder parsedOrder = OrderParser.parseOrder(orders); + return Stream.of( + new CubeJSQuery.OrderItem(parsedOrder.getTerm(), + "ASC".equalsIgnoreCase(parsedOrder.getOrder())? + Filter.Order.ASC:Filter.Order.DESC) + ).collect(Collectors.toList()); + } + private Collection parseFilters(final String filters) { final Tuple2,List> result = FilterParser.parseFilterExpression(filters); diff --git a/dotCMS/src/main/java/com/dotcms/analytics/query/OrderParser.java b/dotCMS/src/main/java/com/dotcms/analytics/query/OrderParser.java new file mode 100644 index 000000000000..55409647508f --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/analytics/query/OrderParser.java @@ -0,0 +1,53 @@ +package com.dotcms.analytics.query; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Order parser + * @author jsanca + */ +public class OrderParser { + + // Expression for order + private static final String ORDER_REGEX = "(\\w+\\.\\w+)\\s+(ASC|DESC)"; + + public static class ParsedOrder { + private String term; + private String order; + + public ParsedOrder(final String term, final String order) { + this.term = term; + this.order = order; + } + + public String getTerm() { + return term; + } + + public String getOrder() { + return order; + } + + @Override + public String toString() { + return "Term: " + term + ", Order: " + order; + } + } + + public static ParsedOrder parseOrder(final String expression) throws IllegalArgumentException { + + // this should be cached and checked + final Pattern pattern = Pattern.compile(ORDER_REGEX, Pattern.CASE_INSENSITIVE); + final Matcher matcher = pattern.matcher(expression.trim()); + + if (matcher.matches()) { + String term = matcher.group(1); // Ex: Events.day + String order = matcher.group(2).toUpperCase(); // Ex: ASC o DESC + + return new ParsedOrder(term, order); + } else { + throw new IllegalArgumentException("The expression is not valid. The format should be 'Term ASC' or 'Term DESC'."); + } + } +} diff --git a/dotCMS/src/main/java/com/dotcms/analytics/query/TimeDimensionParser.java b/dotCMS/src/main/java/com/dotcms/analytics/query/TimeDimensionParser.java new file mode 100644 index 000000000000..da9e9451ca13 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/analytics/query/TimeDimensionParser.java @@ -0,0 +1,52 @@ +package com.dotcms.analytics.query; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Time Dimension Parser + * @author jsanca + */ +public class TimeDimensionParser { + + private static final String FIELD_REGEX = "(\\w+\\.\\w+)\\s+(\\w+)"; + + public static class TimeDimension { + private String term; + private String field; + + public TimeDimension(final String term, final String field) { + this.term = term; + this.field = field; + } + + public String getTerm() { + return term; + } + + public String getField() { + return field; + } + + @Override + public String toString() { + return "Term: " + term + ", Field: " + field; + } + } + + public static TimeDimension parseTimeDimension(final String expression) throws IllegalArgumentException { + // cache and checked + final Pattern pattern = Pattern.compile(FIELD_REGEX); + final Matcher matcher = pattern.matcher(expression.trim()); + + if (matcher.matches()) { + + final String term = matcher.group(1); // Ex: Events.day + final String field = matcher.group(2); // Ex: day + + return new TimeDimension(term, field); + } else { + throw new IllegalArgumentException("The expression is not valid. This should be the format 'Term Field'."); + } + } +} diff --git a/dotCMS/src/main/java/com/dotcms/cube/CubeJSQuery.java b/dotCMS/src/main/java/com/dotcms/cube/CubeJSQuery.java index e6f4aa5a7234..882eba39695a 100644 --- a/dotCMS/src/main/java/com/dotcms/cube/CubeJSQuery.java +++ b/dotCMS/src/main/java/com/dotcms/cube/CubeJSQuery.java @@ -169,6 +169,26 @@ public OrderItem[] orders() { return orders; } + public String[] dimensions() { + return dimensions; + } + + public String[] measures() { + return measures; + } + + public long limit() { + return limit; + } + + public long offset() { + return offset; + } + + public TimeDimension[] timeDimensions() { + return timeDimensions; + } + public CubeJSQuery.Builder builder() { final Builder builder = new Builder() .dimensions(dimensions) @@ -370,7 +390,7 @@ public Builder timeDimensions(Collection timeDimensions) { } } - static class TimeDimension { + public static class TimeDimension { String dimension; String granularity; @@ -388,7 +408,7 @@ public String getGranularity() { } } - static class OrderItem { + public static class OrderItem { private String orderBy; private Order order; diff --git a/dotCMS/src/test/java/com/dotcms/analytics/query/AnalyticsQueryParserTest.java b/dotCMS/src/test/java/com/dotcms/analytics/query/AnalyticsQueryParserTest.java index 290b7e2fb27b..4d464076bd8e 100644 --- a/dotCMS/src/test/java/com/dotcms/analytics/query/AnalyticsQueryParserTest.java +++ b/dotCMS/src/test/java/com/dotcms/analytics/query/AnalyticsQueryParserTest.java @@ -5,6 +5,10 @@ import org.junit.Assert; import org.junit.Test; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + /** * Unit test for {@link AnalyticsQueryParser} * @author jsanca @@ -63,6 +67,51 @@ public void test_simple_query() throws Exception { final Filter[] filters = cubeJSQuery.filters(); Assert.assertNotNull("filters can not be null", filters); + Assert.assertEquals("The filters should have 1 element", 1, filters.length); + Assert.assertTrue("First filter element type should be an OR", filters[0].asMap().containsKey("or")); + final List> filterValues = (List>) filters[0].asMap().get("or"); + Assert.assertNotNull("Filter values can not be null", filterValues); + Assert.assertEquals("Filter values should have 2 elements", 2, filterValues.size()); + + Assert.assertEquals("On the first filter element, member should be Events.variant", "Events.variant", filterValues.get(0).get("member")); + Assert.assertEquals("On the first filter element, operator should be equals", "equals", filterValues.get(0).get("operator")); + Assert.assertEquals("On the first filter element, values should be B", "B", ((Object[])filterValues.get(0).get("values"))[0]); + + Assert.assertEquals("On the second filter element, member should be Events.experiments", "Events.experiments", filterValues.get(1).get("member")); + Assert.assertEquals("On the second filter element, operator should be equals", "equals", filterValues.get(1).get("operator")); + Assert.assertEquals("On the second filter element, values should be B", "B", ((Object[])filterValues.get(1).get("values"))[0]); + + final CubeJSQuery.OrderItem [] orderItems = cubeJSQuery.orders(); + Assert.assertNotNull("orders can not be null", orderItems); + Assert.assertEquals("The orders should have 1 element", 1, orderItems.length); + Assert.assertEquals("The order member should be Events.day", "Events.day", orderItems[0].getOrderBy()); + Assert.assertEquals("The order direction should be ASC", "ASC", orderItems[0].getOrder().name()); + + Assert.assertEquals("Limit should be 100", 100, cubeJSQuery.limit()); + Assert.assertEquals("Offset should be 1", 1, cubeJSQuery.offset()); + + final CubeJSQuery.TimeDimension[] timeDimensions = cubeJSQuery.timeDimensions(); + Assert.assertNotNull("timeDimensions can not be null", timeDimensions); + Assert.assertEquals("The timeDimensions should have 1 element", 1, timeDimensions.length); + Assert.assertEquals("The timeDimensions first element, dimension should be Events.day", "Events.day", timeDimensions[0].getDimension()); + Assert.assertEquals("The timeDimensions first element, granularity should be Events.day", "day", timeDimensions[0].getGranularity()); + + final String [] dimensions = cubeJSQuery.dimensions(); + Assert.assertNotNull("dimensions can not be null", dimensions); + Assert.assertEquals("The dimensions should have 7 elements", 7, dimensions.length); + + Assert.assertTrue("Dimensions should have Events.experiment:", Arrays.asList(dimensions).contains("Events.experiment")); + Assert.assertTrue("Dimensions should have Events.referer:", Arrays.asList(dimensions).contains("Events.referer")); + Assert.assertTrue("Dimensions should have Events.variant:", Arrays.asList(dimensions).contains("Events.variant")); + + final String [] measures = cubeJSQuery.measures(); + + Assert.assertNotNull("dimensions can not be null", measures); + Assert.assertEquals("The measures should have 2 elements", 2, measures.length); + + Assert.assertTrue("Measures should have Events.count:", Arrays.asList(measures).contains("Events.count")); + Assert.assertTrue("Measures should have Events.uniqueCount:", Arrays.asList(measures).contains("Events.uniqueCount")); + } }