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

feat(content analytics) fixes #30521 : Allow users to pass down simple Strings to query for Content Analytics data #30869

Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -10,21 +10,57 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.liferay.util.StringPool.COLON;
import static com.liferay.util.StringPool.COMMA;
import static com.liferay.util.StringPool.PERIOD;

/**
* This class represents the parameters of a Content Analytics Query abstracting the complexity
* of the underlying JSON format. The simplified REST Endpoint and the Content Analytics ViewTool
* use this class so that parameters can be entered in a more user-friendly way.
* use this class so that parameters can be entered in a more user-friendly way. Here's an example
* of what this simple JSON data looks like for the default {@code request} schema:
* <pre>
* {@code
* {
* "measures": "count,totalSessions",
* "dimensions": "host,whatAmI,url",
* "timeDimensions": "createdAt,day:Last month",
* "filters": "totalRequest gt 0,whatAmI contains PAGE||FILE",
* "order": "count asc,createdAt asc",
* "limit": 5,
* "offset": 0
* }
* }
* </pre>
* Under the covers, this builder will prefix the appropriate terms with the specified or default
* schema. If you want to provide a specific one, just add it to the JSON body:
* <pre>
* {@code
* {
* "scheme": "YOUR-SCHEME-NAME-HERE",
* ...
* ...
* }
* }
* </pre>
* Notice how there are four separator characters for different parameters. They must be used
* correctly for the data to be parsed correctly:
* <ul>
* <li>Blank space.</li>
* <li>Comma.</li>
* <li>Colon.</li>
* <li>Double pipes.</li>
* </ul>
*
* @author Jose Castro
* @since Nov 28th, 2024
*/
@JsonDeserialize(builder = ContentAnalyticsQuery.Builder.class)
public class ContentAnalyticsQuery implements Serializable {

public static final String SCHEME_ATTR = "scheme";
public static final String MEASURES_ATTR = "measures";
public static final String DIMENSIONS_ATTR = "dimensions";
public static final String TIME_DIMENSIONS_ATTR = "timeDimensions";
Expand All @@ -39,6 +75,7 @@ public class ContentAnalyticsQuery implements Serializable {
public static final String OPERATOR_ATTR = "operator";
public static final String VALUES_ATTR = "values";

private final String scheme;
@JsonProperty()
private final Set<String> measures;
@JsonProperty()
Expand All @@ -54,9 +91,13 @@ public class ContentAnalyticsQuery implements Serializable {
@JsonProperty()
private final int offset;

private static final String SEPARATOR = COLON;
private static final String SPACE = "\\s+";
private static final String DOUBLE_PIPE = "\\|\\|";
private static final String DEFAULT_DATE_RANGE = "Last week";
private static final String DEFAULT_SCHEME = "request";

private ContentAnalyticsQuery(final Builder builder) {
this.scheme = builder.scheme;
this.measures = builder.measures;
this.dimensions = builder.dimensions;
this.timeDimensions = builder.timeDimensions;
Expand All @@ -66,6 +107,10 @@ private ContentAnalyticsQuery(final Builder builder) {
this.offset = builder.offset;
}

public String scheme() {
return this.scheme;
}

public Set<String> measures() {
return this.measures;
}
Expand Down Expand Up @@ -101,13 +146,14 @@ public static ContentAnalyticsQuery.Builder builder() {
@Override
public String toString() {
return "ContentAnalyticsQuery{" +
"measures='" + measures + '\'' +
", dimensions='" + dimensions + '\'' +
", timeDimensions='" + timeDimensions + '\'' +
", filters='" + filters + '\'' +
", order='" + order + '\'' +
", limit='" + limit + '\'' +
", offset='" + offset + '\'' +
"scheme='" + scheme + '\'' +
", measures=" + measures +
", dimensions=" + dimensions +
", timeDimensions=" + timeDimensions +
", filters=" + filters +
", order=" + order +
", limit=" + limit +
", offset=" + offset +
'}';
}

Expand All @@ -117,6 +163,7 @@ public String toString() {
*/
public static class Builder {

private String scheme = DEFAULT_SCHEME;
private Set<String> measures;
private Set<String> dimensions;
private final List<Map<String, String>> timeDimensions = new ArrayList<>();
Expand All @@ -125,16 +172,29 @@ public static class Builder {
private int limit = 1000;
private int offset = 0;

/**
* Sets the default scheme for the parameters sent to the Content Analytics service.
*
* @param scheme The default scheme for the parameters.
*
* @return The builder instance.
*/
public Builder scheme(final String scheme) {
this.scheme = scheme;
return this;
}

/**
* The measures parameter contains a set of measures and each measure is an aggregation over
* a certain column in your ClickHouse database table.
*
* @param measures A string with the measures separated by a space.
* @param measures A string with the measures separated by
* {@link com.liferay.util.StringPool#COMMA}.
*
* @return The builder instance.
*/
public Builder measures(final String measures) {
this.measures = Set.of(measures.split("\\s+"));
this.measures = addScheme(Set.of(measures.split(COMMA)));
return this;
}

Expand All @@ -143,12 +203,13 @@ public Builder measures(final String measures) {
* an attribute related to a measure, e.g. the measure user_count can have dimensions like
* country, age, occupation, etc.
*
* @param dimensions A string with the dimensions separated by a space.
* @param dimensions A string with the dimensions separated by
* {@link com.liferay.util.StringPool#COMMA}.
*
* @return The builder instance.
*/
public Builder dimensions(final String dimensions) {
this.dimensions = Set.of(dimensions.split("\\s+"));
this.dimensions = addScheme(Set.of(dimensions.split(COMMA)));
return this;
}

Expand All @@ -157,24 +218,28 @@ public Builder dimensions(final String dimensions) {
* an array of objects in timeDimension format. If no date range is provided, the default
* value will be "Last week".
*
* @param timeDimensions A string with the time dimensions separated by a colon.
* @param timeDimensions A string with the time dimensions separated by
* {@link com.liferay.util.StringPool#COMMA}.
*
* @return The builder instance.
*/
public Builder timeDimensions(final String timeDimensions) {
if (UtilMethods.isNotSet(timeDimensions)) {
return this;
}
final String[] timeParams = timeDimensions.split(SEPARATOR);
final String[] timeParams = timeDimensions.split(COMMA);
final Map<String, String> timeDimensionsData = new HashMap<>();
timeDimensionsData.put(TIME_DIMENSIONS_DIMENSION_ATTR, timeParams[0]);
if (timeParams.length > 2) {
timeDimensionsData.put(GRANULARITY_ATTR, timeParams[1]);
timeDimensionsData.put(DATE_RANGE_ATTR, timeParams[2]);
} else if (timeParams.length > 1) {
timeDimensionsData.put(DATE_RANGE_ATTR, timeParams[1]);
timeDimensionsData.put(TIME_DIMENSIONS_DIMENSION_ATTR, addScheme(timeParams[0]));
if (timeParams.length > 1) {
final String[] granularityAndRange = timeParams[1].split(COLON);
if (granularityAndRange.length > 1) {
timeDimensionsData.put(GRANULARITY_ATTR, granularityAndRange[0]);
timeDimensionsData.put(DATE_RANGE_ATTR, granularityAndRange[1]);
} else {
timeDimensionsData.put(DATE_RANGE_ATTR, granularityAndRange[0]);
}
} else {
timeDimensionsData.put(DATE_RANGE_ATTR, "Last week");
timeDimensionsData.put(DATE_RANGE_ATTR, DEFAULT_DATE_RANGE);
}
this.timeDimensions.add(timeDimensionsData);
return this;
Expand All @@ -184,23 +249,24 @@ public Builder timeDimensions(final String timeDimensions) {
* Filters are applied differently to dimensions and measures. When you filter on a
* dimension, you are restricting the raw data before any calculations are made. When you
* filter on a measure, you are restricting the results after the measure has been
* calculated. They are composed of: member, operator, and values.
* calculated. They are composed of 3 parts: member, operator, and values.
*
* @param filters A string with the filters separated by a colon.
* @param filters A string with the filters separated by
* {@link com.liferay.util.StringPool#COMMA}.
*
* @return The builder instance.
*/
public Builder filters(final String filters) {
if (UtilMethods.isNotSet(filters)) {
return this;
}
final String[] filterArr = filters.split(SEPARATOR);
final String[] filterArr = filters.split(COMMA);
for (final String filter : filterArr) {
final String[] filterParams = filter.split("\\s+");
final String[] filterParams = filter.split(SPACE);
final Map<String, Object> filterDataMap = new HashMap<>();
filterDataMap.put(MEMBER_ATTR, filterParams[0]);
filterDataMap.put(MEMBER_ATTR, addScheme(filterParams[0]));
filterDataMap.put(OPERATOR_ATTR, filterParams[1]);
final String[] filterValues = filterParams[2].split(COMMA);
final String[] filterValues = filterParams[2].split(DOUBLE_PIPE);
filterDataMap.put(VALUES_ATTR, filterValues);
this.filters.add(filterDataMap);
}
Expand All @@ -213,18 +279,23 @@ public Builder filters(final String filters) {
* on the order of the keys in the object. If not provided, default ordering is applied. If
* an empty object ([]) is provided, no ordering is applied.
*
* @param order A string with the order separated by a colon.
* @param order A string with the order separated by
* {@link com.liferay.util.StringPool#COMMA}.
*
* @return The builder instance.
*/
public Builder order(final String order) {
if (UtilMethods.isNotSet(order)) {
return this;
}
final Set<String> orderCriteria = Set.of(order.split(SEPARATOR));
final Set<String> orderCriteria = Set.of(order.split(COMMA));
for (final String orderCriterion : orderCriteria) {
final String[] orderParams = orderCriterion.split("\\s+");
this.order.add(orderParams);
final String[] orderParams = orderCriterion.split(SPACE);
if (orderParams.length > 1) {
this.order.add(new String[]{addScheme(orderParams[0]), orderParams[1]});
} else {
this.order.add(orderParams);
}
}
return this;
}
Expand Down Expand Up @@ -253,7 +324,6 @@ public Builder offset(final int offset) {
return this;
}


/**
* This method builds the ContentAnalyticsQuery object based on all the specified
* parameters for the query.
Expand All @@ -268,21 +338,46 @@ public ContentAnalyticsQuery build() {
* This method builds the ContentAnalyticsQuery object based on all the specified
* parameters in the provided map.
*
* @param form A {@link Map} containing the query data.
* @param parameters A {@link Map} containing the query data.
*
* @return The {@link ContentAnalyticsQuery} object.
*/
public ContentAnalyticsQuery build(final Map<String, Object> form) {
this.measures((String) form.get(MEASURES_ATTR));
this.dimensions((String) form.get(DIMENSIONS_ATTR));
this.timeDimensions((String) form.get(TIME_DIMENSIONS_ATTR));
this.filters((String) form.get(FILTERS_ATTR));
this.order((String) form.get(ORDER_ATTR));
this.limit((Integer) form.get(LIMIT_ATTR));
this.offset((Integer) form.get(OFFSET_ATTR));
public ContentAnalyticsQuery build(final Map<String, Object> parameters) {
this.scheme((String) parameters.getOrDefault(SCHEME_ATTR, DEFAULT_SCHEME));
this.measures((String) parameters.get(MEASURES_ATTR));
this.dimensions((String) parameters.get(DIMENSIONS_ATTR));
this.timeDimensions((String) parameters.get(TIME_DIMENSIONS_ATTR));
this.filters((String) parameters.get(FILTERS_ATTR));
this.order((String) parameters.get(ORDER_ATTR));
this.limit((Integer) parameters.get(LIMIT_ATTR));
this.offset((Integer) parameters.get(OFFSET_ATTR));
return new ContentAnalyticsQuery(this);
}

/**
* This method adds the default scheme to the terms if they don't contain it.
*
* @param terms The terms to check.
*
* @return The terms with the default scheme added if they don't contain it.
*/
private Set<String> addScheme(final Set<String> terms) {
return terms.stream()
.map(this::addScheme)
.collect(Collectors.toSet());
}

/**
* This method adds the default scheme to the term if it doesn't contain it.
*
* @param term The term to check.
*
* @return The term with the default scheme added if it doesn't contain it.
*/
private String addScheme(final String term) {
return term.contains(PERIOD) ? term : scheme + PERIOD + term;
}

}

}
Loading
Loading