Improving the usability of Sitecore’s bucket facets

My dear colleague Steve McGill pointed something out to me I never noticed before. However after noticing it I could not help but be extremely bothered about it.

A true case of:

what has been seen

(I might or might not be exaggerating just a tad here)

In any case, I’m talking about the search facets when searching for bucketable content in the Sitecore Content Editor:

search.png

So far so good. Lets select a couple of facets.

facets.png

It’s still not too bad – but now imagine if we’ve have multiple custom facets selected – especially booleans. The facet, when selected, would just say ‘0’ or ‘1’ – but we don’t know which facet this would be.

facets2

This is definitely less useful… So how can we make this more useful? The file that we’d be interested in changing here is webroot/sitecore/shell/Applications/Buckets/Scripts/ItemBucket.js.

A quick fix might be to change one line of code in that JavaScript. In line 263 (on Sitecore 8.1 update 2 at least) the line is:

facetList += '<li class="filter"><a href="javascript:void(0);" onclick="javascript:RemoveFacet(\'' + this.value + '\');" title="' + escapedText + '" class="facetClick facetClickSelected">' + innerText + "</a></li>";

Which we can change to (I’ve added the bold for emphasis):

facetList += '<li class="filter"><a href="javascript:void(0);" onclick="javascript:RemoveFacet(\'' + this.value + '\');" title="' + escapedText + '" class="facetClick facetClickSelected">' + this.value.split('|')[0] + ':' + innerText + "</a></li>";

‘this.value’ is simply the field name Sitecore facets on. I added ‘.split’ as well because it passes the value in there as well (so for English language facet it would pass ‘culture|en’).

facets3

This isn’t flawless either, since the ‘Author’ facet is using the ‘parsedcreatedby’ field in the index. If we wanted to change anything there we’d have to change the AppendFacet method in the same file to take in the display name of the facet as well (rather than the field).

One big consideration to keep in mind here is that we’re overriding a Sitecore file, which of course might change with future updates in Sitecore, so use with caution.

Campaigns, querystrings and FXM

So I saw that in Sitecore’s Federated Experience Manager (FXM) campaigns get added to a page by adding a page filter. I came across this great article that shows in detail how to set goals, campaigns and the like.

In any case, I found it a bit strange that a campaign would have to be set as a page filter, rather than being able to link to an affiliate site with the campaign tracker in  the URL like we’re used to in our normal use of Sitecore (e.g. use sc_camp=someguid).

Of course, as with most things Sitecore, we can customize FXM and I figured this would be a nice way for me to get some more experience with FXM.

The most obvious step first of course: Adding the site to FXM. This can be done through the Federated Experience Manager link on the Launch Pad, and you can then click ‘add external website’. In my case I’ve added the website Sandbox FXM with the domain sandbox.fxm.

fxm-new-site

FXM will then give you a beacon (in the ‘Generated script’ field) which you can parse into your external site. I’ve just dropped mine right before the closing body tag.

Don’t forget to create a site in IIS and update your hosts file as well to make sure you can browse it.

After I pasted that I ran into my first issue: I kept getting the error “Error: Failed to load resource: the server responded with a status of 400 (DomainInvalid)” when loading the FXM site. This has a simple fix: Publish your FXM site, which you can do from the FXM screen.

So, that resolved we can start to write some code. By looking at the TrafficTypeProcessor I found I need to inherit from ITrackPageVisitProcessor and ITrackPageVisitProcessor<ITrackPageVisitArgs>.

Then, in the code I will grab the URL of the page, get the sc_camp querystring from it and register that campaign

public class RegisterCampaignProcessor : ITrackPageVisitProcessor, ITrackPageVisitProcessor<ITrackPageVisitArgs>
{
    public void Process(ITrackPageVisitArgs args)
    {
        var url = System.Web.HttpContext.Current.Server.UrlDecode(WebUtil.GetRawUrl());
        var campaign = string.Empty;
        if (url.IndexOf("sc_camp=") > 0)
        {
            campaign = url.Substring(url.IndexOf("sc_camp=") + 8, 38);
        }
        if (!string.IsNullOrEmpty(campaign))
        {
            var campaignItem = args.Context.Database.Database.GetItem(campaign);
            if (campaignItem != null)
            {
                CampaignItem item = new CampaignItem(campaignItem);
                args.CurrentPageVisit.TriggerCampaign(item);
            }
        }
    }
}

