<commit_before>// @flow weak

import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import { assert } from 'chai';
import { spy } from 'sinon';
import { createMount } from 'src/test-utils';
import Transition, { UNMOUNTED, EXITED, ENTERING, ENTERED, EXITING } from './Transition';

describe('<Transition />', () => {
  let mount;

  before(() => {
    mount = createMount();
  });

  after(() => {
    mount.cleanUp();
  });

  it('should not transition on mount', () => {
    const wrapper = mount(
      <Transition in timeout={0} onEnter={() => assert.fail()}>

      </Transition>,
    );
    assert.strictEqual(wrapper.state('status'), ENTERED);
  });

  it('should start exited with transitionAppear', () => {
    const wrapper = mount(
      <Transition transitionAppear>

      </Transition>,
    );
    assert.strictEqual(wrapper.state('status'), EXITED);
  });

  it('should transition on mount with transitionAppear', () => {
    const wrapper = mount(
      <Transition in transitionAppear timeout={0}>

      </Transition>,
    );
    assert.strictEqual(wrapper.state('status'), ENTERING);
  });

  it('should flush new props to the DOM before initiating a transition', (done) => {
    const wrapper = mount(
      <Transition
        in={false}
        timeout={0}
        enteringClassName="test-entering"
        onEnter={(node) => {
          assert.strictEqual(
            node.classList.contains('test-class'),
            true,
            'should have the test-class',
          );
          assert.strictEqual(
            node.classList.contains('test-entering'),
            false,
            'should not have the test-entering class',
          );
          done();
        }}
      >

      </Transition>,
    );

    assert.strictEqual(wrapper.hasClass('test-class'), false, 'should not have the test-class yet');

    wrapper.setProps({ in: true, className: 'test-class' });
  });

  describe('entering', () => {
    let wrapper;

    beforeEach(() => {
      wrapper = mount(
        <Transition
          timeout={10}
          enteredClassName="test-enter"
          enteringClassName="test-entering"
        >

        </Transition>,
      );
    });

    it('should fire callbacks', (done) => {
      const onEnter = spy();
      const onEntering = spy();

      assert.strictEqual(wrapper.state('status'), EXITED);

      wrapper = wrapper.setProps({
        in: true,
        onEnter,
        onEntering,
        onEntered() {
          assert.strictEqual(onEnter.callCount, 1);
          assert.strictEqual(onEntering.callCount, 1);
          assert.strictEqual(onEnter.calledBefore(onEntering), true);
          done();
        },
      });
    });

    it('should move to each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), EXITED);

      wrapper = wrapper.setProps({
        in: true,
        onEnter() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), EXITED);
        },
        onEntering() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), ENTERING);
        },
        onEntered() {
          assert.strictEqual(wrapper.state('status'), ENTERED);
          assert.strictEqual(count, 2);
          done();
        },
      });
    });

    it('should apply classes at each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), EXITED);

      wrapper = wrapper.setProps({
        in: true,
        onEnter(node) {
          count += 1;
          assert.strictEqual(node.className, '');
        },
        onEntering(node) {
          count += 1;
          assert.strictEqual(node.className, 'test-entering');
        },
        onEntered(node) {
          assert.strictEqual(node.className, 'test-enter');
          assert.strictEqual(count, 2);
          done();
        },
      });
    });
  });

  describe('exiting', () => {
    let wrapper;

    beforeEach(() => {
      wrapper = mount(
        <Transition
          in
          timeout={10}
          exitedClassName="test-exit"
          exitingClassName="test-exiting"
        >

        </Transition>,
      );
    });

    it('should fire callbacks', (done) => {
      const onExit = spy();
      const onExiting = spy();

      assert.strictEqual(wrapper.state('status'), ENTERED);

      wrapper = wrapper.setProps({
        in: false,

        onExit,

        onExiting,

        onExited() {
          assert.strictEqual(onExit.callCount, 1);
          assert.strictEqual(onExiting.callCount, 1);
          assert.strictEqual(onExit.calledBefore(onExiting), true);
          done();
        },
      });
    });

    it('should move to each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), ENTERED);

      wrapper = wrapper.setProps({
        in: false,

        onExit() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), ENTERED);
        },

        onExiting() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), EXITING);
        },

        onExited() {
          assert.strictEqual(wrapper.state('status'), EXITED);
          assert.strictEqual(count, 2);
          done();
        },
      });
    });

    it('should apply classes at each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), ENTERED);

      wrapper = wrapper.setProps({
        in: false,

        onExit(node) {
          count += 1;
          assert.strictEqual(node.className, '');
        },

        onExiting(node) {
          count += 1;
          assert.strictEqual(node.className, 'test-exiting');
        },

        onExited(node) {
          assert.strictEqual(node.className, 'test-exit');
          assert.strictEqual(count, 2);
          done();
        },
      });
    });
  });

  describe('unmountOnExit', () => {
    class UnmountTransition extends Component {
      state = {};

      componentWillMount() {
        this.setState({
          in: this.props.initialIn, // eslint-disable-line react/prop-types
        });
      }

      transition = null;

      getStatus() {
        return this.transition.state.status;
      }

      render() {
        const {
          initialIn, // eslint-disable-line no-unused-vars, react/prop-types
          ...other
        } = this.props;

        return (
          <Transition
            ref={(c) => {
              if (c !== null) {
                this.transition = c;
              }
            }}
            unmountOnExit
            in={this.state.in}
            timeout={10}
            {...other}
          >

          </Transition>
        );
      }
    }

    it('should mount when entering', (done) => {
      const wrapper = mount(
        <UnmountTransition
          initialIn={false}
          onEnter={() => {
            assert.strictEqual(wrapper.instance().getStatus(), EXITED);
            assert.ok(findDOMNode(wrapper.instance()));
            done();
          }}
        />,
      );

      assert.strictEqual(wrapper.instance().getStatus(), UNMOUNTED);
      assert.notOk(findDOMNode(wrapper.instance()));

      wrapper.setState({ in: true });
    });

    it('should unmount after exiting', (done) => {
      const wrapper = mount(
        <UnmountTransition
          initialIn
          onExited={() => {
            assert.strictEqual(wrapper.instance().getStatus(), UNMOUNTED);
            assert.notOk(findDOMNode(wrapper.instance()));
            done();
          }}
        />,
      );

      assert.strictEqual(wrapper.instance().getStatus(), ENTERED);
      assert.ok(findDOMNode(wrapper.instance()));

      wrapper.setState({ in: false });
    });
  });
});
<commit_msg>[Transition] Add test coverage for `shouldComponentUpdate()` (#6680)<commit_after>// @flow weak

import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import { assert } from 'chai';
import { spy } from 'sinon';
import { createMount } from 'src/test-utils';
import Transition, { UNMOUNTED, EXITED, ENTERING, ENTERED, EXITING } from './Transition';

describe('<Transition />', () => {
  let mount;

  before(() => {
    mount = createMount();
  });

  after(() => {
    mount.cleanUp();
  });

  it('should not transition on mount', () => {
    const wrapper = mount(
      <Transition in timeout={0} onEnter={() => assert.fail()}>

      </Transition>,
    );
    assert.strictEqual(wrapper.state('status'), ENTERED);
  });

  it('should start exited with transitionAppear', () => {
    const wrapper = mount(
      <Transition transitionAppear>

      </Transition>,
    );
    assert.strictEqual(wrapper.state('status'), EXITED);
  });

  it('should transition on mount with transitionAppear', () => {
    const wrapper = mount(
      <Transition in transitionAppear timeout={0}>

      </Transition>,
    );
    assert.strictEqual(wrapper.state('status'), ENTERING);
  });

  it('should flush new props to the DOM before initiating a transition', (done) => {
    const wrapper = mount(
      <Transition
        in={false}
        timeout={0}
        enteringClassName="test-entering"
        onEnter={(node) => {
          assert.strictEqual(
            node.classList.contains('test-class'),
            true,
            'should have the test-class',
          );
          assert.strictEqual(
            node.classList.contains('test-entering'),
            false,
            'should not have the test-entering class',
          );
          done();
        }}
      >

      </Transition>,
    );

    assert.strictEqual(wrapper.hasClass('test-class'), false, 'should not have the test-class yet');

    wrapper.setProps({ in: true, className: 'test-class' });
  });

  describe('entering', () => {
    let wrapper;

    beforeEach(() => {
      wrapper = mount(
        <Transition
          timeout={10}
          enteredClassName="test-enter"
          enteringClassName="test-entering"
        >

        </Transition>,
      );
    });

    it('should fire callbacks', (done) => {
      const onEnter = spy();
      const onEntering = spy();

      assert.strictEqual(wrapper.state('status'), EXITED);

      wrapper = wrapper.setProps({
        in: true,
        onEnter,
        onEntering,
        onEntered() {
          assert.strictEqual(onEnter.callCount, 1);
          assert.strictEqual(onEntering.callCount, 1);
          assert.strictEqual(onEnter.calledBefore(onEntering), true);
          done();
        },
      });
    });

    it('should move to each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), EXITED);

      wrapper = wrapper.setProps({
        in: true,
        onEnter() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), EXITED);
        },
        onEntering() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), ENTERING);
        },
        onEntered() {
          assert.strictEqual(wrapper.state('status'), ENTERED);
          assert.strictEqual(count, 2);
          done();
        },
      });
    });

    it('should apply classes at each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), EXITED);

      wrapper = wrapper.setProps({
        in: true,
        onEnter(node) {
          count += 1;
          assert.strictEqual(node.className, '');
        },
        onEntering(node) {
          count += 1;
          assert.strictEqual(node.className, 'test-entering');
        },
        onEntered(node) {
          assert.strictEqual(node.className, 'test-enter');
          assert.strictEqual(count, 2);
          done();
        },
      });
    });
  });

  describe('exiting', () => {
    let wrapper;

    beforeEach(() => {
      wrapper = mount(
        <Transition
          in
          timeout={10}
          exitedClassName="test-exit"
          exitingClassName="test-exiting"
        >

        </Transition>,
      );
    });

    it('should fire callbacks', (done) => {
      const onExit = spy();
      const onExiting = spy();

      assert.strictEqual(wrapper.state('status'), ENTERED);

      wrapper = wrapper.setProps({
        in: false,

        onExit,

        onExiting,

        onExited() {
          assert.strictEqual(onExit.callCount, 1);
          assert.strictEqual(onExiting.callCount, 1);
          assert.strictEqual(onExit.calledBefore(onExiting), true);
          done();
        },
      });
    });

    it('should move to each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), ENTERED);

      wrapper = wrapper.setProps({
        in: false,

        onExit() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), ENTERED);
        },

        onExiting() {
          count += 1;
          assert.strictEqual(wrapper.state('status'), EXITING);
        },

        onExited() {
          assert.strictEqual(wrapper.state('status'), EXITED);
          assert.strictEqual(count, 2);
          done();
        },
      });
    });

    it('should apply classes at each transition state', (done) => {
      let count = 0;

      assert.strictEqual(wrapper.state('status'), ENTERED);

      wrapper = wrapper.setProps({
        in: false,

        onExit(node) {
          count += 1;
          assert.strictEqual(node.className, '');
        },

        onExiting(node) {
          count += 1;
          assert.strictEqual(node.className, 'test-exiting');
        },

        onExited(node) {
          assert.strictEqual(node.className, 'test-exit');
          assert.strictEqual(count, 2);
          done();
        },
      });
    });
  });

  describe('unmountOnExit', () => {
    class UnmountTransition extends Component {
      state = {};

      componentWillMount() {
        this.setState({
          in: this.props.initialIn, // eslint-disable-line react/prop-types
        });
      }

      transition = null;

      getStatus() {
        return this.transition.state.status;
      }

      render() {
        const {
          initialIn, // eslint-disable-line no-unused-vars, react/prop-types
          ...other
        } = this.props;

        return (
          <Transition
            ref={(c) => {
              if (c !== null) {
                this.transition = c;
              }
            }}
            unmountOnExit
            in={this.state.in}
            timeout={10}
            {...other}
          >

          </Transition>
        );
      }
    }

    it('should mount when entering', (done) => {
      const wrapper = mount(
        <UnmountTransition
          initialIn={false}
          onEnter={() => {
            assert.strictEqual(wrapper.instance().getStatus(), EXITED);
            assert.ok(findDOMNode(wrapper.instance()));
            done();
          }}
        />,
      );

      assert.strictEqual(wrapper.instance().getStatus(), UNMOUNTED);
      assert.notOk(findDOMNode(wrapper.instance()));

      wrapper.setState({ in: true });
    });

    it('should unmount after exiting', (done) => {
      const wrapper = mount(
        <UnmountTransition
          initialIn
          onExited={() => {
            assert.strictEqual(wrapper.instance().getStatus(), UNMOUNTED);
            assert.notOk(findDOMNode(wrapper.instance()));
            done();
          }}
        />,
      );

      assert.strictEqual(wrapper.instance().getStatus(), ENTERED);
      assert.ok(findDOMNode(wrapper.instance()));

      wrapper.setState({ in: false });
    });
  });

  describe('shouldComponentUpdate', () => {
    let wrapper;
    let instance;
    let currentState;
    let nextState;
    let result;

    const nextProps = {};

    before(() => {
      wrapper = mount(<Transition>
</Transition>);
    });

    it('should update', () => {
      currentState = { status: EXITED };
      wrapper.setProps({ in: true });
      nextState = currentState;
      wrapper.setState(currentState);
      instance = wrapper.instance();
      result = instance.shouldComponentUpdate(nextProps, nextState);
      assert.strictEqual(result, false);
    });

    it('shouldn\'t update due to prop:in being false', () => {
      currentState = { status: EXITED };
      nextState = currentState;
      wrapper.setState({ status: EXITED });
      wrapper.setProps({ in: false });
      instance = wrapper.instance();
      result = instance.shouldComponentUpdate(nextProps, nextState);
      assert.strictEqual(result, true);
    });

    it('shouldn\'t update due to currentState.status != EXITED', () => {
      currentState = { status: UNMOUNTED };
      wrapper.setState(currentState);
      nextState = currentState;
      wrapper.setProps({ in: true });
      instance = wrapper.instance();
      result = instance.shouldComponentUpdate(nextProps, nextState);
      assert.strictEqual(result, true);
    });

    it('shouldn\'t update due to currentState.status != nextState.status', () => {
      currentState = { status: EXITED };
      wrapper.setState(currentState);
      nextState = currentState;
      nextState.status = UNMOUNTED;
      wrapper.setProps({ in: true });
      instance = wrapper.instance();
      result = instance.shouldComponentUpdate(nextProps, nextState);
      assert.strictEqual(result, true);
    });
  });
});
<|endoftext|>


