<commit_before>'use strict';

describe('unit tests for module ng.django.forms', function() {
	function compileForm($compile, scope, replace_value) {
		var template =
			'<form name="valid_form" action=".">' +
			'<input name="email_field" ng-model="model.email" type="text" {value} />' +
			'</form>';
		var form = angular.element(template.replace('{value}', replace_value));
		$compile(form)(scope);
		scope.$digest();
	}

	describe('test default form behavior', function() {
		var scope;

		beforeEach(inject(function($rootScope) {
			scope = $rootScope.$new();
		}));

		describe('on unbound forms', function() {
			it('the view value of empty input fields should remain undefined', inject(function($compile) {
				compileForm($compile, scope, '');
				expect(scope.valid_form.email_field.$viewValue).toBe(undefined);
			}));
		});

		describe('on bound forms', function() {
			it('the view value of filled input fields should remain undefined', inject(function($compile) {
				compileForm($compile, scope, 'value="john@example.net"');
				expect(scope.valid_form.email_field.$viewValue).toBe(undefined);
			}));
		});
	});

	describe('test overridden form behavior', function() {
		var scope;

		beforeEach(function() {
			// djangular's 'form' directive, overrides the behavior of the view value.
			module('ng.django.forms');
		});

		beforeEach(inject(function($rootScope) {
			scope = $rootScope.$new();
		}));

		describe('on unbound forms', function() {
			it('the view value of empty input fields should be empty', inject(function($compile) {
				compileForm($compile, scope, '');
				expect(scope.valid_form.email_field.$viewValue).toBe(undefined);
			}));
		});

		describe('on bound forms', function() {
			it('the view value of filled input fields should remain as is', inject(function($compile) {
				compileForm($compile, scope, 'value="john@example.net"');
				expect(scope.valid_form.email_field.$viewValue).toBe('john@example.net');
			}));
		});

	});

	describe('test directive validateDate', function() {
		var scope, form;

		beforeEach(function() {
			// djangular's 'form' directive, overrides the behavior of the view value.
			module('ng.django.forms');
		});

		beforeEach(inject(function($rootScope) {
			scope = $rootScope.$new();
		}));

		beforeEach(inject(function($compile) {
			var doc =
				'<form name="form">' +
				'<input name="date_field" ng-model="model.date" type="text" validate-date="^(\\d{4})-(\\d{1,2})-(\\d{1,2})$" />' +
				'</form>';
			var element = angular.element(doc);
			scope.model = { date: null };
			$compile(element)(scope);
			form = scope.form;
		}));

		it('to reject 2014/04/01', function() {
			form.date_field.$setViewValue('2014/04/01');
			scope.$digest();
			expect(form.date_field.$valid).toBe(false);
		});

		it('to accept 2014-04-01', function() {
			form.date_field.$setViewValue('2014-03-01');
			scope.$digest();
			expect(form.date_field.$valid).toBe(true);
		});

		it('to reject 2014-04-31', function() {
			form.date_field.$setViewValue('2014-02-29');
			scope.$digest();
			expect(form.date_field.$valid).toBe(false);
		});
	});

	describe('test provider djangoForm', function() {
		var scope, djangoForm;

		beforeEach(function() {
			module('ng.django.forms');
		});

		describe('using manual instantiation', function() {
			beforeEach(function() {
				angular.module('testApp', function() {}).config(function(djangoFormProvider) {
					djangoForm = djangoFormProvider.$get();
				});
				module('ng.django.forms', 'testApp');
			});

			beforeEach(inject(function($rootScope) {
				scope = $rootScope.$new();
			}));

			beforeEach(inject(function($compile) {
				var form = angular.element(
					'<form name="form" action=".">' +
					'<input name="email_field" ng-model="model.email" type="text" />' +
					'</form>'
				);
				$compile(form)(scope);
				scope.$digest();
			}));

			it('should give a valid form', function() {
				expect(djangoForm.setErrors(scope.form, {})).toBe(false);
				expect(scope.form.email_field.$valid).toBe(true);
			});

			it('should give an invalid form', function() {
				var errors = { email_field: ['A server side error occurred'] };
				expect(djangoForm.setErrors(scope.form, errors)).toBe(true);
				expect(scope.form.email_field.$valid).toBe(false);
			});

		});
	});

	describe('test provider djangoRMI', function() {
		var $httpBackend, djangoRMI;

		beforeEach(function() {
			module('ng.django.forms');
		});

		describe('emulating get_current_remote_methods', function() {
			beforeEach(function() {
				angular.module('testApp', function() {}).config(function(djangoRMIProvider) {
					djangoRMIProvider.configure({"foo": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "foo"}, "method": "auto"}, "bar": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "bar"}, "method": "auto"}});
				});
				module('ng.django.forms', 'testApp');
			});

			beforeEach(inject(function($injector) {
				$httpBackend = $injector.get('$httpBackend');
			}));

			it('should call View method foo using GET', inject(function(djangoRMI) {
				$httpBackend.when('GET', '/straight_methods/').respond(200, {success: true});
				djangoRMI.foo().success(function(data) {
					expect(data.success).toBe(true);
				});
				$httpBackend.flush();
			}));

			it('should call View method foo using POST', inject(function(djangoRMI) {
				$httpBackend.when('POST', '/straight_methods/').respond(200, {yeah: 'success'});
				djangoRMI.foo({some: 'data'}).success(function(data) {
					expect(data.yeah).toBe('success');
				});
				$httpBackend.flush();
			}));
		});

		describe('emulating get_all_remote_methods', function() {
			beforeEach(function() {
				angular.module('testApp', function() {}).config(function(djangoRMIProvider) {
					djangoRMIProvider.configure({"submethods": {"sub": {"app": {"foo": {"url": "/sub_methods/sub/app/", "headers": {"DjNg-Remote-Method": "foo"}, "method": "auto"}, "bar": {"url": "/sub_methods/sub/app/", "headers": {"DjNg-Remote-Method": "bar"}, "method": "auto"}}}}, "straightmethods": {"foo": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "foo"}, "method": "auto"}, "bar": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "bar"}, "method": "auto"}}});
				});
				module('ng.django.forms', 'testApp');
			});

			beforeEach(inject(function($injector) {
				$httpBackend = $injector.get('$httpBackend');
			}));

			it('should call View method foo using GET', inject(function(djangoRMI) {
				$httpBackend.when('GET', '/sub_methods/sub/app/').respond(200, {foo: null});
				djangoRMI.submethods.sub.app.foo().success(function(data) {
					expect(data.foo).toBe(null);
				});
				$httpBackend.flush();
			}));

			it('should call View method bar using GET', inject(function(djangoRMI) {
				$httpBackend.when('GET', '/sub_methods/sub/app/').respond(200, {bar: 'nothing'});
				djangoRMI.submethods.sub.app.bar().success(function(data) {
					expect(data.bar).toBe('nothing');
				});
				$httpBackend.flush();
			}));

			it('should call View method foo using POST', inject(function(djangoRMI) {
				$httpBackend.when('POST', '/sub_methods/sub/app/').respond(200, {foo: 'some data'});
				djangoRMI.submethods.sub.app.foo({some: 'data'}).success(function(data) {
					expect(data.foo).toBe('some data');
				});
				$httpBackend.flush();
			}));

			it('should call View method bar using POST', inject(function(djangoRMI) {
				$httpBackend.when('POST', '/sub_methods/sub/app/').respond(200, {bar: 'other data'});
				djangoRMI.submethods.sub.app.bar({other: 'data'}).success(function(data) {
					expect(data.bar).toBe('other data');
				});
				$httpBackend.flush();
			}));
		});
	});
});
<commit_msg>Fixed unit test<commit_after>'use strict';

describe('unit tests for module ng.django.forms', function() {
	function compileForm($compile, scope, replace_value) {
		var template =
			'<form name="valid_form" action=".">' +
			'<input name="email_field" ng-model="model.email" type="text" {value} />' +
			'</form>';
		var form = angular.element(template.replace('{value}', replace_value));
		$compile(form)(scope);
		scope.$digest();
	}

	describe('test default form behavior', function() {
		var scope;

		beforeEach(inject(function($rootScope) {
			scope = $rootScope.$new();
		}));

		describe('on unbound forms', function() {
			it('the view value of empty input fields should remain undefined', inject(function($compile) {
				compileForm($compile, scope, '');
				expect(scope.valid_form.email_field.$viewValue).toBe(undefined);
			}));
		});

		describe('on bound forms', function() {
			it('the view value of filled input fields should remain undefined', inject(function($compile) {
				compileForm($compile, scope, 'value="john@example.net"');
				expect(scope.valid_form.email_field.$viewValue).toBe(undefined);
			}));
		});
	});

	describe('test overridden form behavior', function() {
		var scope;

		beforeEach(function() {
			// djangular's 'form' directive, overrides the behavior of the view value.
			module('ng.django.forms');
		});

		beforeEach(inject(function($rootScope) {
			scope = $rootScope.$new();
		}));

		describe('on unbound forms', function() {
			it('the view value of empty input fields should be empty', inject(function($compile) {
				compileForm($compile, scope, '');
				expect(scope.valid_form.email_field.$viewValue).toBe('');
			}));
		});

		describe('on bound forms', function() {
			it('the view value of filled input fields should remain as is', inject(function($compile) {
				compileForm($compile, scope, 'value="john@example.net"');
				expect(scope.valid_form.email_field.$viewValue).toBe('john@example.net');
			}));
		});

	});

	describe('test directive validateDate', function() {
		var scope, form;

		beforeEach(function() {
			// djangular's 'form' directive, overrides the behavior of the view value.
			module('ng.django.forms');
		});

		beforeEach(inject(function($rootScope) {
			scope = $rootScope.$new();
		}));

		beforeEach(inject(function($compile) {
			var doc =
				'<form name="form">' +
				'<input name="date_field" ng-model="model.date" type="text" validate-date="^(\\d{4})-(\\d{1,2})-(\\d{1,2})$" />' +
				'</form>';
			var element = angular.element(doc);
			scope.model = { date: null };
			$compile(element)(scope);
			form = scope.form;
		}));

		it('to reject 2014/04/01', function() {
			form.date_field.$setViewValue('2014/04/01');
			scope.$digest();
			expect(form.date_field.$valid).toBe(false);
		});

		it('to accept 2014-04-01', function() {
			form.date_field.$setViewValue('2014-03-01');
			scope.$digest();
			expect(form.date_field.$valid).toBe(true);
		});

		it('to reject 2014-04-31', function() {
			form.date_field.$setViewValue('2014-02-29');
			scope.$digest();
			expect(form.date_field.$valid).toBe(false);
		});
	});

	describe('test provider djangoForm', function() {
		var scope, djangoForm;

		beforeEach(function() {
			module('ng.django.forms');
		});

		describe('using manual instantiation', function() {
			beforeEach(function() {
				angular.module('testApp', function() {}).config(function(djangoFormProvider) {
					djangoForm = djangoFormProvider.$get();
				});
				module('ng.django.forms', 'testApp');
			});

			beforeEach(inject(function($rootScope) {
				scope = $rootScope.$new();
			}));

			beforeEach(inject(function($compile) {
				var form = angular.element(
					'<form name="form" action=".">' +
					'<input name="email_field" ng-model="model.email" type="text" />' +
					'</form>'
				);
				$compile(form)(scope);
				scope.$digest();
			}));

			it('should give a valid form', function() {
				expect(djangoForm.setErrors(scope.form, {})).toBe(false);
				expect(scope.form.email_field.$valid).toBe(true);
			});

			it('should give an invalid form', function() {
				var errors = { email_field: ['A server side error occurred'] };
				expect(djangoForm.setErrors(scope.form, errors)).toBe(true);
				expect(scope.form.email_field.$valid).toBe(false);
			});

		});
	});

	describe('test provider djangoRMI', function() {
		var $httpBackend, djangoRMI;

		beforeEach(function() {
			module('ng.django.forms');
		});

		describe('emulating get_current_remote_methods', function() {
			beforeEach(function() {
				angular.module('testApp', function() {}).config(function(djangoRMIProvider) {
					djangoRMIProvider.configure({"foo": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "foo"}, "method": "auto"}, "bar": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "bar"}, "method": "auto"}});
				});
				module('ng.django.forms', 'testApp');
			});

			beforeEach(inject(function($injector) {
				$httpBackend = $injector.get('$httpBackend');
			}));

			it('should call View method foo using GET', inject(function(djangoRMI) {
				$httpBackend.when('GET', '/straight_methods/').respond(200, {success: true});
				djangoRMI.foo().success(function(data) {
					expect(data.success).toBe(true);
				});
				$httpBackend.flush();
			}));

			it('should call View method foo using POST', inject(function(djangoRMI) {
				$httpBackend.when('POST', '/straight_methods/').respond(200, {yeah: 'success'});
				djangoRMI.foo({some: 'data'}).success(function(data) {
					expect(data.yeah).toBe('success');
				});
				$httpBackend.flush();
			}));
		});

		describe('emulating get_all_remote_methods', function() {
			beforeEach(function() {
				angular.module('testApp', function() {}).config(function(djangoRMIProvider) {
					djangoRMIProvider.configure({"submethods": {"sub": {"app": {"foo": {"url": "/sub_methods/sub/app/", "headers": {"DjNg-Remote-Method": "foo"}, "method": "auto"}, "bar": {"url": "/sub_methods/sub/app/", "headers": {"DjNg-Remote-Method": "bar"}, "method": "auto"}}}}, "straightmethods": {"foo": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "foo"}, "method": "auto"}, "bar": {"url": "/straight_methods/", "headers": {"DjNg-Remote-Method": "bar"}, "method": "auto"}}});
				});
				module('ng.django.forms', 'testApp');
			});

			beforeEach(inject(function($injector) {
				$httpBackend = $injector.get('$httpBackend');
			}));

			it('should call View method foo using GET', inject(function(djangoRMI) {
				$httpBackend.when('GET', '/sub_methods/sub/app/').respond(200, {foo: null});
				djangoRMI.submethods.sub.app.foo().success(function(data) {
					expect(data.foo).toBe(null);
				});
				$httpBackend.flush();
			}));

			it('should call View method bar using GET', inject(function(djangoRMI) {
				$httpBackend.when('GET', '/sub_methods/sub/app/').respond(200, {bar: 'nothing'});
				djangoRMI.submethods.sub.app.bar().success(function(data) {
					expect(data.bar).toBe('nothing');
				});
				$httpBackend.flush();
			}));

			it('should call View method foo using POST', inject(function(djangoRMI) {
				$httpBackend.when('POST', '/sub_methods/sub/app/').respond(200, {foo: 'some data'});
				djangoRMI.submethods.sub.app.foo({some: 'data'}).success(function(data) {
					expect(data.foo).toBe('some data');
				});
				$httpBackend.flush();
			}));

			it('should call View method bar using POST', inject(function(djangoRMI) {
				$httpBackend.when('POST', '/sub_methods/sub/app/').respond(200, {bar: 'other data'});
				djangoRMI.submethods.sub.app.bar({other: 'data'}).success(function(data) {
					expect(data.bar).toBe('other data');
				});
				$httpBackend.flush();
			}));
		});
	});
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A basic statistics tracker.
 */