When we do WebUtil.GetRawUrl() we will actually get back something similar to this:
/sitecore/api/ssc/Beacon/Service/beacon/trackPageVisit/?contactId=6b19088ea2c54bdab775231697a5e0a3|False&sessionId=44ir0jmxxilktj3xiaqlidt5&page=http://sandbox.fxm/?sc_camp={87925D56-34D9-4080-A725-6EC8B3B4CB0A}&referrer=&rt=1453220938704
So I’ll grab the campaign from there – although in a production scenario you might want to be a bit more careful about this because this particular code requires you to have the accolades as part of the campaign id (as well as not having any checks on whether it’s a GUID etc). From that GUID I resolve the campaign item as an Item, create a CampaignItem object from that and trigger that on the campaign.

We can use our Sitecore include files to patch this processor in in the correct place – in my case I’ve patched it right before the TrafficTypeProcessor:

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <group groupName="FXM" name="FXM">
                <pipelines>
                    <tracking.trackpagevisit>
                        <processor patch:before="*[@type='Sitecore.FXM.Pipelines.Tracking.TrackPageVisit.TrafficTypeProcessor, Sitecore.FXM']" type="Sandbox.FXM.TriggerCampaign.RegisterCampaignProcessor, Sandbox" />
                    </tracking.trackpagevisit>
                </pipelines>
            </group>
        </pipelines>
    </sitecore>
</configuration>

So that’s now all well and good but how do I figure out whether it’s working? If I had an empty xDB it would be easy enough but I actually have a lot of dummy data in there so had to be a bit more creative. In Mongo I wrote a query to figure out which data came from FXM:

db.Interactions.find({ "SiteName" : "Sandbox_FXM" }, { "ContactId" : 1 } )

In there, I can see that a campaign has been triggered, but I’d also like to see my xFile, andof course the ContactId is then shown as a binary type 3 value. I’m using MongoVue to view my Mongo data, which actually comes with a very handy option:

mongo-vue-preferences.png

Using that I can find the GUID I had for that visitor. I can then open any random xFile and copy/ paste the GUID of this particular Contact in the URL.

fxm-contact-xfile.png

Although for the people paying attention: The contact ID is also in the URL we had from running WebUtil.GetRawUrl(). Sadly I couldn’t actually use that in my example as it was a new InPrivate window and I had closed it.

Saving WFFM form result as PDF

In the Web Forms For Marketers module I wanted to be able to save the responses as PDF, to then be able to send that PDF with the responses, save the PDF in the Media Library or whatever else.

So there are 2 parts to this:

  1. Create a new custom WFFM Save Action
  2. Create a PDF using some kind of library. In my case I used PDFSharp, which is open source and easy to use but I haven’t done any research into PDF libraries so I couldn’t say whether there’s better ones out there

First up is creating the new WFFM Save Action, which is fairly simple. We’ll need a Sitecore definition item and a bit of code as usual. The WFFM Save Actions can be found in /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Save Actions. I’ve created a ‘Save PDF’ item there based on the ‘Submit Action’ template.

wffm-save-action

Don’t forget to set the Assembly and Class fields.

Then the code. When we’re creating a WFFM save action we need to make sure our class inherits from the WffmSaveAction class. We’ll also have to reference Sitecore.WFFM.Abstractions and Sitecore.WFFM.Actions. For our PDF creation functionality we’ll also need to add a reference to the PdfSharp assembly.

In my case, this is how my class looks:

namespace Sandbox.WFFM.Actions
{
    using Sitecore.WFFM.Abstractions.Actions;
    using Sitecore.WFFM.Actions.Base;
    using Sitecore.Data;
    using PdfSharp.Pdf;
    using PdfSharp.Drawing;

