What is Segment?

Segment is a platform that lets you aggregate all your analytics data from your website and then send it to hundreds of other tools and platforms – such as Google Analytics, Mixpanel, Intercom, MailChimp, Facebook Pixel and even Amazon S3. With Segment you can track users’ activity throughout your application for many tools and platforms. Segment also provides libraries for integration into many languages. For Ruby, they have a gem called Analytics-Ruby.

Segment-homepage

 

We will be using Analytics-Ruby to identify and track users within Solidus, an open source Ruby On Rails eCommerce engine. What we wanted to archive with Segment is simple:

  1. Identify and track users and events
  2. Try not to impact the user’s experience – e.g. API errors or slow speeds when API calls are made.

The docs are very easy to follow. Within minutes I was up and running and sending data to segment.

# config/initializers/analytics_ruby.rb

Analytics = Segment::Analytics.new({
  write_key: ENV['SEGMENT_KEY'],
  on_error: Proc.new { |status, msg| print msg }
})

The two examples below show options that we are going to use to send data to Segment.

# Identify the user for the people section
Analytics.identify(
 {
   user_id: user.id,
   traits: {
     email: user.email,
     first_name: user.first_name,
     last_name: user.last_name
   }
 }
)

Identify will simply allow us to tie an action to a user as well as to send some information about that user as traits.

# Track a user event
Analytics.track(
 {
   user_id: user.id,
   event: 'Created Account'
 }
)

Track is how we are going to record any action the user makes.

At this point we can simply copy and paste the two scripts above whenever we want to track an event or identify a user. We won’t do that, however, as it will go against the DRY principle and become a nightmare to maintain. So we will create a service for it, something like Track::Property.action. This way we can call it from wherever we need to. Additionally if we ever need to change it or add a trait to a user, it can all be in one place.

While testing the events on segment, we had an error and, on production, it would have interrupted the users experience with a 500. As we don’t want errors and service disruptions from the analytics to impact the user, we can run a background job separate from the users process and have Bugsnag notify us if something goes wrong.

# app/jobs/identify_user_job.rb

class IdentifyUserJob < ActiveJob::Base
  queue_as :default

  def perform(options)
    Analytics.identify(options)
  end
end

When enqueueing IdentifyUserJob we will pass a hash as options to it, which will then be passed to Segment’s Analytics-Ruby.

# app/jobs/track_event_job.rb

class TrackEventJob < ActiveJob::Base
  queue_as :default

  def perform(options)
    Analytics.track(options)
  end
end

We will do the same for tracking events.

 

Tracking

Browsing products, ordering, promotions, coupons, sharing, etc… Segments offers  a lot of E-commerce event tracking. I’m only going to cover a few in this post. However, I recommend that, to get an excellent understanding of what users are doing in your system, you track as many events as you can. With services such as Mixpanel you will begin to better understand your users’ behaviour and journey through your site.

Users

# app/services/track/user.rb

class Track::User
  def initialize(user)
    @user = user
  end

  def logged_in_event(event, properties = {})
    identify_user

    TrackEventJob.perform_later(
      {
        user_id: @user.id,
        event: event,
        properties: properties
      }
    )
  end

  def logged_in
    identify_user

    TrackEventJob.perform_later(
      {
        user_id: @user.id,
        event: 'Sign In User'
      }
    )
  end

  def identify_user
    IdentifyUserJob.perform_later(
      {
        user_id: @user.id,
        traits: {
        email: @user.email,
        firstName: @user.first_name,
        lastName: @user.last_name,
        logins: @user.sign_in_count,
        country: @user.country,
      }
      }
    )
  end
end

So our track user service looks something like above. We can now call Track::User.new(user).logged_in_event and it will identify and track any event we specify in the params.

The Track::User.new(user).logged_in will identify and track that the user has logged in. This will be fired only when users first log in. So how can we go about setting it? Well Solidus uses Devise which is based on warden. With warden, we can set a callback in the config for after the user has logged in.

# config/initializers/devise.rb
Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
  Track::User.new(user).logged_in
end

Orders

The Segment Core ordering spec lists a lot of things we can track. But this is an overview, so we will only go over completed orders. The other specs will help you answer specific questions such as what step are people dropping of at. That way you can optimise your checkout process. More on this another time.

class Track::Order
  def initialize(order)
    @order = order
  end

  def completed
    TrackEventJob.perform_later({
      user_id: @order.user_id || @order.email,
      event: 'Order Completed',
      properties: {
        orderId: @order.number,
        total: @order.total,
        revenue: @order.payment_total,
        shipping: @order.shipment_total,
        tax: @order.included_tax_total,
        discount: @order.adjustment_total.abs,
        currency: @order.currency,
        products: order_line_items
      }
    })
  end

  def order_line_items
    line_items = []

    @order.line_items.each do |line_item|
      line_items << {
        id: line_item.variant.id,
        sku: line_item.variant.sku,
        name: line_item.variant.name,
        price: line_item.price,
        quantity: line_item.quantity
      }
    end

    line_items
  end
end

Create a deface for Solidus’ frontend orders shows view (solidus/frontend/app/views/spree/orders/show.html.erb) and update the order_just_completed() conditional so it looks like the following.

<% if order_just_completed?(@order) %>
   <% ::Track::Order.new(@order).completed %>
   <strong><%= Spree.t(:thank_you_for_your_order) %></strong>
 <% end %>

Now when orders are completed, we will get all the line items and details about the order – including the user who placed it.

Products

class Track::Product
  def initialize(product)
    @product = product
  end

  def viewed
    TrackEventJob.perform_later({
        user_id: current_spree_user,
        event: 'Product Viewed',
        properties: {
          id: @product.id,
          sku: @product.sku,
          name: @product.name,
          price: @product.price
        }
      }
    )
  end
end

We can track when a user searches for a product, views a product list or category, and when the list is filtered to find their ideal product. But we kept it simple for now, tracking only when a user views a product.

 

class Track::Subscriptions
  def initialize(subscription)
    @subscription = subscription
  end

  def subscribe
    TrackEventJob.perform_later(
      {
        user_id: @subscription.user.id,
        event: 'Subscribed User',
        properties: {
          plan: @subscription.product.name
        }
      }
    )
  end

  def pause
    TrackEventJob.perform_later(
      {
        user_id: @subscription.user.id,
        event: 'Subscription Paused',
        properties: {
          plan: @subscription.product.name,
        }
      }
    )
  end

  def cancel
    TrackEventJob.perform_later(
      {
        user_id: @subscription.user.id,
        event: 'Subscription Cancelled',
        properties: {
          plan: @subscription.product.name
        }
      }
    )
  end

  def resume
    TrackEventJob.perform_later(
      {
        user_id: @subscription.user.id,
        event: 'Subscription Resume',
        properties: {
          plan: @subscription.product.name
        }
      }
    )
  end
end

 

Conclusion

With just a few simple steps, we have a reliable system in place to track the usage of our application and give us in-depth insights. Within our application, we are now tracking when users log in, when products are added to cart, when a product is viewed and when an order is placed. You can use the data from those events to make actionable changes to your store. Obviously, we can go much deeper. This was just a taster into Segment.

Before you go crazy adding events everywhere though, make sure that the data you receive will be useful. When it comes down to it, a tool like Segment is just as important as any other tool in your app stack. In the follow up post to this one, we will go over where to put all this data, make sense of all the data and how to make it work for you.

More Reading

  • The Segment Spec outlines the semantic definition of the customer data that segment captures across all of the libraries and APIs.
  • How Thoughtbot uses it with ruby.
  • Maybe you want to add your own tool or platform into Segment, this document outlines what you need to do.

Leave a Reply