goog.provide('goog.stats.BasicStat');

goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.string.format');
goog.require('goog.structs.CircularBuffer');

/**
 * Tracks basic statistics over a specified time interval.
 *
 * Statistics are kept in a fixed number of slots, each representing
 * an equal portion of the time interval.
 *
 * Most methods optionally allow passing in the current time, so that
 * higher level stats can synchronize operations on multiple child
 * objects.  Under normal usage, the default of goog.now() should be
 * sufficient.
 *
 * @param {number} interval The stat interval, in milliseconds.
 * @constructor
 * @final
 */
goog.stats.BasicStat = function(interval) {
  'use strict';
  goog.asserts.assert(interval > 50);

  /**
   * The time interval that this statistic aggregates over.
   * @type {number}
   * @private
   */
  this.interval_ = interval;

  /**
   * The number of milliseconds in each slot.
   * @type {number}
   * @private
   */
  this.slotInterval_ = Math.floor(interval / goog.stats.BasicStat.NUM_SLOTS_);

  /**
   * The array of slots.
   * @type {goog.structs.CircularBuffer}
   * @private
   */
  this.slots_ =
      new goog.structs.CircularBuffer(goog.stats.BasicStat.NUM_SLOTS_);
};

/**
 * The number of slots. This value limits the accuracy of the get()
 * method to (this.interval_ / NUM_SLOTS).  A 1-minute statistic would
 * be accurate to within 2 seconds.
 * @type {number}
 * @private
 */
goog.stats.BasicStat.NUM_SLOTS_ = 50;

/**
 * @type {goog.log.Logger}
 * @private
 */
goog.stats.BasicStat.prototype.logger_ =
    goog.log.getLogger('goog.stats.BasicStat');

/**
 * @return {number} The interval which over statistics are being
 *     accumulated, in milliseconds.
 */
goog.stats.BasicStat.prototype.getInterval = function() {
  'use strict';
  return this.interval_;
};

/**
 * Increments the count of this statistic by the specified amount.
 *
 * @param {number} amt The amount to increase the count by.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 */
goog.stats.BasicStat.prototype.incBy = function(amt, opt_now) {
  'use strict';
  var now = opt_now ? opt_now : goog.now();
  this.checkForTimeTravel_(now);
  var slot = /** @type {goog.stats.BasicStat.Slot_} */ (this.slots_.getLast());
  if (!slot || now >= slot.end) {
    slot = new goog.stats.BasicStat.Slot_(this.getSlotBoundary_(now));
    this.slots_.add(slot);
  }
  slot.count += amt;
  slot.min = Math.min(amt, slot.min);
  slot.max = Math.max(amt, slot.max);
};

/**
 * Returns the count of the statistic over its configured time
 * interval.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 * @return {number} The total count over the tracked interval.
 */
goog.stats.BasicStat.prototype.get = function(opt_now) {
  'use strict';
  return this.reduceSlots_(opt_now, function(sum, slot) {
    'use strict';
    return sum + slot.count;
  }, 0);
};

/**
 * Returns the magnitute of the largest atomic increment that occurred
 * during the watched time interval.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 * @return {number} The maximum count of this statistic.
 */
goog.stats.BasicStat.prototype.getMax = function(opt_now) {
  'use strict';
  return this.reduceSlots_(opt_now, function(max, slot) {
    'use strict';
    return Math.max(max, slot.max);
  }, Number.MIN_VALUE);
};

/**
 * Returns the magnitute of the smallest atomic increment that
 * occurred during the watched time interval.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 * @return {number} The minimum count of this statistic.
 */
goog.stats.BasicStat.prototype.getMin = function(opt_now) {
  'use strict';
  return this.reduceSlots_(opt_now, function(min, slot) {
    'use strict';
    return Math.min(min, slot.min);
  }, Number.MAX_VALUE);
};

/**
 * Passes each active slot into a function and accumulates the result.
 *
 * @param {number|undefined} now The current time, in milliseconds.
 * @param {function(number, goog.stats.BasicStat.Slot_): number} func
 *     The function to call for every active slot.  This function
 *     takes two arguments: the previous result and the new slot to
 *     include in the reduction.
 * @param {number} val The initial value for the reduction.
 * @return {number} The result of the reduction.
 * @private
 */
goog.stats.BasicStat.prototype.reduceSlots_ = function(now, func, val) {
  'use strict';
  now = now || goog.now();
  this.checkForTimeTravel_(now);
  var rval = val;
  var start = this.getSlotBoundary_(now) - this.interval_;
  for (var i = this.slots_.getCount() - 1; i >= 0; --i) {
    var slot = /** @type {goog.stats.BasicStat.Slot_} */ (this.slots_.get(i));
    if (slot.end <= start) {
      break;
    }
    rval = func(rval, slot);
  }
  return rval;
};

/**
 * Computes the end time for the slot that should contain the count
 * around the given time.  This method ensures that every bucket is
 * aligned on a "this.slotInterval_" millisecond boundary.
 * @param {number} time The time to compute a boundary for.
 * @return {number} The computed boundary.
 * @private
 */
goog.stats.BasicStat.prototype.getSlotBoundary_ = function(time) {
  'use strict';
  return this.slotInterval_ * (Math.floor(time / this.slotInterval_) + 1);
};

/**
 * Checks that time never goes backwards.  If it does (for example,
 * the user changes their system clock), the object state is cleared.
 * @param {number} now The current time, in milliseconds.
 * @private
 */
goog.stats.BasicStat.prototype.checkForTimeTravel_ = function(now) {
  'use strict';
  var slot = /** @type {goog.stats.BasicStat.Slot_} */ (this.slots_.getLast());
  if (slot) {
    var slotStart = slot.end - this.slotInterval_;
    if (now < slotStart) {
      goog.log.warning(
          this.logger_,
          goog.string.format(
              'Went backwards in time: now=%d, slotStart=%d.  Resetting state.',
              now, slotStart));
      this.reset_();
    }
  }
};

/**
 * Clears any statistics tracked by this object, as though it were
 * freshly created.
 * @private
 */
goog.stats.BasicStat.prototype.reset_ = function() {
  'use strict';
  this.slots_.clear();
};

/**
 * A struct containing information for each sub-interval.
 * @param {number} end The end time for this slot, in milliseconds.
 * @constructor
 * @private
 */
goog.stats.BasicStat.Slot_ = function(end) {
  'use strict';
  /**
   * End time of this slot, exclusive.
   * @type {number}
   */
  this.end = end;
};

/**
 * Aggregated count within this slot.
 * @type {number}
 */
goog.stats.BasicStat.Slot_.prototype.count = 0;

/**
 * The smallest atomic increment of the count within this slot.
 * @type {number}
 */
goog.stats.BasicStat.Slot_.prototype.min = Number.MAX_VALUE;

/**
 * The largest atomic increment of the count within this slot.
 * @type {number}
 */
goog.stats.BasicStat.Slot_.prototype.max = Number.MIN_VALUE;
<commit_msg>Replace goog.now with Date.now. RELNOTES: n/a<commit_after>/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A basic statistics tracker.
 */

goog.provide('goog.stats.BasicStat');

goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.string.format');
goog.require('goog.structs.CircularBuffer');

/**
 * Tracks basic statistics over a specified time interval.
 *
 * Statistics are kept in a fixed number of slots, each representing
 * an equal portion of the time interval.
 *
 * Most methods optionally allow passing in the current time, so that
 * higher level stats can synchronize operations on multiple child
 * objects.  Under normal usage, the default of Date.now() should be
 * sufficient.
 *
 * @param {number} interval The stat interval, in milliseconds.
 * @constructor
 * @final
 */