    public class SavePDF : WffmSaveAction
    {
        public override void Execute(ID formId, AdaptedResultList adaptedFields, ActionCallContext actionCallContext = null, params object[] data)
        {
            // Our code goes here.
        }
    }
}

Now in the Execute method we’ll start by creating a new PDF document, adding a new page and writing some content there.

PdfDocument pdf = new PdfDocument();
PdfPage page = pdf.AddPage();
XGraphics graph = XGraphics.FromPdfPage(page);
XFont font = new XFont("Verdana", 20, XFontStyle.Bold);
var i = 0; // Used for positioning the text
foreach (AdaptedControlResult field in adaptedFields) // Loop through all fields in the form
{
    graph.DrawString(string.Format("{0}: {1}", field.FieldName, field.Value), font, XBrushes.Black,
new XRect(40, i, page.Width.Point, page.Height.Point), XStringFormats.TopLeft);

    i = i + 40; // Make sure to increment i so we don't write on the same line over and over again
}

pdf.Save(@"C:\temp\sandboxtest.pdf"); // Probably shouldn't write this here... If we do we need something smart for the naming

Then to test. I’ve added a form on one of our news articles:
wffm-form
And this is the resulting PDF after filling in the form:
pdf-result

Sitecore – Retrieve data from MongoDB

As we all know, Sitecore’s xDB collects a TON of data. So if we want to pull some of that data from the MongoDBs, how do we go about that? I had never worked with Mongo before, so in case you haven’t either I hope this is of use to you.

Initially, to view the documents in the Mongo collections I used the free edition of MongoVue. It is great for things like viewing how many contacts you have in your Contacts collection, or digging into the details of the Interactions.

Mongo Collections Overview

Now, if I want to see some of the Interactions, that’s completely possible as well. For instance, I can either use the View option on the Contacts collection, or I can write a query.

Viewing:

Contact details

Writing a query:

Sample query in MongoVue

Of course, you can do the same thing in the Mongo shell as well, if you prefer.

I’ve found that I keep re-using a couple of different queries, so here are a couple of the ones I found useful:

Finding interactions with a certain amount of Engagement Value points:


{ "Value" : { $gt : 10 } }

This will get all interactions with more than 10 Engagement Value points. Instead of $gt (greater than), you can also use other operators such as $lt (less than), $lte (less than or equal to) or $gte (greater than or equal to).

For more query operators, view the Mongo Docs page.

In case you’re using the Mongo shell, you might want to add a limit to the results as well, and format the documents. In the Mongo shell, your query might look like this (assuming you’ve set the db to be your Mongo Analytics database):


db.Interactions.find
( 
    { "Value" : { $gt : 10 } } 
)
.limit(1)
.pretty()

Limit here is pretty obvious, it will only result the first result in our case. Pretty will format the document, in a similar way to CTRL+K, CTRL+D in Visual Studio. Another thing you might want to add in the query for the Mongo shell is a projection of the fields you’d like to be displayed or hidden. Projections can be used as follows:


db.Interactions.find
(
    { 
        "Value" : { $gt: 10 } 
    }, 
    { "ContactId" : 1 } 
)
.limit(1)
.pretty()

In this case, we’ll only be displaying the ContactId field. To exclude a field, you can set it to 0 instead. For more information on Projection in Mongo, I again refer you to the Mongo Docs page.

Another query I tend to use is to figure out how many contacts I have from various countries. From MongoVue:


{ "GeoData.Country" : "United Kingdom" }

I have to admit that I’m not sure how to count the number of records in MongoVue.

From the Mongo shell:


db.Interactions.find( { "GeoData.Country" : "United Kingdom" } ).count()

Lastly, finding interactions with (for instance) 2 page events:

Again, in MongoVue:


{ “Pages.PageEvents” : { $size: 2 } }

In the Mongo shell:


db.Interactions.find( { “Pages.PageEvents” : { $size: 2 } } ).pretty()

On a last note, it’s important to realize the fieldnames are case sensitive.

