<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);
    }

    /**
     * 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);
            })
            .catch((err) => {
                // TODO: We might need to do more here.  I think it's OK to just
                // log though.  This only gets used to show slicer info through
                // the API.  We wouldn't want to disrupt the cluster master
                // for rare failures to reach the k8s API.
                logger.error(`Error listing teraslice pods in k8s: ${err}`);
            });
    }

    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;
        // TODO: This will be addressed in the future, see:
        //   https://github.com/terascope/teraslice/issues/744
        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);

        // TODO: This should try slicerAllocationAttempts times??
        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,
        getClusterStats,
        getSlicerStats,
        allocateWorkers,
        allocateSlicer,
        // shutdown,  FIXME: ??
        stopExecution,
        pauseExecution,
        resumeExecution,
        removeWorkers,
        addWorkers,
        setWorkers,
        readyForAllocation,
        // clusterAvailable TODO: return false if k8s API unavailable, not in use
    };

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

    return _initialize();
};
<commit_msg>Using the state-utils module in the k8s cluster manager<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 stateUtils = require('../state-utils');
// 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);
    }

    /**
     * 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);
            })
            .catch((err) => {
                // TODO: We might need to do more here.  I think it's OK to just
                // log though.  This only gets used to show slicer info through
                // the API.  We wouldn't want to disrupt the cluster master
                // for rare failures to reach the k8s API.
                logger.error(`Error listing teraslice pods in k8s: ${err}`);
            });
    }

    function getClusterStats() {
        return clusterStats;
    }

    /**
     * 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;
        // TODO: This will be addressed in the future, see:
        //   https://github.com/terascope/teraslice/issues/744
        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);

        // TODO: This should try slicerAllocationAttempts times??
        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 = stateUtils._iterateState(
            clusterState,
            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 = stateUtils._iterateState(
            clusterState,
            worker => worker.assignment === 'execution_controller' && worker.ex_id === exId
        );
        return _msgWorkersWithExId(exId, { message: 'cluster:execution:pause' }, workers);
    }

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

    const getSlicerStats = stateUtils.newGetSlicerStats(clusterState, context, messaging);

    const api = {
        getClusterState,
        getClusterStats,
        getSlicerStats,
        allocateWorkers,
        allocateSlicer,
        // shutdown,  FIXME: ??
        stopExecution,
        pauseExecution,
        resumeExecution,
        removeWorkers,
        addWorkers,
        setWorkers,
        readyForAllocation,
        // clusterAvailable TODO: return false if k8s API unavailable, not in use
    };

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

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


<commit_before>import { kea } from 'kea'
import PropTypes from 'prop-types'
import { message, Modal } from 'antd'
import { push } from "connected-react-router"

import explorerLogic from '../logic'
import client from 'lib/client'

const connectionsService = client.service('connections')
const connectionTestService = client.service('connection-test')

export default kea({
  path: () => ['scenes', 'connections', 'index'],

  connect: {
    values: [
      explorerLogic, ['connection']
    ]
  },

  actions: () => ({
    setConnection: connection => ({ connection }),

    loadConnections: true,
    connectionsLoaded: connections => ({ connections }),

    addConnection: ({ keyword, url, structurePath, timeoutMs }) => ({ keyword, url, structurePath, timeoutMs }),
    connectionAdded: (connection) => ({ connection }),

    editConnection: (id, url, structurePath, timeoutMs) => ({ id, url, structurePath, timeoutMs }),
    connectionEdited: (connection) => ({ connection }),

    viewStructure: (id) => ({ id }),

    testConnection: (url, structurePath) => ({ url, structurePath }),
    testSuccess: true,
    testFailure: true,

    confirmRemoveConnection: (id) => ({ id }),
    removeConnection: (id) => ({ id }),
    connectionRemoved: (id) => ({ id }),

    openAddConnection: true,
    closeConnection: true,
    openEditConnection: id => ({ id })
  }),

  reducers: ({ actions }) => ({
    isLoading: [false, PropTypes.bool, {
      [actions.loadConnections]: () => true,
      [actions.connectionsLoaded]: () => false
    }],

    connections: [{}, PropTypes.object, {
      [actions.connectionsLoaded]: (_, payload) => {
        let newState = {}
        payload.connections.forEach(connection => {
          newState[connection._id] = connection
        })
        return newState
      },
      [actions.connectionAdded]: (state, payload) => {
        return Object.assign({}, state, { [payload.connection._id]: payload.connection })
      },
      [actions.connectionEdited]: (state, payload) => {
        return Object.assign({}, state, { [payload.connection._id]: payload.connection })
      },
      [actions.connectionRemoved]: (state, payload) => {
        const { [payload.id]: discard, ...rest } = state // eslint-disable-line
        return rest
      }
    }],

    isAddOpen: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => true,
      [actions.closeConnection]: () => false,
      [actions.connectionAdded]: () => false,
      [actions.openEditConnection]: () => false
    }],

    isEditOpen: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => true,
      [actions.closeConnection]: () => false,
      [actions.connectionEdited]: () => false,
      [actions.connectionRemoved]: () => false
    }],

    isSaving: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => false,
      [actions.addConnection]: () => true,
      [actions.editConnection]: () => true,
      [actions.removeConnection]: () => true,
      [actions.connectionAdded]: () => false,
      [actions.connectionEdited]: () => false,
      [actions.connectionRemoved]: () => false
    }],

    didTest: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => false,
      [actions.testConnection]: (_, { url }) => !!url
    }],

    isTesting: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => false,
      [actions.testConnection]: () => true,
      [actions.testSuccess]: () => false,
      [actions.testFailure]: () => false
    }],

    testPassed: [false, PropTypes.bool, {
      [actions.testConnection]: () => false,
      [actions.testSuccess]: () => true,
      [actions.testFailure]: () => false
    }],

    editingConnectionId: [null, PropTypes.string, {
      [actions.openEditConnection]: (_, payload) => payload.id
    }]
  }),

  selectors: ({ constants, selectors }) => ({
    sortedConnections: [
      () => [selectors.connections],
      (connections) => Object.values(connections).sort((a, b) => (a.keyword || '').localeCompare(b.keyword || '')),
      PropTypes.array
    ],

    selectedConnection: [
      () => [selectors.sortedConnections, selectors.connection],
      (sortedConnections, connection) => sortedConnections.filter(c => c.keyword === connection)[0],
      PropTypes.object
    ],

    otherConnections: [
      () => [selectors.sortedConnections, selectors.connection],
      (sortedConnections, connection) => sortedConnections.filter(c => c.keyword !== connection),
      PropTypes.array
    ],

    editingConnection: [
      () => [selectors.editingConnectionId, selectors.connections],
      (editingConnectionId, connections) => editingConnectionId ? connections[editingConnectionId] : null,
      PropTypes.object
    ]
  }),

  events: ({ actions }) => ({
    afterMount: () => {
      actions.loadConnections()
    }
  }),

  listeners: ({ actions, dispatch }) => ({
    [actions.loadConnections]: async function () {
      const connections = await connectionsService.find({})
      actions.connectionsLoaded(connections.data)
    },

    [actions.addConnection]: async function ({ keyword, url, structurePath, timeoutMs }) {
      const connection = await connectionsService.create({ keyword, url, structurePath, timeoutMs })
      actions.connectionAdded(connection)
      message.success(`Connection "${keyword}" added!`);
    },

    [actions.editConnection]: async function ({ id, url, structurePath, timeoutMs }) {
      const connection = await connectionsService.patch(id, { url, structurePath, timeoutMs })
      actions.connectionEdited(connection)
      message.success('Changes saved!');
    },

    [actions.confirmRemoveConnection]: async function ({ id }) {
      Modal.confirm({
        title: 'Are you sure delete this connection?',
        content: 'This can not be undone!',
        okText: 'Yes',
        okType: 'danger',
        cancelText: 'No',
        onOk() {
          actions.removeConnection(id)
        }
      });

    },

    [actions.removeConnection]: async function ({ id }) {
      await connectionsService.remove(id)
      actions.connectionRemoved(id)
    },

    [actions.testConnection]: async function ({ url, structurePath }) {
      if (url) {
        try {
          const result = await connectionTestService.find({query: {url, structurePath}})

          if (result.working) {
            actions.testSuccess()
          } else {
            actions.testFailure()
          }
        } catch (e) {
          actions.testFailure()
        }
      } else {
        actions.testFailure()
      }
    },

    [actions.viewStructure]: async function ({ id }) {
      dispatch(push(`/connections/${id}`))
    }
  })
})
<commit_msg>sync explorer connections<commit_after>import { kea } from 'kea'
import PropTypes from 'prop-types'
import { message, Modal } from 'antd'

import explorerLogic from '../logic'
import client from 'lib/client'

const connectionsService = client.service('connections')
const connectionTestService = client.service('connection-test')

export default kea({
  path: () => ['scenes', 'connections', 'index'],

  connect: {
    values: [
      explorerLogic, ['connection']
    ],
    actions: [
      explorerLogic, ['setConnections as setExplorerConnections']
    ]
  },

  actions: () => ({
    loadConnections: true,
    connectionsLoaded: connections => ({ connections }),

    addConnection: ({ keyword, url, structurePath, timeoutMs }) => ({ keyword, url, structurePath, timeoutMs }),
    connectionAdded: (connection) => ({ connection }),

    editConnection: (id, url, structurePath, timeoutMs) => ({ id, url, structurePath, timeoutMs }),
    connectionEdited: (connection) => ({ connection }),

    testConnection: (url, structurePath) => ({ url, structurePath }),
    testSuccess: true,
    testFailure: true,

    confirmRemoveConnection: (id) => ({ id }),
    removeConnection: (id) => ({ id }),
    connectionRemoved: (id) => ({ id }),

    openAddConnection: true,
    closeConnection: true,
    openEditConnection: id => ({ id })
  }),

  reducers: ({ actions }) => ({
    isLoading: [false, PropTypes.bool, {
      [actions.loadConnections]: () => true,
      [actions.connectionsLoaded]: () => false
    }],

    connections: [{}, PropTypes.object, {
      [actions.connectionsLoaded]: (_, payload) => {
        let newState = {}
        payload.connections.forEach(connection => {
          newState[connection._id] = connection
        })
        return newState
      },
      [actions.connectionAdded]: (state, payload) => {
        return Object.assign({}, state, { [payload.connection._id]: payload.connection })
      },
      [actions.connectionEdited]: (state, payload) => {
        return Object.assign({}, state, { [payload.connection._id]: payload.connection })
      },
      [actions.connectionRemoved]: (state, payload) => {
        const { [payload.id]: discard, ...rest } = state // eslint-disable-line
        return rest
      }
    }],

    isAddOpen: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => true,
      [actions.closeConnection]: () => false,
      [actions.connectionAdded]: () => false,
      [actions.openEditConnection]: () => false
    }],

    isEditOpen: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => true,
      [actions.closeConnection]: () => false,
      [actions.connectionEdited]: () => false,
      [actions.connectionRemoved]: () => false
    }],

    isSaving: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => false,
      [actions.addConnection]: () => true,
      [actions.editConnection]: () => true,
      [actions.removeConnection]: () => true,
      [actions.connectionAdded]: () => false,
      [actions.connectionEdited]: () => false,
      [actions.connectionRemoved]: () => false
    }],

    didTest: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => false,
      [actions.testConnection]: (_, { url }) => !!url
    }],

    isTesting: [false, PropTypes.bool, {
      [actions.openAddConnection]: () => false,
      [actions.openEditConnection]: () => false,
      [actions.testConnection]: () => true,
      [actions.testSuccess]: () => false,
      [actions.testFailure]: () => false
    }],

    testPassed: [false, PropTypes.bool, {
      [actions.testConnection]: () => false,
      [actions.testSuccess]: () => true,
      [actions.testFailure]: () => false
    }],

    editingConnectionId: [null, PropTypes.string, {
      [actions.openEditConnection]: (_, payload) => payload.id
    }]
  }),

  selectors: ({ constants, selectors }) => ({
    sortedConnections: [
      () => [selectors.connections],
      (connections) => Object.values(connections).sort((a, b) => (a.keyword || '').localeCompare(b.keyword || '')),
      PropTypes.array
    ],

    selectedConnection: [
      () => [selectors.sortedConnections, selectors.connection],
      (sortedConnections, connection) => sortedConnections.filter(c => c.keyword === connection)[0],
      PropTypes.object
    ],

    otherConnections: [
      () => [selectors.sortedConnections, selectors.connection],
      (sortedConnections, connection) => sortedConnections.filter(c => c.keyword !== connection),
      PropTypes.array
    ],

    editingConnection: [
      () => [selectors.editingConnectionId, selectors.connections],
      (editingConnectionId, connections) => editingConnectionId ? connections[editingConnectionId] : null,
      PropTypes.object
    ]
  }),

  events: ({ actions }) => ({
    afterMount: () => {
      actions.loadConnections()
    }
  }),

  sharedListeners: ({ actions, values }) => ({
    updateExplorerConnections: () => {
      const { connections } = values
      actions.setExplorerConnections(Object.values(connections))
    }
  }),

  listeners: ({ actions, sharedListeners }) => ({
    [actions.connectionsLoaded]: sharedListeners.updateExplorerConnections,
    [actions.connectionAdded]: sharedListeners.updateExplorerConnections,
    [actions.connectionEdited]: sharedListeners.updateExplorerConnections,
    [actions.connectionRemoved]: sharedListeners.updateExplorerConnections,

    [actions.loadConnections]: async function () {
      const connections = await connectionsService.find({})
      actions.connectionsLoaded(connections.data)
    },

    [actions.addConnection]: async function ({ keyword, url, structurePath, timeoutMs }) {
      const connection = await connectionsService.create({ keyword, url, structurePath, timeoutMs })
      actions.connectionAdded(connection)
      message.success(`Connection "${keyword}" added!`)
    },

    [actions.editConnection]: async function ({ id, url, structurePath, timeoutMs }) {
      const connection = await connectionsService.patch(id, { url, structurePath, timeoutMs })
      actions.connectionEdited(connection)
      message.success('Changes saved!')
    },

    [actions.confirmRemoveConnection]: async function ({ id }) {
      Modal.confirm({
        title: 'Are you sure delete this connection?',
        content: 'This can not be undone!',
        okText: 'Yes',
        okType: 'danger',
        cancelText: 'No',
        onOk () {
          actions.removeConnection(id)
        }
      })
    },

    [actions.removeConnection]: async function ({ id }) {
      await connectionsService.remove(id)
      actions.connectionRemoved(id)
    },

    [actions.testConnection]: async function ({ url, structurePath }) {
      if (url) {
        try {
          const result = await connectionTestService.find({query: {url, structurePath}})

          if (result.working) {
            actions.testSuccess()
          } else {
            actions.testFailure()
          }
        } catch (e) {
          actions.testFailure()
        }
      } else {
        actions.testFailure()
      }
    }
  })
})
<|endoftext|>


<commit_before>// External Requirements
var React = require('react/addons');
var classNames = require('classnames');

// Components
var CharacterCount = require('./CharacterCount');

// Mixins
var FormUtility = require('./FormUtilityMixin');

/**
 * TextArea Component
 * A component used for building long-form text input forms.
 *
 * @class TextArea
 * @type {ReactComponent}
 *
 * @prop  {Number=}    	limit           Max character limit for text area
 * @prop  {String=}    	label
 * @prop  {String=}    	placeholder
 * @prop  {String=}    	helpText
 * @prop  {Boolean=}		disabled
 * @prop  {!String}   	fieldKey
 * @prop 	{Boolean=}		required
 *
 * @memberOf FormComponents
 * @see {@link FormComponents}
 *
 * @example
 * render: function() { return(
 *

 *
 */
