The #ChromeDevSummit site is live, happening Nov 12-13 in San Francisco, CA
Check it out for details and request an invite. We'll be diving deep into modern web tech & looking ahead to the platform's future.

Lab: Integrating Analytics

Overview

This lab shows you how to integrate Google Analytics into your Progressive Web Apps. You'll learn how use analytics in service workers, and how to track offline events.

What you'll learn

  • How to create a Google Analytics account
  • How to create a Google Firebase account
  • How to integrate Google Analytics into a web app
  • How to add and track custom events (including push notifications)
  • How to use Google Analytics with service workers
  • How to use analytics even when offline

What you should know

What you will need

1. Get set up

If you have not downloaded the repository and installed the LTS version of Node.js, follow the instructions in Setting up the labs.

Navigate into the google-analytics-lab/app/ directory and start a local development server:

cd google-analytics-lab/app
npm install
node server.js

You can terminate the server at any time with Ctrl-c.

Open Chrome and navigate to localhost:8081/google-analytics-lab/app/.

In the browser, you should be prompted to allow notifications. If the prompt does not appear, then manually allow notifications. You should see a permission status of "granted" in the console.

You should also see that a service worker registration is logged to the console.

The app for this lab is a simple web page that has some push notification code.

Open the google-analytics-lab/app/ folder in your preferred text editor. The app/ folder is where you will be building the lab.

This folder contains:

  • pages/ folder contains sample resources that we use in experimenting:
  • page-push-notification.html
  • other.html
  • images/ folder contains images to style our app and notifications
  • index.html is the main HTML page for our sample site/application
  • styles/main.css is the app's CSS
  • js/main.js is the main JavaScript for the app
  • js/analytics-helper.js is an empty helper file
  • sw.js is the service worker file
  • manifest.json is the manifest for push notifications
  • package-lock.json & package.json track app dependencies (the only dependencies in this case are for the local development server)
  • server.js is a local development server for testing

Currently, main.js requests notification permission and registers a service worker, sw.js. main.js also contains functions for subscribing and unsubscribing for push notifications. We will address that later (subscribing to push isn't yet possible because we haven't registered with a push service).

sw.js contains listeners for push events and notification events.

Test the notification code by using developer tools to send a push notification.

A notification should appear on your screen. Try clicking it. It should take you to a sample page. You can see the logic for this behavior in sw.js in the notificationclick and push event handlers.

For more information

You can learn more about web push notifications in the Push Notifications lab and in the Web Fundamentals documentation.

2. Create a Google Analytics account

In a separate tab or window, navigate to analytics.google.com. Sign in with your Gmail account, and follow the step that matches your status:

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

Follow the instructions below to set up your new Analytics account.

If you don't have a Google Analytics account:

Select Sign up to begin creating your account.

Follow the instructions below to set up your new Analytics account.

Creating your account

The account creation screen should look like this:

Creating an account

What would you like to track?

Choose Website.

Setting up your account

Enter an account name (for example "PWA Training").

Setting up your property

The property must be associated with a site. We will use a mock GitHub Pages site.

  1. Set the website name to whatever you want, for example "GA Lab Site".
  2. Set the website URL to USERNAME.github.io/google-analytics-lab/, where USERNAME is your GitHub username (or just your name if you don't have a GitHub account). Set the protocol to https://.
  3. Select any industry or category.
  4. Select your timezone.
  5. Unselect any data sharing settings.
  6. Then choose Get Tracking ID and agree to the terms and conditions.

Explanation

Your account is the top most level of organization. For example, an account might represent a company. An account has properties that represent individual collections of data. One property in an account might represent the company's web site, while another property might represent the company's iOS app. These properties have tracking IDs (also called property IDs) that identify them to Google Analytics. You will need to get the tracking ID to use for your app.

For more information

3. Get your tracking ID and snippet

You should now see your property's tracking ID and tracking code snippet.

If you lost your place:

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

Finding the snippet

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

  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-114028926-1"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag() {dataLayer.push(arguments);}
    gtag('js', new Date());

    gtag('config', 'UA-XXXXXXXXX-Y');
  </script>

Copy this script (from the Google Analytics page) and paste it in the bottom of index.html and pages/other.html. Save the scripts and refresh the app page (you can close the page-push-notification.html page that was opened earlier from the notification click).

Now return to the Google Analytics site. Examine the real time data section by selecting Real-Time and then Overview:

Real-time navigation

You should see yourself being tracked. The screen should look similar to this:

Real-time screen

In the Google Analytics dashboard, the Active Page indicates which page is being viewed. Back in the app, click the link to Other page to navigate to the other page. Then check the Google Analytics dashboard and examine Active Page again. It should now show /pages/other.html (this might take a few seconds).

Explanation

When a page loads, the tracking snippet script is executed.

At a high level, that analytics snippet gathers data from the user's IP address, user agent, and other page information, such as:

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

This data is then sent as a pageview hit to your Google Analytics property (specified by your tracking ID).

The real-time section of the Google Analytics dashboard shows the hit received from this script execution, along with the page (Active Page) that it was executed on.

The code so far provides the minimum functionality of Google Analytics. From the data gathered by the analytics library, the pageview event allows Google Analytics to infer information such as:

  • 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)

