Making marketing campaigns in Sitecore Analytics behave like Google Analytics
Asked Answered
G

6

6

The default behavior for Marketing Campaigns in Sitecore Analytics is such that they will only be applied to a visit if the campaign is applied on the first page of the visit. This could be a landing page flagged with that marketing campaign, or via the sc_camp query string parameter.

I find this behavior to be somewhat problematic in certain commerce scenarios. It's also different than how Google Analytics handles marketing campaigns. Google Analytics will start a new visit for the user if he/she re-enters the site via a different marketing campaign.

I'd like to emulate this behavior in Sitecore Analytics for a POC I'm working on. I've attempted this via the initializeTracker pipeline. I can successfully detect a change in the marketing campaign for the visit, but I'm unable to end and restart the visit. I've tried both utilizing Tracker.EndVisit() and simply changing the ID of the visit. Neither seems to result in a new visit, associated with the marketing campaign.

Does anyone know how I can successfully end the previous visit, and start a new one, within the same request?

I am working in CMS/DMS 7.1 rev 140130. My current code is below.

using System;
using System.Web;
using Sitecore.Analytics;
using Sitecore.Analytics.Pipelines.InitializeTracker;
using Sitecore.Analytics.Web;
using Sitecore.Configuration;
using Sitecore.Data;
using Sitecore.Web;

namespace ActiveCommerce.Training.PriceTesting.Analytics
{
    public class RestartVisitOnNewCampaign : InitializeTrackerProcessor
    {
        public override void Process(InitializeTrackerArgs args)
        {
            if (HttpContext.Current == null)
            {
                args.AbortPipeline();
            }

            //no need to restart visit if visit is new
            if (Tracker.CurrentVisit.VisitPageCount < 1)
            {
                return;
            }

            //look for campaign id in query string
            Guid campaign;
            var campaignStr = WebUtil.GetQueryString(Settings.GetSetting("Analytics.CampaignQueryStringKey")).Trim();
            if (string.IsNullOrEmpty(campaignStr) || !Guid.TryParse(campaignStr, out campaign))
            {
                return;
            }

            //don't restart if the campaign isn't changing
            if (!Tracker.CurrentVisit.IsCampaignIdNull() && Tracker.CurrentVisit.CampaignId == campaign)
            {
                return;
            }

            //Tracker.EndVisit(false);

            //restart visit by setting new ID
            var visitCookie = new VisitCookie();
            visitCookie.VisitId = ID.NewID.Guid;
            visitCookie.Save();
        }
    }
}
Gonna answered 2/7, 2014 at 17:23 Comment(0)
G
0

Thanks everyone for the input. Andrew's answer actually ended up being the closest, so I've awarded him the bounty.

The issue in the end appeared to be the Visitor.Settings.IsFirstRequest property, which ultimately is derived from VisitCookie.IsFirstRequest. If this property is false, a new visit will not be created, and the current page request won't be associated with the new visit. This also needs to happen in order for the page to be classified as a "landing page," which would associate the campaign with it.

IsFirstRequest is set in VisitCookie.Load(), and compares the ASP.NET Session ID in the cookie to the current ASP.NET Session ID. This is why invaliding the cookie, or changing the Visit ID was not enough. Unfortunately there's no way to change the Session ID value using the VisitCookie object, so the easiest thing to do, which appears to work, is to just empty the cookie value directly, before the Sitecore.Analytics.Pipelines.InitializeTracker.Initialize processor runs.

From my testing, this results in a new visit whenever the campaign ID changes. I can see this in both the Visits table and when personalizing based on Campaign ID.

namespace ActiveCommerce.Training.PriceTesting.Analytics
{
    public class RestartVisitOnNewCampaign : InitializeTrackerProcessor
    {
        public override void Process(InitializeTrackerArgs args)
        {
            if (HttpContext.Current == null)
            {
                args.AbortPipeline();
                return;
            }

            //no need to restart visit if visit is new
            if (Tracker.Visitor.Settings.IsNew || Tracker.Visitor.Settings.IsFirstRequest || Tracker.CurrentVisit.VisitPageCount < 1)
            {
                return;
            }

            //look for campaign id in query string
            Guid campaign;
            var campaignStr = WebUtil.GetQueryString(Settings.GetSetting("Analytics.CampaignQueryStringKey")).Trim();
            if (string.IsNullOrEmpty(campaignStr) || !Guid.TryParse(campaignStr, out campaign))
            {
                return;
            }

            //don't restart if the campaign isn't changing
            if (!Tracker.CurrentVisit.IsCampaignIdNull() && Tracker.CurrentVisit.CampaignId == campaign)
            {
                return;
            }

            var current = HttpContext.Current;
            var cookie = current.Response.Cookies["SC_ANALYTICS_SESSION_COOKIE"];
            if (cookie == null)
            {
                cookie = new HttpCookie("SC_ANALYTICS_SESSION_COOKIE");
                current.Response.Cookies.Add(cookie);
            }
            cookie.Value = "";
        }

    }
}
Gonna answered 14/7, 2014 at 23:16 Comment(0)
O
2