var TextArea = React.createClass({
	propTypes: {
		limit: React.PropTypes.number,
		label: React.PropTypes.string,
		placeholder: React.PropTypes.string,
		helpText: React.PropTypes.string,
		disabled: React.PropTypes.bool,
		fieldKey: React.PropTypes.string.isRequired,
		required: React.PropTypes.bool
	},

	mixins: [FormUtility],

	/**
	 * Gets the initial state of the component.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @return {Object}
	 */
	getInitialState: function () {
		return {
			isValid: true,
			renderCharacterCounter: false,
			fieldValue: null
		};
	},

	/**
	 * Set fieldValue through props based on attached resource.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @param  {Object}		nextProps	[description]
	 * @return {null}
	 */
	componentWillReceiveProps: function (nextProps) {
		this.setState({
			fieldValue: this._findValueByPath(nextProps.bindResource, nextProps.fieldKey)
		});
	},

	/**
	 * When the field is out of focus, hide the text area character count.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @param					event
	 * @return 	{null}
	 */
	handleBlur: function (event) {
		this.setState({renderCharacterCounter: false});
	},

	/**
	 * When the field is in focus, show the text area character count.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @param      		event
	 * @return {null}
	 */
	handleFocus: function (event) {
		if (typeof this.props.limit !== 'undefined') {
			this.setState({renderCharacterCounter: true});
		}
	},

	/**
	 * Renders the TextArea component.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @return {JSX}
	 */
	render: function () {
		var classes = classNames({
			"form-group": true,
			"has-error": this._checkFieldIsValid()
		});

		return (

				{this.props.label}

				<textarea className="form-control"
					rows={this.props.rows || 3}
					placeholder={this.props.placeholder || ''}
					disabled={this.props.disabled}
					onChange={this.handleChange}
					onFocus={this.handleFocus}
					onBlur={this.handleBlur}
					value={this.state.fieldValue || ''} ></textarea>

				{this.state.renderCharacterCounter && (
					<CharacterCount input={this.state.fieldValue} limit={this.props.limit} />
				)}

				{this._shouldRenderHelpBlock(this.props.helpText)}

		);
	}
});

