<commit_before>/**
 * @module ol/events/condition
 */
import MapBrowserEventType from '../MapBrowserEventType.js';
import {assert} from '../asserts.js';
import {TRUE, FALSE} from '../functions.js';
import {WEBKIT, MAC} from '../has.js';
const _ol_events_condition_ = {};

/**
 * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when
 * additionally the shift-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt key is pressed.
 * @api
 */
_ol_events_condition_.altKeyOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};

/**
 * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise
 * (e.g. when additionally the platform-modifier-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt and shift keys are pressed.
 * @api
 */
_ol_events_condition_.altShiftKeysOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      originalEvent.shiftKey);
};

/**
 * Return always true.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True.
 * @function
 * @api
 */
_ol_events_condition_.always = TRUE;

/**
 * Return `true` if the event is a `click` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `click` event.
 * @api
 */
_ol_events_condition_.click = function(mapBrowserEvent) {
  return mapBrowserEvent.type == MapBrowserEventType.CLICK;
};

/**
 * Return `true` if the event has an "action"-producing mouse button.
 *
 * By definition, this includes left-click on windows/linux, and left-click
 * without the ctrl key on Macs.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} The result.
 */
_ol_events_condition_.mouseActionButton = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return originalEvent.button == 0 &&
      !(WEBKIT && MAC && originalEvent.ctrlKey);
};

/**
 * Return always false.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} False.
 * @function
 * @api
 */
_ol_events_condition_.never = FALSE;

/**
 * Return `true` if the browser event is a `pointermove` event, `false`
 * otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the browser event is a `pointermove` event.
 * @api
 */
_ol_events_condition_.pointerMove = function(mapBrowserEvent) {
  return mapBrowserEvent.type == 'pointermove';
};

/**
 * Return `true` if the event is a map `singleclick` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `singleclick` event.
 * @api
 */
_ol_events_condition_.singleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == MapBrowserEventType.SINGLECLICK;
};

/**
 * Return `true` if the event is a map `dblclick` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `dblclick` event.
 * @api
 */
_ol_events_condition_.doubleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == MapBrowserEventType.DBLCLICK;
};

/**
 * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is
 * pressed.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if there no modifier keys are pressed.
 * @api
 */
_ol_events_condition_.noModifierKeys = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    !originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};

/**
 * Return `true` if only the platform-modifier-key (the meta-key on Mac,
 * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally
 * the shift-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the platform modifier key is pressed.
 * @api
 */
_ol_events_condition_.platformModifierKeyOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return !originalEvent.altKey &&
    (MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
    !originalEvent.shiftKey;
};

/**
 * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when
 * additionally the alt-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the shift key is pressed.
 * @api
 */
_ol_events_condition_.shiftKeyOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    !originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      originalEvent.shiftKey);
};

/**
 * Return `true` if the target element is not editable, i.e. not a `<input>`-,
 * `<select>`- or `<textarea>`-element, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if the target element is not editable.
 * @api
 */
_ol_events_condition_.targetNotEditable = function(mapBrowserEvent) {
  const target = mapBrowserEvent.originalEvent.target;
  const tagName = target.tagName;
  return (
    tagName !== 'INPUT' &&
      tagName !== 'SELECT' &&
      tagName !== 'TEXTAREA');
};

/**
 * Return `true` if the event originates from a mouse device.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a mouse device.
 * @api
 */
_ol_events_condition_.mouseOnly = function(mapBrowserEvent) {
  assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event
  // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType
  return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse';
};

/**
 * Return `true` if the event originates from a primary pointer in
 * contact with the surface or if the left mouse button is pressed.
 * @see http://www.w3.org/TR/pointerevents/#button-states
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a primary pointer.
 * @api
 */
_ol_events_condition_.primaryAction = function(mapBrowserEvent) {
  const pointerEvent = mapBrowserEvent.pointerEvent;
  return pointerEvent.isPrimary && pointerEvent.button === 0;
};
export default _ol_events_condition_;
<commit_msg>Named exports from events/condition<commit_after>/**
 * @module ol/events/condition
 */
import MapBrowserEventType from '../MapBrowserEventType.js';
import {assert} from '../asserts.js';
import {TRUE, FALSE} from '../functions.js';
import {WEBKIT, MAC} from '../has.js';

/**
 * Return `true` if only the alt-key is pressed, `false` otherwise (e.g. when
 * additionally the shift-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt key is pressed.
 * @api
 */
export const altKeyOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};

/**
 * Return `true` if only the alt-key and shift-key is pressed, `false` otherwise
 * (e.g. when additionally the platform-modifier-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the alt and shift keys are pressed.
 * @api
 */
export const altShiftKeysOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      originalEvent.shiftKey);
};

/**
 * Return always true.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True.
 * @function
 * @api
 */
export const always = TRUE;

/**
 * Return `true` if the event is a `click` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `click` event.
 * @api
 */
export const click = function(mapBrowserEvent) {
  return mapBrowserEvent.type == MapBrowserEventType.CLICK;
};

/**
 * Return `true` if the event has an "action"-producing mouse button.
 *
 * By definition, this includes left-click on windows/linux, and left-click
 * without the ctrl key on Macs.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} The result.
 */
export const mouseActionButton = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return originalEvent.button == 0 &&
      !(WEBKIT && MAC && originalEvent.ctrlKey);
};

/**
 * Return always false.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} False.
 * @function
 * @api
 */
export const never = FALSE;

/**
 * Return `true` if the browser event is a `pointermove` event, `false`
 * otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the browser event is a `pointermove` event.
 * @api
 */
export const pointerMove = function(mapBrowserEvent) {
  return mapBrowserEvent.type == 'pointermove';
};

/**
 * Return `true` if the event is a map `singleclick` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `singleclick` event.
 * @api
 */
export const singleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == MapBrowserEventType.SINGLECLICK;
};

/**
 * Return `true` if the event is a map `dblclick` event, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event is a map `dblclick` event.
 * @api
 */
export const doubleClick = function(mapBrowserEvent) {
  return mapBrowserEvent.type == MapBrowserEventType.DBLCLICK;
};

/**
 * Return `true` if no modifier key (alt-, shift- or platform-modifier-key) is
 * pressed.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if there no modifier keys are pressed.
 * @api
 */
export const noModifierKeys = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    !originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      !originalEvent.shiftKey);
};

/**
 * Return `true` if only the platform-modifier-key (the meta-key on Mac,
 * ctrl-key otherwise) is pressed, `false` otherwise (e.g. when additionally
 * the shift-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the platform modifier key is pressed.
 * @api
 */
export const platformModifierKeyOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return !originalEvent.altKey &&
    (MAC ? originalEvent.metaKey : originalEvent.ctrlKey) &&
    !originalEvent.shiftKey;
};

/**
 * Return `true` if only the shift-key is pressed, `false` otherwise (e.g. when
 * additionally the alt-key is pressed).
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if only the shift key is pressed.
 * @api
 */
export const shiftKeyOnly = function(mapBrowserEvent) {
  const originalEvent = mapBrowserEvent.originalEvent;
  return (
    !originalEvent.altKey &&
      !(originalEvent.metaKey || originalEvent.ctrlKey) &&
      originalEvent.shiftKey);
};

/**
 * Return `true` if the target element is not editable, i.e. not a `<input>`-,
 * `<select>`- or `<textarea>`-element, `false` otherwise.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True only if the target element is not editable.
 * @api
 */
export const targetNotEditable = function(mapBrowserEvent) {
  const target = mapBrowserEvent.originalEvent.target;
  const tagName = target.tagName;
  return (
    tagName !== 'INPUT' &&
      tagName !== 'SELECT' &&
      tagName !== 'TEXTAREA');
};

/**
 * Return `true` if the event originates from a mouse device.
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a mouse device.
 * @api
 */
export const mouseOnly = function(mapBrowserEvent) {
  assert(mapBrowserEvent.pointerEvent, 56); // mapBrowserEvent must originate from a pointer event
  // see http://www.w3.org/TR/pointerevents/#widl-PointerEvent-pointerType
  return /** @type {ol.MapBrowserEvent} */ (mapBrowserEvent).pointerEvent.pointerType == 'mouse';
};

/**
 * Return `true` if the event originates from a primary pointer in
 * contact with the surface or if the left mouse button is pressed.
 * @see http://www.w3.org/TR/pointerevents/#button-states
 *
 * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.
 * @return {boolean} True if the event originates from a primary pointer.
 * @api
 */
export const primaryAction = function(mapBrowserEvent) {
  const pointerEvent = mapBrowserEvent.pointerEvent;
  return pointerEvent.isPrimary && pointerEvent.button === 0;
};
<|endoftext|>


<commit_before>// switch to testing mode
process.env.NODE_ENV = 'testing';

var utils               = require('../../lib/utils'),
    rest                = require('../../lib/testing/rest'),
    test_server_helpers = require('../../lib/testing/server'),
    config              = require('config'),
    async               = require('async'),
    _                   = require('underscore');