goog.stats.BasicStat = function(interval) {
  'use strict';
  goog.asserts.assert(interval > 50);

  /**
   * The time interval that this statistic aggregates over.
   * @type {number}
   * @private
   */
  this.interval_ = interval;

  /**
   * The number of milliseconds in each slot.
   * @type {number}
   * @private
   */
  this.slotInterval_ = Math.floor(interval / goog.stats.BasicStat.NUM_SLOTS_);

  /**
   * The array of slots.
   * @type {goog.structs.CircularBuffer}
   * @private
   */
  this.slots_ =
      new goog.structs.CircularBuffer(goog.stats.BasicStat.NUM_SLOTS_);
};

/**
 * The number of slots. This value limits the accuracy of the get()
 * method to (this.interval_ / NUM_SLOTS).  A 1-minute statistic would
 * be accurate to within 2 seconds.
 * @type {number}
 * @private
 */
goog.stats.BasicStat.NUM_SLOTS_ = 50;

/**
 * @type {goog.log.Logger}
 * @private
 */
goog.stats.BasicStat.prototype.logger_ =
    goog.log.getLogger('goog.stats.BasicStat');

/**
 * @return {number} The interval which over statistics are being
 *     accumulated, in milliseconds.
 */
goog.stats.BasicStat.prototype.getInterval = function() {
  'use strict';
  return this.interval_;
};

/**
 * Increments the count of this statistic by the specified amount.
 *
 * @param {number} amt The amount to increase the count by.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 */
goog.stats.BasicStat.prototype.incBy = function(amt, opt_now) {
  'use strict';
  var now = opt_now ? opt_now : Date.now();
  this.checkForTimeTravel_(now);
  var slot = /** @type {goog.stats.BasicStat.Slot_} */ (this.slots_.getLast());
  if (!slot || now >= slot.end) {
    slot = new goog.stats.BasicStat.Slot_(this.getSlotBoundary_(now));
    this.slots_.add(slot);
  }
  slot.count += amt;
  slot.min = Math.min(amt, slot.min);
  slot.max = Math.max(amt, slot.max);
};

/**
 * Returns the count of the statistic over its configured time
 * interval.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 * @return {number} The total count over the tracked interval.
 */
goog.stats.BasicStat.prototype.get = function(opt_now) {
  'use strict';
  return this.reduceSlots_(opt_now, function(sum, slot) {
    'use strict';
    return sum + slot.count;
  }, 0);
};

/**
 * Returns the magnitute of the largest atomic increment that occurred
 * during the watched time interval.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 * @return {number} The maximum count of this statistic.
 */
goog.stats.BasicStat.prototype.getMax = function(opt_now) {
  'use strict';
  return this.reduceSlots_(opt_now, function(max, slot) {
    'use strict';
    return Math.max(max, slot.max);
  }, Number.MIN_VALUE);
};

/**
 * Returns the magnitute of the smallest atomic increment that
 * occurred during the watched time interval.
 * @param {number=} opt_now The time, in milliseconds, to be treated
 *     as the "current" time.  The current time must always be greater
 *     than or equal to the last time recorded by this stat tracker.
 * @return {number} The minimum count of this statistic.
 */
goog.stats.BasicStat.prototype.getMin = function(opt_now) {
  'use strict';
  return this.reduceSlots_(opt_now, function(min, slot) {
    'use strict';
    return Math.min(min, slot.min);
  }, Number.MAX_VALUE);
};

/**
 * Passes each active slot into a function and accumulates the result.
 *
 * @param {number|undefined} now The current time, in milliseconds.
 * @param {function(number, goog.stats.BasicStat.Slot_): number} func
 *     The function to call for every active slot.  This function
 *     takes two arguments: the previous result and the new slot to
 *     include in the reduction.
 * @param {number} val The initial value for the reduction.
 * @return {number} The result of the reduction.
 * @private
 */
goog.stats.BasicStat.prototype.reduceSlots_ = function(now, func, val) {
  'use strict';
  now = now || Date.now();
  this.checkForTimeTravel_(now);
  var rval = val;
  var start = this.getSlotBoundary_(now) - this.interval_;
  for (var i = this.slots_.getCount() - 1; i >= 0; --i) {
    var slot = /** @type {goog.stats.BasicStat.Slot_} */ (this.slots_.get(i));
    if (slot.end <= start) {
      break;
    }
    rval = func(rval, slot);
  }
  return rval;
};

/**
 * Computes the end time for the slot that should contain the count
 * around the given time.  This method ensures that every bucket is
 * aligned on a "this.slotInterval_" millisecond boundary.
 * @param {number} time The time to compute a boundary for.
 * @return {number} The computed boundary.
 * @private
 */
goog.stats.BasicStat.prototype.getSlotBoundary_ = function(time) {
  'use strict';
  return this.slotInterval_ * (Math.floor(time / this.slotInterval_) + 1);
};

/**
 * Checks that time never goes backwards.  If it does (for example,
 * the user changes their system clock), the object state is cleared.
 * @param {number} now The current time, in milliseconds.
 * @private
 */
goog.stats.BasicStat.prototype.checkForTimeTravel_ = function(now) {
  'use strict';
  var slot = /** @type {goog.stats.BasicStat.Slot_} */ (this.slots_.getLast());
  if (slot) {
    var slotStart = slot.end - this.slotInterval_;
    if (now < slotStart) {
      goog.log.warning(
          this.logger_,
          goog.string.format(
              'Went backwards in time: now=%d, slotStart=%d.  Resetting state.',
              now, slotStart));
      this.reset_();
    }
  }
};

/**
 * Clears any statistics tracked by this object, as though it were
 * freshly created.
 * @private
 */
goog.stats.BasicStat.prototype.reset_ = function() {
  'use strict';
  this.slots_.clear();
};

/**
 * A struct containing information for each sub-interval.
 * @param {number} end The end time for this slot, in milliseconds.
 * @constructor
 * @private
 */
goog.stats.BasicStat.Slot_ = function(end) {
  'use strict';
  /**
   * End time of this slot, exclusive.
   * @type {number}
   */
  this.end = end;
};

/**
 * Aggregated count within this slot.
 * @type {number}
 */
goog.stats.BasicStat.Slot_.prototype.count = 0;

/**
 * The smallest atomic increment of the count within this slot.
 * @type {number}
 */
goog.stats.BasicStat.Slot_.prototype.min = Number.MAX_VALUE;

/**
 * The largest atomic increment of the count within this slot.
 * @type {number}
 */
goog.stats.BasicStat.Slot_.prototype.max = Number.MIN_VALUE;
<|endoftext|>


<commit_before>/*
 * Notice Draft 0.2
 * for jQuery UI
 *
 * Copyright (c) 2009 idle sign
 *
 * Depends:
 *	ui.core.js
 *	effects.pulsate.js
 */
