Friday, October 17, 2014

Using search to redirect a user to the correct home site

image
I was recently tasked with the following challenge.

Given a two-level site structure of Region and Country, a user should be redirected to the corresponding site to where he/she works. This means if you work in Norway you should be redirected to http://intranet/About/Europe/Norway. If you work in Germany, which doesn’t have a country site of it’s own, you should be redirected to the region site at http://intranet/About/Europe.
  • About
    • Europe
      • Norway
      • Sweden
      • Denmark
    • APAC
      • China
      • India
      • Japan
    • Central Office
The URL structure of the sites replicate the taxonomy used for settings the values in Active Directory which is synchronized to the User Profile Application.

In order to search site URL’s I used Elio Struyf’s discovery for on-premises to map the crawled property ows_taxId_SPLocationSite to the managed property SPWebURL.

The solution had to be no code, which means JSOM, and I wanted the least amount of API calls. This is what I came up with conceptually using search.
  1. If you get a match in the URL for country, then that’s the right URL
  2. If there is no country match, then the URL returned with the least parts will be the right Region site. About/Europe has two parts while About/Europe/Norway has three.
  3. If the number of URL parts is the same, then prefer shorter names (as a safe guard)
I ended up with following query template where I’ve added some line spaces for readability
(
    contentclass:sts_web webname:about AND (webname:{User.Region} OR webname:{User.Country})
)
{?XRANK(cb=100) webname:{User.Country}}


The query will bring back all sites (sts_web filter), with about in the name to limit to this site hierarchy, then insert region and country values from the user’s properties if they exist.

Country will be given an XRANK boost of 100, to bring it to the top. If the top result returned has a rank score more than 100, then we have a country match and this is the right URL. If not, then we’ll sort the results on URL length, and use the shortest one.

The following code can be added to a script web part and will output the correct redirect URL. Pretty clever indeed!

The query template can be expanded to cover City as well, with one more level. Then you could boost with 1,000 for City and 100 for Country. A hit with rank above 1,000 would be the right city. The shortest URL with a rank above 100 would be the region.

<script type="text/javascript" src="/_layouts/15/sp.search.js"></script>
<script type="text/javascript">
    'use strict';
    var mAdcOW = mAdcOW || {};

    mAdcOW.getMyWork = function () {
        var onQuerySuccess = function () {
            var urls = [];
            if (results.m_value.ResultTables) {
                $.each(results.m_value.ResultTables, function (index, table) {
                    if (table.TableType == "RelevantResults") {
                        $.each(results.m_value.ResultTables[index].ResultRows, function () {                            
                            urls.push(this.webname);                             
                            if (this.Rank > 100) return; // Country exact hit
                        })
                    }
                });
            }
            if (urls.length === 0) {
                // user didn't have Region/Country properties set in the profile
                jQuery("#whereURL").text("https://intranet/about");
            } else if (urls.length === 1) {
                jQuery("#whereURL").text(urls[0]);
            } else {
                urls.sort(function (a, b) {
                    var aL = a.split('/').length - 1;
                    var bL = b.split('/').length - 1;
                    if (aL == bL) {
                        return a.length - b.length; // ASC -> a - b
                    }
                    return aL - bL; // fewer URL parts rank higher ASC                   
                });
                jQuery("#whereURL").append(urls[0]);
            }

        };
        var onQueryFail = function () {
            alert('Query failed. Error:' + args.get_message());
        };

        var clientContext = SP.ClientContext.get_current();
        var keywordQuery = new Microsoft.SharePoint.Client.Search.Query.KeywordQuery(clientContext);
        keywordQuery.set_trimDuplicates(false);
        keywordQuery.get_selectProperties().add("webname");
        keywordQuery.set_queryText("*");
        keywordQuery.set_queryTemplate("(contentclass:sts_web webname:about AND (webname:{User.Region} OR webname:{User.Country})) {?XRANK(cb=100) webname:{User.Country}}");

        var searchExecutor = new Microsoft.SharePoint.Client.Search.Query.SearchExecutor(clientContext);
        var results = searchExecutor.executeQuery(keywordQuery);
        clientContext.executeQueryAsync(onQuerySuccess, onQueryFail);
    }

    SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {
        SP.SOD.executeFunc('SP.Search.js', 'Microsoft.SharePoint.Client.Search.Query', mAdcOW.getMyWork);
    });
</script>
<h1 id="whereURL"></h1>