Tuesday, October 17, 2017

Creating the Status Page, Part 2: AJAX calls

This post continues the series of how I built the departmental status page in SharePoint.

Go and read the Overview and Part 1 to catch up.

It gets a little more complicated here as we will be calling out to web API's, first to get data from Zendesk and store to SharePoint, and secondly to get data out of SharePoint itself to display in HighCharts.

You will need to add a Content Editor Web Part to the page that references a text file containing your javascript which you have store in a document library (I used Site Assets for convenience).

One of the key things you need to know about SharePoint is that we want to make sure all of the scripts load before we want to do our thing.  The way that I make sure that happens in code is to wrap everything in a "runLast" function and then use this method

_spBodyOnLoadFunctionNames.push("runLast"); 

right before the end script tag.

Why you ask?  SharePoint uses its own body onload function which will collide with your document.ready either running before or after.  This onload function is called _spBodyOnLoadFunctionNames and will resolve any ECMAScript load issues you have been having.

Ok, the next part gets a little hairy, so i will break it down for you.

First, Zendesk does not have an API that calls up any historical data.  You have to use a view in Zendesk that gives you each piece of data you need.  In this case I created three views, one of all open tickets, one of tickets opened within the past 24 hours, and finally one of tickets closed within the past 24 hours.

This means we need to make three http GET requests and wait for the data counts to return and then add them to columns in a SharePoint list, but only if we don't have today's data in Sharepoint.

Let's start with a check before the RESTful call to SharePoint to see if the Zendesk data is there:

let currDate = formatDate(new Date());

$.ajax({
            url:_spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbyTitle('Zendesk%20Tickets')/Items/?$select=Title,Count,DsrDate,Opened,Closed&$filter=DsrDate ge datetime'" + formatDate(subDays(new Date(), 60)) + "T00:00:00Z' and DsrDate le datetime'" + formatDate(addDays(new Date(), 1)) + "T00:00:00Z'",  
            method: "GET",  
            headers: { "Accept": "application/json; odata=verbose" },  
            success: function (data) {  
            dataObject = data.d.results;
                
                Array.prototype.forEach.call(data.d.results, function(dataObject){
                spCount.push(dataObject.Count);
                spOpen.push(dataObject.Opened);
                spClosed.push(dataObject.Closed);
                spTeam.push(dataObject.Title);
                spDate.push(dataObject.DsrDate);
                });
                if(spDate.length > 0){
                    latestSPDate = spDate[spDate.length-1].split("T")[0];
                }
                if(latestSPDate != currDate)
                {
                    //SharePoint is missing date entry for Zendesk
                    getTickets();
                }
                
            },  
                error: function (err) {  
                console.log("error: " + JSON.stringify(err));  
                }  

        });

The if statement --- if(latestSPDate != currDate) checks to see if we have the most current day's worth of data, if not we need to make three calls out to Zendesk to retrieve it via my getTickets() function.  If we don't have an authentication token cached local we create one with the startAuthFlow() function.

function getTickets() {

if (localStorage.getItem('zauth')) {
var access_token = localStorage.getItem('zauth');
makerequest(access_token);
} else {
startAuthFlow();
}

}

Zendesk requires that you have an OAuth token in your web call in order for it to return data.  You will need to set up on the Zendesk side of things first.  Use this link.  You will need a service account that has the correct privileges to get to the data.

If we don't have the token need to get it in JavaScript before we make our API call.

function startAuthFlow() {
var endpoint = 'https://yourzendeskurl/oauth/authorizations/new';
var url_params = '?' +
'response_type=token' + '&' +
'redirect_uri=https://yoursharepointsite/Pages/Home.aspx' + '&' +
'client_id=sharepoint_integration_for_zendesk' + '&' +
'scope=' + encodeURIComponent('read write');
window.location = endpoint + url_params;

}