On the very last note, for some more (beginners) details on MongoDB, not only writing queries but also architectural information, I would very highly recommend the Introduction to MongoDB course on PluralSight.

I hope this will be of some use:-)

Commerce Server – Cart not persisting

So I was trying to get add the ‘add to cart’ functionality to my website. Basically I would get the cart, then create a cart line based on the product and add that to the cart object:


var cartLine = new CommerceCartLine(product[CommerceConstants.KnownFieldIds.CatalogName], product.Name, null, quantity); // null because I don't currently support variants.
var cart = GetCart();
var request = new AddCartLinesRequest(cart, new[] { cartLine });
myServiceProvider.AddCartLines(request);

And while I was stepping through the code I could see the cart being updated. The only problem was after a reload (or after adding another product to the cart) the cart would be empty again.

At first I thought the problem was my code, but no matter what I changed, it didn’t seem to get any better. I figured I’d better check the Commerce Server Customer and Order Manager tool to find out what was happening to my baskets, and it was there that I found the issue. It turns out that by default the Customer and Order Manager wants to have the Profiles webservice and the Orders webservice to run in https mode. As I was working on my local development environment that wasn’t really much of an option, so I had to figure something else out. Luckily I found a blog post on MSDN which explains what to do.

In short: There’s a CustomerAndOrdersManager.exe.config file, in the install directory of Commerce Server (by default C:\Program Files (x86)\Commerce Server 11\Business User Applications) . In the <applicationSettings> you’ll be able to find the setting “AllowHTTP” which is set to False by default. Set it to True, recycle the app pool and you’re in business!


<applicationSettings>
...
<setting name="AllowHTTP" serializeAs="String">
<value>True</value>
</setting>
...
</applicationSettings>

Hidden settings: MinimalProfileScoreCount

I was running through the Sitecore Experience Editor the other day, where I wanted to check whether my various patterns would be showed the correct personalized content. The easiest thing to use is the Experience Explorer, which you can get to through the Other button in the Mode chunk.

Opening the Experience Explorer

This will open the Experience Explorer on the left-hand side of the website with the Experience Viewer on the right-hand side. In the Onsite Behavior tab I set the proper scores to match a particular pattern card. After clicking ‘Apply’ I didn’t see any difference until I checked the Onsite Behavior tab in the Experience Viewer.

Experience Viewer

Easily managed by just clicking ‘Apply’ 2 more times, but a bit annoying. Turns out, there’s a way of going around this: The Analytics.Patterns.MinimalProfileScoreCount setting. By default it won’t show up in your settings so you’ll have to manually add it. After applying this setting the profile personalization will be applied directly after setting the value of Analytics.Patterns.MinimalProfileScoreCount to 1.

Before you change it though, keep in mind that this will not only affect the Experience Explorer, but also the ‘normal’ website – and as such might have adverse effects.

Quick tip: Sitecore Security packages

If you ever want to move Sitecore items over to different environments the easiest way to do this is to create a Sitecore package. This is no different for Sitecore Users and Sitecore Roles. After opening the Package Designer, click the ‘Security Accounts’ button in the ribbon.

The package designer screen

Using the pop-up, you’ll be able to view which roles/ users you have in your package, or add/ remove them using the buttons. Clicking the Add button will pop up another window, which allows you to select your users and roles.

Adding roles and users

After you’ve created your package you can install this on your other environment, using the Package Installer. If the users or roles already exist you’ll get a message that Sitecore will skip the installation of that user or role.

Roles are now all ready for use. Users however need two additional steps to be active:

Firstly, they need to be activated.

Sitecore User Manager showing disabled users

As you can see, in the Locked column, they’ll show up as Disabled. To enable the users the Enable button in the ribbon can be used.

Secondly, the users need their password to be reset. Do this using the Change Password button.

The Reset Password popup

Clicking ‘Generate’ will randomly create a new password, which you can then select and copy to the Old Password field. Fill in the new password, and you’ll be ready to go!

Commerce Server – Missing ProductId in Product Definition

