How many times have you had customers tell you that they want their service catalog to be “like Amazon”?
Well, that got me to thinking; surely we can borrow some ideas from Amazon, implement them in the service portal and hopefully get those customers one step closer to their Amazon like dream. The outcome was an “Item Suggestions” widget that emulates the “Customers who bought this item also bought” section that you find at the bottom of any Amazon listing:
And this is what the widget I have developed looks like in the service portal:
In order to create this type of functionality there are a few components that we are going to need:
- A custom table to hold the data for us
- A business rule to populate the newly created table with data
- A widget to display the data that we are now recording
Table
Create a very basic custom table called “Item Suggestion” [u_item_suggestion] with three fields:
- Original [u_original] – Reference to Catalog Item
- Additional [u_additional] – Reference to Catalog Item
- Score [u_score] – Integer
Business Rule
Create a business rule on the sc_request table with the following configuration:
- Name – Populate Item Suggestions
- Table – sc_request
- Advanced – true
- When – After
- Insert – true
- Script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
(function executeRule(current, previous /*null when async*/) { //Go get any of the requested items under the newly created request var reqItems = new GlideRecord('sc_req_item'); reqItems.addQuery('request', current.sys_id); reqItems.query(); //create an empty array to hold our items var items = []; //loop through the items and add them to the array if they aren't already in there while(reqItems.next()) { if(items.indexOf(reqItems.cat_item.sys_id.toString()) == -1){ items.push(reqItems.cat_item.sys_id.toString()); } } //nested for loop to go through each item combination for(x=0;x<items.length;x++) { for(y=0;y<items.length;y++){ //if the items aren't the same if(items[x] != items[y]) { //look up the item suggestion table querying for those items var suggestionCheck = new GlideRecord('u_item_suggestion'); suggestionCheck.addQuery('u_original', items[x].toString()); suggestionCheck.addQuery('u_additional', items[y].toString()); suggestionCheck.query(); //if a record already exists for that combination, incremenet the score if(suggestionCheck.next()) { suggestionCheck.u_score = suggestionCheck.u_score + 1; suggestionCheck.update(); } //if a record doesn't exist for that combination, create one with an initial score of 1 else{ suggestionCheck.u_original = items[x].toString(); suggestionCheck.u_additional = items[y].toString(); suggestionCheck.u_score = 1; suggestionCheck.insert(); } } } } })(current, previous); |
Widget
HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<div> <h4 ng-if="data.error">{{data.error}}</h4> <div ng-if="data.items.length > 0"> <h4 class="m-t-none">${Customers who ordered this item also ordered:}</h4> <div class="row"> <div class="col-sm-6 col-md-4" ng-repeat="item in data.items"> <div class="panel panel-{{::options.color}} b"> <a href="?id={{item.page}}&sys_id={{item.sys_id}}" class="panel-body block"> <div class="overflow-100"> <h4 class="m-t-none m-b-xs">{{item.name}}</h4> <img ng-src="{{item.picture}}" ng-if="item.picture" class="m-r-sm m-b-sm item-image pull-left" /> <div class="text-muted item-short-desc">{{item.short_description}}</div> </div> </a> <div class="panel-footer"> <a href="?id={{item.page}}&sys_id={{item.sys_id}}" class="pull-left text-muted">${View Details}</a> <span ng-if="item.price != '$0.00'" class="pull-right item-price font-bold">{{item.price}}</span> </div> </div> </div> </div> </div> </div> |
Server Script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
(function() { //get the sys_id from the url data.cat_item_id = $sp.getParameter("sys_id") || $sp.getParameter('sl_sys_id'); //get the catalog home page data.sc_catalog_page = $sp.getDisplayValue("sc_catalog_page") || "sc_home"; //get 3 suggestions where the original item is the item we are looking at //order the results by score (desc) and then by item name (asc) var getSuggestions = new GlideRecord('u_item_suggestion'); getSuggestions.addQuery('u_original', data.cat_item_id); getSuggestions.addQuery('u_additional.active', 'true'); getSuggestions.addQuery('u_additional.sys_class_name', 'NOT IN', 'sc_cat_item_wizard,sc_cat_item_content'); getSuggestions.orderByDesc('u_score'); getSuggestions.orderBy('u_additional.name'); getSuggestions.setLimit(3); getSuggestions.query(); var items = data.items = []; //go through the suggestions and build an item object for each while (getSuggestions.next()) { // Does user have permission to see this item? if (!$sp.canReadRecord("sc_cat_item", getSuggestions.u_additional.sys_id.getDisplayValue())) continue; var item = {}; var gr = new GlideRecord('sc_cat_item'); gr.get(getSuggestions.u_additional); $sp.getRecordDisplayValues(item, gr, 'name,short_description,picture,price,sys_id'); item.sys_class_name = getSuggestions.u_additional.sys_class_name + ""; item.page = 'sc_cat_item'; if (item.sys_class_name == 'sc_cat_item_guide') item.page = 'sc_cat_item_guide'; items.push(item); } })() |
All that is left to do is add the widget to the bottom of your sc_cat_item page and start ordering items to build up the data in your custom table.
Note: If you want complete control over the relationships between items then you could inactivate the business rule on the sc_request table and manually enter records and scores on your u_item_suggestion table.
Hopefully this article has been of use to you, I think we may be quite some way off getting the catalog to the full “Amazon experience” but small enhancements like this certainly get us one step closer.