module.exports = TextArea;
<commit_msg>fix doc typo in TextArea component<commit_after>// External Requirements
var React = require('react/addons');
var classNames = require('classnames');

// Components
var CharacterCount = require('./CharacterCount');

// Mixins
var FormUtility = require('./FormUtilityMixin');

/**
 * TextArea Component
 * A component used for building long-form text input forms.
 *
 * @class TextArea
 * @type {ReactComponent}
 *
 * @prop  {Number=}    	limit           Max character limit for text area
 * @prop  {String=}    	label
 * @prop  {String=}    	placeholder
 * @prop  {String=}    	helpText
 * @prop  {Boolean=}		disabled
 * @prop  {!String}   	fieldKey
 * @prop 	{Boolean=}		required
 *
 * @memberOf FormComponents
 * @see {@link FormComponents}
 *
 * @example
 * render: function() { return(
 *

 * )}
 *
 */
var TextArea = React.createClass({
	propTypes: {
		limit: React.PropTypes.number,
		label: React.PropTypes.string,
		placeholder: React.PropTypes.string,
		helpText: React.PropTypes.string,
		disabled: React.PropTypes.bool,
		fieldKey: React.PropTypes.string.isRequired,
		required: React.PropTypes.bool
	},

	mixins: [FormUtility],

	/**
	 * Gets the initial state of the component.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @return {Object}
	 */
	getInitialState: function () {
		return {
			isValid: true,
			renderCharacterCounter: false,
			fieldValue: null
		};
	},

	/**
	 * Set fieldValue through props based on attached resource.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @param  {Object}		nextProps	[description]
	 * @return {null}
	 */
	componentWillReceiveProps: function (nextProps) {
		this.setState({
			fieldValue: this._findValueByPath(nextProps.bindResource, nextProps.fieldKey)
		});
	},

	/**
	 * When the field is out of focus, hide the text area character count.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @param					event
	 * @return 	{null}
	 */
	handleBlur: function (event) {
		this.setState({renderCharacterCounter: false});
	},

	/**
	 * When the field is in focus, show the text area character count.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @param      		event
	 * @return 	{null}
	 */
	handleFocus: function (event) {
		if (typeof this.props.limit !== 'undefined') {
			this.setState({renderCharacterCounter: true});
		}
	},

	/**
	 * Renders the TextArea component.
	 *
	 * @memberOf FormComponents.TextArea
	 *
	 * @return {JSX}
	 */
	render: function () {
		var classes = classNames({
			"form-group": true,
			"has-error": this._checkFieldIsValid()
		});

		return (

				{this.props.label}

				<textarea className="form-control"
					rows={this.props.rows || 3}
					placeholder={this.props.placeholder || ''}
					disabled={this.props.disabled}
					onChange={this.handleChange}
					onFocus={this.handleFocus}
					onBlur={this.handleBlur}
					value={this.state.fieldValue || ''} ></textarea>

				{this.state.renderCharacterCounter && (
					<CharacterCount input={this.state.fieldValue} limit={this.props.limit} />
				)}

				{this._shouldRenderHelpBlock(this.props.helpText)}

		);
	}
});

module.exports = TextArea;
<|endoftext|>


<commit_before>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt

cur_frm.add_fetch('employee','employee_name','employee_name');
cur_frm.add_fetch('employee','company','company');