<commit_before>var builder = require('node-xmpp')
    Base    = require('./base')

var Disco = function() {}

Disco.prototype = new Base()

Disco.prototype.NS_ITEMS = "http://jabber.org/protocol/disco#items"
Disco.prototype.NS_INFO  = "http://jabber.org/protocol/disco#info"

Disco.prototype.attributes = ['type', 'name', 'category', 'var', 'jid', 'node']

Disco.prototype.registerEvents = function() {
    var self = this
    this.socket.on('xmpp.discover.items', function(data, callback) {
        self.getItems(data, callback)
    })
    this.socket.on('xmpp.discover.info', function(data, callback) {
        self.getFeatures(data, callback)
    })
}

Disco.prototype.handles = function(stanza) {
    return stanza.is('iq')
        && (query = stanza.getChild('query'))
        && (query.getNS() == this.NS_INFO)
}

Disco.prototype.handle = function(stanza) {
    var self = this
    this.socket.emit('xmpp.discover.info', {from: stanza.attrs.from}, function(data) {
    var reply = new builder.Element(
        'iq',
        { to: stanza.attrs.from, type: 'result', id: stanza.attrs.id }
        ).c('query', {xmlns: this.NS_INFO})
        if (data instanceof Array) {
            data.forEach(function(item) {
                if (!item.kind) return
        	var attributes = {}
        		self.attributes.forEach(function(attr) {
        			if (item[attr]) attributes[attr] = item[attr]
        	    })
        		reply.c(item.kind, attributes).up()
        	})
        }
        self.client.send(reply)
	})
    return true
}

Disco.prototype.getItems = function(data, callback) {
    var self = this
    if (!data.of) return this._clientError("Missing 'of' key", data, callback)

    var attributes = {xmlns: this.NS_ITEMS}
    if (data.node) attributes.node = data.node

    var stanza = new builder.Element(
        'iq',
        { to: data.of, type: 'get', id: this._getId() }
    ).c('query', attributes).up()

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        self._handleDiscoItems(stanza, callback)
    })
    this.client.send(stanza)
}

Disco.prototype._handleDiscoItems = function(stanza, callback) {
    var self = this
    if (typeof(callback) != 'function') return console.error('No callback provided')
    if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)
    items = []
    stanza.getChild('query').getChildren('item').forEach(function(item) {
    	var entry = {}
    	for (var name in item.attrs) {
            var value = item.attrs[name]
            if (value.length > 0)
                entry[name] = value
        }
        items.push(entry)
    })
    callback(null, items)
}

Disco.prototype.getFeatures = function(data, callback) {
    var self   = this
    if (!data.of) return this._clientError("Missing 'of' key", data, callback)

    var stanza = new builder.Element(
        'iq',
        { to: data.of, type: 'get', id: this._getId() }
    ).c('query', {xmlns: this.NS_INFO}).up()

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        self._handleDiscoInfo(stanza, callback)
    })
    this.client.send(stanza)
}

Disco.prototype._handleDiscoInfo = function(stanza, callback) {
    var self = this
    if (typeof(callback) != 'function') return console.error('No callback provided')
    if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)

    items = []
    stanza.getChild('query').children.forEach(function(item) {
        var info = { kind: item.getName().toLowerCase() }
        if (info.kind != 'identity' && info.kind != 'feature' && info.kind != 'item') return
        self.attributes.forEach(function(attr) {
            if ((null != (attrValue = item.attrs[attr])) && attrValue.length > 0)
                info[attr] = attrValue
        })
        items.push(info)
    })
    callback(null, items)
}

module.exports = Disco
<commit_msg>Support disco#info for a node<commit_after>var builder = require('node-xmpp')
    Base    = require('./base')

var Disco = function() {}

Disco.prototype = new Base()

Disco.prototype.NS_ITEMS = "http://jabber.org/protocol/disco#items"
Disco.prototype.NS_INFO  = "http://jabber.org/protocol/disco#info"

Disco.prototype.attributes = ['type', 'name', 'category', 'var', 'jid', 'node']

Disco.prototype.registerEvents = function() {
    var self = this
    this.socket.on('xmpp.discover.items', function(data, callback) {
        self.getItems(data, callback)
    })
    this.socket.on('xmpp.discover.info', function(data, callback) {
        self.getFeatures(data, callback)
    })
}

Disco.prototype.handles = function(stanza) {
    return stanza.is('iq')
        && (query = stanza.getChild('query'))
        && (query.getNS() == this.NS_INFO)
}

Disco.prototype.handle = function(stanza) {
    var self = this
    this.socket.emit('xmpp.discover.info', {from: stanza.attrs.from}, function(data) {
    var reply = new builder.Element(
        'iq',
        { to: stanza.attrs.from, type: 'result', id: stanza.attrs.id }
        ).c('query', {xmlns: this.NS_INFO})
        if (data instanceof Array) {
            data.forEach(function(item) {
                if (!item.kind) return
        	var attributes = {}
        		self.attributes.forEach(function(attr) {
        			if (item[attr]) attributes[attr] = item[attr]
        	    })
        		reply.c(item.kind, attributes).up()
        	})
        }
        self.client.send(reply)
	})
    return true
}

Disco.prototype.getItems = function(data, callback) {
    var self = this
    if (!data.of) return this._clientError("Missing 'of' key", data, callback)

    var attributes = {xmlns: this.NS_ITEMS}
    if (data.node) attributes.node = data.node

    var stanza = new builder.Element(
        'iq',
        { to: data.of, type: 'get', id: this._getId() }
    ).c('query', attributes).up()

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        self._handleDiscoItems(stanza, callback)
    })
    this.client.send(stanza)
}

Disco.prototype._handleDiscoItems = function(stanza, callback) {
    var self = this
    if (typeof(callback) != 'function') return console.error('No callback provided')
    if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)
    items = []
    stanza.getChild('query').getChildren('item').forEach(function(item) {
    	var entry = {}
    	for (var name in item.attrs) {
            var value = item.attrs[name]
            if (value.length > 0)
                entry[name] = value
        }
        items.push(entry)
    })
    callback(null, items)
}

Disco.prototype.getFeatures = function(data, callback) {
    var self   = this
    if (!data.of) return this._clientError("Missing 'of' key", data, callback)

    var attrs = {xmlns: this.NS_INFO }
    if (data.node) attrs.node = data.node

    var stanza = new builder.Element(
        'iq',
        { to: data.of, type: 'get', id: this._getId() }
    ).c('query', attrs)

    this.manager.trackId(stanza.root().attr('id'), function(stanza) {
        self._handleDiscoInfo(stanza, callback)
    })
    this.client.send(stanza)
}

Disco.prototype._handleDiscoInfo = function(stanza, callback) {
    var self = this
    if (typeof(callback) != 'function') return console.error('No callback provided')
    if (stanza.attrs.type == 'error') return callback(self._parseError(stanza), null)

    items = []
    stanza.getChild('query').children.forEach(function(item) {
        var info = { kind: item.getName().toLowerCase() }
        if (info.kind != 'identity' && info.kind != 'feature' && info.kind != 'item') return
        self.attributes.forEach(function(attr) {
            if ((null != (attrValue = item.attrs[attr])) && attrValue.length > 0)
                info[attr] = attrValue
        })
        items.push(info)
    })
    callback(null, items)
}

module.exports = Disco
<|endoftext|>


<commit_before>var uuid = require('uuid');
var util = require('util');
var events = require('events');
var Promise = require('bluebird');
var ModelEvent = require('./ModelEvent.js');

/**
 * Conventions:
 *  - The "id" property is always used as the unique ID for classes.
 *  - Classes should be defined then extended as:
 *      function Cls(..) { .. }; // Makes class name available.
 *      Model(Cls);
 *  - Classes have a "self" property that references the static class.
 */