module.exports = {

    setUp: function (setUp_done) {

      this.rest = new rest('foobar','v1');

      utils.delete_all_testing_databases( function () {
        utils.load_test_fixtures( function () {
            test_server_helpers.start_server( function () {
                setUp_done();
            });
        });
      });
    },

    tearDown: function (tearDown_done) {
      test_server_helpers.stop_server(tearDown_done);
    },

    "access API docs": function (test) {

      test.expect(2);

      this.rest.get('/api').on('complete', function(data, response) {

        // check for 200 and json
        test.equal(response.statusCode, 200, "got 200 response");
        test.equal(response.headers['content-type'], 'text/html; charset=utf-8', "got HTML");

        test.done();
      });
    },

    "access API": function (test) {

      test.expect(3);

      this.rest.get('').on('complete', function(data, response) {

        // check for 200 and json
        test.equal(response.statusCode, 200, "got 200 response");
        test.equal(response.headers['content-type'], 'application/json; charset=utf-8', "got JSON");

        // check that data looks right
        test.deepEqual(
          data,
          {
            "comment": "This is the API entry point - use a '*_api_url' link in 'meta' to search a collection.",
            "meta": {
              "person_api_url":       "http://foobar.127.0.0.1.xip.io:3100/api/v1/person",
              "organisation_api_url": "http://foobar.127.0.0.1.xip.io:3100/api/v1/organisation",
              "position_api_url":     "http://foobar.127.0.0.1.xip.io:3100/api/v1/position",
              "image_proxy_url":      "http://foobar.127.0.0.1.xip.io:3100/image-proxy/"
            }
          },
          "response in links to parts of the API"
        );

        test.done();
      });
    },

    "access API using JSONP": function (test) {

      test.expect(3);

      this.rest.get('?callback=name_of_callback').on('complete', function(data, response) {

        test.equal(response.statusCode, 200, "got 200 response");

        test.equal(
          response.headers['content-type'],
          'text/javascript; charset=utf-8',
          "got JavaScript"
        );

        test.ok(
          /^name_of_callback\({.+}\);$/.test(data),
          "response JSON is wrapped in a callback"
        );

        test.done();
      });
    },

    "access API bit that does not exist": function (test) {

      test.expect(3);

      this.rest.get('this/does/not/exist').on('complete', function(data, response) {

        // check for 200 and json
        test.equal(response.statusCode, 404, "got 404 response");
        test.equal(response.headers['content-type'], 'application/json; charset=utf-8', "got JSON");

        // check that data looks right
        test.deepEqual(data, {error: 'page not found'}, "data is error message");

        test.done();
      });
    },

    "list people" : function (test) {
      test.expect(3);

      this.rest
        .get('person')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          var results = data.results;

          // test we got the first four presidents
          test.deepEqual(
            _.pluck(results, 'slug').sort(),
            [ 'george-bush', 'bill-clinton', 'george-w-bush', 'barack-obama' ].sort(),
            "got president slugs"
          );

          // test that a person object looks correct
          test.deepEqual(
            results.sort()[0],
            {
              _id:     '4f9ea1306e8770d854c45a1d',
               slug:   'george-bush',
              name:    'George Bush',
              summary: '41th President of the United States',
              meta: {
                api_url:  'http://foobar.127.0.0.1.xip.io:3100/api/v1/person/4f9ea1306e8770d854c45a1d',
                edit_url: 'http://foobar.127.0.0.1.xip.io:3100/person/george-bush',
              },
            },
            "george-bush details correct"
          );

          test.done();
        });
    },

    "limit people by name" : function (test) {
      test.expect(3);

      this.rest
        .get('person?name=bush')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          var results = data.results;

          // test we got the first four presidents
          test.deepEqual(
            _.pluck(results, 'slug').sort(),
            [ 'george-bush', 'george-w-bush', ].sort(),
            "got Bush president slugs"
          );

          // test that the api_url is correct
          test.equal(
            _.sortBy( results, 'slug')[0].meta.api_url,
            'http://foobar.127.0.0.1.xip.io:3100/api/v1/person/4f9ea1306e8770d854c45a1d',
            "got api_url as expected"
          );

          test.done();
        });
    },

    "load one person (by ObjectId)" : function (test) {
      test.expect(2);

      this.rest
        .get('person/4f9ea1306e8770d854c45a1d')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          var result = data.result;

          // test that a person object looks correct
          test.deepEqual(
            result,
            {
              __v: 1,
              _id:     '4f9ea1306e8770d854c45a1d',
              name:    'George Bush',
              slug:    'george-bush',
              summary: '41th President of the United States',
              other_names:     [],
              images:          [],
              links:           [],
              contact_details: [],
              name_words: [ 'george', 'bush' ],
              name_dm: [ 'JRJ', 'KRK', 'PX', 'PX', 'george', 'bush' ],
              meta: {
                edit_url: 'http://foobar.127.0.0.1.xip.io:3100/person/george-bush',
              },
            },
            "george-bush details correct"
          );

          test.done();
        });
    },

    "load one person (by slug)" : function (test) {

      // find the entry, and then redirect to the ObjectId based api url

      test.expect(2);

      this.rest
        .get('person/george-bush')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          test.equal(
            response.client._httpMessage.path, // note - can't see how to get this through proper calls
            '/api/v1/person/4f9ea1306e8770d854c45a1d',
            "redirected to ObjectId url"
          );

          test.done();
        });
    },

    "load a not found objectid" : function (test) {
      test.expect(1);

      this.rest
        .get('person/4f9ea1306e8770d854c4aaaa')
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 404, "got 404 response");
          test.done();
        });
    },

    "load a not found slug" : function (test) {
      test.expect(1);

      this.rest
        .get('person/i-dont-exist')
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 404, "got 404 response");
          test.done();
        });
    },

    "list all positions": function (test) {
      test.expect(2);

      this.rest
        .get('position')
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 200, "got 200 response");

          // test we got the first four presidents
          test.deepEqual(
            _.pluck(data.results, 'title').sort(),
            [ 'President', 'President', 'President', 'President' ].sort(),
            "got expected titles"
          );

          test.done();
        });

    },

    "list one persons positions": function (test) {
      test.expect(2);

      this.rest
        .get('position?person=4f9ea1326e8770d854c45a1e') // Clinton
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 200, "got 200 response");

          // test we got the first four presidents
          test.deepEqual(
            data.results,
            [{
              _id:          '4f9ea1326e8770d854c45a23',
              title:        'President',
              person:       '4f9ea1326e8770d854c45a1e',
              organisation: '4f9ea1326e8770d854c45a21',
              meta: {
                api_url: 'http://foobar.127.0.0.1.xip.io:3100/api/v1/position/4f9ea1326e8770d854c45a23',
                edit_url: 'http://foobar.127.0.0.1.xip.io:3100/position/4f9ea1326e8770d854c45a23'
              },
            }],
            "got Clinton's Presidency"
          );

          test.done();
        });

    },

    // test pagination

    // test sorting

};

<commit_msg>update tests with new ids in test fixture<commit_after>// switch to testing mode
process.env.NODE_ENV = 'testing';

var utils               = require('../../lib/utils'),
    rest                = require('../../lib/testing/rest'),
    test_server_helpers = require('../../lib/testing/server'),
    config              = require('config'),
    async               = require('async'),
    _                   = require('underscore');

