November Happy Hour will be moved to Thursday December 5th.

Daniel Isaacs
Dec 30, 2021
  6032
(4 votes)

Optimizely Data Platform (ODP) - Tracking and Usage Examples

The goal of this post is to provide some examples of working with Optimizely Data Platform (ODP) with Optimizely Content Cloud and Commerce Cloud. Note that, while focused on those platforms using our Foundation reference implementation, the overall strategies can be used for tracking on any solution.

Rather than recreate the wheel, for a great starting point please reference David Knipe's blog post "Adding Optimizely Data Platform to Optimizely Commerce Cloud". It provides a very solid base on which to build, and allows us to go straight into other tracking examples and how you might use the data in ODP itself. One key step from that blog post you must complete is to add the standard JavaScript tracking script for ODP to your site, to enable pageview and customer tracking. The script can be found by going to the ODP "Integrations" admin screen, and clicking into the "JavaScript Tag" integration -- refer to the ODP JavaScript tag documentation here for more details.

Now, let's get into some custom event tracking.

Optimizely Forms Events

ODP Prep

First you'll need to create a new field in ODP. Log in to ODP, then go to account settings by clicking the gear icon in the top-right of the page.

ODP-Adding new field 1

Then select "Create New Field" to create the new property -- I used the following values (the "field name" will be used in the tracking section below):

ODP-Adding new field 2

Tracking

The example tracking below for Optimizely Forms leverages the available Forms client-side events to track form impressions and form submissions. Alternatively, form submission tracking could be done server-side by modifying it to use the .NET events documented on that page.

// ODP Tracking for Optimizely Forms

if (typeof $$epiforms !== 'undefined') {
    $$epiforms(document).ready(function myfunction() {
        $$epiforms(".EPiServerForms").on("formsNavigationNextStep formsNavigationPrevStep formsSetupCompleted formsReset formsStartSubmitting formsSubmitted formsSubmittedError formsNavigateToStep formsStepValidating",
            function (event, param1, param2) {
                var eventType = event.type;
                var formName = event.workingFormInfo.Name;

                if (eventType == 'formsSetupCompleted') {
                    console.log('ODP: web_form impression: ' + formName);
                    zaius.event('web_form', { action: 'impression', form_name: formName });
                } else if (eventType == 'formsStepValidating') {
                    if (!event.isValid) {
                        console.log('ODP: web_form validation failed: ' + formName);
                        zaius.event('web_form', { action: 'submission_validation_failed', form_name: formName });
                    }
                } else if (eventType == 'formsSubmitted') {
                    console.log('ODP: web_form submission: ' + formName);
                    zaius.event('web_form', { action: 'submission', form_name: formName });
                } else {
                    // handle other form events here
                }
            });
    });
}

Once that script is included on your site, then viewing a page with an Optimizely Form will trigger a "Web Form: Impression" event, and submitting a form will trigger a "Web Form: Submission" event:

Example Form events in ODP

Usage in ODP

Once you're tracking those form events, you can start to put them to work for you. Some simple suggestions:

  • Build a report in ODP to track overall form conversion rates (impressions vs submissions)
  • Build a report in ODP to track form conversion rates for a specific form across multiple pages -- which page has more success?
  • Build a segment of visitors that saw the form, but didn't submit it -- convert that to a Facebook lookalike audience, and target them in an ad campaign

Let's walk through the steps for the first example -- a report to track form conversion rates:

Forms Report - Conversion rate by form

  1. First, create a filter -- Event Type = "web_form" and Form Name is not empty
    Form events filter
  2. Create a new report. Apply your new filter by clicking "All Traffic" in the top-left, and selecting your filter. Click the checkbox to apply:
    Form conversions report - apply filter
  3. Add columns to your report -- in this example, I added the field "Form Name", and then the following rocket columns to get the count of impressions, submissions and the calculated conversion rate:
    Conversion rate per Form - rocket columns