For one of my Sitecore projects using Commerce Server I wanted to add a new Product Definition (to see how that’s done, go to my previous blogpost on just that subject).

So after I created a new Product Definition and had the definition to my specifications I updated my data templates in Sitecore and started working on some products and product pages. To work on products I’d use the SPEAK application Merchandising Manager (can be downloaded here for Sitecore 7.5 or here for Sitecore 7.2), which is where I ran into an issue. Opening a product based on my new product definition an exception was thrown. The error is ‘Sequence contains no matching element’, with the stacktrace showing that a renderer was trying to get a field called ‘ProductId’. Looking at my definition, the ProductId property was indeed missing – and what’s more, it wasn’t possible to add it either as displayed in the screenshow below.

The New Definition window

There is an extremely simple workaround for this: in Sitecore, we can go to our data templates, by default located in /sitecore/templates/Commerce/Catalog Generated. Find the template generated for the product (or category) definition, in our case the MyDefinition template, and add a Single-Line Text field called ProductId.

Added ProductId field

Save that and that’ll take care of the issue. One thing left though. If we were to browse to the Merchandising Manager and open our product now it would load up, but the ProductId would still be blank and there would be an error on the top of the page because of this. Of course, we could go ahead and fill in the ProductId and save it, or we could open the Content Editor, browse to our product and hit Save. This will automatically set the ProductId field we created and will fix the general error we were having. Of course, if you have a lot of products, you might consider creating a script to do set the ProductId for you.

Sitecore Commerce Server – Products and Definitions

I’ve been working with Commerce Server now for a while, trying to enable it on our dev environment. After the installation process (which might require its own post as well), I figured I’d write something about how to get product definitions, products, category definitions and categories from Commerce Server into Sitecore.

Creating definitions

The Product Definitions and Category Definitions (and I’m guessing Property Definitions also, but I haven’t used custom properties yet) work in a similar way. They can be created in the Commerce Server Catalog and Inventory Schema Manager, using the appropriate view in the Views panel on the bottom left in the screenshot below.

CategoryDefinitions

Creating a new definition is reasonably straightforward. Just give the definition a name and optionally a description, and select the properties you’d like that particular definition to use. After this, you’re done! You can now create new products or categories based on that new definition.

Creating products and categories

Creating new categories and products can be done in the Commerce Server Catalog Manager. Right-click where you’d like to add a new category or product, and use the context menu to create a new category or product. Alternatively, you could use the SPEAK app Merchandising Manager, on which I’m sure more blog posts will be coming as well. In the meantime, more on the Merchandising Manager can be found here. When using the Merchandising Manager, you can skip to the section about getting the definitions to Sitecore. When using the Commerce Server Catalog Manager, you can follow the steps below to get create products or categories.

ContextMenu

In this screenshot you can see I’m using the MVC demo store (of which you can download the source on the Sitecore Marketplace or the Sitecore package from SDN if you don’t need the source).

Creating a new category and/ or new product will pop up with a wizard which will run through the various steps (Selecting the definition, entering the properties followed by the custom properties, selecting primary and additional categories and then selecting relations for the category or product). This is all very easy to do.

All done, we have our products and categories in Commerce Server.
Now, how do we get this to go into Sitecore? If the product and category definitions already exist in Sitecore,  products and categories will be synced automatically, although caching might get in your way sometimes.

Syncing products and categories

If they don’t get synced automatically, and the definitions exist, there’s two options:

  1. In the Commerce Server Catalog Manager, in the Tasks pane, click ‘Refresh Site Cache’.
    SiteCache
  1. After logging into Sitecore, you can go to the Content Editor, go to the Sitecore Commerce tab in the ribbon, and click ‘Refresh Sitecore Cache’.
    SitecoreSiteCache

Syncing definitions

To make sure your definitions go into SItecore (they will be used as the data templates), you need to go into the Content Editor and click ‘Update Data Templates’ in the Sitecore Commerce tab in the ribbon.

DataTemplates
Don’t forget to publish – in fact, when there’s new definitions and new products/ categories I’ve had to to a full republish in some cases- and don’t forget to re-index as well.