/**
 * Represents internal model data, acts as a hook for model enhancements.
 *
 * @param {string[]} properties An array of property names.
 * @param {function} idGenerator A function used to generate unique IDs. Defaults to uuid.v4.
 * @param {Object} options Any options that were passed to the original
 * @augments events.EventEmitter
 * @private
 */
function ModelData(properties, options) {
  this.properties = (properties instanceof Array) ? properties : [];
  this.options = options || {};
  this.idgen = this.options.idgen || uuid.v4;
  this.plugins = {};
}
util.inherits(ModelData, events.EventEmitter);

/**
 * Keep track of what functionality has been added to a model.
 *
 * @param {string} name The unique (per class) name of the plugin being registered.
 * @param {string} description A brief description of what the plugin does.
 */
ModelData.prototype.plugin = function(name, description) {
  if (typeof this.plugins[name] !== 'undefined') {
    throw new Error("Plugin " + name + " already registered on this class");
  }

  this.plugins[name] = description;

  return this;
}

/**
 * Option getter/setter
 *
 * @param {string} opt The option name.
 * @param {mixed} val The option value.
 * @returns {mixed} The option value, or undefined if the option is not defined.
 */
ModelData.prototype.option = function(opt, val) {
  if (val !== undefined) {
    this.options[opt] = val;
  }

  return this.options[opt];
}

/**
 * Mix model functions into a class.
 *
 * Static methods mixed in to classes are documented under {@link Model.statics}.
 * Instance methods are documented under {@link Model.instance}.
 *
 * @param {Object} cls A class to imbue with Model functionality.
 * @param {string[]} properties A list of properties to be stored in a redis hash.
 * @param {Redis} client A redis client to be used for this model.
 * @param {Object} options An object containing options used for this model.
 *
 * @example
 * var redis = require('redis').createClient();
 * function MyModelClass() {}
 * Model(MyModelClass, ['name']);
 * // Register storage class here...
 *
 * // Create
 * var mdl = new MyModelClass();
 * mdl.name = "John Smith";
 * mdl.save();
 * var id = mdl.id;
 *
 * // Retrieve
 * mdl = MyModelClass.get(id);
 *
 * // Update
 * mdl.name = "John Smythe";
 * mdl.save();
 *
 * // Delete
 * mdl.delete();
 *
 * // All asynchronous methods accept a callback and return a promise. You can pick your poison:
 * MyModelClass.get(id, function(err, mdl) {
 *   console.log("Got model via callback:", mdl);
 * }).then(function(mdl) {
 *   console.log("Got model via promise:", mdl);
 * });
 */
function Model(cls, properties, options) {
  if (cls._mdl) return cls._mdl;

  var mdl = new ModelData(properties, options);
  var override = mdl.option('override', Boolean(mdl.option('override')));

  /* Mix in static methods and properties */
  for (s in Model.statics) {
    if ((cls[s] === undefined) || override) {
      cls[s] = Model.statics[s];
    }
  }

  /* Mix in instance methods and properties */
  for (i in Model.instance) {
    if ((cls.prototype[i] === undefined) || override) {
      cls.prototype[i] = Model.instance[i];
    }
  }

  /* Properties considered protected */
  cls._mdl = mdl;
  cls.prototype.self = cls;

  return cls._mdl;
}

/**
 * A list of static properties and functions that are mixed in to classes.
 *
 * The {@link Model} function should be used to perform the mixing.
 *
 * @mixin
 */
Model.statics = {
  /**
   * Retrieve one or more models by key.
   *
   * @param {string} id The identifier for the object to fetch. An array of ids is also accepted.
   * @param {Model.statics.getCallback} callback The callback receiving the get result(s).
   */
  get: function(id, callback) {
    if (typeof(id) === 'undefined') {
      var err = "No ID provided";
      if (typeof callback === 'function') {
        callback.call(undefined, err, undefined);
      }
      return Promise.reject(err);
    }

    return emitEvent(this._mdl, 'get', this, id, callback).promise;
  },
  /**
   * @callback Model.statics.getCallback
   * @param {string} error An error string, or null if no error occurred.
   * @param {Model} result The retrieved model object, or null on error.
   */

  /**
   * Get a list of all object identifiers for this model type.
   *
   * @param {function} callback
   */
  all: function(callback) {
    return emitEvent(this._mdl, 'all', this, undefined, callback).promise;
  },

  /**
   * Build one or more objects from an object or array of objects.
   *
   * @param {string} id The unique identifier for the object.
   * @param {Object} data Typically an anonymous hgetall result object, or an array of hgetall results.
   */
  hydrate: function(id, data) {
    if (data instanceof Array) {
      var results = [];
      for (var i = 0; i < data.length; i++) {
        results.push(data[i] ? this.hydrate(id[i], data[i]) : data[i]);
      }
      return results;
    } else {
      var mdl = new this();
      mdl.id = id;
      mdl.fromObject(data);
      return mdl;
    }
  },

  /**
   * Purge all objects of this type.
   *
   * @param {function} callback
   */
  purge: function(callback) {
    return emitEvent(this._mdl, 'purge', this, undefined, callback).promise;
  }
};

/**
 * Methods and properties that get mixed into object prototypes.
 *
 * The {@link Model} function should be used to perform the mixing.
 *
 * @mixin
 */
Model.instance = {
  /**
   * Save an object to Redis.
   *
   * @param {function} callback
   * @instance
   */
  save: function(callback) {
    if (!this.id) {
      this.id = this.self._mdl.idgen();
    }

    return emitEvent(this.self._mdl, 'save', this, undefined, callback).promise;
  },

  /**
   * Remove an object.
   *
   * @param {function} callback Method to be called
   * @instance
   */
  delete: function(callback) {
    return emitEvent(this.self._mdl, 'delete', this, undefined, callback).promise;
  },

  /**
   * Copy properties from an anonymous object into this object.
   *
   * @param {Object} obj
   * @instance
   */
  fromObject: function(obj) {
    var properties = this.self._mdl.properties;
    for (var p = 0; p < properties.length; p++) {
      var property = properties[p];
      if (typeof(obj[property]) !== "undefined") {
        this[property] = obj[property];
      }
    }
  },

  /**
   * Convert a model to an anonymous object.
   *
   * @returns {Object} An anonymous object with properties and values belonging to this class.
   * @instance
   *
   * @example
   * JSON.stringify(mymodel.toObject());
   */
  toObject: function() {
    var dst = {};
    var properties = this.self._mdl.properties;
    for (var p = 0; p < properties.length; p++) {
      var property = properties[p];
      dst[property] = this[property] || "";
    }
    return dst;
  }
};

/**
 * Create a new sequence-based unique ID generator.
 *
 * @param {integer} init The initial value. Defaults to 0.
 * @return {function} An ID generator function.
 * @alias sequence
 *
 * @example
 * var init = 123; // Get initial value somehow.
 * function MyModel() {};
 * Model(MyModel);
 * MyModel.idgen = Model.sequence(init);
 */
Model.sequence = function(init) {
  var sequence = (init !== undefined) ? init : 0;
  return function() { return new String(sequence++).valueOf(); };
};

/**
 * Private function to emit the same event pattern for all methods.
 *
 * @param {string} type The event type, e.g. 'get'
 * @param {mixed} target The event target, an object, id, or undefined.
 * @param {function} callback The callback to be called on event completion.
 * @returns {ModelEvent} The emitted event, including its promise.
 *
 * This function:
 *  - Creates the callback on the event to call an "after" event for any synchronous postprocessing.
 *  - Emits a "before" event which is expected to do any synchronous preprocessing.
 *  - Emits the event, which may or may not happen asynchronously.
 */
function emitEvent(emitter, type, target, data, callback) {
  var Type = type.substr(0, 1).toUpperCase() + type.substr(1);
  var evt = new ModelEvent(type, target, data);

  /* Overwrite because bluebird returns immuable promises; Calls outside need to be chained off these. */
  evt.promise = evt.promise.nodeify(callback)
    .timeout(1000, "No Result: timed out.")
    .finally(function() {
      emitter.emit('after' + Type, evt);
    });

  /* If the callback is present, work around the "no error handler" behavior of bluebird. */
  if (typeof callback === 'function') {
    evt.promise = evt.promise.catch(function(e) {});
  }

  /* This allows the promise be registered in the case when the immediate before<Type> handler resolves/rejects the promise. */
  setTimeout(function() {
    emitter.emit('before' + Type, evt);
    emitter.emit(type, evt);
  }, 0);
  return evt;
};

module.exports = Model;
<commit_msg>Use nextTick rather than setTimeout.<commit_after>var uuid = require('uuid');
var util = require('util');
var events = require('events');
var Promise = require('bluebird');
var ModelEvent = require('./ModelEvent.js');

/**
 * Conventions:
 *  - The "id" property is always used as the unique ID for classes.
 *  - Classes should be defined then extended as:
 *      function Cls(..) { .. }; // Makes class name available.
 *      Model(Cls);
 *  - Classes have a "self" property that references the static class.
 */

/**
 * Represents internal model data, acts as a hook for model enhancements.
 *
 * @param {string[]} properties An array of property names.
 * @param {function} idGenerator A function used to generate unique IDs. Defaults to uuid.v4.
 * @param {Object} options Any options that were passed to the original
 * @augments events.EventEmitter
 * @private
 */
function ModelData(properties, options) {
  this.properties = (properties instanceof Array) ? properties : [];
  this.options = options || {};
  this.idgen = this.options.idgen || uuid.v4;
  this.plugins = {};
}
util.inherits(ModelData, events.EventEmitter);

/**
 * Keep track of what functionality has been added to a model.
 *
 * @param {string} name The unique (per class) name of the plugin being registered.
 * @param {string} description A brief description of what the plugin does.
 */
ModelData.prototype.plugin = function(name, description) {
  if (typeof this.plugins[name] !== 'undefined') {
    throw new Error("Plugin " + name + " already registered on this class");
  }

  this.plugins[name] = description;

  return this;
}

/**
 * Option getter/setter
 *
 * @param {string} opt The option name.
 * @param {mixed} val The option value.
 * @returns {mixed} The option value, or undefined if the option is not defined.
 */
ModelData.prototype.option = function(opt, val) {
  if (val !== undefined) {
    this.options[opt] = val;
  }

  return this.options[opt];
}

/**
 * Mix model functions into a class.
 *
 * Static methods mixed in to classes are documented under {@link Model.statics}.
 * Instance methods are documented under {@link Model.instance}.
 *
 * @param {Object} cls A class to imbue with Model functionality.
 * @param {string[]} properties A list of properties to be stored in a redis hash.
 * @param {Redis} client A redis client to be used for this model.
 * @param {Object} options An object containing options used for this model.
 *
 * @example
 * var redis = require('redis').createClient();
 * function MyModelClass() {}
 * Model(MyModelClass, ['name']);
 * // Register storage class here...
 *
 * // Create
 * var mdl = new MyModelClass();
 * mdl.name = "John Smith";
 * mdl.save();
 * var id = mdl.id;
 *
 * // Retrieve
 * mdl = MyModelClass.get(id);
 *
 * // Update
 * mdl.name = "John Smythe";
 * mdl.save();
 *
 * // Delete
 * mdl.delete();
 *
 * // All asynchronous methods accept a callback and return a promise. You can pick your poison:
 * MyModelClass.get(id, function(err, mdl) {
 *   console.log("Got model via callback:", mdl);
 * }).then(function(mdl) {
 *   console.log("Got model via promise:", mdl);
 * });
 */