frappe.ui.form.on("Leave Application", {
	onload: function(frm) {
		if (!frm.doc.posting_date) {
			frm.set_value("posting_date", frappe.datetime.get_today());
		}

		frm.set_query("leave_approver", function() {
			return {
				query: "erpnext.hr.doctype.leave_application.leave_application.get_approvers",
				filters: {
					employee: frm.doc.employee
				}
			};
		});

		frm.set_query("employee", erpnext.queries.employee);

	},

	validate: function(frm) {
		frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
	},

	refresh: function(frm) {
		if (frm.is_new()) {
			frm.set_value("status", "Open");
			frm.trigger("calculate_total_days");
		}
	},

	leave_approver: function(frm) {
		if(frm.doc.leave_approver){
			frm.set_value("leave_approver_name", frappe.user.full_name(frm.doc.leave_approver));
		}
	},

	employee: function(frm) {
		frm.trigger("get_leave_balance");
	},

	leave_type: function(frm) {
		frm.trigger("get_leave_balance");
	},

	half_day: function(frm) {
		if (frm.doc.from_date == frm.doc.to_date) {
			frm.set_value("half_day_date", frm.doc.from_date);
		}
		else {
			frm.trigger("half_day_datepicker");
		}
		frm.trigger("calculate_total_days");
	},

	from_date: function(frm) {
		frm.trigger("half_day_datepicker");
		frm.trigger("calculate_total_days");
	},

	to_date: function(frm) {
		frm.trigger("half_day_datepicker");
		frm.trigger("calculate_total_days");
	},

	half_day_date(frm) {
		frm.trigger("calculate_total_days");
	},

	half_day_datepicker: function(frm) {
		frm.set_value('half_day_date', '');
		var half_day_datepicker = frm.fields_dict.half_day_date.datepicker;
		half_day_datepicker.update({
			minDate: frappe.datetime.str_to_obj(frm.doc.from_date),
			maxDate: frappe.datetime.str_to_obj(frm.doc.to_date)
		})
	},

	get_leave_balance: function(frm) {
		if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) {
			return frappe.call({
				method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
				args: {
					employee: frm.doc.employee,
					date: frm.doc.from_date,
					leave_type: frm.doc.leave_type,
					consider_all_leaves_in_the_allocation_period: true
				},
				callback: function(r) {
					if (!r.exc && r.message) {
						frm.set_value('leave_balance', r.message);
					}
				}
			});
		}
	},

	calculate_total_days: function(frm) {
		if(frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) {
				// server call is done to include holidays in leave days calculations
			return frappe.call({
				method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days',
				args: {
					"employee": frm.doc.employee,
					"leave_type": frm.doc.leave_type,
					"from_date": frm.doc.from_date,
					"to_date": frm.doc.to_date,
					"half_day": frm.doc.half_day,
					"half_day_date": frm.doc.half_day_date,
				},
				callback: function(r) {
					if (r && r.message) {
						frm.set_value('total_leave_days', r.message);
						frm.trigger("get_leave_balance");
					}
				}
			});
		}
	},
});
<commit_msg>Clear leave balance on changing leave type<commit_after>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt

cur_frm.add_fetch('employee','employee_name','employee_name');
cur_frm.add_fetch('employee','company','company');

frappe.ui.form.on("Leave Application", {
	onload: function(frm) {
		if (!frm.doc.posting_date) {
			frm.set_value("posting_date", frappe.datetime.get_today());
		}

		frm.set_query("leave_approver", function() {
			return {
				query: "erpnext.hr.doctype.leave_application.leave_application.get_approvers",
				filters: {
					employee: frm.doc.employee
				}
			};
		});

		frm.set_query("employee", erpnext.queries.employee);

	},

	validate: function(frm) {
		frm.toggle_reqd("half_day_date", frm.doc.half_day == 1);
	},

	refresh: function(frm) {
		if (frm.is_new()) {
			frm.set_value("status", "Open");
			frm.trigger("calculate_total_days");
		}
	},

	leave_approver: function(frm) {
		if(frm.doc.leave_approver){
			frm.set_value("leave_approver_name", frappe.user.full_name(frm.doc.leave_approver));
		}
	},

	employee: function(frm) {
		frm.trigger("get_leave_balance");
	},

	leave_type: function(frm) {
		frm.trigger("get_leave_balance");
	},

	half_day: function(frm) {
		if (frm.doc.from_date == frm.doc.to_date) {
			frm.set_value("half_day_date", frm.doc.from_date);
		}
		else {
			frm.trigger("half_day_datepicker");
		}
		frm.trigger("calculate_total_days");
	},

	from_date: function(frm) {
		frm.trigger("half_day_datepicker");
		frm.trigger("calculate_total_days");
	},

	to_date: function(frm) {
		frm.trigger("half_day_datepicker");
		frm.trigger("calculate_total_days");
	},

	half_day_date(frm) {
		frm.trigger("calculate_total_days");
	},

	half_day_datepicker: function(frm) {
		frm.set_value('half_day_date', '');
		var half_day_datepicker = frm.fields_dict.half_day_date.datepicker;
		half_day_datepicker.update({
			minDate: frappe.datetime.str_to_obj(frm.doc.from_date),
			maxDate: frappe.datetime.str_to_obj(frm.doc.to_date)
		})
	},

	get_leave_balance: function(frm) {
		if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) {
			return frappe.call({
				method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on",
				args: {
					employee: frm.doc.employee,
					date: frm.doc.from_date,
					leave_type: frm.doc.leave_type,
					consider_all_leaves_in_the_allocation_period: true
				},
				callback: function(r) {
					if (!r.exc && r.message) {
						frm.set_value('leave_balance', r.message);
					}
					else {
						frm.set_value('leave_balance', "0");
					}
				}
			});
		}
	},

	calculate_total_days: function(frm) {
		if(frm.doc.from_date && frm.doc.to_date && frm.doc.employee && frm.doc.leave_type) {
				// server call is done to include holidays in leave days calculations
			return frappe.call({
				method: 'erpnext.hr.doctype.leave_application.leave_application.get_number_of_leave_days',
				args: {
					"employee": frm.doc.employee,
					"leave_type": frm.doc.leave_type,
					"from_date": frm.doc.from_date,
					"to_date": frm.doc.to_date,
					"half_day": frm.doc.half_day,
					"half_day_date": frm.doc.half_day_date,
				},
				callback: function(r) {
					if (r && r.message) {
						frm.set_value('total_leave_days', r.message);
						frm.trigger("get_leave_balance");
					}
				}
			});
		}
	},
});
<|endoftext|>


<commit_before>'use strict';

exports = module.exports = MessageQueueAMQP;

var MessageQueue = require('./..');

var amqp = require('amqplib');
var util = require('util');
var Q = require('q');
var debug = require('debug')('fire:amqp');

/**
 * AMQP implemention of the message queue.
 *
 * @param {String} connectionString The connection string. This is the `AMQP_URL` environment variable.
 */
function MessageQueueAMQP(connectionString) {
	MessageQueue.call(this, connectionString);

	this.connection = null;
}
util.inherits(MessageQueueAMQP, MessageQueue);

/**
 * Connects to the AMQP instance e.g. RabbitMQ.
 *
 * @return {Promise}
 */
MessageQueueAMQP.prototype.connect = function() {
	var self = this;
	return amqp.connect(this.connectionString)
		.then(function(connection) {
			self.connection = connection;
		});
};

/**
 * Disconnections from the AMQP instance.
 *
 * @return {Promise}
 */
MessageQueueAMQP.prototype.disconnect = function() {
	return this.connection && this.connection.close();
};

/**
 * Creates a channel and publishes the implementation specific `taskMap` to queue named `queueName`.
 *
 * @param {String} queueName
 * @param {Dictionary} taskMap
 */
MessageQueueAMQP.prototype.createTask = function(queueName, taskMap) {
	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertQueue(queueName);

			// TODO: Method name and params is workers specific. Use messageMap instead.

			return result.then(function() {
				return channel.sendToQueue(queueName, new Buffer(JSON.stringify(taskMap)));
			});
		});
};

/**
 * Starts consuming tasks on the given queue. Each message gets serialized to a dictionary.
 *
 * @param {String}   queueName The name of the queue.
 * @param {Function} callback  The callback invoked whenever a new message is received. The message is acknowledged only if the callback resolves the returned promise. The message is not acknowledged when the callback returns and rejects the returned promise.
 */
MessageQueueAMQP.prototype.startConsumingTasks = function(queueName, callback) {
	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertQueue(queueName);

			return result.then(function() {
				return channel.consume(queueName, function(message) {
					// Normalize the message to a standard format so we can switch message queue providers.
					var messageMap = JSON.parse(message.content);

					Q.when(callback(messageMap))
						.then(function() {
							// Acknowledge the message if we didn't encounter an error.
							channel.ack(message);
						})
						.fail(function(error) {
							// We are not acknoldiging the message if the callback rejects with an error.

							// TODO: Should we send a nack message?

							debug(error);
						})
						.done();
				});
			});
		});
};