Bonus: want to track an individual form, and see its success rate across different pages? Follow the steps above, and then:

  1. Use the "Simple Filter" option to specify the form by name (don't forget to click "Apply" after adding the simple filter!)
    Form conversions report - quick filter by form name
  2. Add the Page to the report columns.
    Form conversion rate by page

Search Events

Note: the example tracking below is specific to the Foundation reference site, but the principles will apply and can easily be modified to fit any search implementation.

ODP Prep

First, create a new field for the search term -- the scripts below use the field name "search_term", type "text".

Tracking

I've included three types of tracking below: the initial search event, searches with no results, and search result click tracking.

Initial Search Event

// Enter for search
$(document).keypress(function(event){
    var key = event.which;
    if(key == 13)  // the enter key code
    {
        var searchValue = null;
        var searchInputs = $('.jsSearchText');
        if (searchInputs != null)
        {
            for (var i = 0; i < searchInputs.length; i++)
            {
                var input = searchInputs[i];
                if (input.value != null && input.value != '')
                {
                    searchValue = input.value;
                    console.log('ODP: search: ' + searchValue);
                    zaius.event('navigation', { action: 'search', search_term: searchValue });
                }
            }
        }
    }
});

"No Results" and Search Result Click Tracking

This script tracks searches that have no results, and also tracks search result clicks (breaking them up between clicks on content results versus product search results).

$(document).ready(function() {
	var searchValue = null;
	const params = new URLSearchParams(window.location.search);
	searchValue = params.get('search');
		 
	if (document.querySelector('.content-search-results') == null && document.querySelector('.product-tile-grid') == null) 
	{
		 console.log("ODP: Track no search results")
		 zaius.event('search', { action: 'no_results' , search_term: searchValue });
	}
	
	$('.content-search-results > a').click(function() {
		let clickedLink = $(this).attr("href");
		console.log("ODP: Track search result link click -- " + clickedLink + " (search term: " + searchValue + ")");
		zaius.event('search', { action: 'click' , search_term: searchValue, search_clicked_content: clickedLink });
	});

	$('.product-tile-grid').click(function() {
		let site = location.protocol + '//' + location.host;
		let clickedProd = $(this).children('.product-tile-grid__title').children('a').attr("href");
		let clickedProdLink  = site + clickedProd;
		console.log("ODP: Track search result product click -- " + clickedProdLink + " (search term: " + searchValue + ")");
		zaius.event('search', { action: 'click' , search_term: searchValue, search_clicked_product: clickedProdLink });
	});
});

Usage in ODP

Search event tracking can be used in a variety of ways. A few quick examples include:

  • Top searches
  • Top searches without results
  • Clickthrough rate for search results

Build reports in ODP to identify popular searches and searches without results to help drive content creation. Create Best Bets in Optimizely Search & Navigation to address searches without results. Build segments to target visitors searching for specific search terms.

Video Events

Just for fun, a rough implementation of tracking of YouTube video events. Identify users that loaded a video, and track their progress through it. Then use that data to identify popular (or unpopular) videos, or target visitors that stopped a certain video before completion, or visitors that are interested in certain types of videos.

ODP Prep

For this example I added three new fields to ODP:

  • video_id_yt (YouTube video ID) (type: Text)
  • video_play_percentage (type: Number)
  • video_title (type: Text)

Video Event Tracking

The following script will track YouTube video events, including video loads, video plays, video progress (10%, 25%, 50%, 75%, 90%, and completed), and pauses/restarts. It leverages the YouTube API for iframe embeds.

A couple caveats: the script as-is will only track if there is a single YouTube video block on the page, because it targets the video based on the id="youtube-block" attribute when the block is rendered.

var tag = document.createElement('script');
tag.id = 'youtube-iframe';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

var player;
function onYouTubeIframeAPIReady() {
    player = new YT.Player('youtube-block', {
        events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
        }
    });
}

var sendEvents = true;

var videoDuration;
var videoId;
var videoTitle;
var timer;
var currentProgress = 0;
var previousProgress = 0;
var progressEventPoints = [10, 25, 50, 75, 90];


var startedPlay = false;
var pausedPlay = false;
var halfway = false;

function writeLoadVideoEvent() {
    console.log("ODP - video loaded - " + videoTitle);
    if (sendEvents) {
        zaius.event("video", {
            action: "loaded",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: 0,
            video_action: true
        });
    }
}

function writeStartVideoEvent() {
    console.log("ODP - video started");
    if (sendEvents) {
        zaius.event("video", {
            action: "started",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: 0,
            video_action: true
        });
    }
}

function writeHalfVideoEvent() {
    console.log("ODP - video 50% completed");
    if (sendEvents) {
        zaius.event("video", {
            action: "watched_half",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: 50,
            video_action: true
        });
    }
}

function writeEndVideoEvent() {
    console.log("ODP - video completed");
    if (sendEvents) {
        zaius.event("video", {
            action: "watched",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: 100,
            video_action: true
        });
    }
}

function writeVideoProgressEvent(percent) {
    console.log("ODP - video " + percent + "% completed");
    if (sendEvents) {
        zaius.event("video", {
            action: "video_progress",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: percent,
            video_action: true
        });
    }
}

function writePauseVideoEvent(percent) {
    console.log("ODP - video paused " + percent + "%");
    if (sendEvents) {
        zaius.event("video", {
            action: "paused",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: percent,
            video_action: true
        });
    }
}