function Model(cls, properties, options) {
  if (cls._mdl) return cls._mdl;

  var mdl = new ModelData(properties, options);
  var override = mdl.option('override', Boolean(mdl.option('override')));

  /* Mix in static methods and properties */
  for (s in Model.statics) {
    if ((cls[s] === undefined) || override) {
      cls[s] = Model.statics[s];
    }
  }

  /* Mix in instance methods and properties */
  for (i in Model.instance) {
    if ((cls.prototype[i] === undefined) || override) {
      cls.prototype[i] = Model.instance[i];
    }
  }

  /* Properties considered protected */
  cls._mdl = mdl;
  cls.prototype.self = cls;

  return cls._mdl;
}

/**
 * A list of static properties and functions that are mixed in to classes.
 *
 * The {@link Model} function should be used to perform the mixing.
 *
 * @mixin
 */
Model.statics = {
  /**
   * Retrieve one or more models by key.
   *
   * @param {string} id The identifier for the object to fetch. An array of ids is also accepted.
   * @param {Model.statics.getCallback} callback The callback receiving the get result(s).
   */
  get: function(id, callback) {
    if (typeof(id) === 'undefined') {
      var err = "No ID provided";
      if (typeof callback === 'function') {
        callback.call(undefined, err, undefined);
      }
      return Promise.reject(err);
    }

    return emitEvent(this._mdl, 'get', this, id, callback).promise;
  },
  /**
   * @callback Model.statics.getCallback
   * @param {string} error An error string, or null if no error occurred.
   * @param {Model} result The retrieved model object, or null on error.
   */

  /**
   * Get a list of all object identifiers for this model type.
   *
   * @param {function} callback
   */
  all: function(callback) {
    return emitEvent(this._mdl, 'all', this, undefined, callback).promise;
  },

  /**
   * Build one or more objects from an object or array of objects.
   *
   * @param {string} id The unique identifier for the object.
   * @param {Object} data Typically an anonymous hgetall result object, or an array of hgetall results.
   */
  hydrate: function(id, data) {
    if (data instanceof Array) {
      var results = [];
      for (var i = 0; i < data.length; i++) {
        results.push(data[i] ? this.hydrate(id[i], data[i]) : data[i]);
      }
      return results;
    } else {
      var mdl = new this();
      mdl.id = id;
      mdl.fromObject(data);
      return mdl;
    }
  },

  /**
   * Purge all objects of this type.
   *
   * @param {function} callback
   */
  purge: function(callback) {
    return emitEvent(this._mdl, 'purge', this, undefined, callback).promise;
  }
};

/**
 * Methods and properties that get mixed into object prototypes.
 *
 * The {@link Model} function should be used to perform the mixing.
 *
 * @mixin
 */
Model.instance = {
  /**
   * Save an object to Redis.
   *
   * @param {function} callback
   * @instance
   */
  save: function(callback) {
    if (!this.id) {
      this.id = this.self._mdl.idgen();
    }

    return emitEvent(this.self._mdl, 'save', this, undefined, callback).promise;
  },

  /**
   * Remove an object.
   *
   * @param {function} callback Method to be called
   * @instance
   */
  delete: function(callback) {
    return emitEvent(this.self._mdl, 'delete', this, undefined, callback).promise;
  },

  /**
   * Copy properties from an anonymous object into this object.
   *
   * @param {Object} obj
   * @instance
   */
  fromObject: function(obj) {
    var properties = this.self._mdl.properties;
    for (var p = 0; p < properties.length; p++) {
      var property = properties[p];
      if (typeof(obj[property]) !== "undefined") {
        this[property] = obj[property];
      }
    }
  },

  /**
   * Convert a model to an anonymous object.
   *
   * @returns {Object} An anonymous object with properties and values belonging to this class.
   * @instance
   *
   * @example
   * JSON.stringify(mymodel.toObject());
   */
  toObject: function() {
    var dst = {};
    var properties = this.self._mdl.properties;
    for (var p = 0; p < properties.length; p++) {
      var property = properties[p];
      dst[property] = this[property] || "";
    }
    return dst;
  }
};

/**
 * Create a new sequence-based unique ID generator.
 *
 * @param {integer} init The initial value. Defaults to 0.
 * @return {function} An ID generator function.
 * @alias sequence
 *
 * @example
 * var init = 123; // Get initial value somehow.
 * function MyModel() {};
 * Model(MyModel);
 * MyModel.idgen = Model.sequence(init);
 */
Model.sequence = function(init) {
  var sequence = (init !== undefined) ? init : 0;
  return function() { return new String(sequence++).valueOf(); };
};

/**
 * Private function to emit the same event pattern for all methods.
 *
 * @param {string} type The event type, e.g. 'get'
 * @param {mixed} target The event target, an object, id, or undefined.
 * @param {function} callback The callback to be called on event completion.
 * @returns {ModelEvent} The emitted event, including its promise.
 *
 * This function:
 *  - Creates the callback on the event to call an "after" event for any synchronous postprocessing.
 *  - Emits a "before" event which is expected to do any synchronous preprocessing.
 *  - Emits the event, which may or may not happen asynchronously.
 */
function emitEvent(emitter, type, target, data, callback) {
  var Type = type.substr(0, 1).toUpperCase() + type.substr(1);
  var evt = new ModelEvent(type, target, data);

  /* Overwrite because bluebird returns immuable promises; Calls outside need to be chained off these. */
  evt.promise = evt.promise.nodeify(callback)
    .timeout(1000, "No Result: timed out.")
    .finally(function() {
      emitter.emit('after' + Type, evt);
    });

  /* If the callback is present, work around the "no error handler" behavior of bluebird. */
  if (typeof callback === 'function') {
    evt.promise = evt.promise.catch(function(e) {});
  }

  /* This allows the promise be registered in the case when the immediate before<Type> handler resolves/rejects the promise. */
  process.nextTick(function() {
    emitter.emit('before' + Type, evt);
    emitter.emit(type, evt);
  });
  return evt;
};