module.exports = {

    setUp: function (setUp_done) {

      this.rest = new rest('foobar','v1');

      utils.delete_all_testing_databases( function () {
        utils.load_test_fixtures( function () {
            test_server_helpers.start_server( function () {
                setUp_done();
            });
        });
      });
    },

    tearDown: function (tearDown_done) {
      test_server_helpers.stop_server(tearDown_done);
    },

    "access API docs": function (test) {

      test.expect(2);

      this.rest.get('/api').on('complete', function(data, response) {

        // check for 200 and json
        test.equal(response.statusCode, 200, "got 200 response");
        test.equal(response.headers['content-type'], 'text/html; charset=utf-8', "got HTML");

        test.done();
      });
    },

    "access API": function (test) {

      test.expect(3);

      this.rest.get('').on('complete', function(data, response) {

        // check for 200 and json
        test.equal(response.statusCode, 200, "got 200 response");
        test.equal(response.headers['content-type'], 'application/json; charset=utf-8', "got JSON");

        // check that data looks right
        test.deepEqual(
          data,
          {
            "comment": "This is the API entry point - use a '*_api_url' link in 'meta' to search a collection.",
            "meta": {
              "person_api_url":       "http://foobar.127.0.0.1.xip.io:3100/api/v1/person",
              "organisation_api_url": "http://foobar.127.0.0.1.xip.io:3100/api/v1/organisation",
              "position_api_url":     "http://foobar.127.0.0.1.xip.io:3100/api/v1/position",
              "image_proxy_url":      "http://foobar.127.0.0.1.xip.io:3100/image-proxy/"
            }
          },
          "response in links to parts of the API"
        );

        test.done();
      });
    },

    "access API using JSONP": function (test) {

      test.expect(3);

      this.rest.get('?callback=name_of_callback').on('complete', function(data, response) {

        test.equal(response.statusCode, 200, "got 200 response");

        test.equal(
          response.headers['content-type'],
          'text/javascript; charset=utf-8',
          "got JavaScript"
        );

        test.ok(
          /^name_of_callback\({.+}\);$/.test(data),
          "response JSON is wrapped in a callback"
        );

        test.done();
      });
    },

    "access API bit that does not exist": function (test) {

      test.expect(3);

      this.rest.get('this/does/not/exist').on('complete', function(data, response) {

        // check for 200 and json
        test.equal(response.statusCode, 404, "got 404 response");
        test.equal(response.headers['content-type'], 'application/json; charset=utf-8', "got JSON");

        // check that data looks right
        test.deepEqual(data, {error: 'page not found'}, "data is error message");

        test.done();
      });
    },

    "list people" : function (test) {
      test.expect(3);

      this.rest
        .get('person')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          var results = data.results;

          // test we got the first four presidents
          test.deepEqual(
            _.pluck(results, 'slug').sort(),
            [ 'george-bush', 'bill-clinton', 'george-w-bush', 'barack-obama' ].sort(),
            "got president slugs"
          );

          // test that a person object looks correct
          test.deepEqual(
            results.sort()[0],
            {
              _id:     '4f9ea1306e8770d854c45a1d',
               slug:   'george-bush',
              name:    'George Bush',
              summary: '41th President of the United States',
              meta: {
                api_url:  'http://foobar.127.0.0.1.xip.io:3100/api/v1/person/4f9ea1306e8770d854c45a1d',
                edit_url: 'http://foobar.127.0.0.1.xip.io:3100/person/george-bush',
              },
            },
            "george-bush details correct"
          );

          test.done();
        });
    },

    "limit people by name" : function (test) {
      test.expect(3);

      this.rest
        .get('person?name=bush')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          var results = data.results;

          // test we got the first four presidents
          test.deepEqual(
            _.pluck(results, 'slug').sort(),
            [ 'george-bush', 'george-w-bush', ].sort(),
            "got Bush president slugs"
          );

          // test that the api_url is correct
          test.equal(
            _.sortBy( results, 'slug')[0].meta.api_url,
            'http://foobar.127.0.0.1.xip.io:3100/api/v1/person/4f9ea1306e8770d854c45a1d',
            "got api_url as expected"
          );

          test.done();
        });
    },

    "load one person (by ObjectId)" : function (test) {
      test.expect(2);

      this.rest
        .get('person/4f9ea1306e8770d854c45a1d')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          var result = data.result;

          // test that a person object looks correct
          test.deepEqual(
            result,
            {
              __v: 1,
              _id:     '4f9ea1306e8770d854c45a1d',
              name:    'George Bush',
              slug:    'george-bush',
              summary: '41th President of the United States',
              other_names:     [],
              images:          [],
              links:           [],
              contact_details: [],
              name_words: [ 'george', 'bush' ],
              name_dm: [ 'JRJ', 'KRK', 'PX', 'PX', 'george', 'bush' ],
              meta: {
                edit_url: 'http://foobar.127.0.0.1.xip.io:3100/person/george-bush',
              },
            },
            "george-bush details correct"
          );

          test.done();
        });
    },

    "load one person (by slug)" : function (test) {

      // find the entry, and then redirect to the ObjectId based api url

      test.expect(2);

      this.rest
        .get('person/george-bush')
        .on('complete', function(data, response) {

          test.equal(response.statusCode, 200, "got 200 response");

          test.equal(
            response.client._httpMessage.path, // note - can't see how to get this through proper calls
            '/api/v1/person/4f9ea1306e8770d854c45a1d',
            "redirected to ObjectId url"
          );

          test.done();
        });
    },

    "load a not found objectid" : function (test) {
      test.expect(1);

      this.rest
        .get('person/4f9ea1306e8770d854c4aaaa')
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 404, "got 404 response");
          test.done();
        });
    },

    "load a not found slug" : function (test) {
      test.expect(1);

      this.rest
        .get('person/i-dont-exist')
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 404, "got 404 response");
          test.done();
        });
    },

    "list all positions": function (test) {
      test.expect(2);

      this.rest
        .get('position')
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 200, "got 200 response");

          // test we got the first four presidents
          test.deepEqual(
            _.pluck(data.results, 'title').sort(),
            [ 'President', 'President', 'President', 'President' ].sort(),
            "got expected titles"
          );

          test.done();
        });

    },

    "list one persons positions": function (test) {
      test.expect(2);

      this.rest
        .get('position?person=4f9ea1316e8770d854c45a1e') // Clinton
        .on('complete', function(data, response) {
          test.equal(response.statusCode, 200, "got 200 response");

          // test we got the first four presidents
          test.deepEqual(
            data.results,
            [{
              _id:          '4f9ea1326e8770d854c45a23',
              title:        'President',
              person:       '4f9ea1316e8770d854c45a1e',
              organisation: '4f9ea1326e8770d854c45a21',
              meta: {
                api_url: 'http://foobar.127.0.0.1.xip.io:3100/api/v1/position/4f9ea1326e8770d854c45a23',
                edit_url: 'http://foobar.127.0.0.1.xip.io:3100/position/4f9ea1326e8770d854c45a23'
              },
            }],
            "got Clinton's Presidency"
          );

          test.done();
        });

    },

    // test pagination

    // test sorting

};

<|endoftext|>


<commit_before><commit_msg>refractor, renamiming, removed old commented code<commit_after><|endoftext|>


<commit_before>/*eslint no-console: ["error", { allow: ["log"] }] */
// dependencies ------------------------------------------------------------

import aws     from '../libs/aws';
import scitran from '../libs/scitran';
import crypto  from 'crypto';
import uuid    from 'uuid';
import mongo         from '../libs/mongo';
import {ObjectID}    from 'mongodb';
import archiver      from 'archiver';
import config from '../config';
import async from 'async';
import notifications from '../libs/notifications';
import emitter from '../libs/events';

