Integrating Analytics

Codelab: Integrating Analytics

What is Google Analytics?

Google Analytics is a service that collects, processes, and reports data about an application's use patterns and performance. Adding Google Analytics to a web application enables the collection of data like visitor traffic, user agent, user's location, and so forth. This data is sent to Google Analytics servers where it is processed. The processed data is then reported to the developer and/or application owner. This information is accessible from the Google Analytics web interface (dashboard) and through the reporting API.

Why use it?

Using analytics tools gives developers valuable information about their application such as:

  • User's geographic location, user agent, screen resolution, and language
  • How long users spend on pages, how often they visit pages, and the order in which pages are viewed
  • What times users are visiting the site and from where they arrived at the site

Google Analytics is free, relatively simple to integrate, and customizable.

Creating an account

Google Analytics requires creating a Google Analytics account. An account has properties that represent individual collections of data. These properties have tracking IDs (also called property IDs) that identify them to Google Analytics. For example, an account might represent a company. One property in that account might represent the company's web site, while another property might represent the company's iOS app.

Accounts and Properties

If you only have one app, the simplest scenario is to create a single Google Analytics account, and add a single property to that account. That property can represent your app.

A Google Analytics account can be created from analytics.google.com.

If you already have a Google Analytics account

Create another one. Select the Admin tab. Under account, select your current Google Analytics account and choose create new account. A single Gmail account can have multiple (currently 100) Google Analytics accounts.

Adding an account

If you don't have a Google Analytics account

Select Sign up to begin creating your account. The account creation screen should look like this:

Creating an account

What would you like to track?

Websites and mobile apps implement Google Analytics differently. This document assumes a web app is being used. For mobile apps, see analytics for mobile applications.

Setting up your account

This is where you can set the name for your account, for example "PWA Training" or "Company X".

Setting up your property

A property must be associated with a website (for web apps). The website name can be whatever you want, for example "GA Code Lab Site" or "My New App". The website URL should be the URL where your app is hosted.

You can set an industry category to get benchmarking information later (in other words, to compare your app with other apps in the same industry). You can set your timezone here as well. You may also see data sharing options, but these are not required.

Once you have filled in your information, choose Get Tracking ID and agree to the terms and conditions to finish creating your account and its first property. This will take you to the tracking code page where you get the tracking ID and tracking snippet for your app.

Add analytics to your site

Once you have created an account, you need to add the tracking snippet to your app. You can find the tracking snippet with the following steps:

  1. Select the Admin tab.
  2. Under account, select your account (for example "PWA Training") from the dropdown list.
  3. Then under property, select your property (for example "GA Code Lab Site") from the dropdown list.
  4. Now choose Tracking Info, and then Tracking Code.

Finding the snippet

Your tracking ID looks like UA-XXXXXXXX-Y and your tracking code snippet looks like:

index.html

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]) \
.push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0]; \
a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script', \
'https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-XXXXXXXX-Y', 'auto');
  ga('send', 'pageview');

</script>

Your tracking ID is embedded into your tracking snippet. This snippet needs to be embedded into every page that you want to track.

When a page with the snippet loads, the tracking snippet script is executed. The IIFE ( Immediately Invoked Function Expression) in the script does two things:

  • Creates another script tag that starts asynchronously downloading analytics.js, the library that does all of the analytics work.
  • Initializes a global ga function, called the command queue.

The ga command queue is the main interface for using analytics.js. The command queue stores commands (in order) until analytics.js has loaded. Once analytics.js has loaded, the commands are executed sequentially. This functionality ensures that analytics can begin independent of the loading time of analytics.js.

Commands are added by calling ga(). The first argument passed is the command itself, which is a method of the analytics.js library. The remaining arguments are parameters for that method.

The next lines add two commands to the queue. The first creates a new tracker object. Tracker objects track and store data. When the new tracker is created, the analytics library gets the user's IP address, user agent, and other page information, and stores it in the tracker. From this info Google Analytics can extract:

  • User's geographic location
  • User's browser and operating system (OS)
  • Screen size
  • If Flash or Java is installed
  • The referring site

You can learn more about creating trackers in the documentation.

The second command sends a " hit". This sends the tracker's data to Google Analytics. Sending a hit is also used to note a user interaction with your app. The user interaction is specified by the hit type, in this case a "pageview". Since the tracker was created with your tracking ID, this data is sent to your account and property. You can learn more about sending data in the Google Analytics documentation.

