Cabin — React & Redux Example App — Keen

This is the 6th post in the 8 part tutorial series created by getstream.io. The final result is your own feature-rich, scalable social network app built with React and Redux! Visit cabin.getstream.io for an overview of all 8 tutorials and a live demo. The full source code is available on Github.

Stay updated to upcoming posts in this React & Redux series:

Tutorial 3: Cabin & Keen IO

Keen IO makes it easy to add a custom analytics dashboard to your app. It’s not a web analytics service for marketers like Google Analytics and Mixpanel. Keen is a platform for building your own custom analytics. It’s extremely easy to get up and running and scales well. This is why we’ve chosen it to power the stats page of the Cabin React example app. Here’s a screenshot of what we will be building in this tutorial:

Screen Shot 2016-06-21 at 10.26.45 AM

The analytics page shows how many people have viewed your profile and the images you’ve submitted to Cabin. It also shows you your most popular uploads.

Tracking

We’re going to track profile and item views and follows. The following 6 steps will help you set this up:

Step 0: Install & Clone the Example

If you didn’t complete the introduction tutorial yet, now is a good moment to quickly go over it and install Cabin.

Step 1: Get Your Keen API Token and Key

Visit https://keen.io/ and signup to get your API key and secret and fill in your API key details in env.sh:

export KEEN_PROJECT_ID=VALUE
export KEEN_WRITE_KEY=VALUE
export KEEN_READ_KEY=VALUE

Step 2: Restart the App

cd app
source ../env.sh; webpack --watch

Step 3: Track Your First View

Visit localhost:3000 and paste the following snippet in your console.

function viewPhoto(userId, postId, postAuthorId) {
var eventDetails = {
user: userId,
postId: postId,
postAuthorId: postAuthorId,
type: 'item'
}
keenClient.addEvent('views', eventDetails, function(err, res) {
if (err) {
console.log(err);
return;
};
console.log(res);
});
};
viewPhoto(1,1,1);

This will write an event to the views collection. You can visit your Keen dashboard to verify the data has been tracked. (Note: There can sometimes be a 10–20 second delay before the data shows up in Keen’s dashboard)

Screen Shot 2016-06-14 at 9.21.47 AM

Step 4: Country Information

One of my favorite features of Keen is data enrichment. In a nutshell it magically adds more data to your tracking events. We’re going to implement the IP to Geo parser. It will automatically add the IP and extract the GEO location. While we’re at it, we’ll also track the user agent information.

function viewPhoto(userId, postId, postAuthorId) {
var eventDetails = {
user: userId,
postId: postId,
postAuthorId: postAuthorId,
type: 'item',
ip_address: "${keen.ip}",
user_agent: "${keen.user_agent}",
keen: {
timestamp: new Date().toISOString(),
addons: [{
name: "keen:ip_to_geo",
input: {
ip: "ip_address"
},
output: "ip_geo_info"
}, {
name: "keen:ua_parser",
input: {
ua_string: "user_agent"
},
output: "parsed_user_agent"
}]
}
}
keenClient.addEvent('views', eventDetails, function(err, res) {
if (err) {
console.log(err);
return;
};
console.log(res);
});
};
viewPhoto(1,1,1);

Note how ${keen.ip} and ${keen.user_agent} are automatically replaced by keen. The keen:ip_to_geo and keen:ua_parser add-ons tell Keen how to handle the data enrichment. Try running

the above code in your console. You’ll see it show up in Keen’s dashboard.

Refactor

Since this code is becoming a bit verbose we’re going to refactor the analytics tracking to a module. Have a look at the code for analytics.js. It shows how we track photo views, profile views, likes and follows.

Querying Keen

Now that we’re correctly storing data in Keen, we want to query that data and show it on the stats page. In total we need to query 5 things:

  1. The number of profile views
  2. The number of item views
  3. The regions most views come from
  4. The top 5 most viewed items

For all of these queries we’re interested in the data for the last 30 days.

Step I: Profile and Item Views

