Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#30369 adding the custom user event #30376

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public boolean test(CollectorContextMap collectorContextMap) {
public CollectorPayloadBean collect(final CollectorContextMap collectorContextMap,
final CollectorPayloadBean collectorPayloadBean) {

// todo: add the user id
final String requestId = (String)collectorContextMap.get("requestId");
final Long time = (Long)collectorContextMap.get("time");
final String clusterId = (String)collectorContextMap.get("cluster");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ public class ConcurrentCollectorPayloadBean implements CollectorPayloadBean {

private final ConcurrentHashMap<String, Serializable> map = new ConcurrentHashMap<>();

public ConcurrentCollectorPayloadBean() {
// empty
}

public ConcurrentCollectorPayloadBean(final Map<String, Serializable> customMap) {
map.putAll(customMap);
}

@Override
public CollectorPayloadBean put(final String key, final Serializable value) {
if (null != value) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.dotcms.analytics.track.collectors;

import com.dotcms.analytics.track.matchers.RequestMatcher;
import com.dotcms.analytics.track.matchers.UserCustomDefinedRequestMatcher;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Map;

/**
* This class is in charge of firing the collectors to populate the event payload. Also do the triggering the event to save the analytics data
Expand All @@ -26,4 +29,15 @@ void fireCollectors (final HttpServletRequest request, final HttpServletResponse
* @param collectorId
*/
void removeCollector(final String collectorId);

/**
* Fire the collectors and emit the event
* @param request
* @param response
* @param requestMatcher
* @param userEventPayload
*/
void fireCollectorsAndEmitEvent(HttpServletRequest request, HttpServletResponse response,
final RequestMatcher requestMatcher, Map<String, Serializable> userEventPayload);

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import com.dotmarketing.filters.Constants;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.PageMode;
import com.dotmarketing.util.UtilMethods;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -78,14 +80,24 @@ public void fireCollectors(final HttpServletRequest request,
}
}

private void fireCollectorsAndEmitEvent(final HttpServletRequest request,
@Override
/**
* Allows to fire the collections and emit the event from a base payload map already built by the user
* @param request
* @param response
* @param requestMatcher
* @param basePayloadMap
*/
public void fireCollectorsAndEmitEvent(final HttpServletRequest request,
final HttpServletResponse response,
final RequestMatcher requestMatcher) {
final RequestMatcher requestMatcher,
final Map<String, Serializable> basePayloadMap) {

final Character character = WebAPILocator.getCharacterWebAPI().getOrCreateCharacter(request, response);
final Host site = WebAPILocator.getHostWebAPI().getCurrentHostNoThrow(request);

final CollectorPayloadBean base = new ConcurrentCollectorPayloadBean();
final CollectorPayloadBean base = UtilMethods.isSet(basePayloadMap)?
new ConcurrentCollectorPayloadBean(basePayloadMap): new ConcurrentCollectorPayloadBean();
final CollectorContextMap syncCollectorContextMap =
new RequestCharacterCollectorContextMap(request, character, requestMatcher);

Expand Down Expand Up @@ -120,6 +132,13 @@ private void fireCollectorsAndEmitEvent(final HttpServletRequest request,
}
}

private void fireCollectorsAndEmitEvent(final HttpServletRequest request,
final HttpServletResponse response,
final RequestMatcher requestMatcher) {

this.fireCollectorsAndEmitEvent(request, response, requestMatcher, Map.of());
}

private static Map<String, Object> getCollectorContextMap(final HttpServletRequest request,
final PageMode pageMode, final Host site) {
final Map<String, Object> contextMap = new HashMap<>(Map.of("uri", request.getRequestURI(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.dotcms.analytics.track.matchers;

/**
* This is just flag class to identify the user custom defined event matcher
* @author jsanca
*/
public final class UserCustomDefinedRequestMatcher implements RequestMatcher {

public static final String USER_CUSTOM_EVENT_MATCHER_ID = "user-custom-event";


@Override
public String getId() {
return USER_CUSTOM_EVENT_MATCHER_ID;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
import com.dotcms.analytics.content.ContentAnalyticsAPI;
import com.dotcms.analytics.content.ReportResponse;
import com.dotcms.analytics.model.ResultSetItem;
import com.dotcms.analytics.track.collectors.WebEventsCollectorServiceFactory;
import com.dotcms.analytics.track.matchers.UserCustomDefinedRequestMatcher;
import com.dotcms.cdi.CDIUtils;
import com.dotcms.rest.InitDataObject;
import com.dotcms.rest.ResponseEntityStringView;
import com.dotcms.rest.WebResource;
import com.dotcms.rest.annotation.NoCache;
import com.dotcms.util.DotPreconditions;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UUIDUtil;
import com.google.common.annotations.VisibleForTesting;
import com.liferay.portal.model.User;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -27,7 +31,10 @@
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
Expand All @@ -44,6 +51,8 @@
description = "Endpoints that exposes information related to how dotCMS content is accessed and interacted with by users.")
public class ContentAnalyticsResource {

private static final UserCustomDefinedRequestMatcher USER_CUSTOM_DEFINED_REQUEST_MATCHER = new UserCustomDefinedRequestMatcher();

private final WebResource webResource;
private final ContentAnalyticsAPI contentAnalyticsAPI;

Expand Down Expand Up @@ -137,7 +146,7 @@ public ReportResponseEntityView query(@Context final HttpServletRequest request,
*
* @param request the HTTP request.
* @param response the HTTP response.
* @param queryForm the query form.
* @param cubeJsQueryJson the query form.
* @return the report response entity view.
*/
@Operation(
Expand Down Expand Up @@ -189,4 +198,55 @@ public ReportResponseEntityView queryCubeJs(@Context final HttpServletRequest re
return new ReportResponseEntityView(reportResponse.getResults().stream().map(ResultSetItem::getAll).collect(Collectors.toList()));
}

/**
* Fire an user custom event.
*
* @param request the HTTP request.
* @param response the HTTP response.
* @param userEventPayload the query form.
* @return the report response entity view.
*/
@Operation(
operationId = "fireUserCustomEvent",
summary = "Fire an user custom event.",
description = "receives a custom event payload and fires the event to the collectors",
tags = {"Content Analytics"},
responses = {
@ApiResponse(responseCode = "200", description = "If the event was created successfully",
content = @Content(mediaType = "application/json",
examples = {
@ExampleObject(
value = "TBD"
)
}
)
),
@ApiResponse(responseCode = "400", description = "Bad Request"),
@ApiResponse(responseCode = "403", description = "Forbidden"),
@ApiResponse(responseCode = "500", description = "Internal Server Error")
}
)
@POST
@Path("/event")
@JSONP
@NoCache
@Consumes(MediaType.APPLICATION_JSON)
@Produces({MediaType.APPLICATION_JSON, "application/javascript"})
public ResponseEntityStringView fireUserCustomEvent(@Context final HttpServletRequest request,
@Context final HttpServletResponse response,
final Map<String, Serializable> userEventPayload) {

new WebResource.InitBuilder(this.webResource)
.requestAndResponse(request, response)
.rejectWhenNoUser(true)
.init();

DotPreconditions.checkNotNull(userEventPayload, IllegalArgumentException.class, "The 'userEventPayload' JSON cannot be null");
DotPreconditions.checkNotNull(userEventPayload.get("event_type"), IllegalArgumentException.class, "The 'event_type' field is required");
Logger.debug(this, ()->"Creating an user custom event with the payload: " + userEventPayload);
request.setAttribute("requestId", UUIDUtil.uuid());
WebEventsCollectorServiceFactory.getInstance().getWebEventsCollectorService().fireCollectorsAndEmitEvent(request, response, USER_CUSTOM_DEFINED_REQUEST_MATCHER, userEventPayload);
return new ResponseEntityStringView("User event created successfully");
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"info": {
"_postman_id": "e4fd950b-b8f4-4213-99a1-eb468e732dd6",
"_postman_id": "e003f644-856a-4639-ac55-dfbcd946bd4e",
"name": "Content Analytics",
"description": "Performs simple data validation for the Content Analytics REST Endpoint. It's very important to notice that, for the time being, the CICD instance does not start up any of the additional third-party tools required to actually run the Content Analytics feature.\n\nThis means that these test do not deal with retrieveing or saving data at all. It verifies that important/required information is present.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "5403727",
"_collection_link": "https://cloudy-robot-285072.postman.co/workspace/JCastro-Workspace~5bfa586e-54db-429b-b7d5-c4ff997e3a0d/collection/5403727-e4fd950b-b8f4-4213-99a1-eb468e732dd6?action=share&source=collection_link&creator=5403727"
"_exporter_id": "781456"
},
"item": [
{
Expand Down Expand Up @@ -93,6 +92,121 @@
"response": []
}
]
},
{
"name": "Events",
"item": [
{
"name": "No Query Form on Event",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"HTTP Status code must be 400\", function () {",
" pm.response.to.have.status(400);",
"});",
""
],
"type": "text/javascript",
"packages": {}
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{jwt}}",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/analytics/content/event",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"analytics",
"content",
"event"
]
}
},
"response": []
},
{
"name": "EventType is must",
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test(\"HTTP Status code must be 400\", function () {",
" pm.response.to.have.status(400);",
"});",
""
],
"type": "text/javascript",
"packages": {}
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{jwt}}",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"test\":\"test\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{serverURL}}/api/v1/analytics/content/event",
"host": [
"{{serverURL}}"
],
"path": [
"api",
"v1",
"analytics",
"content",
"event"
]
}
},
"response": []
}
]
}
],
"auth": {
Expand Down