Useful Data Elements

Every one of us has a bag full of little tricks that they carry with them.

The bag contains knowledge, ideas that we developed with or for customers, snippets of code, pieces of architectural blue prints, and much more.

This blog, every now and again, empties the bag onto the floor and explains some of the stuff lying there. Today: standard Data Elements that I often use.

DTM Rules that have Fired so far

A relatively new addition to my toolbox, and inspired partly by the excellent Tagtician by Jim Gordon, partly by my own test framework. This Data Element checks which Rules DTM has fired since page load, and returns them in list form, with their type, like so:

[screenshot]
DE “DTM Rules that have Fired so far” in action
It is a “Custom Script” type Data Element, of course:

var lms = _satellite.Logger.getHistory();
var ruleNameArray = [];
for (var i = 0; i < lms.length; i++) { if (lms[i][1].indexOf(" fired.") > 0) {
		var ruleName = lms[i][1].replace(/Rule /, "").replace(/ fired./, "").replace(/\u0022/g, "");
		ruleNameArray.push(ruleName);
	}
}
var result = [];

function isRuleInList(ruleList, name) {
	for (var i = ruleList.length - 1; i >= 0; i--) {
		if (ruleList[i].name == name) {
			return true;
		}
	}
	return false;
}

// try and assign those rules to their type
for (var i = 0; i < ruleNameArray.length; i++) {
	var foundthisone = false;
	if (isRuleInList(_satellite.pageLoadRules, ruleNameArray[i])) {
		result.push("PLR " + ruleNameArray[i]);
		foundthisone = true;
	}
	if (!foundthisone) {
		if (isRuleInList(_satellite.rules, ruleNameArray[i])) {
			result.push("EBR " + ruleNameArray[i]);
			foundthisone = true;
		}
		if (!foundthisone) {
			if (isRuleInList(_satellite.directCallRules, ruleNameArray[i])) {
				result.push("DCR " + ruleNameArray[i]);
				foundthisone = true;
			}
		}
	}
	if (!foundthisone) {
		result.push("??? " + ruleNameArray[i]);
	}
}
result = result.join("|");
return result;

I use it in situations where data doesn’t make sense, and I need to know which rules have conspired to give me that flawed data.

From what I gather, the Logger facility in DTM and the getHistory() method are “official” and might make it into Launch, so this one is hopefully future-proof.

Version

I almost always track a version. The main reason is that it can help find and segment mistakes in the tracking.

How complex the version is depends on the job. The article I linked was about a pretty involved, partly manual solution, while this one is a bit like a condensed version of the Debugging Helpers snippets. Take a pick.

The Data Element is “Custom Script”, and the script is as follows:

