Tracking interests with a custom Groovy Action
Overview
Continuing from the events we’ve been collecting using the previous tutorial, we’re going to configure jCustomer to perform various types of analysis.
In this tutorial we will be collecting interests (Healthcare or others), and automatically add them to user profiles, the objective being to obtain an array of interests such as:
{
"interests": [
{ "healthcare": 5 },
{ "automotive": 1 },
{ "sports": 10 },
{ "cooking": 3 },
{ "technology": 2 }
]
}
Collect interests
As explained above, the objective of our first step is to collect interests and append them to a profile, if this profile already has the interest, then its count gets incremented.
In jCustomer, such an operation require the user a two elements:
- A rule must be created to act when an event condition is met.
- An action will be called by the rule on all events matching its condition.
Introducing Karaf shell
Before proceeding further with creating actions and rules, it is important to get familiar with karaf tools and in particular event-tail and rule-tail which are a useful mean of verifying various operations.
The Karaf shell is accessible via SSH, by default on port 8012.
ubuntu@ip-10-0-3-253:~$ ssh -p 8102 karaf@localhost
Password authentication
Password:
__ __ ____
/ //_/____ __________ _/ __/
/ ,< / __ `/ ___/ __ `/ /_
/ /| |/ /_/ / / / /_/ / __/
/_/ |_|\__,_/_/ \__,_/_/
Apache Karaf (4.2.11)
Hit '<tab>' for a list of available commands
and '[cmd] --help' for help on a specific command.
Hit 'system:shutdown' to shutdown Karaf.
Hit '<ctrl-d>' or type 'logout' to disconnect shell from current session.
karaf@root()>
The event-tail command will automatically list events as they’re being received by the system, for example if you were to click on “Healthcare” again.
karaf@root()> event-tail
ID |Type |Session |Profile |Timestamp |Scope |Persi|
-------------------------------------------------------------------------------------------------------
34b[...]0f7|click |a37[...]566 |bd3[...]304 |Mon Nov 29 18:25:58 UTC 2021 |digitall |true |
Similarly, the “rule-tail” will list rules as they’re being executed by the system and can be used as an easy way to validate that rule conditions are properly defined.
Register an action
We will begin by registering a new Groovy Action in jCustomer, for now we only want to make sure our action actually gets triggered when an event is received, so we’re going to keep its content to a minimum (displaying a log).
Save the code below to a file called “interestAction.groovy”
import java.util.logging.Logger
@Action(id = "scriptGroovyAction",
description = "A Groovy action recording interests",
actionExecutor = "groovy:interestAction",
hidden = false)
def execute() {
Logger logger = Logger.getLogger("scriptGroovyAction")
logger.info("Groovy action for event type: " + event.getEventType())
EventService.NO_CHANGE
}
From the same folder, we’re going to use curl to submit the action to jCustomer (don't forget to update credentials as needed).
curl --request POST \
--url http://localhost:8181/cxs/groovyActions \
--user karaf:karaf \
--form file=@interestAction.groovy
We will revisit the action once we confirmed that it gets properly triggered when an event is received.
Register a rule
Now that we have an action that will be doing something, we can create a rule to trigger it on events matching a particular condition. Luckily for us, we defined that condition earlier when we were using unomi to search for our events.
Using curl, we’re going to submit the following rule:
curl --request POST \
--url http://localhost:8181/cxs/rules \
--user karaf:karaf \
--header 'Content-Type: application/json' \
--data '{
"metadata": {
"id": "testGroovyActionRule",
"name": "Test Groovy Action Rule",
"description": "A sample rule to test Groovy actions"
},
"condition": {
"type": "eventTypeCondition",
"parameterValues" : {
"eventTypeId" : "click"
}
},
"actions": [
{
"type": "scriptGroovyAction",
"parameterValues": {}
}
]
}'
Validate rule execution
If all is going well, submitting a new event by clicking on “Healthcare” on Digitall should trigger the rule.
Open-up the Karaf shell and run the “rule-tail” command.
karaf@root()> rule-tail
Rule ID |Rule Name |Event Type |Session |Profile |Timestamp |Scope|
-------------------------------------------------------------------------------------------------
testGroovyActionRule |Test [...] Rule |click |a02[...]b5c|bd3[...]c89|Mon Nov 29 20:1|digit|
If you look into jCustomer logs you should also see the log message we added to the action.
Nov 29 20:15:23 ip-10-0-3-140 docker_jcustomer[9776]: 2021-11-29T20:15:23,624 | INFO | qtp1054571393-286 | | 5 - org.ops4j.pax.logging.pax-logging-api - 1.11.9 | Groovy action for itemID: healthcare
Update the action
By following the tutorial up to that point we managed to trigger our action on a particular type of event, our next step will consist in updating the action to populate the interests array.
Log messages are present in this example to facilitate debugging, you should be cautious when using logs in production.
\
import java.util.logging.Logger;
import org.apache.unomi.api.Event;
import org.apache.unomi.api.Profile;
import org.apache.unomi.api.services.EventService;
@Action(id = "scriptGroovyAction",
description = "A Groovy action recording interests",
actionExecutor = "groovy:interestAction",
hidden = false
)
def execute() {
Logger logger = Logger.getLogger("groovyActionInterests")
final String EVENT_INTERESTS_PROPERTY = "interests";
final Profile profile = event.getProfile();
Map<String, Double> srcInterestsMap = (Map<String, Double>) profile.getProperty( EVENT_INTERESTS_PROPERTY );
final Map<String, Double> dstInterestsMap = new HashMap<>();
if ( srcInterestsMap == null || !srcInterestsMap.containsKey(event.target.itemId)) {
// The profile does not have any interests or the received interest
// does does not exist yet in the profile
dstInterestsMap.putAll(srcInterestsMap)
dstInterestsMap.put(event.target.itemId, 1)
} else {
// Copying the interests and incrementing the one which was just received
srcInterestsMap.forEach((interest, clickCount) -> {
if (interest == event.target.itemId) {
dstInterestsMap.put(interest, clickCount + 1)
} else {
dstInterestsMap.put(interest, clickCount)
}
});
}
logger.info("Profile interests before processing the event: " + srcInterestsMap)
logger.info("Profile interests after processing the event: " + dstInterestsMap)
// Save the updated interests property
event.getProfile().setProperty(EVENT_INTERESTS_PROPERTY, dstInterestsMap)
return EventService.PROFILE_UPDATED;
}
Triggering an event will give you the following logs:
2021-11-30T04:19:56,261 | INFO | qtp1407221069-237 | | 5 - org.ops4j.pax.logging.pax-logging-api - 1.11.9 | Profile interests before processing the event: [media:5, healthcare:17]
2021-11-30T04:19:56,262 | INFO | qtp1407221069-237 | | 5 - org.ops4j.pax.logging.pax-logging-api - 1.11.9 | Profile interests after processing the event: [media:5, healthcare:18]
As you can see, interests for the user who clicked on the site are increasing according to the element being clicked on.
Conclusion
In this tutorial, we reviewed how a Groovy action can be created and enabled on jCustomer. We used interest-tracking as an example, but Apache Unomi already has a built-in action to track interests, action we would recommend using in production.