(function($) {

	$.widget('ui.notice', {

		// create notice
		_init: function() {

			if (this.element.is('div')) {
				var o = this.options;

				this.noticeBox = this.element;
				this.hide();
				this.noticeBox.attr('role', 'notice')
				this.noticeBox.wrapInner('

');
				this.innerBox = this.noticeBox.children().eq(0).addClass(o.innerBoxClass);
				this.textBox = this.innerBox.children().eq(0);
				this.storedText = this.textBox.html();
				// custom text
				if (o.text) this.text(o.text);
				this.innerBox.prepend('');
				this.iconBox = this.innerBox.children().eq(0);
				this.noticeBox.addClass(o.widgetClass);
				this.setType(o.type);
				if (o.autoShow)	this.animate();
			}

		},

		text: function(text){
			this.textBox.html(text);
		},

		setType: function(type){
			if (this.bordersClass) this.innerBox.removeClass(this.bordersClass);
			if (this.iconClass) this.iconBox.removeClass(this.iconClass);
			switch (type) {
				case 'error':
					this.bordersClass = 'ui-state-error';
					this.iconClass = 'ui-icon ui-notice-icon ui-icon-alert';
					break;
				default:
					this.bordersClass = 'ui-state-highlight';
					this.iconClass = 'ui-icon ui-notice-icon ui-icon-info';
					break;
			}
			this.innerBox.addClass(this.bordersClass);
			this.iconBox.addClass(this.iconClass);
		},

		show: function(params){
			if (params.type) this.setType(params.type)
			if (params.text) this.text(params.text);
			this.animate();
		},

		animate: function(){
			var o = this.options,
				fn = '';

			if (o.animate){
				if (o.autoHide){
					fn = function(){ $(this).hide(); }
				}
				this.noticeBox.effect('pulsate', { times:3 }, 1000, fn );
			} else {
				this.noticeBox.show();
			}
		},

		hide: function(){
			this.noticeBox.hide();
		},

		destroy: function(){
			var o = this.options;
			this.innerBox
				.removeClass(o.innerBoxClass)
				.removeClass(this.borderClass);
			this.noticeBox
				.html(this.storedText)
				.removeAttr('role')
				.removeClass(o.widgetClass);
		}

	});

	$.extend($.ui.notice, {
		version: '0.2',
		defaults: {
			type: 'alert',
			text: false,
			animate: true,
			autoShow: false,
			autoHide: false,
			// styling
			widgetClass: 'ui-helper-reset ui-widget',
			innerBoxClass: 'ui-corner-all ui-notice-inner'
		}
	});

})(jQuery);<commit_msg>* autoShow default set to true<commit_after>/*
 * Notice Draft 0.2
 * for jQuery UI
 *
 * Copyright (c) 2009 idle sign
 *
 * Depends:
 *	ui.core.js
 *	effects.pulsate.js
 */
(function($) {

	$.widget('ui.notice', {

		// create notice
		_init: function() {

			if (this.element.is('div')) {
				var o = this.options;

				this.noticeBox = this.element;
				if (!o.autoShow) this.hide();
				this.noticeBox.attr('role', 'notice')
				this.noticeBox.wrapInner('

');
				this.innerBox = this.noticeBox.children().eq(0).addClass(o.innerBoxClass);
				this.textBox = this.innerBox.children().eq(0);
				this.storedText = this.textBox.html();
				// custom text
				if (o.text) this.text(o.text);
				this.innerBox.prepend('');
				this.iconBox = this.innerBox.children().eq(0);
				this.noticeBox.addClass(o.widgetClass);
				this.setType(o.type);
				if (o.autoShow)	this.animate();
			}

		},

		text: function(text){
			this.textBox.html(text);
		},

		setType: function(type){
			if (this.bordersClass) this.innerBox.removeClass(this.bordersClass);
			if (this.iconClass) this.iconBox.removeClass(this.iconClass);
			switch (type) {
				case 'error':
					this.bordersClass = 'ui-state-error';
					this.iconClass = 'ui-icon ui-notice-icon ui-icon-alert';
					break;
				default:
					this.bordersClass = 'ui-state-highlight';
					this.iconClass = 'ui-icon ui-notice-icon ui-icon-info';
					break;
			}
			this.innerBox.addClass(this.bordersClass);
			this.iconBox.addClass(this.iconClass);
		},

		show: function(params){
			if (params.type) this.setType(params.type)
			if (params.text) this.text(params.text);
			this.animate();
		},

		animate: function(){
			var o = this.options,
				fn = '';

			if (o.animate){
				if (o.autoHide){
					fn = function(){ $(this).hide(); }
				}
				this.noticeBox.effect('pulsate', { times:3 }, 1000, fn );
			} else {
				this.noticeBox.show();
			}
		},

		hide: function(){
			this.noticeBox.hide();
		},

		destroy: function(){
			var o = this.options;
			this.innerBox
				.removeClass(o.innerBoxClass)
				.removeClass(this.borderClass);
			this.noticeBox
				.html(this.storedText)
				.removeAttr('role')
				.removeClass(o.widgetClass);
		}

	});

	$.extend($.ui.notice, {
		version: '0.2',
		defaults: {
			type: 'alert',
			text: false,
			animate: true,
			autoShow: true,
			autoHide: false,
			// styling
			widgetClass: 'ui-helper-reset ui-widget',
			innerBoxClass: 'ui-corner-all ui-notice-inner'
		}
	});

})(jQuery);<|endoftext|>


<commit_before>var assert        = require('assert');
var debug         = require('debug')('base:exchanges');
var _             = require('lodash');
var Promise       = require('promise');
var path          = require('path');
var fs            = require('fs');
var Validator     = require('./validator').Validator;
var aws           = require('aws-sdk-promise');
var amqplib       = require('amqplib');
var events        = require('events');
var util          = require('util');

/** Class for publishing to a set of declared exchanges */
var Publisher = function(conn, channel, entries, options) {
  events.EventEmitter.call(this);
  assert(options.validator instanceof Validator,
         "options.validator must be an instance of Validator");
  this._conn = conn;
  this._channel = channel;
  this._entries = entries;
  this._options = options;

  var that = this;
  this._channel.on('error', function(err) {
    debug("Channel error in Publisher: ", err.stack);
    that.emit('error', err);
  });
  this._conn.on('error', function(err) {
    debug("Connection error in Publisher: ", err.stack);
    that.emit('error', err);
  });

  entries.forEach(function(entry) {
    that[entry.name] = function() {
      // Copy arguments
      var args = Array.prototype.slice.call(arguments);

      // Return promise
      return new Promise(function(accept, reject) {
        // Construct message and routing key from arguments
        var message     = entry.messageBuilder.apply(undefined, args);
        var routingKey  = entry.routingKeyBuilder.apply(undefined, args);

        // Validate against schema
        var errors = that._options.validator.check(message, entry.schema);
        if (errors) {
          debug("Failed validate message: %j against schema: %s, errors: %j",
                message, entry.schema, errors);
          var error = new Error("Message validation failed");
          error.errors = errors;
          throw error;
        }

        // Convert routingKey to string if needed
        if (typeof(routingKey) !== 'string') {
          routingKey = entry.routingKey.map(function(key) {
            var word = routingKey[key.name];
            if (!key.required) {
              word = word || '_';
            }
            // Convert numbers to strings
            if (typeof(word) === 'number') {
              word = '' + word;
            }
            assert(typeof(word) === 'string', "non-string routingKey entry: "
                                              + key.name);
            assert(word.length <= key.maxSize,
                   "routingKey word: '" + word + "' for '" + key.name +
                   "' is longer than maxSize: " + key.maxSize);
            if (!key.multipleWords) {
              assert(word.indexOf('.') === -1, "routingKey for " + key.name +
                     " is not declared multipleWords and cannot contain '.' " +
                     "as is the case with '" + word + "'");
            }
            return word;
          }).join('.')
        }
        // Ensure the routing key is a string
        assert(typeof(routingKey) === 'string', "routingKey must be a string");

        // Serialize message
        var data = JSON.stringify(message);

        // Find exchange name
        var exchange = options.exchangePrefix + entry.exchange;

        // Publish message
        that._channel.publish(exchange, routingKey, new Buffer(data, 'utf8'), {
          persistent:         true,
          contentType:        'application/json',
          contentEncoding:    'utf-8'
        }, function(err, val) {
          if (err) {
            debug("Failed to publish message: %j and routingKey: %s, " +
                  "with error: %s, %j", message, routingKey, err, err);
            return reject(err);
          }
          accept(val);
        });
      });
    };
  });
};

// Inherit from events.EventEmitter
util.inherits(Publisher, events.EventEmitter);

/** Close the connection */
Publisher.prototype.close = function() {
  return this._conn.close();
};

/** Create a collection of exchange declarations
 *
 * options:
 * {
 *   title:              "Title of documentation page",
 *   description:        "Description in markdown",
 *   exchangePrefix:     'prefix/'     // For all exchanges declared here
 *   durableExchanges:   true || false // If exchanges are durable
 * }
 *
 * You may choose the provide all the options now or later. Normally it makes
 * sense to declare title and description immediately, but leave exchangePrefix
 * and connection string as configurable things defined at runtime.
 */
var Exchanges = function(options) {
  this._entries = [];
  this._options = {
    exchangePrefix:       '',
    durableExchanges:     true
  };
  this.configure(options);
};

/** Declare an new exchange
 *
 * options:
 * {
 *   exchange:     'exchange-name',      // exchange identifier on AMQP
 *   name:         "name_for_clients",   // name usable in client APIs
 *   title:        "Exchange title",
 *   description:  "Exchange description in markdown",
 *   routingKey: [ // Description of words, that make up the routing key
 *     {
 *       name:           'name_of_key',  // name of key for client APIs
 *       summary:        "Details in **markdown**",  // For documentation
 *       multipleWords:  true || false,  // true, if entry can contain dot
 *       required:       true || false,  // true, if a value is required
 *       maxSize:        22,             // Maximum size of word
 *     },
 *     // More entries...
 *   ],
 *   schema:       'http://schemas...'   // Message schema
 *   messageBuilder: function() {...}    // Return message from arguments given
 *   routingKeyBuilder: function() {...} // Return routing key from arguments
 * }
 *
 * Remark, it is only possible to have one routing key entry that has the
 * multipleWords entry set to true. This restriction is necessary to facilitate
 * automatic parsing of the routing key.
 *
 * When a publisher is constructor with `connect` the `name` from options will
 * be the identifier for the method used to publish messages. The arguments
 * passed to this method will be passed to both `messageBuilder` and
 * `routingKeyBuilder`.
 *
 * Note, `routingKeyBuilder` may return either a string, or an object mapping
 * from name of routingKey entries to string values. If returning an object
 * then `maxSize` will be checked for all entries, as will `required`, and if
 * `required` is `false` the entry will default to `_` if no value is provided.
 * (It's not recommended to return a string).
 */
Exchanges.prototype.declare = function(options) {
  assert(options, "options must be given to declare");

  // Check that we have properties that must be strings
  [
    'exchange', 'name', 'title', 'description', 'schema'
  ].forEach(function(key) {
    assert(typeof(options[key]) === 'string', "Option: '" + key + "' must be " +
           "a string");
  });

  // Validate routingKey declaration
  assert(options.routingKey instanceof Array,
         "routingKey must be an array");

  var keyNames = [];
  var sizeLeft = 255;
  var firstMultiWordKey = null;
  options.routingKey.forEach(function(key) {
    // Check that the key name is unique
    assert(keyNames.indexOf(key.name) === -1, "Can't have two routing key " +
           "entries named: '" + key.name + "'");
    keyNames.push(key.name);
    // Check that we have a summary
    assert(typeof(key.summary) === 'string', "summary of routingKey entry " +
           "must be provided.");

    // Ensure that have a boolean value for simplicity
    key.multipleWords = (key.multipleWords ? true : false);
    key.required      = (key.required ? true : false);

    // Check that we only have one multipleWords key in the routing key. If we
    // have more than one then we can't really parse the routing key
    // automatically. And technically, there is probably little need for two
    // multiple word routing key entries.
    // Note: if the need arises we should probably consider CC'ing multiple
    // routing keys, or something like that. At least that is a possible cleaner
    // design solution.
    if (key.multipleWords) {
      assert(firstMultiWordKey === null,
             "Can't have two multipleWord entries in a routing key, " +
             "here we have both '" + firstMultiWordKey + "' and " +
             "'" + key.name + "'");
      firstMultiWordKey = key.name;
    }

    // Check that we have a maxSize
    assert(typeof(key.maxSize) == 'number' && key.maxSize > 0,
           "routingKey declaration " + key.name + " must have maxSize > 0");

    // Check size left in routingKey space
    if (sizeLeft != 255) {
      sizeLeft -= 1; // Remove on for the joining dot
    }
    sizeLeft -= key.maxSize;
    assert(sizeLeft >= 0, "Combined routingKey cannot be larger than 255 " +
           "including joining dots");
  });

  // Validate messageBuilder
  assert(options.messageBuilder instanceof Function,
         "messageBuilder must be a Function");

  // Validate routingKeyBuilder
  assert(options.routingKeyBuilder instanceof Function,
         "routingKeyBuilder must be a function");

  // Check that `exchange` and `name` are unique
  this._entries.forEach(function(entry) {
    assert(entry.exchange !== options.exchange,
           "Cannot have two declarations with exchange: '" +
           entry.exchange + "'");
    assert(entry.name !== options.name,
           "Cannot have two declarations with name: '" + entry.name + "'");
  });

  // Add options to set of options
  this._entries.push(options);
};

/** Configure the events declaration */
Exchanges.prototype.configure = function(options) {
  this._options = _.defaults(options, this._options);
};

/**
 * Connect by AMQP and create a publisher
 *
 * Options:
 * {
 *   connectionString:   '...',  // AMQP connection string
 *   validator:                  // Instance of base.validator.Validator
 * }
 *
 * This method will connect to AMQP server and return a instance of Publisher.
 * The publisher will have a method for each declared exchange, the method
 * will carry the `name` given when the exchange was declared.
 *
 * In case of connection or internal errors the publisher will emit the `error`
 * event and all further attempts to use it will fail. In the future we may
 * implement a form of reconnection, but for now, just leave the `error` events
 * unhandled and let the process restart on its own.
 *
 * Return a promise for an instance of `Publisher`.
 */
Exchanges.prototype.connect = function(options) {
  options = _.defaults(options || {}, this._options);

  // Check we have a connection string
  assert(options.connectionString, "ConnectionString must be provided");
  assert(options.validator instanceof Validator,
         "An instance of base.validator.Validator must be given");

  // Clone entries for consistency
  var entries = _.cloneDeep(this._entries);

  // Create connection
  var conn = null;
  var channel = null;
  return amqplib.connect(options.connectionString).then(function(conn_) {
    conn = conn_;
    return conn.createConfirmChannel();
  }).then(function(channel_) {
    channel = channel_;

    return Promise.all(entries.map(function(entry) {
      var name = options.exchangePrefix + entry.exchange;
      return channel.assertExchange(name, 'topic', {
        durable:      options.durableExchanges,
        internal:     false,
        autoDelete:   false
      });
    }));
  }).then(function() {
    return new Publisher(conn, channel, entries, options);
  });
};

/** Return reference as JSON for the declared exchanges */
Exchanges.prototype.reference = function(options) {
  options = _.defaults(options || {}, this._options);

  // Check title and description
  assert(options.title,       "title must be provided");
  assert(options.description, "description must be provided");

  // Create reference
  var reference = {
    version:            '0.2.0',
    title:              options.title,
    description:        options.description,
    exchangePrefix:     options.exchangePrefix,
    entries: this._entries.map(function(entry) {
      return {
        type:           'topic-exchange',
        exchange:       entry.exchange,
        name:           entry.name,
        title:          entry.title,
        description:    entry.description,
        routingKey:     entry.routingKey,
        schema:         entry.schema
      };
    })
  };

  // Create validator to validate schema
  var validator = new Validator();
  // Load exchanges-reference.json schema from disk
  var schemaPath = path.join(__dirname, 'schemas', 'exchanges-reference.json');
  var schema = fs.readFileSync(schemaPath, {encoding: 'utf-8'});
  validator.register(JSON.parse(schema));

  // Check against it
  var refSchema = 'http://schemas.taskcluster.net/base/v1/' +
                  'exchanges-reference.json#';
  var errors = validator.check(reference, refSchema);
  if (errors) {
    debug("Exchanges.references(): Failed to ½ against schema, " +
          "errors: %j reference: %j", errors, reference);
    throw new Error("API.references(): Failed to validate against schema");
  }

  // Return reference
  return reference;
};

/**
 * Publish JSON reference for the declared exchanges
 *
 * options:
 * {
 *   exchangePrefix:  'queue/v1/'             // Prefix for all exchanges
 *   referencePrefix: 'queue/v1/events.json'  // Prefix within S3 bucket
 *   referenceBucket: 'reference.taskcluster.net',
 *   aws: {             // AWS credentials and region
 *    accessKeyId:      '...',
 *    secretAccessKey:  '...',
 *    region:           'us-west-2'
 *   }
 * }
 *
 * Return a promise that reference was published.
 */
Exchanges.prototype.publish = function(options) {
  // Provide default options
  options = _.defaults(options || {}, this._options, {
    referenceBucket:    'references.taskcluster.net'
  });
  // Check that required options are provided
  ['referencePrefix', 'aws'].forEach(function(key) {
    assert(options[key], "Option '" + key + "' must be provided");
  });
  // Create S3 object
  var s3 = new aws.S3(options.aws);
  // Upload object
  return s3.putObject({
    Bucket:           options.referenceBucket,
    Key:              options.referencePrefix,
    Body:             JSON.stringify(this.reference(options), undefined, 2),
    ContentType:      'application/json'
  }).promise();
};

/**
 * Setup exchanges, return promise for a publisher and publish reference if,
 * ordered to do so.
 *
 * options:
 * {
 *   publish:        false // Publish reference during setup
 * }
 *
 * Takes the same options as `publish` and `connect`.
 */
Exchanges.prototype.setup = function(options) {
  var promises = [];
  promises.push(this.connect(options));
  if (options.publish === true) {
    promises.push(this.publish(options));
  }
  return Promise.all(promises).then(function(vals) {
    return vals[0]; // Return publisher
  });
};

// Export the Exchanges class
module.exports = Exchanges;

// Export reference to Publisher
Exchanges.Publisher = Publisher;

<commit_msg>Allow 0 as value non-required routing key entry<commit_after>var assert        = require('assert');
var debug         = require('debug')('base:exchanges');
var _             = require('lodash');
var Promise       = require('promise');
var path          = require('path');
var fs            = require('fs');
var Validator     = require('./validator').Validator;
var aws           = require('aws-sdk-promise');
var amqplib       = require('amqplib');
var events        = require('events');
var util          = require('util');

/** Class for publishing to a set of declared exchanges */
var Publisher = function(conn, channel, entries, options) {
  events.EventEmitter.call(this);
  assert(options.validator instanceof Validator,
         "options.validator must be an instance of Validator");
  this._conn = conn;
  this._channel = channel;
  this._entries = entries;
  this._options = options;

  var that = this;
  this._channel.on('error', function(err) {
    debug("Channel error in Publisher: ", err.stack);
    that.emit('error', err);
  });
  this._conn.on('error', function(err) {
    debug("Connection error in Publisher: ", err.stack);
    that.emit('error', err);
  });

  entries.forEach(function(entry) {
    that[entry.name] = function() {
      // Copy arguments
      var args = Array.prototype.slice.call(arguments);

      // Return promise
      return new Promise(function(accept, reject) {
        // Construct message and routing key from arguments
        var message     = entry.messageBuilder.apply(undefined, args);
        var routingKey  = entry.routingKeyBuilder.apply(undefined, args);

        // Validate against schema
        var errors = that._options.validator.check(message, entry.schema);
        if (errors) {
          debug("Failed validate message: %j against schema: %s, errors: %j",
                message, entry.schema, errors);
          var error = new Error("Message validation failed");
          error.errors = errors;
          throw error;
        }

        // Convert routingKey to string if needed
        if (typeof(routingKey) !== 'string') {
          routingKey = entry.routingKey.map(function(key) {
            var word = routingKey[key.name];
            if (!key.required && (word === undefined || word === null)) {
              word = '_';
            }
            // Convert numbers to strings
            if (typeof(word) === 'number') {
              word = '' + word;
            }
            assert(typeof(word) === 'string', "non-string routingKey entry: "
                                              + key.name);
            assert(word.length <= key.maxSize,
                   "routingKey word: '" + word + "' for '" + key.name +
                   "' is longer than maxSize: " + key.maxSize);
            if (!key.multipleWords) {
              assert(word.indexOf('.') === -1, "routingKey for " + key.name +
                     " is not declared multipleWords and cannot contain '.' " +
                     "as is the case with '" + word + "'");
            }
            return word;
          }).join('.')
        }
        // Ensure the routing key is a string
        assert(typeof(routingKey) === 'string', "routingKey must be a string");

        // Serialize message
        var data = JSON.stringify(message);

        // Find exchange name
        var exchange = options.exchangePrefix + entry.exchange;

        // Publish message
        that._channel.publish(exchange, routingKey, new Buffer(data, 'utf8'), {
          persistent:         true,
          contentType:        'application/json',
          contentEncoding:    'utf-8'
        }, function(err, val) {
          if (err) {
            debug("Failed to publish message: %j and routingKey: %s, " +
                  "with error: %s, %j", message, routingKey, err, err);
            return reject(err);
          }
          accept(val);
        });
      });
    };
  });
};

// Inherit from events.EventEmitter
util.inherits(Publisher, events.EventEmitter);

/** Close the connection */
Publisher.prototype.close = function() {
  return this._conn.close();
};

/** Create a collection of exchange declarations
 *
 * options:
 * {
 *   title:              "Title of documentation page",
 *   description:        "Description in markdown",
 *   exchangePrefix:     'prefix/'     // For all exchanges declared here
 *   durableExchanges:   true || false // If exchanges are durable
 * }
 *
 * You may choose the provide all the options now or later. Normally it makes
 * sense to declare title and description immediately, but leave exchangePrefix
 * and connection string as configurable things defined at runtime.
 */
var Exchanges = function(options) {
  this._entries = [];
  this._options = {
    exchangePrefix:       '',
    durableExchanges:     true
  };
  this.configure(options);
};

/** Declare an new exchange
 *
 * options:
 * {
 *   exchange:     'exchange-name',      // exchange identifier on AMQP
 *   name:         "name_for_clients",   // name usable in client APIs
 *   title:        "Exchange title",
 *   description:  "Exchange description in markdown",
 *   routingKey: [ // Description of words, that make up the routing key
 *     {
 *       name:           'name_of_key',  // name of key for client APIs
 *       summary:        "Details in **markdown**",  // For documentation
 *       multipleWords:  true || false,  // true, if entry can contain dot
 *       required:       true || false,  // true, if a value is required
 *       maxSize:        22,             // Maximum size of word
 *     },
 *     // More entries...
 *   ],
 *   schema:       'http://schemas...'   // Message schema
 *   messageBuilder: function() {...}    // Return message from arguments given
 *   routingKeyBuilder: function() {...} // Return routing key from arguments
 * }
 *
 * Remark, it is only possible to have one routing key entry that has the
 * multipleWords entry set to true. This restriction is necessary to facilitate
 * automatic parsing of the routing key.
 *
 * When a publisher is constructor with `connect` the `name` from options will
 * be the identifier for the method used to publish messages. The arguments
 * passed to this method will be passed to both `messageBuilder` and
 * `routingKeyBuilder`.
 *
 * Note, `routingKeyBuilder` may return either a string, or an object mapping
 * from name of routingKey entries to string values. If returning an object
 * then `maxSize` will be checked for all entries, as will `required`, and if
 * `required` is `false` the entry will default to `_` if no value is provided.
 * (It's not recommended to return a string).
 */
Exchanges.prototype.declare = function(options) {
  assert(options, "options must be given to declare");

  // Check that we have properties that must be strings
  [
    'exchange', 'name', 'title', 'description', 'schema'
  ].forEach(function(key) {
    assert(typeof(options[key]) === 'string', "Option: '" + key + "' must be " +
           "a string");
  });

  // Validate routingKey declaration
  assert(options.routingKey instanceof Array,
         "routingKey must be an array");

  var keyNames = [];
  var sizeLeft = 255;
  var firstMultiWordKey = null;
  options.routingKey.forEach(function(key) {
    // Check that the key name is unique
    assert(keyNames.indexOf(key.name) === -1, "Can't have two routing key " +
           "entries named: '" + key.name + "'");
    keyNames.push(key.name);
    // Check that we have a summary
    assert(typeof(key.summary) === 'string', "summary of routingKey entry " +
           "must be provided.");

    // Ensure that have a boolean value for simplicity
    key.multipleWords = (key.multipleWords ? true : false);
    key.required      = (key.required ? true : false);

    // Check that we only have one multipleWords key in the routing key. If we
    // have more than one then we can't really parse the routing key
    // automatically. And technically, there is probably little need for two
    // multiple word routing key entries.
    // Note: if the need arises we should probably consider CC'ing multiple
    // routing keys, or something like that. At least that is a possible cleaner
    // design solution.
    if (key.multipleWords) {
      assert(firstMultiWordKey === null,
             "Can't have two multipleWord entries in a routing key, " +
             "here we have both '" + firstMultiWordKey + "' and " +
             "'" + key.name + "'");
      firstMultiWordKey = key.name;
    }

    // Check that we have a maxSize
    assert(typeof(key.maxSize) == 'number' && key.maxSize > 0,
           "routingKey declaration " + key.name + " must have maxSize > 0");

    // Check size left in routingKey space
    if (sizeLeft != 255) {
      sizeLeft -= 1; // Remove on for the joining dot
    }
    sizeLeft -= key.maxSize;
    assert(sizeLeft >= 0, "Combined routingKey cannot be larger than 255 " +
           "including joining dots");
  });

  // Validate messageBuilder
  assert(options.messageBuilder instanceof Function,
         "messageBuilder must be a Function");

  // Validate routingKeyBuilder
  assert(options.routingKeyBuilder instanceof Function,
         "routingKeyBuilder must be a function");

  // Check that `exchange` and `name` are unique
  this._entries.forEach(function(entry) {
    assert(entry.exchange !== options.exchange,
           "Cannot have two declarations with exchange: '" +
           entry.exchange + "'");
    assert(entry.name !== options.name,
           "Cannot have two declarations with name: '" + entry.name + "'");
  });

  // Add options to set of options
  this._entries.push(options);
};

/** Configure the events declaration */
Exchanges.prototype.configure = function(options) {
  this._options = _.defaults(options, this._options);
};

/**
 * Connect by AMQP and create a publisher
 *
 * Options:
 * {
 *   connectionString:   '...',  // AMQP connection string
 *   validator:                  // Instance of base.validator.Validator
 * }
 *
 * This method will connect to AMQP server and return a instance of Publisher.
 * The publisher will have a method for each declared exchange, the method
 * will carry the `name` given when the exchange was declared.
 *
 * In case of connection or internal errors the publisher will emit the `error`
 * event and all further attempts to use it will fail. In the future we may
 * implement a form of reconnection, but for now, just leave the `error` events
 * unhandled and let the process restart on its own.
 *
 * Return a promise for an instance of `Publisher`.
 */
Exchanges.prototype.connect = function(options) {
  options = _.defaults(options || {}, this._options);

  // Check we have a connection string
  assert(options.connectionString, "ConnectionString must be provided");
  assert(options.validator instanceof Validator,
         "An instance of base.validator.Validator must be given");

  // Clone entries for consistency
  var entries = _.cloneDeep(this._entries);

  // Create connection
  var conn = null;
  var channel = null;
  return amqplib.connect(options.connectionString).then(function(conn_) {
    conn = conn_;
    return conn.createConfirmChannel();
  }).then(function(channel_) {
    channel = channel_;

    return Promise.all(entries.map(function(entry) {
      var name = options.exchangePrefix + entry.exchange;
      return channel.assertExchange(name, 'topic', {
        durable:      options.durableExchanges,
        internal:     false,
        autoDelete:   false
      });
    }));
  }).then(function() {
    return new Publisher(conn, channel, entries, options);
  });
};

/** Return reference as JSON for the declared exchanges */
Exchanges.prototype.reference = function(options) {
  options = _.defaults(options || {}, this._options);

  // Check title and description
  assert(options.title,       "title must be provided");
  assert(options.description, "description must be provided");

  // Create reference
  var reference = {
    version:            '0.2.0',
    title:              options.title,
    description:        options.description,
    exchangePrefix:     options.exchangePrefix,
    entries: this._entries.map(function(entry) {
      return {
        type:           'topic-exchange',
        exchange:       entry.exchange,
        name:           entry.name,
        title:          entry.title,
        description:    entry.description,
        routingKey:     entry.routingKey,
        schema:         entry.schema
      };
    })
  };

  // Create validator to validate schema
  var validator = new Validator();
  // Load exchanges-reference.json schema from disk
  var schemaPath = path.join(__dirname, 'schemas', 'exchanges-reference.json');
  var schema = fs.readFileSync(schemaPath, {encoding: 'utf-8'});
  validator.register(JSON.parse(schema));

  // Check against it
  var refSchema = 'http://schemas.taskcluster.net/base/v1/' +
                  'exchanges-reference.json#';
  var errors = validator.check(reference, refSchema);
  if (errors) {
    debug("Exchanges.references(): Failed to ½ against schema, " +
          "errors: %j reference: %j", errors, reference);
    throw new Error("API.references(): Failed to validate against schema");
  }

  // Return reference
  return reference;
};

/**
 * Publish JSON reference for the declared exchanges
 *
 * options:
 * {
 *   exchangePrefix:  'queue/v1/'             // Prefix for all exchanges
 *   referencePrefix: 'queue/v1/events.json'  // Prefix within S3 bucket
 *   referenceBucket: 'reference.taskcluster.net',
 *   aws: {             // AWS credentials and region
 *    accessKeyId:      '...',
 *    secretAccessKey:  '...',
 *    region:           'us-west-2'
 *   }
 * }
 *
 * Return a promise that reference was published.
 */
Exchanges.prototype.publish = function(options) {
  // Provide default options
  options = _.defaults(options || {}, this._options, {
    referenceBucket:    'references.taskcluster.net'
  });
  // Check that required options are provided
  ['referencePrefix', 'aws'].forEach(function(key) {
    assert(options[key], "Option '" + key + "' must be provided");
  });
  // Create S3 object
  var s3 = new aws.S3(options.aws);
  // Upload object
  return s3.putObject({
    Bucket:           options.referenceBucket,
    Key:              options.referencePrefix,
    Body:             JSON.stringify(this.reference(options), undefined, 2),
    ContentType:      'application/json'
  }).promise();
};

/**
 * Setup exchanges, return promise for a publisher and publish reference if,
 * ordered to do so.
 *
 * options:
 * {
 *   publish:        false // Publish reference during setup
 * }
 *
 * Takes the same options as `publish` and `connect`.
 */
Exchanges.prototype.setup = function(options) {
  var promises = [];
  promises.push(this.connect(options));
  if (options.publish === true) {
    promises.push(this.publish(options));
  }
  return Promise.all(promises).then(function(vals) {
    return vals[0]; // Return publisher
  });
};

// Export the Exchanges class
module.exports = Exchanges;

// Export reference to Publisher
Exchanges.Publisher = Publisher;

<|endoftext|>


<commit_before>/*!
 * jQuery Fillmore
 * Version 1.0.0
 * https://github.com/Gjslick/jquery-fillmore
 *
 * Add a dynamically-resized background image to any element
 *
 * Copyright (c) 2011 Gregory Jacobs (jux.com)
 * Dual licensed under the MIT and GPL licenses.
 */
(function(d){var c={src:null,centeredX:true,centeredY:true,speed:0};var b=function(g){var f="absolute";this.settings=d.extend({},c);var e=this.$containerEl=this.$containerSizingEl=d(g);e.css("background","transparent");if(e.is("body")){this.$containerSizingEl=("onorientationchange" in window)?d(document):d(window);f="fixed"}else{this.originalContainerOverflow=e[0].style.overflow;e.css("overflow","hidden");if(e.css("position")==="static"){e.css("position","relative");this.containerPositionModified=true}if(e.css("z-index")==="auto"){e.css("z-index",0);this.containerZIndexModified=true}}this.$fillmoreEl=d('
').appendTo(e);this.resizeProxy=d.proxy(this.resize,this);d(window).resize(this.resizeProxy)};d.extend(b.prototype,{$imgEl:null,imgRatio:null,containerPositionModified:false,containerZIndexModified:false,imgLoaded:false,loaded:false,updateSettings:function(e){this.settings=d.extend(this.settings,e)},showImage:function(e,f){this.$fillmoreEl.find("img").addClass("deletable");this.imgLoaded=false;this.loaded=false;this.$imgEl=d('').bind("load error",d.proxy(function(g){this.onImageLoaded(g,f)},this)).appendTo(this.$fillmoreEl);this.$imgEl.attr("src",e)},onImageLoaded:function(f,j){var h=f.target,i=this.$fillmoreEl;if(h!==this.$imgEl[0]){return}this.imgLoaded=true;this.$imgEl.css({width:"auto",height:"auto"});var g=h.width||this.$imgEl.width(),e=h.height||this.$imgEl.height();this.imgRatio=g/e;this.resize();this.$imgEl.fadeIn(this.settings.speed,jQuery.proxy(function(){i.find("img.deletable").remove();this.loaded=true;if(typeof j==="function"){j()}},this))},resize:function(){if(this.$imgEl&&this.imgLoaded){try{var e=this.settings,g=this.$containerEl,k=this.$containerSizingEl,n=k.outerHeight()||k.height(),l=k.outerWidth()||k.width(),j={left:0,top:0},i=l,h=i/this.imgRatio,m;if(h>=n){m=(h-n)/2;if(e.centeredY){d.extend(j,{top:"-"+m+"px"})}}else{h=n;i=h*this.imgRatio;m=(i-l)/2;if(e.centeredX){d.extend(j,{left:"-"+m+"px"})}}this.$fillmoreEl.width(i).height(h);this.$imgEl.width(i).height(h).css(j)}catch(f){}}},isLoaded:function(){return this.loaded},getSrc:function(){return this.settings.src},destroy:function(){if(this.containerPositionModified){this.$containerEl.css("position","")}if(this.containerZIndexModified){this.$containerEl.css("z-index","")}this.$containerEl.css("overflow",this.originalContainerOverflow);this.$fillmoreEl.remove();d(window).unbind("resize",this.resizeProxy)}});var a={init:function(k){for(var h=0,e=this.length;h<e;h++){var j=this[h],g=d(j),f=g.data("fillmore");if(!f){g.data("fillmore",f=new b(j))}f.updateSettings(k);f.showImage(k.src,k.callback)}return this},isLoaded:function(){var f=this[0],e;if(f&&(e=d(f).data("fillmore"))){return e.isLoaded()}else{return false}},getSrc:function(){var f=this[0],e;if(f&&(e=d(f).data("fillmore"))){return e.getSrc()}else{return undefined}},resize:function(){for(var h=0,e=this.length;h<e;h++){var g=d(this[h]),f=g.data("fillmore");if(f){f.resize()}}return this},destroy:function(){for(var h=0,e=this.length;h<e;h++){var g=d(this[h]),f=g.data("fillmore");if(f){f.destroy();g.removeData("fillmore")}}return this}};d.fn.fillmore=function(e){if(typeof e==="string"&&a[e]){return a[e].apply(this,Array.prototype.slice.call(arguments,1))}else{if(typeof e==="object"||!e){return a.init.apply(this,arguments)}else{d.error("Method '"+e+"' does not exist on Fillmore.")}}};d.fillmore=function(e){return d("body").fillmore(e)}})(jQuery);<commit_msg>removed minified version from git<commit_after><|endoftext|>


<commit_before>//    Licensed to the Apache Software Foundation (ASF) under one
//    or more contributor license agreements.  See the NOTICE file
//    distributed with this work for additional information
//    regarding copyright ownership.  The ASF licenses this file
//    to you under the Apache License, Version 2.0 (the
//    "License"); you may not use this file except in compliance
//    with the License.  You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing,
//    software distributed under the License is distributed on an
//    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//    KIND, either express or implied.  See the License for the
//    specific language governing permissions and limitations
//    under the License.

(function ($) {
    $.sendFeedback = function (options) {
        var settings = $.extend({
            instructionText: 'Use Your Mouse to Highlight Areas on the Page',
            instructionTextColor: '#f00',
            instructionBackgroundColor: '#fff',
            instructionTextSize: '40px',
            overlayColor: '#000',
            overlayOpacity: '.25',
            overlayZIndex: '9000000',
            highlightBorderColor: '#f00',
            highlightBorderWidth: '3px',
            highlightBorderStyle: 'solid',
            highlightBorderRadius: '5px',
            formTitle: 'Send Feedback',
            formText: 'Your feedback is very useful to us when resolving problems. Please be very detailed in your description.',
            formTextColor: '#000',
            formBackgroundColor: '#fff',
            formBorderColor: '#000',
            formBorderWidth: '1px',
            formBorderStyle: 'solid',
            subjectText: 'Subject',
            detailsText: 'Details'
        }, options);

        var $feedbackContainer = $('

').appendTo('body');

        var $feedbackHighlights = $('

').appendTo('body');

        var $feedbackOverlay = $('

')
        .css({
            position: 'absolute',
            width: '100%',
            height: '100%',
            backgroundColor: settings.overlayColor,
            opacity: settings.overlayOpacity,
            left: '0',
            top: '0',
            zIndex: settings.overlayZIndex,
            textAlign: 'center'
        })
        .appendTo($feedbackHighlights);

        var $feedbackInstructions = $('

').css({
            position: 'absolute',
            width: '100%',
            left: '0',
            top: '35%',
            zIndex: settings.overlayZIndex - 1,
            textAlign: 'center',
            fontSize: settings.instructionTextSize,
            fontWeight: 'bold',
            color: settings.instructionTextColor,
            fontFamily: 'Arial',
            backgroundColor: settings.instructionBackgroundColor,
            padding: '10px'
        })
        .text(settings.instructionText)
        .appendTo($feedbackContainer)
        .hide();

        var $feedbackForm = $('

').css({
            position: 'fixed',
            bottom: '0',
            right: '0',
            width: '275px',
            height: '335px',
            padding: '10px',
            color: settings.formTextColor,
            backgroundColor: settings.formBackgroundColor,
            borderStyle: settings.formBorderStyle,
            borderWidth: settings.formBorderWidth,
            borderColor: settings.formBorderColor,
            borderRadius: '10px 0 0 0',
            zIndex: settings.overlayZIndex + 1
        });
        $('

').css({
            fontSize: '20px',
            textAlign: 'center',
            marginBottom: '10px'
        })
        .text(settings.formTitle)
        .appendTo($feedbackForm);
        $('

')
        .css({
            fontSize: '11px',
            marginBottom: '10px'
        })
        .text(settings.formText)
        .appendTo($feedbackForm);
        $('

')
        .css({
            fontSize: '11px'
        })
        .text(settings.subjectText)
        .appendTo($feedbackForm);
        var $feedbackSubject = $('<input type="text" />').css({
            width: '270px',
            marginBottom: '10px',
            display: 'block'
        })
        .appendTo($feedbackForm);
        $('

')
        .css({
            fontSize: '11px'
        })
        .text(settings.detailsText)
        .appendTo($feedbackForm);
        var $feedbackDetails = $('<textarea></textarea>').css({
            width: '270px',
            height: '150px',
            marginBottom: '10px',
            display: 'block'
        })
        .appendTo($feedbackForm);
        $('<input type="button" value="Send" />').click(function (e) {
            $feedbackContainer.remove();
            $('*').each(function () {
                if ($(this).attr('style'))
                    $(this).data('oldStyle', $(this).attr('style'));
                else
                    $(this).data('oldStyle', 'none');
                $(this).width($(this).width());
                $(this).height($(this).height());
            });
            var html = '' + $('html').html() + '';
            $('*').each(function () {
                if ($(this).data('oldStyle') != 'none')
                    $(this).attr('style', $(this).data('oldStyle'));
                else
                    $(this).removeAttr('style');
            });
            $feedbackHighlights.remove();
            var feedbackInformation = {
                url: document.URL,
                userAgent: navigator.userAgent,
                subject: $feedbackSubject.val(),
                details: $feedbackDetails.val(),
                html: html
            };
            if (settings.feedbackSent)
                settings.feedbackSent(feedbackInformation);
        })
        .appendTo($feedbackForm);
        $('<input type="button" value="Cancel" />').click(function (e) {
            $feedbackContainer.remove();
            $feedbackHighlights.remove();
        })
        .appendTo($feedbackForm);
        $feedbackForm.appendTo($feedbackContainer);

        var originalCoords = { top: 0, left: 0 };

        var $currentHighlight;

        $feedbackOverlay.bind('mousedown', function (e) {
            $currentHighlight = $('

').css({
                width: '1px',
                height: '1px',
                borderStyle: settings.highlightBorderStyle,
                borderWidth: settings.highlightBorderWidth,
                borderColor: settings.highlightBorderColor,
                borderRadius: settings.highlightBorderRadius,
                position: 'absolute',
                left: e.pageX,
                top: e.pageY,
                zIndex: settings.overlayZIndex - 1
            })
            .appendTo($feedbackHighlights);
            originalCoords = { top: e.pageY, left: e.pageX };
        });

        $feedbackOverlay.bind('mousemove', function (e) {
            if ($currentHighlight) {
                var newCoords = { top: e.pageY, left: e.pageX };

                if (newCoords.top < originalCoords.top) $currentHighlight.css('top', newCoords.top);
                if (newCoords.left < originalCoords.left) $currentHighlight.css('left', newCoords.left);

                $currentHighlight.height(Math.abs(newCoords.top - originalCoords.top));
                $currentHighlight.width(Math.abs(newCoords.left - originalCoords.left));
            }
        });

        $feedbackOverlay.bind('mouseup', function (e) {
            $currentHighlight = null;
        });

        $feedbackInstructions.fadeIn(500, function () {
            setTimeout(function () {
                $feedbackInstructions.fadeOut(1000);
            }, 2000);
        });

        $feedbackOverlay.height($(document).height());
    }
})(jQuery);<commit_msg>- Modified feedback form to fade out when dragging screen highlights so that it is out of the way for the duration of the highlight.<commit_after>//    Licensed to the Apache Software Foundation (ASF) under one
//    or more contributor license agreements.  See the NOTICE file
//    distributed with this work for additional information
//    regarding copyright ownership.  The ASF licenses this file
//    to you under the Apache License, Version 2.0 (the
//    "License"); you may not use this file except in compliance
//    with the License.  You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing,
//    software distributed under the License is distributed on an
//    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//    KIND, either express or implied.  See the License for the
//    specific language governing permissions and limitations
//    under the License.

(function ($) {
    $.sendFeedback = function (options) {
        var settings = $.extend({
            instructionText: 'Use Your Mouse to Highlight Areas on the Page',
            instructionTextColor: '#f00',
            instructionBackgroundColor: '#fff',
            instructionTextSize: '40px',
            overlayColor: '#000',
            overlayOpacity: '.25',
            overlayZIndex: '9000000',
            highlightBorderColor: '#f00',
            highlightBorderWidth: '3px',
            highlightBorderStyle: 'solid',
            highlightBorderRadius: '5px',
            formTitle: 'Send Feedback',
            formText: 'Your feedback is very useful to us when resolving problems. Please be very detailed in your description.',
            formTextColor: '#000',
            formBackgroundColor: '#fff',
            formBorderColor: '#000',
            formBorderWidth: '1px',
            formBorderStyle: 'solid',
            subjectText: 'Subject',
            detailsText: 'Details'
        }, options);

        var $feedbackContainer = $('

').appendTo('body');

        var $feedbackHighlights = $('

').appendTo('body');

        var $feedbackOverlay = $('

')
        .css({
            position: 'absolute',
            width: '100%',
            height: '100%',
            backgroundColor: settings.overlayColor,
            opacity: settings.overlayOpacity,
            left: '0',
            top: '0',
            zIndex: settings.overlayZIndex,
            textAlign: 'center'
        })
        .appendTo($feedbackHighlights);

        var $feedbackInstructions = $('

').css({
            position: 'absolute',
            width: '100%',
            left: '0',
            top: '35%',
            zIndex: settings.overlayZIndex - 1,
            textAlign: 'center',
            fontSize: settings.instructionTextSize,
            fontWeight: 'bold',
            color: settings.instructionTextColor,
            fontFamily: 'Arial',
            backgroundColor: settings.instructionBackgroundColor,
            padding: '10px'
        })
        .text(settings.instructionText)
        .appendTo($feedbackContainer)
        .hide();

        var $feedbackForm = $('

').css({
            position: 'fixed',
            bottom: '0',
            right: '0',
            width: '275px',
            height: '335px',
            padding: '10px',
            color: settings.formTextColor,
            backgroundColor: settings.formBackgroundColor,
            borderStyle: settings.formBorderStyle,
            borderWidth: settings.formBorderWidth,
            borderColor: settings.formBorderColor,
            borderRadius: '10px 0 0 0',
            zIndex: settings.overlayZIndex + 1
        });
        $('

').css({
            fontSize: '20px',
            textAlign: 'center',
            marginBottom: '10px'
        })
        .text(settings.formTitle)
        .appendTo($feedbackForm);
        $('

')
        .css({
            fontSize: '11px',
            marginBottom: '10px'
        })
        .text(settings.formText)
        .appendTo($feedbackForm);
        $('

')
        .css({
            fontSize: '11px'
        })
        .text(settings.subjectText)
        .appendTo($feedbackForm);
        var $feedbackSubject = $('<input type="text" />').css({
            width: '270px',
            marginBottom: '10px',
            display: 'block'
        })
        .appendTo($feedbackForm);
        $('

')
        .css({
            fontSize: '11px'
        })
        .text(settings.detailsText)
        .appendTo($feedbackForm);
        var $feedbackDetails = $('<textarea></textarea>').css({
            width: '270px',
            height: '150px',
            marginBottom: '10px',
            display: 'block'
        })
        .appendTo($feedbackForm);
        $('<input type="button" value="Send" />').click(function (e) {
            $feedbackContainer.remove();
            $('*').each(function () {
                if ($(this).attr('style'))
                    $(this).data('oldStyle', $(this).attr('style'));
                else
                    $(this).data('oldStyle', 'none');
                $(this).width($(this).width());
                $(this).height($(this).height());
            });
            var html = '' + $('html').html() + '';
            $('*').each(function () {
                if ($(this).data('oldStyle') != 'none')
                    $(this).attr('style', $(this).data('oldStyle'));
                else
                    $(this).removeAttr('style');
            });
            $feedbackHighlights.remove();
            var feedbackInformation = {
                url: document.URL,
                userAgent: navigator.userAgent,
                subject: $feedbackSubject.val(),
                details: $feedbackDetails.val(),
                html: html
            };
            if (settings.feedbackSent)
                settings.feedbackSent(feedbackInformation);
        })
        .appendTo($feedbackForm);
        $('<input type="button" value="Cancel" />').click(function (e) {
            $feedbackContainer.remove();
            $feedbackHighlights.remove();
        })
        .appendTo($feedbackForm);
        $feedbackForm.appendTo($feedbackContainer);

        var originalCoords = { top: 0, left: 0 };

        var $currentHighlight;

        $feedbackOverlay.bind('mousedown', function (e) {
            $feedbackForm.fadeOut();
            $currentHighlight = $('

').css({
                width: '1px',
                height: '1px',
                borderStyle: settings.highlightBorderStyle,
                borderWidth: settings.highlightBorderWidth,
                borderColor: settings.highlightBorderColor,
                borderRadius: settings.highlightBorderRadius,
                position: 'absolute',
                left: e.pageX,
                top: e.pageY,
                zIndex: settings.overlayZIndex - 1
            })
            .appendTo($feedbackHighlights);
            originalCoords = { top: e.pageY, left: e.pageX };
        });

        $feedbackOverlay.bind('mousemove', function (e) {
            if ($currentHighlight) {
                var newCoords = { top: e.pageY, left: e.pageX };

                if (newCoords.top < originalCoords.top) $currentHighlight.css('top', newCoords.top);
                if (newCoords.left < originalCoords.left) $currentHighlight.css('left', newCoords.left);

                $currentHighlight.height(Math.abs(newCoords.top - originalCoords.top));
                $currentHighlight.width(Math.abs(newCoords.left - originalCoords.left));
            }
        });

        $feedbackOverlay.bind('mouseup', function (e) {
            $feedbackForm.fadeIn();
            $currentHighlight = null;
        });

        $feedbackInstructions.fadeIn(500, function () {
            setTimeout(function () {
                $feedbackInstructions.fadeOut(1000);
            }, 2000);
        });

        $feedbackOverlay.height($(document).height());
    }
})(jQuery);<|endoftext|>


<commit_before>const downloadFiles = require('./downloadFiles');
const createFoldersInPath = require('./createFoldersInPath');
const sanitizeFolder = require('filenamify');
const copyExistingFiles = require('./copyExistingFiles');
const copyCordovaFiles = require('./copyCordovaFiles');

/**
 * Fetches the update files. Tries to copy from current version first if hashes match, reverts to downloading if that fails.
 * @param  {Object} ccs                     The ccs info stored in localstorage
 * @param  {Array[String]} filesToCopy 		Files to be copied
 * @param  {Array[String]} filesToDownload 	Files to be downloaded
 * @param  {Object} versionInfo             The info received from the server
 * @param  {Object} options                 See calling function
 * @return {Promise}
 */
function fetchFiles(ccs, { filesToCopy, filesToDownload }, versionInfo, options, instanceOptions) {
	const contentUrl = versionInfo.content_url;
	const dataDir = cordova.file.dataDirectory;
	const destinationFolderName = instanceOptions.debug.preserveBreakpoints ? 'ccsDebug' : sanitizeFolder(versionInfo.release || '');
	const versionFolderName = sanitizeFolder(ccs.release || '');

	return createFoldersInPath(destinationFolderName)
		.then(() => {
			// abort copying/downloading existing files if in debug mode and trying to copy from 'ccsDebug' to 'ccsDebug'
			if (versionFolderName == destinationFolderName) {
				return;
			}
			return copyExistingFiles(dataDir, versionFolderName, filesToCopy, destinationFolderName)
				.catch(() => downloadFiles(contentUrl, filesToCopy, destinationFolderName, options))
				.then(() => {
					if (instanceOptions.iframe) {
						return;
					}
					return copyCordovaFiles(dataDir, destinationFolderName);
				});
		})
		.then(() => downloadFiles(contentUrl, filesToDownload, destinationFolderName, options));
}

module.exports = fetchFiles;
<commit_msg>Now copies cordova-files when using iframe. No idea why it was turned off in iframe-mode.<commit_after>const downloadFiles = require('./downloadFiles');
const createFoldersInPath = require('./createFoldersInPath');
const sanitizeFolder = require('filenamify');
const copyExistingFiles = require('./copyExistingFiles');
const copyCordovaFiles = require('./copyCordovaFiles');

/**
 * Fetches the update files. Tries to copy from current version first if hashes match, reverts to downloading if that fails.
 * @param  {Object} ccs                     The ccs info stored in localstorage
 * @param  {Array[String]} filesToCopy 		Files to be copied
 * @param  {Array[String]} filesToDownload 	Files to be downloaded
 * @param  {Object} versionInfo             The info received from the server
 * @param  {Object} options                 See calling function
 * @return {Promise}
 */
function fetchFiles(ccs, { filesToCopy, filesToDownload }, versionInfo, options, instanceOptions) {
	const contentUrl = versionInfo.content_url;
	const dataDir = cordova.file.dataDirectory;
	const destinationFolderName = instanceOptions.debug.preserveBreakpoints ? 'ccsDebug' : sanitizeFolder(versionInfo.release || '');
	const versionFolderName = sanitizeFolder(ccs.release || '');

	return createFoldersInPath(destinationFolderName)
		.then(() => {
			// abort copying/downloading existing files if in debug mode and trying to copy from 'ccsDebug' to 'ccsDebug'
			if (versionFolderName == destinationFolderName) {
				return;
			}
			return copyExistingFiles(dataDir, versionFolderName, filesToCopy, destinationFolderName)
				.catch(() => downloadFiles(contentUrl, filesToCopy, destinationFolderName, options))
				.then(() => copyCordovaFiles(dataDir, destinationFolderName));
		})
		.then(() => downloadFiles(contentUrl, filesToDownload, destinationFolderName, options));
}

module.exports = fetchFiles;
<|endoftext|>


<commit_before>(function( $, undefined ) {

$.widget("ui.rotatable", $.ui.mouse, {

    options: {
        handle: false,
        angle: false,

        // callbacks
        start: null,
        rotate: null,
        stop: null
    },

    handle: function(handle) {
        if (handle === undefined) {
            return this.options.handle;
        }
        this.options.handle = handle;
    },

    angle: function(angle) {
        if (angle === undefined) {
            return this.options.angle;
        }
        this.options.angle = angle;
        this.performRotation(this.options.angle);
    },

    _create: function() {
        var handle;
        if (!this.options.handle) {
            handle = $(document.createElement('div'));
            handle.addClass('ui-rotatable-handle');
        }
        else {
            handle = this.options.handle;
        }

        this.listeners = {
            rotateElement: $.proxy(this.rotateElement, this),
            startRotate: $.proxy(this.startRotate, this),
            stopRotate: $.proxy(this.stopRotate, this)
        };

        handle.draggable({ helper: 'clone', start: this.dragStart, handle: handle });
        handle.bind('mousedown', this.listeners.startRotate);
        handle.appendTo(this.element);

        if(this.options.angle != false) {
            this.elementCurrentAngle = this.options.angle;
            this.performRotation(this.elementCurrentAngle);
        }
        else {
            this.elementCurrentAngle = 0;
        }
    },

    _destroy: function() {
        this.element.removeClass('ui-rotatable');
        this.element.find('.ui-rotatable-handle').remove();
    },

    performRotation: function(angle) {
        this.element.css('transform','rotate(' + angle + 'rad)');
        this.element.css('-moz-transform','rotate(' + angle + 'rad)');
        this.element.css('-webkit-transform','rotate(' + angle + 'rad)');
        this.element.css('-o-transform','rotate(' + angle + 'rad)');
    },

    getElementOffset: function() {
        this.performRotation(0);
        var offset = this.element.offset();
        this.performRotation(this.elementCurrentAngle);
        return offset;
    },

    getElementCenter: function() {
        var elementOffset = this.getElementOffset();
        var elementCentreX = elementOffset.left + this.element.width() / 2;
        var elementCentreY = elementOffset.top + this.element.height() / 2;
        return Array(elementCentreX, elementCentreY);
    },

    dragStart: function(event) {
        if (this.element) {
            return false;
        }
    },

    startRotate: function(event) {
        var center = this.getElementCenter();
        var startXFromCenter = event.pageX - center[0];
        var startYFromCenter = event.pageY - center[1];
        this.mouseStartAngle = Math.atan2(startYFromCenter, startXFromCenter);
        this.elementStartAngle = this.elementCurrentAngle;
        this.hasRotated = false;

        this._propagate("start", event);

        $(document).bind('mousemove', this.listeners.rotateElement);
        $(document).bind('mouseup', this.listeners.stopRotate);

        return false;
    },

    rotateElement: function(event) {
        if (!this.element || this.element.disabled) {
            return false;
        }

        var center = this.getElementCenter();

        var xFromCenter = event.pageX - center[0];
        var yFromCenter = event.pageY - center[1];
        var mouseAngle = Math.atan2(yFromCenter, xFromCenter);
        var rotateAngle = mouseAngle - this.mouseStartAngle + this.elementStartAngle;

        this.performRotation(rotateAngle);
        var previousRotateAngle = this.elementCurrentAngle;
        this.elementCurrentAngle = rotateAngle;

        // Plugins callbacks need to be called first.
        this._propagate("rotate", event);

        if (previousRotateAngle != rotateAngle) {
            this._trigger("rotate", event, this.ui());
            this.hasRotated = true;
        }

        return false;
    },

    stopRotate: function(event) {
        if (!this.element || this.element.disabled) {
            return;
        }

        $(document).unbind('mousemove', this.listeners.rotateElement);
        $(document).unbind('mouseup', this.listeners.stopRotate);

        this.elementStopAngle = this.elementCurrentAngle;
        if (this.hasRotated) {
            this._propagate("stop", event);
        }

        setTimeout( function() { this.element = false; }, 10 );
        return false;
    },

    _propagate: function(n, event) {
        $.ui.plugin.call(this, n, [event, this.ui()]);
        (n !== "rotate" && this._trigger(n, event, this.ui()));
    },

    plugins: {},

    ui: function() {
        return {
            element: this.element,
            angle: {
                start: this.elementStartAngle,
                current: this.elementCurrentAngle,
                stop: this.elementStopAngle
            }
        };
    }

});

})(jQuery);
<commit_msg>Snap to predefined angle<commit_after>(function( $, undefined ) {

$.widget("ui.rotatable", $.ui.mouse, {

    options: {
        handle: false,
        angle: false,

        // callbacks
        start: null,
        rotate: null,
        stop: null
    },

    handle: function(handle) {
        if (handle === undefined) {
            return this.options.handle;
        }
        this.options.handle = handle;
    },

    angle: function(angle) {
        if (angle === undefined) {
            return this.options.angle;
        }
        this.options.angle = angle;
        this.performRotation(this.options.angle);
    },

    _create: function() {
        var handle;
        if (!this.options.handle) {
            handle = $(document.createElement('div'));
            handle.addClass('ui-rotatable-handle');
        }
        else {
            handle = this.options.handle;
        }

        this.listeners = {
            rotateElement: $.proxy(this.rotateElement, this),
            startRotate: $.proxy(this.startRotate, this),
            stopRotate: $.proxy(this.stopRotate, this)
        };

        handle.draggable({ helper: 'clone', start: this.dragStart, handle: handle });
        handle.bind('mousedown', this.listeners.startRotate);
        handle.appendTo(this.element);

        if(this.options.angle != false) {
            this.elementCurrentAngle = this.options.angle;
            this.performRotation(this.elementCurrentAngle);
        }
        else {
            this.elementCurrentAngle = 0;
        }
    },

    _destroy: function() {
        this.element.removeClass('ui-rotatable');
        this.element.find('.ui-rotatable-handle').remove();
    },

    performRotation: function(angle) {
        this.element.css('transform','rotate(' + angle + 'rad)');
        this.element.css('-moz-transform','rotate(' + angle + 'rad)');
        this.element.css('-webkit-transform','rotate(' + angle + 'rad)');
        this.element.css('-o-transform','rotate(' + angle + 'rad)');
    },

    getElementOffset: function() {
        this.performRotation(0);
        var offset = this.element.offset();
        this.performRotation(this.elementCurrentAngle);
        return offset;
    },

    getElementCenter: function() {
        var elementOffset = this.getElementOffset();
        var elementCentreX = elementOffset.left + this.element.width() / 2;
        var elementCentreY = elementOffset.top + this.element.height() / 2;
        return Array(elementCentreX, elementCentreY);
    },

    dragStart: function(event) {
        if (this.element) {
            return false;
        }
    },

    startRotate: function(event) {
        var center = this.getElementCenter();
        var startXFromCenter = event.pageX - center[0];
        var startYFromCenter = event.pageY - center[1];
        this.mouseStartAngle = Math.atan2(startYFromCenter, startXFromCenter);
        this.elementStartAngle = this.elementCurrentAngle;
        this.hasRotated = false;

        this._propagate("start", event);

        $(document).bind('mousemove', this.listeners.rotateElement);
        $(document).bind('mouseup', this.listeners.stopRotate);

        return false;
    },

    rotateElement: function(event) {
        if (!this.element || this.element.disabled) {
            return false;
        }

        var center = this.getElementCenter();

        var xFromCenter = event.pageX - center[0];
        var yFromCenter = event.pageY - center[1];
        var mouseAngle = Math.atan2(yFromCenter, xFromCenter);
        var rotateAngle = mouseAngle - this.mouseStartAngle + this.elementStartAngle;

        if (event.shiftKey) {
            //15deg is a common value, but this could be an option
			var predefinedAngle = 15 / 180 * Math.PI;
			if (rotateAngle < 0)
				predefinedAngle *= -1;
			rotateAngle -= (rotateAngle + predefinedAngle / 2) % (predefinedAngle) - predefinedAngle / 2;
		}

        this.performRotation(rotateAngle);
        var previousRotateAngle = this.elementCurrentAngle;
        this.elementCurrentAngle = rotateAngle;

        // Plugins callbacks need to be called first.
        this._propagate("rotate", event);

        if (previousRotateAngle != rotateAngle) {
            this._trigger("rotate", event, this.ui());
            this.hasRotated = true;
        }

        return false;
    },

    stopRotate: function(event) {
        if (!this.element || this.element.disabled) {
            return;
        }

        $(document).unbind('mousemove', this.listeners.rotateElement);
        $(document).unbind('mouseup', this.listeners.stopRotate);

        this.elementStopAngle = this.elementCurrentAngle;
        if (this.hasRotated) {
            this._propagate("stop", event);
        }

        setTimeout( function() { this.element = false; }, 10 );
        return false;
    },

    _propagate: function(n, event) {
        $.ui.plugin.call(this, n, [event, this.ui()]);
        (n !== "rotate" && this._trigger(n, event, this.ui()));
    },

    plugins: {},

    ui: function() {
        return {
            element: this.element,
            angle: {
                start: this.elementStartAngle,
                current: this.elementCurrentAngle,
                stop: this.elementStopAngle
            }
        };
    }

});

})(jQuery);
<|endoftext|>