Repository URL to install this package:
|
Version:
1.7.2-rc.3 ▾
|
| dist |
| package.json |
| README.md |
@doodle/tracking provides declarative and imperative APIs for tracking, both centered around the same interface: a Tracking Intent, which allows handling multiple tracking destination services, with mappers to each expected format. For now there is support for:
To track declaratively, pass a tracking intent to getTrackingDataAttrs and spread the returned value over your clickable element. A global click listener will pick up resulting data attributes and dispatch accordingly.
When a declarative approach is not possible, invoke analytics.track with a tracking intent directly. For example when tracking from a Redux Saga or after a specific widget behavior.
Each tracking intent contains 1 or more of the following top-level keys:
Below is the shape for each top-level field. Mandatory ones are in bold:
| Property | Type | Description |
|---|---|---|
| event | string | The name of the event you are tracking, following Doodle Data Layer Taxonomy, e.g. 'Click Compare Plans', 'Click FAQs' or 'Start Trial' |
| properties | Object | Custom event properties |
| properties.category | string | A label for the track event, usually a page name like 'Checkout' or 'Pricing Page' |
| properties.description | string | A summary of the track event |
| type | string | Default: 'user Interaction'. Another valid value: 'conversion' |
| Property | Type | Description |
|---|---|---|
| userId | string | Id of the user in Doodle |
| properties | Object | A mapping of properties you know about the user. Things like email, name or friends |
| Property | Type | Description |
|---|---|---|
| name | string | The name of the page |
| properties | Object | Custom event properties |
| properties.path | string | Path portion of the URL of the page. Equivalent to canonical path which defaults to location.pathname from the DOM API |
| properties.title | string | Title of the page. Equivalent to document.title from the DOM API |
| properties.url | string | Full URL of the page. First we look for the canonical url. If the canonical url is not provided, we use location.href from the DOM API |
| properties.referrer | string | Full URL of the previous page. Equivalent to document.referrer from the DOM API |
| properties.category | string | The category of the page. Useful for cases where many pages might live under a single category. |
Finally, a tracking intent may contain an options top-level key:
| Property | Type | Description |
|---|---|---|
| autoTracking | boolean | Default: true. Controls automated declarative tracking behavior for a specific tracked item |
| services | Object | Controls disabling specific services |
| services.amplitude | boolean | Default: true. Dispatches tracking data to Amplitude |
| services.doodleDataLayer | boolean | Default: true. Dispatches tracking data to Doodle's Data Layer |
| services.ga | boolean | Default: true. Dispatches tracking data to Google Analytics |
If you do not have it yet, add Gemfury's private registry configuration to your project:
echo "registry=https://npm-proxy.fury.io/mfsTqYdDz3bsKFQJuMAR/tmf/" >> .npmrc
Install with yarn add @doodle/tracking
In the client side initialization of your application, insert a call to API.init. That is usually from where you run your rootSaga, e.g. in Billing that's src/client.js. You will want to make the Amplitude key secret available at runtime as an environment variable then use it like so:
import { API } from '@doodle/tracking'; export const analytics = API.init({ clientId: 'web-scheduling-experience', // or web-billing (following kubernetes service name) env: { amplitudeApiKey: process.env.AMPLITUDE_API_KEY, // needed when Amplitude service is enabled svcDataLayerApi: process.env.SVC_DATA_LAYER_API, // needed when Doodle Data Layer service is enabled doodleEnv: process.env.DOODLE_ENV, // needed when Google Analytics service is enabled nodeEnv: process.env.NODE_ENV, // needed when Google Analytics service is enabled }, services: { amplitude: true, doodleDataLayer: true, ga: false }, // enable or disable specific tracking services });
Add a identify call on the authentication step of your app. This can be done by taking the @doodle/user/LOAD_USER Redux action in projects that adopt @doodle/users-api-connector, or a dependent app action like in Dashboard:
function* onUserLoaded() { const id = yield select(state => state.user.data.id); yield apply(analytics, analytics.identify, [{ trackingIntent: { identify: { userId: id } } }]); } function* watchUserLoaded() { yield takeLatest(UserActionTypes.USER_LOADED, onUserLoaded); }
After that all tracking calls will be embedded with that user id.
NOTE: After identifying, if your app logs out without reloading the page you need to call
analytics.reset()to avoid tracking with a wrong user id.
NOTE: In case the data to be tracked is dynamic, e.g. when we track parts of the current state of the application, it is preferrable to go with the imperative API. This will avoid DOM re-renders caused by the update of the data tracking attributes. However for cases like the below, where the tracked data is static, the declarative approach may be more desirable.
For track:
import { getTrackingDataAttrs } from '@doodle/tracking'; const MyCTA = () => { const trackingIntent = { track: { event: 'Click Trial Button', type: 'user Interaction', properties: { category: 'Pricing Page', description: 'User clicks on "Start free Trial"', 'Trial Premium Plan': 'Starter', }, }, }; return ( <Button {...getTrackingDataAttrs(trackingIntent)}> <span>Start free Trial</span> </Button> ); }
For identify:
import { getTrackingDataAttrs } from '@doodle/tracking'; const MyPageWrapper = () => { const trackingIntent = { identify: { userId: '123456' }, }; return ( <article {...getTrackingDataAttrs(trackingIntent)}> ... </article> ); }
For page (WIP):
import { getTrackingDataAttrs } from '@doodle/tracking'; const MyPageWrapper = () => { const trackingIntent = { page: { name: 'Pricing Page', properties: { path: '/premium', title: 'Pricing Page', url: 'http://doodle.com', }, }, }; return ( <article {...getTrackingDataAttrs(trackingIntent)}> ... </article> ); }
At each module you need to track, first import the analytics instance you have initialized in your application startup. It has 3 methods you can use: analytics.track, analytics.page and analytics.identify.
Each accepts as first argument an object containing either a trackingIntent or a trackingEl key. If both are provided, the intent is preferred and will be used instead of the element's data attributes when mapping the tracking data for dispatch.
Below are three examples with trackingIntent and one with trackingEl:
import { analytics } from '../../analytics'; const MyCTA = () => { const trackingIntent = { track: { event: 'Click Trial Button', type: 'user Interaction', properties: { category: 'Pricing Page', description: 'User clicks on "Start free Trial"', 'Trial Premium Plan': 'Starter', }, }, }; const handleClick = trackingIntent => event => { analytics.track({ trackingIntent }); } return ( <Button onClick={handleClick(trackingIntent)}> <span>Start free Trial</span> </Button> ); }
In the second example we call analytics.identify in a class component. Note this time we use <form>'s onSubmit prop to ensure we do not track in case of failed validations.
import { analytics } from '../../analytics'; class MyCTA extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit() { const trackingIntent = { identify: { userId: 'doodleUserId', properties: { isOnExperimentWEB3000: true, }, }, }; analytics.identify({ trackingIntent }); } render() { return ( <form onSubmit={this.handleSubmit}> <Button><span>Start free Trial<span></Button> </form> ) } }
The third example is with a saga worker:
import { call } from 'redux-saga/effects'; import { analytics } from '../../analytics'; // You may want to group all tracking intents in a separate module const onMyActionTrackingIntent = { track: { event: 'Click Trial Button', type: 'user Interaction', properties: { category: 'Pricing Page', description: 'User clicks on "Start free Trial"', 'Trial Premium Plan': 'Starter', }, }, }; export function* onMyAction() { try { yield call(analytics.track, { trackingIntent: onMyActionTrackingIntent }); } catch (error) { Sentry.captureException(error); } } export function* watchMyAction(options) { yield takeLatest(MyActionTypes.MY_ACTION, onMyAction, options); } export default function* trackingSaga(options = {}) { yield all([ call(watchMyAction, options), ]); }
Alternatively, we can pass analytics.track a trackingEl (which is a DOMElement), and optionally an event so it can manage cancellation (preventDefault) and await completion of the tracking call before redirecting.
Although less direct, this approach may be preferred to resemble other declarative trackers by also storing the tracking data in the DOM, which hopefully helps towards faster debugging and QA experiences.
NOTE: Event cancellation is only needed for IE 11. All other supported browsers implement Beacon API, which is a non-blocking interface for POST requests, so @doodle/tracking does not need to await before redirecting.
import { getTrackingDataAttrs } from '@doodle/tracking'; import { analytics } from '../../analytics'; const MyCTA = () => { const trackingIntent = { options: { // we disable the global listener from automatically tracking this intent, so we can track it conditionally autoTracking: false, } track: { event: 'Click Trial Button', type: 'user Interaction', properties: { category: 'Pricing Page', description: 'User clicks on "Start free Trial"', 'Trial Premium Plan': 'Starter', }, }, }; const handleClick = event => { if (someCondition) { analytics.track({ trackingEl: event.currentTarget, event }); } } return ( <Button onClick={handleClick}> <span>Start free Trial</span> </Button> ); }
Reference:
A Doodle Data Layer's track field is dispatched as an Amplitude's AmplitudeClient.logEvent call:
| Doodle Data Layer | Amplitude |
|---|---|
| event | eventName |
| type | properties['Event Type'] |
| properties.category | properties.EventCategory |
| properties.description | properties.EventDescription |
| properties['Custom property'] | properties['Custom property'] |
A Doodle Data Layer's identify field is dispatched as one or multiple amplitude-js' Identify.set call:
| Doodle Data Layer | Amplitude |
|---|---|
| event | eventName |
| properties | properties |
No mappings for Amplitude, which does not support page calls directly.
Reference:
A Doodle Data Layer's track field is dispatched as a Google Analytics' event. Note value should always be a number:
| Doodle Data Layer | Google Analytics | Short description | | ----------------- | ---------------- | | event | eventAction | Mandatory, string | | type | eventCategory | Optional, string | | properties.category | eventPage | Optional, string | | properties.label | eventLabel | Optional, string | | properties.name | eventName | Optional, string | | properties.value | eventValue | Optional, must be a number |
No mappings for Google Analytics, which does not support identify calls directly.
A Doodle Data Layer's page field is dispatched as a Google Analytics' page. For real page views:
| Doodle Data Layer | Google Analytics |
|---|---|
| properties.category + properties.name | title |
| properties.location | collected automatically by GA client |
| properties.page | collected automatically by GA client |
For "virtual page views", i.e. those where navigation occurs without a location bar URL change:
| Doodle Data Layer | Google Analytics |
|---|---|
| properties.path | page |
| properties.title | title |
| properties.url | location |
This library was architected to be more developer-friendly by not using Sagas and not distributing transpiled source code, which allows for a regular usage of yarn link.
This library is published to the registry using Tagflow.
git tag pub.1.0.0-rc.0 git push origin pub.1.0.0-rc.0
git tag pub.1.0.0 git push origin pub.1.0.0