For more information

Viewing user data

We are using real-time viewing because we have just created this app. Normally, records of past data would also be available. You can view this by selecting Audience and then Overview.

Here you can see general information such as pageview records, bounce rate, ratio of new and returning visitors, 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

For more information

4. Add analytics events

Google Analytics supports marking "events" that allow fine grain analysis of user behavior.

In main.js, replace the favorite function with the following:

const favorite = () => {
  gtag('event', 'favorite', {
    'event_category': 'photos',
    'event_label': 'cats'
  });
};

Save the script and refresh the app's main page (index.html). Click Favorite.

Return to the Real-Time reporting section of the Google Analytics dashboard. Instead of selecting Overview, select Events. Do you see the custom event? (If not, try clicking Favorite again.)

Real-time events

Explanation

We can use the gtag.js API to send specific events to our property, which can be organized with categories and labels. In this example, a custom event with the action "favorite" is fired. The event is organized with the "photos" category and "cats" label.

Google Analytics also supports default events, which are generally recommended because they facilitate interoperability with other tools. For simplicity, we'll stick to custom events for now, as default events require specific parameters.

You can view past events in the Google Analytics dashboard by selecting Behavior, followed by Events and then Overview. However your account won't yet have any past events to view (because you just created it).

Recorded events

Learn more

Event parameters reference

5. Showing push notifications

Let's use a custom event to let us know when users subscribe to push notifications.

5.1 Create a project on Firebase

First we need to add push subscribing to our app. To subscribe to the push service in Chrome, you need to create a project on Firebase.

  1. In the Firebase console, select Add project.
  2. Supply a project name and click Create Project.
  3. Click the Settings (gear) icon next to your project name in the navigation pane, and select Project Settings.
  4. Select the Cloud Messaging tab. You can find your Server key and Sender ID in this page. Save these values.

Replace YOUR_SENDER_ID in the manifest.json file with the Sender ID of your Firebase project. The manifest.json file should look like this:

{
  "name": "Google Analytics lab",
  "gcm_sender_id": "YOUR_SENDER_ID"
}

Save the file. Refresh the app and click Subscribe. The browser console should indicate that you have subscribed to push notifications.

Explanation

Chrome uses Firebase Cloud Messaging (FCM) to route its push messages. All push messages are sent to FCM, and then FCM passes them to the correct client.

5.2 Add custom analytics

Now we can add custom analytics events for push subscriptions.

In the subscribe function, add the following code to mark subscription events:

gtag('event', 'subscribe', {
  'event_category': 'push',
  'event_label': 'cat updates'
});

Similarly, add the following code to mark unsubscribe events in the unsubscribe function:

gtag('event', 'unsubscribe', {
  'event_category': 'push',
  'event_label': 'cat updates'
});

Save the script and refresh the app. Now test the Subscribe and Unsubscribe buttons. Confirm that you see the custom events in the the Google Analytics dashboard.

Optional: Add analytics hits for the catch blocks of the subscribe and unsubscribe functions. In other words, add analytics code to record when users have errors subscribing or unsubscribing. Make sure the event's action (the second argument to gtag) is distinct, for example subscribe-err). Then manually block notifications in the app by clicking the icon next to the URL and revoking permission for notifications. Refresh the page and test subscribing, you should see an event fired for the subscription error in the Google Analytics dashboard. Remember to restore notification permissions when you are done.

Explanation

Adding these custom events lets us track how often users are subscribing and unsubscribing to our push notifications, and if they are experiencing errors in the process.

6. Using analytics in the service worker

The gtag.js analytics library requires access to the Window object. Service workers don't have access to Window, so we will need to use a separate API to send analytics hits from the service worker. The Measurement Protocol API is a low level interface that allows developers to send analytics data directly as HTTP requests.