function writeRestartVideoEvent(percent) {
    console.log("ODP - video restarted at " + percent + "%");
    if (sendEvents) {
        zaius.event("video", {
            action: "restarted",
            video_id_yt: videoId,
            video_title: videoTitle,
            video_play_percentage: percent,
            video_action: true
        });
    }
}

function onPlayerReady(event) {
    videoDuration = player.getDuration();
    videoId = player.getVideoData().video_id;
    videoTitle = player.getVideoData().title;
    writeLoadVideoEvent();
}

function play_progress_reached() {
    current_time = player.getCurrentTime();
    currentProgress = parseInt((current_time / videoDuration) * 100);
    if (player.getPlayerState() == YT.PlayerState.PLAYING) {
        if (startedPlay == false) {
            writeStartVideoEvent();
            startedPlay = true;
        } else if (pausedPlay == true) {
            writeRestartVideoEvent(currentProgress);
            pausedPlay = false;
        } else if (currentProgress > 0 && currentProgress % 5 == 0 && currentProgress > previousProgress) {
            if (currentProgress > previousProgress && progressEventPoints.includes(currentProgress)) {
                writeVideoProgressEvent(currentProgress);
                previousProgress = currentProgress;
            }
        }
    } else if (player.getPlayerState() == YT.PlayerState.PAUSED) {
        writePauseVideoEvent(currentProgress);
        pausedPlay = true;
        clearInterval(timer);
    } else if (player.getPlayerState() == YT.PlayerState.ENDED) {
        writeEndVideoEvent();
        clearInterval(timer);
    } else {
        clearInterval(timer);
    }
}

function play_progress_callback() {
    clearInterval(timer);
    current_time = player.getCurrentTime();
    currentProgress = parseInt((current_time / videoDuration) * 100);
    remaining_time = videoDuration - current_time;
    if (remaining_time > 0) {
        timer = setInterval(play_progress_reached, 500);
    }
}

function onPlayerStateChange(event) {
    if (event.data == YT.PlayerState.PLAYING) {
        console.log("Video playing");
    }
    clearInterval(timer);
    play_progress_callback();
}

Usage in ODP

Use the tracked events to identify which videos are getting watched (and watched to completion), versus videos that aren't getting any plays at all or are frequently stopped before the end.

Here's a sample report, as a starting point:

Video Events Report

Conclusions

Optimizely Data Platform (ODP) provides your team with the flexibility to track any custom events for both known and unknown visitors, and use that information to build reports and create targeted campaigns to increase engagement with your visitors.

A few additional references for working with ODP:

Dec 30, 2021

Comments

Praful Jangid
Praful Jangid Feb 28, 2022 12:09 PM

Great initiative Daniel.

Thanks for sharing that.

Binh Nguyen Thi
Binh Nguyen Thi Jun 20, 2024 01:00 PM

Hi Daniel,

It is nice article about ODP events. I am working on this one and now I want to track impression when viewing product listing, load more as same as GTM such as:

 dataLayer.push({
     'event': 'impressions',
     'ecommerce': {
         currencyCode,
         'impressions': products
     }
 });

I do not think that we should push array value into event data with ODP like this 

 zaius.event("product", {
     'action': 'impressions',
     currencyCode,
     'impressions': products  //arry
 });

Do you have any suggestions how we should do it with ODP?

Please login to comment.
Latest blogs
Shared optimizely cart between non-optimizley front end site

E-commerce ecosystems often demand a seamless shopping experience where users can shop across multiple sites using a single cart. Sharing a cart...

PuneetGarg | Dec 3, 2024

CMS Core 12.22.0 delisted from Nuget feed

We have decided to delist version 12.22.0 of the CMS Core packages from our Nuget feed, following the discovery of a bug that affects rendering of...

Magnus Rahl | Dec 3, 2024

Force Login to Optimizely DXP Environments using an Authorization Filter

When working with sites deployed to the Optimizely DXP, you may want to restrict access to the site in a particular environment to only authenticat...

Chris Sharp | Dec 2, 2024 | Syndicated blog

Video Guides: Image Generation Features in Optimizely

The AI Assistant for Optimizely now integrates seamlessly with Recraft AI, providing advanced image generation capabilities directly within your...

Luc Gosso (MVP) | Dec 1, 2024 | Syndicated blog

DAM integration new major version, performance improvements and Library Picker folder selection

As you might already have seen we have decided to delist the EPiServer.CMS.WelcomeIntegration version 1.4.0 where we introduced Graph support....

Robert Svallin | Nov 29, 2024

Adding Geolocation Personalisation to Optimizely CMS with Cloudflare

Enhance your Optimizely CMS personalisation by integrating Cloudflare's geolocation headers. Learn how my Cloudflare Geo-location Criteria package...

Andy Blyth | Nov 26, 2024 | Syndicated blog