let c = mongo.collections;
let events = config.events;

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Create Job Definition
     */
    createJobDefinition(req, res, next) {
        let jobDef = Object.assign({}, req.body);
        //AWS Batch will choke if we leave descriptions property on payload so deleting before sending
        delete jobDef.descriptions;
        delete jobDef.parametersMetadata;
        delete jobDef.analysisLevels;

        aws.batch.registerJobDefinition(jobDef, (err, data) => {
            if (err) {
                console.log(err);
                return next(err);
            } else {
                let extendeJobDef = data;
                extendeJobDef.parameters = req.body.parameters || {};
                extendeJobDef.descriptions = req.body.descriptions || {};
                extendeJobDef.parametersMetadata = req.body.parametersMetadata || {};
                extendeJobDef.analysisLevels = req.body.analysisLevels || {};
                c.crn.jobDefinitions.insertOne(extendeJobDef, (err) => {
                    if(err){
                        //TODO -- error handling? make response dependant on inserting document?
                    }
                });
                // can go ahead and respond to client without waiting on mongo insert
                res.send(data);
            }
        });
    },

    /**
    * Disable Job Definition
    */
    disableJobDefinition(req, res, next) {
        let jobArn = req.body.arn;
        aws.batch.sdk.deregisterJobDefinition({jobDefinition: jobArn}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        c.crn.jobDefinitions.find({}, {jobDefinitionArn: true}).toArray((err, appDefs) => {
            let arns = appDefs.map((app) => {
                return app.jobDefinitionArn;
            });
            aws.batch.sdk.describeJobDefinitions({jobDefinitions: arns}, (err, data) => {
                if (err) {
                    console.log(err);
                    return next(err);
                } else {
                    let definitions = {};
                    //need to attach job definition descriptions from mongo to job defs returned from AWS batch
                    async.each(data.jobDefinitions, (definition, cb) => {
                        if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                            definitions[definition.jobDefinitionName] = {};
                        }
                        c.crn.jobDefinitions.find({
                            jobDefinitionName: definition.jobDefinitionName,
                            jobDefinitionArn: definition.jobDefinitionArn,
                            revision: definition.revision
                        }, {descriptions: true, parameters: true, parametersMetadata: true, analysisLevels: true}).toArray((err, def) => {
                            // there will either be a one element array or an empty array returned
                            let parameters = def.length === 0 || !def[0].parameters ? {} : def[0].parameters;
                            let descriptions = def.length === 0 || !def[0].descriptions ? {} : def[0].descriptions;
                            let parametersMetadata = def.length === 0 || !def[0].parametersMetadata ? {} : def[0].parametersMetadata;
                            let analysisLevels = def.length === 0 || !def[0].analysisLevels ? {} : def[0].analysisLevels;
                            definition.parameters = parameters;
                            definition.descriptions = descriptions;
                            definition.parametersMetadata = parametersMetadata;
                            definition.analysisLevels = analysisLevels;
                            definitions[definition.jobDefinitionName][definition.revision] = definition;
                            cb();
                        });
                    }, () => {
                        res.send(definitions);
                    });
                }
            });
        });
    },

    /**
     * Submit Job
     * Inserts a job document into mongo and starts snapshot upload
     * returns job to client
     */
    submitJob(req, res, next) {
        let job = req.body;

        job.uploadSnapshotComplete = !!job.uploadSnapshotComplete;
        job.analysis = {
            analysisId: uuid.v4(),
            status: 'UPLOADING',
            created: new Date(),
            attempts: 0
        };
        //making consistent with agave ??
        job.appLabel = job.jobName;
        job.appVersion = job.jobDefinition.match(/\d+$/)[0];

        scitran.downloadSymlinkDataset(job.snapshotId, (err, hash) => {
            job.datasetHash = hash;
            job.parametersHash = crypto.createHash('md5').update(JSON.stringify(job.parameters)).digest('hex');

            // jobDefintion is the full ARN including version, region, etc
            c.crn.jobs.findOne({
                jobDefinition:  job.jobDefinition,
                datasetHash:    job.datasetHash,
                parametersHash: job.parametersHash,
                snapshotId:     job.snapshotId
            }, {}, (err, existingJob) => {
                if (err){return next(err);}
                if (existingJob) {
                    // allow retrying failed jobs
                    if (existingJob.analysis && existingJob.analysis.status === 'FAILED') {
                        handlers.retry({params: {jobId: existingJob.jobId}}, res, next);
                        return;
                    }
                    let error = new Error('A job with the same dataset and parameters has already been run.');
                    error.http_code = 409;
                    res.status(409).send({message: 'A job with the same dataset and parameters has already been run.'});
                    return;
                }

                c.crn.jobs.insertOne(job, (err, mongoJob) => {

                    // Finish the client request so S3 upload can happen async
                    res.send({jobId: mongoJob.insertedId});

                    // TODO - handle situation where upload to S3 fails
                    aws.s3.uploadSnapshot(hash, () => {
                        const batchJobParams = {
                            jobDefinition: job.jobDefinition,
                            jobName:       job.jobName,
                            jobQueue:      'bids-queue',
                            parameters:    job.parameters,
                            containerOverrides:{
                                environment: [{
                                    name: 'BIDS_SNAPSHOT_ID',
                                    value: hash
                                }, {
                                    name: 'BIDS_ANALYSIS_ID',
                                    value: job.analysis.analysisId
                                }]
                            }
                        };

                        aws.batch.startBatchJob(batchJobParams, mongoJob.insertedId);
                        emitter.emit(events.JOB_STARTED, {job: batchJobParams, createdDate: job.analysis.created});
                    });
                });
            });
        }, {snapshot: true});
    },

    /**
     * GET Job
     */
    getJob(req, res) {
        let jobId = req.params.jobId; //this is the mongo id for the job.

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            if (!job) {
                res.status(404).send({message: 'Job not found.'});
                return;
            }
            let status = job.analysis.status;
            let analysisId = job.analysis.analysisId;
            let jobs = job.analysis.jobs;
            let createdDate = job.analysis.created;

            // check if job is already known to be completed
            // there could be a scenario where we are polling before the AWS batch job has been setup. !jobs check handles this.
            if ((status === 'SUCCEEDED' && job.results && job.results.length > 0) || status === 'FAILED' || !jobs) {
                res.send(job);
            } else {
                let params = {
                    jobs: jobs
                };
                aws.batch.sdk.describeJobs(params, (err, resp) => {
                    let analysis = {};
                    let statusArray = resp.jobs.map((job) => {
                        return job.status;
                    });
                    //if every status is either succeeded or failed, all jobs have completed.
                    let finished = statusArray.every((status) => {
                        return status === 'SUCCEEDED' || status === 'FAILED';
                    });

                    analysis.status = !finished ? 'RUNNING' : 'FINALIZING';
                    analysis.created = createdDate;
                    // check status
                    if(finished){
                        let logStreamNames; //this will be an array of cloudwatch logstream names logs for each job
                        //Check if any jobs failed, if so analysis failed, else succeeded
                        let finalStatus = statusArray.some((status)=>{ return status === 'FAILED';}) ? 'FAILED' : 'SUCCEEDED';
                        let s3Prefix = job.datasetHash + '/' + job.analysis.analysisId + '/';
                        let params = {
                            Bucket: 'openneuro.outputs',
                            Prefix: s3Prefix,
                            StartAfter: s3Prefix
                        };
                        // emit a job finished event so we can add logs and sent notification email
                        // cloning job here and sending out event and email because mongos updateOne does not return updated doc
                        let clonedJob = JSON.parse(JSON.stringify(job));
                        clonedJob.analysis.status = finalStatus;
                        emitter.emit(events.JOB_COMPLETED, {job: clonedJob, completedDate: new Date()});
                        notifications.jobComplete(clonedJob);

                        if(resp.jobs && resp.jobs.length > 0) {
                            logStreamNames = resp.jobs.reduce((acc, job) => {
                                if (job.attempts && job.attempts.length > 0) {
                                    job.attempts.forEach((attempt)=> {
                                        acc.push(job.jobName + '/' + job.jobId + '/' + attempt.container.taskArn.split('/').pop());
                                    });
                                }
                                return acc;
                            }, []);
                        }

                        aws.s3.sdk.listObjectsV2(params, (err, data) => {
                            let results = [];
                            data.Contents.forEach((obj) => {
                                if(!/\/$/.test(obj.Key)) {
                                    let result = {};
                                    result.name = obj.Key;
                                    result.path = params.Bucket + '/' + obj.Key;
                                    results.push(result);
                                }
                            });
                            c.crn.jobs.updateOne({_id: ObjectID(jobId)}, {
                                $set:{
                                    'analysis.status': finalStatus,
                                    results: results,
                                    'analysis.logstreams': logStreamNames
                                }
                            });
                        });
                    }
                    res.send({
                        analysis: analysis,
                        jobId: analysisId,
                        datasetId: job.datasetId,
                        snapshotId: job.snapshotId
                    });

                    // notifications.jobComplete(job);

                });
            }
        });
    },

    /**
     * GET File
     * listObjects to find everything in the s3 bucket for a given job
     * stream all files in series(?) to zip
     */
    downloadAllS3(req, res) {
        let jobId = req.params.jobId;

        const path = 'all-results'; //req.ticket.filePath;
        if (path === 'all-results' || path === 'all-logs') {

            const type = path.replace('all-', '');

            // initialize archive
            let archive = archiver('zip');

            // log archiving errors
            archive.on('error', (err) => {
                console.log('archiving error - job: ' + jobId);
                console.log(err);
            });

            c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
                let archiveName = job.datasetLabel + '__' + job.analysis.analysisId + '__' + type;
                let s3Prefix = job.datasetHash + '/' + job.analysis.analysisId + '/';
                //params to list objects for a job
                let params = {
                    Bucket: config.aws.s3.analysisBucket,
                    Prefix: s3Prefix,
                    StartAfter: s3Prefix
                };

                // set archive name
                res.attachment(archiveName + '.zip');

                // begin streaming archive
                archive.pipe(res);

                aws.s3.sdk.listObjectsV2(params, (err, data) => {
                    let keysArray = [];
                    data.Contents.forEach((obj) => {
                        //only include files in results. listObjectsV2 returns keys for directories also so need to filter those out.
                        if(!/\/$/.test(obj.Key)) {
                            keysArray.push(obj.Key);
                        }
                    });

                    async.eachSeries(keysArray, (key, cb) => {
                        let objParams = {
                            Bucket: config.aws.s3.analysisBucket,
                            Key: key
                        };
                        let fileName = key.split('/')[key.split('/').length - 1]; //get filename from key
                        aws.s3.sdk.getObject(objParams, (err, response) => {
                            //append to zip
                            archive.append(response.Body, {name: fileName});
                            cb();
                        });
                    }, () => {
                        archive.finalize();
                    });
                });
            });
        }

    },

    getJobLogs (req, res, next) {
        let jobId = req.params.jobId; //this will be the mongoId for a given analysis

        //Recursive function to snag all logs from a logstream
        let logsFunc = (params, logs, callback) => {
            let logStreamName = params.logStreamName;
            aws.cloudwatch.sdk.getLogEvents(params, (err, data)=> {
                if(err) {return callback(err);}
                //Cloudwatch returns a token even if there are no events. That is why checking events length
                if((data.events && data.events.length > 0) && data.nextForwardToken) {
                    if(!logs[logStreamName]) {
                        logs[logStreamName] = [];
                    }
                    logs[logStreamName] = logs[logStreamName].concat(data.events);
                    params.nextToken = data.nextForwardToken;
                    if(params.startFromHead) {
                        delete params.startFromHead; //only necessary on first call I think.
                    }
                    logsFunc(params, logs, callback);
                } else {
                    callback();
                }
            });
        };

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            if(err) {next(err);}
            let logStreamNames = job.analysis.logstreams || [];
            let logs = {};
            //cloudwatch log events requires knowing jobId and taskArn(s)
            // taskArns are available on job which we can access with a describeJobs call to batch
            async.eachSeries(logStreamNames, (logStreamName, cb) => {
                // this currently works to grab the latest logs for a job. Need to update to get all logs for a job using next tokens
                // however there is a bug that will require a little more work to make this happen. https://forums.aws.amazon.com/thread.jspa?threadID=251240&tstart=0
                let params = {
                    logGroupName: config.aws.cloudwatchlogs.logGroupName,
                    logStreamName: logStreamName,
                    startFromHead: true
                };
                logsFunc(params, logs, cb);
            }, (err) =>{
                if(err) {return next(err);}
                res.send(logs);
            });
        });
    },

    /**
     * Retry a job using existing parameters
     */
    retry (req, res, next) {
        // let jobId = req.params.jobId;
        // TODO - This is a stub for testing - need to resubmit using the same CRN job data but a new AWS Batch job
        let error = new Error('Retry is not yet supported.');
        error.http_code = 409;
        return next(error);
    }

};