/**
 * Publishes a message to a fanout exchange. This is used for Publish/Subscribe mechanisms to send messages to many consumers at once.
 *
 * @param {String} exchangeName The name of the exchange.
 * @param {Dictionary} messageMap   The message to sent to the consumers. This is JSONified and send directly.
 */
MessageQueueAMQP.prototype.publishMessage = function(exchangeName, messageMap) {
	debug('Publish message ' + exchangeName);

	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertExchange(exchangeName, 'fanout', {});

			return result.then(function() {
				return channel.publish(exchangeName, '', new Buffer(JSON.stringify(messageMap)));
			});
		});
};

/**
 * Starts consuming messages on a fanout exchange named `exchangeName`. This is used for Publish/Subscribe mechanisms, see {@link MessageQueueAMQP#publishMessage}.
 *
 * If you want a work queue type of system, see {@link MessageQueueAMQP#createTask} instead.
 *
 * @param {String}   exchangeName The name of the exchange.
 * @param {Function} callback     [description]
 */
MessageQueueAMQP.prototype.startConsumingMessages = function(exchangeName, callback) {
	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertExchange(exchangeName, 'fanout', {});

			result = result.then(function() {
				return channel.assertQueue('', {exclusive: true});
			});

			result = result.then(function(queue) {
				return channel.bindQueue(queue.queue, exchangeName);
			});

			return result.then(function() {
				return channel.consume('', function(message) {
					debug('Consume message.');

					// Normalize the message to a standard format so we can switch message queue providers.
					var messageMap = JSON.parse(message.content);

					callback(messageMap);
				}, {noAck: true});
			});
		});
};
<commit_msg>adds error message when connecting to broker fails<commit_after>'use strict';

exports = module.exports = MessageQueueAMQP;

var MessageQueue = require('./..');

var amqp = require('amqplib');
var util = require('util');
var Q = require('q');
var debug = require('debug')('fire:amqp');

/**
 * AMQP implemention of the message queue.
 *
 * @param {String} connectionString The connection string. This is the `AMQP_URL` environment variable.
 */
function MessageQueueAMQP(connectionString) {
	MessageQueue.call(this, connectionString);

	this.connection = null;
}
util.inherits(MessageQueueAMQP, MessageQueue);

/**
 * Connects to the AMQP instance e.g. RabbitMQ.
 *
 * @return {Promise}
 */
MessageQueueAMQP.prototype.connect = function() {
	var self = this;
	return amqp.connect(this.connectionString)
		.then(function(connection) {
			self.connection = connection;
		})
		.catch(function(error) {
			console.log('Warning: could not connect to AMQP broker.');
			console.log('Connection string: ' + self.connectionString);

			throw error;
		});
};

/**
 * Disconnections from the AMQP instance.
 *
 * @return {Promise}
 */
MessageQueueAMQP.prototype.disconnect = function() {
	return this.connection && this.connection.close();
};

/**
 * Creates a channel and publishes the implementation specific `taskMap` to queue named `queueName`.
 *
 * @param {String} queueName
 * @param {Dictionary} taskMap
 */
MessageQueueAMQP.prototype.createTask = function(queueName, taskMap) {
	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertQueue(queueName);

			// TODO: Method name and params is workers specific. Use messageMap instead.

			return result.then(function() {
				return channel.sendToQueue(queueName, new Buffer(JSON.stringify(taskMap)));
			});
		});
};

/**
 * Starts consuming tasks on the given queue. Each message gets serialized to a dictionary.
 *
 * @param {String}   queueName The name of the queue.
 * @param {Function} callback  The callback invoked whenever a new message is received. The message is acknowledged only if the callback resolves the returned promise. The message is not acknowledged when the callback returns and rejects the returned promise.
 */
MessageQueueAMQP.prototype.startConsumingTasks = function(queueName, callback) {
	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertQueue(queueName);

			return result.then(function() {
				return channel.consume(queueName, function(message) {
					// Normalize the message to a standard format so we can switch message queue providers.
					var messageMap = JSON.parse(message.content);

					Q.when(callback(messageMap))
						.then(function() {
							// Acknowledge the message if we didn't encounter an error.
							channel.ack(message);
						})
						.fail(function(error) {
							// We are not acknoldiging the message if the callback rejects with an error.

							// TODO: Should we send a nack message?

							debug(error);
						})
						.done();
				});
			});
		});
};

/**
 * Publishes a message to a fanout exchange. This is used for Publish/Subscribe mechanisms to send messages to many consumers at once.
 *
 * @param {String} exchangeName The name of the exchange.
 * @param {Dictionary} messageMap   The message to sent to the consumers. This is JSONified and send directly.
 */
MessageQueueAMQP.prototype.publishMessage = function(exchangeName, messageMap) {
	debug('Publish message ' + exchangeName);

	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertExchange(exchangeName, 'fanout', {});

			return result.then(function() {
				return channel.publish(exchangeName, '', new Buffer(JSON.stringify(messageMap)));
			});
		});
};

/**
 * Starts consuming messages on a fanout exchange named `exchangeName`. This is used for Publish/Subscribe mechanisms, see {@link MessageQueueAMQP#publishMessage}.
 *
 * If you want a work queue type of system, see {@link MessageQueueAMQP#createTask} instead.
 *
 * @param {String}   exchangeName The name of the exchange.
 * @param {Function} callback     [description]
 */
MessageQueueAMQP.prototype.startConsumingMessages = function(exchangeName, callback) {
	return this.connection.createChannel()
		.then(function(channel) {
			var result = channel.assertExchange(exchangeName, 'fanout', {});

			result = result.then(function() {
				return channel.assertQueue('', {exclusive: true});
			});

			result = result.then(function(queue) {
				return channel.bindQueue(queue.queue, exchangeName);
			});

			return result.then(function() {
				return channel.consume('', function(message) {
					debug('Consume message.');

					// Normalize the message to a standard format so we can switch message queue providers.
					var messageMap = JSON.parse(message.content);

					callback(messageMap);
				}, {noAck: true});
			});
		});
};
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