Keen allows you to define queries and run those via client.run. A full example is shown below:

var userId = 1;
var itemViewsQuery = new Keen.Query("count", {
event_collection: 'views',
timeframe: 'this_30_days',
filters: [{
"property_name": 'postAuthorId',
"operator": 'eq',
"property_value": userId
}, {
"property_name": 'type',
"operator": 'eq',
"property_value": 'item'
}]
});

keenClient.run(itemViewsQuery, function(err, res) {
console.log(err, res)
if (err) {
console.log(err)
return
}
//Hand over the results to Redux
dispatch(_loadResponse(userId, {
'itemViews': res.result
}))
});

For timeframe we’ve selected the last 30 days. We’re filtering on item views where the postAuthorId is the current user id. You can run this in your console to verify the query executes and returns a count.

The profile views query is the same with a slightly different setting for the filters:

var profileViewsQuery = new Keen.Query("count", {
event_collection: 'views',
timeframe: 'this_30_days',
filters: [{
property_name: 'profileUser',
operator: 'eq',
property_value: userId
}, {
property_name: 'type',
operator: 'eq',
property_value: 'user'
}]
});

Step II: The Number of New Followers

The followers query is a bit more complicated. We need to account both for follows and unfollows. To get the net effect of follow changes we use a sum query:

var newFollowersQuery = new Keen.Query('sum', {
event_collection: "follow",
timeframe: "this_30_days",
filters: [{
property_name: 'targetId',
operator: 'eq',
property_value: userId
}],
target_property: 'directionInt'
});

Note how we specify which field to sum by sending target_property: directionInt

Step III: GEO Location for Views

For the geo views we want to see the count of the number of views on your items grouped by city.

var geoViewsQuery = new Keen.Query("count", {
event_collection: 'views',
timeframe: 'this_30_days',
filters: [{
property_name: 'postAuthorId',
operator: 'eq',
property_value: 1
}, {
property_name: 'type',
operator: 'eq',
property_value: 'item'
}, {
property_name: 'ip_geo_info.city',
operator: 'ne',
property_value: null
}],
group_by: ['ip_geo_info.city', 'ip_geo_info.province', 'ip_geo_info.country'],
});

Note how we use the dot syntax to get the geo info. The group by is specified as a simple list group_by: [‘ip_geo_info.city’, ‘ip_geo_info.province’, ‘ip_geo_info.country’].

You can see an overview of all these queries in Stats.js.

Step IV: Most Viewed Items

First let’s restart your backend and make sure we have the latest environment variables:

cd api
source ../env.sh; node index

The most viewed items are a bit harder to retrieve. We don’t only want the count, we also need to load the items from the database. To do this we’re going to create a route on the API endpoint called stats.js.

The query is similar to the other ones, the only difference is that we group by item:

var keenQuery = new Keen.Query("count", {
event_collection: 'views',
timeframe: 'this_30_days',
group_by: "postId",
filters: [{
property_name: 'postAuthorId',
operator: 'eq',
property_value: 1,
}]
});

One tricky part is that KeenIO doesn’t allow you to specify a limit to get only the top 5 items. So we’ll need to do this manually. In addition we’ll need to query the database for the details about the items. You can view the full implementation in stats.js. Look at the beautiful end result:

Screen Shot 2016-06-21 at 10.26.45 AM

Future Improvements

One area we didn’t explore is Keen’s saved queries. Saved queries allow you to implement caching and will vastly improve the performance of your stats. You can create saved queries programmatically so it’s easy to integrate into your app. Read the saved query documentation.

Conclusion

Using Keen as a building block we were able to build our own analytics system in just a few hours. In the next post we’ll cover how we’re using Algolia to power the search for Cabin. Add your email on cabin.getstream.io or follow @getstream_io on Twitter to stay up to date about the latest Cabin tutorials.

This tutorial series is created by getstream.io. Try the 5 min interactive tutorial to learn how Stream works.


Originally published at The Stream Blog.