(function() {
'use strict';
var app = angular.module('topcat');
/**
* @interface helpers
*/
app.service('helpers', function($http, $q, $timeout, $interval, $rootScope, $injector, $compile, uiGridConstants, icatSchema, topcatSchema, plugins){
var helpers = this;
this.setupColumnDef = function(columnDef, entityType, translateTitleNameSpace, translateStatusNameSpace){
var type = columnDef.type;
var field = columnDef.field.replace(/^.*\./, '').replace(/\|.*$/, '');
if(!columnDef.filter){
if(type == 'string' || type === undefined){
columnDef.filter = {
"condition": uiGridConstants.filter.CONTAINS,
"placeholder": "Containing...",
"type": "input"
}
}
}
if(!columnDef.filters){
if(type == 'date'){
columnDef.filters = [
{
"condition": 'GREATER_THAN_OR_EQUAL',
"placeholder": "From...",
"type": "input"
},
{
"condition": 'LESS_THAN_OR_EQUAL',
"placeholder": "To...",
"type": "input"
}
];
}
//this is a hack to satify Isis
//todo: refactor to make more generic
if(type == 'number' && columnDef.field == "datafileParameter.numericValue"){
columnDef.filters = [
{
"placeholder": "From...",
"type": "input"
},
{
"placeholder": "To...",
"type": "input"
}
];
}
}
if(!columnDef.cellFilter){
if(field.match(/Date$/)){
columnDef.cellFilter = "date: 'yyyy-MM-dd'"
} else if(field.match(/Time$/)) {
columnDef.cellFilter = "date: 'yyyy-MM-dd HH:mm:ss'"
}
}
if(!columnDef.title){
var fieldNamespace = helpers.constantify(field);
columnDef.title = translateTitleNameSpace + '.' + fieldNamespace;
}
if(field === 'size') {
columnDef.cellTemplate = columnDef.cellTemplate || '<div class="ui-grid-cell-contents"><span ng-if="row.entity.size === undefined && $root.requestCounter != 0" class="loading"> </span>{{row.entity.size|bytes}}</div>';
columnDef.enableSorting = false;
columnDef.enableFiltering = false;
}
if(field === 'fileSize') {
columnDef.cellTemplate = columnDef.cellTemplate || '<div class="ui-grid-cell-contents">{{row.entity.fileSize|bytes}}</div>';
}
if(field === 'datafileCount') {
columnDef.cellTemplate = columnDef.cellTemplate || '<div class="ui-grid-cell-contents"><span ng-if="row.entity.datafileCount === undefined && $root.requestCounter != 0" class="loading"> </span>{{row.entity.datafileCount + (row.entity.datafileCount == 1 ? " file" : " files")}}</div>';
columnDef.enableSorting = false;
columnDef.enableFiltering = false;
}
if(field === 'datasetCount') {
columnDef.cellTemplate = columnDef.cellTemplate || '<div class="ui-grid-cell-contents"><span ng-if="row.entity.datasetCount === undefined && $root.requestCounter != 0" class="loading"> </span>{{row.entity.datasetCount + (row.entity.datasetCount == 1 ? " dataset" : " datasets")}}</div>';
columnDef.enableSorting = false;
columnDef.enableFiltering = false;
}
if(field === 'status') {
columnDef.cellTemplate = columnDef.cellTemplate || '<div class="ui-grid-cell-contents"><span ng-if="row.entity.status === undefined && $root.requestCounter != 0" class="loading"></span><span ng-if="row.entity.status">{{"' + translateStatusNameSpace + '." + row.entity.status | translate}}</span></div>';
}
if(columnDef.title){
columnDef.displayName = columnDef.title;
columnDef.headerCellFilter = 'translate';
}
if(columnDef.sort){
if(columnDef.sort.direction.toLowerCase() == 'desc'){
columnDef.sort.direction = uiGridConstants.DESC;
} else {
columnDef.sort.direction = uiGridConstants.ASC;
}
}
if(columnDef.type == 'date'){
if(columnDef.field && columnDef.field.match(/Date$/)){
columnDef.filterHeaderTemplate = '<div class="ui-grid-filter-container" datetime-picker only-date ng-model="col.filters[0].term" placeholder="From..."></div><div class="ui-grid-filter-container" datetime-picker only-date ng-model="col.filters[1].term" placeholder="To..."></div>';
} else {
columnDef.filterHeaderTemplate = '<div class="ui-grid-filter-container" datetime-picker ng-model="col.filters[0].term" placeholder="From..."></div><div class="ui-grid-filter-container" datetime-picker ng-model="col.filters[1].term" placeholder="To..."></div>';
}
}
if(columnDef.filter && typeof columnDef.filter.condition == 'string'){
columnDef.filter.condition = uiGridConstants.filter[columnDef.filter.condition.toUpperCase()];
}
};
this.setupTopcatGridOptions = function(gridOptions, entityType){
var pagingConfig = tc.config().paging;
var isScroll = pagingConfig.pagingType == 'scroll';
var pageSize = isScroll ? pagingConfig.scrollPageSize : pagingConfig.paginationNumberOfRows;
gridOptions.enableHorizontalScrollbar = uiGridConstants.scrollbars.NEVER;
gridOptions.enableRowSelection = false;
gridOptions.enableRowHeaderSelection = false;
gridOptions.gridMenuShowHideColumns = false;
gridOptions.pageSize = !this.isScroll ? pagingConfig.paginationNumberOfRows : null;
gridOptions.paginationPageSizes = pagingConfig.paginationPageSizes;
gridOptions.paginationNumberOfRows = pagingConfig.paginationNumberOfRows;
gridOptions.useExternalPagination = true;
gridOptions.useExternalSorting = true;
gridOptions.useExternalFiltering = true;
gridOptions.enableFiltering = true;
gridOptions.enableSelection = false;
var entitySchema = topcatSchema.entityTypes[entityType];
_.each(gridOptions.columnDefs, function(columnDef){
columnDef.enableHiding = false;
var field = columnDef.field;
var type = entitySchema.fields[field];
if(!columnDef.type) columnDef.type = type;
var translateColumnNameSpace = helpers.constantify(entityType.replace(/Item$/, '')) + '.COLUMN';
var translateStatusNameSpace = helpers.constantify(entityType.replace(/Item$/, '')) + '.STATUS';
helpers.setupColumnDef(columnDef, entityType, translateColumnNameSpace, translateStatusNameSpace);
});
};
this.setupIcatGridOptions = function(gridOptions, entityType, showInfoButton){
if(entityType != 'facility'){
gridOptions.useExternalPagination = true;
gridOptions.useExternalSorting = true;
gridOptions.useExternalFiltering = true;
}
var enableSelection = gridOptions.enableSelection === true && entityType.match(/^investigation|dataset|datafile$/) !== null;
gridOptions.enableSelectAll = gridOptions.enableSelectAll === true && enableSelection;
gridOptions.enableRowSelection = enableSelection;
gridOptions.enableRowHeaderSelection = enableSelection;
gridOptions.enableFiltering = true;
// gridOptions.rowTemplate = '<div ng-click="grid.appScope.showTabs(row)" ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name" class="ui-grid-cell" ng-class="{ \'ui-grid-row-header-cell\': col.isRowHeader }" ui-grid-cell></div>';
_.each(gridOptions.columnDefs, function(columnDef, i){
columnDef.enableHiding = false;
var matches;
var field = columnDef.field;
var variableEntityType = entityType;
if(matches = field.replace(/\|.+$/, '').match(/^([^\[\]]+).*?\.([^\.\[\]]+)$/)){
var variableName = matches[1];
variableEntityType = icatSchema.variableEntityTypes[variableName];
if(!entityType){
console.error("Unknown variableName: " + variableName, columnDef)
}
entityType = entityType;
field = matches[2];
}
if(!variableEntityType) return;
var entitySchema = icatSchema.entityTypes[variableEntityType];
var type = entitySchema.fields[field];
if(!columnDef.type) columnDef.type = type;
var entityTypeNamespace = helpers.constantify(variableEntityType);
helpers.setupColumnDef(columnDef, entityType, 'BROWSE.COLUMN.' + entityTypeNamespace);
var filters = "";
var matches;
if(matches = columnDef.field.match(/^(.*?)(\|[^\.\[\]]*)$/)){
columnDef.field = matches[1];
filters = matches[2];
}
if(columnDef.type == 'date'){
if(columnDef.field && columnDef.field.match(/Date$/)){
filters = filters + "|date:'yyyy-MM-dd'"
} else {
filters = filters + "|date:'yyyy-MM-dd HH:mm:ss'"
}
}
if(columnDef.excludeFuture){
var date = new Date();
var day = "" + date.getDate();
if(day.length == 1) day = '0' + day;
var month = "" + (date.getMonth() + 1);
if(month.length == 1) month = '0' + month;
var year = date.getFullYear();
var filter = year + '-' + month + '-' + day;
$timeout(function(){
columnDef.filters[1].term = filter;
});
}
var jpqlExpression = columnDef.field;
if(!columnDef.field.match(/\./)){
if(entityType == 'proposal'){
jpqlExpression = 'investigation.' + jpqlExpression;
} else {
jpqlExpression = entityType + '.' + jpqlExpression;
}
}
if(!columnDef.jpqlFilter) columnDef.jpqlFilter = jpqlExpression;
if(!columnDef.jpqlSort) columnDef.jpqlSort = jpqlExpression;
var titleTemplate;
var showCondition;
if(columnDef.type == 'number' && columnDef.filters){
var pair = jpqlExpression.split(/\./);
var _entityType = pair[0];
var entityField = pair[1];
var fieldNameSuffix = helpers.capitalize(_entityType) + helpers.capitalize(entityField);
var minFieldName = "min" + fieldNameSuffix;
var maxFieldName = "max" + fieldNameSuffix;
titleTemplate = '{{row.entity.find("' + minFieldName + '")[0]' + filters + '}} - {{row.entity.find("' + maxFieldName + '")[0]' + filters + '}}';
showCondition = 'row.entity.find("' + minFieldName + '").length > 0 && row.entity.find("' + maxFieldName + '").length > 0';
columnDef.enableSorting = false;
} else {
titleTemplate = '{{row.entity.find("' + columnDef.field + '")[0]' + filters + '}}';
showCondition = 'row.entity.find("' + columnDef.field + '").length > 0';
}
if(columnDef.link) {
if(typeof columnDef.link == "string"){
titleTemplate = '<a ng-click="grid.appScope.browse(row.entity.' + columnDef.link + ')">' + titleTemplate + '</a>';
} else {
titleTemplate = '<a ng-click="grid.appScope.browse(row.entity)">' + titleTemplate + '</a>';
}
}
var tooltipPlacement = i == 0 ? 'right' : 'top';
columnDef.cellTemplate = columnDef.cellTemplate || [
'<div class="ui-grid-cell-contents">',
// '<a ng-if="colRenderIndex == 0" ng-click="grid.appScope.showTabs(row)" class="btn btn-xs glyphicon glyphicon-info-sign" uib-tooltip="{{"BROWSE.INTERFACE.TOGGLE_METATABS_TOOLTIP.TEXT" | translate}}" tooltip-placement="right"></a>',
// '<span ng-if="!(' + showCondition + ') && $root.requestCounter != 0" class="loading"> </span>',
'<span ',
'ng-if="row.entity.find("' + columnDef.field + '").length > 1" ',
'uib-dropdown dropdown-append-to-body ',
'tooltip-append-to-body="true">',
'<button ng-click="$event.stopPropagation();" type="button" class="btn btn-default btn-xs" uib-dropdown-toggle>',
'<span class="caret"></span> Multiple values (e.g. "{{row.entity.find("' + columnDef.field + '")[0]}}")',
'</button>',
'<ul class="dropdown-menu" uib-dropdown-menu>',
'<li ng-repeat="item in row.entity.find("' + columnDef.field + '")"><a>{{item}}</a></li>',
'</ul>',
'</span> ',
'<span ng-if="row.entity.find("' + columnDef.field + '").length == 1 && ' + showCondition + '">',
titleTemplate,
'</span>',
'</div>'
].join('');
});
if(showInfoButton){
gridOptions.columnDefs.unshift({
name: 'info',
displayName: '',
visible: 'true',
width: 25,
enableFiltering: false,
enable: false,
enableColumnMenu: false,
enableSorting: false,
enableHiding: false,
cellTemplate: '<a ng-click="grid.appScope.showTabs(row)" class="btn btn-xs glyphicon glyphicon-info-sign" uib-tooltip="{{"BROWSE.INTERFACE.TOGGLE_METATABS_TOOLTIP.TEXT" | translate}}" tooltip-placement="right"></a>'
});
}
var actionButtons = [];
if(gridOptions.enableDownload){
actionButtons.push({
name: "download",
click: function(entity){
var sessionId = entity.facility.icat().session().sessionId;
var id = entity.id;
var name;
if(entity.entityType == 'datafile'){
name = entity.location.replace(/^.*\//, '');
} else {
name = entity.name;
}
var idsUrl = entity.facility.config().idsUrl + [
'/ids/getData?sessionId=' + encodeURIComponent(sessionId),
'' + entity.entityType + 'Ids=' + id,
'compress=false',
'zip=' + entity.entityType != 'datafile' ? 'true' : 'false',
'outfile=' + encodeURIComponent(name)
].join('&');
$(document.body).append($('<iframe>').attr({
src: idsUrl
}).css({
position: 'relative',
left: '-1000000px',
height: '1px',
width: '1px'
}));
},
class: "btn btn-primary",
translate: "DOWNLOAD_ENTITY_ACTION_BUTTON.TEXT",
translateTooltip: "DOWNLOAD_ENTITY_ACTION_BUTTON.TOOLTIP.TEXT"
});
}
_.each($injector.get('tc').ui().entityActionButtons(), function(button){
if(_.includes(button.options.entityTypes, entityType)){
actionButtons.push({
name: button.name,
click: button.click,
class: button.options.class || "btn btn-primary",
translate: button.name.toUpperCase().replace(/-/g, '_') + "_ENTITY_ACTION_BUTTON.TEXT",
translateValues: button.options.translateValues,
translateTooltip: button.name.toUpperCase().replace(/-/g, '_') + "_ENTITY_ACTION_BUTTON.TOOLTIP.TEXT",
insertBefore: button.options.insertBefore,
insertAfter: button.options.insertAfter,
show: button.options.show
});
}
});
gridOptions.actionButtons = this.mergeNamedObjectArrays([], actionButtons);
if(gridOptions.actionButtons.length > 0){
gridOptions.columnDefs.push({
name : 'actions',
visible: true,
title: 'BROWSE.COLUMN.ACTIONS.NAME',
enableFiltering: false,
enable: false,
enableColumnMenu: false,
enableSorting: false,
enableHiding: false,
cellTemplate : [
'<div class="ui-grid-cell-contents">',
'<a ',
'ng-repeat="actionButton in grid.options.actionButtons" ',
'type="button" ',
'class="{{actionButton.class}} btn-xs btn-entity-action" ',
'translate="{{actionButton.translate}}" ',
'translateValues="{{actionButton.translateValues}}" ',
'uib-tooltip="{{actionButton.translateTooltip | translate}}" ',
'tooltip-placement="left" ',
'tooltip-append-to-body="true" ',
'ng-show="actionButton.show(row.entity) === undefined ? true : actionButton.show(row.entity)"',
'ng-click="actionButton.click(row.entity); $event.stopPropagation();">',
'</a>',
'</div>'
].join('')
});
}
};
this.generateEntitySorter = function(sortColumns){
var sorters = [];
_.each(sortColumns, function(sortColumn){
if(sortColumn.colDef){
sorters.push(function(entityA, entityB){
var field = sortColumn.colDef.field;
var valueA = (((entityA.find ? entityA.find(field)[0] : entityA[field]) || '') + '').toLowerCase();
var valueB = (((entityB.find ? entityB.find(field)[0] : entityB[field]) || '') + '').toLowerCase();
var out = 0;
if(valueA < valueB){
out = -1
} else if(valueA > valueB){
out = 1
}
if(sortColumn.sort.direction == 'desc') out = out * -1;
return out;
});
}
});
return function(entityA, entityB){
var out = 0;
_.each(sorters, function(sorter){
var current = sorter(entityA, entityB);
if(current != 0){
out = current;
return false;
}
});
return out;
};
};
this.generateEntityFilter = function(gridOptions){
var conditions = [];
_.each(gridOptions.columnDefs, function(columnDef){
if(!columnDef.field) return;
if(columnDef.type == 'date' && columnDef.filters){
conditions.push(function(entity){
var fromDate = helpers.completePartialFromDate(columnDef.filters[0].term);
var toDate = helpers.completePartialToDate(columnDef.filters[1].term);
var field = columnDef.field;
var value = (entity.find ? entity.find(field)[0] : entity[field]) || '';
return value >= fromDate && value <= toDate;
});
} else if(columnDef.type == 'string' && columnDef.filter){
conditions.push(function(entity){
var field = columnDef.field;
var value = (((entity.find ? entity.find(field)[0] : entity[field]) || '') + '').toLowerCase();
return columnDef.filter.term === undefined || columnDef.filter.term === null || value.indexOf(columnDef.filter.term.toLowerCase()) >= 0;
});
}
});
return function(row){
var out = true;
_.each(conditions, function(condition){
if(!condition(row)){
out = false;
return false;
}
});
return out;
};
};
this.mergeNamedObjectArrays = function(existingObjects, toBeMergedObjects){
var out = _.clone(existingObjects);
var changed;
while(true){
changed = false;
_.each(_.clone(toBeMergedObjects), function(toBeMergedObject){
if(toBeMergedObject.insertBefore){
var index = _.findIndex(out, function(tab){
return tab.name == toBeMergedObject.insertBefore
});
if(index !== -1){
out.splice(index, 0, toBeMergedObject);
_.remove(toBeMergedObjects, {name: toBeMergedObject.name});
changed = true;
}
} else if(toBeMergedObject.insertAfter){
var index = _.findIndex(out, function(tab){
return tab.name == toBeMergedObject.insertAfter;
});
if(index !== -1){
out.splice(index + 1, 0, toBeMergedObject);
_.remove(toBeMergedObjects, {name: toBeMergedObject.name});
changed = true;
}
} else {
out.push(toBeMergedObject);
_.remove(toBeMergedObjects, {name: toBeMergedObject.name});
changed = true;
}
});
if(!changed) break;
}
return out;
};
this.completePartialFromDate = function(date){
var segments = (date || '').split(/[-:\s\/]+/);
var year = segments[0];
var month = segments[1] || "01";
var day = segments[2] || "01";
var hours = segments[3] || "00";
var minutes = segments[4] || "00";
var seconds = segments[5] || "00";
year = year + '0000'.slice(year.length, 4);
month = month + '00'.slice(month.length, 2);
day = day + '00'.slice(day.length, 2);
hours = hours + '00'.slice(hours.length, 2);
minutes = minutes + '00'.slice(minutes.length, 2);
seconds = seconds + '00'.slice(seconds.length, 2);
if(parseInt(month) == 0) month = '01';
if(parseInt(day) == 0) day = '01';
return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds;
};
this.completePartialToDate = function(date){
var segments = (date || '').split(/[-:\s\/]+/);
var year = segments[0] || "";
var month = segments[1] || "";
var day = segments[2] || "";
var hours = segments[3] || "23";
var minutes = segments[4] || "59";
var seconds = segments[5] || "59";
year = year + '9999'.slice(year.length, 4);
month = month + '99'.slice(month.length, 2);
day = day + '99'.slice(day.length, 2);
hours = hours + '33'.slice(hours.length, 2);
minutes = minutes + '99'.slice(minutes.length, 2);
seconds = seconds + '99'.slice(seconds.length, 2);
if(parseInt(month) > 12) month = '12';
var daysInMonth = new Date(year, day, 0).getDate();
if(parseInt(day) > daysInMonth) day = daysInMonth;
return year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds;
};
this.typeOf = function(data){
if(data === null) return 'null';
var out = typeof data;
if(out == 'object'){
if(data instanceof Array || data instanceof Uint8Array) return 'array';
if(data.then instanceof Function) return 'promise';
}
return out;
}
this.overload = function(variations){
var fn = function(){
var that = this;
var args = arguments;
var argTypeOfs = _.map(args, function(arg){ return helpers.typeOf(arg); });
var found = false;
var out;
_.each(variations, function(fn, pattern){
if(pattern == 'default') return false;
pattern = pattern.trim().split(/\s*,\s*/);
found = _.isEqual(argTypeOfs, pattern);
if(found){
out = fn.apply(that, args);
return false;
}
});
if(argTypeOfs.length == 0 && variations['']){
out = variations[''].apply(that, args);
} else if(!found){
if(variations.default){
out = variations.default.apply(that, args);
} else {
if(this){
_.each(this, function(value, name){
if(value == fn) throw "Could not satisfy overloaded method " + name + "(" + argTypeOfs.join(', ') + ").";
});
} else {
throw "Could not satisfy overloaded function (" + argTypeOfs.join(', ') + ").";
}
}
}
return out;
};
return fn;
}
this.jpqlSanitize = function(data){
if(typeof data == 'string' && !data.isSafe){
return "'" + data.replace(/'/g, "''") + "'";
}
return data;
};
this.buildQuery = function(query){
while(true){
query = _.map(query, function(i){
if(helpers.typeOf(i) == 'function') i = i.call(this);
return i;
});
query = _.flatten(query);
var isFunction = _.select(query, function(i){ return helpers.typeOf(i) == 'function'; }).length > 0;
var isArray = _.select(query, function(i){ return helpers.typeOf(i) == 'array'; }).length > 0;
if(!isFunction && !isArray) break;
}
query = _.select(query, function(i){ return i !== undefined; });
try {
var _query = [];
for(var i = 0; i < query.length; i++){
var expression = [];
var fragments = query[i].split(/\?/);
for(var j in fragments){
expression.push(fragments[j]);
if(j < fragments.length - 1){
i++;
expression.push(helpers.jpqlSanitize(query[i]));
}
}
_query.push(expression.join(''));
}
} catch(e) {
console.error("can't build query", query, e)
}
return _query.join(' ');
};
this.urlEncode = function(o){
var out = [];
_.each(o, function(value, key){
out.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
});
return out.join('&');
};
this.uncapitalize = function(text){
return ('' + text).replace(/^(.)/, function(s){ return s.toLowerCase(); });
};
this.capitalize = function(text){
return ('' + text).replace(/^(.)/, function(s){ return s.toUpperCase(); });
};
this.constantify = function(text){
return ('' + text).replace(/([A-Z])/g, '_$1').replace(/-/g, '_').replace(/^_/, '').toUpperCase();
};
var lowPriorityCounter = 0;
var lowPriorityQueue = [];
// Monitoring queue behaviour
var lowPriorityTimes = [];
var maxLowPriorityTime = 0;
var maxLowPriorityQueueLength = 0;
var maxLowPriorityTimeEver = 0;
var maxLowPriorityQueueLengthEver = 0;
// flag to control monitoring
// This can be set in the browser console by doing tc.setMonitoring(true) (or false)
var monitorLowPriorityQueue = false;
this.setLowPriorityQueueMonitoring = function(truthVal){
monitorLowPriorityQueue = truthVal;
}
$interval(function(){
if(lowPriorityCounter < 2 && lowPriorityQueue.length > 0){
if( monitorLowPriorityQueue ){
if( lowPriorityQueue.length > maxLowPriorityQueueLength ) maxLowPriorityQueueLength = lowPriorityQueue.length;
if( maxLowPriorityQueueLength > maxLowPriorityQueueLengthEver) maxLowPriorityQueueLengthEver = maxLowPriorityQueueLength;
// If monitoring were ever turned on during queue changes,
// then it's possible that lowPriorityTimes may not have been saved for the current entry;
// so ignore it in that case
var lpTime = 0;
if( lowPriorityTimes.length > 0 ) {
lpTime = (new Date).getTime() - lowPriorityTimes.shift();
}
if( lpTime > maxLowPriorityTime ) maxLowPriorityTime = lpTime;
if( lpTime > maxLowPriorityTimeEver ) maxLowPriorityTimeEver = lpTime;
}
lowPriorityQueue.shift().call();
}
}, 10);
// Report queue stats every minute, reset every 10 mins
var lowPriorityQueueStatsResetCount = 0;
$interval(function(){
if( monitorLowPriorityQueue ){
console.log('LowPriorityQueue: max length (ever): '
+ maxLowPriorityQueueLength + ' (' + maxLowPriorityQueueLengthEver + ')'
+ '; max time (ever): ' + maxLowPriorityTime + ' (' + maxLowPriorityTimeEver + ')');
if(lowPriorityQueueStatsResetCount++ == 10){
lowPriorityQueueStatsResetCount = 0;
maxLowPriorityTime = 0;
maxLowPriorityQueueLength = 0;
console.log('LowPriorityQueue: resetting recent stats' );
}
}
}, 1000 * 60);
this.generateRestMethods = function(that, prefix){
defineMethods.call(that, 'get');
defineMethods.call(that, 'delete');
defineMethods.call(that, 'post');
defineMethods.call(that, 'put');
function defineMethods(methodName){
this[methodName] = helpers.overload({
'string, string, object': function(offset, params, options){
return send(offset, params, options);
},
'string, array, object': function(offset, data, options){
return send(offset, data, options);
},
'string, object, object': function(offset, params, options){
return this[methodName].call(this, offset, helpers.urlEncode(params), options)
},
'string, promise, object': function(offset, timeout, params){
return this[methodName].call(this, offset, params, {timeout: timeout});
},
'string, object': function(offset, params){
return this[methodName].call(this, offset, params, {});
},
'string, promise': function(offset, timeout){
return this[methodName].call(this, offset, {}, {timeout: timeout});
},
'string': function(offset){
return this[methodName].call(this, offset, {}, {});
}
});
var urlLengthMethodName = methodName + 'UrlLength';
this[urlLengthMethodName] = helpers.overload({
'string, string, object': function(offset, params, options){
var url = prefix + offset;
if(methodName.match(/get|delete/) && params !== '') url += '?' + params;
return url.length;
},
'string, object, object': function(offset, params, options){
return this[urlLengthMethodName].call(this, offset, helpers.urlEncode(params), options)
},
'string, object': function(offset, params){
return this[urlLengthMethodName].call(this, offset, params, {});
},
'string': function(offset){
return this[urlLengthMethodName].call(this, offset, {}, {});
}
});
function send(offset, data, options){
options = _.clone(options);
if(methodName.match(/post|put/)){
if(!options.headers) options.headers = {};
if(!options.headers['Content-Type']) options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
var url = prefix + offset;
if(methodName.match(/get|delete/)){
if(data !== '') url += '?' + data;
} else if(options.queryParams) {
url += '?' + helpers.urlEncode(options.queryParams);
}
var out = $q.defer();
function call(){
if(options.lowPriority) lowPriorityCounter++;
if(options.bypassInterceptors){
var xhr = $.ajax(url, {
method: methodName.toUpperCase(),
headers: options.headers,
data: methodName.match(/post|put/) ? data : undefined
});
xhr.then(function(data){
success({data: data})
}, function(qXHR, textStatus, errorThrown){
failure({data: errorThrown})
});
if(options.timeout){
options.timeout.then(function(){
xhr.abort();
});
}
} else {
var args = [url];
if(methodName.match(/post|put/)) args.push(data);
args.push(options);
$http[methodName].apply($http, args).then(success, failure);
}
}
function success(response){
out.resolve(response.data);
if(options.lowPriority) lowPriorityCounter--;
}
function failure(response){
// BR: have observed "TypeError: response is null" errors;
// probably not coming from here, but let's log anyway
if(response == null){
console.log("Failure response from " + methodName + " " + prefix + offset + " is null");
} else if(response.data == null){
console.log("Failure response from " + methodName + " " + prefix + offset + " has no/null data");
}
out.reject(response?response.data:null);
if(options.lowPriority) lowPriorityCounter--;
}
if(options.lowPriority){
if( monitorLowPriorityQueue ) lowPriorityTimes.push((new Date).getTime());
lowPriorityQueue.push(call);
} else {
call();
}
return out.promise;
}
}
};
this.throttle = function(size, delay, timeout, items, fn){
var defered = $q.defer();
var chunks = _.chunk(items, size);
function processChunks(){
var chunk = chunks.pop();
if(chunk){
$q.all(_.map(chunk, function(item){ return fn(item); })).then(function(){
if(timeout){
var promise = $timeout(processChunks, delay);
timeout.then(function(){
$timeout.cancel(promise);
});
} else {
$timeout(processChunks, delay);
}
}, function(){
defered.reject();
});
} else {
defered.resolve();
}
}
processChunks();
return defered.promise;
};
/**
* A defered asynchronous call
*
* @interface Promise
*/
(function(){
var methods = {
get: $http.get,
delete: $http.delete,
post: $http.post,
put: $http.put
};
_.each(methods, function(method, name){
$http[name] = function(){
return extendPromise(method.apply(this, arguments));
};
});
var allMethod = $q.all;
$q.all = function(){
var out = allMethod.apply(this, arguments);
extendPromise(out);
return out;
};
var deferMethod = $q.defer;
$q.defer = function(){
var out = deferMethod.apply(this, arguments);
extendPromise(out.promise);
return out;
};
var resolveMethod = $q.resolve;
$q.resolve = function(){
var out = resolveMethod.apply(this, arguments);
extendPromise(out);
return out;
};
var rejectMethod = $q.reject;
$q.reject = function(){
var out = rejectMethod.apply(this, arguments);
extendPromise(out);
return out;
};
function extendPromise(promise){
/**
* Logs the response to the browsers console; useful for debugging.
*
* @method
* @name Promise#log
* @return {Promise}
*/
promise.log = function(){
var start = (new Date()).getTime();
return this.then(function(data){
var end = (new Date()).getTime();
var diff = end - start;
console.log('(success - ' + diff + ' milliseconds)', data);
}, function(data){
console.log('(error)', data);
}, function(data){
console.log('(notify)', data);
});
};
/**
* Defines callback functions for the promise.
*
* @method
* @name Promise#then
* @param {Function} successCallback gets called the response is success
* @param {Function} [errorCallback] gets called when the reponse errors
* @param {Function} [notifyCallback] gets called to indicate some kind of progress
* @return {Promise}
*/
var then = promise.then;
promise.then = function(){
return extendPromise(then.apply(this, arguments));
};
return promise;
}
})();
this.mixinPluginMethods = function(name, that){
_.each(plugins, function(plugin){
if(plugin.extend && plugin.extend[name]){
$injector.invoke(plugin.extend[name], that);
}
});
};
});
String.prototype.safe = function(){
return new SafeString(this);
};
function SafeString(value){
this.isSafe = true;
this.value = value;
}
SafeString.prototype.toString = function(){
return this.value;
};
})();