In the Audience Targeting (AT) application, a campaign defines a set of content targeted to specific user segments during a time period. Campaign custom reports allow campaign administrators to learn how users behave in the context of a campaign by monitoring their interaction over different elements of the site. Out of the box, Liferay provides several metrics that are based on entity types that you can track, such as content, forms, links, pages, etc. You can use these metrics to create custom reports. For example, if you want track how many users watch a YouTube video that is published on your site, you might create a custom report with the YouTube Videos metric.
The AT app ships with many metrics you can apply to custom reports, but it’s also extensible. This means that if the default metrics available do not fulfill your needs, you can create one yourself!
A metric’s development strategy comes down to four choices:
- Entity to Track
- Tracking Mechanism
- Tracking Events
- Differentiation Method
Creating a metric involves targeting what you want to track in a custom report. Suppose you’re the owner of a hardware store and you’d like to send emails to your customers notifying them of the store’s weekly newsletter. You send the email every week, but you’re in the dark about how many customers actually open and read the newsletter. For this example, your entity to track is a newsletter.
To track how many customers view the newsletter, you’ll need to create a tracking mechanism. You can provide a custom tracking mechanism (e.g., a servlet) or you can use the ones provided by Audience Targeting. For a newsletter, you could use a transparent image as the tracking mechanism, which would have the View tracking event capability. Whenever the image is viewed, the Audience Targeting app computes and stores the information.
In many cases, a metric can have multiple tracking event options. For example, the YouTube Videos metric provides tracking event options like Buffering, Playing, Paused, Ended, etc. This lets you track different kinds of actions on an entity, providing a more accurate report on user interactions.
Finally, you must assign the metric to an entity. For a newsletter, you could provide a Newsletter ID field that the user could fill in to differentiate newsletters, if there’s more than one.
To learn more about how metrics are used in the Audience Targeting application, visit the Defining Metrics section.
For this tutorial, you’ll create a newsletter that can track who views it. This process involves defining the view/save lifecycle, which is when a user applies a metric to a report using the Report Editor. Then you’ll define its tracking mechanism, tracking event(s), and differentiation method, similar to what was described above.
Figure 1: The sample Newsletter metric requires the newsletter name, ID, and event type.
Now that you have an idea of how to plan your new metric, you’ll begin creating one next!
Creating a Metric
Adding a new metric to the Audience Targeting application is easy. First, you must create a module and ensure it has the necessary Content Targeting API dependencies.
-
Create a module project for deploying a metric. A Blade CLI content-targeting-tracking-action template is available to help you get started quickly. It sets the default configuration for you, and it contains boilerplate code so you can skip the file creation steps and get started right away.
-
Make sure your module specifies the dependencies necessary for an Audience Targeting metric. For example, you should specify the Content Targeting API and necessary Liferay packages. For example, this is the example
build.gradle
file used from a Gradle based metric:dependencies { compileOnly group: "com.liferay.content-targeting", name: "com.liferay.content.targeting.analytics.api", version: "3.0.0" compileOnly group: "com.liferay.content-targeting", name: "com.liferay.content.targeting.anonymous.users.api", version: "2.0.2" compileOnly group: "com.liferay.content-targeting", name: "com.liferay.content.targeting.api", version: "4.0.0" compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.3.0" compileOnly group: "com.liferay.portal", name: "com.liferay.util.taglib", version: "2.0.0" compileOnly group: "javax.portlet", name: "portlet-api", version: "2.0" compileOnly group: "javax.servlet", name: "javax.servlet-api", version: "3.0.1" compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations", version: "1.3.0" }
You can learn more about exposing the Content Targeting API in the Accessing the Content Targeting API tutorial. Once you’ve created your module and specified its dependencies, you’ll need to define your metric’s behavior. How your metric behaves is controlled by a Java class file that you create.
-
Create a unique package name in the module’s
src
directory, and create a new Java class in that package. To follow naming conventions, your class name should begin with the metric’s name you’re creating and end with TrackingAction (e.g.,NewsletterTrackingAction.java
). Your Java class should implement the com.liferay.content.targeting.api.model.TrackingAction` interface.You must implement the TrackingAction interface, but there are
TrackingAction
extension classes that provide helpful utilities that you can extend. For example, your metric can extend the BaseJSPTrackingAction class to support generating your metric’s UI using JSPs. This tutorial demonstrates implementing the UI using a JSP and assumes the TrackingAction interface is implemented by extending theBaseJSPTrackingAction
class. For more information on choosing a UI for your metric, see the Selecting a UI Technology section. -
Directly above the class’s declaration, insert the following annotation:
@Component(immediate = true, service = TrackingAction.class)
This declares the Component’s implementation class and configures it to start immediately once deployed to Liferay DXP.
Now that your Java class is set up, you’ll need to define how your metric works by implementing the TrackingAction interface’s methods. You’ll begin implementing these methods next.
The first thing you’ll define in your newsletter metric is the view/save lifecycle.
Defining a Metric’s View/Save Lifecycle
This section covers how to define a metric’s view/save lifecycle. This is when a user applies a metric to a report using the Report Editor.
In this section, you’ll begin defining the newsletter metric’s Java class. This
assumes that you followed the instructions above, creating the
NewsletterTrackingAction
class and extending
BaseJSPTrackingAction.
If you used the content-targeting-tracking-action
Blade CLI template, your
project is already extending BaseJSPTrackingAction
and a default view.jsp
file is already created.
-
Add the activation and deactivation methods to your class.
@Activate @Override public void activate() { super.activate(); } @Deactivate @Override public void deActivate() { super.deActivate(); }
These methods call the super class BaseTrackingAction to implement necessary logging and processing for when your metric starts and stops. Make sure to include the @Activate and @Deactivate annotations, which are required.
-
Add the following method:
@Override protected void populateContext( TrackingActionInstance trackingActionInstance, Map<String, Object> context, Map<String, String> values) { String alias = StringPool.BLANK; String elementId = StringPool.BLANK; String eventType = StringPool.BLANK; if (!values.isEmpty()) { alias = values.get("alias"); elementId = values.get("elementId"); eventType = values.get("eventType"); } else if (trackingActionInstance != null) { alias = trackingActionInstance.getAlias(); elementId = trackingActionInstance.getElementId(); eventType = trackingActionInstance.getEventType(); } context.put("alias", alias); context.put("elementId", elementId); context.put("eventType", eventType); context.put("eventTypes", getEventTypes()); }
To understand what this method accomplishes, you should understand the metric’s configuration lifecycle.
Figure 2: An Audience Targeting metric must be configured by the user and processed before it can become part of a Report.
When the user opens the Report Editor, the render phase begins for the metric. The
getFormHTML(...)
method retrieves the HTML to display. You don’t have to worry about implementing this method because it’s already implemented in the BaseJSPTrackingAction class you’re extending. ThegetFormHTML
method calls thepopulateContext(...)
method.You’ll notice the
populateContext
method is not available in the TrackingAction interface. This is because it’s not needed in all cases. It’s available by extending theBaseJSPTrackingAction
class, and you’ll need to add more logic to it for the newsletter metric.The goal of the
populateContext
method is to generate a map with all the parameters your JSP view needs to render the metric’s HTML. This map is stored in thecontext
variable, which is pre-populated with basic values in the Portlet logic, and then each metric contributes its specific parameters to it. ThepopulateContext
method above populates thealias
,elementId
,eventType
, andeventTypes
context variables with the adjacent values from thevalues
map parameter, which is then passed to the JSP.For the newsletter metric, the
populateContext
method accounts for three use cases:a. The metric was added but has no set values yet. In this case, the default values defined by the developer are injected (e.g.,
alias=""
).b. The metric was added and a value is set, but the request failed to complete (e.g., due to an error). In this case, the
values
parameter of thepopulateContext
method contains the values that were intended to be saved, and they are injected so that they are displayed in the metric’s view together with the error message.c. The metric was added and a value was successfully set. In this case, the
values
parameter is empty, and you have to obtain the values from storage that the form should display and inject them in the context so they’re displayed in the metric’s HTML. The newsletter metric stores values in the metric’s instance, but complex metrics could use services to store values.You can think of the
populateContext
method as the intermediary between your JSP and your backend code. You can see how to create the newsletter metric’s UI using a JSP by skipping to the Defining the Metric’s UI section. Once the HTML is successfully retrieved and the user has set the newsletter’s values and clicked Save, the action phase begins. -
Once the action phase begins, AT processes the tracking action (metric). The
processTrackingAction(...)
method takes the values from the metric’s UI form and stores them in the corresponding fields of thetrackingActionInstance
. Since the BaseTrackingAction class provides a default implementation of this method that returnsnull
, theNewsletterTrackingAction
class does not need to implement it.If you need to process any custom fields in your metric, you should override this method. If you want your custom values to be stored in the
typeSettings
field of thetrackingActionInstance
, return their value instead ofnull
.Once the metric processing ends, the form is reloaded and the lifecycle restarts again. The value(s) specified in the metric are stored and are ready to be accessed once the report generation begins. Next, you must set the event types that the newsletter metric should evaluate.
-
Add the following method and private field:
@Override public List<String> getEventTypes() { return ListUtil.fromArray(_EVENT_TYPES); } private static final String[] _EVENT_TYPES = {"view"};
This specifies that your newsletter metric only tracks who views the newsletter.
-
Define a way to retrieve the metric’s localized summary. In many instances, you can do this by combining keys in the metric’s resource bundle with the information stored for the metric. For the newsletter metric, you can provide information about the ID of the newsletter being tracked, which is stored in the
alias
field of thetrackingActionInstance
object.@Override public String getSummary( String summary = LanguageUtil.format( locale, "tracking-newsletter-x", new Object[] {trackingActionInstance.getAlias()}); return summary; }
-
Set the servlet context for your metric.
@Override @Reference( target = "(osgi.web.symbolicname=newsletter)", unbind = "-" ) public void setServletContext(ServletContext servletContext) { super.setServletContext(servletContext); }
This is only required for metrics extending the
BaseJSPTrackingAction
class. The servlet context must be set for the metric to render its own JSP files. ThesetServletContext
method is invoked automatically when the metric module is installed and resolved in Liferay. Make sure theosgi.web.symbolicname
in thetarget
property of the@Reference
annotation is set to the same value as theBundle-SymbolicName
defined in thebnd.bnd
file of the module.
Next, you’ll define a tracking mechanism for your metric to use.
Using a Tracking Mechanism
Imagine an administrator has successfully configured and saved your custom
metric to his or her report. Now what? Your metric needs to fulfill its
purpose, which is to track the view
event type for the defined newsletter. To
do this, you must define a tracking mechanism. For your newsletter, you’ll use a
transparent image as the tracking mechanism, which would have the View
tracking event capability. Whenever the image is viewed, the newsletter metric
computes and stores the information.
For the newsletter metric, you’ll use a tracking mechanism provided by the Audience Targeting app.
-
You must set the analytics processor that the Content Targeting API provides for tracking events. Add the following method and private field:
@Reference protected void setAnalyticsProcessor(AnalyticsProcessor analyticsProcessor) { _analyticsProcessor = analyticsProcessor; } private AnalyticsProcessor _analyticsProcessor;
The analytics processor is a module of the Audience Targeting Analytics system. It contains a servlet to track analytics from Liferay pages (views, clicks, etc.) and an API to leverage this tracking mechanism. In the
setAnalyticsProcesoor(...)
method, you’re obtaining a reference of the current analytics processor to build the URL used to generate a transparent image. All you have to do is insert the generated URL into your newsletter’s HTML, and the transparent image tracks who reads it. Everything is processed by the default Audience Targeting Analytics system automatically.Now that you’ve obtained a reference of the analytics processor, you need to add logic for generating the appropriate tracking URL.
-
Replace the
populateContext
method with the updated method:@Override protected void populateContext( TrackingActionInstance trackingActionInstance, Map<String, Object> context, Map<String, String> values) { String alias = StringPool.BLANK; String elementId = StringPool.BLANK; String eventType = StringPool.BLANK; String trackImageHTML = StringPool.BLANK; if (!values.isEmpty()) { alias = values.get("alias"); elementId = values.get("elementId"); eventType = values.get("eventType"); } else if (trackingActionInstance != null) { alias = trackingActionInstance.getAlias(); elementId = trackingActionInstance.getElementId(); eventType = trackingActionInstance.getEventType(); String trackImageURL = _analyticsProcessor.getTrackingURL( trackingActionInstance.getCompanyId(), 0, 0, "", 0, Campaign.class.getName(), new long[] {trackingActionInstance.getCampaignId()}, trackingActionInstance.getElementId(), "view", ""); trackImageHTML = "<img alt=\"\" src=\"" + trackImageURL + "\" />"; } context.put("alias", alias); context.put("elementId", elementId); context.put("eventType", eventType); context.put("eventTypes", getEventTypes()); context.put("trackImageHTML", trackImageHTML); }
This updated method creates a new variable named
trackImageHTML
, retrieves a tracking URL using the analytics processor, and then populates thetrackImageHTML
context variable. When creating a new metric, the transparent image’s URL field is not present in the metric’s form. When the metric is initially saved, however, the URL is generated using the analytics processor and is available for copying.
Excellent! You’ve obtained the analytics processor and can create the transparent image tracking mechanism. The newsletter metric’s behavior is defined and complete. The last thing you need to do is create a JSP template.
Defining the Metric’s UI
The Java code you’ve added to this point has assumed that there are three configurable fields for your newsletter metric:
- Name: used in reports that count the number of times a metric has been triggered. This is also known as the newsletter’s alias.
- Newsletter ID: used to differentiate between newsletters.
- Event Type: used to differentiate several actions on the same newsletter, such as opening the newsletter or clicking on a link.
To let administrators set these values, you must define a UI so your metric can
be configured during the view/save lifecycle. Remember that you must also define
a field to display the generated transparent image’s URL. Create a view.jsp
file in your metric’s module (e.g.,
/src/main/resources/META-INF/resources/view.jsp
) and add the following logic:
<%
Map<String, Object> context = (Map<String, Object>)request.getAttribute("context");
String alias = (String)context.get("alias");
String elementId = (String)context.get("elementId");
String eventType = (String)context.get("eventType");
List<String> eventTypes = (List<String>)context.get("eventTypes");
String trackImageHTML = (String)context.get("trackImageHTML");
%>
<aui:input helpMessage="name-help" label="name" name='<%= ContentTargetingUtil.GUID_REPLACEMENT + "alias" %>' type="text" value="<%= alias %>">
<aui:validator name="required" />
</aui:input>
<aui:input helpMessage="enter-the-id-of-the-newsletter-to-be-tracked" label="newsletter-id" name='<%= ContentTargetingUtil.GUID_REPLACEMENT + "elementId" %>' type="text" value="<%= elementId %>">
<aui:validator name="required" />
</aui:input>
<c:if test="<%= ListUtil.isNotEmpty(eventTypes) %>">
<aui:select label="event-type" name='<%= ContentTargetingUtil.GUID_REPLACEMENT + "eventType" %>'>
<%
for (String curEventType : eventTypes) {
%>
<aui:option label="<%= curEventType %>" selected="<%= curEventType.equals(eventType) %>" value="<%= curEventType %>" />
<%
}
%>
</aui:select>
</c:if>
<c:if test="<%= !Validator.isBlank(trackImageHTML) %>">
<span class="h5">
<liferay-ui:message key="paste-this-code-at-the-beginning-of-your-newsletter" />
</span>
<label for='<%= renderResponse.getNamespace() + ContentTargetingUtil.GUID_REPLACEMENT + "trackImageHTML" %>' key="paste-this-code-at-the-beginning-of-your-newsletter" /></label>
<liferay-ui:input-resource id='<%= renderResponse.getNamespace() + ContentTargetingUtil.GUID_REPLACEMENT + "trackImageHTML" %>' url="<%= trackImageHTML %>" />
</c:if>
First you instantiate the context
variable and its attributes you configured
in your Java class’s populateContext
method. Then you specify the appropriate
fields Name, Newsletter ID, and Event Type. Finally, you present the generated
transparent image URL.
Notice that the input field names in the JSP are prefixed with
ContentTargetingUtil.GUID_REPLACEMENT
. This prefix is required for
multi-instantiable metrics, which are metrics that return true
in the
isInstantiable
method of their -TrackingAction
class and can be added more
than once to the Metrics form.
Figure 3: Once you've saved the metric, you can copy the generated transparent image URL into your newsletter's HTML to track who views it.
Congratulations! You’ve created the newsletter metric and can now track whether users viewed a newsletter. You can test if the metric is working by copying the generated tracking image HTML into an email HTML editor, sending it, and opening it as if it were an actual newsletter. Then open the custom report containing the newsletter metric and select Update Report. A chart and table with the newsletter’s view count is shown.
You can view the finished version of the newsletter metric by downloading its ZIP file.
Now you’ve created and examined a fully functional metric and have the knowledge to create your own.