-
Notifications
You must be signed in to change notification settings - Fork 460
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Creating Util classes to build a CubeJS Query and sent a Request to a… (
#23895) * Creating Util classes to build a CubeJS Query and sent a Request to a CubeJs Server * Feedback
- Loading branch information
1 parent
a9bfca3
commit aa04d26
Showing
11 changed files
with
1,661 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package com.dotcms.cube; | ||
|
||
import static com.dotcms.util.CollectionsUtils.map; | ||
|
||
import com.dotcms.http.CircuitBreakerUrl; | ||
import com.dotcms.http.CircuitBreakerUrl.Method; | ||
import com.dotcms.http.CircuitBreakerUrl.Response; | ||
import com.dotcms.jitsu.EventLogRunnable; | ||
import com.dotcms.util.DotPreconditions; | ||
import com.dotcms.util.JsonUtil; | ||
import com.dotmarketing.util.Logger; | ||
import com.dotmarketing.util.UtilMethods; | ||
import com.liferay.util.StringPool; | ||
import io.vavr.control.Try; | ||
import java.io.IOException; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/** | ||
* CubeJS Client it allow to send a Request to a Cube JS Server. | ||
* Example: | ||
* | ||
* <code> | ||
* | ||
* final String cubeServerIp = "127.0.0.1"; | ||
* final int cubeJsServerPort = 5000; | ||
* | ||
* final CubeJSQuery cubeJSQuery = new Builder() | ||
* .dimensions("Events.experiment", "Events.variant") | ||
* .build(); | ||
* | ||
* final CubeClient cubeClient = new CubeClient(String.format("http://%s:%s", cubeServerIp, cubeJsServerPort)); | ||
* final CubeJSResultSet cubeJSResultSet = cubeClient.send(cubeJSQuery); | ||
* </code> | ||
*/ | ||
public class CubeJSClient { | ||
private String url; | ||
|
||
public CubeJSClient(final String url) { | ||
this.url = url; | ||
} | ||
|
||
/** | ||
* Send a request to a CubeJS Server. | ||
* | ||
* Example: | ||
* | ||
* <code> | ||
* | ||
* final String cubeServerIp = "127.0.0.1"; | ||
* final int cubeJsServerPort = 5000; | ||
* | ||
* final CubeJSQuery cubeJSQuery = new Builder() | ||
* .dimensions("Events.experiment", "Events.variant", "Events.utcTime") | ||
* .build(); | ||
* | ||
* final CubeClient cubeClient = new CubeClient(String.format("http://%s:%s", cubeServerIp, cubeJsServerPort)); | ||
* final CubeJSResultSet cubeJSResultSet = cubeClient.send(cubeJSQuery); | ||
* | ||
* for (ResultSetItem resultSetItem : cubeJSResultSet) { | ||
* System.out.println("Events.experiment", resultSetItem.get("Events.experiment").get()) | ||
* System.out.println("Events.variant", resultSetItem.get("Events.variant").get()) | ||
* System.out.println("Events.utcTime", resultSetItem.get("Events.utcTime").get()) | ||
* } | ||
* </code> | ||
* | ||
* @param query Query to be run in the CubeJS Server | ||
* @return | ||
*/ | ||
public CubeJSResultSet send(final CubeJSQuery query) { | ||
|
||
DotPreconditions.notNull(query, "Query not must be NULL"); | ||
|
||
final CircuitBreakerUrl cubeJSClient = CircuitBreakerUrl.builder() | ||
.setMethod(Method.GET) | ||
.setUrl(String.format("%s/cubejs-api/v1/load", url)) | ||
.setParams(map("query", query.toString())) | ||
.setTimeout(4000) | ||
.build(); | ||
|
||
final Response<String> response = Try.of(cubeJSClient::doResponse) | ||
.onFailure(e -> Logger.warnAndDebug(EventLogRunnable.class, e.getMessage(), e)) | ||
.getOrElse(CircuitBreakerUrl.EMPTY_RESPONSE); | ||
|
||
try { | ||
final String responseAsString = UtilMethods.isSet(response) ? response.getResponse() : | ||
StringPool.BLANK; | ||
final Map<String, Object> responseAsMap = UtilMethods.isSet(responseAsString) ? | ||
JsonUtil.getJsonFromString(responseAsString) : new HashMap<>(); | ||
final List<Map<String, Object>> data = (List<Map<String, Object>>) responseAsMap.get("data"); | ||
|
||
return new CubeJSResultSet(UtilMethods.isSet(data) ? data : Collections.emptyList()); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
|
||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
package com.dotcms.cube; | ||
|
||
|
||
|
||
import com.dotcms.cube.filters.Filter.Order; | ||
import com.dotcms.cube.filters.LogicalFilter; | ||
import com.dotcms.cube.filters.SimpleFilter; | ||
import com.dotcms.cube.filters.SimpleFilter.Operator; | ||
import com.dotcms.cube.filters.Filter; | ||
import com.dotcms.util.JsonUtil; | ||
import com.dotmarketing.util.UtilMethods; | ||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* Represents a Cube JS Query | ||
* You can use the {@link Builder} to create a CubeJSQuery and later using the | ||
* {@link CubeJSQuery#toString()}. | ||
* | ||
* Examples: | ||
* | ||
* <code> | ||
* final CubeJSQuery cubeJSQuery = new Builder() | ||
* .dimensions("Events.experiment") | ||
* .measures("Events.count") | ||
* .filter("Events.variant", SimpleFilter.Operator.EQUALS, "B") | ||
* .build(); | ||
* </code> | ||
* | ||
* To get: | ||
* | ||
* <code> | ||
* { | ||
* "dimensions": [ | ||
* "Events.experiment" | ||
* ], | ||
* { | ||
* "measures": [ | ||
* "Events.count" | ||
* ], | ||
* filters: [ | ||
* { | ||
* member: "Events.variant", | ||
* operator: "equals", | ||
* values: ["B"] | ||
* } | ||
* ] | ||
* } | ||
* </code> | ||
* | ||
* @see <a href="https://cube.dev/docs/query-format">CubeJS Query format</a> | ||
*/ | ||
public class CubeJSQuery { | ||
|
||
private String[] dimensions; | ||
private String[] measures; | ||
private Filter[] filters; | ||
|
||
private OrderItem[] orders; | ||
|
||
private CubeJSQuery(final String[] dimensions, | ||
final String[] measures, | ||
final Filter[] filters, | ||
final OrderItem[] orderItems) { | ||
|
||
this.dimensions = dimensions; | ||
this.measures = measures; | ||
this.filters = filters; | ||
this.orders = orderItems; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
try { | ||
return JsonUtil.getJsonAsString(getMap()); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private Map<String, Object> getMap() { | ||
final Map<String, Object> map = new HashMap<>(); | ||
|
||
if (UtilMethods.isSet(dimensions)) { | ||
map.put("dimensions", dimensions); | ||
} | ||
|
||
if (UtilMethods.isSet(measures)) { | ||
map.put("measures", measures); | ||
} | ||
|
||
if (filters.length > 0) { | ||
map.put("filters", getFiltersAsMap()); | ||
} | ||
|
||
if (orders.length > 0) { | ||
map.put("order", getOrdersAsMap()); | ||
} | ||
|
||
return map; | ||
} | ||
|
||
private Map<String, String> getOrdersAsMap() { | ||
final Map<String, String> resultMap = new HashMap(); | ||
|
||
for (final OrderItem order : orders) { | ||
resultMap.put(order.orderBy, order.order.name().toLowerCase()); | ||
} | ||
|
||
return resultMap; | ||
} | ||
private List<Map<String, Object>> getFiltersAsMap() { | ||
return Arrays.stream(filters) | ||
.map(filter -> filter.asMap()) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
public static class Builder { | ||
private String[] dimensions; | ||
private String[] measures; | ||
private List<Filter> filters = new ArrayList<>(); | ||
private List<OrderItem> orders = new ArrayList<>(); | ||
|
||
public CubeJSQuery build(){ | ||
if (!UtilMethods.isSet(dimensions) && !UtilMethods.isSet(measures)) { | ||
throw new IllegalStateException("Must set dimensions or measures"); | ||
} | ||
|
||
return new CubeJSQuery(dimensions, measures, | ||
filters.toArray(new Filter[filters.size()]), | ||
orders.toArray(new OrderItem[orders.size()])); | ||
} | ||
|
||
public Builder dimensions(final String... dimensions) { | ||
this.dimensions = dimensions; | ||
return this; | ||
} | ||
|
||
public Builder measures(final String... measures) { | ||
this.measures = measures; | ||
return this; | ||
} | ||
|
||
public Builder filter(final String member, Operator operator, final String... values) { | ||
filters.add(new SimpleFilter(member, operator, values)); | ||
return this; | ||
} | ||
|
||
public Builder filter(final LogicalFilter logicalFilter) { | ||
filters.add(logicalFilter); | ||
return this; | ||
} | ||
|
||
public Builder order(final String orderBy, final Order order) { | ||
orders.add(new OrderItem(orderBy, order)); | ||
return this; | ||
} | ||
} | ||
|
||
private static class OrderItem { | ||
private String orderBy; | ||
private Order order; | ||
|
||
public OrderItem(final String orderBy, final Order order) { | ||
this.orderBy = orderBy; | ||
this.order = order; | ||
} | ||
|
||
public String getOrderBy() { | ||
return orderBy; | ||
} | ||
|
||
public Order getOrder() { | ||
return order; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.dotcms.cube; | ||
|
||
import com.dotcms.cube.CubeJSResultSet.ResultSetItem; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Spliterator; | ||
import java.util.function.Consumer; | ||
import java.util.stream.Collectors; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
/** | ||
* Represent a Result from running a CubeJS Query in a CubeJS Server. | ||
*/ | ||
public class CubeJSResultSet implements Iterable<ResultSetItem> { | ||
private List<ResultSetItem> data; | ||
public CubeJSResultSet(final List<Map<String, Object>> data){ | ||
this.data = data.stream().map(map -> new ResultSetItem(map)).collect(Collectors.toList()); | ||
} | ||
|
||
public int size() { | ||
return data.size(); | ||
} | ||
|
||
@NotNull | ||
@Override | ||
public Iterator<ResultSetItem> iterator() { | ||
return data.iterator(); | ||
} | ||
|
||
@Override | ||
public void forEach(Consumer<? super ResultSetItem> action) { | ||
Iterable.super.forEach(action); | ||
} | ||
|
||
@Override | ||
public Spliterator<ResultSetItem> spliterator() { | ||
return Iterable.super.spliterator(); | ||
} | ||
|
||
public static class ResultSetItem { | ||
|
||
private Map<String, Object> item; | ||
ResultSetItem(final Map<String, Object> item) { | ||
this.item = item; | ||
} | ||
|
||
public Optional<Object> get(final String name){ | ||
return Optional.ofNullable(item.get(name)); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.dotcms.cube.filters; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* Represents a CubeJs Query Filter | ||
* | ||
* @see <a href="https://cube.dev/docs/query-format#filters-formate">Filters format</a> | ||
*/ | ||
public interface Filter { | ||
|
||
static LogicalFilter.Builder and(){ | ||
return null; | ||
} | ||
|
||
Map<String, Object> asMap(); | ||
|
||
public enum Order { | ||
ASC, DESC; | ||
} | ||
} |
Oops, something went wrong.