UI Instrumentation at Pennylane, Part 2

Guillaume Terral
Pennylane Tech & Product
5 min readSep 6, 2022

--

How we implemented our event tracking system

Intro

At Pennylane, data is our nerve center. On a daily basis, we encourage all Pennylaners and especially members of our tech team (Product Designers; Product Managers; Engineers; Data Analysts; Data Engineers — we’re hiring 😉) to base their decisions on our data.

As presented in our previous article, to gather all our data, we needed to build a reliable tracking system that supports both auto (leveraging our UI Kit) and precision tracking.

In this article, we will present the frontend implementation of our tracking system based on Segment Track function.

Get ready to track

As the data is at the center of our decisions, we actually need to pour our data into multiple destinations (Amplitude; Intercom; Redshift; etc).

To easily integrate with all these destinations, we’ve decided to use Segment. Segment is a Customer Data Platform (CDP) that “collects events from your web and mobile apps and provides a complete data toolkit to every team in your company”.

Concretely, it means that once you’ve set up the Segment SDK you can connect all your data destinations within Segment directly. You don’t have to implement a specific SDK for each destination.

Naming convention

Before we started tracking anything, we needed to normalize the naming of our TrackingPlan.

Following Segment Best Practices, we:

  • Picked a casing convention. At Pennylane, all our events are using Pascal Case. Example : Invoice Edit Button Clicked.
  • Picked an event name structure. We are using the Object / Action framework. Taking the same example, in this case, the object name is Invoice and the event name is actually Edit Button Clicked.
  • Decided to never use a dynamic variable in the names of our events.
  • Always name properties and events with meaningful words, ie we avoid “feature_number”, “new_feature”, etc.
  • Created a restricted list of vocabulary to be used across all our events. For example, we are mainly using these action verbs: “Clicked; Completed; Opened; Initiate; Submitted”. You shouldn’t use “Clicked” to track a button and “Pressed” to track another one.

Track generic and custom properties

In Segment, each event can have an optional list of properties (key/value pair). At Pennylane, we are using two types of properties :

  • Generic properties
  • Custom properties

Generic properties are shared between multiple events. They are always sent when dealing with a specific object. For instance, for events that concern “Invoice” (one of our core database models), we are always sending: the ID, the date, the deadline, and the amount.

Custom properties are only available for specific events. This is when you want to add specific information about a particular event. These properties should never be optional, they should always be passed, we privilege the null value if needed. For example, when tracking a table, we would pass this kind of specific information :{ currentFilter: [{field: "date", operator: "eq", value: "today" }]}.

Following this logic, we build our own track function based on the one provided by Segment :

Usage example:

Track function accessible from everywhere

Once you have this track function, you actually need to make sure that it becomes accessible throughout your whole codebase. The best way to do that is to use the Context API from React and render it on the topmost level of the render tree.

Track

As we decided to take an auto/precision tracking approach, we sat down and thought about where we actually needed to add front-end tracking. We came up with 2 main sources :

  • During a specific lifecycle event of a page or react component (mounting, updating, or unmounting for instance);
  • During a specific callback.

Lifecycle event tracking

This kind of tracking can easily be implemented by leveraging the lifecycle hooks available in React. Using them you can define your own lifecycle hooks :

When we perform a specific callback

ℹ️ This is our main tracking source at Pennylane

Once you have access to the TrackingContext, it's easy to call your track function from everywhere in the app, as it is exposed by the TrackingContext.

Following auto-tracking principles, we wanted to make sure that as soon as we added a button to our app, our engineers were thinking about tracking these buttons. At Pennylane, our front-end codebase is actually based on two important principles: a UI Kit and a Typescript :

  • Through the use of a UI Kit, we can ensure that every button of the application originates from the same file;
  • Typescript ensures that our engineers pass specifics properties: track properties.

The idea is to guide our engineers to be explicit when it comes to tracking. It’s ok to not track a specific button, in this case, you can pass a no-track property. Either way, an engineer has to always pass track properties or explicitly decide not to track.

Communicate your tracking plan

Once we knew how to track events, it was time to think about how we were going to communicate/document our tracking plan to all members of our company. As data is at the core of the decision-making at Pennylane, we needed a way to document our Tracking Plan in a non-tech environment, Google Sheet seemed the best option to us.

Build an event dictionary

We started by creating an event dictionary. This is a list of events with:

  • a required actionName
  • a required objectName
  • optional genericProperties (based on the objectName)
  • optional additionalProperties
  • optional description.

Using what we already implemented, we changed a couple of typescript definitions to ensure that all tracked events are coming from this dictionary and respect this kind of structure :

Keep the tracking plan up-to-date with your CI/CD pipelines

Once we had our event dictionary, it was time to generate our Google Sheet. To do that, we created a simple script to convert our dictionary into a CSV file, then upload it to Google Drive.

Having a Google Sheet TrackingPlan is nice, but having an up-to-date tracking plan is even nicer! At Pennylane, features are changing a lot and at a fast pace, based on user feedback and it’s pretty common to either lose some events while refactoring a piece of code or to add new tracking while building a new feature. To ensure that this TrackingPlan was not built for nothing and ensure that people can actually trust it, we needed a way to always keep it up-to-date.

Thus, we decided to use a GH action to call our script whenever a new piece of code is pushed on our master branch.

Convert the TS file into a CSV file

Conclusion

In the end, having a tracking plan will give your employees a single source of data truth to base their decision upon.

When creating the plan, the three key principles are:

  • Correctly name your events and determine the generic/custom properties for each event.
  • Leverage your front-end tools: UI Kit and Typescript. By making sure tracking properties are not optional, you will have answers even before raising the questions.
  • Always keep your tracking plan up-to-date using an event dictionary and a GitHub action.

The tracking plan system only covers one side of our tracking setup — stay tuned for future articles concerning our tracking strategy!

--

--