Skip to content

Commit

Permalink
Switch to GA4; add flexible analytics models
Browse files Browse the repository at this point in the history
- switch from using universal analytics with analytics.js to google analytics for with gtag.js
- move analytics logic into a model that handles initializing gtag, checking if google analytics is enable, sending events, etc.
- create a base analytics model that can be extended in the future to use a service other than google analytics

Issues #1709 and #2149
  • Loading branch information
robyngit committed Jun 28, 2023
1 parent 45d66c7 commit 5840e42
Show file tree
Hide file tree
Showing 19 changed files with 359 additions and 176 deletions.
5 changes: 5 additions & 0 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ MetacatUI.mapModel = MetacatUI.mapModel || {};
MetacatUI.appLookupModel = MetacatUI.appLookupModel || {};
MetacatUI.nodeModel = MetacatUI.nodeModel || {};
MetacatUI.appUserModel = MetacatUI.appUserModel || {};
MetacatUI.analytics = MetacatUI.analytics || {};

/* Setup the application scaffolding first */
require(['bootstrap', 'views/AppView', 'models/AppModel'],
Expand Down Expand Up @@ -193,6 +194,10 @@ function(Bootstrap, AppView, AppModel) {

MetacatUI.appUserModel = new UserModel();

require(['models/analytics/GoogleAnalytics'], function (Analytics) {
MetacatUI.analytics = new Analytics();
});

/* Create a general event dispatcher to enable
communication across app components
*/
Expand Down
16 changes: 7 additions & 9 deletions src/js/collections/DataPackage.js
Original file line number Diff line number Diff line change
Expand Up @@ -1377,15 +1377,6 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
m.set("uploadStatus", m.defaults().uploadStatus);
});

//Send this exception to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "exception", {
"exDescription": "DataPackage save error: " + data.responseText +
" | Id: " + collection.packageModel.get("id") + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": true
});
}

//When there is no network connection (status == 0), there will be no response text
if( data.status == 408 || data.status == 0 ){
var parsedResponse = "There was a network issue that prevented this file from uploading. " +
Expand All @@ -1402,6 +1393,13 @@ define(['jquery', 'underscore', 'backbone', 'rdflib', "uuid", "md5",
collection.packageModel.set("uploadStatus", "e");

collection.trigger("errorSaving", parsedResponse);

// Track this error in our analytics
MetacatUI.analytics?.trackException(
`DataPackage save error: ${parsedResponse}`,
collection.packageModel.get("id"),
true
);
}
}
$.ajax(_.extend(requestSettings, MetacatUI.appUserModel.createAjaxSettings()));
Expand Down
44 changes: 20 additions & 24 deletions src/js/models/DataONEObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,14 +668,12 @@ define(['jquery', 'underscore', 'backbone', 'uuid', 'he', 'collections/AccessPol
//Trigger a custom event for the model save error
model.trigger("errorSaving", parsedResponse);

//Send this exception to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "exception", {
"exDescription": "DataONEObject save error: " + parsedResponse +
" | Id: " + model.get("id") + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": true
});
}
// Track this error in our analytics
MetacatUI.analytics?.trackException(
`DataONEObject save error: ${parsedResponse}`,
model.get("id"),
true
);
}
}
};
Expand Down Expand Up @@ -820,14 +818,13 @@ define(['jquery', 'underscore', 'backbone', 'uuid', 'he', 'collections/AccessPol
// errored
model.trigger("sysMetaUpdateError");

//Send this exception to Google Analytics
if (MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")) {
ga("send", "exception", {
"exDescription": "DataONEObject update system metadata error: " + parsedResponse +
" | Id: " + model.get("id") + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": true
});
}
// Track this error in our analytics
MetacatUI.analytics?.trackException(
`DataONEObject update system metadata ` +
`error: ${parsedResponse}`,
model.get("id"),
true
);
}
}
}
Expand Down Expand Up @@ -944,14 +941,13 @@ define(['jquery', 'underscore', 'backbone', 'uuid', 'he', 'collections/AccessPol
//Log an error to the console
console.error("Couldn't check the authority for this user: ", e);

//Send this exception to Google Analytics
if (MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")) {
ga("send", "exception", {
"exDescription": "Couldn't check the authority for the user " + MetacatUI.appModel.get("username") +
" | Object Id: " + this.get("id") + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": true
});
}
// Track this error in our analytics
const name = MetacatUI.appModel.get('username')
MetacatUI.analytics?.trackException(
`Couldn't check the authority for the user ${name}: ${e}`,
this.get("id"),
true
);

//Set the user as unauthorized
model.set("isAuthorized_" + action, false);
Expand Down
11 changes: 4 additions & 7 deletions src/js/models/NodeModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,10 @@ define(['jquery', 'underscore', 'backbone'],
//Log the error to the console
console.error("Couldn't get the DataONE Node info document: ", textStatus, errorThrown);

//Send this exception to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "exception", {
"exDescription": "Couldn't get the DataONE Node info document: " + textStatus + ", " + errorThrown + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": false
});
}
// Track the error
const message = `Couldn't get the DataONE Node info document: ` +
`${textStatus}, ${errorThrown}`;
MetacatUI.analytics?.trackException(message, null, false);