sap.ui.define([
		"jquery.sap.global",
		"sap/ui/core/UIComponent",
		"sap/ui/Device",
		"sap/ui/documentation/sdk/model/models",
		"sap/ui/documentation/sdk/controller/ErrorHandler",
		"sap/ui/model/json/JSONModel",
		"sap/ui/documentation/sdk/controller/util/ConfigUtil",
		"sap/ui/documentation/sdk/controller/util/APIInfo",
		"sap/ui/documentation/sdk/util/DocumentationRouter", // used via manifest.json
		"sap/m/ColumnListItem" // implements sap.m.TablePopin
	], function (jQuery, UIComponent, Device, models, ErrorHandler, JSONModel, ConfigUtil, APIInfo /*, DocumentationRouter, ColumnListItem*/) {
		"use strict";

		var aTreeContent = [],
			oLibsData = {},
			iTreeModelLimit = 1000000;

		return UIComponent.extend("sap.ui.documentation.sdk.Component", {

			metadata : {
				manifest : "json",
				includes : [
					"css/style.css"
				]
			},

			/**
			 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
			 * In this method, the device models are set and the router is initialized.
			 * @public
			 * @override
			 */
			init : function () {

				this._oErrorHandler = new ErrorHandler(this);

				// set the device model
				this.setModel(models.createDeviceModel(), "device");

				// set the global tree data
				this.setModel(new JSONModel(), "treeData");

				// set the global libs data
				this.setModel(new JSONModel(), "libsData");

				// set the global version data
				this.setModel(new JSONModel(), "versionData");

				// call the base component's init function and create the App view
				UIComponent.prototype.init.apply(this, arguments);

				// Load VersionInfo model promise
				this.loadVersionInfo();

				// create the views based on the url/hash
				this.getRouter().initialize();
			},

			/**
			 * The component is destroyed by UI5 automatically.
			 * In this method, the ListSelector and ErrorHandler are destroyed.
			 * @public
			 * @override
			 */
			destroy : function () {
				this._oErrorHandler.destroy();
				this._oConfigUtil.destroy();
				this._oConfigUtil = null;
				// call the base component's destroy function
				UIComponent.prototype.destroy.apply(this, arguments);
			},

			/**
			 * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
			 * design mode class should be set, which influences the size appearance of some controls.
			 * @public
			 * @return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
			 */
			getContentDensityClass : function() {
				if (this._sContentDensityClass === undefined) {
					// check whether FLP has already set the content density class; do nothing in this case
					if (jQuery(document.body).hasClass("sapUiSizeCozy") || jQuery(document.body).hasClass("sapUiSizeCompact")) {
						this._sContentDensityClass = "";
					}
					// The default density class for the sap.ui.documentation project will be compact
					this._sContentDensityClass = "sapUiSizeCompact";
				}
				return this._sContentDensityClass;
			},
			getConfigUtil: function() {
				if (!this._oConfigUtil) {
					this._oConfigUtil = new ConfigUtil(this);
				}
				return this._oConfigUtil;
			},

			// MODELS

			loadVersionInfo: function () {
				if (!this._oVersionInfoPromise) {
					this._oVersionInfoPromise = sap.ui.getVersionInfo({async: true})
						.then(this._bindVersionModel.bind(this));
				}

				return this._oVersionInfoPromise;
			},

			fetchAPIIndex: function () {
				if (this._indexPromise) {
					return this._indexPromise;
				}

				this._indexPromise = new Promise(function (resolve, reject) {
					APIInfo.getIndexJsonPromise().then(function (aData) {
						this._parseLibraryElements(aData);
						this._bindTreeModel(aTreeContent);
						resolve(aData);
					}.bind(this));
				}.bind(this));

				return this._indexPromise;
			},

			_parseLibraryElements : function (aLibraryElementsJSON) {

				for (var i = 0; i < aLibraryElementsJSON.length; i++) {
					if (!aLibraryElementsJSON[i].children) {
						oLibsData[aLibraryElementsJSON[i].name] = aLibraryElementsJSON[i];
					}

					this._addElementToTreeData(aLibraryElementsJSON[i]);

					if (aLibraryElementsJSON[i].children) {
						this._parseLibraryElements(aLibraryElementsJSON[i].children, true);
					}
				}
			},

			_addElementToTreeData : function (oJSONElement) {
				var oNewNodeNamespace,
					aAllowedMembers = this.aAllowedMembers;

				if (aAllowedMembers.indexOf(oJSONElement.visibility) !== -1) {
					if (oJSONElement.kind !== "namespace") {
						var aNameParts = oJSONElement.name.split("."),
							sBaseName = aNameParts.pop(),
							sNodeNamespace = aNameParts.join("."), // Note: Array.pop() on the previous line modifies the array itself
							oTreeNode = this._createTreeNode(sBaseName, oJSONElement.name, oJSONElement.name === this._topicId, oJSONElement.lib),
							oExistingNodeNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);

						if (oExistingNodeNamespace) {
							if (!oExistingNodeNamespace.nodes) {
								oExistingNodeNamespace.nodes = [];
							}
							oExistingNodeNamespace.nodes.push(oTreeNode);
						} else if (sNodeNamespace) {
							oNewNodeNamespace = this._createTreeNode(sNodeNamespace, sNodeNamespace, sNodeNamespace === this._topicId, oJSONElement.lib);
							oNewNodeNamespace.nodes = [];
							oNewNodeNamespace.nodes.push(oTreeNode);
							aTreeContent.push(oNewNodeNamespace);

							this._removeDuplicatedNodeFromTree(sNodeNamespace);
						} else {
							// Entities for which we can't resolve namespace we are shown in the root level
							oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name, oJSONElement.name === this._topicId, oJSONElement.lib);
							aTreeContent.push(oNewNodeNamespace);
						}
					} else {
						oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name, oJSONElement.name === this._topicId, oJSONElement.lib);
						aTreeContent.push(oNewNodeNamespace);
					}
				}
			},

			_createTreeNode : function (text, name, isSelected, sLib) {
				var oTreeNode = {};
				oTreeNode.text = text;
				oTreeNode.name = name;
				oTreeNode.ref = "#/api/" + name;
				oTreeNode.isSelected = isSelected;
				oTreeNode.lib = sLib;
				return oTreeNode;
			},

			_findNodeNamespaceInTreeStructure : function (sNodeNamespace, aTreeStructure) {
				aTreeStructure = aTreeStructure || aTreeContent;
				for (var i = 0; i < aTreeStructure.length; i++) {
					var oTreeNode = aTreeStructure[i];
					if (oTreeNode.name === sNodeNamespace) {
						return oTreeNode;
					}
					if (oTreeNode.nodes) {
						var oChildNode = this._findNodeNamespaceInTreeStructure(sNodeNamespace, oTreeNode.nodes);
						if (oChildNode) {
							return oChildNode;
						}
					}
				}
			},

			_removeNodeFromNamespace : function (sNode, oNamespace) {
				for (var i = 0; i < oNamespace.nodes.length; i++) {
					if (oNamespace.nodes[i].text === sNode) {
						oNamespace.nodes.splice(i, 1);
						return;
					}
				}
			},

			_removeDuplicatedNodeFromTree : function (sNodeFullName) {
				if (oLibsData[sNodeFullName]) {
					var sNodeNamespace = sNodeFullName.substring(0, sNodeFullName.lastIndexOf("."));
					var oNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);
					var sNode = sNodeFullName.substring(sNodeFullName.lastIndexOf(".") + 1, sNodeFullName.lenght);
					this._removeNodeFromNamespace(sNode, oNamespace);
				}
			},

			_bindTreeModel : function (aTreeContent) {
				var treeModel = this.getModel("treeData");
				treeModel.setSizeLimit(iTreeModelLimit);

				// Inject Deprecated, Experimental and Since links
				if (aTreeContent.length > 0) {
					aTreeContent.push({
						isSelected: false,
						name : "experimental",
						ref: "#/api/experimental",
						text: "Experimental APIs"
					}, {
						isSelected: false,
						name : "deprecated",
						ref: "#/api/deprecated",
						text: "Deprecated APIs"
					}, {
						isSelected: false,
						name : "since",
						ref: "#/api/since",
						text: "Index by Version"
					});
				}

				treeModel.setData(aTreeContent, false);
			},

			_bindVersionModel : function (oVersionInfo) {
				var sVersion,
					oVersionInfoData,
					bIsInternal = false;

				this.aAllowedMembers = ["public", "protected"];

				if (!oVersionInfo) {
					return;
				}

				sVersion = oVersionInfo.version;
				if (/internal/i.test(oVersionInfo.name)) {
					bIsInternal = true;
					this.aAllowedMembers.push("restricted");
				}
				oVersionInfoData = {
					versionGav: oVersionInfo.gav,
					versionName: oVersionInfo.name,
					version: jQuery.sap.Version(sVersion).getMajor() + "." + jQuery.sap.Version(sVersion).getMinor() + "." + jQuery.sap.Version(sVersion).getPatch(),
					fullVersion: sVersion,
					openUi5Version: sap.ui.version,
					isOpenUI5: oVersionInfo && oVersionInfo.gav && /openui5/i.test(oVersionInfo.gav),
					isSnapshotVersion: oVersionInfo && oVersionInfo.gav && /snapshot/i.test(oVersionInfo.gav),
					isDevVersion: sVersion.indexOf("SNAPSHOT") > -1 || (sVersion.split(".").length > 1 && parseInt(sVersion.split(".")[1], 10) % 2 === 1),
					isBetaVersion: false,
					isInternal: bIsInternal,
					libraries: oVersionInfo.libraries,
					allowedMembers: this.aAllowedMembers
				};

				if (!oVersionInfoData.isOpenUI5 && !oVersionInfoData.isSnapshotVersion) {
					jQuery.ajax({
						url: "versionoverview.json"
					}).done(function(data) {
						if (data.versions && data.versions.length) {
							oVersionInfoData.isBetaVersion = data.versions.some(function (element) {
								return element.beta && element.beta.indexOf(oVersionInfoData.openUi5Version + '-beta') > -1;
							});
						}
						this.getModel("versionData").setData(oVersionInfoData, false /* mo merge with previous data */);
					}.bind(this)).fail(function () {
						this.getModel("versionData").setData(oVersionInfoData, false /* mo merge with previous data */);
					}.bind(this));
				} else {
					this.getModel("versionData").setData(oVersionInfoData, false /* mo merge with previous data */);
				}
			}
		});
	}
);<commit_msg>[INTERNAL] sap.ui.documentation: Detection of Beta version fixed<commit_after>/*!
 * ${copyright}
 */