module.exports = Model;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'UnstyledTableView',
  extends: 'foam.u2.Element',

  implements: [
    'foam.mlang.Expressions'
  ],

  requires: [
    'foam.dao.ProxyDAO',
    'foam.u2.md.OverlayDropdown',
    'foam.u2.view.OverlayActionListView',
    'foam.u2.view.EditColumnsView',
    'foam.u2.tag.Image'
  ],

  exports: [
    'columns',
    'selection',
    'hoverSelection'
  ],

  imports: [
    'ctrl',
    'dblclick?',
    'editRecord?',
    'selection? as importSelection'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data',
      postSet: function(_, data) {
        if ( ! this.of && data ) this.of = data.of;
      }
    },
    {
      name: 'order'
    },
    {
      name: 'columns_',
      expression: function(columns, of) {
        var of = this.of;
        if ( ! of ) return [];

        return columns.map(function(p) {
          var c = typeof p == 'string' ?
            of.getAxiomByName(p) :
            p;

           if ( ! c ) {
             console.error('Unknown table column: ', p);
           }

          return c;
        }).filter(function(c) { return c; });
      }
    },
    {
      name: 'columns',
      expression: function(of) {
        var of = this.of;
        if ( ! of ) return [];

        var tableColumns = of.getAxiomByName('tableColumns');

        if ( tableColumns ) return tableColumns.columns;

        return of.getAxiomsByClass(foam.core.Property).
            filter(function(p) { return p.tableCellFormatter && ! p.hidden; }).
            map(foam.core.Property.NAME.f);
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions',
      documentation: `
        Each table row has a context menu that contains actions you can perform
        on the object in that row. The actions used to populate that menu come
        from two different sources. The first source is this property. If you
        want a context menu action to do something in the view, then you should
        write the code for that action in the view model and pass it to the
        table view via this property. The second source of actions is from the
        model of the object being shown in the table.
      `
    },
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      value: true,
      documentation: 'Set this to true to let the user select columns.'
    },
    {
      class: 'Boolean',
      name: 'disableUserSelection',
      value: false,
      documentation: 'Ignores selection by user.'
    },
    {
      name: 'restingIcon',
      documentation: 'Image for grayed out double arrow when table header is not sorting',
      value: 'images/resting-arrow.svg'
    },
    {
      name: 'ascIcon',
      documentation: 'Image for table header ascending sorting arrow',
      value: 'images/up-arrow.svg'
    },
    {
      name: 'descIcon',
      documentation: 'Image for table header descending sorting arrow',
      value: 'images/down-arrow.svg'
    },
    {
      name: 'selection',
      expression: function(importSelection) { return importSelection || null; },
    },
    'hoverSelection',
    'dropdownOrigin',
    'overlayOrigin',
    {
      type: 'Boolean',
      name: 'showHeader',
      value: true,
      documentation: 'Set to false to not render the header.'
    }
  ],

  methods: [
    function sortBy(column) {
      this.order = this.order === column ?
        this.DESC(column) :
        column;
    },

    function createColumnSelection() {
      var editor = this.EditColumnsView.create({
        columns: this.columns,
        columns_$: this.columns_$,
        table: this.of
      });

      return this.OverlayDropdown.create().add(editor);
    },

    function initE() {
      var view = this;
      var columnSelectionE;

      if ( this.editColumnsEnabled ) {
        columnSelectionE = this.createColumnSelection();
        this.ctrl.add(columnSelectionE);
      }

      this.
        addClass(this.myClass()).
        addClass(this.myClass(this.of.id.replace(/\./g, '-'))).
        setNodeName('table').
        start('thead').
          show(this.showHeader$).
          add(this.slot(function(columns_) {
            return this.E('tr').
              forEach(columns_, function(column) {
                this.start('th').
                  addClass(view.myClass('th-' + column.name)).
                  callIf(column.tableWidth, function() {
                    this.style({ width: column.tableWidth + 'px' });
                  }).
                  on('click', function(e) {
                    view.sortBy(column);
                  }).
                  call(column.tableHeaderFormatter, [column]).
                  callIf(column.label != '', function() {
                    this.start('img').attr('src', this.slot(function(order) {
                      return column === order ? view.ascIcon :
                          (view.Desc.isInstance(order) && order.arg1 === column)
                          ? view.descIcon : view.restingIcon;
                    }, view.order$)).end();
                  }).
                end();
              }).
              call(function() {
                this.start('th').
                  callIf(view.editColumnsEnabled, function() {
                    this.addClass(view.myClass('th-editColumns')).
                    on('click', function(e) {
                      columnSelectionE.open(e.clientX, e.clientY);
                    }).
                    tag(view.Image, { data: '/images/Icon_More_Resting.svg' }).
                    addClass(view.myClass('vertDots')).
                    addClass(view.myClass('noselect'));
                  }).
                  style({ width: '60px' }).
                  tag('div', null, view.dropdownOrigin$).
                end();
              });
          })).
        end().
        add(this.rowsFrom(this.data));
    },
    {
      name: 'rowsFrom',
      code: function(dao) {
        var view = this;
        return this.slot(function(columns_) {
          // Make sure the DAO set here responds to ordering when a user clicks
          // on a table column header to sort by that column.
          if ( this.order ) dao = dao.orderBy(this.order);
          var proxy = view.ProxyDAO.create({ delegate: dao });
          view.sub('propertyChange', 'order', function(_, __, ___, s) {
            proxy.delegate = dao.orderBy(s.get());
          });

          var modelActions = view.of.getAxiomsByClass(foam.core.Action);
          var actions = Array.isArray(view.contextMenuActions)
            ? view.contextMenuActions.concat(modelActions)
            : modelActions;

          return this.
            E('tbody').
            select(proxy, function(obj) {
              return this.E('tr').
                on('mouseover', function() { view.hoverSelection = obj; }).
                callIf(view.dblclick && ! view.disableUserSelection, function() {
                  this.on('dblclick', function() {
                    view.dblclick && view.dblclick(obj);
                  });
                }).
                callIf( ! view.disableUserSelection, function() {
                  this.on('click', function(evt) {
                    // If we're clicking somewhere to close the context menu,
                    // don't do anything.
                    if (
                      evt.target.nodeName === 'DROPDOWN-OVERLAY' ||
                      evt.target.classList.contains(view.myClass('vertDots'))
                    ) return;

                    view.selection = obj;
                    if ( view.importSelection$ ) view.importSelection = obj;
                    if ( view.editRecord$ ) view.editRecord(obj);
                  });
                }).
                addClass(view.slot(function(selection) {
                  return selection && foam.util.equals(obj.id, selection.id) ?
                      view.myClass('selected') : '';
                })).
                addClass(view.myClass('row')).
                forEach(columns_, function(column) {
                  this.
                    start('td').
                      callOn(column.tableCellFormatter, 'format', [
                        column.f ? column.f(obj) : null, obj, column
                      ]).
                      callIf(column.f, function() {
                        try {
                          var value = column.f(obj);
                          if ( foam.util.isPrimitive(value) ) {
                            this.attr('title', value);
                          }
                        } catch (err) {}
                      }).
                    end();
                }).
                start('td')
                  .attrs({ name:'contextMenuCell' }).
                  addClass(view.myClass('context-menu-cell')).
                  tag(view.OverlayActionListView, {
                    data: actions,
                    obj: obj
                  }).
                end();
            });
        });
      }
    }
  ]
});
<commit_msg>Use the DAOProperty's proxy in the tableview so it reacts when the dao changes.<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'UnstyledTableView',
  extends: 'foam.u2.Element',

  implements: [
    'foam.mlang.Expressions'
  ],

  requires: [
    'foam.dao.ProxyDAO',
    'foam.u2.md.OverlayDropdown',
    'foam.u2.view.OverlayActionListView',
    'foam.u2.view.EditColumnsView',
    'foam.u2.tag.Image'
  ],

  exports: [
    'columns',
    'selection',
    'hoverSelection'
  ],

  imports: [
    'ctrl',
    'dblclick?',
    'editRecord?',
    'selection? as importSelection'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data',
      postSet: function(_, data) {
        if ( ! this.of && data ) this.of = data.of;
      }
    },
    {
      name: 'order'
    },
    {
      name: 'columns_',
      expression: function(columns, of) {
        var of = this.of;
        if ( ! of ) return [];

        return columns.map(function(p) {
          var c = typeof p == 'string' ?
            of.getAxiomByName(p) :
            p;

           if ( ! c ) {
             console.error('Unknown table column: ', p);
           }

          return c;
        }).filter(function(c) { return c; });
      }
    },
    {
      name: 'columns',
      expression: function(of) {
        var of = this.of;
        if ( ! of ) return [];

        var tableColumns = of.getAxiomByName('tableColumns');

        if ( tableColumns ) return tableColumns.columns;

        return of.getAxiomsByClass(foam.core.Property).
            filter(function(p) { return p.tableCellFormatter && ! p.hidden; }).
            map(foam.core.Property.NAME.f);
      }
    },
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions',
      documentation: `
        Each table row has a context menu that contains actions you can perform
        on the object in that row. The actions used to populate that menu come
        from two different sources. The first source is this property. If you
        want a context menu action to do something in the view, then you should
        write the code for that action in the view model and pass it to the
        table view via this property. The second source of actions is from the
        model of the object being shown in the table.
      `
    },
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      value: true,
      documentation: 'Set this to true to let the user select columns.'
    },
    {
      class: 'Boolean',
      name: 'disableUserSelection',
      value: false,
      documentation: 'Ignores selection by user.'
    },
    {
      name: 'restingIcon',
      documentation: 'Image for grayed out double arrow when table header is not sorting',
      value: 'images/resting-arrow.svg'
    },
    {
      name: 'ascIcon',
      documentation: 'Image for table header ascending sorting arrow',
      value: 'images/up-arrow.svg'
    },
    {
      name: 'descIcon',
      documentation: 'Image for table header descending sorting arrow',
      value: 'images/down-arrow.svg'
    },
    {
      name: 'selection',
      expression: function(importSelection) { return importSelection || null; },
    },
    'hoverSelection',
    'dropdownOrigin',
    'overlayOrigin',
    {
      type: 'Boolean',
      name: 'showHeader',
      value: true,
      documentation: 'Set to false to not render the header.'
    }
  ],

  methods: [
    function sortBy(column) {
      this.order = this.order === column ?
        this.DESC(column) :
        column;
    },

    function createColumnSelection() {
      var editor = this.EditColumnsView.create({
        columns: this.columns,
        columns_$: this.columns_$,
        table: this.of
      });

      return this.OverlayDropdown.create().add(editor);
    },

    function initE() {
      var view = this;
      var columnSelectionE;

      if ( this.editColumnsEnabled ) {
        columnSelectionE = this.createColumnSelection();
        this.ctrl.add(columnSelectionE);
      }

      this.
        addClass(this.myClass()).
        addClass(this.myClass(this.of.id.replace(/\./g, '-'))).
        setNodeName('table').
        start('thead').
          show(this.showHeader$).
          add(this.slot(function(columns_) {
            return this.E('tr').
              forEach(columns_, function(column) {
                this.start('th').
                  addClass(view.myClass('th-' + column.name)).
                  callIf(column.tableWidth, function() {
                    this.style({ width: column.tableWidth + 'px' });
                  }).
                  on('click', function(e) {
                    view.sortBy(column);
                  }).
                  call(column.tableHeaderFormatter, [column]).
                  callIf(column.label != '', function() {
                    this.start('img').attr('src', this.slot(function(order) {
                      return column === order ? view.ascIcon :
                          (view.Desc.isInstance(order) && order.arg1 === column)
                          ? view.descIcon : view.restingIcon;
                    }, view.order$)).end();
                  }).
                end();
              }).
              call(function() {
                this.start('th').
                  callIf(view.editColumnsEnabled, function() {
                    this.addClass(view.myClass('th-editColumns')).
                    on('click', function(e) {
                      columnSelectionE.open(e.clientX, e.clientY);
                    }).
                    tag(view.Image, { data: '/images/Icon_More_Resting.svg' }).
                    addClass(view.myClass('vertDots')).
                    addClass(view.myClass('noselect'));
                  }).
                  style({ width: '60px' }).
                  tag('div', null, view.dropdownOrigin$).
                end();
              });
          })).
        end().
        add(this.rowsFrom(this.data$proxy));
    },
    {
      name: 'rowsFrom',
      code: function(dao) {
        var view = this;
        return this.slot(function(columns_) {
          // Make sure the DAO set here responds to ordering when a user clicks
          // on a table column header to sort by that column.
          if ( this.order ) dao = dao.orderBy(this.order);
          var proxy = view.ProxyDAO.create({ delegate: dao });
          view.sub('propertyChange', 'order', function(_, __, ___, s) {
            proxy.delegate = dao.orderBy(s.get());
          });

          var modelActions = view.of.getAxiomsByClass(foam.core.Action);
          var actions = Array.isArray(view.contextMenuActions)
            ? view.contextMenuActions.concat(modelActions)
            : modelActions;

          return this.
            E('tbody').
            select(proxy, function(obj) {
              return this.E('tr').
                on('mouseover', function() { view.hoverSelection = obj; }).
                callIf(view.dblclick && ! view.disableUserSelection, function() {
                  this.on('dblclick', function() {
                    view.dblclick && view.dblclick(obj);
                  });
                }).
                callIf( ! view.disableUserSelection, function() {
                  this.on('click', function(evt) {
                    // If we're clicking somewhere to close the context menu,
                    // don't do anything.
                    if (
                      evt.target.nodeName === 'DROPDOWN-OVERLAY' ||
                      evt.target.classList.contains(view.myClass('vertDots'))
                    ) return;

                    view.selection = obj;
                    if ( view.importSelection$ ) view.importSelection = obj;
                    if ( view.editRecord$ ) view.editRecord(obj);
                  });
                }).
                addClass(view.slot(function(selection) {
                  return selection && foam.util.equals(obj.id, selection.id) ?
                      view.myClass('selected') : '';
                })).
                addClass(view.myClass('row')).
                forEach(columns_, function(column) {
                  this.
                    start('td').
                      callOn(column.tableCellFormatter, 'format', [
                        column.f ? column.f(obj) : null, obj, column
                      ]).
                      callIf(column.f, function() {
                        try {
                          var value = column.f(obj);
                          if ( foam.util.isPrimitive(value) ) {
                            this.attr('title', value);
                          }
                        } catch (err) {}
                      }).
                    end();
                }).
                start('td')
                  .attrs({ name:'contextMenuCell' }).
                  addClass(view.myClass('context-menu-cell')).
                  tag(view.OverlayActionListView, {
                    data: actions,
                    obj: obj
                  }).
                end();
            });
        });
      }
    }
  ]
});
<|endoftext|>


<commit_before>var request = require('request');
var _ = require('underscore');
var Promise = require('bluebird');
var Boom = require('boom');
var spawn = require('child_process').spawn;
var couchUpdateViews = require('couch-update-views');
var path = require('path');
var qs = require('querystring');
var os = require('os');
var tarGzip = require('node-targz');