export default handlers;
<commit_msg>adding error handling to getJob<commit_after>/*eslint no-console: ["error", { allow: ["log"] }] */
// dependencies ------------------------------------------------------------

import aws     from '../libs/aws';
import scitran from '../libs/scitran';
import crypto  from 'crypto';
import uuid    from 'uuid';
import mongo         from '../libs/mongo';
import {ObjectID}    from 'mongodb';
import archiver      from 'archiver';
import config from '../config';
import async from 'async';
import notifications from '../libs/notifications';
import emitter from '../libs/events';

let c = mongo.collections;
let events = config.events;

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Create Job Definition
     */
    createJobDefinition(req, res, next) {
        let jobDef = Object.assign({}, req.body);
        //AWS Batch will choke if we leave descriptions property on payload so deleting before sending
        delete jobDef.descriptions;
        delete jobDef.parametersMetadata;
        delete jobDef.analysisLevels;

        aws.batch.registerJobDefinition(jobDef, (err, data) => {
            if (err) {
                console.log(err);
                return next(err);
            } else {
                let extendeJobDef = data;
                extendeJobDef.parameters = req.body.parameters || {};
                extendeJobDef.descriptions = req.body.descriptions || {};
                extendeJobDef.parametersMetadata = req.body.parametersMetadata || {};
                extendeJobDef.analysisLevels = req.body.analysisLevels || {};
                c.crn.jobDefinitions.insertOne(extendeJobDef, (err) => {
                    if(err){
                        //TODO -- error handling? make response dependant on inserting document?
                    }
                });
                // can go ahead and respond to client without waiting on mongo insert
                res.send(data);
            }
        });
    },

    /**
    * Disable Job Definition
    */
    disableJobDefinition(req, res, next) {
        let jobArn = req.body.arn;
        aws.batch.sdk.deregisterJobDefinition({jobDefinition: jobArn}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                res.send(data);
            }
        });
    },

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        c.crn.jobDefinitions.find({}, {jobDefinitionArn: true}).toArray((err, appDefs) => {
            let arns = appDefs.map((app) => {
                return app.jobDefinitionArn;
            });
            aws.batch.sdk.describeJobDefinitions({jobDefinitions: arns}, (err, data) => {
                if (err) {
                    console.log(err);
                    return next(err);
                } else {
                    let definitions = {};
                    //need to attach job definition descriptions from mongo to job defs returned from AWS batch
                    async.each(data.jobDefinitions, (definition, cb) => {
                        if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                            definitions[definition.jobDefinitionName] = {};
                        }
                        c.crn.jobDefinitions.find({
                            jobDefinitionName: definition.jobDefinitionName,
                            jobDefinitionArn: definition.jobDefinitionArn,
                            revision: definition.revision
                        }, {descriptions: true, parameters: true, parametersMetadata: true, analysisLevels: true}).toArray((err, def) => {
                            // there will either be a one element array or an empty array returned
                            let parameters = def.length === 0 || !def[0].parameters ? {} : def[0].parameters;
                            let descriptions = def.length === 0 || !def[0].descriptions ? {} : def[0].descriptions;
                            let parametersMetadata = def.length === 0 || !def[0].parametersMetadata ? {} : def[0].parametersMetadata;
                            let analysisLevels = def.length === 0 || !def[0].analysisLevels ? {} : def[0].analysisLevels;
                            definition.parameters = parameters;
                            definition.descriptions = descriptions;
                            definition.parametersMetadata = parametersMetadata;
                            definition.analysisLevels = analysisLevels;
                            definitions[definition.jobDefinitionName][definition.revision] = definition;
                            cb();
                        });
                    }, () => {
                        res.send(definitions);
                    });
                }
            });
        });
    },

    /**
     * Submit Job
     * Inserts a job document into mongo and starts snapshot upload
     * returns job to client
     */
    submitJob(req, res, next) {
        let job = req.body;

        job.uploadSnapshotComplete = !!job.uploadSnapshotComplete;
        job.analysis = {
            analysisId: uuid.v4(),
            status: 'UPLOADING',
            created: new Date(),
            attempts: 0
        };
        //making consistent with agave ??
        job.appLabel = job.jobName;
        job.appVersion = job.jobDefinition.match(/\d+$/)[0];

        scitran.downloadSymlinkDataset(job.snapshotId, (err, hash) => {
            job.datasetHash = hash;
            job.parametersHash = crypto.createHash('md5').update(JSON.stringify(job.parameters)).digest('hex');

            // jobDefintion is the full ARN including version, region, etc
            c.crn.jobs.findOne({
                jobDefinition:  job.jobDefinition,
                datasetHash:    job.datasetHash,
                parametersHash: job.parametersHash,
                snapshotId:     job.snapshotId
            }, {}, (err, existingJob) => {
                if (err){return next(err);}
                if (existingJob) {
                    // allow retrying failed jobs
                    if (existingJob.analysis && existingJob.analysis.status === 'FAILED') {
                        handlers.retry({params: {jobId: existingJob.jobId}}, res, next);
                        return;
                    }
                    let error = new Error('A job with the same dataset and parameters has already been run.');
                    error.http_code = 409;
                    res.status(409).send({message: 'A job with the same dataset and parameters has already been run.'});
                    return;
                }

                c.crn.jobs.insertOne(job, (err, mongoJob) => {

                    // Finish the client request so S3 upload can happen async
                    res.send({jobId: mongoJob.insertedId});

                    // TODO - handle situation where upload to S3 fails
                    aws.s3.uploadSnapshot(hash, () => {
                        const batchJobParams = {
                            jobDefinition: job.jobDefinition,
                            jobName:       job.jobName,
                            jobQueue:      'bids-queue',
                            parameters:    job.parameters,
                            containerOverrides:{
                                environment: [{
                                    name: 'BIDS_SNAPSHOT_ID',
                                    value: hash
                                }, {
                                    name: 'BIDS_ANALYSIS_ID',
                                    value: job.analysis.analysisId
                                }]
                            }
                        };

                        aws.batch.startBatchJob(batchJobParams, mongoJob.insertedId);
                        emitter.emit(events.JOB_STARTED, {job: batchJobParams, createdDate: job.analysis.created});
                    });
                });
            });
        }, {snapshot: true});
    },

    /**
     * GET Job
     */
    getJob(req, res, next) {
        let jobId = req.params.jobId; //this is the mongo id for the job.

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            if (!job) {
                res.status(404).send({message: 'Job not found.'});
                return;
            }
            let status = job.analysis.status;
            let analysisId = job.analysis.analysisId;
            let jobs = job.analysis.jobs;
            let createdDate = job.analysis.created;

            // check if job is already known to be completed
            // there could be a scenario where we are polling before the AWS batch job has been setup. !jobs check handles this.
            if ((status === 'SUCCEEDED' && job.results && job.results.length > 0) || status === 'FAILED' || !jobs) {
                res.send(job);
            } else {
                let params = {
                    jobs: jobs
                };
                aws.batch.sdk.describeJobs(params, (err, resp) => {
                    if(err) {return next(err);}
                    let analysis = {};
                    let statusArray = resp.jobs.map((job) => {
                        return job.status;
                    });
                    //if every status is either succeeded or failed, all jobs have completed.
                    let finished = statusArray.every((status) => {
                        return status === 'SUCCEEDED' || status === 'FAILED';
                    });

                    analysis.status = !finished ? 'RUNNING' : 'FINALIZING';
                    analysis.created = createdDate;
                    // check status
                    if(finished){
                        let logStreamNames; //this will be an array of cloudwatch logstream names logs for each job
                        //Check if any jobs failed, if so analysis failed, else succeeded
                        // note, if statusArray is empty, this means the job does not exist on Batch anymore and we did not catch it's
                        //   pass or fail state for some reason so we are going to call this a failure.
                        let finalStatus = !statusArray.length || statusArray.some((status)=>{ return status === 'FAILED';}) ? 'FAILED' : 'SUCCEEDED';
                        let s3Prefix = job.datasetHash + '/' + job.analysis.analysisId + '/';
                        let params = {
                            Bucket: 'openneuro.outputs',
                            Prefix: s3Prefix,
                            StartAfter: s3Prefix
                        };
                        // emit a job finished event so we can add logs and sent notification email
                        // cloning job here and sending out event and email because mongos updateOne does not return updated doc
                        let clonedJob = JSON.parse(JSON.stringify(job));
                        clonedJob.analysis.status = finalStatus;
                        emitter.emit(events.JOB_COMPLETED, {job: clonedJob, completedDate: new Date()});
                        notifications.jobComplete(clonedJob);

                        if(resp.jobs && resp.jobs.length > 0) {
                            logStreamNames = resp.jobs.reduce((acc, job) => {
                                if (job.attempts && job.attempts.length > 0) {
                                    job.attempts.forEach((attempt)=> {
                                        acc.push(job.jobName + '/' + job.jobId + '/' + attempt.container.taskArn.split('/').pop());
                                    });
                                }
                                return acc;
                            }, []);
                        }

                        aws.s3.sdk.listObjectsV2(params, (err, data) => {
                            if(err) {return next(err);}

                            let results = [];
                            data.Contents.forEach((obj) => {
                                if(!/\/$/.test(obj.Key)) {
                                    let result = {};
                                    result.name = obj.Key;
                                    result.path = params.Bucket + '/' + obj.Key;
                                    results.push(result);
                                }
                            });
                            c.crn.jobs.updateOne({_id: ObjectID(jobId)}, {
                                $set:{
                                    'analysis.status': finalStatus,
                                    results: results,
                                    'analysis.logstreams': logStreamNames
                                }
                            });
                        });
                    }
                    res.send({
                        analysis: analysis,
                        jobId: analysisId,
                        datasetId: job.datasetId,
                        snapshotId: job.snapshotId
                    });

                    // notifications.jobComplete(job);

                });
            }
        });
    },

    /**
     * GET File
     * listObjects to find everything in the s3 bucket for a given job
     * stream all files in series(?) to zip
     */
    downloadAllS3(req, res) {
        let jobId = req.params.jobId;

        const path = 'all-results'; //req.ticket.filePath;
        if (path === 'all-results' || path === 'all-logs') {

            const type = path.replace('all-', '');

            // initialize archive
            let archive = archiver('zip');

            // log archiving errors
            archive.on('error', (err) => {
                console.log('archiving error - job: ' + jobId);
                console.log(err);
            });

            c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
                let archiveName = job.datasetLabel + '__' + job.analysis.analysisId + '__' + type;
                let s3Prefix = job.datasetHash + '/' + job.analysis.analysisId + '/';
                //params to list objects for a job
                let params = {
                    Bucket: config.aws.s3.analysisBucket,
                    Prefix: s3Prefix,
                    StartAfter: s3Prefix
                };

                // set archive name
                res.attachment(archiveName + '.zip');

                // begin streaming archive
                archive.pipe(res);

                aws.s3.sdk.listObjectsV2(params, (err, data) => {
                    let keysArray = [];
                    data.Contents.forEach((obj) => {
                        //only include files in results. listObjectsV2 returns keys for directories also so need to filter those out.
                        if(!/\/$/.test(obj.Key)) {
                            keysArray.push(obj.Key);
                        }
                    });

                    async.eachSeries(keysArray, (key, cb) => {
                        let objParams = {
                            Bucket: config.aws.s3.analysisBucket,
                            Key: key
                        };
                        let fileName = key.split('/')[key.split('/').length - 1]; //get filename from key
                        aws.s3.sdk.getObject(objParams, (err, response) => {
                            //append to zip
                            archive.append(response.Body, {name: fileName});
                            cb();
                        });
                    }, () => {
                        archive.finalize();
                    });
                });
            });
        }

    },

    getJobLogs (req, res, next) {
        let jobId = req.params.jobId; //this will be the mongoId for a given analysis

        //Recursive function to snag all logs from a logstream
        let logsFunc = (params, logs, callback) => {
            let logStreamName = params.logStreamName;
            aws.cloudwatch.sdk.getLogEvents(params, (err, data)=> {
                if(err) {return callback(err);}
                //Cloudwatch returns a token even if there are no events. That is why checking events length
                if((data.events && data.events.length > 0) && data.nextForwardToken) {
                    if(!logs[logStreamName]) {
                        logs[logStreamName] = [];
                    }
                    logs[logStreamName] = logs[logStreamName].concat(data.events);
                    params.nextToken = data.nextForwardToken;
                    if(params.startFromHead) {
                        delete params.startFromHead; //only necessary on first call I think.
                    }
                    logsFunc(params, logs, callback);
                } else {
                    callback();
                }
            });
        };

        c.crn.jobs.findOne({_id: ObjectID(jobId)}, {}, (err, job) => {
            if(err) {next(err);}
            let logStreamNames = job.analysis.logstreams || [];
            let logs = {};
            //cloudwatch log events requires knowing jobId and taskArn(s)
            // taskArns are available on job which we can access with a describeJobs call to batch
            async.eachSeries(logStreamNames, (logStreamName, cb) => {
                // this currently works to grab the latest logs for a job. Need to update to get all logs for a job using next tokens
                // however there is a bug that will require a little more work to make this happen. https://forums.aws.amazon.com/thread.jspa?threadID=251240&tstart=0
                let params = {
                    logGroupName: config.aws.cloudwatchlogs.logGroupName,
                    logStreamName: logStreamName,
                    startFromHead: true
                };
                logsFunc(params, logs, cb);
            }, (err) =>{
                if(err) {return next(err);}
                res.send(logs);
            });
        });
    },

    /**
     * Retry a job using existing parameters
     */
    retry (req, res, next) {
        // let jobId = req.params.jobId;
        // TODO - This is a stub for testing - need to resubmit using the same CRN job data but a new AWS Batch job
        let error = new Error('Retry is not yet supported.');
        error.http_code = 409;
        return next(error);
    }

};

