Commit 516a8f11 by Torkel Ödegaard

small refactoring, and added config panel that can edit, add/remove targets

parent 54eae2e3
...@@ -43,15 +43,20 @@ require.config({ ...@@ -43,15 +43,20 @@ require.config({
modernizr: '../vendor/modernizr-2.6.1', modernizr: '../vendor/modernizr-2.6.1',
elasticjs: '../vendor/elasticjs/elastic-angular-client', elasticjs: '../vendor/elasticjs/elastic-angular-client',
rq: '../vendor/rq',
setImmediate: '../vendor/setImmediate',
'ts-widget': '../vendor/timeserieswidget/jquery.tswidget',
'ts-graphite-helpers': '../vendor/timeserieswidget/graphite_helpers'
}, },
shim: { shim: {
underscore: { underscore: {
exports: '_' exports: '_'
}, },
rq: {
deps: ['setImmediate'],
exports: 'RQ'
},
angular: { angular: {
deps: ['jquery'], deps: ['jquery'],
exports: 'angular' exports: 'angular'
......
<div class="editor-row"> <div class="editor-row">
<div class="section">
<h5>Values</h5> <h5>Graphite Targets</h5>
<div class="editor-option">
<label class="small">Chart value</label> <div ng-repeat="target in panel.targets">
<select ng-change="set_refresh(true)" class="input-small" ng-model="panel.mode" ng-options="f for f in ['count','min','mean','max','total']"></select>
<div class="row-fluid">
<div class="span12">
<input type="text" ng-model="target.target" class="input-large" style="width:95%" ng-change="render()" />
<i ng-click="panel.targets = _.without(panel.targets, target)" class="pointer icon-remove" style="position: relative; top: -5px; left: 5px;"></i>
</div>
</div> </div>
<div class="editor-option" ng-show="panel.mode != 'count'">
<label class="small">Value Field <tip>This field must contain a numeric value</tip></label>
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-large" ng-model="panel.value_field">
</div>
</div>
<div class="section">
<h5>Transform Series</h5>
<div class="editor-option" ng-show="panel.mode != 'count'">
<label class="small">Scale</label>
<input type="text" class="input-mini" ng-model="panel.scale">
</div>
<div class="editor-option">
<label class="small">Seconds <tip>Normalize intervals to per-second</tip></label><input type="checkbox" ng-model="panel.scaleSeconds" ng-checked="panel.scaleSeconds">
</div>
<div class="editor-option">
<label class="small">Derivative <tip>Plot the change per interval in the series</tip></label><input type="checkbox" ng-model="panel.derivative" ng-checked="panel.derivative" ng-change="set_refresh(true)">
</div>
</div>
</div>
<h5>Time Options</h5>
<div class="editor-row">
<div class="editor-option">
<label class="small">Time Field</label>
<input ng-change="set_refresh(true)" placeholder="Start typing" bs-typeahead="fields.list" type="text" class="input-small" ng-model="panel.time_field">
</div>
<div class="editor-option">
<label class="small">Time correction</label>
<select ng-model="panel.timezone" class='input-small' ng-options="f for f in ['browser','utc']"></select>
</div>
<div class="editor-option">
<label class="small">Auto-interval</label><input type="checkbox" ng-model="panel.auto_int" ng-checked="panel.auto_int" />
</div>
<div class="editor-option" ng-show='panel.auto_int'>
<label class="small">Resolution <tip>Shoot for this many data points, rounding to sane intervals</tip></label>
<input type="number" class='input-mini' ng-model="panel.resolution" ng-change='set_refresh(true)'/>
</div>
<div class="editor-option" ng-hide='panel.auto_int'>
<label class="small">Interval <tip>Use Elasticsearch date math format (eg 1m, 5m, 1d, 2w, 1y)</tip></label>
<input type="text" class='input-mini' ng-model="panel.interval" ng-change='set_refresh(true)'/>
</div> </div>
<button ng-click="add_target(panel.target);" class="btn btn-success" ng-show="editor.index == 1">Add target</button>
</div> </div>
\ No newline at end of file
define([ define([
'jquery' 'jquery',
'rq',
'config'
], ],
function ($) { function ($, RQ, config) {
'use strict'; 'use strict';
String.prototype.graphiteGlob = function(glob) {
var regex = '^';
for (var i = 0; i < glob.length; i++ ) {
var c = glob.charAt(i);
switch (c) {
case '*':
regex += '[^\.]+';
break;
case '.':
regex += '\\.';
break;
default:
regex += c;
}
}
regex += '$';
return this.match(regex);
}
function build_graphite_options(options, raw) { function build_graphite_options(options, raw) {
raw = raw || false; raw = raw || false;
...@@ -75,77 +59,36 @@ function ($) { ...@@ -75,77 +59,36 @@ function ($) {
return clean_options; return clean_options;
} }
function build_graphite_url(options) { function loadGraphiteData(requestion, options)
var limit = 2000; // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers {
var url = options.graphite_url + "?"; var graphOptions = {
from: $.plot.formatDate(options.range.from, '%H%:%M_%Y%m%d'),
options = build_graphite_options(options, false); until: $.plot.formatDate(options.range.to, '%H%:%M_%Y%m%d'),
$.map(options, function(option) { targets: options.targets
if (url.length + option.length < limit) {
url += '&' + option;
}
});
return url.replace(/\?&/, "?");
}
function find_definition (target_graphite, options) {
var matching_i = undefined;
for (var cfg_i = 0; cfg_i < options.targets.length && matching_i == undefined; cfg_i++) {
// string match (no globbing)
if(options.targets[cfg_i].target == target_graphite.target) {
matching_i = cfg_i;
}
// glob match?
else if(target_graphite.target.graphiteGlob(options.targets[cfg_i].target)) {
matching_i = cfg_i;
}
} }
if (matching_i == undefined) { var graphiteParameters = build_graphite_options(graphOptions, true);
console.error ("internal error: could not figure out which target_option target_graphite '" + getGraphiteData(graphiteParameters)
target_graphite.target + "' comes from"); .done(function(data) {
return []; requestion(data);
} })
.fail(function() {
return options.targets[matching_i]; requestion(null, 'Error in ajax call to graphite');
});
} }
function add_targets(options, response_data) { function getGraphiteData(parameters) {
var all_targets = []; return $.ajax({
for (var res_i = 0; res_i < response_data.length; res_i++) { accepts: { text: 'application/json' },
var target = find_definition(response_data[res_i], options); cache: false,
target.label = target.name; // flot wants 'label' dataType: 'json',
target.data = []; url: config.graphiteUrl,
var nulls = 0; type: "POST",
var non_nulls = 0; data: parameters.join('&')
for (var i in response_data[res_i].datapoints) { });
if(response_data[res_i].datapoints[i][0] == null) {
nulls++;
if('drawNullAsZero' in options && options['drawNullAsZero']) {
response_data[res_i].datapoints[i][0] = 0;
} else {
// don't tell flot about null values, it prevents adjacent non-null values from
// being rendered correctly
continue;
}
} else {
non_nulls++;
}
target.data.push([response_data[res_i].datapoints[i][1] * 1000, response_data[res_i].datapoints[i][0]]);
}
if (nulls/non_nulls > 0.3) {
console.log("warning: rendered target contains " + nulls + " null values, " + non_nulls + " non_nulls");
}
all_targets.push(target);
}
return all_targets;
} }
return { return {
build_graphite_options: build_graphite_options, loadGraphiteData: loadGraphiteData
build_graphite_url: build_graphite_url,
add_targets: add_targets
}; };
}); });
\ No newline at end of file
...@@ -19,8 +19,7 @@ define([ ...@@ -19,8 +19,7 @@ define([
'kbn', 'kbn',
'moment', 'moment',
'./timeSeries', './timeSeries',
'./graphiteUtil', './graphiteSrv',
'config',
'jquery.flot', 'jquery.flot',
'jquery.flot.events', 'jquery.flot.events',
'jquery.flot.selection', 'jquery.flot.selection',
...@@ -29,7 +28,7 @@ define([ ...@@ -29,7 +28,7 @@ define([
'jquery.flot.stack', 'jquery.flot.stack',
'jquery.flot.stackpercent' 'jquery.flot.stackpercent'
], ],
function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { function (angular, app, $, _, kbn, moment, timeSeries, graphiteSrv) {
'use strict'; 'use strict';
...@@ -50,11 +49,7 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -50,11 +49,7 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
{ {
title:'Style', title:'Style',
src:'app/panels/graphite/styleEditor.html' src:'app/panels/graphite/styleEditor.html'
}, }
{
title:'Queries',
src:'app/panels/graphite/queriesEditor.html'
},
], ],
status : "Stable", status : "Stable",
description : "A bucketed time series chart of the current query or queries. Uses the "+ description : "A bucketed time series chart of the current query or queries. Uses the "+
...@@ -169,10 +164,6 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -169,10 +164,6 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
*/ */
legend : true, legend : true,
/** @scratch /panels/histogram/3 /** @scratch /panels/histogram/3
* show_query:: If no alias is set, should the query be displayed?
*/
show_query : true,
/** @scratch /panels/histogram/3
* interactive:: Enable click-and-drag to zoom functionality * interactive:: Enable click-and-drag to zoom functionality
*/ */
interactive : true, interactive : true,
...@@ -197,16 +188,11 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -197,16 +188,11 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
/** @scratch /panels/histogram/3 /** @scratch /panels/histogram/3
* derivative:: Show each point on the x-axis as the change from the previous point * derivative:: Show each point on the x-axis as the change from the previous point
*/ */
derivative : false,
/** @scratch /panels/histogram/3
* tooltip object::
* tooltip.value_type::: Individual or cumulative controls how tooltips are display on stacked charts
* tooltip.query_as_alias::: If no alias is set, should the query be displayed?
*/
tooltip : { tooltip : {
value_type: 'cumulative', value_type: 'cumulative',
query_as_alias: true query_as_alias: true
} },
targets: []
}; };
_.defaults($scope.panel,_d); _.defaults($scope.panel,_d);
...@@ -215,7 +201,6 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -215,7 +201,6 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
_.defaults($scope.panel.grid,_d.grid); _.defaults($scope.panel.grid,_d.grid);
$scope.init = function() { $scope.init = function() {
// Hide view options by default // Hide view options by default
$scope.options = false; $scope.options = false;
...@@ -268,33 +253,6 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -268,33 +253,6 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
return $scope.panel.interval; return $scope.panel.interval;
}; };
var graphOptions = {
until: 'now',
targets: [
{
name: 'series 1',
color: '#CC6699',
target: "summarize(sum(prod.apps.tradera_site.*.counters.global.request_status.code_404.count), '30s')",
//target: 'integral(prod.apps.touchweb.snake.counters.login.success.count)',
//target: "randomWalk('random1')",
}
]
};
$scope.getGraphiteData = function (options, parameters) {
return $.ajax({
accepts: { text: 'application/json' },
cache: false,
dataType: 'json',
url: config.graphiteUrl,
type: "POST",
data: parameters.join('&'),
error: function(xhr, textStatus, errorThrown) {
$scope.panel.error = 'Failed to do graphite POST request: ' + textStatus + ' : ' + errorThrown;
}
});
};
$scope.colors = [ $scope.colors = [
"#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1 "#7EB26D","#EAB839","#6ED0E0","#EF843C","#E24D42","#1F78C1","#BA43A9","#705DA0", //1
"#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2 "#508642","#CCA300","#447EBC","#C15C17","#890F02","#0A437C","#6D1F62","#584477", //2
...@@ -317,18 +275,24 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -317,18 +275,24 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
$scope.get_data = function() { $scope.get_data = function() {
delete $scope.panel.error; delete $scope.panel.error;
$scope.panelMeta.loading = true;
var range = $scope.get_time_range(); var range = $scope.get_time_range();
var interval = $scope.get_interval(range); var interval = $scope.get_interval(range);
graphOptions.from = $.plot.formatDate(range.from, '%H%:%M_%Y%m%d'); var graphiteLoadOptions = {
range: range,
targets: $scope.panel.targets
};
$scope.panelMeta.loading = true; var result = RQ.sequence([graphiteSrv.loadGraphiteData]);
var graphiteParameters = graphiteUtil.build_graphite_options(graphOptions, true); result(function (results, failure) {
var request = $scope.getGraphiteData(graphOptions, graphiteParameters); if (failure || !results) {
$scope.populate_modal(graphiteParameters); $scope.panel.error = 'Failed to do fetch graphite data: ' + failure;
return;
}
request.done(function(results) {
$scope.data = []; $scope.data = [];
$scope.panelMeta.loading = false; $scope.panelMeta.loading = false;
...@@ -339,7 +303,7 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -339,7 +303,7 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
console.log('Data from graphite:', results); console.log('Data from graphite:', results);
var tsOpts = { var tsOpts = {
interval: "30s", interval: interval,
start_date: range && range.from, start_date: range && range.from,
end_date: range && range.to, end_date: range && range.to,
fill_style: 'connect' fill_style: 'connect'
...@@ -380,7 +344,13 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) { ...@@ -380,7 +344,13 @@ function (angular, app, $, _, kbn, moment, timeSeries, graphiteUtil, config) {
// Tell the histogram directive to render. // Tell the histogram directive to render.
$scope.$emit('render'); $scope.$emit('render');
});
}, graphiteLoadOptions);
};
$scope.add_target = function() {
$scope.panel.targets.push({target: ''});
}; };
// function $scope.zoom // function $scope.zoom
......
<h4>Charted</h4>
<div ng-include src="'app/partials/querySelect.html'"></div>
<div class="editor-row">
<h4>Markers</h4>
<div class="small">
Here you can specify a query to be plotted on your chart as a marker. Hovering over a marker will display the field you specify below. If more documents are found than the limit you set, they will be scored by Elasticsearch and events that best match your query will be displayed.
</div>
<style>
.querySelect .query {
margin-right: 5px;
}
.querySelect .selected {
border: 3px solid;
}
.querySelect .unselected {
border: 0px solid;
}
</style>
<p>
<div class="editor-option">
<label class="small">Enable</label>
<input type="checkbox" ng-change="set_refresh(true)" ng-model="panel.annotate.enable" ng-checked="panel.annotate.enable">
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Marker Query</label>
<input type="text" ng-change="set_refresh(true)" class="input-large" ng-model="panel.annotate.query"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Tooltip field</label>
<input type="text" class="input-small" ng-model="panel.annotate.field" bs-typeahead="fields.list"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Limit <tip>Max markers on the chart</tip></label>
<input type="number" class="input-mini" ng-model="panel.annotate.size" ng-change="set_refresh(true)"/>
</div>
<div class="editor-option" ng-show="panel.annotate.enable">
<label class="small">Sort <tip>Determine the most relevant markers using this field</tip></label>
<input type="text" class="input-small" bs-typeahead="fields.list" ng-model="panel.annotate.sort[0]" ng-change="set_refresh(true)" />
<i ng-click="panel.annotate.sort[1] = _.toggle(panel.annotate.sort[1],'desc','asc');set_refresh(true)" ng-class="{'icon-chevron-up': panel.annotate.sort[1] == 'asc','icon-chevron-down': panel.annotate.sort[1] == 'desc'}"></i>
</div>
</div>
/*
rq.js
Douglas Crockford
2013-10-11
Public Domain
This package uses four kinds of functions:
requestor
requestion
quash
requestory
requestor(requestion [, initial])
may return a quash function
A requestor is a function that makes a request. Such a request need not
be satisified immediately. It is likely that the request will not be
satisified until some future turn. Requestors provide a means of dealing
with future activities without blocking.
A requestor is a function that takes a requestion function as its first
parameter, and optionally an initial value as its second parameter. The
requestor uses the requestion to report its result. A requestor may
optionally return a quash function that might be used to cancel the
request, triggering the requestion function with a failure result.
The initial parameter contains a value that may be used to initialize the
request. It is provided specifically for RQ.sequence, but it may be passed
to any requestor.
requestion(success, failure)
returns undefined
A requestion function is a continuation or callback. It is used to deliver
the result of a request. A requestion takes two arguments: success and
failure. If the request succeeds, then the result will be passed to the
requestion function as the success parameter, and the failure parameter
will be undefined. If the request fails, then the requestion function will
be passed the reason as the failure parameter. If failure is undefined,
then the request succeeded. If failure is any other value, then the request
failed.
quash(reason)
returns undefined
If a request is likely to be expensive to satisfy, the requestor may
optionally return a quash function that would allow the request to be
cancelled. A requestor is not required to return a quash function, and
the quash function will not be guaranteed to cancel the request. The
quash's reason argument may become the requestion's failure argument.
requestory([arguments])
returns a requestor function
A requestory is a factory function that produces a requestor function. A
requestory function will usually take parameters that will customize or
specialize a request. It is possible to write requestor functions by hand,
but it is usually easier to generate them with requestories.
The RQ object contains some requestory functions that permit the composition of
requestors:
RQ.fallback(requestors, milliseconds)
RQ.race(requestors, milliseconds)
RQ.parallel(requestors, optionals, milliseconds, tilliseconds)
RQ.sequence(requestors, milliseconds)
Each of these four requestory functions returns a requestor function that
returns a quash function.
RQ.fallback(requestors, milliseconds)
RQ.fallback returns a requestor function that will call the first element
in the requestors array. If that is ultimately successful, its value will
be passed to the requestion. But if it fails, the next element will be
called, and so on. If none of the elements are successful, then the
fallback fails. If any succeeds, then the fallback succeeds.
If the optional milliseconds argument is supplied, then if a request is not
successful in the allotted time, then the fallback fails, and the pending
requestor is cancelled.
RQ.race(requestors [, milliseconds])
RQ.race returns a requestor that starts all of the functions in the
requestors array in parallel. Its result is the result of the first of
those requestors to successfully finish (all of the other requestors are
cancelled). If all of those requestors fail, then the race fails.
If the optional milliseconds argument is supplied, then if no requestor has
been successful in the allotted time, then the race fails, and all pending
requestors are cancelled.
RQ.parallel(requestors [, milliseconds])
RQ.parallel(requestors, optionals [, milliseconds, [tilliseconds]])
RQ.parallel returns a requestor that processes many requestors in parallel,
producing an array of all of the successful results. It can take two arrays
of requests: Those that are required to produce results, and those that may
optionally produce results. Each of the optional requestors has until all
of the required requestors have finished, or until the optional
tilliseconds timer has expired.
The result maps the requestors and optionals into a single array. The
value produced by the first element of the requestors array provides the
first element of the result.
If the optional milliseconds argument is supplied, then if all of the
required requestors are not successful in the allotted time, then the
parallel fails. If there are no required requestors, and if at least one
optional requestor is successful within the allotted time, then the
parallel succeeds.
RQ.sequence(requestors [, milliseconds])
RQ.sequence returns a requestor that processes each element of the
requestors array one at a time. Each will be passed the result of the
previous. If all succeed, then the sequence succeeds, having the result of
the last of the requestors. If any fail, then the sequence fails.
If the optional milliseconds argument is supplied, then if all of the
requestors have not all completed in the allotted time, then the sequence
fails and the pending requestor is cancelled.
*/
/*global
clearTimeout, setImmediate, setTimeout
*/
/*properties
array, evidence, fallback, freeze, forEach, index, isArray, length,
message, method, milliseconds, name, parallel, race, sequence, value
*/
var RQ = (function () {
'use strict';
function expired(method, milliseconds) {
// Make an expired exception.
return {
name: "expired",
method: method,
message: "expired after " + milliseconds,
milliseconds: milliseconds
};
}
function check(method, requestors, milliseconds, optionals, tilliseconds) {
// Verify that the arguments are typed properly.
function is_function(value, index, array) {
if (typeof value !== 'function') {
var e = new TypeError("not a function");
e.array = array;
e.index = index;
e.method = method;
e.value = value;
throw e;
}
}
// requestors must be an array of functions, and it may be empty only if
// optionals is present.
if (optionals === undefined) {
if (!Array.isArray(requestors) || requestors.length === 0) {
throw new TypeError(method + " requestors");
}
} else {
if (requestors && !Array.isArray(requestors)) {
throw new TypeError(method + " requestors");
}
if (!Array.isArray(optionals) || optionals.length === 0) {
throw new TypeError(method + " optionals");
}
optionals.forEach(is_function);
}
requestors.forEach(is_function);
if (milliseconds &&
(typeof milliseconds !== 'number' || milliseconds < 0)) {
throw new TypeError(method + " milliseconds");
}
if (tilliseconds &&
(typeof tilliseconds !== 'number' || tilliseconds < 0)) {
throw new TypeError(method + " tilliseconds");
}
}
function check_requestion(method, requestion, initial) {
if (typeof requestion !== 'function') {
throw new TypeError(method + " requestion");
}
if (initial !== null && typeof initial === 'object') {
Object.freeze(initial);
}
}
return {
fallback : function fallback(requestors, milliseconds) {
// RQ.fallback takes an array of requestor functions, and returns a requestor
// that will call them each in order until it finds a successful outcome.
// If all of the requestor functions fail, then the fallback fails. If the time
// expires, then work in progress is cancelled.
check("RQ.fallack", requestors, milliseconds);
return function requestor(requestion, initial) {
var cancel,
timeout_id;
function finish(success, failure) {
var r = requestion;
cancel = null;
if (r) {
if (timeout_id) {
clearTimeout(timeout_id);
}
requestion = null;
timeout_id = null;
return r(success, failure);
}
}
function quash(reason) {
if (requestion && typeof cancel === 'function') {
setImmediate(cancel, reason);
}
return finish(undefined, reason || true);
}
check_requestion("RQ.fallack", requestion, initial);
if (milliseconds) {
timeout_id = setTimeout(function () {
return quash(expired("RQ.fallback", milliseconds));
}, milliseconds);
}
(function next(index, failure) {
if (typeof requestion === 'function') {
// If there are no more requestors, then signal failure.
if (index >= requestors.length) {
clearTimeout(timeout_id);
cancel = null;
return quash(failure);
}
// If there is another requestor, call it in the next turn, passing the value
// and a requestion that will take the next step.
var requestor = requestors[index];
setImmediate(function () {
var once = true;
if (typeof requestion === 'function') {
cancel = requestor(
function requestion(success, failure) {
if (once) {
once = false;
cancel = null;
return failure === undefined
? finish(success)
: next(index + 1, failure);
}
},
initial
);
}
});
}
}(0));
return quash;
};
},
parallel: function parallel(requestors, optionals, milliseconds,
tilliseconds) {
// RQ.parallel takes an array of requestors, and an optional second array of
// requestors, and starts them all. It succeeds if all of the requestors in
// the first array finish successfully before the time expires. The result
// is an array collecting the results of all of the requestors.
if (typeof optionals === 'number') {
milliseconds = optionals;
tilliseconds = undefined;
optionals = undefined;
}
check("RQ.parallel", requestors, milliseconds, optionals,
tilliseconds);
return function requestor(requestion, initial) {
var quashes = [],
optionals_remaining,
optionals_successes = 0,
requestors_length = requestors.length,
requestors_remaining = requestors.length,
results = [],
timeout_till,
timeout_id;
function finish(success, failure) {
var r = requestion;
if (r) {
requestion = null;
if (timeout_id) {
clearTimeout(timeout_id);
timeout_id = null;
}
if (timeout_till) {
clearTimeout(timeout_till);
timeout_till = null;
}
quashes.forEach(function (quash) {
if (typeof quash === 'function') {
return setImmediate(quash, failure);
}
});
quashes = null;
results = null;
return r(success, failure);
}
}
function quash(reason) {
return finish(undefined, reason || true);
}
check_requestion("RQ.parallel", requestion, initial);
// milliseconds, if specified, says take no longer to process this request. If
// any of the required requestors are not successful by this time, the parallel
// requestor fails.
if (milliseconds) {
timeout_id = setTimeout(function () {
timeout_id = null;
return requestors_remaining === 0 &&
(requestors_length > 0 ||
optionals_successes > 0)
? finish(results)
: quash(expired("RQ.parallel", milliseconds));
}, milliseconds);
// tilliseconds, if specified, gives more time for the optional requestors to
// complete. Normally, the optional requestors have until all of the required
// requestors finish. If tilliseconds is larger than milliseconds, milliseconds
// wins.
}
if (tilliseconds) {
timeout_till = setTimeout(function () {
timeout_till = null;
if (requestors_remaining === 0) {
return finish(results);
}
}, tilliseconds);
}
if (requestors) {
requestors.forEach(function (requestor, index) {
return setImmediate(function () {
var once = true, cancel = requestor(
function requestion(success, failure) {
if (once && quashes) {
once = false;
quashes[index] = null;
if (failure !== undefined) {
return quash(failure);
}
results[index] = success;
requestors_remaining -= 1;
if (requestors_remaining === 0 &&
!timeout_till) {
return finish(results);
}
}
},
initial
);
if (quashes && quashes[index] === undefined) {
quashes[index] = cancel;
}
});
});
}
if (optionals) {
optionals_remaining = optionals.length;
optionals.forEach(function (requestor, index) {
return setImmediate(function () {
var once = true, cancel = requestor(
function requestion(success, failure) {
if (once && quashes) {
once = false;
quashes[requestors_length + index]
= null;
if (failure === undefined) {
results[requestors_length + index]
= success;
optionals_successes += 1;
}
optionals_remaining -= 1;
if (optionals_remaining === 0) {
if (requestors_remaining === 0) {
return requestors_length > 0 ||
optionals_successes > 0
? finish(results)
: quash(failure);
}
if (timeout_till) {
clearTimeout(timeout_till);
timeout_till = null;
}
}
}
},
initial
);
if (quashes[requestors_length + index] ===
undefined) {
quashes[requestors_length + index] = cancel;
}
});
});
}
return quash;
};
},
race: function race(requestors, milliseconds) {
// RQ.race takes an array of requestor functions. It starts them all
// immediately. The first to finish wins. A race is successful if any
// contestant is successful. It fails if all requestors fail or if the time
// expires.
check("RQ.race", requestors, milliseconds);
return function requestor(requestion, initial) {
var quashes = [],
remaining = requestors.length,
timeout_id;
function finish(success, failure) {
var r = requestion;
if (r) {
requestion = null;
if (timeout_id) {
clearTimeout(timeout_id);
}
quashes.forEach(function stop(quash) {
if (typeof quash === 'function') {
return setImmediate(quash);
}
});
quashes = null;
return r(success, failure);
}
}
function quash(reason) {
return finish(undefined, reason || true);
}
check_requestion("RQ.race", requestion, initial);
if (milliseconds) {
timeout_id = setTimeout(function timeout_id() {
return quash(expired("RQ.race", milliseconds));
}, milliseconds);
}
requestors.forEach(function (requestor, index) {
return setImmediate(function () {
var once = true, cancel = requestor(
function requestion(success, failure) {
if (once && quashes) {
once = false;
quashes[index] = null;
if (failure === undefined) {
return finish(success);
}
remaining -= 1;
if (remaining === 0) {
return quash(failure);
}
}
},
initial
);
if (quashes[index] === undefined) {
quashes[index] = cancel;
}
});
});
return quash;
};
},
sequence: function sequence(requestors, milliseconds) {
// RQ.sequence takes an array of requestor functions, and returns a requestor
// that will call them each in order. An initial value is passed to each, which
// is the previous success result.
// If any of the requestor functions fail, then the whole sequence fails, and
// the remaining requestors are not called.
check("RQ.sequence", requestors, milliseconds);
return function requestor(requestion, initial) {
var cancel,
timeout_id;
function finish(success, failure) {
var r = requestion;
cancel = null;
if (r) {
if (timeout_id) {
clearTimeout(timeout_id);
}
requestion = null;
return r(success, failure);
}
}
function quash(reason) {
if (requestion && typeof cancel === 'function') {
setImmediate(cancel, reason);
}
return finish(undefined, reason || true);
}
check_requestion("RQ.sequence", requestion, initial);
if (milliseconds) {
timeout_id = setTimeout(function () {
timeout_id = null;
return quash(expired("RQ.sequence", milliseconds));
}, milliseconds);
}
(function next(index) {
var requestor, r = requestion;
if (typeof r === 'function') {
// If there are no more requestors, then signal success.
if (index >= requestors.length) {
if (timeout_id) {
clearTimeout(timeout_id);
}
requestion = null;
cancel = null;
return r(initial);
}
// If there is another requestor, call it in the next turn, passing the value
// and a requestion that will take the next step.
requestor = requestors[index];
setImmediate(function () {
var once = true;
cancel = requestor(
function requestion(success, failure) {
if (once) {
once = false;
cancel = null;
if (failure !== undefined) {
return quash(failure);
}
initial = success;
return next(index + 1);
}
},
initial
);
});
}
}(0));
return quash;
};
}
};
}());
\ No newline at end of file
(function (global, undefined) {
"use strict";
var tasks = (function () {
function Task(handler, args) {
this.handler = handler;
this.args = args;
}
Task.prototype.run = function () {
// See steps in section 5 of the spec.
if (typeof this.handler === "function") {
// Choice of `thisArg` is not in the setImmediate spec; `undefined` is in the setTimeout spec though:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html
this.handler.apply(undefined, this.args);
} else {
var scriptSource = "" + this.handler;
/*jshint evil: true */
eval(scriptSource);
}
};
var nextHandle = 1; // Spec says greater than zero
var tasksByHandle = {};
var currentlyRunningATask = false;
return {
addFromSetImmediateArguments: function (args) {
var handler = args[0];
var argsToHandle = Array.prototype.slice.call(args, 1);
var task = new Task(handler, argsToHandle);
var thisHandle = nextHandle++;
tasksByHandle[thisHandle] = task;
return thisHandle;
},
runIfPresent: function (handle) {
// From the spec: "Wait until any invocations of this algorithm started before this one have completed."
// So if we're currently running a task, we'll need to delay this invocation.
if (!currentlyRunningATask) {
var task = tasksByHandle[handle];
if (task) {
currentlyRunningATask = true;
try {
task.run();
} finally {
delete tasksByHandle[handle];
currentlyRunningATask = false;
}
}
} else {
// Delay by doing a setTimeout. setImmediate was tried instead, but in Firefox 7 it generated a
// "too much recursion" error.
global.setTimeout(function () {
tasks.runIfPresent(handle);
}, 0);
}
},
remove: function (handle) {
delete tasksByHandle[handle];
}
};
}());
function canUseNextTick() {
// Don't get fooled by e.g. browserify environments.
return typeof process === "object" &&
Object.prototype.toString.call(process) === "[object process]";
}
function canUseMessageChannel() {
return !!global.MessageChannel;
}
function canUsePostMessage() {
// The test against `importScripts` prevents this implementation from being installed inside a web worker,
// where `global.postMessage` means something completely different and can't be used for this purpose.
if (!global.postMessage || global.importScripts) {
return false;
}
var postMessageIsAsynchronous = true;
var oldOnMessage = global.onmessage;
global.onmessage = function () {
postMessageIsAsynchronous = false;
};
global.postMessage("", "*");
global.onmessage = oldOnMessage;
return postMessageIsAsynchronous;
}
function canUseReadyStateChange() {
return "document" in global && "onreadystatechange" in global.document.createElement("script");
}
function installNextTickImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
process.nextTick(function () {
tasks.runIfPresent(handle);
});
return handle;
};
}
function installMessageChannelImplementation(attachTo) {
var channel = new global.MessageChannel();
channel.port1.onmessage = function (event) {
var handle = event.data;
tasks.runIfPresent(handle);
};
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
channel.port2.postMessage(handle);
return handle;
};
}
function installPostMessageImplementation(attachTo) {
// Installs an event handler on `global` for the `message` event: see
// * https://developer.mozilla.org/en/DOM/window.postMessage
// * http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html#crossDocumentMessages
var MESSAGE_PREFIX = "com.bn.NobleJS.setImmediate" + Math.random();
function isStringAndStartsWith(string, putativeStart) {
return typeof string === "string" && string.substring(0, putativeStart.length) === putativeStart;
}
function onGlobalMessage(event) {
// This will catch all incoming messages (even from other windows!), so we need to try reasonably hard to
// avoid letting anyone else trick us into firing off. We test the origin is still this window, and that a
// (randomly generated) unpredictable identifying prefix is present.
if (event.source === global && isStringAndStartsWith(event.data, MESSAGE_PREFIX)) {
var handle = event.data.substring(MESSAGE_PREFIX.length);
tasks.runIfPresent(handle);
}
}
if (global.addEventListener) {
global.addEventListener("message", onGlobalMessage, false);
} else {
global.attachEvent("onmessage", onGlobalMessage);
}
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
// Make `global` post a message to itself with the handle and identifying prefix, thus asynchronously
// invoking our onGlobalMessage listener above.
global.postMessage(MESSAGE_PREFIX + handle, "*");
return handle;
};
}
function installReadyStateChangeImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
// Create a <script> element; its readystatechange event will be fired asynchronously once it is inserted
// into the document. Do so, thus queuing up the task. Remember to clean up once it's been called.
var scriptEl = global.document.createElement("script");
scriptEl.onreadystatechange = function () {
tasks.runIfPresent(handle);
scriptEl.onreadystatechange = null;
scriptEl.parentNode.removeChild(scriptEl);
scriptEl = null;
};
global.document.documentElement.appendChild(scriptEl);
return handle;
};
}
function installSetTimeoutImplementation(attachTo) {
attachTo.setImmediate = function () {
var handle = tasks.addFromSetImmediateArguments(arguments);
global.setTimeout(function () {
tasks.runIfPresent(handle);
}, 0);
return handle;
};
}
if (!global.setImmediate) {
// If supported, we should attach to the prototype of global, since that is where setTimeout et al. live.
var attachTo = typeof Object.getPrototypeOf === "function" && "setTimeout" in Object.getPrototypeOf(global) ?
Object.getPrototypeOf(global)
: global;
if (canUseNextTick()) {
// For Node.js before 0.9
installNextTickImplementation(attachTo);
} else if (canUsePostMessage()) {
// For non-IE10 modern browsers
installPostMessageImplementation(attachTo);
} else if (canUseMessageChannel()) {
// For web workers, where supported
installMessageChannelImplementation(attachTo);
} else if (canUseReadyStateChange()) {
// For IE 6–8
installReadyStateChangeImplementation(attachTo);
} else {
// For older browsers
installSetTimeoutImplementation(attachTo);
}
attachTo.clearImmediate = tasks.remove;
}
}(typeof global === "object" && global ? global : this));
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment