Blog posts by Daniel Isaacs2022-03-05T15:48:34.0000000Z/blogs/daniel-isaacs/Optimizely WorldGuess who's back? Alloy's back!https://blog.danisaacs.net/guess-whos-back-alloys-back/2022-03-05T15:48:34.0000000Z<h2 id="alloy-on-cms-12">Alloy! On CMS 12!</h2><p>If you've worked with Optimizely Content Cloud for awhile, two things are probably true:</p><ol><li>You still sometimes call it Episerver CMS (and if it's been a longer while, you write it as EPiServer CMS)</li><li>You've seen the Alloy demo site</li></ol>Running with Foundationhttps://blog.danisaacs.net/running-with-foundation/2022-02-17T02:51:25.0000000ZGetting started with Foundation, the Optimizely reference site.Optimizely Data Platform (ODP) - Tracking and Usage Exampleshttps://blog.danisaacs.net/optimizely-data-platform-odp-tracking-and-usage-examples/2022-01-14T02:30:44.0000000ZGetting started with Optimizely Data Platform -- Some example tracking and usage of ODPOptimizely Data Platform (ODP) - Tracking and Usage Examples/blogs/daniel-isaacs/dates/2021/12/optimizely-data-platform-odp---example-tracking-and-usage/2021-12-30T20:21:47.0000000Z<p>The goal of this post is to provide some examples of working with <a href="https://www.optimizely.com/products/intelligence/data-platform/">Optimizely Data Platform (ODP)</a> with <a href="https://www.optimizely.com/products/content/">Optimizely Content Cloud</a> and <a href="https://www.optimizely.com/products/commerce/b2c/">Commerce Cloud</a>. Note that, while focused on those platforms using our <a href="https://github.com/episerver/Foundation">Foundation reference implementation</a>, the overall strategies can be used for tracking on any solution.</p>
<p>Rather than recreate the wheel, for a great starting point please reference David Knipe's blog post <a href="https://www.david-tec.com/2021/05/adding-optimizely-data-platform-to-optimizely-commerce-cloud/">"Adding Optimizely Data Platform to Optimizely Commerce Cloud"</a>. 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 <a href="https://docs.zaius.com/hc/en-us/articles/360033327194-Install-the-Zaius-JavaScript-tag">ODP JavaScript tag documentation here</a> for more details.</p>
<p>Now, let's get into some custom event tracking.</p>
<h2>Optimizely Forms Events</h2>
<h3>ODP Prep</h3>
<p>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.</p>
<p><img src="/link/fd6de1453a894a178d2a6dc558498030.aspx" alt="ODP-Adding new field 1" height="100" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></p>
<p>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):</p>
<p><img src="/link/0275fb650e0e447b94f00267ca0ce501.aspx" alt="ODP-Adding new field 2" height="95" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></p>
<h3>Tracking</h3>
<p>The example tracking below for Optimizely Forms leverages the available <a href="/link/6183a48cd93b42e0bea8e08b16990e25.aspx">Forms client-side events</a> 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.</p>
<pre class="language-javascript"><code>// 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
}
});
});
}</code></pre>
<p>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:</p>
<p><img src="/link/ed75c25ef90a4a298385742cafa53f0d.aspx" width="706" alt="Example Form events in ODP" height="94" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></p>
<h3>Usage in ODP</h3>
<p>Once you're tracking those form events, you can start to put them to work for you. Some simple suggestions:</p>
<ul>
<li>Build a report in ODP to track overall form conversion rates (impressions vs submissions)</li>
<li>Build a report in ODP to track form conversion rates for a specific form across multiple pages -- which page has more success?</li>
<li>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</li>
</ul>
<p>Let's walk through the steps for the first example -- a report to track form conversion rates:</p>
<p><img src="/link/215c7a69180d49c18b54a70f76790a45.aspx" alt="Forms Report - Conversion rate by form" height="211" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></p>
<ol>
<li>First, create a filter -- Event Type = "web_form" and Form Name is not empty<br /><img src="/link/2ccb5d58f2264429801eae818266376d.aspx" alt="Form events filter" height="219" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></li>
<li>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:<br /><img src="/link/074bfaf643a249599e1e8141df0ebf89.aspx" alt="Form conversions report - apply filter" height="110" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></li>
<li>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:<br /><img src="/link/592b86531e0247a3bd1bafadbe72984c.aspx" alt="Conversion rate per Form - rocket columns" height="161" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></li>
</ol>
<p>Bonus: want to track an individual form, and see its success rate across different pages? Follow the steps above, and then:</p>
<ol>
<li>Use the "Simple Filter" option to specify the form by name (don't forget to click "Apply" after adding the simple filter!)<br /><img src="/link/f94652ace82946b2b233d72f691670d2.aspx" alt="Form conversions report - quick filter by form name" height="145" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></li>
<li>Add the Page to the report columns.<br /><img src="/link/b1b72dcd3f264514a3761983f7ae9700.aspx" alt="Form conversion rate by page" height="210" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></li>
</ol>
<h2>Search Events</h2>
<p>Note: the example tracking below is specific to the <a href="https://github.com/episerver/foundation">Foundation reference site</a>, but the principles will apply and can easily be modified to fit any search implementation.</p>
<h3>ODP Prep</h3>
<p>First, create a new field for the search term -- the scripts below use the field name "search_term", type "text".</p>
<h3>Tracking</h3>
<p>I've included three types of tracking below: the initial search event, searches with no results, and search result click tracking.</p>
<h4>Initial Search Event</h4>
<pre class="language-javascript"><code>// 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 });
}
}
}
}
});</code></pre>
<h4>"No Results" and Search Result Click Tracking</h4>
<p>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).</p>
<pre class="language-javascript"><code>$(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 });
});
});</code></pre>
<h3>Usage in ODP</h3>
<p>Search event tracking can be used in a variety of ways. A few quick examples include:</p>
<ul>
<li>Top searches</li>
<li>Top searches without results</li>
<li>Clickthrough rate for search results</li>
</ul>
<p>Build reports in ODP to identify popular searches and searches without results to help drive content creation. Create <a href="https://webhelp.optimizely.com/latest/en/searchnavigation/best-bets.htm">Best Bets</a> in Optimizely Search & Navigation to address searches without results. Build segments to target visitors searching for specific search terms.</p>
<h2>Video Events</h2>
<p>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.</p>
<h3>ODP Prep</h3>
<p>For this example I added three new fields to ODP:</p>
<ul>
<li>video_id_yt (YouTube video ID) (type: Text)</li>
<li>video_play_percentage (type: Number)</li>
<li>video_title (type: Text)</li>
</ul>
<h3>Video Event Tracking</h3>
<p>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 <a href="https://developers.google.com/youtube/iframe_api_reference">YouTube API for iframe embeds</a>.</p>
<p>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 <span class="pl-e x">id</span><span class="x">=</span><span class="pl-s"><span class="pl-pds x">"</span><span class="x">youtube-block</span><span class="pl-pds x">" attribute when the block is rendered.</span></span></p>
<pre class="language-javascript"><code>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();
}</code></pre>
<h3>Usage in ODP</h3>
<p>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.</p>
<p>Here's a sample report, as a starting point:</p>
<p><img src="/link/fa7cf58cbf1c436ea42d24ffd969a5ec.aspx" alt="Video Events Report" height="360" style="border: 1px solid #b3b3b3; padding: 5px; border-radius: 8px; margin-left: 5%;" /></p>
<h1>Conclusions</h1>
<p><a href="https://www.optimizely.com/products/intelligence/data-platform/">Optimizely Data Platform (ODP)</a> 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.</p>
<p>A few additional references for working with ODP:</p>
<ul>
<li>Using ODP with Commerce Cloud: <a href="https://www.david-tec.com/2021/05/adding-optimizely-data-platform-to-optimizely-commerce-cloud/">https://www.david-tec.com/2021/05/adding-optimizely-data-platform-to-optimizely-commerce-cloud/</a></li>
<li>ODP reference guide: <a href="/link/24399036e80b40148582aca6f2a6ccc8.aspx">https://world.optimizely.com/blogs/K-Khan-/Dates/2021/6/odp-handy-reference-guide---web-sdk/</a></li>
<li>ODP documentation: <a href="https://docs.zaius.com/">https://docs.zaius.com/</a></li>
<li>ODP developer documentation: <a href="https://docs.developers.zaius.com/">https://docs.developers.zaius.com/</a></li>
</ul>