export default handlers;
<|endoftext|>


<commit_before>// dependencies ------------------------------------------------------------

import aws           from '../libs/aws';

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        aws.batch.sdk.describeJobDefinitions({}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                let definitions = {};
                for (let definition of data.jobDefinitions) {
                    if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                        definitions[definition.jobDefinitionName] = {};
                    }
                    definitions[definition.jobDefinitionName][definition.revision] = definition;
                }

                res.send(definitions);
            }
        });
    },

    /**
     * Submit Job
     */
    submitJob(req, res) {
        let job = req.body;

        const batchJobParams = {
            jobDefinition: job.jobDefinition,
            jobName:       job.jobName,
            parameters:    job.parameters
        };
        batchJobParams.jobQueue = 'bids-queue';

        aws.batch.sdk.submitJob(batchJobParams, (err, data) => {
            res.send(data);
        });
    }

};

export default handlers;<commit_msg>Spacing change<commit_after>// dependencies ------------------------------------------------------------

import aws from '../libs/aws';

// handlers ----------------------------------------------------------------

/**
 * Jobs
 *
 * Handlers for job actions.
 */
let handlers = {

    /**
     * Describe Job Definitions
     */
    describeJobDefinitions(req, res, next) {
        aws.batch.sdk.describeJobDefinitions({}, (err, data) => {
            if (err) {
                return next(err);
            } else {
                let definitions = {};
                for (let definition of data.jobDefinitions) {
                    if (!definitions.hasOwnProperty(definition.jobDefinitionName)) {
                        definitions[definition.jobDefinitionName] = {};
                    }
                    definitions[definition.jobDefinitionName][definition.revision] = definition;
                }

                res.send(definitions);
            }
        });
    },

    /**
     * Submit Job
     */
    submitJob(req, res) {
        let job = req.body;

        const batchJobParams = {
            jobDefinition: job.jobDefinition,
            jobName:       job.jobName,
            parameters:    job.parameters
        };
        batchJobParams.jobQueue = 'bids-queue';

        aws.batch.sdk.submitJob(batchJobParams, (err, data) => {
            res.send(data);
        });
    }

};

export default handlers;<|endoftext|>