As you wrote, by default, only the first campaign triggered in a visit gets assigned to it (inside Sitecore.Analytics.Pipelines.StartTracking.ProcessQueryString). If you need to update the campaign associated to a visit, you can hook into the triggerCampaign pipeline and set the value manually.

public class AlwaysSetCampaignId : TriggerCampaignProcessor
{
    public override void Process(TriggerCampaignArgs args)
    {
        // Set the campaign ID to the current visit
        Sitecore.Analytics.Tracker.CurrentVisit.CampaignId = args.Definition.ID.ToGuid();
    }
}

This will not create a new visit, but it will change the CampaignId associated to the current visit.

I've successfully made a few tests with this in SC 7.2, but you will want to test this more thoroughly as it is modifying default behaviour.

Overweary answered 8/7, 2014 at 12:35 Comment(1)
A quick Update for Sitecore 9: Sitecore.Analytics.Tracker.Current.Interaction.CampaignId = args.Definition.IdTallie
P
1

I have had a similar issue. A few solutions: 1. If actually you want a personalisation rule to trigger if a campaign has ever been triggered. Do as follows (This can be seen against support ticket I raised 407150):

  1. Go to "/sitecore/system/Settings/Rules/Definitions/Elements/Visit/Campaign was Triggered" and duplicate this item
  2. In the copied item, change text to e.g. "where the [CampaignId,Tree,root=/sitecore/system/Marketing Center/Campaigns,specific] campaign was ever triggered during the current visit"
  3. In the copied item, change type to your own class and your own assembly
  4. Create new assembly with the condition class like in the following example:

    public class HasCampaignAtAllCondition : WhenCondition where T: RuleContext { private Guid CampaignGuid { get; set; } public string CampaignId { get; set; } protected override bool Execute(T ruleContext) { Assert.ArgumentNotNull(ruleContext, "ruleContext"); try { this.CampaignGuid = new Guid(this.CampaignId); } catch { Log.Warn(string.Format("Could not convert value to guid: {0}", this.CampaignId), base.GetType()); return false; } return Tracker.Visitor.DataSet.PageEvents.Any(row => row.PageEventDefinitionId == new Guid("{F358D040-256F-4FC6-B2A1-739ACA2B2983}") && row.Data == this.CampaignId); }
    }

If you want to reset a visit then you can look into the following:

if (HttpContext.Current.Request.Cookies["SC_ANALYTICS_GLOBAL_COOKIE"] != null)
{
    if (!string.IsNullOrEmpty(HttpContext.Current.Request.Cookies["SC_ANALYTICS_GLOBAL_COOKIE"].Value) && QueryStringHelperFunctions.GetQueryStringBool(HttpContext.Current.Request.QueryString, "forcenewvisitor", false))
    {
        HttpContext.Current.Response.Cookies["SC_ANALYTICS_GLOBAL_COOKIE"].Value = "";
        HttpContext.Current.Response.Cookies["SC_ANALYTICS_SESSION_COOKIE"].Value = "";
    }
}

This is covered in more detail here:http://www.sitecore.net/Community/Technical-Blogs/Charlie-Darney/Posts/2014/06/Sitecore-Project-Create-Reset.aspx

To be honest Tracker.EndVisit(true); should do the trick, it invalidates the visit cookie, if you add true it invalidates the visitor, but you have to be careful where you call it, hence processor and button approach here.