sap.ui.define([
		"jquery.sap.global",
		"sap/ui/core/UIComponent",
		"sap/ui/Device",
		"sap/ui/documentation/sdk/model/models",
		"sap/ui/documentation/sdk/controller/ErrorHandler",
		"sap/ui/model/json/JSONModel",
		"sap/ui/documentation/sdk/controller/util/ConfigUtil",
		"sap/ui/documentation/sdk/controller/util/APIInfo",
		"sap/ui/documentation/sdk/util/DocumentationRouter", // used via manifest.json
		"sap/m/ColumnListItem" // implements sap.m.TablePopin
	], function (jQuery, UIComponent, Device, models, ErrorHandler, JSONModel, ConfigUtil, APIInfo /*, DocumentationRouter, ColumnListItem*/) {
		"use strict";

		var aTreeContent = [],
			oLibsData = {},
			iTreeModelLimit = 1000000;

		return UIComponent.extend("sap.ui.documentation.sdk.Component", {

			metadata : {
				manifest : "json",
				includes : [
					"css/style.css"
				]
			},

			/**
			 * The component is initialized by UI5 automatically during the startup of the app and calls the init method once.
			 * In this method, the device models are set and the router is initialized.
			 * @public
			 * @override
			 */
			init : function () {

				this._oErrorHandler = new ErrorHandler(this);

				// set the device model
				this.setModel(models.createDeviceModel(), "device");

				// set the global tree data
				this.setModel(new JSONModel(), "treeData");

				// set the global libs data
				this.setModel(new JSONModel(), "libsData");

				// set the global version data
				this.setModel(new JSONModel(), "versionData");

				// call the base component's init function and create the App view
				UIComponent.prototype.init.apply(this, arguments);

				// Load VersionInfo model promise
				this.loadVersionInfo();

				// create the views based on the url/hash
				this.getRouter().initialize();
			},

			/**
			 * The component is destroyed by UI5 automatically.
			 * In this method, the ListSelector and ErrorHandler are destroyed.
			 * @public
			 * @override
			 */
			destroy : function () {
				this._oErrorHandler.destroy();
				this._oConfigUtil.destroy();
				this._oConfigUtil = null;
				// call the base component's destroy function
				UIComponent.prototype.destroy.apply(this, arguments);
			},

			/**
			 * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
			 * design mode class should be set, which influences the size appearance of some controls.
			 * @public
			 * @return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
			 */
			getContentDensityClass : function() {
				if (this._sContentDensityClass === undefined) {
					// check whether FLP has already set the content density class; do nothing in this case
					if (jQuery(document.body).hasClass("sapUiSizeCozy") || jQuery(document.body).hasClass("sapUiSizeCompact")) {
						this._sContentDensityClass = "";
					}
					// The default density class for the sap.ui.documentation project will be compact
					this._sContentDensityClass = "sapUiSizeCompact";
				}
				return this._sContentDensityClass;
			},
			getConfigUtil: function() {
				if (!this._oConfigUtil) {
					this._oConfigUtil = new ConfigUtil(this);
				}
				return this._oConfigUtil;
			},

			// MODELS

			loadVersionInfo: function () {
				if (!this._oVersionInfoPromise) {
					this._oVersionInfoPromise = sap.ui.getVersionInfo({async: true})
						.then(this._bindVersionModel.bind(this));
				}

				return this._oVersionInfoPromise;
			},

			fetchAPIIndex: function () {
				if (this._indexPromise) {
					return this._indexPromise;
				}

				this._indexPromise = new Promise(function (resolve, reject) {
					APIInfo.getIndexJsonPromise().then(function (aData) {
						this._parseLibraryElements(aData);
						this._bindTreeModel(aTreeContent);
						resolve(aData);
					}.bind(this));
				}.bind(this));

				return this._indexPromise;
			},

			_parseLibraryElements : function (aLibraryElementsJSON) {

				for (var i = 0; i < aLibraryElementsJSON.length; i++) {
					if (!aLibraryElementsJSON[i].children) {
						oLibsData[aLibraryElementsJSON[i].name] = aLibraryElementsJSON[i];
					}

					this._addElementToTreeData(aLibraryElementsJSON[i]);

					if (aLibraryElementsJSON[i].children) {
						this._parseLibraryElements(aLibraryElementsJSON[i].children, true);
					}
				}
			},

			_addElementToTreeData : function (oJSONElement) {
				var oNewNodeNamespace,
					aAllowedMembers = this.aAllowedMembers;

				if (aAllowedMembers.indexOf(oJSONElement.visibility) !== -1) {
					if (oJSONElement.kind !== "namespace") {
						var aNameParts = oJSONElement.name.split("."),
							sBaseName = aNameParts.pop(),
							sNodeNamespace = aNameParts.join("."), // Note: Array.pop() on the previous line modifies the array itself
							oTreeNode = this._createTreeNode(sBaseName, oJSONElement.name, oJSONElement.name === this._topicId, oJSONElement.lib),
							oExistingNodeNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);

						if (oExistingNodeNamespace) {
							if (!oExistingNodeNamespace.nodes) {
								oExistingNodeNamespace.nodes = [];
							}
							oExistingNodeNamespace.nodes.push(oTreeNode);
						} else if (sNodeNamespace) {
							oNewNodeNamespace = this._createTreeNode(sNodeNamespace, sNodeNamespace, sNodeNamespace === this._topicId, oJSONElement.lib);
							oNewNodeNamespace.nodes = [];
							oNewNodeNamespace.nodes.push(oTreeNode);
							aTreeContent.push(oNewNodeNamespace);

							this._removeDuplicatedNodeFromTree(sNodeNamespace);
						} else {
							// Entities for which we can't resolve namespace we are shown in the root level
							oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name, oJSONElement.name === this._topicId, oJSONElement.lib);
							aTreeContent.push(oNewNodeNamespace);
						}
					} else {
						oNewNodeNamespace = this._createTreeNode(oJSONElement.name, oJSONElement.name, oJSONElement.name === this._topicId, oJSONElement.lib);
						aTreeContent.push(oNewNodeNamespace);
					}
				}
			},

			_createTreeNode : function (text, name, isSelected, sLib) {
				var oTreeNode = {};
				oTreeNode.text = text;
				oTreeNode.name = name;
				oTreeNode.ref = "#/api/" + name;
				oTreeNode.isSelected = isSelected;
				oTreeNode.lib = sLib;
				return oTreeNode;
			},

			_findNodeNamespaceInTreeStructure : function (sNodeNamespace, aTreeStructure) {
				aTreeStructure = aTreeStructure || aTreeContent;
				for (var i = 0; i < aTreeStructure.length; i++) {
					var oTreeNode = aTreeStructure[i];
					if (oTreeNode.name === sNodeNamespace) {
						return oTreeNode;
					}
					if (oTreeNode.nodes) {
						var oChildNode = this._findNodeNamespaceInTreeStructure(sNodeNamespace, oTreeNode.nodes);
						if (oChildNode) {
							return oChildNode;
						}
					}
				}
			},

			_removeNodeFromNamespace : function (sNode, oNamespace) {
				for (var i = 0; i < oNamespace.nodes.length; i++) {
					if (oNamespace.nodes[i].text === sNode) {
						oNamespace.nodes.splice(i, 1);
						return;
					}
				}
			},

			_removeDuplicatedNodeFromTree : function (sNodeFullName) {
				if (oLibsData[sNodeFullName]) {
					var sNodeNamespace = sNodeFullName.substring(0, sNodeFullName.lastIndexOf("."));
					var oNamespace = this._findNodeNamespaceInTreeStructure(sNodeNamespace);
					var sNode = sNodeFullName.substring(sNodeFullName.lastIndexOf(".") + 1, sNodeFullName.lenght);
					this._removeNodeFromNamespace(sNode, oNamespace);
				}
			},

			_bindTreeModel : function (aTreeContent) {
				var treeModel = this.getModel("treeData");
				treeModel.setSizeLimit(iTreeModelLimit);

				// Inject Deprecated, Experimental and Since links
				if (aTreeContent.length > 0) {
					aTreeContent.push({
						isSelected: false,
						name : "experimental",
						ref: "#/api/experimental",
						text: "Experimental APIs"
					}, {
						isSelected: false,
						name : "deprecated",
						ref: "#/api/deprecated",
						text: "Deprecated APIs"
					}, {
						isSelected: false,
						name : "since",
						ref: "#/api/since",
						text: "Index by Version"
					});
				}

				treeModel.setData(aTreeContent, false);
			},

			_bindVersionModel : function (oVersionInfo) {
				var sVersion,
					oVersionInfoData,
					bIsInternal = false;

				this.aAllowedMembers = ["public", "protected"];

				if (!oVersionInfo) {
					return;
				}

				sVersion = oVersionInfo.version;
				if (/internal/i.test(oVersionInfo.name)) {
					bIsInternal = true;
					this.aAllowedMembers.push("restricted");
				}
				oVersionInfoData = {
					versionGav: oVersionInfo.gav,
					versionName: oVersionInfo.name,
					version: jQuery.sap.Version(sVersion).getMajor() + "." + jQuery.sap.Version(sVersion).getMinor() + "." + jQuery.sap.Version(sVersion).getPatch(),
					fullVersion: sVersion,
					openUi5Version: sap.ui.version,
					isOpenUI5: oVersionInfo && oVersionInfo.gav && /openui5/i.test(oVersionInfo.gav),
					isSnapshotVersion: oVersionInfo && oVersionInfo.gav && /snapshot/i.test(oVersionInfo.gav),
					isDevVersion: sVersion.indexOf("SNAPSHOT") > -1 || (sVersion.split(".").length > 1 && parseInt(sVersion.split(".")[1], 10) % 2 === 1),
					isBetaVersion: false,
					isInternal: bIsInternal,
					libraries: oVersionInfo.libraries,
					allowedMembers: this.aAllowedMembers
				};

				if (!oVersionInfoData.isOpenUI5 && !oVersionInfoData.isSnapshotVersion) {
					jQuery.ajax({
						url: "neo-app.json"
					}).done(function(data) {
						if (data.routes && data.routes.length) {
							var sOpenUI5BetaVersion = oVersionInfoData.openUi5Version + '-beta'; // Concatenates openUI5 version with '-beta' string
							oVersionInfoData.isBetaVersion = data.routes.some(function (element) {
								return element.target && element.target.version && (element.target.version === sOpenUI5BetaVersion);
							});
						}
						this.getModel("versionData").setData(oVersionInfoData, false /* mo merge with previous data */);
					}.bind(this)).fail(function () {
						this.getModel("versionData").setData(oVersionInfoData, false /* mo merge with previous data */);
					}.bind(this));
				} else {
					this.getModel("versionData").setData(oVersionInfoData, false /* mo merge with previous data */);
				}
			}
		});
	}
);<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides the Design Time Metadata for the sap.ui.layout.form.FormElement
sap.ui.define(['sap/ui/layout/form/Form', 'sap/ui/layout/form/FormContainer', 'sap/ui/layout/form/ResponsiveGridLayout'],
	function(Form, FormContainer, ResponsiveGridLayout) {
	"use strict";

	return {
		domRef : function(oFormElement) {
			var oParent = oFormElement.getParent();
			if (oParent instanceof FormContainer) {
				oParent = oParent.getParent();
				if (oParent instanceof Form) {
					var oLayout = oParent.getLayout();
					// formLayout, responsiveLayout and GridLayout are implementing getRenderedDomFor
					// method thus don't need design-time dom ref definition
					if (oLayout instanceof ResponsiveGridLayout) {
						var aFields = oFormElement.getFields();
						var oLabel = oFormElement.getLabel();
						if (typeof (oLabel) === "string"){
							if (oFormElement.getLabelControl){
								oLabel = oFormElement.getLabelControl();
							} else { // can't retrieve label object
								oLabel = null;
							}
						}
						if (oLabel) {
							aFields.unshift(oLabel);
						}

						return aFields.filter(function(oElement) {
							return oElement.getDomRef && oElement.getDomRef();
						}).map(function(oElement) {
							var oDomRef = oElement.getDomRef();
							return oDomRef.parentNode;
						});
					}
				}
			}
		}
	};

}, /* bExport= */ false);
<commit_msg>[INTERNAL] FormElement.designtime improve determination of Label<commit_after>/*!
 * ${copyright}
 */

// Provides the Design Time Metadata for the sap.ui.layout.form.FormElement
sap.ui.define(['sap/ui/layout/form/Form', 'sap/ui/layout/form/FormContainer', 'sap/ui/layout/form/ResponsiveGridLayout'],
	function(Form, FormContainer, ResponsiveGridLayout) {
	"use strict";

	return {
		domRef : function(oFormElement) {
			var oParent = oFormElement.getParent();
			if (oParent instanceof FormContainer) {
				oParent = oParent.getParent();
				if (oParent instanceof Form) {
					var oLayout = oParent.getLayout();
					// formLayout, responsiveLayout and GridLayout are implementing getRenderedDomFor
					// method thus don't need design-time dom ref definition
					if (oLayout instanceof ResponsiveGridLayout) {
						var aFields = oFormElement.getFields();
						var oLabel = oFormElement.getLabelControl();
						if (oLabel) {
							aFields.unshift(oLabel);
						}

						return aFields.filter(function(oElement) {
							return oElement.getDomRef && oElement.getDomRef();
						}).map(function(oElement) {
							var oDomRef = oElement.getDomRef();
							return oDomRef.parentNode;
						});
					}
				}
			}
		}
	};

}, /* bExport= */ false);
<|endoftext|>