Skip to content

Commit

Permalink
#29865 adding the filters parser
Browse files Browse the repository at this point in the history
  • Loading branch information
jdotcms committed Sep 17, 2024
1 parent 7c5d3db commit 8c5f68a
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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<CubeJSQuery.TimeDimension> 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<CubeJSQuery.OrderItem> 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<Filter> parseFilters(final String filters) {
final Tuple2<List<FilterParser.Token>,List<FilterParser.LogicalOperator>> result =
FilterParser.parseFilterExpression(filters);
Expand Down
53 changes: 53 additions & 0 deletions dotCMS/src/main/java/com/dotcms/analytics/query/OrderParser.java
Original file line number Diff line number Diff line change
@@ -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'.");
}
}
}
Original file line number Diff line number Diff line change
@@ -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'.");
}
}
}
24 changes: 22 additions & 2 deletions dotCMS/src/main/java/com/dotcms/cube/CubeJSQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -370,7 +390,7 @@ public Builder timeDimensions(Collection<TimeDimension> timeDimensions) {
}
}

static class TimeDimension {
public static class TimeDimension {
String dimension;
String granularity;

Expand All @@ -388,7 +408,7 @@ public String getGranularity() {
}
}

static class OrderItem {
public static class OrderItem {
private String orderBy;
private Order order;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Map<String, Object>> filterValues = (List<Map<String, Object>>) 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"));

}

}

0 comments on commit 8c5f68a

Please sign in to comment.