// check DTM & Launch
var result = "";
if ("undefined" == typeof _satellite) {
        result += "not loaded"
} else {
        if ('undefined' !== typeof _satellite.buildInfo && _satellite.buildInfo) {
                result += "Launch|";
                // version
                result += "?";
                var bi = _satellite.buildInfo;
                if ('undefined' !== typeof bi.buildDate && bi.buildDate) {
                        result += "|" + bi.buildDate;
                } else {
                        result += "|???";
                }
                if ('undefined' !== typeof _satellite.property && _satellite.property && 'undefined' !== _satellite.property.name && _satellite.property.name) {
                        result += " - " + _satellite.property.name;
                        if ('undefined' !== bi.environment && bi.environment) {
                                result += " (" + bi.environment + ")";
                        }
                }
                if ('undefined' !== bi.turbineVersion && bi.turbineVersion) {
                        result += " - " + bi.turbineVersion;
                }
        } else {
                result += "DTM|";
                if ('undefined' !== typeof _satellite.appVersion && _satellite.appVersion) {
                        result += "" + _satellite.appVersion;
                } else {
                        result += "?";
                }
                if ('undefined' !== typeof _satellite.buildDate && _satellite.buildDate) {
                        result += "|" + _satellite.buildDate;
                }
        }
}
// end DTM
// check MCIDS
result += ",MCID|";
if ("function" != typeof Visitor) {
        result += "not loaded";
} else {
        if ('undefined' !== typeof Visitor.version && Visitor.version) {
                result += "" + Visitor.version;
        } else {
                result += "?.?.?"
        }
        var idType = "unknown ID state";
        if ("undefined" !== typeof s_c_il && s_c_il) {
                for (var i = s_c_il.length - 1; i >= 0; i--) {
                        var candidate = s_c_il[i];
                        if ("undefined" !== typeof candidate && candidate && "undefined" !== typeof candidate._c && candidate._c && "Visitor" === candidate._c) {
                                var idState = candidate.isClientSideMarketingCloudVisitorID();
                                if (null == idState) {
                                        idType = "Existing ID";
                                } else {
                                        idType = idState ? "Client Side ID" : "Server Side ID";
                                }
                        }
                }
        }
        result += "|" + idType;
}
// end MCIDS
// check AAM
// collect AA object if we find one while we're at it
var sObject = null;
result += ",AAM|";
if ("function" == typeof DIL) {
        if ("function" == typeof AppMeasurement_Module_AudienceManagement) {
                result += "module|" + DIL.version;
        } else if ("undefined" !== typeof s_c_il) {
                for (var i = s_c_il.length - 1; i >= 0; i--) {
                var candidate = s_c_il[i];
                if ("undefined" !== typeof candidate && candidate && "undefined" !== typeof candidate._c && candidate._c && "s_m" === candidate._c && 'undefined' !== typeof candidate._n && candidate._n && "DIL" === candidate._n) {
                        result += "DIL|" + DIL.version;
                        break;
                } else if ("undefined" !== typeof candidate && candidate && "undefined" !== typeof candidate._c && candidate._c && "s_c" === candidate._c) {
                        sObject = candidate; // just in case we come across an s object
                        }
                }
        } else {
                result += "?|" + DIL.version;
        }
} else {
        result += "not loaded";
}
// end AAM
// check AT
result += ",AT|";
if ("undefined" !== typeof mboxVersion && mboxVersion) {
        result += "mbox.js|" + mboxVersion
} else if ("undefined" !== typeof adobe && adobe && 'undefined' !== typeof adobe.target && adobe.target && "undefined" !== typeof adobe.target.VERSION && adobe.target.VERSION) {
        result += "at.js|" + adobe.target.VERSION;
} else {
        result += "not loaded";
}
// end AT
// check AA
result += ",AA|";
var aatype = "?";
var aaversion = "?.?";
if ("function" !== typeof s_gi) {
        result += "not loaded";
} else {
        var aatmp;
        if ("function" === typeof AppMeasurement) {
                aatype = "AppMeasurement";
                aatmp = AppMeasurement.toString();
        } else {
                aatype = "legacy";
                aatmp = s_gi.toString();
        }
        if (typeof aatmp !== 'undefined' && aatmp) {
                aatmp = aatmp.split(".version=");
                if (typeof aatmp !== 'undefined' && aatmp && aatmp.length > 1 && aatmp[1]) {
                        aatmp = aatmp[1].split(";");
                        if (typeof aatmp !== 'undefined' && aatmp && aatmp.length > 0 && aatmp[0]) {
                                aaversion = aatmp[0].replace(/[\"\']/g, "");
                        }
                }
        }
        result += aatype + "|" + aaversion;
}
return result;

Overkill? Maybe. I like it.

Browser Width

CSS Grid layout is on the rise, but “mobile first” and responsive layout using media queries are still a thing.

As an Analytics person, I can help by tracking how many visitors fall into which bucket. I do that by tracking the browser width, then using Classifications to aggregate to “S”, “M”, “L”, “XL”, or something like that, aligned with the media queries the design team used.

Again, the DE is “Custom Script”, and the script looks like this:

var cw = -1;
if (typeof document !== 'undefined' && document && typeof document.documentElement !== 'undefined' && document.documentElement) {
	cw = document.documentElement.clientWidth;
}
var iw = -1;
if (typeof window !== 'undefined' && window) {
	iw = window.innerWidth;
}

return cw < iw ? iw : cw;

I shall be very open with you and admit that I have absolutely no clue why those two values are used. I suspect it has to do with cross-browser streamlining.

To use this, you have to use classifications. Take your list of break points, then classify the widths accordingly. That way you get a lovely report with Page Views or Visits by break point.

Or you can create segments, and use those to compare proper metrics like Revenue, Orders, Signups, and so forth.

(Yes, you can use Segments as dimensions in Analysis Workspace.)

MCIDS Present

This one is a classic. It came along with the Marketing Cloud ID Service. The idea is that if you cannot switch all your digital assets to the MCID Service at once, you can run the old Analytics visitor ID in parallel, just by calling Customer Care and telling them you need a grace period.

Using a prop to check how good your MCID Service coverage is helps you decide when you can let the grace period expire, or whether you need to renew it.

The code is from the documentation, but I have adapted it slightly, for the sake of readability in the report.

return (typeof Visitor !== "undefined" ? "MCID Service present" : "MCID Service missing");

(Am I the only one who thinks typeof(Visitor) != "undefined" looks really wrong? And what happened to the good old “constants first in comparisons”? Should we use "undefined" !== typeof Visitor? Since Javascript is essentially voodoo, I’d say: whatever floats your boat.)

Visitor ID

We used to simply use “Dynamic Variables” to track the Analytics visitor ID, like so: s.prop70 = D=s_vi. But then came the fallback id in s_fid, then the Marketing Cloud ID Service. Today, there are different IDs, and reading them is a bit more complex.

var result = "";
var sep = "";

// start checking for old, AA-only visitor IDs
var s_vi = _satellite.readCookie("s_vi");
if (typeof s_vi !== 'undefined' && s_vi) {
  result += sep + "s_vi|" + s_vi;
  sep = "|";
}
var s_fid = _satellite.readCookie("s_fid");
if (typeof s_fid !== 'undefined' && s_fid) {
  result += sep + "s_fid|" + s_fid;
  sep = "|";
}

// now add AMC visitor ID
if (typeof _satellite.getToolsByType("visitor_id") !== 'undefined' && _satellite.getToolsByType("visitor_id") && typeof _satellite.getToolsByType("visitor_id")[0] !== 'undefined' && _satellite.getToolsByType("visitor_id")[0] && typeof _satellite.getToolsByType("visitor_id")[0].settings !== 'undefined' && _satellite.getToolsByType("visitor_id")[0].settings && typeof _satellite.getToolsByType("visitor_id")[0].settings.mcOrgId !== 'undefined' && _satellite.getToolsByType("visitor_id")[0].settings.mcOrgId) {
	var mc_org_id = encodeURIComponent(_satellite.getToolsByType("visitor_id")[0].settings.mcOrgId);
	var amcv_cookie = decodeURIComponent(_satellite.readCookie("AMCV_" + mc_org_id));
	var amcv_parts = amcv_cookie.split("|");
	var mid = "";
	for (var i = 0; i < amcv_parts.length; i++) { 
	  if (amcv_parts[i] === 'MCMID') { 
	    mid = amcv_parts[i+1];
	    break;
	  } 
	}
	if (typeof mid !== 'undefined' && mid) {
	  result += sep + "mid|" + mid;
	  sep = "|";
	}
}
return result;

Note that you will likely not use this data in any reports.

Why do it, then?

Highly granular data like this can be useful when you export it into other analysis or BI tools. The customers I know who track some sort of visitor ID all use Data Feeds to get raw data back out of Analytics and into some ETL/DWH/data lake.

Most of the time, this is done so that visitor IDs can be matched with logins (if there are any), and given where Audience Manager and the Marketing Cloud ID Service currently go, I expect this Data Element to be obsolete at some point.

Tool or Rule?

As you all know, you can set Analytics “variables” in DTM Rules. You can also do so in the Analytics Tool configuration, under “Global Variables”.

What’s the difference?

The “variable” mappings in rules apply when the rule fires. If, as a result, there is a tracking call to Analytics (for PLRs at page load, for EBRs and DCRs if you configure them so), the “variable” will be on the call, i.e. sent to Analytics.

The “Global Variables” mappings, in contrast, apply to every tracking call that DTM triggers, i.e. at page load, and when EBRs or DCRs are configured to track something.

If I want some data on every tracking call sent by DTM, I therefore use “Global Variables”, whereas if I think it’s enough to have it on each page load, I put it into the mappings of something like a “Normal Page Load” PLR, which fires on every page on my site.

Makes sense?

How about the DEs above?

“DTM Rules that have Fired so far”, “Version”, and “Visitor ID” should be on every call, and I will therefor set them as “Global Variables”.

“Browser Width” is an edge case. I would usually put it into my “Normal Page Load” PLR, but that way I will not catch people changing it (rotating their tablet or resizing their browser window). That is probably not really an issue, except if my site is a bit wonky.

Put it whereever you like.

14 thoughts on “Useful Data Elements

  1. Quick edit: replaced replace(/\”/g, “”) with replace(/\u0022/g, “”) in the first script, because the former threw the WordPress highlighting.

    170920 – another edit: the “Version” script can now also be used for a Data Element in the “Global Variables” section of the Analytics Tool.

    Like

  2. Well, what the hell is the point of testing if (“undefined” === typeof _satellite) in Custom JavaScript Data Element? 😀

    Like

  3. I’m trying out your “Version” Data Element and row 71 of the script is throwing a “Cannot read property ‘version’ of undefined” error. Also, it looks like ‘nameOfSObject’ is out of scope in row 55.

    Like

      1. Yep. That works for me. Thanks!
        I did notice that typeof is missing one or more times in rows 6,11,21,38,41,56,70, and 79. Example: if (‘undefined’ !== _satellite.appVersion) should be if (‘undefined’ !== typeof _satellite.appVersion)
        If desired, you can fix all of them using this RegEx to do a find/replace in Notepad ++ (or similar):
        Find: ([“‘])undefined([“‘]) \!== (?!typeof)
        Replace with: $1undefined$2 !== typeof (note: there is space after this typeof)

        Like

  4. Hi Jan! Awesome post as always and it just occurred to me that you’ve been creating this type of awesome content for 4+ years. It’s amazing and has been so useful for so many of us.

    Onto my minor question – In the Visitor ID scraping you demo, is there a way to know client side which ID Adobe Analytics will use to track the visitor? We are integrating a 3rd party product with Analytics and would like to be smart about which ID we send to the 3rd party.

    Like

    1. Thank you, Jason, for the thumbs up!

      It’s not totally possible, although the rules are relatively simple.

      If you see an “mid” parameter on the call, then Analytics will use that as the ID. If not, then Analytics will try to use the ID from the “s_vi” cookie, which might be set on your domain, on “2o7.net”, or on “omtrdc.net”. If that cookie doesn’t exist, then the system will look for the “fid” parameter, and if that isn’t available, either, then it will calculate an ID from the IP address and the HTTP User Agent.

      The weak point in your detection is to check the “s_vi” cookie in case you use 3rd-party tracking (“2o7.net” or “omtrdc.net”). The rest is easy.

      Does that make sense?

      Like

      1. Ah – yep that makes sense! The beautiful part of it is our tracking servers are behind cnames so the s_vi cookie is set against our domain and not the 207.net or omtrdc.net domains! Thanks Jan and appreciate the response!

        Like

  5. Hi, with the Launch, does this script here still works. when we used logger.history, it didnt seem to work the way it is right now with DTM. Any pointers with respect to launch will help.

    Like

    1. Hi GK,

      Launch is indeed different, and you (or I, or someone) will have to write a test for it.

      I’ll do it eventually unless someone else does it first.

      Cheers,
      Jan

      Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.