6.1 Use the Measurement Protocol interface

In analytics-helper.js, add the following code to specify your property's tracking ID (use your analytics tracking ID instead of UA-XXXXXXXX-Y):

// Set this to your tracking ID: UA-XXXXXXXX-Y
const trackingId = null;

Below the tracking ID variable, add the following Measurement Protocol helper function:

const sendAnalyticsEvent = (eventAction, eventCategory) => {

  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:\nconst 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(subscription => {
    if (subscription === null) {
      throw new Error('No subscription currently available.');
    }

    // Create hit data
    const 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
    const payloadString = Object.keys(payloadData)
    .filter(analyticsKey => {
      return payloadData[analyticsKey];
    })
    .map(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(response => {
    if (!response.ok) {
      return response.text()
      .then(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);
  });
};

Save the script.

Explanation

We start by creating a variable with your tracking ID. The Measurement Protocol needs this to identify your 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 analytics data is created in the payloadData variable:

const 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'
};

The version number, client ID, tracking ID, and hit type parameters are required by the API. The event category, event action, and event label are the same parameters that we have been using with the gtag.js interface.

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

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

Finally, the data is sent to the API endpoint with the following code:

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

6.2 Send hits from the service worker

Now that we can use the Measurement Protocol interface to send hits, let's add custom events to the service worker.

Import the helper file at the top of the service worker (sw.js):

importScripts('js/analytics-helper.js');

In the notificationclose listener, add the following code to send notification close events:

e.waitUntil(
  sendAnalyticsEvent('close', 'notification')
);

In the notificationclick listener, add the following code to mark click events:

sendAnalyticsEvent('click', 'notification')

Finally, add the following code to the push listener:

sendAnalyticsEvent('received', 'push')

Save the script. Refresh the page to install the new service worker. Then close and reopen the app to activate the new service worker (remember to close all tabs and windows running the app).

Click Subscribe to subscribe the new service worker to push notifications.

Now try these experiments and check the Google Analytics dashboard for each:

  1. Trigger a push notification.
  2. Click the notification, and note what happens.
  3. Trigger another notification and then close it.

Do you see the events on Google Analytics?

Explanation

We start by using ImportScripts to import the analytics-helper.js file with our sendAnalyticsEvent helper function. This function is used send custom events at appropriate places (such as when push events are received, or notifications are interacted with). The eventAction and eventCategory that we want to associate with the event are passed in as parameters.

7. Use analytics offline

What can you do about sending analytics hits when your users are offline? Analytics data can be stored when users are offline and sent at a later time when they have reconnected.

Fortunately, Workbox has a module that supports offline analytics.

In the top of the service worker, add the following code to import and instantiate the analytics module:

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.4.1/workbox-sw.js');

workbox.googleAnalytics.initialize();

Save the script. Update the service worker by refreshing the page and closing and reopening the app (remember to close all tabs and windows running the app).

Refresh the page again. Simulate offline mode by terminating the node server with Ctrl + c and turning off your machine's wifi.

Then click Favorite to fire a custom analytics event.

You will see an error in the console because we are offline and can't make requests to Google Analytics servers.

Now check IndexedDB. Open the requests store in the workbox-background-sync database. You should see a URL cached. (You may need to refresh IndexedDB.)

Offline hits

Now restart the server with node server.js and turn wifi back on. Wait a few moments and check IndexedDB again (you'll most likely need to refresh the IndexedDB UI), and observe that the URL is no longer cached.

Now check the Google Analytics dashboard. You should see the custom event!

Explanation

The Workbox analytics module adds a fetch event handler to the service worker that only listens for requests made to the Google Analytics endpoint. The handler attempts to send Google Analytics data just like we have done so far, by network requests. If the network request fails, the request is stored in IndexedDB and queued with background sync. The requests are then sent later when connectivity is re-established. You can learn more in the Workbox Google Analytics documentation.

So far we have successfully enabled offline analytics. But retried requests are currently indistinguishable from requests that succeeded while online. This means you'll receive all the interaction data from offline users, but you won't be able to tell which interactions occurred while the user was offline. To address this, you can annotate data that is sent in retried requests.

One solution is to modify offline requests with a custom dimension. This task is outside the scope of the lab, but you can see an example in the Workbox Google Analytics documentation. If you're using the gtag.js library instead of analytics.js, you'll want to read about custom dimensions with gtag.js.

Congratulations!

You now know how to integrate Google Analytics into your progressive web apps. You also learned how to use analytics with service workers and to track offline events.

Resources