//Trigger an error on this model
thisModel.set("error", true);
Expand Down
28 changes: 12 additions & 16 deletions src/js/models/PackageModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1269,10 +1269,8 @@ define(['jquery', 'underscore', 'backbone', 'uuid', 'md5', 'rdflib', 'models/Sol
a.remove();
}

//Send this exception to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "event", "download", "Download Package", model.get("id"));
}
// Track this event
MetacatUI.analytics?.trackEvent("download", "Download Package", model.get("id"))

model.trigger("downloadComplete");
};
Expand All @@ -1284,19 +1282,17 @@ define(['jquery', 'underscore', 'backbone', 'uuid', 'md5', 'rdflib', 'models/Sol
}
};

xhr.onerror = function(e){
model.trigger("downloadError");

//Send this exception to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "exception", {
"exDescription": "Download package error: " + (e || "") +
" | Id: " + model.get("id") + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": true
});
}
};
xhr.onerror = function (e) {
model.trigger("downloadError");

// Track this event
MetacatUI.analytics?.trackEvent(
"download",
"Download Package",
model.get("id")
);

};
//Open and send the request with the user's auth token
xhr.open('GET', url);
xhr.responseType = "blob";
Expand Down
26 changes: 12 additions & 14 deletions src/js/models/SolrResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,25 +332,23 @@ define(['jquery', 'underscore', 'backbone'],
a.remove();
}

model.trigger("downloadComplete");

//Send this event to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "event", "download", "Download DataONEObject", model.get("id"));
}
model.trigger("downloadComplete");

// Track this event
MetacatUI.analytics?.trackEvent(
"download",
"Download DataONEObject",
model.get("id")
);
};

xhr.onerror = function(e){
model.trigger("downloadError");

//Send this exception to Google Analytics
if(MetacatUI.appModel.get("googleAnalyticsKey") && (typeof ga !== "undefined")){
ga("send", "exception", {
"exDescription": "Download DataONEObject error: " + (e || "") +
" | Id: " + model.get("id") + " | v. " + MetacatUI.metacatUIVersion,
"exFatal": true
});
}
// Track the error
MetacatUI.analytics?.trackException(
`Download DataONEObject error: ${e || ""}`, model.get("id"), true
);
};

xhr.onprogress = function(e){
Expand Down
133 changes: 133 additions & 0 deletions src/js/models/analytics/Analytics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* global define */
define(["backbone"], function (Backbone) {
/**
* @class Analytics
* @classdesc A model that connects with an analytics service to record user
* interactions with the app. This is a generic model that is meant to be
* extended for a specific analytics service.
* @classcategory Models/Analytics
* @extends Backbone.Model
* @constructs
* @since x.x.x
*/
var Analytics = Backbone.Model.extend(
/** @lends Analytics.prototype */
{
/**
* The name of this Model
* @name Analytics#type
* @type {string}
* @readonly
*/
type: "Analytics",

/**
* Default attributes for this model
* @type {object}
* @returns {object}
*/
defaults: function () {
return {};
},

/**
* Creates a new Analytics model
*/
initialize: function (attributes, options) {
this.setupAnalytics();
},

/**
* Set up the analytics service.
*/
setupAnalytics: function () {
return;
},

/**
* Get the key for the analytics service. This is the ID that is used to
* initialize the analytics service.
* @returns {string} The key for the analytics service
*/
getKey: function () {
return "";
},

/**
* Get the version number of the MetacatUI app.
* @returns {string} The version number of the MetacatUI app
*/
getVersion: function () {
return MetacatUI.metacatUIVersion || "unknown";
},

/**
* The main function for sending analytics events to the service.
* @type {function}
*/
track: null,

/**
* Check if analytics service is enabled and ready to use.
* @returns {boolean} True if the service is enabled in the app and the
* track method is available.
*/
ready: function () {
return false;
},

/**
* Given a message and an optional ID, create a description of an
* exception event that can be sent to an analytics service.
* @param {string} message - A description of the exception
* @param {string} id - The ID for the associated package or object
* @returns {string} A description of the exception event
*/
createExceptionDescription: function (message, id) {
const version = this.getVersion();
const sep = ` | `;
const desc = `${message}`;
if (id) desc += `${sep}Id: ${id}`;
desc += `${sep}v. ${version}`;
return desc;
},

/**
* Send the details of an exception event to an analytics service. This
* will automatically include the MetacatUI version number in the event
* details. The function will do nothing if an analytics service is not
* enabled.
* @param {string} message - A description of the exception
* @param {string} id - The ID for the associated package or object
* @param {boolean} fatal - Whether the exception was fatal
*/
trackException: function (message, id, fatal) {
return;
},

/**
* Send the details of an event to an analytics service. The function will
* do nothing if an analytics service is not enabled.
* @param {string} category - The category of the event
* @param {string} action - The action of the event
* @param {string} label - The label of the event
* @param {string} value - The value of the event
*/
trackEvent: function (category, action, label, value) {
return;
},

/**
* Send the details of a page view to an analytics service. The function
* will do nothing if an analytics service is not enabled.
* @param {string} path - The path of the page
* @param {string} title - The title of the page
*/
trackPageView: function (path, title) {
return;
},
}
);

return Analytics;
});
Loading

0 comments on commit 5840e42

Please sign in to comment.