The code so far provides the basic functionality of Google Analytics. A tracker is created and a pageview hit is sent every time the page is visited. In addition to the data gathered by tracker creation, the pageview event allows Google Analytics to infer:

  • The total time the user spends on the site
  • The time spent on each page and the order in which the pages are visited
  • Which internal links are clicked (based on the URL of the next pageview)

Debugging and development

Google Analytics offers the analytics.js library with a debug mode: analytics_debug.js. Using this version will log detailed messages to the console that break down each hit sent. It also logs warnings and errors for your tracking code. To use this version, replace analytics.js with analytics_debug.js (in all instances of your tracking snippet).

For more information

Google Analytics dashboard

All of the data that is sent to Google Analytics can be viewed in the Google Analytics dashboard (the Google Analytics web interface). For example, overview data is available by selecting Audience and then Overview (shown below).

From the overview page you can see general information such as pageview records, bounce rate, ratio of new and returning visitor, and other statistics.

Records overview

You can also see specific information like visitors' language, country, city, browser, operating system, service provider, screen resolution, and device.

Records details

Real time analytics

It's also possible to view analytics information in real time from the Real-Time tab. The Overview section is shown below:

Real-time navigation

If you are visiting your app in another tab or window, you should see yourself being tracked. The screen should look similar to this:

Real-time screen

These are only the basic aspects of the Google Analytics dashboard. There is an extensive set of features and functionality.

For more information

Custom events

Google Analytics supports custom events that allow for fine-grain analysis of user behavior.

For example, the following code will send a custom event:

main.js

ga('send', {
  hitType: 'event',
  eventCategory: 'products',
  eventAction: 'purchase',
  eventLabel: 'Summer products launch'
});

Here the hit type is set to 'event' and values associated with the event are added as parameters. These values represent the eventCategory, eventAction, and eventLabel. All of these are arbitrary, and used to organize events. Sending these custom events allow us to deeply understand user interactions with our site.

Event data can also be viewed in the Google Analytics dashboard. Real-time events are found in the Real-Time tab under Events, and should look like the following:

Real-time events

Here you can see events as they are occurring. You can view past events in the Google Analytics dashboard by selecting Behavior, followed by Events and then Overview:

Recorded events

For more information

Analytics and service worker

Service workers do not have access to the analytics command queue, ga, because the command queue is in the main thread (not the service worker thread) and requires the window object. You need to use the Measurement Protocol interface to send hits from the service worker.