module.exports = function (server, conf) {

	if(!server.methods.clusterprovider){
		throw new Error("Have you installed the 'couch-provider' plugin with namespace 'clusterprovider'?");
	}

	couchUpdateViews.migrateUp(server.methods.clusterprovider.getCouchDBServer(), path.join(__dirname, 'views'), true);

	var handler = {};
	/*
	*/
	handler.createJob = function(req, rep){

		var job = req.payload;
		job.timestamp = new Date();
		job.jobstatus = {
			status: 'CREATE'
		};

		server.methods.clusterprovider.uploadDocuments(job)
		.then(function(res){
			if(res.length === 1){
				return res[0];
			}else{
				return res;
			}
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.badRequest(e));
		});

	}

	/*
	*/
	handler.updateJob = function(req, rep){

		var job = req.payload;
		var credentials = req.auth.credentials;

		var prom = [
			server.methods.clusterprovider.validateJobOwnership(job, credentials),
			server.methods.clusterprovider.getDocument(job._id)
			.then(function(doc){
				return server.methods.clusterprovider.validateJobOwnership(doc, credentials);
			})
			.catch(function(e){
				throw Boom.unauthorized("You are not allowed to update the document, it belongs to someone else");
			})
		]

		Promise.all(prom)
		.then(function(){
			return server.methods.clusterprovider.uploadDocuments(job);
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	/*
	*/
	handler.addData = function(req, rep){
		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.isJobDocument(doc);
		})
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){
			return server.methods.clusterprovider.addDocumentAttachment(doc, req.params.name, req.payload);
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	const getDocumentURIAttachment = function(doc, name){
		if(doc._attachments && doc._attachments[name]){
			return server.methods.clusterprovider.getDocumentURIAttachment(doc._id + "/" + name);
		}else{
			var att = _.find(doc.inputs, function(input){
				return input.name === name;
			});
			if(!att){
				att = _.find(doc.outputs, function(output){
					return output.name === name;
				});
			}
			if(!att){
				throw Boom.notFound("The attachment was not found -> " + name);
			}
			if(att.type === 'tar.gz'){
				name += ".tar.gz";
			}
			if(att.remote){
				if(att.remote.serverCodename){
					return server.methods.clusterprovider.getDocumentURIAttachment(att.remote.uri, att.remote.serverCodename);
				}else{ //The next case is for the dataprovider-fs
					return {
						uri: att.remote.uri,
						passThrough: true,
						rejectUnauthorized: false
					};
				}
			}else if(att.local){
				return server.methods.clusterprovider.getDocumentURIAttachment(att.local.uri);
			}else{
				return server.methods.clusterprovider.getDocumentURIAttachment(doc._id + "/" + name);
			}

		}
	}

	/*
	*/
	handler.getJob = function(req, rep){

		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){
			if(req.params.name){
				rep.proxy(getDocumentURIAttachment(doc, req.params.name));
			}else{
				rep(doc);
			}

		})
		.catch(function(e){
			rep(Boom.wrap(e));
		});

	}

	handler.getDownloadToken = function(req, rep){

		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){

			var name = req.params.name;
			rep(server.methods.jwtauth.sign({ _id: doc._id, name: name }));

		})
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	handler.downloadAttachment = function(req, rep){
		var token = req.params.token;

		try{

			var decodedToken = server.methods.jwt.verify(token);

			server.methods.clusterprovider.getDocument(decodedToken._id)
			.then(function(doc){
				rep.proxy(getDocumentURIAttachment(doc, decodedToken.name));
			})
			.catch(function(e){
				rep(Boom.unauthorized(e));
			});

		}catch(e){
			rep(Boom.unauthorized(e));
		}

	}

	const jobDelete = function(doc){
		return server.methods.clusterprovider.getDocument(doc._id)
		.then(function(doc){
			return server.methods.clusterprovider.deleteDocument(doc);
		});
	}

	server.method({
	    name: 'dataprovider.jobDelete',
	    method: jobDelete,
	    options: {}
	});

	handler.deleteJob = function(req, rep){

		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.isJobDocument(doc);
		})
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){
			//The job is added to the delete queue. The actual deleation is done in the dataprovider.jobDelete function
			//After the job is deleted in the executionserver side, the deletion is done there.
			//The call to these functions is done in the cronprovider that manages the queues
			return server.methods.cronprovider.addJobToDeleteQueue(doc);

		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	/*
	*/
	handler.getUserJobs = function(req, rep){

		var credentials = req.auth.credentials;
		var email = credentials.email;

		if(req.query.userEmail && email !== req.query.userEmail && credentials.scope.indexOf('admin') === -1){
			throw Boom.unauthorized("You are not allowed to view the jobs of other users.");
		}else if(req.query.userEmail){
			email = req.query.userEmail;
		}

		var jobstatus = req.query.jobstatus;
		var executable = req.query.executable;
		var executionserver = req.query.executionserver;
		var view;

		if(jobstatus && executable){
			var key = [email, jobstatus, executable];
			view = '_design/searchJob/_view/useremailjobstatusexecutable?include_docs=true&key=' + JSON.stringify(key);
		}else if(jobstatus && email){
			var key = [email, jobstatus];
			view = '_design/searchJob/_view/useremailjobstatus?include_docs=true&key=' + JSON.stringify(key);
		}else if(executable){
			var key = [email, executable];
			view = '_design/searchJob/_view/useremailexecutable?include_docs=true&key=' + JSON.stringify(key);
		}else if(executionserver){
			var key = {
				key: JSON.stringify([executionserver, jobstatus]),
				include_docs: true
			}
		    view = '_design/searchJob/_view/executionserverjobstatus?' + qs.stringify(key);
		}else{
			view = '_design/searchJob/_view/useremail?include_docs=true&key=' + JSON.stringify(email);
		}

		server.methods.clusterprovider.getView(view)
		.then(function(rows){
			return _.pluck(rows, 'doc');
		})
		.then(function(docs){
			return Promise.map(credentials.scope, function(sc){
				var key = {
					key: JSON.stringify(sc),
					include_docs: true
				}
				var v = '_design/searchJob/_view/scope?' + qs.stringify(key);
				return server.methods.clusterprovider.getView(v)
				.then(function(rows){
					return _.pluck(rows, 'doc');
				});
			})
			.then(function(res){
				return _.uniq(_.compact(_.flatten(res)));
			})
			.then(function(docssp){
				return _.union(docs, docssp);
			});
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		})
	}

	/*
	*/

	handler.getAllJobs = function(req, rep){
		var executable = req.query.executable;

		if(executable){
			var key = executable;
			view = '_design/searchJob/_view/executable?include_docs=true&key=' + JSON.stringify(key);
		}else{
			view = '_design/searchJob/_view/executable?include_docs=true';
		}
		server.methods.clusterprovider.getView(view)
		.then(function(rows){
			return _.pluck(rows, 'doc');
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	const saveAttachment = function(options, filename){

		return new Promise(function(resolve, reject){

			var writestream = fs.createWriteStream(filename);
            request(options).pipe(writestream);

            writestream.on('finish', function(err){
                if(err){
                    reject({
                        "path" : filename,
                        "status" : false,
                        "error": err
                    });
                }else{
                    resolve({
                        "path" : filename,
                        "status" : true
                    });
                }
            });
		})
	}

	/*
	*/
	handler.getDownload = function(req, rep){
		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){

			try{

				var tempdir = path.join(os.tmpdir(), doc._id);
				fs.mkdirSync(tempdir);

				var outputs = doc.outputs;

				return Promise.map(outputs, function(output){
					return saveAttachment(getDocumentURIAttachment(doc._id, output.name), output.name);
				})
				.then(function(){
					return new Promise(function(resolve, reject){
						var tarname = tempdir + ".tar.gz";
						tarGzip.compress({
						    source: tempdir,
						    destination: tarname
						}, function(){
							resolve(tarname);
						});
					});
				});

			}catch(e){
				rep(Boom.badImplementation(e));
			}

		})
		.then(function(tarname){
			try{
				if(fs.statFileSync(tarname)){
					rep.file(tarname);
				}
			}catch(e){
				throw Boom.badImplementation(e);
			}
		})
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	const deleteFolderRecursive = function(dir) {
		var dirstat;
		try{
			dirstat = fs.statSync(dir);
		}catch(e){
			//does not exist
			dirstat = undefined;
		}
		if(dirstat){
			fs.readdirSync(dir).forEach(function(file) {
				var currentpath = path.join(dir, file);
				if(fs.statSync(currentpath).isDirectory()) {
					handler.deleteFolderRecursive(currentpath);
				}else{
					fs.unlinkSync(currentpath);
				}
			});
			fs.rmdirSync(dir);
			return true;
	    }
	    return false;
	}

	handler.deleteDownload = function(req, rep){

		try{
			var tempdir = path.join(os.tmpdir(), req.params.id);
			deleteFolderRecursive(tempdir);
			var tarfile = tempdir + ".tar.gz";
			if(fs.statSync(tarfile)){
				fs.unlinkSync(tarfile);
			}
		}catch(e){
			console.error(e);
		}
		rep(true);
	}

	return handler;
}
<commit_msg>ENH: Filter jobs when retrieve by scope<commit_after>var request = require('request');
var _ = require('underscore');
var Promise = require('bluebird');
var Boom = require('boom');
var spawn = require('child_process').spawn;
var couchUpdateViews = require('couch-update-views');
var path = require('path');
var qs = require('querystring');
var os = require('os');
var tarGzip = require('node-targz');

module.exports = function (server, conf) {

	if(!server.methods.clusterprovider){
		throw new Error("Have you installed the 'couch-provider' plugin with namespace 'clusterprovider'?");
	}

	couchUpdateViews.migrateUp(server.methods.clusterprovider.getCouchDBServer(), path.join(__dirname, 'views'), true);

	var handler = {};
	/*
	*/
	handler.createJob = function(req, rep){

		var job = req.payload;
		job.timestamp = new Date();
		job.jobstatus = {
			status: 'CREATE'
		};

		server.methods.clusterprovider.uploadDocuments(job)
		.then(function(res){
			if(res.length === 1){
				return res[0];
			}else{
				return res;
			}
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.badRequest(e));
		});

	}

	/*
	*/
	handler.updateJob = function(req, rep){

		var job = req.payload;
		var credentials = req.auth.credentials;

		var prom = [
			server.methods.clusterprovider.validateJobOwnership(job, credentials),
			server.methods.clusterprovider.getDocument(job._id)
			.then(function(doc){
				return server.methods.clusterprovider.validateJobOwnership(doc, credentials);
			})
			.catch(function(e){
				throw Boom.unauthorized("You are not allowed to update the document, it belongs to someone else");
			})
		]

		Promise.all(prom)
		.then(function(){
			return server.methods.clusterprovider.uploadDocuments(job);
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	/*
	*/
	handler.addData = function(req, rep){
		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.isJobDocument(doc);
		})
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){
			return server.methods.clusterprovider.addDocumentAttachment(doc, req.params.name, req.payload);
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	const getDocumentURIAttachment = function(doc, name){
		if(doc._attachments && doc._attachments[name]){
			return server.methods.clusterprovider.getDocumentURIAttachment(doc._id + "/" + name);
		}else{
			var att = _.find(doc.inputs, function(input){
				return input.name === name;
			});
			if(!att){
				att = _.find(doc.outputs, function(output){
					return output.name === name;
				});
			}
			if(!att){
				throw Boom.notFound("The attachment was not found -> " + name);
			}
			if(att.type === 'tar.gz'){
				name += ".tar.gz";
			}
			if(att.remote){
				if(att.remote.serverCodename){
					return server.methods.clusterprovider.getDocumentURIAttachment(att.remote.uri, att.remote.serverCodename);
				}else{ //The next case is for the dataprovider-fs
					return {
						uri: att.remote.uri,
						passThrough: true,
						rejectUnauthorized: false
					};
				}
			}else if(att.local){
				return server.methods.clusterprovider.getDocumentURIAttachment(att.local.uri);
			}else{
				return server.methods.clusterprovider.getDocumentURIAttachment(doc._id + "/" + name);
			}

		}
	}

	/*
	*/
	handler.getJob = function(req, rep){

		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){
			if(req.params.name){
				rep.proxy(getDocumentURIAttachment(doc, req.params.name));
			}else{
				rep(doc);
			}

		})
		.catch(function(e){
			rep(Boom.wrap(e));
		});

	}

	handler.getDownloadToken = function(req, rep){

		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){

			var name = req.params.name;
			rep(server.methods.jwtauth.sign({ _id: doc._id, name: name }));

		})
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	handler.downloadAttachment = function(req, rep){
		var token = req.params.token;

		try{

			var decodedToken = server.methods.jwt.verify(token);

			server.methods.clusterprovider.getDocument(decodedToken._id)
			.then(function(doc){
				rep.proxy(getDocumentURIAttachment(doc, decodedToken.name));
			})
			.catch(function(e){
				rep(Boom.unauthorized(e));
			});

		}catch(e){
			rep(Boom.unauthorized(e));
		}

	}

	const jobDelete = function(doc){
		return server.methods.clusterprovider.getDocument(doc._id)
		.then(function(doc){
			return server.methods.clusterprovider.deleteDocument(doc);
		});
	}

	server.method({
	    name: 'dataprovider.jobDelete',
	    method: jobDelete,
	    options: {}
	});

	handler.deleteJob = function(req, rep){

		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.isJobDocument(doc);
		})
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){
			//The job is added to the delete queue. The actual deleation is done in the dataprovider.jobDelete function
			//After the job is deleted in the executionserver side, the deletion is done there.
			//The call to these functions is done in the cronprovider that manages the queues
			return server.methods.cronprovider.addJobToDeleteQueue(doc);

		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	/*
	*/
	handler.getUserJobs = function(req, rep){

		var credentials = req.auth.credentials;
		var email = credentials.email;

		if(req.query.userEmail && email !== req.query.userEmail && credentials.scope.indexOf('admin') === -1){
			throw Boom.unauthorized("You are not allowed to view the jobs of other users.");
		}else if(req.query.userEmail){
			email = req.query.userEmail;
		}

		var jobstatus = req.query.jobstatus;
		var executable = req.query.executable;
		var executionserver = req.query.executionserver;
		var view;

		if(jobstatus && executable){
			var key = [email, jobstatus, executable];
			view = '_design/searchJob/_view/useremailjobstatusexecutable?include_docs=true&key=' + JSON.stringify(key);
		}else if(jobstatus && email){
			var key = [email, jobstatus];
			view = '_design/searchJob/_view/useremailjobstatus?include_docs=true&key=' + JSON.stringify(key);
		}else if(executable){
			var key = [email, executable];
			view = '_design/searchJob/_view/useremailexecutable?include_docs=true&key=' + JSON.stringify(key);
		}else if(executionserver){
			var key = {
				key: JSON.stringify([executionserver, jobstatus]),
				include_docs: true
			}
		    view = '_design/searchJob/_view/executionserverjobstatus?' + qs.stringify(key);
		}else{
			view = '_design/searchJob/_view/useremail?include_docs=true&key=' + JSON.stringify(email);
		}

		server.methods.clusterprovider.getView(view)
		.then(function(rows){
			return _.pluck(rows, 'doc');
		})
		.then(function(docs){
			return Promise.map(credentials.scope, function(sc){
				var key = {
					key: JSON.stringify(sc),
					include_docs: true
				}
				var v = '_design/searchJob/_view/scope?' + qs.stringify(key);
				return server.methods.clusterprovider.getView(v)
				.then(function(rows){
					var docs = _.pluck(rows, 'doc');
					if(executable){
						docs = _.filter(docs, function(doc){
							return doc.executable == executable;
						});
					}
					if(jobstatus){
						docs = _.filter(docs, function(doc){
							if(doc.jobstatus && doc.jobstatus.status){
								return doc.jobstatus.status == jobstatus;
							}
							return false;
						});
					}
					return docs;
				});
			})
			.then(function(res){
				return _.uniq(_.compact(_.flatten(res)));
			})
			.then(function(docssp){
				return _.union(docs, docssp);
			});
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		})
	}

	/*
	*/

	handler.getAllJobs = function(req, rep){
		var executable = req.query.executable;

		if(executable){
			var key = executable;
			view = '_design/searchJob/_view/executable?include_docs=true&key=' + JSON.stringify(key);
		}else{
			view = '_design/searchJob/_view/executable?include_docs=true';
		}
		server.methods.clusterprovider.getView(view)
		.then(function(rows){
			return _.pluck(rows, 'doc');
		})
		.then(rep)
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	const saveAttachment = function(options, filename){

		return new Promise(function(resolve, reject){

			var writestream = fs.createWriteStream(filename);
            request(options).pipe(writestream);

            writestream.on('finish', function(err){
                if(err){
                    reject({
                        "path" : filename,
                        "status" : false,
                        "error": err
                    });
                }else{
                    resolve({
                        "path" : filename,
                        "status" : true
                    });
                }
            });
		})
	}

	/*
	*/
	handler.getDownload = function(req, rep){
		server.methods.clusterprovider.getDocument(req.params.id)
		.then(function(doc){
			return server.methods.clusterprovider.validateJobOwnership(doc, req.auth.credentials);
		})
		.then(function(doc){

			try{

				var tempdir = path.join(os.tmpdir(), doc._id);
				fs.mkdirSync(tempdir);

				var outputs = doc.outputs;

				return Promise.map(outputs, function(output){
					return saveAttachment(getDocumentURIAttachment(doc._id, output.name), output.name);
				})
				.then(function(){
					return new Promise(function(resolve, reject){
						var tarname = tempdir + ".tar.gz";
						tarGzip.compress({
						    source: tempdir,
						    destination: tarname
						}, function(){
							resolve(tarname);
						});
					});
				});

			}catch(e){
				rep(Boom.badImplementation(e));
			}

		})
		.then(function(tarname){
			try{
				if(fs.statFileSync(tarname)){
					rep.file(tarname);
				}
			}catch(e){
				throw Boom.badImplementation(e);
			}
		})
		.catch(function(e){
			rep(Boom.wrap(e));
		});
	}

	const deleteFolderRecursive = function(dir) {
		var dirstat;
		try{
			dirstat = fs.statSync(dir);
		}catch(e){
			//does not exist
			dirstat = undefined;
		}
		if(dirstat){
			fs.readdirSync(dir).forEach(function(file) {
				var currentpath = path.join(dir, file);
				if(fs.statSync(currentpath).isDirectory()) {
					handler.deleteFolderRecursive(currentpath);
				}else{
					fs.unlinkSync(currentpath);
				}
			});
			fs.rmdirSync(dir);
			return true;
	    }
	    return false;
	}

	handler.deleteDownload = function(req, rep){

		try{
			var tempdir = path.join(os.tmpdir(), req.params.id);
			deleteFolderRecursive(tempdir);
			var tarfile = tempdir + ".tar.gz";
			if(fs.statSync(tarfile)){
				fs.unlinkSync(tarfile);
			}
		}catch(e){
			console.error(e);
		}
		rep(true);
	}

	return handler;
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/***
 * Setup:
 *
 * npm install maxcdn subarg async
 *
 ***/
var MaxCDN, subarg, async;
try {
    MaxCDN = require('maxcdn');
    subarg = require('subarg');
    async  = require('async');
} catch (e) {
    console.error("Setup:");
    console.error(" ");
    console.error("$ npm install maxcdn subarg async ");
    console.error(" ");
    console.trace(e);
    process.exit(1);
}
var opts   = subarg(process.argv.slice(2));

if (opts.help || opts.h) usage();

[ 'alias', 'key', 'secret', 'type', 'name', 'url' ].forEach(function(opt) {
    // support ALIAS, KEY, SECRET from environment
    opts[opt] = opts[opt] || process.env[opt.toUpperCase()];

    // ensure required params exist
    if (typeof opts[opt] === 'undefined') {
        usage(1, "Missing required argument: "+opt); // usage with error status
    }

    if (opts[opt] === '') {
        usage(1, "Argument can't be an empty string: "+opt); // usage with error status
    }
});

// init MaxCDN
var maxcdn = new MaxCDN(opts.alias, opts.key, opts.secret);

createZone(opts.type, opts.name, opts.url);

function createZone(type, name, url) {
    process.stdout.write('creating ' + type + ' zone ' + name + '... ');
    maxcdn.post('zones/'+type+'.json', {name: name, url: url}, function (err, res) {
        if (res == undefined) {
            console.log('zone creation failed');
        } else {
            console.log('complete', res);
        }
    });
}

function usage(status, error) {
    status = status || 0; // default to zero exit status

    if (error) {
        console.error("ERROR:", error);
        console.error(" ");
    }

    console.log("Usage: deploy.js --alias ALIAS --key KEY --secret SECRET [OPTIONS]");
    console.log(" ");
    console.log(" Required:");
    console.log(" - alias:  Your consumer alias.");
    console.log(" - key:    Your oauth consumer key.");
    console.log(" - secret: Your oauth consumer secret token.");
    console.log(" ");
    console.log(" Note:");
    console.log("   alias, key and secret can also be read from your environment");
    console.log("   via exporting ALIAS, KEY, and/or SECRET with your credentials.");
    console.log(" ");
    console.log(" Examples:");
    console.log(" ");
    console.log(" $ ./deploy.js --alias ALIAS --key KEY --SECRET --type pull --name test123 --url http://www.example.com");
    console.log(" ");
    process.exit(status);
}
<commit_msg>updated setup<commit_after>#!/usr/bin/env node
/***
 * Setup:
 *
 * npm install
 *
 ***/
var MaxCDN, subarg, async;
try {
    MaxCDN = require('maxcdn');
    subarg = require('subarg');
    async  = require('async');
} catch (e) {
    console.error("Setup:");
    console.error(" ");
    console.error("$ npm install");
    console.error(" ");
    console.trace(e);
    process.exit(1);
}
var opts   = subarg(process.argv.slice(2));

if (opts.help || opts.h) usage();

[ 'alias', 'key', 'secret', 'type', 'name', 'url' ].forEach(function(opt) {
    // support ALIAS, KEY, SECRET from environment
    opts[opt] = opts[opt] || process.env[opt.toUpperCase()];

    // ensure required params exist
    if (typeof opts[opt] === 'undefined') {
        usage(1, "Missing required argument: "+opt); // usage with error status
    }

    if (opts[opt] === '') {
        usage(1, "Argument can't be an empty string: "+opt); // usage with error status
    }
});

// init MaxCDN
var maxcdn = new MaxCDN(opts.alias, opts.key, opts.secret);

createZone(opts.type, opts.name, opts.url);

function createZone(type, name, url) {
    process.stdout.write('creating ' + type + ' zone ' + name + '... ');
    maxcdn.post('zones/'+type+'.json', {name: name, url: url}, function (err, res) {
        if (res == undefined) {
            console.log('zone creation failed');
        } else {
            console.log('complete', res);
        }
    });
}

function usage(status, error) {
    status = status || 0; // default to zero exit status

    if (error) {
        console.error("ERROR:", error);
        console.error(" ");
    }

    console.log("Usage: deploy.js --alias ALIAS --key KEY --secret SECRET [OPTIONS]");
    console.log(" ");
    console.log(" Required:");
    console.log(" - alias:  Your consumer alias.");
    console.log(" - key:    Your oauth consumer key.");
    console.log(" - secret: Your oauth consumer secret token.");
    console.log(" ");
    console.log(" Note:");
    console.log("   alias, key and secret can also be read from your environment");
    console.log("   via exporting ALIAS, KEY, and/or SECRET with your credentials.");
    console.log(" ");
    console.log(" Examples:");
    console.log(" ");
    console.log(" $ ./deploy.js --alias ALIAS --key KEY --SECRET --type pull --name test123 --url http://www.example.com");
    console.log(" ");
    process.exit(status);
}
<|endoftext|>


<commit_before>"use strict";

class gsuiEffects extends HTMLElement {
	constructor() {
		const elFxsList = GSUI.getTemplate( "gsui-effects" ),
			elBtnSelect = elFxsList.querySelector( ".gsuiEffects-addBtn" ),
			elAddSelect = elFxsList.querySelector( ".gsuiEffects-addSelect" );

		super();
		this.askData = () => {};
		this._fxsHtml = new Map();
		this._elFxsList = elFxsList;
		this._elAddSelect = elAddSelect;
		this._dispatch = GSUI.dispatchEvent.bind( null, this, "gsuiEffects" );
		Object.seal( this );

		elBtnSelect.onclick = () => this._elAddSelect.value = "";
		elAddSelect.onchange = this._onchangeAddSelect.bind( this );
		elAddSelect.onkeydown = () => false;
		new gsuiReorder( {
			rootElement: elFxsList,
			direction: "column",
			dataTransferType: "effect",
			itemSelector: ".gsuiEffects-fx",
			handleSelector: ".gsuiEffects-fx-grip",
			parentSelector: ".gsuiEffects-list",
		} );
		GSUI.listenEvents( this, {
			default: {
				liveChange: ( d, t ) => {
					d.args.unshift( t.dataset.id );
					d.component = "gsuiEffects";
					d.eventName = "liveChangeEffect";
					return true;
				},
				changeProp: ( d, t ) => {
					d.args.unshift( t.dataset.id );
					d.component = "gsuiEffects";
					d.eventName = "changeEffect";
					return true;
				},
			},
		} );
		this._fillSelect();
	}

	// .........................................................................
	connectedCallback() {
		if ( !this.firstChild ) {
			this.classList.add( "gsuiEffects" );
			this.append( this._elFxsList );
		}
	}

	// .........................................................................
	expandToggleEffect( id ) {
		const root = this._fxsHtml.get( id ).root;

		this.expandEffect( id, !root.classList.contains( "gsuiEffects-fx-expanded" ) );
	}
	expandEffect( id, b ) {
		const html = this._fxsHtml.get( id ),
			type = html.root.dataset.type;

		html.root.classList.toggle( "gsuiEffects-fx-expanded", b );
		html.expand.dataset.icon = b ? "caret-down" : "caret-right";
		html.content.style.height = `${ b ? gsuiEffects.fxsMap.get( type ).height : 0 }px`;
	}

	// .........................................................................
	addEffect( id, fx ) {
		const root = GSUI.getTemplate( "gsui-effects-fx" ),
			name = root.querySelector( ".gsuiEffects-fx-name" ),
			expand = root.querySelector( ".gsuiEffects-fx-expand" ),
			toggle = root.querySelector( ".gsuiEffects-fx-toggle" ),
			remove = root.querySelector( ".gsuiEffects-fx-remove" ),
			content = root.querySelector( ".gsuiEffects-fx-content" ),
			fxAsset = gsuiEffects.fxsMap.get( fx.type ),
			uiFx = new fxAsset.cmp(),
			html = Object.seal( {
				uiFx,
				root,
				expand,
				content,
			} );

		expand.onclick = () => this.expandToggleEffect( id );
		toggle.onclick = () => this._dispatch( "toggleEffect", id );
		remove.onclick = () => this._dispatch( "removeEffect", id );
		uiFx.askData = this.askData.bind( null, id, fx.type );
		uiFx.dataset.id = id;
		root.dataset.type = fx.type;
		name.textContent = fxAsset.name;
		content.append( uiFx );
		this._fxsHtml.set( id, html );
		this._elFxsList.append( root );
	}
	removeEffect( id ) {
		this._fxsHtml.get( id ).root.remove();
		this._fxsHtml.delete( id );
	}
	changeEffect( id, prop, val ) {
		switch ( prop ) {
			case "toggle": this._changeToggle( id, val ); break;
			case "order": this._fxsHtml.get( id ).root.dataset.order = val; break;
		}
	}
	_changeToggle( id, b ) {
		const html = this._fxsHtml.get( id );

		html.root.classList.toggle( "gsuiEffects-fx-enable", b );
		html.uiFx.toggle( b );
	}
	reorderEffects( effects ) {
		gsuiReorder.listReorder( this._elFxsList, effects );
	}

	// events:
	// .........................................................................
	_onchangeAddSelect() {
		const type = this._elAddSelect.value;

		this._elAddSelect.blur();
		this._elAddSelect.value = "";
		this._dispatch( "addEffect", type );
	}

	// .........................................................................
	_createOption( enable, fxId, fxName ) {
		const opt = document.createElement( "option" );

		opt.value = fxId;
		opt.disabled = !enable;
		opt.textContent = fxName;
		return opt;
	}
	_fillSelect() {
		const def = this._createOption( false, "", "-- Select an Fx" ),
			options = [ def ];

		gsuiEffects.fxsMap.forEach( ( fx, id ) => {
			options.push( this._createOption( true, id, fx.name ) );
		} );
		this._elAddSelect.append( ...options );
	}
}

gsuiEffects.fxsMap = new Map();

customElements.define( "gsui-effects", gsuiEffects );
<commit_msg>gsuiEffects: private fields<commit_after>"use strict";

class gsuiEffects extends HTMLElement {
	#fxsHtml = new Map()
	#dispatch = GSUI.dispatchEvent.bind( null, this, "gsuiEffects" )
	#elFxsList = null
	#elAddSelect = null

	constructor() {
		const elFxsList = GSUI.getTemplate( "gsui-effects" ),
			elBtnSelect = elFxsList.querySelector( ".gsuiEffects-addBtn" ),
			elAddSelect = elFxsList.querySelector( ".gsuiEffects-addSelect" );

		super();
		this.askData = GSUI.noop;
		this.#elFxsList = elFxsList;
		this.#elAddSelect = elAddSelect;
		Object.seal( this );

		elBtnSelect.onclick = () => this.#elAddSelect.value = "";
		elAddSelect.onchange = this.#onchangeAddSelect.bind( this );
		elAddSelect.onkeydown = () => false;
		new gsuiReorder( {
			rootElement: elFxsList,
			direction: "column",
			dataTransferType: "effect",
			itemSelector: ".gsuiEffects-fx",
			handleSelector: ".gsuiEffects-fx-grip",
			parentSelector: ".gsuiEffects-list",
		} );
		GSUI.listenEvents( this, {
			default: {
				liveChange: ( d, t ) => {
					d.args.unshift( t.dataset.id );
					d.component = "gsuiEffects";
					d.eventName = "liveChangeEffect";
					return true;
				},
				changeProp: ( d, t ) => {
					d.args.unshift( t.dataset.id );
					d.component = "gsuiEffects";
					d.eventName = "changeEffect";
					return true;
				},
			},
		} );
		this.#fillSelect();
	}

	// .........................................................................
	connectedCallback() {
		if ( !this.firstChild ) {
			this.classList.add( "gsuiEffects" );
			this.append( this.#elFxsList );
		}
	}

	// .........................................................................
	getFxHTML( id ) {
		return this.#fxsHtml.get( id );
	}
	expandToggleEffect( id ) {
		const root = this.#fxsHtml.get( id ).root;

		this.expandEffect( id, !root.classList.contains( "gsuiEffects-fx-expanded" ) );
	}
	expandEffect( id, b ) {
		const html = this.#fxsHtml.get( id ),
			type = html.root.dataset.type;

		html.root.classList.toggle( "gsuiEffects-fx-expanded", b );
		html.expand.dataset.icon = b ? "caret-down" : "caret-right";
		html.content.style.height = `${ b ? gsuiEffects.fxsMap.get( type ).height : 0 }px`;
	}

	// .........................................................................
	addEffect( id, fx ) {
		const root = GSUI.getTemplate( "gsui-effects-fx" ),
			name = root.querySelector( ".gsuiEffects-fx-name" ),
			expand = root.querySelector( ".gsuiEffects-fx-expand" ),
			toggle = root.querySelector( ".gsuiEffects-fx-toggle" ),
			remove = root.querySelector( ".gsuiEffects-fx-remove" ),
			content = root.querySelector( ".gsuiEffects-fx-content" ),
			fxAsset = gsuiEffects.fxsMap.get( fx.type ),
			uiFx = new fxAsset.cmp(),
			html = Object.seal( {
				uiFx,
				root,
				expand,
				content,
			} );

		expand.onclick = () => this.expandToggleEffect( id );
		toggle.onclick = () => this.#dispatch( "toggleEffect", id );
		remove.onclick = () => this.#dispatch( "removeEffect", id );
		uiFx.askData = this.askData.bind( null, id, fx.type );
		uiFx.dataset.id = id;
		root.dataset.type = fx.type;
		name.textContent = fxAsset.name;
		content.append( uiFx );
		this.#fxsHtml.set( id, html );
		this.#elFxsList.append( root );
	}
	removeEffect( id ) {
		this.#fxsHtml.get( id ).root.remove();
		this.#fxsHtml.delete( id );
	}
	changeEffect( id, prop, val ) {
		switch ( prop ) {
			case "toggle": this.#changeToggle( id, val ); break;
			case "order": this.#fxsHtml.get( id ).root.dataset.order = val; break;
		}
	}
	#changeToggle( id, b ) {
		const html = this.#fxsHtml.get( id );

		html.root.classList.toggle( "gsuiEffects-fx-enable", b );
		html.uiFx.toggle( b );
	}
	reorderEffects( effects ) {
		gsuiReorder.listReorder( this.#elFxsList, effects );
	}

	// .........................................................................
	#onchangeAddSelect() {
		const type = this.#elAddSelect.value;

		this.#elAddSelect.blur();
		this.#elAddSelect.value = "";
		this.#dispatch( "addEffect", type );
	}

	// .........................................................................
	#createOption( enable, fxId, fxName ) {
		const opt = document.createElement( "option" );

		opt.value = fxId;
		opt.disabled = !enable;
		opt.textContent = fxName;
		return opt;
	}
	#fillSelect() {
		const def = this.#createOption( false, "", "-- Select an Fx" ),
			options = [ def ];

		gsuiEffects.fxsMap.forEach( ( fx, id ) => {
			options.push( this.#createOption( true, id, fx.name ) );
		} );
		this.#elAddSelect.append( ...options );
	}
}

gsuiEffects.fxsMap = new Map();

customElements.define( "gsui-effects", gsuiEffects );
<|endoftext|>