<commit_before>// The Help tab.
dc.ui.Help = dc.View.extend({

  PAGES : ['', 'searching', 'accounts', 'uploading', 'notes', 'publishing', 'collaboration', 'troubleshooting'],

  callbacks : {
    '.contact_us.click': 'openContactDialog'
  },

  constructor : function() {
    this.el = $('#help')[0];
    this.currentPage = null;
  },

  render : function() {
    dc.history.register(/^#help\//, _.bind(this.openPage, this));
    dc.app.navigation.bind('help', _.bind(this.openHelpTab, this));
    this._toolbar = $('#help_toolbar');
    this._toolbar.prepend(this._createHelpMenu().render().el);
    this.setCallbacks();
    return this;
  },

  openContactDialog : function() {
    dc.ui.Dialog.prompt('Contact Us', '', function(message) {
      $.post('/help/contact_us', {message : message}, function() {
        dc.ui.notifier.show({mode : 'info', text : 'Your message was sent successfully.'});
      });
      return true;
    }, {information : "Send a message and we'll email you back.", saveText : 'Send'});
  },

  openPage : function(page) {
    var noChange = !_.include(this.PAGES, page) || (page === this.currentPage);
    this.currentPage = page;
    this.saveHistory();
    if (noChange) return dc.app.navigation.open('help');
    $.get("/help/" + (page || 'index') + '.html', function(resp) {
      $('#help_content').html(resp);
    });
    dc.app.navigation.open('help');
  },

  openHelpTab : function() {
    this.currentPage ? this.saveHistory() : this.openPage('');
  },

  saveHistory : function() {
    dc.history.save('help/' + (this.currentPage || ''));
  },

  _createHelpMenu : function() {
    return this._helpMenu = new dc.ui.Menu({
      id      : 'how_to_menu',
      label   : 'Guides & How To\'s',
      items   : [
        {onClick : _.bind(this.openPage, this, 'searching'),      title : 'Searching Documents'},
        {onClick : _.bind(this.openPage, this, 'accounts'),       title : 'Adding Accounts'},
        {onClick : _.bind(this.openPage, this, 'uploading'),      title : 'Uploading Documents'},
        {onClick : _.bind(this.openPage, this, 'notes'),          title : 'Editing Notes and Sections'},
        {onClick : _.bind(this.openPage, this, 'publishing'),     title : 'Publishing & Embedding'},
        {onClick : _.bind(this.openPage, this, 'collaboration'),  title : 'Collaboration'},
        {onClick : _.bind(this.openPage, this, 'troubleshooting'),title : 'Troubleshooting Failed Uploads'}
      ]
    });
  }

});<commit_msg>adding a route for the base #help url<commit_after>// The Help tab.
dc.ui.Help = dc.View.extend({

  PAGES : ['', 'searching', 'accounts', 'uploading', 'notes', 'publishing', 'collaboration', 'troubleshooting'],

  callbacks : {
    '.contact_us.click': 'openContactDialog'
  },

  constructor : function() {
    this.el = $('#help')[0];
    this.currentPage = null;
  },

  render : function() {
    dc.history.register(/^#help\//, _.bind(this.openPage, this));
    dc.history.register(/^#help$/, _.bind(this.openPage, this, ''));
    dc.app.navigation.bind('help', _.bind(this.openHelpTab, this));
    this._toolbar = $('#help_toolbar');
    this._toolbar.prepend(this._createHelpMenu().render().el);
    this.setCallbacks();
    return this;
  },

  openContactDialog : function() {
    dc.ui.Dialog.prompt('Contact Us', '', function(message) {
      $.post('/help/contact_us', {message : message}, function() {
        dc.ui.notifier.show({mode : 'info', text : 'Your message was sent successfully.'});
      });
      return true;
    }, {information : "Send a message and we'll email you back.", saveText : 'Send'});
  },

  openPage : function(page) {
    var noChange = !_.include(this.PAGES, page) || (page === this.currentPage);
    this.currentPage = page;
    this.saveHistory();
    if (noChange) return dc.app.navigation.open('help');
    $.get("/help/" + (page || 'index') + '.html', function(resp) {
      $('#help_content').html(resp);
    });
    dc.app.navigation.open('help');
  },

  openHelpTab : function() {
    this.currentPage ? this.saveHistory() : this.openPage('');
  },

  saveHistory : function() {
    dc.history.save('help/' + (this.currentPage || ''));
  },

  _createHelpMenu : function() {
    return this._helpMenu = new dc.ui.Menu({
      id      : 'how_to_menu',
      label   : 'Guides & How To\'s',
      items   : [
        {onClick : _.bind(this.openPage, this, 'searching'),      title : 'Searching Documents'},
        {onClick : _.bind(this.openPage, this, 'accounts'),       title : 'Adding Accounts'},
        {onClick : _.bind(this.openPage, this, 'uploading'),      title : 'Uploading Documents'},
        {onClick : _.bind(this.openPage, this, 'notes'),          title : 'Editing Notes and Sections'},
        {onClick : _.bind(this.openPage, this, 'publishing'),     title : 'Publishing & Embedding'},
        {onClick : _.bind(this.openPage, this, 'collaboration'),  title : 'Collaboration'},
        {onClick : _.bind(this.openPage, this, 'troubleshooting'),title : 'Troubleshooting Failed Uploads'}
      ]
    });
  }

});<|endoftext|>


<commit_before>#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

var args = process.argv.slice(2);

if (args.length === 0) {
    console.log("Usage: technician.js <joint>.* <joint>.*");
    process.exit(1);
}

amqp.connect('amqp://localhost', function (err, conn) {
    conn.createChannel(function (err, ch) {
        var exchange = 'medical_exam';
        ch.assertExchange(exchange, 'topic', { durable: false });

        args.forEach(function (key) {
            var data = key.split('.');
            ch.assertQueue(data[0]);
            ch.prefetch (1);
            console.log(' [*] Waiting for logs. To exit press CTRL+C');
            ch.bindQueue(data[0], exchange, data[0]);

            ch.consume(data[0], function (msg) {
                console.log(` [RECEIVED] '${msg.content.toString()}'`);
            }, { noAck: true });
        });
    });
});<commit_msg>Initial Commit<commit_after>#!/usr/bin/env node

var amqp = require('amqplib/callback_api');

var args = process.argv.slice(2);

if (args.length === 0) {
    console.log("Usage: node technician <joint>.* <joint>.*");
    process.exit(1);
}

amqp.connect('amqp://localhost', function (err, conn) {
    conn.createChannel(function (err, ch) {
        var exchange = 'medical_exam';
        ch.assertExchange(exchange, 'topic', { durable: false });

        args.forEach(function (key) {
            var data = key.split('.');
            ch.assertQueue(data[0]);
            ch.prefetch (1);
            console.log(' [*] Waiting for logs. To exit press CTRL+C');
            ch.bindQueue(data[0], exchange, data[0]);

            ch.consume(data[0], function (msg) {
                console.log(` [RECEIVED] '${msg.content.toString()}'`);
            }, { noAck: true });
        });
    });
});<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const K8s = require('./k8s');
const parseError = require('error_parser');
const Promise = require('bluebird');
const k8sState = require('./k8sState');
// const Queue = require('queue');

/*
 Execution Life Cycle for _status
 pending -> scheduling -> running -> [ paused -> running ] -> [ stopped | completed ]
 Exceptions
 rejected - when a job is rejected prior to scheduling
 failed - when there is an error while the job is running
 aborted - when a job was running at the point when the cluster shutsdown
 */

// FIXME: I want to write tests for this but I don't know how to deal with the
// messaging stuff

module.exports = function kubernetesClusterBackend(context, messaging, executionService) {
    const events = context.apis.foundation.getSystemEvents();
    const logger = context.apis.foundation.makeLogger({ module: 'kubernetes_cluster_service' });
    const slicerAllocationAttempts = context.sysconfig.teraslice.slicer_allocation_attempts;
    let clusterState = {};

    // FIXME: clusterStats stores aggregated stats about what gets processed,
    // when a slice on any job fails, it gets recorded here.  What should this
    // do in the k8s case?
    const clusterStats = {
        slicer: {
            processed: 0,
            failed: 0,
            queued: 0,
            job_duration: 0,
            workers_joined: 0,
            workers_disconnected: 0,
            workers_reconnected: 0
        }
    };
    const k8s = new K8s(logger);

    // Under the hood this makes a node join a room so messaging is possible
    messaging.register({
        event: 'node:online',
        identifier: 'node_id',
        callback: (data, nodeId) => {
            logger.info(`node ${nodeId} has connected`);
        }
    });

    // Periodically update cluster state, update period controlled by:
    //  context.sysconfig.teraslice.node_state_interval
    setInterval(() => {
        logger.trace('cluster_master requesting cluster state update.');
        _getClusterState();
    }, context.sysconfig.teraslice.node_state_interval);

    /**
     * getClusterState returns a copy of the clusterState object
     * @return {Object} a copy of the clusterState object
     */
    function getClusterState() {
        return _.cloneDeep(clusterState);
    }

    // FIXME: add a catch here??
    /**
     * Creates clusterState by iterating over all k8s pods with the label
     * `app=teraslice`
     * @constructor
     * @return      {Promise} [description]
     */
    function _getClusterState() {
        // we build clusterState fresh on every call, so we empty it out here
        // FIXME: sometimes the ts-top output is blank because this is null
        //        briefly.
        clusterState = {};

        return k8s.list('app=teraslice', 'pods')
            .then((k8sPods) => { k8sState.gen(k8sPods, clusterState); });
    }

    function getClusterStats() {
        return clusterStats;
    }

    // FIXME: copied from native
    function _iterateState(cb) {
        return _.chain(clusterState)
            .filter(node => node.state === 'connected') // FIXME: remove
            .map((node) => {
                const workers = node.active.filter(cb);

                return workers.map((worker) => {
                    worker.node_id = worker.pod_ip;
                    worker.hostname = node.hostname;
                    return worker;
                });
            })
            .flatten()
            .value();
    }

    // FIXME: copied from native
    // finds workers of type execution_controller that match ex_id
    function findSlicersByExecutionID(exIdDict) {
        return _iterateState(worker => worker.assignment === 'execution_controller' && exIdDict[worker.ex_id]);
    }

    // FIXME: copied from native, there are minor differences, but I get the
    // feeling that there shouldn't be two copies of this.  Actually, it's
    // changed twice since I copied it.
    //
    // returns something like:
    // [{
    //     "ex_id": "ee8af472-1a09-4e00-ac0a-4d90317e56cc",
    //     "job_id": "4bc0c260-8519-467f-a66c-f0e024024084",
    //     "name": "gen-fifty-thousand",
    //     "workers_available": 0,
    //     "workers_active": 1,
    //     "workers_joined": 1,
    //     "workers_reconnected": 0,
    //     "workers_disconnected": 0,
    //     "failed": 0,
    //     "subslices": 0,
    //     "queued": 1,
    //     "slice_range_expansion": 0,
    //     "processed": 0,
    //     "slicers": 1,
    //     "subslice_by_key": 0,
    //     "started": "2018-04-23T15:35:43.292-07:00"
    // }]
    function getSlicerStats(exIds, specificId) {
        return new Promise(((resolve, reject) => {
            let timer;
            const nodeQueries = [];
            // NO CLUE what this is doing
            const exIdDict = exIds.reduce((prev, curr) => {
                prev[curr] = curr;
                return prev;
            }, {});
            const list = findSlicersByExecutionID(exIdDict);
            const numberOfCalls = list.length;

            if (numberOfCalls === 0) {
                if (specificId) {
                    reject({ message: `Could not find active slicer for ex_id: ${specificId}`, code: 404 });
                } else {
                    // for the general slicer stats query, just return a empty array
                    resolve([]);
                }
            }
            // loops over list of execution_controllers and messages them,
            // asking for their stats info
            _.each(list, (slicer) => {
                const msg = { ex_id: slicer.ex_id };
                nodeQueries.push(messaging.send({
                    to: 'execution_controller',
                    address: slicer.node_id,
                    message: 'cluster:slicer:analytics',
                    payload: msg,
                    response: true
                }));
            });

            function formatResponse(msg) {
                const payload = msg.payload;
                const identifiers = { ex_id: msg.ex_id, job_id: msg.job_id, name: payload.name };
                return Object.assign(identifiers, payload.stats);
            }

            Promise.all(nodeQueries)
                .then((results) => {
                    clearTimeout(timer);
                    resolve(results.map(formatResponse));
                })
                .catch(err => reject({ message: parseError(err), code: 500 }));

            timer = setTimeout(() => {
                reject({ message: 'Timeout has occurred for query', code: 500 });
            }, context.sysconfig.teraslice.action_timeout);
        }));
    }

    /**
     * Return value indicates whether the cluster has enough workers to start
     * an execution.  It must be able to allocate a slicer and at least one
     * worker.
     * @return {boolean} Ok to create job?
     */
    function readyForAllocation() {
        // return _availableWorkers() >= 2;
        // FIXME: define what this means in k8s, for now just return true
        return true;
    }

    /**
     * Creates k8s Service and Deployment for the Teraslice Execution Controller
     * (formerly slicer).  This currently works by creating a service with a
     * hostname that contains the exId in it listening on a well known port.
     * The hostname and port are used later by the workers to contact this
     * Execution Controller.
     * @param  {[type]} execution        [description]
     * @param  {String} recoverExecution ex_id of old execution to be recovered
     * @return {Promise}                 [description]
     */
    function allocateSlicer(execution, recoverExecution) {
        const slicerName = `teraslice-slicer-${execution.ex_id}`;
        const slicerService = require('./services/slicer.json');
        const slicerDeployment = require('./deployments/slicer.json');

        // FIXME: extract this hard coded port out somewhere sensible
        execution.slicer_port = 45680;
        execution.slicer_hostname = slicerName;
        if (recoverExecution) execution.recover_execution = recoverExecution;

        slicerService.metadata.name = slicerName;
        slicerService.metadata.labels.exId = execution.ex_id;
        slicerService.metadata.labels.jobId = execution.job_id;
        slicerService.spec.selector.exId = execution.ex_id;

        slicerDeployment.metadata.name = slicerName;
        slicerDeployment.metadata.labels.exId = execution.ex_id;
        slicerDeployment.metadata.labels.jobId = execution.job_id;
        slicerDeployment.spec.selector.matchLabels.exId = execution.ex_id;
        slicerDeployment.spec.template.metadata.labels.exId = execution.ex_id;
        slicerDeployment.spec.template.metadata.labels.jobId = execution.job_id;
        slicerDeployment.spec.template.spec.containers[0].name = slicerName;
        slicerDeployment.spec.template.spec.containers[0].env[1].value = JSON.stringify(execution);

        return k8s.post(slicerService, 'service')
            .then(result => logger.info(`k8s slicer service submitted: ${JSON.stringify(result)}`))
            .catch((err) => {
                const error = parseError(err);
                logger.error(`Error submitting k8s slicer service: ${error}`);
                return Promise.reject(error);
            })
            .then(() => k8s.post(slicerDeployment, 'deployment'))
            .then(result => logger.info(`k8s slicer deployment submitted: ${JSON.stringify(result)}`))
            .catch((err) => {
                const error = parseError(err);
                logger.error(`Error submitting k8s slicer deployment: ${error}`);
                return Promise.reject(error);
            });
    }

    /**
     * Creates k8s deployment that executes Teraslice workers for specified
     * Execution.
     * @param  {Object} execution  Object that contains information of Execution
     * @param  {number} numWorkers number of workers to allocate
     * @return {Promise}           [description]
     */
    function allocateWorkers(execution, numWorkers) {
        // FIXME: Verify that this gets a fresh worker.json on every call
        const workerDeployment = require('./deployments/worker.json');
        const workerName = `teraslice-worker-${execution.ex_id}`;

        workerDeployment.metadata.name = workerName;
        workerDeployment.metadata.labels.exId = execution.ex_id;
        workerDeployment.metadata.labels.jobId = execution.job_id;
        workerDeployment.spec.replicas = numWorkers;
        workerDeployment.spec.selector.matchLabels.exId = execution.ex_id;
        workerDeployment.spec.template.metadata.labels.exId = execution.ex_id;
        workerDeployment.spec.template.metadata.labels.jobId = execution.job_id;
        workerDeployment.spec.template.spec.containers[0].name = workerName;
        workerDeployment.spec.template.spec.containers[0].env[1].value = JSON.stringify(execution);

        return k8s.post(workerDeployment, 'deployment')
            .then(result => logger.info(`k8s worker deployment submitted: ${JSON.stringify(result)}`))
            .catch((err) => {
                const error = parseError(err);
                logger.error(`Error submitting k8s worker deployment: ${error}`);
                return Promise.reject(error);
            });
    }

    // FIXME: These functions should probably do something with the response
    // FIXME: I find is strange that the expected return value here is
    //        effectively the same as the function inputs
    async function addWorkers(executionContext, numWorkers) {
        const response = await k8s.scaleExecution(executionContext.ex_id, numWorkers, 'add');
        return { action: 'add', ex_id: executionContext.ex_id, workerNum: numWorkers };
    }

    // NOTE: This is passed exId instead of executionContext like addWorkers and
    // removeWorkers.  I don't know why, just dealing with it.
    async function removeWorkers(exId, numWorkers) {
        const response = await k8s.scaleExecution(exId, numWorkers, 'remove');
        return { action: 'remove', ex_id: exId, workerNum: numWorkers };
    }

    async function setWorkers(executionContext, numWorkers) {
        const response = await k8s.scaleExecution(executionContext.ex_id, numWorkers, 'set');
        return { action: 'set', ex_id: executionContext.ex_id, workerNum: numWorkers };
    }

    /**
     * Sends specified message to workers with exId.
     * TODO: In the kubernetes case it's a bit redundant to provide both a list
     * of workers and exId, since there is only one execution per worker node.
     * I should probably rethink this.
     * @param       {string} exId        Execution ID
     * @param       {string} messageData A 'cluster:execution:*' message
     * @param       {[type]} workers
     * @return      {Promise}
     */
    function _msgWorkersWithExId(exId, messageData, workers) {
        return Promise.map(workers, (worker) => {
            const sendingMsg = Object.assign({}, messageData, {
                to: 'execution_controller',
                address: worker.pod_ip,
                ex_id: exId,
                response: true
            });

            return messaging.send(sendingMsg);
        })
            .catch((err) => {
                const errMsg = parseError(err);
                logger.error('could not notify cluster', errMsg);
                return Promise.reject(errMsg);
            });
    }

    /**
     * Stops all workers for exId
     * @param  {string} exId    The execution ID of the Execution to stop
     * @param  {number} timeout Timeout for messaging the nodes in FIXME: units and meaning
     * @return {Promise}
     */
    async function stopExecution(exId, timeout) {
        const stopMessage = { message: 'cluster:execution:stop' };
        if (timeout) {
            stopMessage.timeout = timeout;
        }
        const workers = _iterateState(worker => worker.ex_id === exId);
        const msgResponse = await _msgWorkersWithExId(exId, stopMessage, workers);
        const deleteResponse = await k8s.deleteExecution(exId);
        return msgResponse;
    }

    function pauseExecution(exId) {
        const workers = _iterateState(worker => worker.assignment === 'execution_controller' && worker.ex_id === exId);
        return _msgWorkersWithExId(exId, { message: 'cluster:execution:pause' }, workers);
    }

    function resumeExecution(exId) {
        const workers = _iterateState(worker => worker.assignment === 'execution_controller' && worker.ex_id === exId);
        return _msgWorkersWithExId(exId, { message: 'cluster:execution:resume' }, workers);
    }

    const api = {
        getClusterState, // k8s
        getClusterStats,
        getSlicerStats,
        allocateWorkers, // k8s
        allocateSlicer, // k8s
        // shutdown,  FIXME: ??
        stopExecution,
        pauseExecution,
        resumeExecution,
        removeWorkers, // k8s
        addWorkers, // k8s
        setWorkers, // k8s
        readyForAllocation,
        // clusterAvailable FIXME: ??
    };

    function _initialize() {
        logger.info('Initializing');
        return k8s.init().then(() => Promise.resolve(api));
    }

    return _initialize();
};
<commit_msg>reduce the time clusterState is empty<commit_after>'use strict';

const _ = require('lodash');
const K8s = require('./k8s');
const parseError = require('error_parser');
const Promise = require('bluebird');
const k8sState = require('./k8sState');
// const Queue = require('queue');

/*
 Execution Life Cycle for _status
 pending -> scheduling -> running -> [ paused -> running ] -> [ stopped | completed ]
 Exceptions
 rejected - when a job is rejected prior to scheduling
 failed - when there is an error while the job is running
 aborted - when a job was running at the point when the cluster shutsdown
 */

// FIXME: I want to write tests for this but I don't know how to deal with the
// messaging stuff

module.exports = function kubernetesClusterBackend(context, messaging, executionService) {
    const events = context.apis.foundation.getSystemEvents();
    const logger = context.apis.foundation.makeLogger({ module: 'kubernetes_cluster_service' });
    const slicerAllocationAttempts = context.sysconfig.teraslice.slicer_allocation_attempts;
    let clusterState = {};

    // FIXME: clusterStats stores aggregated stats about what gets processed,
    // when a slice on any job fails, it gets recorded here.  What should this
    // do in the k8s case?
    const clusterStats = {
        slicer: {
            processed: 0,
            failed: 0,
            queued: 0,
            job_duration: 0,
            workers_joined: 0,
            workers_disconnected: 0,
            workers_reconnected: 0
        }
    };
    const k8s = new K8s(logger);

    // Under the hood this makes a node join a room so messaging is possible
    messaging.register({
        event: 'node:online',
        identifier: 'node_id',
        callback: (data, nodeId) => {
            logger.info(`node ${nodeId} has connected`);
        }
    });

    // Periodically update cluster state, update period controlled by:
    //  context.sysconfig.teraslice.node_state_interval
    setInterval(() => {
        logger.trace('cluster_master requesting cluster state update.');
        _getClusterState();
    }, context.sysconfig.teraslice.node_state_interval);

    /**
     * getClusterState returns a copy of the clusterState object
     * @return {Object} a copy of the clusterState object
     */
    function getClusterState() {
        return _.cloneDeep(clusterState);
    }

    // FIXME: add a catch here??
    /**
     * Creates clusterState by iterating over all k8s pods with the label
     * `app=teraslice`
     * @constructor
     * @return      {Promise} [description]
     */
    function _getClusterState() {
        return k8s.list('app=teraslice', 'pods')
            .then((k8sPods) => {
                // we build clusterState fresh on every call, so we empty it out
                // here
                clusterState = {};

                return k8sState.gen(k8sPods, clusterState);
            });
    }

    function getClusterStats() {
        return clusterStats;
    }

    // FIXME: copied from native
    function _iterateState(cb) {
        return _.chain(clusterState)
            .filter(node => node.state === 'connected') // FIXME: remove
            .map((node) => {
                const workers = node.active.filter(cb);

                return workers.map((worker) => {
                    worker.node_id = worker.pod_ip;
                    worker.hostname = node.hostname;
                    return worker;
                });
            })
            .flatten()
            .value();
    }

    // FIXME: copied from native
    // finds workers of type execution_controller that match ex_id
    function findSlicersByExecutionID(exIdDict) {
        return _iterateState(worker => worker.assignment === 'execution_controller' && exIdDict[worker.ex_id]);
    }

    // FIXME: copied from native, there are minor differences, but I get the
    // feeling that there shouldn't be two copies of this.  Actually, it's
    // changed twice since I copied it.
    //
    // returns something like:
    // [{
    //     "ex_id": "ee8af472-1a09-4e00-ac0a-4d90317e56cc",
    //     "job_id": "4bc0c260-8519-467f-a66c-f0e024024084",
    //     "name": "gen-fifty-thousand",
    //     "workers_available": 0,
    //     "workers_active": 1,
    //     "workers_joined": 1,
    //     "workers_reconnected": 0,
    //     "workers_disconnected": 0,
    //     "failed": 0,
    //     "subslices": 0,
    //     "queued": 1,
    //     "slice_range_expansion": 0,
    //     "processed": 0,
    //     "slicers": 1,
    //     "subslice_by_key": 0,
    //     "started": "2018-04-23T15:35:43.292-07:00"
    // }]
    function getSlicerStats(exIds, specificId) {
        return new Promise(((resolve, reject) => {
            let timer;
            const nodeQueries = [];
            // NO CLUE what this is doing
            const exIdDict = exIds.reduce((prev, curr) => {
                prev[curr] = curr;
                return prev;
            }, {});
            const list = findSlicersByExecutionID(exIdDict);
            const numberOfCalls = list.length;

            if (numberOfCalls === 0) {
                if (specificId) {
                    reject({ message: `Could not find active slicer for ex_id: ${specificId}`, code: 404 });
                } else {
                    // for the general slicer stats query, just return a empty array
                    resolve([]);
                }
            }
            // loops over list of execution_controllers and messages them,
            // asking for their stats info
            _.each(list, (slicer) => {
                const msg = { ex_id: slicer.ex_id };
                nodeQueries.push(messaging.send({
                    to: 'execution_controller',
                    address: slicer.node_id,
                    message: 'cluster:slicer:analytics',
                    payload: msg,
                    response: true
                }));
            });

            function formatResponse(msg) {
                const payload = msg.payload;
                const identifiers = { ex_id: msg.ex_id, job_id: msg.job_id, name: payload.name };
                return Object.assign(identifiers, payload.stats);
            }

            Promise.all(nodeQueries)
                .then((results) => {
                    clearTimeout(timer);
                    resolve(results.map(formatResponse));
                })
                .catch(err => reject({ message: parseError(err), code: 500 }));

            timer = setTimeout(() => {
                reject({ message: 'Timeout has occurred for query', code: 500 });
            }, context.sysconfig.teraslice.action_timeout);
        }));
    }

    /**
     * Return value indicates whether the cluster has enough workers to start
     * an execution.  It must be able to allocate a slicer and at least one
     * worker.
     * @return {boolean} Ok to create job?
     */
    function readyForAllocation() {
        // return _availableWorkers() >= 2;
        // FIXME: define what this means in k8s, for now just return true
        return true;
    }

    /**
     * Creates k8s Service and Deployment for the Teraslice Execution Controller
     * (formerly slicer).  This currently works by creating a service with a
     * hostname that contains the exId in it listening on a well known port.
     * The hostname and port are used later by the workers to contact this
     * Execution Controller.
     * @param  {[type]} execution        [description]
     * @param  {String} recoverExecution ex_id of old execution to be recovered
     * @return {Promise}                 [description]
     */
    function allocateSlicer(execution, recoverExecution) {
        const slicerName = `teraslice-slicer-${execution.ex_id}`;
        const slicerService = require('./services/slicer.json');
        const slicerDeployment = require('./deployments/slicer.json');

        // FIXME: extract this hard coded port out somewhere sensible
        execution.slicer_port = 45680;
        execution.slicer_hostname = slicerName;
        if (recoverExecution) execution.recover_execution = recoverExecution;

        slicerService.metadata.name = slicerName;
        slicerService.metadata.labels.exId = execution.ex_id;
        slicerService.metadata.labels.jobId = execution.job_id;
        slicerService.spec.selector.exId = execution.ex_id;

        slicerDeployment.metadata.name = slicerName;
        slicerDeployment.metadata.labels.exId = execution.ex_id;
        slicerDeployment.metadata.labels.jobId = execution.job_id;
        slicerDeployment.spec.selector.matchLabels.exId = execution.ex_id;
        slicerDeployment.spec.template.metadata.labels.exId = execution.ex_id;
        slicerDeployment.spec.template.metadata.labels.jobId = execution.job_id;
        slicerDeployment.spec.template.spec.containers[0].name = slicerName;
        slicerDeployment.spec.template.spec.containers[0].env[1].value = JSON.stringify(execution);

        return k8s.post(slicerService, 'service')
            .then(result => logger.info(`k8s slicer service submitted: ${JSON.stringify(result)}`))
            .catch((err) => {
                const error = parseError(err);
                logger.error(`Error submitting k8s slicer service: ${error}`);
                return Promise.reject(error);
            })
            .then(() => k8s.post(slicerDeployment, 'deployment'))
            .then(result => logger.info(`k8s slicer deployment submitted: ${JSON.stringify(result)}`))
            .catch((err) => {
                const error = parseError(err);
                logger.error(`Error submitting k8s slicer deployment: ${error}`);
                return Promise.reject(error);
            });
    }

    /**
     * Creates k8s deployment that executes Teraslice workers for specified
     * Execution.
     * @param  {Object} execution  Object that contains information of Execution
     * @param  {number} numWorkers number of workers to allocate
     * @return {Promise}           [description]
     */
    function allocateWorkers(execution, numWorkers) {
        // FIXME: Verify that this gets a fresh worker.json on every call
        const workerDeployment = require('./deployments/worker.json');
        const workerName = `teraslice-worker-${execution.ex_id}`;

        workerDeployment.metadata.name = workerName;
        workerDeployment.metadata.labels.exId = execution.ex_id;
        workerDeployment.metadata.labels.jobId = execution.job_id;
        workerDeployment.spec.replicas = numWorkers;
        workerDeployment.spec.selector.matchLabels.exId = execution.ex_id;
        workerDeployment.spec.template.metadata.labels.exId = execution.ex_id;
        workerDeployment.spec.template.metadata.labels.jobId = execution.job_id;
        workerDeployment.spec.template.spec.containers[0].name = workerName;
        workerDeployment.spec.template.spec.containers[0].env[1].value = JSON.stringify(execution);

        return k8s.post(workerDeployment, 'deployment')
            .then(result => logger.info(`k8s worker deployment submitted: ${JSON.stringify(result)}`))
            .catch((err) => {
                const error = parseError(err);
                logger.error(`Error submitting k8s worker deployment: ${error}`);
                return Promise.reject(error);
            });
    }

    // FIXME: These functions should probably do something with the response
    // FIXME: I find is strange that the expected return value here is
    //        effectively the same as the function inputs
    async function addWorkers(executionContext, numWorkers) {
        const response = await k8s.scaleExecution(executionContext.ex_id, numWorkers, 'add');
        return { action: 'add', ex_id: executionContext.ex_id, workerNum: numWorkers };
    }

    // NOTE: This is passed exId instead of executionContext like addWorkers and
    // removeWorkers.  I don't know why, just dealing with it.
    async function removeWorkers(exId, numWorkers) {
        const response = await k8s.scaleExecution(exId, numWorkers, 'remove');
        return { action: 'remove', ex_id: exId, workerNum: numWorkers };
    }

    async function setWorkers(executionContext, numWorkers) {
        const response = await k8s.scaleExecution(executionContext.ex_id, numWorkers, 'set');
        return { action: 'set', ex_id: executionContext.ex_id, workerNum: numWorkers };
    }

    /**
     * Sends specified message to workers with exId.
     * TODO: In the kubernetes case it's a bit redundant to provide both a list
     * of workers and exId, since there is only one execution per worker node.
     * I should probably rethink this.
     * @param       {string} exId        Execution ID
     * @param       {string} messageData A 'cluster:execution:*' message
     * @param       {[type]} workers
     * @return      {Promise}
     */
    function _msgWorkersWithExId(exId, messageData, workers) {
        return Promise.map(workers, (worker) => {
            const sendingMsg = Object.assign({}, messageData, {
                to: 'execution_controller',
                address: worker.pod_ip,
                ex_id: exId,
                response: true
            });

            return messaging.send(sendingMsg);
        })
            .catch((err) => {
                const errMsg = parseError(err);
                logger.error('could not notify cluster', errMsg);
                return Promise.reject(errMsg);
            });
    }

    /**
     * Stops all workers for exId
     * @param  {string} exId    The execution ID of the Execution to stop
     * @param  {number} timeout Timeout for messaging the nodes in FIXME: units and meaning
     * @return {Promise}
     */
    async function stopExecution(exId, timeout) {
        const stopMessage = { message: 'cluster:execution:stop' };
        if (timeout) {
            stopMessage.timeout = timeout;
        }
        const workers = _iterateState(worker => worker.ex_id === exId);
        const msgResponse = await _msgWorkersWithExId(exId, stopMessage, workers);
        const deleteResponse = await k8s.deleteExecution(exId);
        return msgResponse;
    }

    function pauseExecution(exId) {
        const workers = _iterateState(worker => worker.assignment === 'execution_controller' && worker.ex_id === exId);
        return _msgWorkersWithExId(exId, { message: 'cluster:execution:pause' }, workers);
    }

    function resumeExecution(exId) {
        const workers = _iterateState(worker => worker.assignment === 'execution_controller' && worker.ex_id === exId);
        return _msgWorkersWithExId(exId, { message: 'cluster:execution:resume' }, workers);
    }

    const api = {
        getClusterState, // k8s
        getClusterStats,
        getSlicerStats,
        allocateWorkers, // k8s
        allocateSlicer, // k8s
        // shutdown,  FIXME: ??
        stopExecution,
        pauseExecution,
        resumeExecution,
        removeWorkers, // k8s
        addWorkers, // k8s
        setWorkers, // k8s
        readyForAllocation,
        // clusterAvailable FIXME: ??
    };

    function _initialize() {
        logger.info('Initializing');
        return k8s.init().then(() => Promise.resolve(api));
    }

    return _initialize();
};
<|endoftext|>


<commit_before>'use strict';
var angular;

angular.module('app').controller('rgiProfileCtrl', function ($scope, rgiIdentitySrvc, rgiUserMethodSrvc, rgiNotifier) {
    // set page resources to be those of the current identity
    $scope.fullName = rgiIdentitySrvc.currentUser.firstName + " " + rgiIdentitySrvc.currentUser.lastName;
    $scope.first_name = rgiIdentitySrvc.currentUser.firstName;
    $scope.last_name = rgiIdentitySrvc.currentUser.lastName;
    $scope.email = rgiIdentitySrvc.currentUser.email;
    $scope.username = rgiIdentitySrvc.currentUser.username;
    $scope.role = rgiIdentitySrvc.currentUser.role;
    $scope.address = rgiIdentitySrvc.currentUser.address;
    // update functinonality for update button
    $scope.update = function () {
        // pass in update data
        var newUserData = {
            firstName: $scope.first_name,
            lastName: $scope.last_name,
            email: $scope.email,
            address: $scope.address
        };
        // check if password update exists and pass it in
        if ($scope.password && $scope.password.length > 0) {
            if ($scope.password !== $scope.password_rep) {
                rgiNotifier.error('Passwords must match!');
            } else {
                newUserData.password = $scope.password;
            }
        }
        // use authorization service to update user data
        rgiUserMethodSrvc.updateUser(newUserData).then(function () {
            rgiNotifier.notify('Your user account has been updated');
        }, function (reason) {
            rgiNotifier.notify(reason);
        });
    };
});
<commit_msg>refactored public/app/account files<commit_after>'use strict';
var angular;

angular.module('app').controller('rgiProfileCtrl', function ($scope, rgiIdentitySrvc, rgiUserMethodSrvc, rgiNotifier) {
    // set page resources to be those of the current identity
    $scope.fullName = rgiIdentitySrvc.currentUser.firstName + " " + rgiIdentitySrvc.currentUser.lastName;
    $scope.first_name = rgiIdentitySrvc.currentUser.firstName;
    $scope.last_name = rgiIdentitySrvc.currentUser.lastName;
    $scope.email = rgiIdentitySrvc.currentUser.email;
    $scope.username = rgiIdentitySrvc.currentUser.username;
    $scope.role = rgiIdentitySrvc.currentUser.role;
    $scope.address = rgiIdentitySrvc.currentUser.address;

    // update functinonality for update button
    $scope.update = function () {
        // pass in update data
        var newUserData = {
            firstName: $scope.first_name,
            lastName: $scope.last_name,
            email: $scope.email,
            address: $scope.address
        };
        // check if password update exists and pass it in
        if ($scope.password && $scope.password.length > 0) {
            if ($scope.password !== $scope.password_rep) {
                rgiNotifier.error('Passwords must match!');
            } else {
                newUserData.password = $scope.password;
            }
        }
        // use authorization service to update user data
        rgiUserMethodSrvc.updateUser(newUserData).then(function () {
            rgiNotifier.notify('Your user account has been updated');
        }, function (reason) {
            rgiNotifier.error(reason);
        });
    };
});
<|endoftext|>


<commit_before>/*jslint sloppy: true */
/*global define */

/**
 * Classify - Sugar syntax for Prototypal Inheritance
 *
 * @author Luís Couto <lcouto87@gmail.com>
 * @version 1.0.0
 *
 * @example
 *
 *      var MyClass = Classify({
 *          Implements: [SomeInterface, OtherInterface],
 *          Extends: ParentClass,
 *          Borrows: [SomeMixin, OtherMixin],
 *          Binds: ['method1', 'method2'],
 *          Statics: {
 *              staticMethod1: function () {},
 *              staticMethod2: function () {},
 *              staticMethod3: function () {},
 *          },
 *          initialize: function () {},
 *          method1: function () {},
 *          method2: function () {},
 *          method3: function () {}
 *      });
 */
define("Trinity/Classify", ["Classify.Abstract", "Classify.Interface", "Classify.Singleton"], function (Abstract, Singleton, Interface) {

    /**
     * Create a class definition.
     *
     * @param {Object} params An object containing methods and properties
     *
     * @returns Function
     */
    function Classify(params) {

        var classify;

        /**
         * Extends an object with another given object.
         *
         * @private
         *
         * @param {Object} source The object to copy from
         * @param {Object} target The object that will get the source properties and methods
         */
        function extend(source, target) {

            var k;

            for (k in source) {
                if (source.hasOwnProperty(k)) {
                    target[k] = source[k];
                }
            }
        }

        /**
         * Borrows the properties and methods of various source objects to the target
         *
         * @private
         *
         * @param {Array}  sources Array of objects that will give their methods
         * @param {Object} target  Target that will receive the methods
         */
        function borrows(sources, target) {

            var i,
                length = sources.length,
                constructorBck,
                current;

            for (i = 0; i < length; i += 1) {

                current = sources[i];

                if (current.prototype && current.prototype.constructor) {
                    constructorBck = current.prototype.constructor;
                    delete current.prototype.constructor;
                    extend(current.prototype, target.prototype);
                    current.prototype.constructor = constructorBck;
                } else {
                    extend(current.prototype || current, target.prototype);
                }
            }
        }

        /**
         * Fixes the context in given methods.
         *
         * @private
         *
         * @param {Array} fns The array of functions to be binded
         * @param {Object} context The context that will be bound
         * @param {Object} target The target class that will have these methods
         */
        function binds(fns, context, target) {

            var proxy = function (func) {

                if (Function.prototype.bind) {
                    return func.bind(context);
                }

                return function () {
                    return func.apply(context, arguments);
                };

            },
                i = fns.length - 1;

            for (i; i >= 0; i -= 1) {
                target[fns[i]] = proxy(target[fns[i]], classify);
            }
        }

        /**
         * Copies the given object into a freshly created empty function's prototype
         *
         * @private
         *
         * @param {Object} object Object
         *
         * @returns {Function} Thew new instance
         */
        function clone(object) {

            function F() {}
            F.prototype = object;

            return new F();
        }

        /**
         *
         */
        function interfaces(implementations, target) {

            var i = implementations.length - 1,
                k;

            for (; i >= 0; i -= 1) {
                for (k in implementations[i]) {
                    if (!target.hasOwnProperty(k) && (k !== "Extends" || k !== "Name")) {
                        throw new Error("Class does not implements Interface " + implementations[i].Name + "correctly");
                    }
                }
            }
        }

        classify = params.initialize || function () {};

        if (params.Extends) {
            classify.Parent = params.Extends.prototype;
            classify.prototype = clone(classify.Parent);
            extend(params, classify.prototype);
        } else {
            classify.prototype = params;
        }

        classify.prototype.constructor = classify;

        if (params.Borrows) {
            borrows(params.Borrows, classify);
        }

        if (params.Binds) {
            binds(params.Binds, classify, classify.prototype);
        }

        if (params.Statics) {
            extend(params.Statics, classify);
            delete classify.prototype.Static;
        }

        if (params.Implements) {
            interfaces(params.Implements, this);
        }

        return classify;
    }

    Classify.Abstract = Abstract;
    Classify.Interface = Interface;
    Classify.Singleton = Singleton;

    return Classify;
});
<commit_msg>Documentation fix.<commit_after>/*jslint sloppy: true */
/*global define */

/**
 * Classify - Sugar syntax for Prototypal Inheritance
 *
 * @author Luís Couto <lcouto87@gmail.com>
 * @version 1.0.0
 *
 * @example
 *
 *      var MyClass = Classify({
 *          Implements: [SomeInterface, OtherInterface],
 *          Extends: ParentClass,
 *          Borrows: [SomeMixin, OtherMixin],
 *          Binds: ['method1', 'method2'],
 *          Statics: {
 *              staticMethod1: function () {},
 *              staticMethod2: function () {},
 *              staticMethod3: function () {},
 *          },
 *          initialize: function () {},
 *          method1: function () {},
 *          method2: function () {},
 *          method3: function () {}
 *      });
 */
define("Trinity/Classify", ["Classify.Abstract", "Classify.Interface", "Classify.Singleton"], function (Abstract, Singleton, Interface) {

    /**
     * Create a class definition.
     *
     * @param {Object} params An object containing methods and properties
     *
     * @returns {Function} The constructor
     */
    function Classify(params) {

        var classify;

        /**
         * Extends an object with another given object.
         *
         * @private
         *
         * @param {Object} source The object to copy from
         * @param {Object} target The object that will get the source properties and methods
         */
        function extend(source, target) {

            var k;

            for (k in source) {
                if (source.hasOwnProperty(k)) {
                    target[k] = source[k];
                }
            }
        }

        /**
         * Borrows the properties and methods of various source objects to the target
         *
         * @private
         *
         * @param {Array}  sources Array of objects that will give their methods
         * @param {Object} target  Target that will receive the methods
         */
        function borrows(sources, target) {

            var i,
                length = sources.length,
                constructorBck,
                current;

            for (i = 0; i < length; i += 1) {

                current = sources[i];

                if (current.prototype && current.prototype.constructor) {
                    constructorBck = current.prototype.constructor;
                    delete current.prototype.constructor;
                    extend(current.prototype, target.prototype);
                    current.prototype.constructor = constructorBck;
                } else {
                    extend(current.prototype || current, target.prototype);
                }
            }
        }

        /**
         * Fixes the context in given methods.
         *
         * @private
         *
         * @param {Array} fns The array of functions to be binded
         * @param {Object} context The context that will be bound
         * @param {Object} target The target class that will have these methods
         */
        function binds(fns, context, target) {

            var proxy = function (func) {

                if (Function.prototype.bind) {
                    return func.bind(context);
                }

                return function () {
                    return func.apply(context, arguments);
                };

            },
                i = fns.length - 1;

            for (i; i >= 0; i -= 1) {
                target[fns[i]] = proxy(target[fns[i]], classify);
            }
        }

        /**
         * Copies the given object into a freshly created empty function's prototype
         *
         * @private
         *
         * @param {Object} object Object
         *
         * @returns {Function} Thew new instance
         */
        function clone(object) {

            function F() {}
            F.prototype = object;

            return new F();
        }

        /**
         *
         */
        function interfaces(implementations, target) {

            var i = implementations.length - 1,
                k;

            for (; i >= 0; i -= 1) {
                for (k in implementations[i]) {
                    if (!target.hasOwnProperty(k) && (k !== "Extends" || k !== "Name")) {
                        throw new Error("Class does not implements Interface " + implementations[i].Name + "correctly");
                    }
                }
            }
        }

        classify = params.initialize || function () {};

        if (params.Extends) {
            classify.Parent = params.Extends.prototype;
            classify.prototype = clone(classify.Parent);
            extend(params, classify.prototype);
        } else {
            classify.prototype = params;
        }

        classify.prototype.constructor = classify;

        if (params.Borrows) {
            borrows(params.Borrows, classify);
        }

        if (params.Binds) {
            binds(params.Binds, classify, classify.prototype);
        }

        if (params.Statics) {
            extend(params.Statics, classify);
            delete classify.prototype.Static;
        }

        if (params.Implements) {
            interfaces(params.Implements, this);
        }

        return classify;
    }

    Classify.Abstract = Abstract;
    Classify.Interface = Interface;
    Classify.Singleton = Singleton;

    return Classify;
});
<|endoftext|>


<commit_before>'use strict';

var is = require( '../is' );
var util = require( '../util' );
var Promise = require('../promise');

var elesfn = ({

  // using standard layout options, apply position function (w/ or w/o animation)
  layoutPositions: function( layout, options, fn ){
    var nodes = this.nodes();
    var cy = this.cy();

    layout.trigger( { type: 'layoutstart', layout: layout } );

    layout.animations = [];

    var calculateSpacing = function( spacing, nodesBb, pos ){
      var center = {
        x: nodesBb.x1 + nodesBb.w / 2,
        y: nodesBb.y1 + nodesBb.h / 2
      };
      var spacingVector = { // scale from center of bounding box (not necessarily 0,0)
        x: (pos.x - center.x) * spacing,
        y: (pos.y - center.y) * spacing
      };
      return {
        x: center.x + spacingVector.x,
        y: center.y + spacingVector.y
      };
    };

    if( options.animate ){
      var nodesBb = nodes.boundingBox();
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[ i ];

        var newPos = fn.call( node, i, node );
        var pos = node.position();

        if( !is.number( pos.x ) || !is.number( pos.y ) ){
          node.silentPosition( { x: 0, y: 0 } );
        }

        if ( options.spacingFactor && options.spacingFactor !== 1){
          var spacing = Math.abs(options.spacingFactor);
          newPos = calculateSpacing(spacing, nodesBb, newPos);
        }

        var ani = node.animation( {
          position: newPos,
          duration: options.animationDuration,
          easing: options.animationEasing
        } );

        layout.animations.push( ani );

        ani.play();
      }

      var onStep;
      cy.on( 'step.*', ( onStep = function(){
        if( options.fit ){
          cy.fit( options.eles, options.padding );
        }
      }) );

      layout.one('layoutstop', function(){
        cy.off('step.*', onStep);
      });

      layout.one( 'layoutready', options.ready );
      layout.trigger( { type: 'layoutready', layout: layout } );

      Promise.all( layout.animations.map(function( ani ){
        return ani.promise();
      }) ).then(function(){
        cy.off('step.*', onStep);

        if( options.zoom != null ){
          cy.zoom( options.zoom );
        }

        if( options.pan ){
          cy.pan( options.pan );
        }

        if( options.fit ){
          cy.fit( options.eles, options.padding );
        }

        layout.one( 'layoutstop', options.stop );
        layout.trigger( { type: 'layoutstop', layout: layout } );
      });
    } else {
      nodes.positions( fn );

      if ( options.spacingFactor && options.spacingFactor !== 1){
        var spacing = Math.abs(options.spacingFactor);
        nodes.positions( function( i, node){
          var pos = node.position();
          var nodesBb = nodes.boundingBox();
          return calculateSpacing(spacing, nodesBb, pos);
        });
      }

      if( options.fit ){
        cy.fit( options.eles, options.padding );
      }

      if( options.zoom != null ){
        cy.zoom( options.zoom );
      }

      if( options.pan ){
        cy.pan( options.pan );
      }

      layout.one( 'layoutready', options.ready );
      layout.trigger( { type: 'layoutready', layout: layout } );

      layout.one( 'layoutstop', options.stop );
      layout.trigger( { type: 'layoutstop', layout: layout } );
    }

    return this; // chaining
  },

  layout: function( options ){
    var cy = this.cy();

    cy.layout( util.extend( {}, options, {
      eles: this
    } ) );

    return this;
  },

  makeLayout: function( options ){
    var cy = this.cy();

    return cy.makeLayout( util.extend( {}, options, {
      eles: this
    } ) );
  }

});

// aliases:
elesfn.createLayout = elesfn.makeLayout;

module.exports = elesfn;
<commit_msg>don't call positions() twice<commit_after>'use strict';

var is = require( '../is' );
var util = require( '../util' );
var Promise = require('../promise');

var elesfn = ({

  // using standard layout options, apply position function (w/ or w/o animation)
  layoutPositions: function( layout, options, fn ){
    var nodes = this.nodes();
    var cy = this.cy();

    layout.trigger( { type: 'layoutstart', layout: layout } );

    layout.animations = [];

    var calculateSpacing = function( spacing, nodesBb, pos ){
      var center = {
        x: nodesBb.x1 + nodesBb.w / 2,
        y: nodesBb.y1 + nodesBb.h / 2
      };
      var spacingVector = { // scale from center of bounding box (not necessarily 0,0)
        x: (pos.x - center.x) * spacing,
        y: (pos.y - center.y) * spacing
      };
      return {
        x: center.x + spacingVector.x,
        y: center.y + spacingVector.y
      };
    };

    if( options.animate ){
      var nodesBb = nodes.boundingBox();
      for( var i = 0; i < nodes.length; i++ ){
        var node = nodes[ i ];

        var newPos = fn.call( node, i, node );
        var pos = node.position();

        if( !is.number( pos.x ) || !is.number( pos.y ) ){
          node.silentPosition( { x: 0, y: 0 } );
        }

        if ( options.spacingFactor && options.spacingFactor !== 1){
          var spacing = Math.abs(options.spacingFactor);
          newPos = calculateSpacing(spacing, nodesBb, newPos);
        }

        var ani = node.animation( {
          position: newPos,
          duration: options.animationDuration,
          easing: options.animationEasing
        } );

        layout.animations.push( ani );

        ani.play();
      }

      var onStep;
      cy.on( 'step.*', ( onStep = function(){
        if( options.fit ){
          cy.fit( options.eles, options.padding );
        }
      }) );

      layout.one('layoutstop', function(){
        cy.off('step.*', onStep);
      });

      layout.one( 'layoutready', options.ready );
      layout.trigger( { type: 'layoutready', layout: layout } );

      Promise.all( layout.animations.map(function( ani ){
        return ani.promise();
      }) ).then(function(){
        cy.off('step.*', onStep);

        if( options.zoom != null ){
          cy.zoom( options.zoom );
        }

        if( options.pan ){
          cy.pan( options.pan );
        }

        if( options.fit ){
          cy.fit( options.eles, options.padding );
        }

        layout.one( 'layoutstop', options.stop );
        layout.trigger( { type: 'layoutstop', layout: layout } );
      });
    } else {
      if( options.spacingFactor && options.spacingFactor !== 1){
        var spacing = Math.abs(options.spacingFactor);
        nodes.positions( function( i, node){
          var pos = fn( i, node);
          var nodesBb = nodes.boundingBox();
          return calculateSpacing(spacing, nodesBb, pos);
        });
      } else {
        nodes.positions(fn);
      }

      if( options.fit ){
        cy.fit( options.eles, options.padding );
      }

      if( options.zoom != null ){
        cy.zoom( options.zoom );
      }

      if( options.pan ){
        cy.pan( options.pan );
      }

      layout.one( 'layoutready', options.ready );
      layout.trigger( { type: 'layoutready', layout: layout } );

      layout.one( 'layoutstop', options.stop );
      layout.trigger( { type: 'layoutstop', layout: layout } );
    }

    return this; // chaining
  },

  layout: function( options ){
    var cy = this.cy();

    cy.layout( util.extend( {}, options, {
      eles: this
    } ) );

    return this;
  },

  makeLayout: function( options ){
    var cy = this.cy();

    return cy.makeLayout( util.extend( {}, options, {
      eles: this
    } ) );
  }

});

// aliases:
elesfn.createLayout = elesfn.makeLayout;

module.exports = elesfn;
<|endoftext|>


<commit_before>//############################################################################################
//##
//# Copyright (C) 2017 Nicola Peditto
//##
//# Licensed under the Apache License, Version 2.0 (the "License");
//# you may not use this file except in compliance with the License.
//# You may obtain a copy of the License at
//##
//# http://www.apache.org/licenses/LICENSE-2.0
//##
//# Unless required by applicable law or agreed to in writing, software
//# distributed under the License is distributed on an "AS IS" BASIS,
//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//# See the License for the specific language governing permissions and
//# limitations under the License.
//##
//############################################################################################

//service logging configuration: "manageCommands"
var logger = log4js.getLogger('servicesManager');
logger.setLevel(loglevel);

//Services list: it is used to store the services process data that are started in the current LR session
servicesProcess = [];

var Q = require("q");

// This function expose a service
exports.enableService = function(args){

    // Parsing the input arguments
    var serviceName = String(args[0]);
    var localPort = String(args[1]);
    var publicPort = String(args[2]);
    var restore = String(args[3]);

    var response = {
        message: '',
        result: ''
    };

    var d = Q.defer();

    logger.debug("[SERVICE] - RPC enableService called: " + args);

    //Looking for the process in the array
    var i = findValue(servicesProcess, serviceName, 'key');

    if ( i != -1) {

        if(restore == "true"){

            logger.info("[SERVICE] - Restoring tunnel for '"+ serviceName + "' service...");

            //Killing the process
            logger.debug('[SERVICE] --> killing '+serviceName+' process [ PID ' + servicesProcess[i].process.pid + " ]");
            servicesProcess[i].process.kill('SIGINT');
            servicesProcess.splice(i,1);

            createTunnel(serviceName, localPort, publicPort, function (newTunnel) {

                response.result = "SUCCESS";
                response.message = "Service "+ serviceName +" successfully restored on port " + publicPort + " - [PID "+newTunnel.process.pid+"]";
                logger.info('[SERVICE] --> ' + response.message);
                d.resolve(response);

            });

        }else{

            logger.warn("Service already active!");
            response.result = "WARNING";
            response.message = "Service "+ serviceName +" already active!";
            logger.info('[SERVICE] - ' + response.message);
            d.resolve(response);

        }

    }else{

        logger.info('[SERVICE] - Exposing new tunnel for '+ serviceName + ' service...');
        createTunnel(serviceName, localPort, publicPort, function (newTunnel) {

            response.result = "SUCCESS";
            response.message = "Service "+ serviceName +" successfully exposed on port " + publicPort + " - [PID "+newTunnel.process.pid+"]";
            logger.info('[SERVICE] --> ' + response.message);
            d.resolve(response);

        });

    }

    return d.promise;

};

// This function expose a service
exports.disableService = function(args){

    var serviceName = args[0];

    var response = {
        message: '',
        result: ''
    };

    var service = {
        status: "",
        pid: "",
        name: "",
        message:""
    };

    logger.info('[SERVICE] - Disabling ' + serviceName + " service...");

    var d = Q.defer();

    //Looking for the process in the array
    var i = findValue(servicesProcess, serviceName, 'key');

    if(i != -1){

        //Killing the process
        logger.debug('[SERVICE] --> killing '+serviceName+' process [ PID ' + servicesProcess[i].process.pid + " ]");
        servicesProcess[i].process.kill('SIGINT');

        response.result = "SUCCESS";
        response.message = "Tunnel for "+ serviceName +" service successfully removed from public port " + servicesProcess[i].port;
        logger.info('[SERVICE] --> ' + response.message);

        servicesProcess.splice(i,1);

        d.resolve(response);

    }else{
        response.result = "WARNING";
        service.message = serviceName +" service is inactive!";
        service.status = "INACTIVE";
        service.pid = null;
        service.name = serviceName;
        response.message = service;
        logger.warn('[SERVICE] --> ' + JSON.stringify(response.message));
        d.resolve(response);
    }

    return d.promise;

};

// This function check service status
exports.checkService = function(args){

    var serviceName = args[0];

    var response = {
        message: '',
        result: ''
    };

    var service = {
        status: "",
        pid: "",
        name: "",
        message:""
    };

    logger.info('[SERVICE] - Checking ' + serviceName + " service...");

    var d = Q.defer();

    //Looking for the process in the array
    var i = findValue(servicesProcess, serviceName, 'key');

    if(i == -1){

        response.result = "SUCCESS";
        service.message = serviceName +" service is inactive!";
        service.status = "INACTIVE";
        service.pid = null;
        service.name = serviceName;
        response.message = service;
        logger.warn('[SERVICE] --> ' + JSON.stringify(response.message));
        d.resolve(response);

    }else{

        response.result = "WARNING";
        service.message = serviceName +" service is active!";
        service.status = "ACTIVE";
        service.pid = servicesProcess[i].process.pid;
        service.name = serviceName;
        response.message = service;
        logger.info('[SERVICE] --> ' + JSON.stringify(response.message));
        d.resolve(response);

    }

    return d.promise;

};

function createTunnel(serviceName, localPort, publicPort, callback) {

    reverseTunnellingServer = nconf.get('config:reverse:server:url_reverse')+":"+nconf.get('config:reverse:server:port_reverse');

    logger.info('[SERVICE] --> exposing local port ' + localPort + ' on public port ' + publicPort + ' contacting remote server ' + reverseTunnellingServer);

    //Getting the path of the wstun.js module from the configuration file
    var reverseTunnellingClient = nconf.get('config:reverse:lib:bin');

    //I spawn a process executing the reverse tunnel client with appropriate parameters
    var spawn = require('child_process').spawn;

    logger.debug('[SERVICE] --> executing command: ' + reverseTunnellingClient + ' -r '+publicPort+':'+'127.0.0.1'+':'+localPort + ' ' + reverseTunnellingServer);

    //I insert the new service in the array so that I can find it later when I have to stop the service
    var newTunnel = {
        key: serviceName,
        port: publicPort,
        process: spawn(reverseTunnellingClient, ['-r '+publicPort+':'+'127.0.0.1'+':'+localPort, reverseTunnellingServer])
    };

    servicesProcess.push(newTunnel);

    newTunnel.process.stdout.on('data', function(data){
        logger.debug('[SERVICE] - onData - '+newTunnel.key+' stdout of process ' + newTunnel.process.pid + ': '+data);
    });
    newTunnel.process.stderr.on('data', function(data){
        logger.error('[SERVICE] - onError - '+newTunnel.key+' stderr of process ' + newTunnel.process.pid + ': '+ data);
    });
    newTunnel.process.on('close', function(code){
        logger.debug('[SERVICE] - onClose - '+newTunnel.key+' child process ' + newTunnel.process.pid + ' exited with code '+ code);
    });

    callback(newTunnel);

}

//Function that search a process in the array
function findValue(myArray, value, property) {
    for(var i = 0, len = myArray.length; i < len; i++) {
        if (myArray[i][property] === value) {
            return i;
        }
    }
    return -1;
}

//This function exports all the functions in the module as WAMP remote procedure calls
exports.Init = function (session){

    //Register all the module functions as WAMP RPCs
    session.register('s4t.'+boardCode+'.service.enable', exports.enableService);
    session.register('s4t.'+boardCode+'.service.disable', exports.disableService);
    session.register('s4t.'+boardCode+'.service.checkService', exports.checkService);

    logger.info('[WAMP-EXPORTS] Services commands exported to the cloud!');

    logger.info('[SERVICE] - Service restoring started...');
    session.call('s4t.iotronic.service.restore', [boardCode]).then(
        function (response) {
            logger.info('[SERVICE] --> Response from IoTronic: ' + response.message);
            logger.info('[SERVICE] --> Services restoring completed!');
        }
    );

};

<commit_msg>Improved service restore system.<commit_after>//############################################################################################
//##
//# Copyright (C) 2017 Nicola Peditto
//##
//# Licensed under the Apache License, Version 2.0 (the "License");
//# you may not use this file except in compliance with the License.
//# You may obtain a copy of the License at
//##
//# http://www.apache.org/licenses/LICENSE-2.0
//##
//# Unless required by applicable law or agreed to in writing, software
//# distributed under the License is distributed on an "AS IS" BASIS,
//# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//# See the License for the specific language governing permissions and
//# limitations under the License.
//##
//############################################################################################

//service logging configuration: "manageCommands"
var logger = log4js.getLogger('servicesManager');
logger.setLevel(loglevel);

//Services list: it is used to store the services process data that are started in the current LR session
servicesProcess = [];

var Q = require("q");
var running = require('is-running');  	//In order to verify if a tunnel-process is alive or not.

// This function expose a service
exports.enableService = function(args){

    // Parsing the input arguments
    var serviceName = String(args[0]);
    var localPort = String(args[1]);
    var publicPort = String(args[2]);
    var restore = String(args[3]);

    var db_tunnel_pid = String(args[4]);

    var response = {
        message: '',
        result: ''
    };

    var d = Q.defer();

    logger.debug("[SERVICE] - RPC enableService called: " + args);

    if(running(db_tunnel_pid)){

        // Call when Restore tunnel API is called and after injection LR conf is called

        //kill tunnel process
        process.kill(db_tunnel_pid);

        //clean data structure
        var i = findValue(servicesProcess, serviceName, 'key');
        servicesProcess.splice(i,1);

        logger.warn("[SERVICE] --> Tunnel process of service "+ serviceName +" still active: killed!");

        createTunnel(serviceName, localPort, publicPort, function (newTunnel) {

            response.result = "SUCCESS";
            response.pid = newTunnel.process.pid;
            response.message = "Service "+ serviceName +" successfully restored on port " + publicPort + " - [PID "+newTunnel.process.pid+"]";
            logger.info('[SERVICE] --> ' + response.message);
            d.resolve(response);

        });

    }else{

        //Looking for the process in the array
        var i = findValue(servicesProcess, serviceName, 'key');

        if ( i != -1) {

            // if service is active and stored in servicesProcess array

            if(restore == "true"){

                // Call when self-Restore tunnel procedure is called: when connection is recovered!
                logger.info("[SERVICE] - Restoring tunnel for '"+ serviceName + "' service...");

                //Killing the process
                logger.debug('[SERVICE] --> killing '+serviceName+' process [ PID ' + servicesProcess[i].process.pid + " ]");
                servicesProcess[i].process.kill('SIGINT');
                servicesProcess.splice(i,1);

                createTunnel(serviceName, localPort, publicPort, function (newTunnel) {

                    response.result = "SUCCESS";
                    response.pid = newTunnel.process.pid;
                    response.message = "Service "+ serviceName +" successfully restored (after reconnection) on port " + publicPort + " - [PID "+newTunnel.process.pid+"]";
                    logger.info('[SERVICE] --> ' + response.message);
                    d.resolve(response);

                });

            }else{

                response.result = "WARNING";
                response.message = "Service '"+ serviceName +"' already active!";
                logger.info('[SERVICE] - ' + response.message);
                d.resolve(response);

            }

        }
        else{

            // Call when LR restore tunnels at boot time and when enable-service API is called

            logger.info('[SERVICE] - Exposing new tunnel for '+ serviceName + ' service...');
            createTunnel(serviceName, localPort, publicPort, function (newTunnel) {

                response.result = "SUCCESS";
                response.pid = newTunnel.process.pid;
                response.message = "Service "+ serviceName +" successfully exposed on port " + publicPort + " - [PID "+newTunnel.process.pid+"]";
                logger.info('[SERVICE] --> ' + response.message);
                d.resolve(response);

            });

        }

    }

    return d.promise;

};

// This function expose a service
exports.disableService = function(args){

    var serviceName = args[0];

    var response = {
        message: '',
        result: ''
    };

    var service = {
        status: "",
        pid: "",
        name: "",
        message:""
    };

    logger.info('[SERVICE] - Disabling ' + serviceName + " service...");

    var d = Q.defer();

    //Looking for the process in the array
    var i = findValue(servicesProcess, serviceName, 'key');

    if(i != -1){

        //Killing the process
        logger.debug('[SERVICE] --> killing '+serviceName+' process [ PID ' + servicesProcess[i].process.pid + " ]");
        servicesProcess[i].process.kill('SIGINT');

        response.result = "SUCCESS";
        response.message = "Tunnel for "+ serviceName +" service successfully removed from public port " + servicesProcess[i].port;
        logger.info('[SERVICE] --> ' + response.message);

        servicesProcess.splice(i,1);

        d.resolve(response);

    }else{
        response.result = "WARNING";
        service.message = serviceName +" service is inactive!";
        service.status = "INACTIVE";
        service.pid = null;
        service.name = serviceName;
        response.message = service;
        logger.warn('[SERVICE] --> ' + JSON.stringify(response.message));
        d.resolve(response);
    }

    return d.promise;

};

// This function check service status
exports.checkService = function(args){

    var serviceName = args[0];

    var serviceDbPID = args[1];

    var response = {
        message: '',
        result: ''
    };

    var service = {
        status: "",
        pid: "",
        name: "",
        message:""
    };

    logger.info('[SERVICE] - Checking ' + serviceName + " service...");

    var d = Q.defer();

    if(running(serviceDbPID)){

        response.result = "WARNING";
        service.message = serviceName +" service is still active!";
        service.status = "ACTIVE";
        service.pid = serviceDbPID;
        service.name = serviceName;
        response.message = service;
        logger.info('[SERVICE] --> ' + JSON.stringify(response.message));
        d.resolve(response);

    }else{

        //Looking for the process in the array
        var i = findValue(servicesProcess, serviceName, 'key');

        if(i == -1){

            response.result = "SUCCESS";
            service.message = serviceName +" service is inactive!";
            service.status = "INACTIVE";
            service.pid = null;
            service.name = serviceName;
            response.message = service;
            logger.warn('[SERVICE] --> ' + JSON.stringify(response.message));
            d.resolve(response);

        }else{

            response.result = "WARNING";
            service.message = serviceName +" service is active!";
            service.status = "ACTIVE";
            service.pid = servicesProcess[i].process.pid;
            service.name = serviceName;
            response.message = service;
            logger.info('[SERVICE] --> ' + JSON.stringify(response.message));
            d.resolve(response);

        }

    }

    return d.promise;

};

function createTunnel(serviceName, localPort, publicPort, callback) {

    reverseTunnellingServer = nconf.get('config:reverse:server:url_reverse')+":"+nconf.get('config:reverse:server:port_reverse');

    logger.info('[SERVICE] --> exposing local port ' + localPort + ' on public port ' + publicPort + ' contacting remote server ' + reverseTunnellingServer);

    //Getting the path of the wstun.js module from the configuration file
    var reverseTunnellingClient = nconf.get('config:reverse:lib:bin');

    //I spawn a process executing the reverse tunnel client with appropriate parameters
    var spawn = require('child_process').spawn;

    logger.debug('[SERVICE] --> executing command: ' + reverseTunnellingClient + ' -r '+publicPort+':'+'127.0.0.1'+':'+localPort + ' ' + reverseTunnellingServer);

    //I insert the new service in the array so that I can find it later when I have to stop the service
    var newTunnel = {
        key: serviceName,
        port: publicPort,
        process: spawn(reverseTunnellingClient, ['-r '+publicPort+':'+'127.0.0.1'+':'+localPort, reverseTunnellingServer])
    };

    servicesProcess.push(newTunnel);

    newTunnel.process.stdout.on('data', function(data){
        logger.debug('[SERVICE] - onData - '+newTunnel.key+' stdout of process ' + newTunnel.process.pid + ': '+data);
    });
    newTunnel.process.stderr.on('data', function(data){
        logger.error('[SERVICE] - onError - '+newTunnel.key+' stderr of process ' + newTunnel.process.pid + ': '+ data);
    });
    newTunnel.process.on('close', function(code){
        logger.debug('[SERVICE] - onClose - '+newTunnel.key+' child process ' + newTunnel.process.pid + ' exited with code '+ code);
    });

    callback(newTunnel);

}

//Function that search a process in the array
function findValue(myArray, value, property) {
    for(var i = 0, len = myArray.length; i < len; i++) {
        if (myArray[i][property] === value) {
            return i;
        }
    }
    return -1;
}

//This function exports all the functions in the module as WAMP remote procedure calls
exports.Init = function (session){

    //Register all the module functions as WAMP RPCs
    session.register('s4t.'+boardCode+'.service.enable', exports.enableService);
    session.register('s4t.'+boardCode+'.service.disable', exports.disableService);
    session.register('s4t.'+boardCode+'.service.checkService', exports.checkService);

    logger.info('[WAMP-EXPORTS] Services commands exported to the cloud!');

    logger.info('[SERVICE] - Service restoring started...');
    session.call('s4t.iotronic.service.restore', [boardCode]).then(
        function (response) {
            logger.info('[SERVICE] --> Response from IoTronic: ' + response.message);
            logger.info('[SERVICE] --> Services restoring completed!');
        }
    );

};

<|endoftext|>


<commit_before>var records = [
  {
    id: '1',
    record: '',
    tags: ['AAA'],
  },

  {
    id: '2',
    record: '',
    tags: ['BBB'],
  },

  {
    id: '3',
    record: '',
    tags: ['ABC'],
  },
];

<commit_msg>サンプルデータを追加する<commit_after>var records = [
  {
    id: '1',
    record: 'ns=QbLl&ss=36gl176401kcwSw000&m=1&v=0.97',
    tags: ['TSD', 'TOILSZJ'],
  },

  {
    id: '2',
    record: 'ns=bKYkHf&ss=1w5Yj174w36Ao35glw1g0000000&m=1&v=0.97',
    tags: ['パフェ', 'ILSTZOJ'],
  },

  {
    id: '3',
    record: 'ns=dvyOxHTsKh&ss=1tcl35gh01A5gNAchM1Dco1hcn01D5sm1g0025Wp1H000&m=1&v=0.97',
    tags: ['DT砲', 'ISLZOJT'],
  },
];

<|endoftext|>


<commit_before>const Bluebird = require('bluebird');
const fs = Bluebird.promisifyAll(require('fs'));
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const moment = require('moment');
moment.locale('fi');

class CalendarService {
  constructor(calendars) {
    this.calendars = calendars;
  }

  async getEventsText(eventCount = 1) {
    const events = await this.getEvents(eventCount);
    return events.reduce((acc, cur) => {
      const start = moment(cur.start).format('DD.MM. HH:mm');
      const end = moment(cur.end).format('HH:mm');
      return `${acc}${acc !== '' ? '\n' : ''}${cur.name} - ${start} to ${end} - ${cur.summary}`
    }, 'Next 2 reservations:');
  }

  async getFreeText() {
    const events = await this.getEvents();
    return this.calendars.reduce((acc, cur) => {
      const e = events.find(e => e.name == cur.name);

      if (!e)
        return `${acc}${acc !== '' ? '\n' : ''}${cur.name} - indefinitely`;

      const diff = moment.duration(moment(e.start).diff(moment()));
      const diffAsHours = diff.asHours();

      if (diffAsHours > 0 && diffAsHours < 1) {
        return `${acc}${acc !== '' ? '\n' : ''}${e.name} - ${diff.asMinutes().toFixed(0)} minutes`
      } else if (diffAsHours > 0) {
        return `${acc}${acc !== '' ? '\n' : ''}${e.name} - ${diffAsHours.toFixed(1)} hours`
      } else {
        return acc;
      }
    }, '');
  }

  async getEvents(eventCount = 1) {
    const client = await this.getOAuthClient();
    return await this.getCurrentOrNextEvents(eventCount, client);
  }

  async bookEvent(booker, meetingRoom, durationMinutes = 15) {
    const client = await this.getOAuthClient();
    return this.bookMeetingRoom(booker, meetingRoom, durationMinutes, client);
  }

  async cancelEvent(canceller, meetingRoom) {
    const client = await this.getOAuthClient();
    return await this.cancelMeeting(canceller, meetingRoom, client);
  }

  async getOAuthClient() {
    const secretPromise = fs.readFileAsync('client_secret.json');
    const tokenPromise = fs.readFileAsync('calendar-authToken.json');
    const secret = await secretPromise;
    const token = await tokenPromise;

    const credentials = JSON.parse(secret);
    const clientSecret = credentials.installed.client_secret;
    const clientId = credentials.installed.client_id;
    const redirectUrl = credentials.installed.redirect_uris[0];

    const auth = new googleAuth();
    const oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
    oauth2Client.credentials = JSON.parse(token);
    return oauth2Client;
  }

  async getCurrentOrNextEvents(eventCount, auth) {
    const promises = this.calendars.map(c => {
      return this.getCalendarEvents(c.name, c.id, eventCount, auth);
    }, this);

    return await Promise.all(promises).then(results => {
      return results.reduce((p, [success, result]) => {
        return success ? p.concat(result) : p;
      }, []);
    });
  }

  getCalendarEvents(calendarName, calendarId, eventCount, auth) {
    const params = {
      auth: auth,
      calendarId: calendarId,
      timeMin: (new Date()).toISOString(),
      maxResults: eventCount,
      singleEvents: true,
      orderBy: 'startTime'
    };
    return new Promise((resolve, reject) => {
      google.calendar('v3').events.list(params, (err, response) => {
        if (err) {
          resolve([false, 'The API (calendar.events.list) returned an error: ' + err]);
          return;
        }

        const isPublic = (item) => item.visibility !== 'private';

        const events = response.items.map(e => {
          return {
            id: e.id,
            name: calendarName,
            start: e.start.dateTime || e.start.date,
            end: e.end.dateTime || e.end.date,
            summary: isPublic(e) ? e.summary : 'Private',
            description: isPublic(e) ? e.description : 'Private',
            attendees: isPublic(e) ? e.attendees : 'Private',
            creator: e.creator
          }
        });

        resolve([true, events]);
      });
    });
  }

  async bookMeetingRoom(booker, roomName, durationMinutes, auth) {
    if (!roomName)
      return `Define a room name. (${this.calendars.map(c => c.name)})`;

    const selected = this.calendars.filter(c => c.name.toUpperCase() == roomName.toUpperCase());

    if (selected.length == 0)
      return `${roomName} not found. (${this.calendars.map(c => c.name)})`;

    const [success, nextReservation] = await this.getCalendarEvents(selected[0].name, selected[0].id, 1, auth);

    if (!success)
      return `Failed to get the calendar events`;

    const start = new Date();
    const end = new Date(start.getTime() + durationMinutes * 60000);

    if (nextReservation[0]) {
      const bookStart = moment(start);
      const bookEnd = moment(end);
      const resStart = moment(new Date(nextReservation[0].start));
      const resEnd = moment(new Date(nextReservation[0].end));

      if (bookStart.isBetween(resStart, resEnd) || bookEnd.isBetween(resStart, resEnd))
        return `Can't book ${roomName} for ${durationMinutes} minutes at ${bookStart.format('H:mm')}. The room is already reserved from ${resStart.format('H:mm')} till ${resEnd.format('H:mm')}.`;
    }

    const event = {
      'summary': `${ booker.name || booker.email } - SlackBot quick booking`,
      'description': `A quick booking made from SlackBot for ${booker.name} - ${booker.email}`,
      'start': {
        'dateTime': start
      },
      'end': {
        'dateTime': end
      },
      'attendees': [{
        'email': booker.email
      }]
    };

    return await new Promise((resolve, reject) => {
      google.calendar('v3').events.insert({
        'auth': auth,
        'calendarId': selected[0].id,
        'resource': event
      }, (err, response) => {
        if (err) {
          reject(`The API (calendar.events.insert) returned an error: ${err}\ncalendarId: ${selected[0].id}\nresource: ${JSON.stringify(event, null, 4)}`);
        }
        resolve(`${selected[0].name} booked for ${durationMinutes} minutes at ${moment(start).format('H:mm')}`);
      });
    });
  }

  async cancelMeeting(canceller, roomName, auth) {
    if (!roomName)
      return `Define a room name. (${this.calendars.map(c => c.name)})`;

    const selected = this.calendars.filter(c => c.name.toUpperCase() == roomName.toUpperCase());

    if (selected.length == 0)
      return `${roomName} not found. (${this.calendars.map(c => c.name)})`;

    const [success, upcomingReservations] = await this.getCalendarEvents(selected[0].name, selected[0].id, 1, auth);

    if (!success)
      return `Failed to get the calendar events`;

    const cancellerReservations = upcomingReservations.filter(reservation =>
      reservation.attendees && reservation.attendees.some(a => a.email == canceller.email) &&
            reservation.description && reservation.description.includes('A quick booking made from SlackBot for'));

    if (cancellerReservations.length == 0)
      return `${canceller.email} has not made any room reservations with SlackBot - Cannot cancel`;

    return await new Promise((resolve, reject) => {
      google.calendar('v3').events.delete({
        'calendarId': selected[0].id,
        'eventId': cancellerReservations[0].id,
        'auth': auth
      }, (err, response) => {
        if (err)
          reject('The API (calendar.events.delete) returned an error: ' + err);
        resolve(`The reservation of ${selected[0].name} at ${moment(cancellerReservations[0].start).format('H:mm')} by ${canceller.email} has been cancelled`);
      });
    });
  }
}

module.exports = CalendarService;<commit_msg>Fix calendar booking availability check<commit_after>const Bluebird = require('bluebird');
const fs = Bluebird.promisifyAll(require('fs'));
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const moment = require('moment');
moment.locale('fi');

class CalendarService {
  constructor(calendars) {
    this.calendars = calendars;
  }

  async getEventsText(eventCount = 1) {
    const events = await this.getEvents(eventCount);
    return events.reduce((acc, cur) => {
      const start = moment(cur.start).format('DD.MM. HH:mm');
      const end = moment(cur.end).format('HH:mm');
      return `${acc}${acc !== '' ? '\n' : ''}${cur.name} - ${start} to ${end} - ${cur.summary}`
    }, 'Next 2 reservations:');
  }

  async getFreeText() {
    const events = await this.getEvents();
    return this.calendars.reduce((acc, cur) => {
      const e = events.find(e => e.name == cur.name);

      if (!e)
        return `${acc}${acc !== '' ? '\n' : ''}${cur.name} - indefinitely`;

      const diff = moment.duration(moment(e.start).diff(moment()));
      const diffAsHours = diff.asHours();

      if (diffAsHours > 0 && diffAsHours < 1) {
        return `${acc}${acc !== '' ? '\n' : ''}${e.name} - ${diff.asMinutes().toFixed(0)} minutes`
      } else if (diffAsHours > 0) {
        return `${acc}${acc !== '' ? '\n' : ''}${e.name} - ${diffAsHours.toFixed(1)} hours`
      } else {
        return acc;
      }
    }, '');
  }

  async getEvents(eventCount = 1) {
    const client = await this.getOAuthClient();
    return await this.getCurrentOrNextEvents(eventCount, client);
  }

  async bookEvent(booker, meetingRoom, durationMinutes = 15) {
    const client = await this.getOAuthClient();
    return this.bookMeetingRoom(booker, meetingRoom, durationMinutes, client);
  }

  async cancelEvent(canceller, meetingRoom) {
    const client = await this.getOAuthClient();
    return await this.cancelMeeting(canceller, meetingRoom, client);
  }

  async getOAuthClient() {
    const secretPromise = fs.readFileAsync('client_secret.json');
    const tokenPromise = fs.readFileAsync('calendar-authToken.json');
    const secret = await secretPromise;
    const token = await tokenPromise;

    const credentials = JSON.parse(secret);
    const clientSecret = credentials.installed.client_secret;
    const clientId = credentials.installed.client_id;
    const redirectUrl = credentials.installed.redirect_uris[0];

    const auth = new googleAuth();
    const oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
    oauth2Client.credentials = JSON.parse(token);
    return oauth2Client;
  }

  async getCurrentOrNextEvents(eventCount, auth) {
    const promises = this.calendars.map(c => {
      return this.getCalendarEvents(c.name, c.id, eventCount, auth);
    }, this);

    return await Promise.all(promises).then(results => {
      return results.reduce((p, [success, result]) => {
        return success ? p.concat(result) : p;
      }, []);
    });
  }

  getCalendarEvents(calendarName, calendarId, eventCount, auth) {
    const params = {
      auth: auth,
      calendarId: calendarId,
      timeMin: (new Date()).toISOString(),
      maxResults: eventCount,
      singleEvents: true,
      orderBy: 'startTime'
    };
    return new Promise((resolve, reject) => {
      google.calendar('v3').events.list(params, (err, response) => {
        if (err) {
          resolve([false, 'The API (calendar.events.list) returned an error: ' + err]);
          return;
        }

        const isPublic = (item) => item.visibility !== 'private';

        const events = response.items.map(e => {
          return {
            id: e.id,
            name: calendarName,
            start: e.start.dateTime || e.start.date,
            end: e.end.dateTime || e.end.date,
            summary: isPublic(e) ? e.summary : 'Private',
            description: isPublic(e) ? e.description : 'Private',
            attendees: isPublic(e) ? e.attendees : 'Private',
            creator: e.creator
          }
        });

        resolve([true, events]);
      });
    });
  }

  async bookMeetingRoom(booker, roomName, durationMinutes, auth) {
    if (!roomName)
      return `Define a room name. (${this.calendars.map(c => c.name)})`;

    const selected = this.calendars.filter(c => c.name.toUpperCase() == roomName.toUpperCase());

    if (selected.length == 0)
      return `${roomName} not found. (${this.calendars.map(c => c.name)})`;

    const [success, nextReservation] = await this.getCalendarEvents(selected[0].name, selected[0].id, 1, auth);

    if (!success)
      return `Failed to get the calendar events`;

    const start = new Date();
    const end = new Date(start.getTime() + durationMinutes * 60000);

    if (nextReservation[0]) {
      const bookStart = moment(start);
      const bookEnd = moment(end);
      const nextResStart = moment(new Date(nextReservation[0].start));
      const nextResEnd = moment(new Date(nextReservation[0].end));

      if (bookStart.isBetween(nextResStart, nextResEnd)
        || bookEnd.isBetween(nextResStart, nextResEnd)
        || nextResStart.isBetween(bookStart, bookEnd)
        || nextResEnd.isBetween(bookStart, bookEnd))
        return `Can't book ${roomName} for ${durationMinutes} minutes at ${bookStart.format('H:mm')}. The room is already reserved from ${nextResStart.format('H:mm')} till ${nextResEnd.format('H:mm')}.`;
    }

    const event = {
      'summary': `${ booker.name || booker.email } - SlackBot quick booking`,
      'description': `A quick booking made from SlackBot for ${booker.name} - ${booker.email}`,
      'start': {
        'dateTime': start
      },
      'end': {
        'dateTime': end
      },
      'attendees': [{
        'email': booker.email
      }]
    };

    return await new Promise((resolve, reject) => {
      google.calendar('v3').events.insert({
        'auth': auth,
        'calendarId': selected[0].id,
        'resource': event
      }, (err, response) => {
        if (err) {
          reject(`The API (calendar.events.insert) returned an error: ${err}\ncalendarId: ${selected[0].id}\nresource: ${JSON.stringify(event, null, 4)}`);
        }
        resolve(`${selected[0].name} booked for ${durationMinutes} minutes at ${moment(start).format('H:mm')}`);
      });
    });
  }

  async cancelMeeting(canceller, roomName, auth) {
    if (!roomName)
      return `Define a room name. (${this.calendars.map(c => c.name)})`;

    const selected = this.calendars.filter(c => c.name.toUpperCase() == roomName.toUpperCase());

    if (selected.length == 0)
      return `${roomName} not found. (${this.calendars.map(c => c.name)})`;

    const [success, upcomingReservations] = await this.getCalendarEvents(selected[0].name, selected[0].id, 1, auth);

    if (!success)
      return `Failed to get the calendar events`;

    const cancellerReservations = upcomingReservations.filter(reservation =>
      reservation.attendees && reservation.attendees.some(a => a.email == canceller.email) &&
            reservation.description && reservation.description.includes('A quick booking made from SlackBot for'));

    if (cancellerReservations.length == 0)
      return `${canceller.email} has not made any room reservations with SlackBot - Cannot cancel`;

    return await new Promise((resolve, reject) => {
      google.calendar('v3').events.delete({
        'calendarId': selected[0].id,
        'eventId': cancellerReservations[0].id,
        'auth': auth
      }, (err, response) => {
        if (err)
          reject('The API (calendar.events.delete) returned an error: ' + err);
        resolve(`The reservation of ${selected[0].name} at ${moment(cancellerReservations[0].start).format('H:mm')} by ${canceller.email} has been cancelled`);
      });
    });
  }
}

module.exports = CalendarService;<|endoftext|>