<commit_before>$( document ).ready(function() {

  function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) == (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  var csrftoken = getCookie('csrftoken');

  /*
  * Crawl Dashboard interactions
  */

  var buttons = {
    play: "playButton",
    stop: "stopButton",
    restart: "restartButton",
    kill: "forceStopButton",
    images: "dumpImages",
    cca: "common-crawl-dump",
    rounds: "rounds",
    log: "getCrawlLog",
    seeds: "getInitialSeeds",
  }

  /*
  * This object contains information on which buttons should be enabled or
  * disabled based on the status of the crawl.
  */

  var statuses = {
    "NOT STARTED": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "log",
      ],
      "enabled": [
        "play",
        "rounds",
        "seeds",
      ],
    },
    "STARTING": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "play",
        "rounds",
      ],
      "enabled": [
        "log",
        "seeds",
      ],
    },
    "STARTED": {
      "disabled": [
        "play",
        "restart",
        "images",
        "cca",
        "rounds",
      ],
      "enabled": [
        "stop",
        "kill",
        "log",
        "seeds",
      ],
    },
    "SUCCESS": {
      "disabled": [
        "play",
        "stop",
        "kill",
      ],
      "enabled": [
        "restart",
        "images",
        "cca",
        "rounds",
        "log",
        "seeds",
      ],
    },
    "FAILURE": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "play",
        "rounds",
      ],
      "enabled": [
        "log",
        "seeds",
      ],
    },
    "FORCE STOPPED": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "play",
        "rounds",
      ],
      "enabled": [
        "log",
        "seeds",
      ],
    },
  }

  function disable(element){
    document.getElementById(element).disabled=true;
  }

  function enable(element){
    document.getElementById(element).disabled=false;
  }

  function disableList(elementList){
    for(var i=0; i<elementList.length; i++){
      disable(buttons[elementList[i]]);
    }
  }

  function enableList(elementList){
    for(var i=0; i<elementList.length; i++){
      enable(buttons[elementList[i]]);
    }
  }

  function statusCall(){
    return $.ajax({
      type: "POST",
      data: {"action": "status"},
      success: function(response){
        $( '#status' ).text(response.status);
        $( '#roundsLeft' ).text(response.rounds_left);
        $( '#stats-pages' ).text(response.pages_crawled);
        if ('harvest_rate' in response) {
          $( '#stats-harvest' ).text(response.harvest_rate);
          if (response.harvest_rate > 0) {
            $('#getSeeds').attr("disabled", false);
          }
        }
        disableList(statuses[response.status]["disabled"]);
        enableList(statuses[response.status]["enabled"]);
      }
    });
  }

  statusCall();

  setInterval(function(){
    statusCall();
  }, 5000);

  $( document ).ready(function() {
    $('[data-toggle="tooltip"]').tooltip();
  });

  $("#dumpImages").attr("disabled", true);

  $('#common-crawl-dump').on('click', function() {
     $( '#status' ).text( "DUMPING" );
     this.disabled = true;
     $('#playButton').attr("disabled", true);
     $('#stopButton').attr("disabled", true);
     $('#common-crawl-dump').attr("disabled", false);

    $("#nutchButtons").append('')
    $.ajax({
        type: "POST",
        data: {"action": "ccadump"},
        success: function(response) {
          sweetAlert("Success", "Crawled data has been successfully dumped in CCA format!", "success");
          $("#imageSpinner").remove()
          $('#playButton').attr("disabled", false);
        },
        failure: function() {
          sweetAlert("Error", "Dump in CCA format has failed.", "error");
          $("#imageSpinner").remove()
        }
      });
  });

  $('#playButton').on('click', function() {

    $( '#status' ).text( "STARTING" );
    this.disabled = true;
    $('#stopButton').attr("disabled", false);
    $('#rounds').attr("disabled", true);

    val = $("#rounds")? $("#rounds").val() : 0,

    $.ajax({
      type: "POST",
      data: {
        "action": "start",
        "rounds": val,
      },
      success: function(response) {
        $('#getCrawlLog').attr("disabled", false);
        $('#forceStopButton').attr("disabled", false);
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not start crawl)" );
      }
    });
  });

  $('#stopButton').on('click', function() {

    $( '#status' ).text( "STOPPING" );
    this.disabled = true;

    $.ajax({
      type: "POST",
      data: {
        "action": "stop",
      },
      success: function(response) {
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not stop crawl)" );
      }
    });
  });

  function forceStop(){
    $( '#status' ).text( "STOPPING" );
    this.disabled = true;

    $.ajax({
      type: "POST",
      data: {
        "action": "force_stop",
      },
      success: function(response) {
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not stop crawl)" );
      }
    });
    return "success"
  }

  $('#forceStopButton').on('click', function() {
    swal({
      title: "Are you sure?",
      text: "This will corrupt all crawl data, and you will be unable to restart the crawl.",
      type: "warning",
      showCancelButton: true,
      confirmButtonColor: '#DD6B55',
      confirmButtonText: 'Yes, stop it!',
      cancelButtonText: "No, cancel!",
      closeOnConfirm: false,
      closeOnCancel: false
    },
    function(isConfirm){
      if (isConfirm){
        forceStop();
        swal("Stopped", "Your crawl has been stopped.", "success");
      } else {
        swal("Cancelled", "You cancelled the force-stop process", "error");
      }
    })
  })

  $('#restartButton').on('click', function() {

    $( '#status' ).text( "RESTARTING" );
    this.disabled = true;

    val = $("#rounds")? $("#rounds").val() : 0,

    $.ajax({
      type: "POST",
      data: {
        "action": "start",
        "rounds": val,
      },
      success: function(response) {
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not restart crawl)" );
      }
    });
  });

  $("#gotoSolr").on('click', function(){
    solr_url = "http://" + window.location.hostname + ":8983/solr/#"
      window.open(solr_url, '_blank');
  });

  $("#dumpImages").on('click', function(){
    $("#nutchButtons").append('')
    $.ajax({
      type: "POST",
      data: {"action": "dump"},
      success: function(response) {
        sweetAlert("Success", "Images have been successfully dumped!", "success");
        $("#imageSpinner").remove()
      },
      failure: function() {
        sweetAlert("Error", "Image dump has failed.", "error");
        $("#imageSpinner").remove()
      }
    });
  });

});
<commit_msg>Generalized enable/disable.<commit_after>$( document ).ready(function() {

  function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) == (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  var csrftoken = getCookie('csrftoken');

  /*
  * Crawl Dashboard interactions
  */

  var buttons = {
    play: "playButton",
    stop: "stopButton",
    restart: "restartButton",
    kill: "forceStopButton",
    images: "dumpImages",
    cca: "common-crawl-dump",
    rounds: "rounds",
    log: "getCrawlLog",
    seeds: "getInitialSeeds",
  }

  /*
  * This object contains information on which buttons should be enabled or
  * disabled based on the status of the crawl.
  */

  var statuses = {
    "NOT STARTED": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "log",
      ],
      "enabled": [
        "play",
        "rounds",
        "seeds",
      ],
    },
    "STARTING": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "play",
        "rounds",
      ],
      "enabled": [
        "log",
        "seeds",
      ],
    },
    "STARTED": {
      "disabled": [
        "play",
        "restart",
        "images",
        "cca",
        "rounds",
      ],
      "enabled": [
        "stop",
        "kill",
        "log",
        "seeds",
      ],
    },
    "SUCCESS": {
      "disabled": [
        "play",
        "stop",
        "kill",
      ],
      "enabled": [
        "restart",
        "images",
        "cca",
        "rounds",
        "log",
        "seeds",
      ],
    },
    "FAILURE": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "play",
        "rounds",
      ],
      "enabled": [
        "log",
        "seeds",
      ],
    },
    "FORCE STOPPED": {
      "disabled": [
        "stop",
        "restart",
        "kill",
        "images",
        "cca",
        "play",
        "rounds",
      ],
      "enabled": [
        "log",
        "seeds",
      ],
    },
  }

  function onOff(element, state){
    document.getElementById(element).disabled=state;
  }

  function onOffGroup(elementArray, itemsObject, state){
    for(var i=0; i<elementArray.length; i++){
      onOff(itemsObject[elementArray[i]], state);
    }
  }

  function statusCall(){
    return $.ajax({
      type: "POST",
      data: {"action": "status"},
      success: function(response){
        $( '#status' ).text(response.status);
        $( '#roundsLeft' ).text(response.rounds_left);
        $( '#stats-pages' ).text(response.pages_crawled);
        if ('harvest_rate' in response) {
          $( '#stats-harvest' ).text(response.harvest_rate);
          if (response.harvest_rate > 0) {
            $('#getSeeds').attr("disabled", false);
          }
        }
        onOffGroup(statuses[response.status]["disabled"], buttons, true);
        onOffGroup(statuses[response.status]["enabled"], buttons, false);
      }
    });
  }

  statusCall();

  setInterval(function(){
    statusCall();
  }, 5000);

  $( document ).ready(function() {
    $('[data-toggle="tooltip"]').tooltip();
  });

  $("#dumpImages").attr("disabled", true);

  $('#common-crawl-dump').on('click', function() {
     $( '#status' ).text( "DUMPING" );
     this.disabled = true;
     $('#playButton').attr("disabled", true);
     $('#stopButton').attr("disabled", true);
     $('#common-crawl-dump').attr("disabled", false);

    $("#nutchButtons").append('')
    $.ajax({
        type: "POST",
        data: {"action": "ccadump"},
        success: function(response) {
          sweetAlert("Success", "Crawled data has been successfully dumped in CCA format!", "success");
          $("#imageSpinner").remove()
          $('#playButton').attr("disabled", false);
        },
        failure: function() {
          sweetAlert("Error", "Dump in CCA format has failed.", "error");
          $("#imageSpinner").remove()
        }
      });
  });

  $('#playButton').on('click', function() {

    $( '#status' ).text( "STARTING" );
    this.disabled = true;
    $('#stopButton').attr("disabled", false);
    $('#rounds').attr("disabled", true);

    val = $("#rounds")? $("#rounds").val() : 0,

    $.ajax({
      type: "POST",
      data: {
        "action": "start",
        "rounds": val,
      },
      success: function(response) {
        $('#getCrawlLog').attr("disabled", false);
        $('#forceStopButton').attr("disabled", false);
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not start crawl)" );
      }
    });
  });

  $('#stopButton').on('click', function() {

    $( '#status' ).text( "STOPPING" );
    this.disabled = true;

    $.ajax({
      type: "POST",
      data: {
        "action": "stop",
      },
      success: function(response) {
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not stop crawl)" );
      }
    });
  });

  function forceStop(){
    $( '#status' ).text( "STOPPING" );
    this.disabled = true;

    $.ajax({
      type: "POST",
      data: {
        "action": "force_stop",
      },
      success: function(response) {
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not stop crawl)" );
      }
    });
    return "success"
  }

  $('#forceStopButton').on('click', function() {
    swal({
      title: "Are you sure?",
      text: "This will corrupt all crawl data, and you will be unable to restart the crawl.",
      type: "warning",
      showCancelButton: true,
      confirmButtonColor: '#DD6B55',
      confirmButtonText: 'Yes, stop it!',
      cancelButtonText: "No, cancel!",
      closeOnConfirm: false,
      closeOnCancel: false
    },
    function(isConfirm){
      if (isConfirm){
        forceStop();
        swal("Stopped", "Your crawl has been stopped.", "success");
      } else {
        swal("Cancelled", "You cancelled the force-stop process", "error");
      }
    })
  })

  $('#restartButton').on('click', function() {

    $( '#status' ).text( "RESTARTING" );
    this.disabled = true;

    val = $("#rounds")? $("#rounds").val() : 0,

    $.ajax({
      type: "POST",
      data: {
        "action": "start",
        "rounds": val,
      },
      success: function(response) {
        console.log(response);
        if (response.status != "error") $( '#status' ).text(response.status);
        else console.log(response)},
      failure: function() {
        $( '#status' ).text( "Error (could not restart crawl)" );
      }
    });
  });

  $("#gotoSolr").on('click', function(){
    solr_url = "http://" + window.location.hostname + ":8983/solr/#"
      window.open(solr_url, '_blank');
  });

  $("#dumpImages").on('click', function(){
    $("#nutchButtons").append('')
    $.ajax({
      type: "POST",
      data: {"action": "dump"},
      success: function(response) {
        sweetAlert("Success", "Images have been successfully dumped!", "success");
        $("#imageSpinner").remove()
      },
      failure: function() {
        sweetAlert("Error", "Image dump has failed.", "error");
        $("#imageSpinner").remove()
      }
    });
  });

});
<|endoftext|>


