Skip to content

Commit

Permalink
Merge branch 'main' into issue-30191-Support-filters-arrays-multi-val…
Browse files Browse the repository at this point in the history
…ues-in-the-CA-Query
  • Loading branch information
jcastro-dotcms authored Oct 2, 2024
2 parents 74c1f71 + 1eae248 commit 2f7a929
Show file tree
Hide file tree
Showing 17 changed files with 350 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ public interface ContentAnalyticsAPI {
*/
ReportResponse runRawReport(CubeJSQuery cubeJSQuery, User user);

/**
* Runs a raw report based on a cubeJS json string query
* @param cubeJsQueryJson
* @param user
* @return ReportResponse
*/
ReportResponse runRawReport(String cubeJsQueryJson, User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,11 @@ public ReportResponse runRawReport(final CubeJSQuery cubeJSQuery, final User use
return this.contentAnalyticsFactory.getRawReport(cubeJSQuery, user);
}

@Override
public ReportResponse runRawReport(final String cubeJsQueryJson, final User user) {

Logger.debug(this, ()-> "Running the report for the raw json query: " + cubeJsQueryJson);
// note: should check any permissions for an user.
return this.contentAnalyticsFactory.getRawReport(cubeJsQueryJson, user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,12 @@ public interface ContentAnalyticsFactory {
*/
ReportResponse getRawReport(final CubeJSQuery query, final User user);

/**
* Runs the raw report based on the cube js json string query and user.
*
* @param cubeJsQueryJson the query to run the report.
* @param user the user to run the report.
* @return the report response.
*/
ReportResponse getRawReport(String cubeJsQueryJson, User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,21 @@ public ReportResponse getRawReport(final CubeJSQuery cubeJSQuery, final User use
}
}

@Override
public ReportResponse getRawReport(final String cubeJsQueryJson, final User user) {

try {

Logger.debug(this, ()-> "Getting the report for the raw query: " + cubeJsQueryJson);
final CubeJSClient cubeClient = cubeJSClientFactory.create(user);
return toReportResponse(cubeClient.send(cubeJsQueryJson));
} catch (DotDataException| DotSecurityException e) {

Logger.error(this, e.getMessage(), e);
throw new DotRuntimeException(e);
}
}

private ReportResponse toReportResponse(final CubeJSResultSet cubeJSResultSet) {

return new ReportResponse(StreamSupport.stream(cubeJSResultSet.spliterator(), false).collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.dotcms.api.system.event.SystemEventType;
import com.dotcms.api.system.event.message.MessageSeverity;
import com.dotcms.api.system.event.message.SystemMessageEventUtil;
import com.dotcms.api.system.event.message.builder.SystemMessage;
import com.dotcms.api.system.event.message.builder.SystemMessageBuilder;
import com.dotcms.exception.AnalyticsException;
import com.dotcms.security.apps.AbstractProperty;
Expand All @@ -22,9 +23,11 @@
import com.dotmarketing.util.DateUtil;
import com.dotmarketing.util.Logger;
import com.liferay.portal.language.LanguageUtil;
import com.liferay.portal.model.User;
import io.vavr.control.Try;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.Objects;
import java.util.Optional;

Expand Down Expand Up @@ -99,6 +102,7 @@ public void notify(final AppSecretSavedEvent event) {
Logger.error(
this,
String.format("Cannot process event for app update due to: %s", e.getMessage()), e);
notifyErrorToTheUser(e, event);
}
});
});
Expand All @@ -112,6 +116,17 @@ public void notify(final AppSecretSavedEvent event) {
}
}

private void notifyErrorToTheUser(final AnalyticsException e, final AppSecretSavedEvent event) {

final SystemMessage systemMessage = new SystemMessageBuilder()
.setMessage(String.format("Cannot do the app update due to: %s", e.getMessage()))
.setSeverity(MessageSeverity.ERROR)
.setLife(DateUtil.TEN_SECOND_MILLIS)
.create();

SystemMessageEventUtil.getInstance().pushMessage(systemMessage, Collections.singletonList(event.getUserId()));
}

/**
* Pushes message to notify that even though analytics app env-var properties were changed though the Analytics
* App, those changes were ignored since they are govern by env-vars.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private void fireCollectorsAndEmitEvent(final HttpServletRequest request,
.collect(java.util.stream.Collectors.toList());
}));
} catch (Exception e) {
Logger.debug(WebEventsCollectorServiceFactory.class, () -> "Error saving Analitycs Events:" + e.getMessage());
Logger.debug(WebEventsCollectorServiceFactory.class, () -> "Error saving Analytics Events:" + e.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public class SystemTableInitializer implements DotInitializer {

@Override
public void init() {
Config.initSystemTableConfigSource();
if (!Config.isSystemTableConfigSourceInit()) {
Config.initSystemTableConfigSource();
}
// Load the all system table into the system cache
APILocator.getSystemAPI().getSystemTable().all();
}
Expand Down
34 changes: 24 additions & 10 deletions dotCMS/src/main/java/com/dotcms/cube/CubeJSClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,27 @@ public CubeJSResultSet send(final CubeJSQuery query) {
DotPreconditions.notNull(query, "Query not must be NULL");
DotPreconditions.notNull(accessToken, "Access token not must be NULL");

final String queryAsString = query.toString();
return send(queryAsString);
}

public CubeJSResultSet send(final String queryAsString) {

DotPreconditions.notNull(queryAsString, "Query not must be NULL");
DotPreconditions.notNull(accessToken, "Access token not must be NULL");

final CircuitBreakerUrl cubeJSClient;
final String cubeJsUrl = String.format("%s/cubejs-api/v1/load", url);
final String queryAsString = query.toString();

try {
cubeJSClient = CircuitBreakerUrl.builder()
.setMethod(Method.GET)
.setHeaders(cubeJsHeaders(accessToken))
.setUrl(cubeJsUrl)
.setParams(new HashMap<>(Map.of("query", queryAsString)))
.setTimeout(4000)
.setThrowWhenNot2xx(false)
.build();
.setMethod(Method.GET)
.setHeaders(cubeJsHeaders(accessToken))
.setUrl(cubeJsUrl)
.setParams(new HashMap<>(Map.of("query", queryAsString)))
.setTimeout(4000)
.setThrowWhenNot2xx(false)
.build();
} catch (AnalyticsException e) {
throw new RuntimeException(e);
}
Expand All @@ -101,7 +110,7 @@ public CubeJSResultSet send(final CubeJSQuery query) {
try {
final String responseAsString = response.getResponse();
final Map<String, Object> responseAsMap = UtilMethods.isSet(responseAsString) && !responseAsString.equals("[]") ?
JsonUtil.getJsonFromString(responseAsString) : new HashMap<>();
JsonUtil.getJsonFromString(responseAsString) : new HashMap<>();
final List<Map<String, Object>> data = (List<Map<String, Object>>) responseAsMap.get("data");

return new CubeJSResultSetImpl(UtilMethods.isSet(data) ? data : Collections.emptyList());
Expand All @@ -113,14 +122,18 @@ public CubeJSResultSet send(final CubeJSQuery query) {
private Response<String> getStringResponse(final CircuitBreakerUrl cubeJSClient,
final String cubeJsUrl,
final String queryAsString) {
final TimeMetric timeMetric = TimeMetric.mark(getClass().getSimpleName());
final TimeMetric timeMetric = TimeMetric.mark(getClass().getSimpleName());

Logger.debug(this, String.format("Getting results from CubeJs [%s] with query [%s]", cubeJsUrl, queryAsString));
final Response<String> response = cubeJSClient.doResponse();

timeMetric.stop();

if (!CircuitBreakerUrl.isWithin2xx(response.getStatusCode())) {

if (400 == response.getStatusCode()) {
throw new IllegalArgumentException(response.getResponse());
}
throw new RuntimeException("CubeJS Server is not available");
}

Expand All @@ -139,4 +152,5 @@ private Map<String, String> cubeJsHeaders(final AccessToken accessToken) throws
.build();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.dotcms.analytics.content.ContentAnalyticsAPI;
import com.dotcms.analytics.content.ReportResponse;
import com.dotcms.analytics.model.ResultSetItem;
import com.dotcms.cdi.CDIUtils;
import com.dotcms.rest.InitDataObject;
import com.dotcms.rest.WebResource;
Expand All @@ -26,6 +27,8 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.stream.Collectors;

/**
* Resource class that exposes endpoints to query content analytics data.
Expand Down Expand Up @@ -126,7 +129,64 @@ public ReportResponseEntityView query(@Context final HttpServletRequest request,
Logger.debug(this, () -> "Querying content analytics data with the form: " + queryForm);
final ReportResponse reportResponse =
this.contentAnalyticsAPI.runReport(queryForm.getQuery(), user);
return new ReportResponseEntityView(reportResponse);
return new ReportResponseEntityView(reportResponse.getResults().stream().map(ResultSetItem::getAll).collect(Collectors.toList()));
}

/**
* Query Content Analytics data.
*
* @param request the HTTP request.
* @param response the HTTP response.
* @param queryForm the query form.
* @return the report response entity view.
*/
@Operation(
operationId = "postContentAnalyticsQuery",
summary = "Retrieve Content Analytics data",
description = "Returns information of specific dotCMS objects whose health and engagement data is tracked.",
tags = {"Content Analytics"},
responses = {
@ApiResponse(responseCode = "200", description = "Content Analytics data being queried",
content = @Content(mediaType = "application/json",
examples = {
@ExampleObject(
value = "{\n" +
" \"dimensions\": [\n" +
" \"Events.experiment\",\n" +
" \"Events.variant\"\n" +
" ]\n" +
"}"
)
}
)
),
@ApiResponse(responseCode = "400", description = "Bad Request"),
@ApiResponse(responseCode = "403", description = "Forbidden"),
@ApiResponse(responseCode = "500", description = "Internal Server Error")
}
)
@POST
@Path("/_query/cube")
@JSONP
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
public ReportResponseEntityView queryCubeJs(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
final String cubeJsQueryJson) {

final InitDataObject initDataObject = new WebResource.InitBuilder(this.webResource)
.requestAndResponse(request, response)
.requiredBackendUser(true)
.rejectWhenNoUser(true)
.init();

final User user = initDataObject.getUser();
DotPreconditions.checkNotNull(cubeJsQueryJson, IllegalArgumentException.class, "The 'query' JSON data cannot be null");
Logger.debug(this, ()->"Querying content analytics data with the cube query json: " + cubeJsQueryJson);
final ReportResponse reportResponse =
this.contentAnalyticsAPI.runRawReport(cubeJsQueryJson, user);
return new ReportResponseEntityView(reportResponse.getResults().stream().map(ResultSetItem::getAll).collect(Collectors.toList()));
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package com.dotcms.rest.api.v1.analytics.content;

import com.dotcms.analytics.content.ReportResponse;
import com.dotcms.rest.ResponseEntityView;

import java.util.List;
import java.util.Map;

/**
*
* View for the analytics report response entity.
*
* @author Jose Castro
* @since Sep 13th, 2024
*/
public class ReportResponseEntityView extends ResponseEntityView<ReportResponse> {
public class ReportResponseEntityView extends ResponseEntityView<List<Map<String, Object>>> {

public ReportResponseEntityView(final ReportResponse entity) {
public ReportResponseEntityView(final List<Map<String, Object>> entity) {
super(entity);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import com.dotcms.jitsu.EventLogWebInterceptor;
import com.dotcms.prerender.PreRenderSEOWebInterceptor;
import com.dotcms.security.multipart.MultiPartRequestSecurityWebInterceptor;
import com.dotcms.system.event.local.business.LocalSystemEventsAPI;
import com.dotcms.variant.business.web.CurrentVariantWebInterceptor;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.util.Config;

import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
Expand All @@ -27,6 +27,9 @@ public class InterceptorFilter extends AbstractWebInterceptorSupportFilter {
@Override
public void init(final FilterConfig config) throws ServletException {

if (!Config.isSystemTableConfigSourceInit()) {
Config.initSystemTableConfigSource();
}
this.addInterceptors(config);
super.init(config);
} // init.
Expand Down
4 changes: 4 additions & 0 deletions dotCMS/src/main/java/com/dotmarketing/util/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ public class Config {
@VisibleForTesting
public static boolean enableSystemTableConfigSource = "true".equalsIgnoreCase(EnvironmentVariablesService.getInstance().getenv().getOrDefault("DOT_ENABLE_SYSTEM_TABLE_CONFIG_SOURCE", "true"));

public static boolean isSystemTableConfigSourceInit() {
return null != systemTableConfigSource;
}

public static void initSystemTableConfigSource() {
systemTableConfigSource = new SystemTableConfigSource();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14752,7 +14752,7 @@ var VScrollBar = require("./scrollbar").VScrollBar;
var RenderLoop = require("./renderloop").RenderLoop;
var FontMetrics = require("./layer/font_metrics").FontMetrics;
var EventEmitter = require("./lib/event_emitter").EventEmitter;
var editorCss = ".ace_editor {\
var editorCss = ".ace_editor, .ace_editor * {\
position: relative;\
overflow: hidden;\
font: 12px/normal 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;\
Expand Down
Loading

0 comments on commit 2f7a929

Please sign in to comment.