Tracking SharePoint Popular Items Accessed through JavaScript

Applies to: SharePoint 2013+

A while back I wrote an AngularJS app running in SharePoint 2013 and using lists as the backing store for some components. Without getting into the reasons this particular architecture was chosen, let’s look at an issue that came up.

The client asked us to show the popular items. There is a standard web part for this, but you can also query the search API directly by sorting by ViewsLifeTime:descending which is what we did so that we could display it in a custom Angular directive. No problem! Right?

It wasn’t until some time later that the client asked about the results we were displaying. The most “popular” items appeared to be the ones they had had trouble with behind the scenes and didn’t match their expectations about what users would be viewing.

Unpopular

When you view a list item (dispform.aspx?id=X) this view is tracked in SharePoint’s Usage Analytics (assuming you’ve configured this correctly). It’s this information that is used by search to determine an item’s popularity.

Unfortunately, it turns out pulling items from the REST API triggers no such view event. This seems obvious in retrospect, but until we really thought about it, usage tracking is just one of those magical behind the scenes things SharePoint does and we just assumed it would work.

This explains the results the client was seeing. The items they were having trouble with are the ones they were viewing directly (outside of the Angular app). While this wasn’t a ton of traffic, it was far more than any other list item was getting since they were only being viewed via the REST API inside a custom application. Ugh.

Fortunately, the fix for this isn’t too complicated, it’s just not very obvious.

Spoofing Item Views in JavaScript

You can log a View event using the SP.Analytics.AnalyticsUsageEntry.logAnalyticsEvent2 method. I had trouble finding any helpful documentation on this method or what the parameters should be. Fortunately, after some experimentation (and patience as the entries are not immediately available), I got it working.

Here’s the basic JS code which you can then adapt and make smarter:


function logItemUsage(itemId, listName) {
SP.SOD.executeOrDelayUntilScriptLoaded(function() {
var stuff = {
ctx: SP.ClientContext.get_current()
};
stuff.user = stuff.ctx.get_web().get_currentUser();
stuff.ctx.load(stuff.user);
stuff.scope = "{00000000-0000-0000-0000-000000000000}";
stuff.site = stuff.ctx.get_site();
stuff.ctx.load(stuff.site);
this.stuff.ctx.executeQueryAsync(
function () {
stuff.siteId = stuff.site.get_id();
var itemUrl = _spPageContextInfo.webAbsoluteUrl + "/Lists/" + listName + "/DispForm.aspx?ID=" + itemId;
SP.Analytics.AnalyticsUsageEntry.logAnalyticsEvent2(
stuff.ctx,
1,
itemUrl,
stuff.scope,
stuff.siteId,
stuff.user
);
stuff.ctx.executeQueryAsync(
function() {
console.log("Logged: " + itemUrl);
}, function (s, a) {
console.log(a.get_message());
}
}, function (s, a) {
console.log(a.get_message());
}
);
}, "SP.js");
}

view raw

LogItemUsage.js

hosted with ❤ by GitHub

For instance, I adapted the above into an AngularJS service and wrapped the call to get the site Id and user into a promise so that I only had to pull that once in my app, but all of that is up to you.

One nice thing about doing this manually is you can control when it fires. For instance, you might want to only log usage during reads but not during edits or exclude administrative accounts, etc.

That’s it! Now your custom interface can participate in the internal popularity contest!