Pardew answered 7/7, 2014 at 16:52 Comment(7)
ps no matter what i did I couldnt get it to format the first code block as code, ctrl-K didnt work.Pardew
Thanks for the new rule condition -- this only solves part of the problem (personalization), but does not fix the discrepancy in reporting. The problem with invalidating the current visit via cookie (whether directly as you do, or via Tracker.EndVisit) seems to be that it does not restart the visit within the current request, which is a requirement in order for the marketing campaign to be associated with the new visit.Gonna
True but if you implement as the blog post says can you just not trigger the campaign using sc_camp=xxxx&forcenewvisit=true added to a page request?Pardew
This is essentially what I'm already doing, if you look at the code above. I just don't require the forcenewvisit query string. This does not result in a new visit with the associated marketing campaign. From what I can tell, you would need to issue a 2nd request, after the visit has been invalidated, in order to trigger the marketing campaign on the new visit. Looking for a solution which does not require this.Gonna
@techphoria414 What happens if you move your code to the beginning of startTracking or an earlier pipeline?Cubit
If you cant issue anther request maybe work out which processors in the httpbeginrequest pipeline trigger the functionality you need and then added them to your custom definition so they trigger after you reset the visit. You might be able to pass the current request, it will take a bit of reflecting of the code I guess. Just an idea, will look into it a bit further tomorrow.Pardew
Andrew -- Turned out that setting the cookie value directly was indeed the solution. See full explanation in the accepted answer.Gonna
M
1

This hasn't been tested thoroughly, but it appears to be creating a new record in the Visits table with the corresponding CampaignId when I change the sc_camp querystring value. Basically, I ripped out the CreateVisit method from the Sitecore.Analytics.Pipelines.InitializeTracker.Initialize pipeline processor (along with a couple other private methods for support - argh, private methods!).

Not terribly elegant but it does seem to work, though it may require some more conditional checks around when to create a new visit. I placed this processor after the Sitecore.Analytics.Pipelines.InitializeTracker.Initialize processor.

public class RestartVisitOnNewCampaign : InitializeTrackerProcessor
{
    public override void Process(InitializeTrackerArgs args)
    {
        if (HttpContext.Current == null)
        {
            args.AbortPipeline();
        }

        //no need to restart visit if visit is new
        if (Tracker.CurrentVisit.VisitPageCount < 1)
        {
            return;
        }

        //look for campaign id in query string
        Guid campaign;
        var campaignStr = WebUtil.GetQueryString(Settings.GetSetting("Analytics.CampaignQueryStringKey")).Trim();
        if (string.IsNullOrEmpty(campaignStr) || !Guid.TryParse(campaignStr, out campaign))
        {
            return;
        }

        //don't restart if the campaign isn't changing
        if (!Tracker.CurrentVisit.IsCampaignIdNull() && Tracker.CurrentVisit.CampaignId == campaign)
        {
            return;
        }

        Tracker.EndVisit(false);

        //restart visit by setting new ID
        //var visitCookie = new VisitCookie();
        //visitCookie.VisitId = ID.NewID.Guid;
        //visitCookie.Save();

        Visitor visitor = Tracker.Visitor;
        CreateNewVisit(HttpContext.Current, visitor);
    }

    protected virtual void CreateNewVisit(HttpContext httpContext, Visitor visitor)
    {
        VisitorDataSet.VisitsRow currentVisit = visitor.CurrentVisit;
        if ((currentVisit == null) || (currentVisit.VisitId != visitor.CookieVisitId))
        {
            currentVisit = visitor.CreateVisit(visitor.CookieVisitId);
        }
        currentVisit.AspNetSessionId = WebUtil.GetSessionID();
        HttpRequest request = httpContext.Request;
        byte[] ip = GetIp(request.UserHostAddress ?? string.Empty);
        string majorName = request.Browser.Browser;
        string minorName = request.Browser.Version;
        string version = request.Browser.Version;
        string str4 = request.Browser.Platform;
        string str5 = string.Empty;
        string str6 = string.Empty;
        currentVisit.Ip = ip;
        currentVisit.Browser = visitor.DataContext.GetBrowser(majorName, minorName, version);
        currentVisit.UserAgent = visitor.DataContext.GetUserAgent(request.UserAgent ?? string.Empty);
        currentVisit.GeoIp = visitor.DataContext.GetGeoIp(ip);
        currentVisit.Location = visitor.DataContext.GetLocation(string.Empty, string.Empty);
        currentVisit.RDNS = request.UserHostName ?? string.Empty;
        currentVisit.OperatingSystem = visitor.DataContext.GetOperatingSystem(str4, str5, str6);
        currentVisit.Screen = visitor.DataContext.GetScreen(GetDimensions(request));
        currentVisit.DeviceName = (Sitecore.Context.Device == null) ? string.Empty : Sitecore.Context.Device.Name;
        currentVisit.Language = Sitecore.Context.Language.Name;
        SiteContext site = Sitecore.Context.Site;
        if (site != null)
        {
            currentVisit.MultiSite = site.Name;
        }
        var args = new CreateVisitArgs(currentVisit, request);
        CreateVisitPipeline.Run(args);
    }