<commit_before>var Q = require('q'),
    fsUtil = require('heinzelmannchen-fs'),
    path = require('path'),
    _ = require('underscore'),
    me = module.exports,
    defaultInterpolate = _.templateSettings.interpolate,
    defaultEvaluate = _.templateSettings.evaluate,
    data;

_.str = require('underscore.string');
_.mixin(_.str.exports());
_.str.include('Underscore.string', 'string');

me.template = function(template, dataOrFile) {
    return fsUtil.readFileOrReturnData(dataOrFile)
        .then(function onDataRead(content) {
            data = parseJson(content);
            return fsUtil.readFileOrReturnData(template);
        })
        .then(function onTemplateRead(template) {
            return me.process(template, data);
        });
};

me.templateFromNpm = function(template, dataOrFile) {
    return exists(template)
        .then(function(packagePath) {
            return me.template(packagePath, dataOrFile);
        });
};

function exists(templateModule) {
    var q = Q.defer(),
        packagePath;
    try {
        packagePath = require.resolve(templateModule);
        q.resolve(packagePath);
    } catch (error) {
        q.reject();
    }
    return q.promise;
}

me.process = function(templateString, data) {
    var templateData = data || {};

    return Q.fcall(_.template, templateString, templateData);
};

me.write = function(file, content, options) {
    var q = Q.defer(),
        filePathAndName;
    options = options || {};

    me.process(file, options.data)
        .then(function onPathVariablesProcessed(processedPath) {
            var filePath = path.dirname(processedPath);
            filePathAndName = processedPath;

            if (options.dryRun) {
                q.resolve(filePathAndName);
            } else {
                return fsUtil.ensurePathExists(filePath, options.force);
            }
        })
        .then(function onPathExists() {
            if (options.override)  {
                return fsUtil.removeFile(filePathAndName);
            } else {
                return;
            }
        })
        .then(function() {
            return fsUtil.createFile(filePathAndName, content);
        })
        .then(function onFileCreated() {
            q.resolve(filePathAndName);
        })
        .fail(onFail(q));

    return q.promise;
};

me.loadCustomScript = function(fileName) {
    global._custom = require(fileName);
};

me.setDelimiter = function(startDelimiter, endDelimiter) {
    var evaluate, interpolate;
    endDelimiter = endDelimiter || startDelimiter;

    evaluate = new RegExp(startDelimiter + '(.+?)' + endDelimiter, 'g');
    interpolate = new RegExp(startDelimiter + '=(.+?)' + endDelimiter, 'g');
    setTemplateSettings(evaluate, interpolate);
};

me.restoreDelimiter = function() {
    setTemplateSettings(defaultEvaluate, defaultInterpolate);
};

me.getData = function() {
    return data;
};

function setTemplateSettings(evaluate, interpolate) {
    _.templateSettings = {
        evaluate: evaluate,
        interpolate: interpolate
    };
}

function parseJson(content) {
    var data;
    if (typeof content === 'string') {
        data = JSON.parse(content);
    } else {
        data = content;
    }
    return data;
}

function onFail(q) {
    return function(error) {
        q.reject(error);
    };
}
<commit_msg>override should only try to delete an existing file and not break<commit_after>var Q = require('q'),
    fsUtil = require('heinzelmannchen-fs'),
    path = require('path'),
    _ = require('underscore'),
    me = module.exports,
    defaultInterpolate = _.templateSettings.interpolate,
    defaultEvaluate = _.templateSettings.evaluate,
    data;

_.str = require('underscore.string');
_.mixin(_.str.exports());
_.str.include('Underscore.string', 'string');

me.template = function(template, dataOrFile) {
    return fsUtil.readFileOrReturnData(dataOrFile)
        .then(function onDataRead(content) {
            data = parseJson(content);
            return fsUtil.readFileOrReturnData(template);
        })
        .then(function onTemplateRead(template) {
            return me.process(template, data);
        });
};

me.templateFromNpm = function(template, dataOrFile) {
    return exists(template)
        .then(function(packagePath) {
            return me.template(packagePath, dataOrFile);
        });
};

function exists(templateModule) {
    var q = Q.defer(),
        packagePath;
    try {
        packagePath = require.resolve(templateModule);
        q.resolve(packagePath);
    } catch (error) {
        q.reject();
    }
    return q.promise;
}

me.process = function(templateString, data) {
    var templateData = data || {};

    return Q.fcall(_.template, templateString, templateData);
};

me.write = function(file, content, options) {
    var q = Q.defer(),
        filePathAndName;
    options = options || {};

    me.process(file, options.data)
        .then(function onPathVariablesProcessed(processedPath) {
            var filePath = path.dirname(processedPath);
            filePathAndName = processedPath;

            if (options.dryRun) {
                q.resolve(filePathAndName);
            } else {
                return fsUtil.ensurePathExists(filePath, options.force);
            }
        })
        .then(function onPathExists() {
            if (options.override)  {
                try {
                    fsUtil.removeFile(filePathAndName);
                } catch (error) {}
            }
            return;
        })
        .then(function() {
            return fsUtil.createFile(filePathAndName, content);
        })
        .then(function onFileCreated() {
            q.resolve(filePathAndName);
        })
        .fail(onFail(q));

    return q.promise;
};

me.loadCustomScript = function(fileName) {
    global._custom = require(fileName);
};

me.setDelimiter = function(startDelimiter, endDelimiter) {
    var evaluate, interpolate;
    endDelimiter = endDelimiter || startDelimiter;

    evaluate = new RegExp(startDelimiter + '(.+?)' + endDelimiter, 'g');
    interpolate = new RegExp(startDelimiter + '=(.+?)' + endDelimiter, 'g');
    setTemplateSettings(evaluate, interpolate);
};

me.restoreDelimiter = function() {
    setTemplateSettings(defaultEvaluate, defaultInterpolate);
};

me.getData = function() {
    return data;
};

function setTemplateSettings(evaluate, interpolate) {
    _.templateSettings = {
        evaluate: evaluate,
        interpolate: interpolate
    };
}

function parseJson(content) {
    var data;
    if (typeof content === 'string') {
        data = JSON.parse(content);
    } else {
        data = content;
    }
    return data;
}

function onFail(q) {
    return function(error) {
        q.reject(error);
    };
}
<|endoftext|>