The `analytics` library

Description

In order to implement different analytics services with Frontity, such as Google Analytics, Google Tag Manager or ComScore, we need a library to support all of them.

User Stories

As a Frontity user
I want to send pageviews to different analytics services with the same action
so that I can track how many people are visiting my web in more than one service in an easy way

As a Frontity user
I want to send events to different analytics services with the same action
so that I can track how people behave at my web in more than one service in an easy way

As a Frontity user
I want to prevent one or more services of sending events
so that I don’t send additional data if I don’t want

As a Frontity user
I want to prevent one or more services of sending pageviews
so that I don’t send additional data if I don’t want

Implementation

I am a bit worried that the current beta of frontity/analytics is so coupled with Google Analytics implementation of events:

type Event = {
  category: string;
  action: string;
  label?: string;
};

I don’t think we should use that event interface.

Most analytics services use this other interface:

type Event = {
  name: string;
  payload: object;
};

For example:

  • Mixpanel:
mixpanel.track("Played song", {"genre": "hip-hop"});
  • Amplitude:
var eventProperties = {
 'color': 'blue',
 'age': 20,
 'key': 'value'
};
amplitude.getInstance().logEvent('EVENT_TYPE', eventProperties);
  • KissMetrics:
/* Records an event "Signed Up" with additional properties
 * (Plan & Amount) plus related values (Pro & 99.95)
 */
_kmq.push(['record', 'Signed Up', {'Plan':'Pro', 'Amount':99.95}]);
  • Google Tag Manager:
 window.dataLayer.push({
   'event': 'registrationComplete',
   'registrationCountry': 'United States',
   'plan': 'Premium'
 });

cc: @SantosGuillamot @David

I totally agree. It seems the standard so I guess the best option will be to follow it. Segment, which connects with most of the Analytics packages we may build, also uses that pattern. Maybe our solution should be based on it.

https://segment.com/docs/connections/sources/catalog/libraries/server/node-js/#track

Oh, checking what Segment does is quite interesting actually.

Do you know how do they map their event interface with the Google Analytics one? That’s the only thing that it’s not clear to me how to do:

// From
{
  event: "name of the event",
  payload: {
    anyProp: "..."
  }
}
// to
{
  action: "...",
  category: "...",
  label: "..."
}

Oh, here it is: https://segment.com/docs/connections/destinations/catalog/google-analytics/#track

{
  "userId": "12345",
  "action": "track",
  "event": "Logged In",
  "properties": {
    "category": "Account",
    "label": "Premium",
    "value": 50
  }
}

It looks like the category, label and value props only work when you explicitly declare them in the payload. I guess that’s ok.

Yes, I think this could be the correct approach. If each library is well documented it shouldn’t be a problem, it’s pretty clear and easy.

I want to propose several changes to the analytics library for the release of the v1 version.

Implementation proposal for v1

Remove send from action names

Rename actions.analytics.sendPageview() and actions.analytics.sendEvent() to just actions.analytics.pageview() and actions.analytics.event().

Rename page argument to link

The page is not consistent with the rest of Frontity. We should rename it to link:

actions.analytics.pageview({
  link: "/current-url",
  title: "The current title"
});

Refactor event to use a payload

Events with action, label and category are not used anywhere but in Google Analytics so we should get rid of that and move to the standard event name + payload:

actions.analytics.event({
  name: "Some event",
  payload: {
    someProp: "Some payload"
  }
};

Replace namespaces array by two objects

Right now, the analytics libraries use an array called state.analytics.namespaces where they can register themselves to be used by the actions.analytics.pageview and actions.analytics.event actions.

The problem with arrays is that they cannot be changed from the state because arrays are merged together. There is no way for a user to deactivate one of the analytics implementation using frontity.settings.js.

We should change that array for two objects, one for pageviews and another for events.

export default {
  state: {
    analytics: {
      pageviews: {},
      events: {}
    }
  }
};

Each analytics package can register itself like this:

export default {
  state: {
    analytics: {
      pageviews: {
        googleAnalytics: true
      },
      events: {
        googleAnalytics: true
      }
    }
  },
  actions: {
    googleAnalytics: {
      pageview: () => {
        /* ... */
      },
      event: () => {
        /* ... */
      }
    }
  }
};

That way, if a user wants to turn one implementation off, they can do so in their frontity.settings.js file:

const settings = {
  packages: [
    {
      name: "@frontity/google-analytics",
      state: {
        events: {
          googleAnalytics: false
        }
      }
    }
  ]
};

In the future, this object structure could be used to add options or priorities to each action (if that ever makes sense).

Issues

  • Remove send from action names
  • Rename page argument to link
  • Refactor event to use a payload
  • Replace namespaces array by two objects

EDIT: this is an old message but was posted in the wrong thread, so I’ve moved it here.


I’ve done a list of possible cases before writing e2e tests for the @frontity/analytics library. Feel free to suggest more cases or change anything.

A pageview should be sent

  • in the first CSR.

  • when state.router.link has changed and

    1. the title changes, right after data.isReady has become true:
    // 1. -------------------------
    data.isReady = true;
    <Head>
      <title>Old Title - My site</title>
    </Head>
    
    // Change current link.
    actions.router.set("/new-link");
    
    // Fetch the new URL.
    data.isReady = false;
    data.isReady = true;
    
    // When data.isReady render the new title.
    <Head>
      <title>New Title - My site</title>
    </Head>
    
    // A pageview should be sent.
    actions.analytics.sendPageview()
    
    1. data.isReady is already true but no pageview has been sent yet:

    Note: this case doesn’t happen right now because data.isReady will always change to false right after running actions.source.fetch, even though data is already fetched.

    // 2. ------------------------
    data.isReady = true;
    <Head>
      <title>Old Title - My site</title>
    </Head>
    
    // Change current link.
    actions.router.set("/new-link");
    
    // New URL is already fetched.
    data.isReady = true;
    
    // A new title is rendered
    <Head>
      <title>New Title - My site</title>
    </Head>
    
    // A pageview should be sent.
    actions.analytics.sendPageview()
    
    1. data.isReady remains true, the title won’t and no pageview has been sent yet.

    Note: this case doesn’t happen right now because data.isReady will always change to false right after running actions.source.fetch, even though data is already fetched.

    // 3. ------------------------
    data.isReady = true;
    <Head>
      <title>My site</title>
    </Head>
    
    // Change current link.
    actions.router.set("/new-link");
    
    // New URL is already fetched.
    data.isReady = true;
    
    // The same title is rendered.
    <Head>
      <title>My site</title>
    </Head>
    
    // A pageview should be sent.
    actions.analytics.sendPageview()
    

A pageview must not be sent

  • when the title changes while data.isReady is false; these changes should be ignored.

  • if the title changes after a pageview has been sent but state.router.link is the same.

  • if any other property of <head> changes but neither the link nor the title.

The first stable version for the @frontity/analytics library is ready and is about to be released! :tada:

Its final implementation follows what was specified in the v1 proposal.

There are breaking changes! Here’s a summary of the changes compared to the previous version:

  • In state.analytics, the namespace array is replaced by two objects that will contain the namespaces with a boolean value: pageviews and events. This allow users to have more control over which analytics packages should send pageviews or events, and it can also be configured in frontity.settings.js.

  • In actions.analytics, the sendPageview and sendEvent actions are renamed to pageview and event respectively.

  • Pageview page property is now link.

  • Event event property is now name.

2 Likes

This has been included in the latest release :tada: : Read the full Release Announcement.