    protected virtual byte[] GetIp(string userHostAddress)
    {
        IPAddress address;
        if (IPAddress.TryParse(userHostAddress, out address))
        {
            return address.GetAddressBytes();
        }
        Log.Warn("Failed to parse ip address: " + userHostAddress, this);
        return new byte[4];
    }

    protected virtual string GetDimensions(HttpRequest request)
    {
        HttpBrowserCapabilities browser = request.Browser;
        if (browser == null)
        {
            return string.Empty;
        }
        return string.Format("{0}x{1}", browser.ScreenPixelsWidth, browser.ScreenPixelsHeight);
    }
}
Mighell answered 8/7, 2014 at 15:47 Comment(1)
Thanks for this Adam, got me looking in the right place, but the solution did not seem to trigger the new campaign on the same request.Gonna
M
0

not sure if this helps or not but Mike Casey blogged awhile ago about the DMS and Google Analytics. Not sure if this helps or not but wanted to at least point it out:

Good luck.

Matusow answered 7/7, 2014 at 15:20 Comment(1)
Thanks for this Kyle. I'd seen the article comparing campaign setup. Doesn't address the differences in behavior between the two platforms though.Gonna
C
0

Have you tried calling Invalidate() on the cookie and also InvalidateVisitorCache()?

Content answered 7/7, 2014 at 15:46 Comment(1)
Tracker.EndVisit invalidates the cookie. However this is not enough, as it just ends the current visit, it does not start a new one within the current page request.Gonna
G
0

Thanks everyone for the input. Andrew's answer actually ended up being the closest, so I've awarded him the bounty.

The issue in the end appeared to be the Visitor.Settings.IsFirstRequest property, which ultimately is derived from VisitCookie.IsFirstRequest. If this property is false, a new visit will not be created, and the current page request won't be associated with the new visit. This also needs to happen in order for the page to be classified as a "landing page," which would associate the campaign with it.

IsFirstRequest is set in VisitCookie.Load(), and compares the ASP.NET Session ID in the cookie to the current ASP.NET Session ID. This is why invaliding the cookie, or changing the Visit ID was not enough. Unfortunately there's no way to change the Session ID value using the VisitCookie object, so the easiest thing to do, which appears to work, is to just empty the cookie value directly, before the Sitecore.Analytics.Pipelines.InitializeTracker.Initialize processor runs.

From my testing, this results in a new visit whenever the campaign ID changes. I can see this in both the Visits table and when personalizing based on Campaign ID.

namespace ActiveCommerce.Training.PriceTesting.Analytics
{
    public class RestartVisitOnNewCampaign : InitializeTrackerProcessor
    {
        public override void Process(InitializeTrackerArgs args)
        {
            if (HttpContext.Current == null)
            {
                args.AbortPipeline();
                return;
            }

            //no need to restart visit if visit is new
            if (Tracker.Visitor.Settings.IsNew || Tracker.Visitor.Settings.IsFirstRequest || Tracker.CurrentVisit.VisitPageCount < 1)
            {
                return;
            }

            //look for campaign id in query string
            Guid campaign;
            var campaignStr = WebUtil.GetQueryString(Settings.GetSetting("Analytics.CampaignQueryStringKey")).Trim();
            if (string.IsNullOrEmpty(campaignStr) || !Guid.TryParse(campaignStr, out campaign))
            {
                return;
            }

            //don't restart if the campaign isn't changing
            if (!Tracker.CurrentVisit.IsCampaignIdNull() && Tracker.CurrentVisit.CampaignId == campaign)
            {
                return;
            }

            var current = HttpContext.Current;
            var cookie = current.Response.Cookies["SC_ANALYTICS_SESSION_COOKIE"];
            if (cookie == null)
            {
                cookie = new HttpCookie("SC_ANALYTICS_SESSION_COOKIE");
                current.Response.Cookies.Add(cookie);
            }
            cookie.Value = "";
        }

    }
}
Gonna answered 14/7, 2014 at 23:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.