function readUrlParam(url, param) {
param += '=';
if (url.indexOf(param) !== -1) {
var start = url.indexOf(param) + param.length;
var value = url.substr(start);
if (value.indexOf('&') !== -1) {
var end = value.indexOf('&');
value = value.substring(0, end);
}
return value;
} else {
return false;
}
}

var url = window.location.href;
    if (url.indexOf('https://yoursharepointpage') !== -1) {
if (url.indexOf('access_token=') !== -1) {
var access_token = readUrlParam(url, 'access_token');
localStorage.setItem('zauth', access_token);

window.location.hash = "";
makerequestAll(access_token);
}

if (url.indexOf('error=') !== -1) {
var error_desc = readUrlParam(url, 'error_description');
var msg = 'Authorization error: ' + error_desc;
showError(msg);
}
  }

And finally we make the three asynchronous http requests, but wrap them in a jquery when/done function so that we have all the data before we attempt to write it into the SharePoint list during the done function.


function makerequest(token) {
console.log('Making requests to Zendesk');

var allTickets = $.ajax({
url: 'https://yourzendeskurl/api/v2/views/275146127/execute.json',
method: "GET",      
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
success: function (data) {
ZendeskTicketCount = data.count;
console.log("Total:" + ZendeskTicketCount);
},
error: function () { console.log('Failed to retrieve all ticket count from Zendesk API');}

}),
openTickets = $.ajax({
url: 'https://yourzendeskurl/api/v2/views/114124387753/execute.json',
method: "GET",      
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
success: function (data) {
ZendeskOpenCount = data.count;
console.log("Total:" + ZendeskOpenCount);
},
error: function () { console.log('Failed to retrieve open ticket count from Zendesk API');}

}),
closedTickets = $.ajax({
url: 'https://yourzendeskurl/api/v2/views/114124363474/execute.json',
method: "GET",      
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
},
success: function (data) {
ZendeskClosedCount = data.count;
console.log("Closed:" + ZendeskClosedCount);
},
error: function () { console.log('Failed to retrieve closed ticket count from Zendesk API');}

}),
zendeskDone = $.when(allTickets, openTickets, closedTickets);

zendeskDone.done(function(){
console.log("Zendesk load finished.  Adding data to SharePoint");
var itemType = GetItemTypeForListName('Zendesk Tickets');
var item = {
"__metadata": { "type": itemType},
"Title": "All",
"Count": ZendeskTicketCount,
"Opened": ZendeskOpenCount,
"Closed": ZendeskClosedCount,
"DsrDate": new Date()
};
createListItem(_spPageContextInfo.webAbsoluteUrl, 'Zendesk Tickets', item)
.done(function (data) {
console.log('Zendesk count has been created successfully');
})
.fail(function (error) {
console.log(JSON.stringify(error));
});
});
zendeskDone.fail(function(){
console.log("Zendesk load did not finish.");
});

And you will need the getForDigest() function to grab the form digest from SharePoint to avoid a security error, and the actual http post inside createListItem to insert the data.

function getFormDigest(webUrl) {
    return $.ajax({
        url: webUrl + "/_api/contextinfo",
        method: "POST",
        headers: { "Accept": "application/json; odata=verbose" }
    });
}


function createListItem(webUrl, listName, itemProperties) {
    return getFormDigest(webUrl).then(function (data) {

        return $.ajax({
            url: webUrl + "/_api/web/lists/getbytitle('" + listName + "')/items",
            type: "POST",
            processData: false,
            contentType: "application/json;odata=verbose",
            data: JSON.stringify(itemProperties),
            headers: {
                "Accept": "application/json;odata=verbose",
                "X-RequestDigest": data.d.GetContextWebInformation.FormDigestValue
            }
        });
    });

}

That should get you through the calling out to Zendesk and then writing the data back to the SharePoint list for historical record keeping.  In my next article, we can tackle requesting the data from SharePoint and then displaying to a graph in HighCharts.  
Don't worry, if you have made it this for the rest should be easy.
Series Links

No comments:

Post a Comment