/** * Class ReportRequest * * Handles all the functionality of querying a reusable report end-point. * Requires JQuery * * @restUrl - the url of the endpoint * @dataFormat - default data format (xml or json); may be overridden * * Author: Michael Kontogiannis (michael.kontogiannis@rockstarnorth.com) */ var ReportRequest = function(restUrl, dataFormat, asyncRestUrl, asyncQueryUrl) { var xmlFormat = "xml", jsonContentType = "application/json", asyncInterval = 500; // in millisecs var _restUrl = restUrl, _dataFormat = dataFormat, _asyncRestUrl = asyncRestUrl, _asyncQueryUrl = asyncQueryUrl; /** * Function sendSingleRequest * * A single function for implementing all the data flow process. * * @pValues - the object with the values for populating the available parameters once they have been retrieved, * check respective populateParams functions inside the class * @callback - the callback function for the retrieved data * @onComplete - function to call on complete of the second ajax call (optional) * @dataFormat - override the default dataformat (optional) */ function sendSingleRequest(pValues, callback, onComplete, dataFormat) { var _dF = ((dataFormat) ? dataFormat : _dataFormat); $.ajax({ url: _restUrl, type: "GET", dataType: _dF, data: {}, async: true, success: function(params, textStatus, jqXHR) { params = ((_dF == xmlFormat) ? populateXmlParams(params, pValues) : populateJsonParams(params, pValues)); var reqData = ((_dF == xmlFormat) ? new XMLSerializer().serializeToString(params) : JSON.stringify(params)); var contentType = ((_dF == xmlFormat) ? xmlFormat : jsonContentType); $.ajax({ url: _restUrl, type: "POST", dataType: _dF, contentType: contentType, data: reqData, async: true, success: function(result, textStatus, jqXHR) { callback(result); }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); }, complete: function() { if (onComplete) onComplete(); } }); }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); } }); } /** * Function sendSingleAsyncRequest * * A single function for implementing all the data flow process in async endpoint and track the progress. * * @pValues - the object with the values for populating the available parameters once they have been retrieved, * check respective populateParams functions inside the class * @callback - the callback function for the retrieved data * */ function sendSingleAsyncRequest(pValues, callback, returnedDataType) { $.ajax({ url: _restUrl, type: "GET", dataType: _dataFormat, data: {}, async: true, success: function(params, textStatus, jqXHR) { params = ((_dataFormat == xmlFormat) ? populateXmlParams(params, pValues) : populateJsonParams(params, pValues)); var reqData = ((_dataFormat == xmlFormat) ? new XMLSerializer().serializeToString(params) : JSON.stringify(params)); var contentType = ((_dataFormat == xmlFormat) ? xmlFormat : jsonContentType); // hack for the endpoints that can't return json if (returnedDataType) _dataFormat = returnedDataType; // Place the request and to get the task identifier $.ajax({ url: _asyncRestUrl, type: "POST", dataType: _dataFormat, contentType: contentType, data: reqData, async: true, success: function(result, textStatus, jqXHR) { // Start querying the service for progress updates and results showProgressbar(); var Progress, Result, TaskIdentifier; if (_dataFormat == xmlFormat) { Progress = Number($(result).find("Progress").text()); Result = $(result).find("Result"); TaskIdentifier = $(result).find("TaskIdentifier").text(); } else { Progress = result.Progress; Result = result.Result; TaskIdentifier = result.TaskIdentifier; } if (Progress == 1.0) { if (Result != null) { callback(Result); } hideProgressbar(); } else { querySingleAsyncProcess(TaskIdentifier, callback); } }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); }, complete: function() {} }); }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); } }); } /** * Function querySingleAsyncProcess * * A function for querying the status of an asynchronous request on the server and passing the result to * callback function when it has finished * * @taskIdentifier - the id of the asynchronous request on the server * @callback - the callback function for the retrieved data */ function querySingleAsyncProcess(taskIdentifier, callback) { // If cancel is checked then stop the process if ($("#cancel-request").is(":checked")) { hideProgressbar(); // Uncheck for future requests $("#cancel-request").prop("checked", false); return; } $.ajax({ url: _asyncQueryUrl, type: "GET", dataType: _dataFormat, async: true, data: {guid: taskIdentifier}, cache: false, success: function(result, textStatus, jqXHR) { updateProgressbar(result.Progress * 100); if (_dataFormat == xmlFormat) { Progress = Number($(result).find("Progress").text()); Result = $(result).find("Result"); TaskIdentifier = $(result).find("TaskIdentifier").text(); } else { Progress = result.Progress; Result = result.Result; TaskIdentifier = result.TaskIdentifier; } if (Progress == 1.0) { if (Result != null) { callback(Result); } else { Sexy.alert(config.asyncRequestFailTxt); } hideProgressbar(); } else setTimeout(function() {querySingleAsyncProcess(TaskIdentifier, callback);}, asyncInterval); }, error: function (xhr, ajaxOptions, thrownError) { hideProgressbar(); console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError ); } }); } /** * Function sendMultipleAsyncRequest * * Helper function for choosing between different methods of querying the server. * Check sendMultipleAsyncRequestConcurrently and queryMultipleAsyncProcessesSequentially for more info * * @endpointsAndParamValues - Array of objects for each endpoint, each object has the format of: * { * restUrl: Value, * restAsyncUrl: Value, * pValues: [ Array of pValue objects ] * } * * @callback - the callback function for the retrieved data */ function sendMultipleAsyncRequest(endpointsAndParamValues, callback) { //sendMultipleAsyncRequestConcurrently(endpointsAndParamValues, callback); sendMultipleAsyncRequestSequentially(endpointsAndParamValues, callback); } /** * Function sendMultipleAsyncRequestConcurrently * * A function for implementing all the data flow process in multiple async requests in many endpoints * and track their progress. * * This one sends the all the requests together to the server and queries all of them periodically * * @endpointsAndParamValues - Array of objects for each endpoint, each object has the format of: * { * restUrl: Value, * restAsyncUrl: Value, * pValues: [ Array of pValue objects ] * } * * @callback - the callback function for the retrieved data */ function sendMultipleAsyncRequestConcurrently(endpointsAndParamValues, callback) { showProgressbar(); // Create an array to monitor the processes of each endpoint var monitoredProcesses = new Array(endpointsAndParamValues.length); // For each endpoint $.each(endpointsAndParamValues, function(i, endpoint) { // get the endpoint parameters $.ajax({ url: endpoint.restUrl, type: "GET", dataType: _dataFormat, data: {}, async: false, success: function(params, textStatus, jqXHR) { //var contentType = ((_dataFormat == xmlFormat) ? xmlFormat : jsonContentType); // for monitoring each request of the current endpoint monitoredProcesses[i] = new Array(endpoint.pValues.length); // make the request and monitor it via its guid $.each(endpoint.pValues, function(j, pValue) { var reqParams = ((_dataFormat == xmlFormat) ? populateXmlParams(params, pValue) : populateJsonParams(params, pValue)); var reqData = ((_dataFormat == xmlFormat) ? new XMLSerializer().serializeToString(reqParams) : JSON.stringify(reqParams)); monitoredProcesses[i][j] = placeAsyncProcess({asyncUrl: endpoint.restAsyncUrl, reqData: reqData}); /* $.ajax({ url: endpoint.restAsyncUrl, type: "POST", dataType: _dataFormat, contentType: contentType, data: reqData, async: false, success: function(result, textStatus, jqXHR) { monitoredProcesses[i][j] = {guid: result.TaskIdentifier, response: null}; }, error: function (xhr, ajaxOptions, thrownError) { monitoredProcesses[i][j] = {guid: null, response: config.asyncRequestFailTxt}; console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); }, complete: function() {} }); */ }); }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); } }); // End of Ajax }); //End of each endpoint // Query the status of all the requests until they have all finished queryMultipleAsyncProcessesConcurrently(monitoredProcesses, callback); } /** * Function queryMultipleAsyncProcessesConcurrently * * A function for querying the status of many asynchronous requests on the server concurrently and passing the result to * callback function when it has finished * * @monitoredProcesses - a multidimensional array with the requests of each endpoint * @callback - the callback function for the retrieved data */ function queryMultipleAsyncProcessesConcurrently(monitoredProcesses, callback) { var completed = 0, progressPercent = 0.0, totalProcesses = 0; $.each(monitoredProcesses, function(i, endpointProcesses) { totalProcesses += endpointProcesses.length; }); //console.log("Total Processes: " + totalProcesses); // Loop through the monitored processes $.each(monitoredProcesses, function(i, endpointProcesses) { $.each(endpointProcesses, function(j, process) { // If the current loop process is finished add the counter and update the progress var if (process.response) { completed++; progressPercent += (100.0 / totalProcesses) } // Else query for it's status else { $.ajax({ url: _asyncQueryUrl, type: "GET", dataType: _dataFormat, async: false, data: {guid: process.guid}, cache: false, success: function(info, textStatus, jqXHR) { // On success update the progress progressPercent += ((info.Progress * 100.0) / totalProcesses) // If it's completed ... if (info.Progress == 1.0) { // Save the result or set the respective text if it has failed //console.log(info); if (info.Result != null) process.response = info.Result; else process.response = config.asyncRequestFailTxt; completed++; } // else pass for the next round }, error: function (xhr, ajaxOptions, thrownError) { // on failure update the progress as completed and set the response to the predefined failure text progressPercent += (100.0 / totalProcesses) process.response = config.asyncRequestFailTxt; completed++; console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError ); } }); // end $.ajax(); } // end else }); // end internal $.each(); }); // end external $.each(); // update the final calculated progress updateProgressbar(progressPercent); // If all the processes are finished then check if there were failed ones and call the callback function if (completed == totalProcesses) { var failed = countFailedRequests(monitoredProcesses); hideProgressbar(); if (failed == totalProcesses) return; callback(monitoredProcesses); } else setTimeout(function() {queryMultipleAsyncProcessesConcurrently(monitoredProcesses, callback); }, asyncInterval); } /** * Function sendMultipleAsyncRequestSequentially * * A function for implementing all the data flow process in multiple async requests in many endpoints * and track their progress. * * This one sends the requests sequentially and only one at a time to server * * @endpointsAndParamValues - Array of objects for each endpoint, each object has the format of: * { * restUrl: Value, * restAsyncUrl: Value, * pValues: [ Array of pValue objects ] * } * * @callback - the callback function for the retrieved data */ function sendMultipleAsyncRequestSequentially(endpointsAndParamValues, callback) { showProgressbar(); // Create an array to monitor the processes of each endpoint var monitoredProcesses = new Array(endpointsAndParamValues.length); // For each endpoint $.each(endpointsAndParamValues, function(i, endpoint) { // get the endpoint parameters $.ajax({ url: endpoint.restUrl, type: "GET", dataType: _dataFormat, data: {}, async: false, success: function(params, textStatus, jqXHR) { // for monitoring each request of the current endpoint monitoredProcesses[i] = new Array(endpoint.pValues.length); // make the request and monitor it via its guid $.each(endpoint.pValues, function(j, pValue) { var reqParams = ((_dataFormat == xmlFormat) ? populateXmlParams(params, pValue) : populateJsonParams(params, pValue)); var reqData = ((_dataFormat == xmlFormat) ? new XMLSerializer().serializeToString(reqParams) : JSON.stringify(reqParams)); // construct the monitored process object monitoredProcesses[i][j] = {asyncUrl: endpoint.restAsyncUrl, progress: 0, reqData: reqData, guid: null, response: null}; }); }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); } }); // End of Ajax }); //End of each endpoint // Make and query the status of all the requests sequentially until they have all finished queryMultipleAsyncProcessesSequentially(monitoredProcesses, callback); } /** * Function queryMultipleAsyncProcessesSequentially * * A function for querying the status of many asynchronous requests on the server sequentially and passing the result to * callback function when it has finished * * @monitoredProcesses - a multidimensional array with the requests of each endpoint * @callback - the callback function for the retrieved data */ function queryMultipleAsyncProcessesSequentially(monitoredProcesses, callback) { var completed = 0, progressPercent = 0.0, totalProcesses = 0; $.each(monitoredProcesses, function(i, endpointProcesses) { totalProcesses += endpointProcesses.length; }); //console.log("Total Processes: " + totalProcesses); // Loop through the monitored processes $.each(monitoredProcesses, function(i, endpointProcesses) { var exitLoop = false; $.each(endpointProcesses, function(j, process) { //console.log(process); //var exitLoop = false; if (process.progress == 1.0) { completed++; progressPercent += (100.0 / totalProcesses); } else { // If there's no guid first, then if (!process.guid) { placeAsyncProcess(process); } // In case the reuslt was returned immediately if (process.progress == 1.0) { if (process.response == null) process.response = config.asyncRequestFailTxt; completed++; } else { //console.log(process.progress); // Else query for its status $.ajax({ url: _asyncQueryUrl, type: "GET", dataType: _dataFormat, async: false, data: {guid: process.guid}, cache: false, success: function(info, textStatus, jqXHR) { // On success update the progress progressPercent += ((info.Progress * 100.0) / totalProcesses); process.progress = info.Progress; // console.log(process.guid + " : " + process.progress); // If it's complete ... if (info.Progress == 1.0) { // Save the result or set the respective text if it has failed // console.log(info); if (info.Result != null) process.response = info.Result; else process.response = config.asyncRequestFailTxt; completed++; } else { // console.log("should exit loop"); // Using the exitloop flag since return fails to exit the external loop exitLoop = true; // return; // exit the loop to force this process being queried again until it's done } }, error: function (xhr, ajaxOptions, thrownError) { // on failure update the progress as completed and set the response to the predefined failure text progressPercent += (100.0 / totalProcesses) process.progress = 1; process.response = config.asyncRequestFailTxt; completed++; console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError ); } }); // end $.ajax(); } // end small else } //end big else if (exitLoop) return false; }); // end internal $.each(); if (exitLoop) return false; }); // end external $.each(); // update the final calculated progress updateProgressbar(progressPercent); var cancelChecked = $("#cancel-request").is(":checked"); // If all the processes are finished or cancel is checked then call the callback function if (completed == totalProcesses || cancelChecked) { var failed = countFailedRequests(monitoredProcesses); hideProgressbar(); if (failed == totalProcesses) return; // If cancel is checked, uncheck it for future use and do not proceed if (cancelChecked) { $("#cancel-request").prop("checked", false); return; } callback(monitoredProcesses); } else { var t = setTimeout(function() { queryMultipleAsyncProcessesSequentially(monitoredProcesses, callback); }, asyncInterval); } } /** * * * */ function placeAsyncProcess(processObj) { var contentType = ((_dataFormat == xmlFormat) ? xmlFormat : jsonContentType); $.ajax({ url: processObj.asyncUrl, type: "POST", dataType: _dataFormat, contentType: contentType, data: processObj.reqData, async: false, success: function(result, textStatus, jqXHR) { processObj.guid = result.TaskIdentifier; processObj.response = result.Result; processObj.progress = result.Progress; }, error: function (xhr, ajaxOptions, thrownError) { processObj.guid= null; processObj.response = config.asyncRequestFailTxt; processObj.progress = 1.0; console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); } }); //console.log(processObj); return processObj; } function countFailedRequests(monitoredProcesses) { var totalProcesses = 0, failedCounter = 0; // Count the final failed requests $.each(monitoredProcesses, function(i, endpointProcesses) { totalProcesses += endpointProcesses.length; $.each(endpointProcesses, function(j, process) { if (process.response == config.asyncRequestFailTxt) failedCounter++; }); }); if (failedCounter > 0) { Sexy.alert(failedCounter + " out of " + totalProcesses + " requests have failed!"); } return failedCounter; } /** * Function getParameters - NOT TESTED * * Gets only the end-point's parameters (synchronously). * * @dataFormat - override the default dataformat (optional) * * @returns - the end-point's available parameters * */ function getParameters(dataFormat) { var _dF = ((dataFormat) ? dataFormat : _dataFormat), parameters = {}; $.ajax({ url: restUrl, type: "GET", dataType: _dF, data: {}, async: false, success: function(params, textStatus, jqXHR) { parameters = params; }, error: function (xhr, ajaxOptions, thrownError) { console.error(this.url + "\n" + ajaxOptions + " " + xhr.status + " " + thrownError); } }); return parameters; } /** * Function constructRequestXml * * Constructs an xml document from a js object, to be used for the filtering POST request * * @options - the js object with the information in the form of: * { * rootElement: Value, * requestData: [ * {elementName: Value, elementValue: Value}, * ... * ] * } * * @returns - a jQuery xml object * */ function constructRequestXml(options) { var requestXml = $.parseXML("<" + options.rootElement + " />").documentElement; $.each(options.requestData, function(i, d) { $(requestXml) .append( $($.parseXML("<" + d.elementName + " />").documentElement).text(d.elementValue) ); }); return requestXml; } /** * Function populateXmlParams * * Populates a reporting framework parameters xml with given values * * @params - the ajax returned xml document * @pValues - a predefined object with the values to populate the xml doc; Example: * { * ElementName: "ReportParameter", * ElementKeyName: "Name", * ElementValueName: "Value", * Pairs: [ * {Key: "BuildIdentifiers", Value: "300"}, * {Key: "PlatformIdentifiers", Value: "PS3"}, * .... * ] * } * * @returns - the populated xml document */ function populateXmlParams(params, pValues) { $(params).find(pValues.ElementName).each(function() { xmlElement = $(this); $.each(pValues.Pairs, function(i, pair) { if (xmlElement.find(pValues.ElementKeyName).text() == pair.Key) { xmlElement.find(pValues.ElementValueName).removeAttr("i:nil"); xmlElement.find(pValues.ElementValueName).text(pair.Value); } }); }); return params; } /** * Function populateJsonParams * * Populates a reporting framework parameters json array with given values * * @params - the ajax returned json array * @pValues - a predefined object with the values to populate the json array; Example: * { * ElementKeyName: "Name", * ElementValueName: "Value", * Pairs: { * "BuildIdentifiers": "300", * "PlatformIdentifiers": "PS3", * "StartDate": start, "EndDate": end, * .... * } * } * * @returns - the populated json array */ function populateJsonParams(params, pValues) { $.each(params, function(i, jItem) { if (pValues.Pairs[jItem[pValues.ElementKeyName]] != null) jItem[pValues.ElementValueName] = pValues.Pairs[jItem[pValues.ElementKeyName]]; }); return params; } /* Expose the public functions */ return { sendSingleRequest: sendSingleRequest, sendSingleAsyncRequest: sendSingleAsyncRequest, sendMultipleAsyncRequest: sendMultipleAsyncRequest, getParameters: getParameters, constructRequestXml: constructRequestXml, populateXmlParams: populateXmlParams, populateJsonParams: populateJsonParams, }; }