This interface allows us to make HTTP requests to send hits, regardless of the execution context. This can be achieved by sending a URI containing your tracking ID and the custom event parameters (eventCategory, eventAction, and eventLabel) along with some required parameters (version number, client ID, and hit type) to the API endpoint (https://www.google-analytics.com/collect). Let's look at an example using the Measurement Protocol interface to send hits related to push events in the service worker.

A helper script, analytics-helper.js has the following code:

analytics-helper.js

// Set this to your tracking ID
var trackingId = 'UA-XXXXXXXX-Y';

function sendAnalyticsEvent(eventAction, eventCategory) {
  'use strict';

  console.log('Sending analytics event: ' + eventCategory + '/' + eventAction);

  if (!trackingId) {
    console.error('You need your tracking ID in analytics-helper.js');
    console.error('Add this code:\nvar trackingId = \'UA-XXXXXXXX-X\';');
    // We want this to be a safe method, so avoid throwing unless absolutely necessary.
    return Promise.resolve();
  }

  if (!eventAction && !eventCategory) {
    console.warn('sendAnalyticsEvent() called with no eventAction or ' +
    'eventCategory.');
    // We want this to be a safe method, so avoid throwing unless absolutely necessary.
    return Promise.resolve();
  }

  return self.registration.pushManager.getSubscription()
  .then(function(subscription) {
    if (subscription === null) {
      throw new Error('No subscription currently available.');
    }

    // Create hit data
    var payloadData = {
      // Version Number
      v: 1,
      // Client ID
      cid: subscription.endpoint,
      // Tracking ID
      tid: trackingId,
      // Hit Type
      t: 'event',
      // Event Category
      ec: eventCategory,
      // Event Action
      ea: eventAction,
      // Event Label
      el: 'serviceworker'
    };

    // Format hit data into URI
    var payloadString = Object.keys(payloadData)
    .filter(function(analyticsKey) {
      return payloadData[analyticsKey];
    })
    .map(function(analyticsKey) {
      return analyticsKey + '=' + encodeURIComponent(payloadData[analyticsKey]);
    })
    .join('&');

    // Post to Google Analytics endpoint
    return fetch('https://www.google-analytics.com/collect', {
      method: 'post',
      body: payloadString
    });
  })
  .then(function(response) {
    if (!response.ok) {
      return response.text()
      .then(function(responseText) {
        throw new Error(
          'Bad response from Google Analytics:\n' + response.status
        );
      });
    } else {
      console.log(eventCategory + '/' + eventAction +
        'hit sent, check the Analytics dashboard');
    }
  })
  .catch(function(err) {
    console.warn('Unable to send the analytics event', err);
  });
}

The script starts by creating a variable with your tracking ID (replace UA-XXXXXXXX-Y with your actual tracking ID). This ensures that hits are sent to your account and property, just like in the analytics snippet.

The sendAnalyticsEvent helper function starts by checking that the tracking ID is set and that the function is being called with the correct parameters. After checking that the client is subscribed to push, the hit data is created in the payloadData variable:

analytics-helper.js

var payloadData = {
  // Version Number
  v: 1,
  // Client ID
  cid: subscription.endpoint,
  // Tracking ID
  tid: trackingId,
  // Hit Type
  t: 'event',
  // Event Category
  ec: eventCategory,
  // Event Action
  ea: eventAction,
  // Event Label
  el: 'serviceworker'
};

Again, the version number, client ID, tracking ID, and hit type parameters are required by the API. The eventCategory, eventAction, and eventLabel are the same parameters that we have been using with the command queue interface.

Next, the hit data is formatted into a URI with the following code:

analytics-helper.js

var payloadString = Object.keys(payloadData)
.filter(function(analyticsKey) {
  return payloadData[analyticsKey];
})
.map(function(analyticsKey) {
  return analyticsKey + '=' + encodeURIComponent(payloadData[analyticsKey]);
})
.join('&');

Finally the data is sent to the API endpoint (https://www.google-analytics.com/collect) with the following code:

analytics-helper.js

return fetch('https://www.google-analytics.com/collect', {
  method: 'post',
  body: payloadString
});

This sends the hit with the Fetch API using a POST request. The body of the request is the hit data.

Now we can import the helper script functionality into a service worker by adding the following code to the service worker file:

sw.js

self.importScripts('path/to/analytics-helper.js');

Where path/to/analytics-helper.js is the path to the analytics-helper.js file. Now we should be able to send custom events from the service worker by making calls to the sendAnalyticsEvent function. For example, to send a custom "notification close" event, we could add code like this to the service worker file:

sw.js

self.addEventListener('notificationclose', function(event) {
  event.waitUntil(
    sendAnalyticsEvent('close', 'notification')
  );
});

Observe that we have used event.waitUntil to wrap an asynchronous operation. If unfamiliar, event.waitUntil extends the life of an event until the asynchronous actions inside of it have completed. This ensures that the service worker will not be terminated pre-emptively while waiting for an asynchronous action to complete.

For more information

Offline analytics

With the help of service workers, analytics data can be stored when users are offline and sent at a later time when they have reconnected based on an npm package.

Install the package with the following command-line command:

npm install sw-offline-google-analytics

This imports the node module.

In your service worker file, add the following code:

sw.js

importScripts('path/to/offline-google-analytics-import.js');
goog.offlineGoogleAnalytics.initialize();

Where path/to/offline-google-analytics-import.js is the path to the offline-google-analytics-import.js file in the node module. This will likely look something like:

sw.js

node_modules/sw-offline-google-analytics/offline-google-analytics-import.js

We import and initialize the offline-google-analytics-import.js library. This library adds a fetch event handler to the service worker that only listens for requests made to the Google Analytics domain. The handler attempts to send Google Analytics data first by network requests. If the network request fails, the request is stored in IndexedDB. The requests are then sent later when connectivity is re-established.

You can test this by simulating offline behavior, and then firing hit events. You will see an error in the console since you are offline and can't make requests to Google Analytics servers. Then check IndexedDB. Open offline-google-analytics. You should see URLs cached in urls (you may need to click the refresh icon inside the indexedDB interface). These are the stored hits.

Offline hits

Now disable offline mode, and refresh the page. Check IndexedDB again, and observe that the URL is no longer cached (and has been sent to analytics servers).

For more information

Further reading