<commit_before>'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactBootstrap = require('react-bootstrap');

var _reactS3UploaderS3upload = require('react-s3-uploader/s3upload');

var _reactS3UploaderS3upload2 = _interopRequireDefault(_reactS3UploaderS3upload);

var _reactDropzone = require('react-dropzone');

var _reactDropzone2 = _interopRequireDefault(_reactDropzone);

var DropzoneS3Uploader = (function (_React$Component) {
  _inherits(DropzoneS3Uploader, _React$Component);

  function DropzoneS3Uploader() {
    var _this = this;

    _classCallCheck(this, DropzoneS3Uploader);

    _get(Object.getPrototypeOf(DropzoneS3Uploader.prototype), 'constructor', this).apply(this, arguments);

    this.onProgress = function (progress) {
      var progFn = _this.props.onProgress;
      if (progFn) progFn(progress);
      _this.setState({ progress: progress });
    };

    this.onError = function (err) {
      var errFn = _this.props.onError;
      if (errFn) errFn(err);
      _this.setState({ error: err });
    };

    this.onFinish = function (info) {
      var finFn = _this.props.onFinish;
      if (finFn) finFn(info);
      _this.setState({ filename: info.filename, error: null, progress: null });
    };

    this.handleDrop = function (files) {
      var error = null;
      var size = files[0].size;
      var max_file_size = _this.props.max_file_size || _this.props.maxFileSize;

      if (!_this.props.multiple && files.length > 1) {
        error = 'Only drop one file';
      } else if (max_file_size && size > max_file_size) {
        var size_kb = (size / 1024 / 1024).toFixed(2);
        var max_kb = (max_file_size / 1024 / 1024).toFixed(2);
        error = 'Files nust be smaller than ' + max_kb + 'kb. Yours is ' + size_kb + 'kb';
      }
      _this.setState({ error: error });
      if (error) return;

      new _reactS3UploaderS3upload2['default']({ // eslint-disable-line
        files: files,
        signingUrl: _this.props.signing_url || _this.props.signingUrl || '/s3/sign',
        onProgress: _this.onProgress,
        onFinishS3Put: _this.onFinish,
        onError: _this.onError,
        uploadRequestHeaders: _this.props.headers || { 'x-amz-acl': 'public-read' },
        contentDisposition: 'auto',
        server: _this.props.server || _this.props.host || ''
      });
    };
  }

  _createClass(DropzoneS3Uploader, [{
    key: 'render',
    value: function render() {
      var state = this.state || { filename: this.props.filename };
      var filename = state.filename;
      var progress = state.progress;
      var error = state.error;

      var s3_url = this.props.s3_url || this.props.s3Url;
      var file_url = filename ? s3_url + '/' + filename : null;

      var dropzone_props = {
        style: this.props.style || {
          height: 200,
          border: 'dashed 2px #999',
          borderRadius: 5,
          position: 'relative',
          cursor: 'pointer'
        },
        activeStyle: this.props.active_style || this.props.activeStyle || {
          borderStyle: 'solid',
          backgroundColor: '#eee'
        },
        rejectStyle: this.props.reject_style || this.props.rejectStyle || {
          borderStyle: 'solid',
          backgroundColor: '#ffdddd'
        },
        multiple: this.props.multiple || false,
        accept: this.props.accept
      };

      var image_style = this.props.image_style || this.props.imageStyle || {
        position: 'absolute',
        top: 0,
        width: 'auto',
        height: '100%'
      };

      var contents = null;
      if (this.props.children) {
        contents = _react2['default'].cloneElement(_react2['default'].Children.only(this.props.children), {
          file_url: file_url, s3_url: s3_url, filename: filename, progress: progress, error: error, image_style: image_style,
          fileUrl: file_url, s3Url: s3_url, imageStyle: image_style
        });
      } else if (file_url) {
        if (DropzoneS3Uploader.isImage(file_url)) {
          contents = _react2['default'].createElement('img', { src: file_url, style: image_style });
        } else {
          contents = _react2['default'].createElement(
            'div',
            null,
            _react2['default'].createElement('span', { className: 'glyphicon glyphicon-file', style: { fontSize: '50px' } }),
            filename
          );
        }
      }

      return _react2['default'].createElement(
        _reactDropzone2['default'],
        _extends({ onDrop: this.handleDrop }, dropzone_props),
        contents,
        progress ? _react2['default'].createElement(_reactBootstrap.ProgressBar, { now: progress, label: '%(percent)s%', srOnly: true }) : null,
        error ? _react2['default'].createElement(
          'small',
          null,
          error
        ) : null
      );
    }
  }], [{
    key: 'isImage',
    value: function isImage(filename) {
      return filename && filename.match(/\.(jpeg|jpg|gif|png)/);
    }
  }, {
    key: 'propTypes',
    value: {
      host: _react.PropTypes.string,
      server: _react.PropTypes.string,
      s3_url: _react.PropTypes.string,
      s3Url: _react.PropTypes.string,
      signing_url: _react.PropTypes.string,
      signingUrl: _react.PropTypes.string,

      children: _react.PropTypes.element,
      headers: _react.PropTypes.object,
      multiple: _react.PropTypes.bool,
      accept: _react.PropTypes.string,
      filename: _react.PropTypes.string,
      max_file_size: _react.PropTypes.number,
      maxFileSize: _react.PropTypes.number,

      style: _react.PropTypes.object,
      active_style: _react.PropTypes.object,
      activeStyle: _react.PropTypes.object,
      reject_style: _react.PropTypes.object,
      rejectStyle: _react.PropTypes.object,
      image_style: _react.PropTypes.object,
      imageStyle: _react.PropTypes.object,

      onError: _react.PropTypes.func,
      onProgress: _react.PropTypes.func,
      onFinish: _react.PropTypes.func
    },
    enumerable: true
  }]);

  return DropzoneS3Uploader;
})(_react2['default'].Component);

exports['default'] = DropzoneS3Uploader;
module.exports = exports['default'];<commit_msg>build<commit_after>'use strict';

Object.defineProperty(exports, '__esModule', {
  value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }

function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _reactBootstrap = require('react-bootstrap');

var _reactS3UploaderS3upload = require('react-s3-uploader/s3upload');

var _reactS3UploaderS3upload2 = _interopRequireDefault(_reactS3UploaderS3upload);

var _reactDropzone = require('react-dropzone');

var _reactDropzone2 = _interopRequireDefault(_reactDropzone);

var DropzoneS3Uploader = (function (_React$Component) {
  _inherits(DropzoneS3Uploader, _React$Component);

  function DropzoneS3Uploader() {
    var _this = this;

    _classCallCheck(this, DropzoneS3Uploader);

    _get(Object.getPrototypeOf(DropzoneS3Uploader.prototype), 'constructor', this).apply(this, arguments);

    this.onProgress = function (progress) {
      var progFn = _this.props.onProgress;
      if (progFn) progFn(progress);
      _this.setState({ progress: progress });
    };

    this.onError = function (err) {
      var errFn = _this.props.onError;
      if (errFn) errFn(err);
      _this.setState({ error: err });
    };

    this.onFinish = function (info) {
      var finFn = _this.props.onFinish;
      if (finFn) finFn(info);
      _this.setState({ filename: info.filename, error: null, progress: null });
    };

    this.handleDrop = function (files) {
      var error = null;
      var size = files[0].size;
      var max_file_size = _this.props.max_file_size || _this.props.maxFileSize;

      if (!_this.props.multiple && files.length > 1) {
        error = 'Only drop one file';
      } else if (max_file_size && size > max_file_size) {
        var size_kb = (size / 1024 / 1024).toFixed(2);
        var max_kb = (max_file_size / 1024 / 1024).toFixed(2);
        error = 'Files nust be smaller than ' + max_kb + 'kb. Yours is ' + size_kb + 'kb';
      }
      _this.setState({ error: error });
      if (error) return;

      new _reactS3UploaderS3upload2['default']({ // eslint-disable-line
        files: files,
        signingUrl: _this.props.signing_url || _this.props.signingUrl || '/s3/sign',
        signingUrlQueryParams: _this.props.signing_url_query_params || _this.props.signingUrlQueryParams || {},
        onProgress: _this.onProgress,
        onFinishS3Put: _this.onFinish,
        onError: _this.onError,
        uploadRequestHeaders: _this.props.headers || { 'x-amz-acl': 'public-read' },
        contentDisposition: 'auto',
        server: _this.props.server || _this.props.host || ''
      });
    };
  }

  _createClass(DropzoneS3Uploader, [{
    key: 'render',
    value: function render() {
      var state = this.state || { filename: this.props.filename };
      var filename = state.filename;
      var progress = state.progress;
      var error = state.error;

      var s3_url = this.props.s3_url || this.props.s3Url;
      var file_url = filename ? s3_url + '/' + filename : null;

      var dropzone_props = {
        style: this.props.style || {
          height: 200,
          border: 'dashed 2px #999',
          borderRadius: 5,
          position: 'relative',
          cursor: 'pointer'
        },
        activeStyle: this.props.active_style || this.props.activeStyle || {
          borderStyle: 'solid',
          backgroundColor: '#eee'
        },
        rejectStyle: this.props.reject_style || this.props.rejectStyle || {
          borderStyle: 'solid',
          backgroundColor: '#ffdddd'
        },
        multiple: this.props.multiple || false,
        accept: this.props.accept
      };

      var image_style = this.props.image_style || this.props.imageStyle || {
        position: 'absolute',
        top: 0,
        width: 'auto',
        height: '100%'
      };

      var contents = null;
      if (this.props.children) {
        contents = _react2['default'].cloneElement(_react2['default'].Children.only(this.props.children), {
          file_url: file_url, s3_url: s3_url, filename: filename, progress: progress, error: error, image_style: image_style,
          fileUrl: file_url, s3Url: s3_url, imageStyle: image_style
        });
      } else if (file_url) {
        if (DropzoneS3Uploader.isImage(file_url)) {
          contents = _react2['default'].createElement('img', { src: file_url, style: image_style });
        } else {
          contents = _react2['default'].createElement(
            'div',
            null,
            _react2['default'].createElement('span', { className: 'glyphicon glyphicon-file', style: { fontSize: '50px' } }),
            filename
          );
        }
      }

      return _react2['default'].createElement(
        _reactDropzone2['default'],
        _extends({ onDrop: this.handleDrop }, dropzone_props),
        contents,
        progress ? _react2['default'].createElement(_reactBootstrap.ProgressBar, { now: progress, label: '%(percent)s%', srOnly: true }) : null,
        error ? _react2['default'].createElement(
          'small',
          null,
          error
        ) : null
      );
    }
  }], [{
    key: 'isImage',
    value: function isImage(filename) {
      return filename && filename.match(/\.(jpeg|jpg|gif|png)/);
    }
  }, {
    key: 'propTypes',
    value: {
      host: _react.PropTypes.string,
      server: _react.PropTypes.string,
      s3_url: _react.PropTypes.string,
      s3Url: _react.PropTypes.string,
      signing_url: _react.PropTypes.string,
      signingUrl: _react.PropTypes.string,
      signing_url_query_params: _react.PropTypes.object,
      signingUrlQueryParams: _react.PropTypes.object,

      children: _react.PropTypes.element,
      headers: _react.PropTypes.object,
      multiple: _react.PropTypes.bool,
      accept: _react.PropTypes.string,
      filename: _react.PropTypes.string,
      max_file_size: _react.PropTypes.number,
      maxFileSize: _react.PropTypes.number,

      style: _react.PropTypes.object,
      active_style: _react.PropTypes.object,
      activeStyle: _react.PropTypes.object,
      reject_style: _react.PropTypes.object,
      rejectStyle: _react.PropTypes.object,
      image_style: _react.PropTypes.object,
      imageStyle: _react.PropTypes.object,

      onError: _react.PropTypes.func,
      onProgress: _react.PropTypes.func,
      onFinish: _react.PropTypes.func
    },
    enumerable: true
  }]);

  return DropzoneS3Uploader;
})(_react2['default'].Component);

exports['default'] = DropzoneS3Uploader;
module.exports = exports['default'];<|endoftext|>


<commit_before>// convert function arguments object to array
// slice prevents optimization: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments

function toArray(args) {
  const max = args.length

  let array   = []
  let current = 0

  while(current < max) {
    array.push(args[current])
    current++
  }

  return array
}

// an optimized curry function
// calls the original function when all arguments have been recieved, collecting them until then

export default function hurry(original) {
  // cache number of arguments passed to original function
  const target = original.length

  return function collector() {
    // collect current arguments (future arguments are added to this)
    const args = toArray(arguments)

    // check number of arguments
    return args.length >= target
      ? original.apply(null, args)                                          // if we have them all, call original function
      : function() {                                                        // otherwise, add new arguments to current ones
          return collector.apply(null, args.concat(toArray(arguments)))
        }
  }
}
<commit_msg>fix linting errors<commit_after>// convert function arguments object to array
// slice prevents optimization: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments

const toArray = args => {
  const max = args.length

  let array = []
  let current = 0

  while (current < max) {
    array.push(args[current])
    current++
  }

  return array
}

// an optimized curry function
// calls the original function when all arguments have been recieved, collecting them until then

const hurry = original => {
  // cache number of arguments passed to original function
  const target = original.length

  return function collector () {
    // collect current arguments (future arguments are added to this)
    const args = toArray(arguments)

    // check number of arguments
    return args.length >= target
      ? original.apply(null, args)                                      // if we have them all, call original function
      : function () {                                                   // otherwise, add new arguments to current ones
        return collector.apply(null, args.concat(toArray(arguments)))
      }
  }
}

export default hurry
<|endoftext|>


<commit_before>/* global sails, Connector, DocumentKinds, DocumentOrigins, ExternalDocument, ExternalDocumentGroup, ScopusConnector, User, Group, Authorship, ExternalImporter, Citation */
// ScopusExternalImporter.js - in api/services

"use strict";

const _ = require('lodash');

const chunkSize = 100;
const interval = 12 * 60 * 60 * 1000;

module.exports = {
    updateUser: async (user) => {
        if (_.isEmpty(user.scopusId)) {
            sails.log.info('ScopusExternalImporter: user ' + user.username + ' empty scopusId');
            return;
        }
        const total = await updateResearchEntityProfile(ExternalDocument, user);
        sails.log.info('updated/inserted ' + total + ' scopus external documents of user ' + user.username);
    },
    updateGroup: async (group) => {
        if (_.isEmpty(group.scopusId)) {
            sails.log.info('ScopusExternalImporter: user ' + user.username + ' empty scopusId');
            return;
        }
        const total = await updateResearchEntityProfile(ExternalDocumentGroup, group);
        sails.log.info('updated/inserted ' + total + ' scopus external documents of group ' + group.name);
    },
    updateAll: async () => {
        let total;
        total = await updateResearchEntityProfiles(User, ExternalDocument);
        total += await updateResearchEntityProfiles(Group, ExternalDocumentGroup);

        const externalDocuments = await Document.find({
            kind: DocumentKinds.EXTERNAL,
            origin: DocumentOrigins.SCOPUS,
            updatedAt: {
                '<=': getUpdateLimitDate()
            }
        });
        const currentExternalIds = externalDocuments.map(ed => ed.scopusId);
        const importedDocuments = await importDocuments(currentExternalIds);

        total += importedDocuments.length;
        sails.log.info('updated/inserted ' + total + ' scopus external documents');
    },
    updateDocument: getAndCreateOrUpdateDocument
};

async function updateResearchEntityProfile(externalDocumentModel, researchEntity) {
    let documentScopusIds;
    try {
        documentScopusIds = await getResearchEntityDocumentsScopusIds(researchEntity);
    } catch (e) {
        return 0;
    }
    const importedDocuments = await importDocuments(documentScopusIds);
    await updateExternalDocuments(externalDocumentModel, researchEntity.id, importedDocuments);

    return importedDocuments.length;
}

async function updateResearchEntityProfiles(researchEntityModel, externalDocumentModel) {
    const researchEntities = await researchEntityModel.find({scopusId: {'!': ''}});

    let total = 0;

    for (let re of researchEntities)
        total += await updateResearchEntityProfile(externalDocumentModel, re);

    return total;
}

async function getResearchEntityDocumentsScopusIds(researchEntity) {
    const params = {
        limit: 200,
        skip: 0
    };

    if (researchEntity.getType() === 'user') {
        params.type = 'author';
        const res = await scopusLoop(researchEntity.scopusId, params);
        return res.scopusIds;
    }

    params.type = 'affiliation';
    let scopusIds = [];

    if (researchEntity.institute) {
        const childInstitutes = await Institute.find({parentId: researchEntity.institute});
        for (let childInstitute of childInstitutes) {
            if (!childInstitute.scopusId) continue;
            const res = await scopusLoop(childInstitute.scopusId, params);
            scopusIds = scopusIds.concat(res.scopusIds);
        }
    }

    const total = await getDocumentsTotal(researchEntity.scopusId, params);
    const startingYear = ((new Date()).getFullYear()) + 1;

    return scopusIds.concat(await scopusYearLoop(researchEntity.scopusId, params, startingYear, total));

    async function scopusYearLoop(scopusId, baseParams, year, total, totalDone = 0) {
        const params = _.cloneDeep(baseParams);
        params.additionalFields = {'year': year};

        try {
            const res = await scopusLoop(scopusId, params);
            const newTotalDone = totalDone + parseInt(res.done, 10);
            const documentsScopusIds = res.scopusIds;
            if (newTotalDone < total)
                return documentsScopusIds.concat(await scopusYearLoop(scopusId, baseParams, year - 1, total, newTotalDone));

            return documentsScopusIds;
        } catch (err) {
            sails.log.debug(err);
        }
    }

    async function scopusLoop(scopusId, params) {
        const extracted = await scopusRequest(scopusId, params);
        if (!extracted.documents)
            return {
                done: 0,
                scopusIds: []
            };

        const documentsScopusIds = extracted.documents.map(ed => getScopusId(ed));

        const nextParams = _.cloneDeep(params);
        nextParams.skip += params.limit;

        if (extracted.count <= nextParams.skip)
            return {
                done: extracted.count,
                scopusIds: documentsScopusIds
            };

        const res = await scopusLoop(scopusId, nextParams);
        return {
            done: res.done,
            scopusIds: documentsScopusIds.concat(res.scopusIds)
        }
    }

    async function scopusRequest(scopusId, params) {
        const config = await Connector.getConfig(DocumentOrigins.SCOPUS, scopusId, params);
        const res = await Connector.makeRequest(config);
        return config.fieldExtract(res.body);
    }

    async function getDocumentsTotal(scopusId, params) {
        const countParams = _.cloneDeep(params);
        countParams.limit = 1;
        const res = await scopusRequest(scopusId, countParams);
        return res.count;
    }
}

async function updateExternalDocuments(externalDocumentsModel, researchEntityId, documents) {
    await externalDocumentsModel.destroy({
        researchEntity: researchEntityId,
        origin: DocumentOrigins.SCOPUS
    });

    for (let d of documents)
        await externalDocumentsModel.create({
            researchEntity: researchEntityId,
            document: d.id,
            origin: DocumentOrigins.SCOPUS
        })

}

function getScopusId(d) {
    const identifier = d['dc:identifier'];
    if (_.startsWith(identifier, 'SCOPUS_ID:')) {
        return _.replace(identifier, 'SCOPUS_ID:', '');
    }
    const eid = d['eid'];
    if (_.startsWith(eid, '2-s2.0-')) {
        return _.trimStart(eid, '2-s2.0-');
    }
    return null;
}

async function importDocuments(documentScopusIds) {
    if (_.isEmpty(documentScopusIds))
        return [];

    let documents = [];
    const docsIterator = chunks(documentScopusIds, chunkSize);

    for (let scopusId of docsIterator)
        documents = documents.concat(await updateDocs(scopusId));

    return documents;

    async function updateDocs(documentScopusIds) {
        const docs = await Promise.all(
            documentScopusIds.map(
                async scopusId => {

                    let document = await Document.findOne({
                        kind: DocumentKinds.EXTERNAL,
                        origin: DocumentOrigins.SCOPUS,
                        scopusId: scopusId
                    });

                    if (document && document.updatedAt > getUpdateLimitDate())
                        return document;

                    try {
                        document = await getAndCreateOrUpdateDocument(scopusId);
                        if (_.has(document, 'id')) {
                            await updateCitations(document);
                            return document;
                        }

                    }
                    catch (err) {
                        sails.log.debug('Updater: Document failed ' + scopusId);
                    }

                    return {};
                }
            )
        );
        return docs.filter(d => !_.isEmpty(d));
    }
}

async function getAndCreateOrUpdateDocument(scopusId) {
    const documentData = await ScopusConnector.getDocument(scopusId);
    if (!_.isEmpty(documentData))
        return await ExternalImporter.createExternalDocument(DocumentOrigins.SCOPUS, documentData);
    return {};
}

async function updateCitations(document) {
    const date = document.year + '-' + (new Date()).getFullYear();
    const citations = await ScopusConnector.getDocumentCitations(document.scopusId, date);

    for (const cit of citations)
        await Citation.createOrUpdate({
            origin: DocumentOrigins.SCOPUS,
            originId: document.scopusId,
            year: cit.year
        }, {
            origin: DocumentOrigins.SCOPUS,
            originId: document.scopusId,
            year: cit.year,
            citations: cit.value
        });

}

function* chunks(array, chunkSize) {
    const len = array.length;
    for (let i = 0; i < len; i += chunkSize) {
        yield array.slice(i, i + chunkSize);
    }
}

function getUpdateLimitDate() {
    return new Date((new Date()).getTime() - interval);
}<commit_msg>Increased by one end year of citations search<commit_after>/* global sails, Connector, DocumentKinds, DocumentOrigins, ExternalDocument, ExternalDocumentGroup, ScopusConnector, User, Group, Authorship, ExternalImporter, Citation */
// ScopusExternalImporter.js - in api/services

"use strict";

const _ = require('lodash');

const chunkSize = 100;
const interval = 12 * 60 * 60 * 1000;

module.exports = {
    updateUser: async (user) => {
        if (_.isEmpty(user.scopusId)) {
            sails.log.info('ScopusExternalImporter: user ' + user.username + ' empty scopusId');
            return;
        }
        const total = await updateResearchEntityProfile(ExternalDocument, user);
        sails.log.info('updated/inserted ' + total + ' scopus external documents of user ' + user.username);
    },
    updateGroup: async (group) => {
        if (_.isEmpty(group.scopusId)) {
            sails.log.info('ScopusExternalImporter: user ' + user.username + ' empty scopusId');
            return;
        }
        const total = await updateResearchEntityProfile(ExternalDocumentGroup, group);
        sails.log.info('updated/inserted ' + total + ' scopus external documents of group ' + group.name);
    },
    updateAll: async () => {
        let total;
        total = await updateResearchEntityProfiles(User, ExternalDocument);
        total += await updateResearchEntityProfiles(Group, ExternalDocumentGroup);

        const externalDocuments = await Document.find({
            kind: DocumentKinds.EXTERNAL,
            origin: DocumentOrigins.SCOPUS,
            updatedAt: {
                '<=': getUpdateLimitDate()
            }
        });
        const currentExternalIds = externalDocuments.map(ed => ed.scopusId);
        const importedDocuments = await importDocuments(currentExternalIds);

        total += importedDocuments.length;
        sails.log.info('updated/inserted ' + total + ' scopus external documents');
    },
    updateDocument: getAndCreateOrUpdateDocument
};

async function updateResearchEntityProfile(externalDocumentModel, researchEntity) {
    let documentScopusIds;
    try {
        documentScopusIds = await getResearchEntityDocumentsScopusIds(researchEntity);
    } catch (e) {
        return 0;
    }
    const importedDocuments = await importDocuments(documentScopusIds);
    await updateExternalDocuments(externalDocumentModel, researchEntity.id, importedDocuments);

    return importedDocuments.length;
}

async function updateResearchEntityProfiles(researchEntityModel, externalDocumentModel) {
    const researchEntities = await researchEntityModel.find({scopusId: {'!': ''}});

    let total = 0;

    for (let re of researchEntities)
        total += await updateResearchEntityProfile(externalDocumentModel, re);

    return total;
}

async function getResearchEntityDocumentsScopusIds(researchEntity) {
    const params = {
        limit: 200,
        skip: 0
    };

    if (researchEntity.getType() === 'user') {
        params.type = 'author';
        const res = await scopusLoop(researchEntity.scopusId, params);
        return res.scopusIds;
    }

    params.type = 'affiliation';
    let scopusIds = [];

    if (researchEntity.institute) {
        const childInstitutes = await Institute.find({parentId: researchEntity.institute});
        for (let childInstitute of childInstitutes) {
            if (!childInstitute.scopusId) continue;
            const res = await scopusLoop(childInstitute.scopusId, params);
            scopusIds = scopusIds.concat(res.scopusIds);
        }
    }

    const total = await getDocumentsTotal(researchEntity.scopusId, params);
    const startingYear = ((new Date()).getFullYear()) + 1;

    return scopusIds.concat(await scopusYearLoop(researchEntity.scopusId, params, startingYear, total));

    async function scopusYearLoop(scopusId, baseParams, year, total, totalDone = 0) {
        const params = _.cloneDeep(baseParams);
        params.additionalFields = {'year': year};

        try {
            const res = await scopusLoop(scopusId, params);
            const newTotalDone = totalDone + parseInt(res.done, 10);
            const documentsScopusIds = res.scopusIds;
            if (newTotalDone < total)
                return documentsScopusIds.concat(await scopusYearLoop(scopusId, baseParams, year - 1, total, newTotalDone));

            return documentsScopusIds;
        } catch (err) {
            sails.log.debug(err);
        }
    }

    async function scopusLoop(scopusId, params) {
        const extracted = await scopusRequest(scopusId, params);
        if (!extracted.documents)
            return {
                done: 0,
                scopusIds: []
            };

        const documentsScopusIds = extracted.documents.map(ed => getScopusId(ed));

        const nextParams = _.cloneDeep(params);
        nextParams.skip += params.limit;

        if (extracted.count <= nextParams.skip)
            return {
                done: extracted.count,
                scopusIds: documentsScopusIds
            };

        const res = await scopusLoop(scopusId, nextParams);
        return {
            done: res.done,
            scopusIds: documentsScopusIds.concat(res.scopusIds)
        }
    }

    async function scopusRequest(scopusId, params) {
        const config = await Connector.getConfig(DocumentOrigins.SCOPUS, scopusId, params);
        const res = await Connector.makeRequest(config);
        return config.fieldExtract(res.body);
    }

    async function getDocumentsTotal(scopusId, params) {
        const countParams = _.cloneDeep(params);
        countParams.limit = 1;
        const res = await scopusRequest(scopusId, countParams);
        return res.count;
    }
}

async function updateExternalDocuments(externalDocumentsModel, researchEntityId, documents) {
    await externalDocumentsModel.destroy({
        researchEntity: researchEntityId,
        origin: DocumentOrigins.SCOPUS
    });

    for (let d of documents)
        await externalDocumentsModel.create({
            researchEntity: researchEntityId,
            document: d.id,
            origin: DocumentOrigins.SCOPUS
        })

}

function getScopusId(d) {
    const identifier = d['dc:identifier'];
    if (_.startsWith(identifier, 'SCOPUS_ID:')) {
        return _.replace(identifier, 'SCOPUS_ID:', '');
    }
    const eid = d['eid'];
    if (_.startsWith(eid, '2-s2.0-')) {
        return _.trimStart(eid, '2-s2.0-');
    }
    return null;
}

async function importDocuments(documentScopusIds) {
    if (_.isEmpty(documentScopusIds))
        return [];

    let documents = [];
    const docsIterator = chunks(documentScopusIds, chunkSize);

    for (let scopusId of docsIterator)
        documents = documents.concat(await updateDocs(scopusId));

    return documents;

    async function updateDocs(documentScopusIds) {
        const docs = await Promise.all(
            documentScopusIds.map(
                async scopusId => {

                    let document = await Document.findOne({
                        kind: DocumentKinds.EXTERNAL,
                        origin: DocumentOrigins.SCOPUS,
                        scopusId: scopusId
                    });

                    if (document && document.updatedAt > getUpdateLimitDate())
                        return document;

                    try {
                        document = await getAndCreateOrUpdateDocument(scopusId);
                        if (_.has(document, 'id')) {
                            await updateCitations(document);
                            return document;
                        }

                    }
                    catch (err) {
                        sails.log.debug('Updater: Document failed ' + scopusId);
                    }

                    return {};
                }
            )
        );
        return docs.filter(d => !_.isEmpty(d));
    }
}

async function getAndCreateOrUpdateDocument(scopusId) {
    const documentData = await ScopusConnector.getDocument(scopusId);
    if (!_.isEmpty(documentData))
        return await ExternalImporter.createExternalDocument(DocumentOrigins.SCOPUS, documentData);
    return {};
}

async function updateCitations(document) {
    const endDate = ((new Date()).getFullYear() + 1);
    const date = document.year + '-' + endDate;
    const citations = await ScopusConnector.getDocumentCitations(document.scopusId, date);

    for (const cit of citations)
        await Citation.createOrUpdate({
            origin: DocumentOrigins.SCOPUS,
            originId: document.scopusId,
            year: cit.year
        }, {
            origin: DocumentOrigins.SCOPUS,
            originId: document.scopusId,
            year: cit.year,
            citations: cit.value
        });

}

function* chunks(array, chunkSize) {
    const len = array.length;
    for (let i = 0; i < len; i += chunkSize) {
        yield array.slice(i, i + chunkSize);
    }
}

function getUpdateLimitDate() {
    return new Date((new Date()).getTime() - interval);
}<|endoftext|>


<commit_before>'use strict';

const WorldstateObject = require('./WorldstateObject.js');

const repBase = 1000;

/**
 * Represents an alert
 * @extends {WorldstateObject}
 */
class NightwaveChallenge extends WorldstateObject {
  /**
   * @param   {Object}             data            The alert data
   * @param   {Object}             deps            The dependencies object
   * @param   {Translator}         deps.translator The string translator
   * @param   {TimeDateFunctions}  deps.timeDate   The time and date functions
   * @param   {string}             deps.locale     Locale to use for translations
   */
  constructor(data, {
    translator, timeDate, locale,
  }) {
    super(data, { timeDate });

    /**
     * Whether or not this is a daily challenge
     * @type {Boolean}
     */
    this.isDaily = data.Daily;

    /**
     * Whether or not the challenge is an elite challenge
     * @type {Boolean}
     */
    this.isElite = /hard/ig.test(data.Challenge);

    /**
     * The descriptor for this challenge
     * @type {string}
     */
    this.desc = translator.languageDesc(data.Challenge, locale);

    /**
     * The title for this challenge
     * @type {string}
     */
    this.title = translator.languageString(data.Challenge, locale);

    /**
     * Generated id from expiry, challenge string,
     *  and whether or not it has `[PH]` (designating placeholder text)
     * @type {string}
     */
    this.id = `${this.expiry.getTime()}${data.Challenge.split('/').slice(-1)[0].toLowerCase()}${this.desc.indexOf('[PH]') > -1 ? 0 : 1}`;

    /**
     * Reputation reward for ranking up in the Nightwave
     * @type {Number}
     */
    this.reputation = repBase + (!this.isDaily ? 5000 : 0) + (this.isElite ? 3000 : 0);
  }
}

module.exports = NightwaveChallenge;
<commit_msg>reputation edit (#168)<commit_after>'use strict';

const WorldstateObject = require('./WorldstateObject.js');

const repBase = 1000;

/**
 * Represents an alert
 * @extends {WorldstateObject}
 */
class NightwaveChallenge extends WorldstateObject {
  /**
   * @param   {Object}             data            The alert data
   * @param   {Object}             deps            The dependencies object
   * @param   {Translator}         deps.translator The string translator
   * @param   {TimeDateFunctions}  deps.timeDate   The time and date functions
   * @param   {string}             deps.locale     Locale to use for translations
   */
  constructor(data, {
    translator, timeDate, locale,
  }) {
    super(data, { timeDate });

    /**
     * Whether or not this is a daily challenge
     * @type {Boolean}
     */
    this.isDaily = data.Daily;

    /**
     * Whether or not the challenge is an elite challenge
     * @type {Boolean}
     */
    this.isElite = /hard/ig.test(data.Challenge);

    /**
     * The descriptor for this challenge
     * @type {string}
     */
    this.desc = translator.languageDesc(data.Challenge, locale);

    /**
     * The title for this challenge
     * @type {string}
     */
    this.title = translator.languageString(data.Challenge, locale);

    /**
     * Generated id from expiry, challenge string,
     *  and whether or not it has `[PH]` (designating placeholder text)
     * @type {string}
     */
    this.id = `${this.expiry.getTime()}${data.Challenge.split('/').slice(-1)[0].toLowerCase()}${this.desc.indexOf('[PH]') > -1 ? 0 : 1}`;

    /**
     * Reputation reward for ranking up in the Nightwave
     * @type {Number}
     */
    this.reputation = repBase + (!this.isDaily ? 3500 : 0) + (this.isElite ? 2500 : 0);
  }
}

module.exports = NightwaveChallenge;
<|endoftext|>


<commit_before>Ext2.namespace('Kwc.Mail.Editable');
Kwc.Mail.Editable.Panel = Ext2.extend(Ext2.Panel, {
    layout: 'border',
    initComponent: function() {
        var settings = new Ext2.Panel({
            layout: 'card',
            title: trlKwf('Settings')
        });
        var componentsGrid = new Kwf.Auto.GridPanel({
            controllerUrl: this.componentsControllerUrl,
            region: 'west',
            width: 300
        });

        var content = new Kwf.Component.ComponentPanel({
            title: trlKwf('Content'),
            componentConfigs: this.componentConfigs,
            autoLoad: false,
            mainComponentText: trlKwf('Content'),
            mainComponentIcon: '/assets/silkicons/email_open.png'
        });

        componentsGrid.on('selectionchange', function() {
            var record = componentsGrid.getSelected();
            if (!record) {
                settings.disable();
                return;
            }
            settings.enable();
            var i = false;
            settings.items.each(function(itm) {
                if (itm.controllerUrl == record.get('settings_controller_url')) {
                    i = itm;
                }
            }, this);
            if (!i) {
                i = new Kwf.Auto.FormPanel({
                    controllerUrl: record.get('settings_controller_url'),
                    autoLoad: false
                });
                settings.add(i);
                settings.doLayout();
            }
            settings.getLayout().setActiveItem(i);
            i.setBaseParams({
                componentId: record.id
            });
            i.load();

            content.setBaseParams({
                componentId: record.id+'-content'
            });
            content.load({
                editComponents: record.get('edit_components'),
                componentClass: record.get('edit_components')[0].componentClass,
                type: record.get('edit_components')[0].type
            });
        }, this);

        this.items = [{
            xtype: 'tabpanel',
            region: 'center',
            activeTab: 0,
            deferredRender: false,
            items: [
                settings,
                content
            ]
        }, componentsGrid];
        Kwc.Mail.Editable.Panel.superclass.initComponent.call(this);
    }
});
Ext2.reg('kwc.mail.editable', Kwc.Mail.Editable.Panel);
<commit_msg>refacte mail editable panel to be more flexible and overridable<commit_after>Ext2.namespace('Kwc.Mail.Editable');
Kwc.Mail.Editable.Panel = Ext2.extend(Ext2.Panel, {
    layout: 'border',
    initComponent: function() {
        this.settings = new Ext2.Panel({
            layout: 'card',
            title: trlKwf('Settings')
        });
        this.componentsGrid = new Kwf.Auto.GridPanel({
            controllerUrl: this.componentsControllerUrl,
            region: 'west',
            width: 300
        });

        this.content = new Kwf.Component.ComponentPanel({
            title: trlKwf('Content'),
            componentConfigs: this.componentConfigs,
            autoLoad: false,
            mainComponentText: trlKwf('Content'),
            mainComponentIcon: '/assets/silkicons/email_open.png'
        });

        this.componentsGrid.on('selectionchange', this.onComponentsGridSelectionChange, this);

        this.items = [{
            xtype: 'tabpanel',
            region: 'center',
            activeTab: 0,
            deferredRender: false,
            items: [
                this.settings,
                this.content
            ]
        }, this.componentsGrid];
        Kwc.Mail.Editable.Panel.superclass.initComponent.call(this);
    },

    onComponentsGridSelectionChange: function() {
        var record = this.componentsGrid.getSelected();
        if (!record) {
            this.settings.disable();
            return;
        }
        this.settings.enable();
        var i = false;
        this.settings.items.each(function(itm) {
            if (itm.controllerUrl == record.get('settings_controller_url')) {
                i = itm;
            }
        }, this);
        if (!i) {
            i = new Kwf.Auto.FormPanel({
                controllerUrl: record.get('settings_controller_url'),
                autoLoad: false
            });
            this.settings.add(i);
            this.settings.doLayout();
        }
        this.settings.getLayout().setActiveItem(i);
        i.setBaseParams({
            componentId: record.id
        });
        i.load();

        this.content.setBaseParams({
            componentId: record.id+'-content'
        });
        this.content.load({
            editComponents: record.get('edit_components'),
            componentClass: record.get('edit_components')[0].componentClass,
            type: record.get('edit_components')[0].type
        });
    }
});
Ext2.reg('kwc.mail.editable', Kwc.Mail.Editable.Panel);
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';

import Block from './spares/block/Block';
import Button from '../lib/button/Button';
import Header from './spares/header/Header';
import List from './spares/list/List';
import ListWithImage from './spares/list_with_image/ListWithImage';
import Paragraph from './spares/paragraph/Paragraph';
import ButtonsTwins from './spares/buttons_twins/ButtonsTwins';
import InputField from './spares/input_field/InputField';
import TextField from './spares/text_field/TextField';
import SelectField from './spares/select_field/SelectField';
import Radio from './spares/radio/Radio';
import Checkbox from './spares/checkbox/Checkbox';
import Dropdown from './spares/dropdown/Dropdown';
import Card from './spares/card/Card';
import Modal from './spares/modal/Modal';
import Nav from './spares/nav/Nav';
import Table from './spares/table/Table';

const listItems = [
  {
    id: 1,
    name: 'Item1',
    description: 'Lorem ipsum1',
    address: 'address1'
  },
  {
    id: 2,
    name: 'Item2',
    description: 'Lorem ipsum2',
    address: 'address2'
  }
];

const listWithImageItems = [
  {
    name: 'Item1',
    pageLink: '/pagelink',
    image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSY-oAsmlxhKCDMkkz74yzGnBGl41cF1LVQmVVPseCRbmzu3kC2',
    description: 'Lorem ipsum1',
    label1: 'text1',
    label2: 'text2'
  },
  {
    name: 'Item2',
    pageLink: '/pagelink',
    image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSY-oAsmlxhKCDMkkz74yzGnBGl41cF1LVQmVVPseCRbmzu3kC2',
    description: 'Lorem ipsum2',
    label1: 'text3',
    label2: 'text4'
  }
];

const testFunc = function (name) {
  console.log(name);
};

const tableItems = [
  {
    'client_unique':null,
    'client_clicks': 1000,
    'client_visit':2562,
    'order_count':73,
    'wait_count':42,
    'cancel_count':29,
    'accepted_count':2,
    'order_debug_count':0,
    'accepted_seller_commission':1400.00,
    'accepted_partner_profit':1200.00,
    'accepted_system_profit':200.00,
    'wait_seller_commission':33820.00,
    'wait_partner_profit':29600.00,
    'wait_system_profit':4220.00,
    'cancel_seller_commission':23080.00,
    'cancel_partner_profit':20200.00,
    'cancel_system_profit':2880.00,
    'max_date_group':1481625525,
    'group_name':'13-12-2016',
    'conversion':0.3724,
    'partner_profit_from_unique_client':0.061218,
    'approve':2.7397,
    'wait':57.5342
  },
  {
    'client_unique':0,
    'client_clicks': 3000,
    'client_visit':2562,
    'order_count':73,
    'wait_count':42,
    'cancel_count':29,
    'accepted_count':2,
    'order_debug_count':0,
    'accepted_seller_commission':1400.00,
    'accepted_partner_profit':1200.00,
    'accepted_system_profit':200.00,
    'wait_seller_commission':33820.00,
    'wait_partner_profit':29600.00,
    'wait_system_profit':4220.00,
    'cancel_seller_commission':23080.00,
    'cancel_partner_profit':20200.00,
    'cancel_system_profit':2880.00,
    'max_date_group':1481625525,
    'group_name':'13-12-2016',
    'conversion':0.3724,
    'partner_profit_from_unique_client':0.061218,
    'approve':2.7397,
    'wait':57.5342
  },
  {
    'client_unique': 23424,
    'client_clicks': 2000,
    'client_visit':25,
    'order_count':7,
    'wait_count':4,
    'cancel_count':2,
    'accepted_count':20,
    'order_debug_count':0,
    'accepted_seller_commission':140.00,
    'accepted_partner_profit':120.00,
    'accepted_system_profit':20.00,
    'wait_seller_commission':3320.00,
    'wait_partner_profit':2900.00,
    'wait_system_profit':420.00,
    'cancel_seller_commission':2380.00,
    'cancel_partner_profit':2000.00,
    'cancel_system_profit':2880.00,
    'max_date_group':1481625525,
    'group_name':'13-12-2016',
    'conversion':0.3724,
    'partner_profit_from_unique_client':0.061218,
    'approve':2.7397,
    'wait':57.5342
  }
]

ReactDOM.render(

    <Block>
      Spares uikit
    </Block>

    <Block>I'm a block</Block>
    <Block flex>

I'm a

flex

block

    </Block>

    <Block>
      Buttons
    </Block>
    <Block>
      <Button onclick={testFunc.bind(null, 'click')}>Click me</Button>
      <Button submit>Submit button</Button>
      <Button color="primary">Click me</Button>
      <Button color="danger">Click me</Button>
    </Block>

    <Block>
      Buttons with icons
    </Block>
    <Block>
      <Button>Button</Button>
      <Button icon="edit">Edit</Button>
      <Button icon="refresh">Refresh</Button>
      <Button icon="menu">Menu</Button>
      <Button icon="dropdown" color="primary">Dropdown</Button>
      <Button icon="remove" color="danger">Remove</Button>
    </Block>

    <Block>
      Disabled buttons
    </Block>
    <Block>
      <Button disabled>Click me</Button>
      <Button color="primary" disabled>Click me</Button>
      <Button color="danger" disabled>Click me</Button>
    </Block>

    <Block>
      Buttons twins
    </Block>
    <Block>
      <ButtonsTwins items={[
        {
          text: 'Button1',
          onclick: testFunc.bind(null, 'name1')
        }, {
          text: 'Button2',
          onclick: testFunc.bind(null, 'name2')
        }
      ]}/>
    </Block>

    <Block>
      Headers
    </Block>
    <Block>
      h1 header
      h1 header, text align center
      h2 header
      h3 header
    </Block>

    <Block>
      Paragraph
    </Block>
    <Block>
        <Paragraph>
          Lorem ipsum dolor sit amet,
          consectetur adipiscing elit,
          sed do eiusmod tempor incididunt ut
          labore et dolore magna aliqua
        </Paragraph>
    </Block>

    <Block>
      Lists
    </Block>
    <Block>
      List 1
      <List items={listItems} template={[
        [ {label: 'name', key: 'name'}, {label: 'name1', key: 'name'} ],
        [ {label: 'description', key: 'description'} ],
        [ {label: 'address', key: 'address'} ]
      ]}/>
    </Block>

    <Block>
      List with image
      <ListWithImage items={listWithImageItems} template={{
        header: 'name',
        pageLink: 'pagelink',
        image: 'image',
        description: 'description',
        labels: [
          {label: 'first line', key: 'label1'},
          {label: 'second line', key: 'label2'},
          {label: 'first line1', key: 'label1'},
          {label: 'second line2', key: 'label2'}
        ]
      }}/>
    </Block>

    <Block>
      Input field
    </Block>
    <Block>
        <InputField
              label="Input field"
              name="name"
              placeholder="input digits"
              pattern={/^\d+$/}
              message="Only digits" />
    </Block>

    <Block>
      Text field
    </Block>
    <Block>
        <TextField
            label="Enter text"
            name="name"
            placeholder="Enter 150 characters"
            char_count={150}
            message="Enter 150 characters" />
    </Block>

    <Block>
      Select
    </Block>
    <Block>
        <SelectField
            label="Select"
            name="select_name"
            onchange={testFunc.bind(null, 'select1')}
            items={[
              {value: 'value1', text: 'value 1'},
              {value: 2, text: 2},
              {value: 'value3', text: 'value 3'}
            ]} />
    </Block>

    <Block>
      Radio buttons
    </Block>
    <Block>
        <Radio items={listItems}
              template={{
                name: 'test',
                keysInItems: {
                  value: 'name',
                  text: 'address'
                }
              }} />
    </Block>

    <Block>
      Checkbox
    </Block>
    <Block>
        <Checkbox items={listItems}
              template={{
                name: 'checkbox',
                keysInItems: {
                  value: 'name',
                  text: 'address'
                }
              }} />
    </Block>

    <Block>
      Dropdown
    </Block>
    <Block>
      <Dropdown items={[
        {text: 'item 1', onclick: testFunc.bind(null, 'item 1')},
        {text: 'item 2', onclick: testFunc.bind(null, 'item 2')},
        {text: 'item 3', onclick: testFunc.bind(null, 'item 3')}]}>
          <Button icon="dropdown">Click me</Button>
      </Dropdown>

      <Dropdown items={[
        {text: 'item 1', onclick: testFunc.bind(null, 'item 1')},
        {text: 'item 2', onclick: testFunc.bind(null, 'item 2')},
        {text: 'item 3', onclick: testFunc.bind(null, 'item 3')}]}>
          <Button icon="dropdown">Click me</Button>
      </Dropdown>
    </Block>

    <Block>
      Card
    </Block>
    <Block>
      <Card>card</Card>
    </Block>

    <Block>
      Modal
    </Block>
    <Block>
      <Modal header="Modal" buttonText="openModal">

Modal

      </Modal>
    </Block>

    <Block>
      Nav
    </Block>
    <Block>
      <Nav items={[
        {text: 'page 1', submenu: [
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'}
        ]},
        {text: 'page 2', submenu: [
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'}
        ]},
        {link: '/page', text: 'page 3'},
        {link: '/page', text: 'page 4'},
        {link: '/page', text: 'page 5'},
        {link: '/page', text: 'page 6'}
      ]}/>
    </Block>

    <Block>
      Table
    </Block>
    <Block>
      <Table head={[
              [
                [{text: ''}], [{text: 'Traffic'}], [{text: 'Indicators'}], [{text: 'Conversion'}], [{text: 'Finance'}]
              ],
              [
                [{text: 'Date'}],
                [
                  {text: 'Clicks', sort: 'client_clicks'},
                  {text: 'Unique clicks', sort: 'client_unique'},
                  {text: 'Conversion', sort: 'conversion'}
                ],
                [
                  {text: 'ECPC', sort: 'partner_profit_from_unique_client'},
                  {text: 'Approve', sort: 'approve'},
                  {text: 'Waiting', sort: 'wait_count'},
                  {text: 'Orders', sort: 'order_count'}
                ],
                [
                  {text: 'Waiting', sort: 'wait'},
                  {text: 'Accepted', sort: 'accepted_count'},
                  {text: 'Canceled', sort: 'cancel_count'},
                  {text: 'Trash', sort: 'order_debug_count'}
                ],
                [
                  {text: 'Enrolled', sort: 'accepted_count'},
                  {text: 'Pending', sort: 'cancel_count'},
                  {text: 'Rejected', sort: 'order_debug_count'}
                ]
              ]
            ]}
            body={{
              items: tableItems,
              template: [
                ['group_name'],
                ['client_clicks', 'client_unique', 'conversion'],
                ['partner_profit_from_unique_client', 'approve', 'wait_count', 'order_count'],
                ['wait', 'accepted_count', 'cancel_count', 'order_debug_count'],
                ['accepted_count', 'cancel_count', 'order_debug_count']
              ]
            }}
            total={[
              ['Total:'],
              ['client_clicks', 'client_unique', 'conversion'],
              ['partner_profit_from_unique_client', 'approve', 'wait_count', 'order_count'],
              ['wait', 'accepted_count', 'cancel_count', 'order_debug_count'],
              ['accepted_count', 'cancel_count', 'order_debug_count']
            ]} />
    </Block>

,
  document.getElementById('root')
);
<commit_msg>Update index.js<commit_after>import React from 'react';
import ReactDOM from 'react-dom';

import Block from './spares/block/Block';
import Button from './spares/button/Button';
import Header from './spares/header/Header';
import List from './spares/list/List';
import ListWithImage from './spares/list_with_image/ListWithImage';
import Paragraph from './spares/paragraph/Paragraph';
import ButtonsTwins from './spares/buttons_twins/ButtonsTwins';
import InputField from './spares/input_field/InputField';
import TextField from './spares/text_field/TextField';
import SelectField from './spares/select_field/SelectField';
import Radio from './spares/radio/Radio';
import Checkbox from './spares/checkbox/Checkbox';
import Dropdown from './spares/dropdown/Dropdown';
import Card from './spares/card/Card';
import Modal from './spares/modal/Modal';
import Nav from './spares/nav/Nav';
import Table from './spares/table/Table';

const listItems = [
  {
    id: 1,
    name: 'Item1',
    description: 'Lorem ipsum1',
    address: 'address1'
  },
  {
    id: 2,
    name: 'Item2',
    description: 'Lorem ipsum2',
    address: 'address2'
  }
];

const listWithImageItems = [
  {
    name: 'Item1',
    pageLink: '/pagelink',
    image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSY-oAsmlxhKCDMkkz74yzGnBGl41cF1LVQmVVPseCRbmzu3kC2',
    description: 'Lorem ipsum1',
    label1: 'text1',
    label2: 'text2'
  },
  {
    name: 'Item2',
    pageLink: '/pagelink',
    image: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSY-oAsmlxhKCDMkkz74yzGnBGl41cF1LVQmVVPseCRbmzu3kC2',
    description: 'Lorem ipsum2',
    label1: 'text3',
    label2: 'text4'
  }
];

const testFunc = function (name) {
  console.log(name);
};

const tableItems = [
  {
    'client_unique':null,
    'client_clicks': 1000,
    'client_visit':2562,
    'order_count':73,
    'wait_count':42,
    'cancel_count':29,
    'accepted_count':2,
    'order_debug_count':0,
    'accepted_seller_commission':1400.00,
    'accepted_partner_profit':1200.00,
    'accepted_system_profit':200.00,
    'wait_seller_commission':33820.00,
    'wait_partner_profit':29600.00,
    'wait_system_profit':4220.00,
    'cancel_seller_commission':23080.00,
    'cancel_partner_profit':20200.00,
    'cancel_system_profit':2880.00,
    'max_date_group':1481625525,
    'group_name':'13-12-2016',
    'conversion':0.3724,
    'partner_profit_from_unique_client':0.061218,
    'approve':2.7397,
    'wait':57.5342
  },
  {
    'client_unique':0,
    'client_clicks': 3000,
    'client_visit':2562,
    'order_count':73,
    'wait_count':42,
    'cancel_count':29,
    'accepted_count':2,
    'order_debug_count':0,
    'accepted_seller_commission':1400.00,
    'accepted_partner_profit':1200.00,
    'accepted_system_profit':200.00,
    'wait_seller_commission':33820.00,
    'wait_partner_profit':29600.00,
    'wait_system_profit':4220.00,
    'cancel_seller_commission':23080.00,
    'cancel_partner_profit':20200.00,
    'cancel_system_profit':2880.00,
    'max_date_group':1481625525,
    'group_name':'13-12-2016',
    'conversion':0.3724,
    'partner_profit_from_unique_client':0.061218,
    'approve':2.7397,
    'wait':57.5342
  },
  {
    'client_unique': 23424,
    'client_clicks': 2000,
    'client_visit':25,
    'order_count':7,
    'wait_count':4,
    'cancel_count':2,
    'accepted_count':20,
    'order_debug_count':0,
    'accepted_seller_commission':140.00,
    'accepted_partner_profit':120.00,
    'accepted_system_profit':20.00,
    'wait_seller_commission':3320.00,
    'wait_partner_profit':2900.00,
    'wait_system_profit':420.00,
    'cancel_seller_commission':2380.00,
    'cancel_partner_profit':2000.00,
    'cancel_system_profit':2880.00,
    'max_date_group':1481625525,
    'group_name':'13-12-2016',
    'conversion':0.3724,
    'partner_profit_from_unique_client':0.061218,
    'approve':2.7397,
    'wait':57.5342
  }
]

ReactDOM.render(

    <Block>
      Spares uikit
    </Block>

    <Block>I'm a block</Block>
    <Block flex>

I'm a

flex

block

    </Block>

    <Block>
      Buttons
    </Block>
    <Block>
      <Button onclick={testFunc.bind(null, 'click')}>Click me</Button>
      <Button submit>Submit button</Button>
      <Button color="primary">Click me</Button>
      <Button color="danger">Click me</Button>
    </Block>

    <Block>
      Buttons with icons
    </Block>
    <Block>
      <Button>Button</Button>
      <Button icon="edit">Edit</Button>
      <Button icon="refresh">Refresh</Button>
      <Button icon="menu">Menu</Button>
      <Button icon="dropdown" color="primary">Dropdown</Button>
      <Button icon="remove" color="danger">Remove</Button>
    </Block>

    <Block>
      Disabled buttons
    </Block>
    <Block>
      <Button disabled>Click me</Button>
      <Button color="primary" disabled>Click me</Button>
      <Button color="danger" disabled>Click me</Button>
    </Block>

    <Block>
      Buttons twins
    </Block>
    <Block>
      <ButtonsTwins items={[
        {
          text: 'Button1',
          onclick: testFunc.bind(null, 'name1')
        }, {
          text: 'Button2',
          onclick: testFunc.bind(null, 'name2')
        }
      ]}/>
    </Block>

    <Block>
      Headers
    </Block>
    <Block>
      h1 header
      h1 header, text align center
      h2 header
      h3 header
    </Block>

    <Block>
      Paragraph
    </Block>
    <Block>
        <Paragraph>
          Lorem ipsum dolor sit amet,
          consectetur adipiscing elit,
          sed do eiusmod tempor incididunt ut
          labore et dolore magna aliqua
        </Paragraph>
    </Block>

    <Block>
      Lists
    </Block>
    <Block>
      List 1
      <List items={listItems} template={[
        [ {label: 'name', key: 'name'}, {label: 'name1', key: 'name'} ],
        [ {label: 'description', key: 'description'} ],
        [ {label: 'address', key: 'address'} ]
      ]}/>
    </Block>

    <Block>
      List with image
      <ListWithImage items={listWithImageItems} template={{
        header: 'name',
        pageLink: 'pagelink',
        image: 'image',
        description: 'description',
        labels: [
          {label: 'first line', key: 'label1'},
          {label: 'second line', key: 'label2'},
          {label: 'first line1', key: 'label1'},
          {label: 'second line2', key: 'label2'}
        ]
      }}/>
    </Block>

    <Block>
      Input field
    </Block>
    <Block>
        <InputField
              label="Input field"
              name="name"
              placeholder="input digits"
              pattern={/^\d+$/}
              message="Only digits" />
    </Block>

    <Block>
      Text field
    </Block>
    <Block>
        <TextField
            label="Enter text"
            name="name"
            placeholder="Enter 150 characters"
            char_count={150}
            message="Enter 150 characters" />
    </Block>

    <Block>
      Select
    </Block>
    <Block>
        <SelectField
            label="Select"
            name="select_name"
            onchange={testFunc.bind(null, 'select1')}
            items={[
              {value: 'value1', text: 'value 1'},
              {value: 2, text: 2},
              {value: 'value3', text: 'value 3'}
            ]} />
    </Block>

    <Block>
      Radio buttons
    </Block>
    <Block>
        <Radio items={listItems}
              template={{
                name: 'test',
                keysInItems: {
                  value: 'name',
                  text: 'address'
                }
              }} />
    </Block>

    <Block>
      Checkbox
    </Block>
    <Block>
        <Checkbox items={listItems}
              template={{
                name: 'checkbox',
                keysInItems: {
                  value: 'name',
                  text: 'address'
                }
              }} />
    </Block>

    <Block>
      Dropdown
    </Block>
    <Block>
      <Dropdown items={[
        {text: 'item 1', onclick: testFunc.bind(null, 'item 1')},
        {text: 'item 2', onclick: testFunc.bind(null, 'item 2')},
        {text: 'item 3', onclick: testFunc.bind(null, 'item 3')}]}>
          <Button icon="dropdown">Click me</Button>
      </Dropdown>

      <Dropdown items={[
        {text: 'item 1', onclick: testFunc.bind(null, 'item 1')},
        {text: 'item 2', onclick: testFunc.bind(null, 'item 2')},
        {text: 'item 3', onclick: testFunc.bind(null, 'item 3')}]}>
          <Button icon="dropdown">Click me</Button>
      </Dropdown>
    </Block>

    <Block>
      Card
    </Block>
    <Block>
      <Card>card</Card>
    </Block>

    <Block>
      Modal
    </Block>
    <Block>
      <Modal header="Modal" buttonText="openModal">

Modal

      </Modal>
    </Block>

    <Block>
      Nav
    </Block>
    <Block>
      <Nav items={[
        {text: 'page 1', submenu: [
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'}
        ]},
        {text: 'page 2', submenu: [
          {link: '/subpage', text: 'subpage'},
          {link: '/subpage', text: 'subpage'}
        ]},
        {link: '/page', text: 'page 3'},
        {link: '/page', text: 'page 4'},
        {link: '/page', text: 'page 5'},
        {link: '/page', text: 'page 6'}
      ]}/>
    </Block>

    <Block>
      Table
    </Block>
    <Block>
      <Table head={[
              [
                [{text: ''}], [{text: 'Traffic'}], [{text: 'Indicators'}], [{text: 'Conversion'}], [{text: 'Finance'}]
              ],
              [
                [{text: 'Date'}],
                [
                  {text: 'Clicks', sort: 'client_clicks'},
                  {text: 'Unique clicks', sort: 'client_unique'},
                  {text: 'Conversion', sort: 'conversion'}
                ],
                [
                  {text: 'ECPC', sort: 'partner_profit_from_unique_client'},
                  {text: 'Approve', sort: 'approve'},
                  {text: 'Waiting', sort: 'wait_count'},
                  {text: 'Orders', sort: 'order_count'}
                ],
                [
                  {text: 'Waiting', sort: 'wait'},
                  {text: 'Accepted', sort: 'accepted_count'},
                  {text: 'Canceled', sort: 'cancel_count'},
                  {text: 'Trash', sort: 'order_debug_count'}
                ],
                [
                  {text: 'Enrolled', sort: 'accepted_count'},
                  {text: 'Pending', sort: 'cancel_count'},
                  {text: 'Rejected', sort: 'order_debug_count'}
                ]
              ]
            ]}
            body={{
              items: tableItems,
              template: [
                ['group_name'],
                ['client_clicks', 'client_unique', 'conversion'],
                ['partner_profit_from_unique_client', 'approve', 'wait_count', 'order_count'],
                ['wait', 'accepted_count', 'cancel_count', 'order_debug_count'],
                ['accepted_count', 'cancel_count', 'order_debug_count']
              ]
            }}
            total={[
              ['Total:'],
              ['client_clicks', 'client_unique', 'conversion'],
              ['partner_profit_from_unique_client', 'approve', 'wait_count', 'order_count'],
              ['wait', 'accepted_count', 'cancel_count', 'order_debug_count'],
              ['accepted_count', 'cancel_count', 'order_debug_count']
            ]} />
    </Block>

,
  document.getElementById('root')
);
<|endoftext|>


<commit_before>const EventEmitter = require("./EventEmitter.js")
const debug = require('debug')('dao')

let lastUid = 0

class Observation {
  constructor(connection, what, pushed) {
    this.what = what
    this.uid = ++lastUid
    this.connection = connection
    this.pushed = pushed
    this.observables = []
    this.receivedSignals = []
    this.disposed = false
  }
  addObservable(observable) {
    if(this.disposed) throw new Error("observation use after disposal")
    this.observables.push(observable)
    observable.observation = this
    if(this.observables.length == 1 && this.connection.connected) {
      this.connection.sendObserve(this)
    }
    //process.nextTick(() => { // next tick will replay events through all layer to the client - it's waste of resources
      for(let { signal, args} of this.receivedSignals) {
        if(typeof observable == 'function') observable(signal, ...args)
        else if(observable.notify) observable.notify(signal, ...args)
        else observable[signal](...args)
      }
    //})
  }
  observable(clazz) {
    if(this.disposed) throw new Error("observation use after disposal")
    let observable = this.observables.find(o => o.clazz == clazz)
    if(observable) return observable

    const what = this.what
    const connection = this.connection
    const dispose = () => {
      observable.observation.removeObservable(observable)
      oldDispose.call(observable)
    }
    const respawn = () => {
      observable.observation = connection.observation(what)
      observable.observation.addObservable(observable)
      oldRespawn.call(observable)
    }

    observable = new clazz(undefined, what, dispose)
    observable.clazz = clazz
    const oldDispose = observable.dispose
    const oldRespawn = observable.respawn
    observable.dispose = dispose
    observable.respawn = respawn

    observable.observation = this
    this.addObservable(observable)
    return observable
  }
  removeObservable(observable) {
    if(this.disposed) throw new Error("observation use after disposal")
    let id = this.observables.indexOf(observable)
    if(id == -1) throw new Error("could not remove not existing observable")
    this.observables.splice(id, 1)
    if(this.connection.connected && this.observables.length == 0) {
      this.connection.sendUnobserve(this)
    }
    if(this.observables.length == 0 && !this.pushed) {
      const whatId = JSON.stringify(this.what)
      this.connection.observations.delete(whatId)
      this.disposed = true
    }
  }
  handleDisconnect() {
    if(this.disposed) throw new Error("observation use after disposal")
    this.pushed = false
    if(this.observables.length == 0) {
      const whatId = JSON.stringify(this.what)
      this.connection.observations.delete(whatId)
      this.disposed = true
    }
  }
  handleConnect() {
    if(this.disposed) throw new Error("observation use after disposal")
    this.receivedSignals = []
    if(this.connection.settings.logLevel > 0) debug("refresh", this.what)
    if(this.observables.length > 0) {
      this.connection.sendObserve(this)
    }
  }
  handleNotifyMessage({ signal, args }) {
    if(this.disposed) return
    this.receivedSignals.push({ signal, args })
    for(let observable of this.observables) {
      process.nextTick(function(){
        if(typeof observable == 'function') observable(signal, ...args)
          else if(observable.notify) observable.notify(signal, ...args)
            else observable[signal](...args)
      })
    }
  }
}

class Connection extends EventEmitter {

  constructor(sessionId, settings) {
    super()
    if(!sessionId) throw new Error("SessionId undefined!")
    this.sessionId = sessionId
    this.settings = settings || {}

    this.connectedCounter = 0
    this.connected = false
    this.lastRequestId = 0
    this.requestsQueue = []
    this.waitingRequests = new Map()

    this.observations = new Map()
    this.messageHandlers = {}

    this.remoteObserveSent = new Map()
    this.remoteUnobserveSent = new Map()

    this.autoReconnect = true

    this.finished = false

    this.connectionMonitor = this.settings.connectionMonitorFactory ? this.settings.connectionMonitorFactory(this) : null
    if(this.settings.timeSynchronization) this.settings.timeSynchronization.setConnection(this)

    /// Backward compatibility TODO: remove in future
    this.on('disconnect', () => this.settings.onDisconnect && this.settings.onDisconnect() )
    this.on('connect', () => this.settings.onConnect && this.settings.onConnect() )
  }

  sendRequest(msg, settings) {
    settings = { ...this.settings, ...settings }
    return new Promise((resolve, reject) => {
      msg.requestId = (++this.lastRequestId)
      let handler = (err, resp) => {
        if (err) {
          this.waitingRequests.delete(msg.requestId)
          return reject(err)
        }
        if (resp.type == 'error') {
          reject(resp.error)
          return false
        }
        resolve(resp.response)
        return false
      }
      const request = { msg, handler, settings }
      if (!this.connected) {
        if(settings.disconnectDebug)
          console.error("SEND REQUEST", msg, "WHEN NOT CONNECTED WITH SETTINGS", settings)
        if (settings.queueRequestsWhenDisconnected) {
          let queuedConnectionId = this.connectedCounter
          let queueId = this.requestsQueue.length
          this.requestsQueue.push(request)
          if(settings.requestSendTimeout && settings.requestSendTimeout < Infinity) {
            setTimeout(() => {
              if(queuedConnectionId == this.connectedCounter) {
                this.requestsQueue[queueId] = null
                reject('disconnected')
              }
            }, settings.requestSendTimeout || 2300)
          }
        } else {
          return reject('disconnected')
        }
      }

      if (settings.requestTimeout && settings.requestTimeout < Infinity) {
        setTimeout(() => {
          let waiting = this.waitingRequests.get(msg.requestId)
          if (waiting) {
            waiting.handler('timeout')
            this.waitingRequests.delete(msg.requestId)
          }
          for(let i = 0; i < this.requestsQueue.length; i++) {
            let req = this.requestsQueue[i]
            if(!req) continue
            if(req.msg.requestId == msg.requestId) {
              const req = this.requestsQueue[i]
              this.requestsQueue[i] = null
              req.handler('timeout')
            }
          }
        }, settings.requestTimeout)
      }

      if(this.connected) {
        this.waitingRequests.set(msg.requestId, request)
        this.send(msg)
      }
    })
  }

  request(method, ...args) {
    const msg={
      type: 'request',
      method: method,
      args: args
    }
    return this.sendRequest(msg)
  }

  requestWithSettings(settings, method, ...args) {
    const msg={
      type: 'request',
      method: method,
      args: args
    }
    return this.sendRequest(msg, settings)
  }

  get(what) {
    const msg={
      type: 'get',
      what: what
    }
    return this.sendRequest(msg)
  }
  getMore(what, more) {
    const msg={
      type: 'getMore',
      what: what,
      more: more
    }
    return this.sendRequest(msg)
  }
  getAll(paths) {
    const msg={
      type: 'getMore',
      paths
    }
    return this.sendRequest(msg)
  }

  event(method, ...args) {
    this.send({
      type: 'event',
      method: method,
      args: args
    })
  }

  handleMessage(message) {
    if(message.type == "pong") {
      this.emit('pong', message)
    }
    if(message.type == "ping") {
      this.emit('ping', message)
      message.type = "pong"
      this.send(message)
    }
    if(message.type == "timeSync") {
      this.emit('timeSync', message)
    }
    if(message.type == "authenticationError") {
      this.finished = true
      this.closeConnection()
      this.emit('authenticationError', message.error)
    }
    if(message.responseId) {
      const request = this.waitingRequests.get(message.responseId)
      if(!request) return
      this.waitingRequests.delete(message.responseId)
      request.handler(null, message)
      return
    }
    if(message.type == "notify") {
      const whatId = JSON.stringify(message.what)
      const observation = this.observations.get(whatId)
      if(observation) observation.handleNotifyMessage(message)
    }
    if(message.type == "push") {
      const whatId = JSON.stringify(message.what)
      const observation = this.observations.get(whatId)
      if(observation) {
        observation.pushed = true
      } else {
        const observation = new Observation(this, message.what, true)
        this.observations.set(whatId, observation)
      }
    }
    if(message.type == "unpush") {
      const whatId = JSON.stringify(message.what)
      const observation = this.observations.get(whatId)
      if(!observation || !observation.pushed) throw Error("observation that is not pushed can not be unpushed")
      observation.pushed = false
      if(observation.observables.length == 0) {
        this.observations.delete(whatId)
        this.observations.disposed = true
      }
    }
    const handler = this.messageHandlers[message.type]
    if(handler) handler(message)
  }

  handleDisconnect() {
    if(this.settings.logLevel > 0) debug( "disconnected")
    this.connected = false
    const queuedConnectionId = this.connectedCounter
    this.emit('disconnect')
    for(const req of this.waitingRequests.values()) {
      if(req.settings.disconnectDebug)
        console.error("SENT REQUEST", req.msg, "BEFORE DISCONNECTED WITH SETTINGS", settings)
      if(req.settings.queueActiveRequestsOnDisconnect) {
        const queuedId = this.requestsQueue.length
        this.requestsQueue.push(req)
        if(req.settings.requestSendTimeout < Infinity) {
          setTimeout(() => {
            if(queuedConnectionId == this.connectedCounter && this.requestsQueue[queuedId]) {
              req.handler('disconnected')
              this.requestsQueue[queuedId] = null
            }
          }, req.settings.requestSendTimeout || 2300)
        }
      } else {
        req.handler('disconnected')
      }
    }
    for(let observation of this.observations.values()) {
      observation.handleDisconnect()
    }

    this.waitingRequests = new Map()
    if(this.finished) return
    if(this.autoReconnect) {
      setTimeout((function(){
        this.emit('reconnect')
        this.initialize()
      }).bind(this), this.settings.autoReconnectDelay || 200)
    }
  }

  observation(what) {
    const whatId = JSON.stringify(what)
    let observation = this.observations.get(whatId)
    if(observation) return observation
    observation = new Observation(this, what, false)
    this.observations.set(whatId, observation)
    return observation
  }

  observable(what, observableclazz) {
    return this.observation(what).observable(observableclazz)
  }

  handleConnect() {
    this.remoteObserveSent = new Map()
    this.remoteUnobserveSent = new Map()

    this.connectedCounter++
    if(this.settings.logLevel > 0) debug("connected")
    this.connected = true
    this.send({
      type: 'initializeSession',
      sessionId: this.sessionId
    })
    /// REFRESH OBSERVABLES!
    for(let observation of this.observations.values()) {
      observation.handleConnect()
    }
    for(let request of this.requestsQueue) {
      if(!request) continue
      this.waitingRequests.set(request.msg.requestId, request)
      this.send(request.msg)
    }
    this.requestsQueue = []
    this.emit('connect')
  }

  sendPing(data = {}) {
    this.send({
      ...data,
      type: "ping"
    })
  }

  sendObserve(observation) {
    if(this.settings.unobserveDebug) {
      const observationKey = JSON.stringify(observation.what)
      let remoteObserves = this.remoteObserveSent.get(observationKey)
      if(remoteObserves) {
        console.error("Observe duplication! existing observations",
            remoteObserves.map(({ what, uid, disposed, observablbes }) =>
                ({ what, uid, disposed, observables: observablbes.length })),
            'new observation',
            { what: observation.what, uid: observation.uid })
        console.trace("OBSERVE DUPLICATION")
        throw new Error("OBSERVE DUPLICATION")
      } else {
        remoteObserves = []
        this.remoteObserveSent.set(observationKey, remoteObserves)
      }
      remoteObserves.push(observation)
      this.remoteUnobserveSent.delete(observationKey)
    }

    this.send({
      type: "observe",
      what: observation.what,
      pushed: observation.pushed
    })
  }

  sendUnobserve(observation) {
    if(this.settings.unobserveDebug) {
      const observationKey = JSON.stringify(observation.what)
      let remoteUnobserves = this.remoteUnobserveSent.get(observationKey)
      if(remoteUnobserves) {
        console.error("unobserve duplication! removed observations",
            { what: remoteUnobserves.what, uid: remoteUnobserves.uid },
            'next observation',
            { what: observation.what, uid: observation.uid })
        console.trace("UNOBSERVE DUPLICATION")
        throw new Error("UNOBSERVE DUPLICATION")
      } else {
        remoteUnobserves = []
        this.remoteUnobserveSent.set(observationKey, remoteUnobserves)
      }
      remoteUnobserves.push(observation)
      this.remoteObserveSent.delete(observationKey)
    }

    this.send({
      type: "unobserve",
      what: observation.what,
      pushed: observation.pushed
    })
  }

  sendTimeSync(timestamp) {
    this.send({
      clientTimestamp: timestamp,
      type: "timeSync"
    })
  }

}

module.exports = Connection
<commit_msg>bug fix<commit_after>const EventEmitter = require("./EventEmitter.js")
const debug = require('debug')('dao')

let lastUid = 0

class Observation {
  constructor(connection, what, pushed) {
    this.what = what
    this.uid = ++lastUid
    this.connection = connection
    this.pushed = pushed
    this.observables = []
    this.receivedSignals = []
    this.disposed = false
  }
  addObservable(observable) {
    if(this.disposed) throw new Error("observation use after disposal")
    this.observables.push(observable)
    observable.observation = this
    if(this.observables.length == 1 && this.connection.connected) {
      this.connection.sendObserve(this)
    }
    //process.nextTick(() => { // next tick will replay events through all layer to the client - it's waste of resources
      for(let { signal, args} of this.receivedSignals) {
        if(typeof observable == 'function') observable(signal, ...args)
        else if(observable.notify) observable.notify(signal, ...args)
        else observable[signal](...args)
      }
    //})
  }
  observable(clazz) {
    if(this.disposed) throw new Error("observation use after disposal")
    let observable = this.observables.find(o => o.clazz == clazz)
    if(observable) return observable

    const what = this.what
    const connection = this.connection
    const dispose = () => {
      observable.observation.removeObservable(observable)
      oldDispose.call(observable)
    }
    const respawn = () => {
      observable.observation = connection.observation(what)
      observable.observation.addObservable(observable)
      oldRespawn.call(observable)
    }

    observable = new clazz(undefined, what, dispose)
    observable.clazz = clazz
    const oldDispose = observable.dispose
    const oldRespawn = observable.respawn
    observable.dispose = dispose
    observable.respawn = respawn

    observable.observation = this
    this.addObservable(observable)
    return observable
  }
  removeObservable(observable) {
    if(this.disposed) throw new Error("observation use after disposal")
    let id = this.observables.indexOf(observable)
    if(id == -1) throw new Error("could not remove not existing observable")
    this.observables.splice(id, 1)
    if(this.connection.connected && this.observables.length == 0) {
      this.connection.sendUnobserve(this)
    }
    if(this.observables.length == 0 && !this.pushed) {
      const whatId = JSON.stringify(this.what)
      this.connection.observations.delete(whatId)
      this.disposed = true
    }
  }
  handleDisconnect() {
    if(this.disposed) throw new Error("observation use after disposal")
    this.pushed = false
    if(this.observables.length == 0) {
      const whatId = JSON.stringify(this.what)
      this.connection.observations.delete(whatId)
      this.disposed = true
    }
  }
  handleConnect() {
    if(this.disposed) throw new Error("observation use after disposal")
    this.receivedSignals = []
    if(this.connection.settings.logLevel > 0) debug("refresh", this.what)
    if(this.observables.length > 0) {
      this.connection.sendObserve(this)
    }
  }
  handleNotifyMessage({ signal, args }) {
    if(this.disposed) return
    this.receivedSignals.push({ signal, args })
    for(let observable of this.observables) {
      process.nextTick(function(){
        if(typeof observable == 'function') observable(signal, ...args)
          else if(observable.notify) observable.notify(signal, ...args)
            else observable[signal](...args)
      })
    }
  }
}

class Connection extends EventEmitter {

  constructor(sessionId, settings) {
    super()
    if(!sessionId) throw new Error("SessionId undefined!")
    this.sessionId = sessionId
    this.settings = settings || {}

    this.connectedCounter = 0
    this.connected = false
    this.lastRequestId = 0
    this.requestsQueue = []
    this.waitingRequests = new Map()

    this.observations = new Map()
    this.messageHandlers = {}

    this.remoteObserveSent = new Map()
    this.remoteUnobserveSent = new Map()

    this.autoReconnect = true

    this.finished = false

    this.connectionMonitor = this.settings.connectionMonitorFactory ? this.settings.connectionMonitorFactory(this) : null
    if(this.settings.timeSynchronization) this.settings.timeSynchronization.setConnection(this)

    /// Backward compatibility TODO: remove in future
    this.on('disconnect', () => this.settings.onDisconnect && this.settings.onDisconnect() )
    this.on('connect', () => this.settings.onConnect && this.settings.onConnect() )
  }

  sendRequest(msg, settings) {
    settings = { ...this.settings, ...settings }
    return new Promise((resolve, reject) => {
      msg.requestId = (++this.lastRequestId)
      let handler = (err, resp) => {
        if (err) {
          this.waitingRequests.delete(msg.requestId)
          return reject(err)
        }
        if (resp.type == 'error') {
          reject(resp.error)
          return false
        }
        resolve(resp.response)
        return false
      }
      const request = { msg, handler, settings }
      if (!this.connected) {
        if(settings.disconnectDebug)
          console.error("SEND REQUEST", msg, "WHEN NOT CONNECTED WITH SETTINGS", req.settings)
        if (settings.queueRequestsWhenDisconnected) {
          let queuedConnectionId = this.connectedCounter
          let queueId = this.requestsQueue.length
          this.requestsQueue.push(request)
          if(settings.requestSendTimeout && settings.requestSendTimeout < Infinity) {
            setTimeout(() => {
              if(queuedConnectionId == this.connectedCounter) {
                this.requestsQueue[queueId] = null
                reject('disconnected')
              }
            }, settings.requestSendTimeout || 2300)
          }
        } else {
          return reject('disconnected')
        }
      }

      if (settings.requestTimeout && settings.requestTimeout < Infinity) {
        setTimeout(() => {
          let waiting = this.waitingRequests.get(msg.requestId)
          if (waiting) {
            waiting.handler('timeout')
            this.waitingRequests.delete(msg.requestId)
          }
          for(let i = 0; i < this.requestsQueue.length; i++) {
            let req = this.requestsQueue[i]
            if(!req) continue
            if(req.msg.requestId == msg.requestId) {
              const req = this.requestsQueue[i]
              this.requestsQueue[i] = null
              req.handler('timeout')
            }
          }
        }, settings.requestTimeout)
      }

      if(this.connected) {
        this.waitingRequests.set(msg.requestId, request)
        this.send(msg)
      }
    })
  }

  request(method, ...args) {
    const msg={
      type: 'request',
      method: method,
      args: args
    }
    return this.sendRequest(msg)
  }

  requestWithSettings(settings, method, ...args) {
    const msg={
      type: 'request',
      method: method,
      args: args
    }
    return this.sendRequest(msg, settings)
  }

  get(what) {
    const msg={
      type: 'get',
      what: what
    }
    return this.sendRequest(msg)
  }
  getMore(what, more) {
    const msg={
      type: 'getMore',
      what: what,
      more: more
    }
    return this.sendRequest(msg)
  }
  getAll(paths) {
    const msg={
      type: 'getMore',
      paths
    }
    return this.sendRequest(msg)
  }

  event(method, ...args) {
    this.send({
      type: 'event',
      method: method,
      args: args
    })
  }

  handleMessage(message) {
    if(message.type == "pong") {
      this.emit('pong', message)
    }
    if(message.type == "ping") {
      this.emit('ping', message)
      message.type = "pong"
      this.send(message)
    }
    if(message.type == "timeSync") {
      this.emit('timeSync', message)
    }
    if(message.type == "authenticationError") {
      this.finished = true
      this.closeConnection()
      this.emit('authenticationError', message.error)
    }
    if(message.responseId) {
      const request = this.waitingRequests.get(message.responseId)
      if(!request) return
      this.waitingRequests.delete(message.responseId)
      request.handler(null, message)
      return
    }
    if(message.type == "notify") {
      const whatId = JSON.stringify(message.what)
      const observation = this.observations.get(whatId)
      if(observation) observation.handleNotifyMessage(message)
    }
    if(message.type == "push") {
      const whatId = JSON.stringify(message.what)
      const observation = this.observations.get(whatId)
      if(observation) {
        observation.pushed = true
      } else {
        const observation = new Observation(this, message.what, true)
        this.observations.set(whatId, observation)
      }
    }
    if(message.type == "unpush") {
      const whatId = JSON.stringify(message.what)
      const observation = this.observations.get(whatId)
      if(!observation || !observation.pushed) throw Error("observation that is not pushed can not be unpushed")
      observation.pushed = false
      if(observation.observables.length == 0) {
        this.observations.delete(whatId)
        this.observations.disposed = true
      }
    }
    const handler = this.messageHandlers[message.type]
    if(handler) handler(message)
  }

  handleDisconnect() {
    if(this.settings.logLevel > 0) debug( "disconnected")
    this.connected = false
    const queuedConnectionId = this.connectedCounter
    this.emit('disconnect')
    for(const req of this.waitingRequests.values()) {
      if(req.settings.disconnectDebug)
        console.error("SENT REQUEST", req.msg, "BEFORE DISCONNECTED WITH SETTINGS", req.settings)
      if(req.settings.queueActiveRequestsOnDisconnect) {
        const queuedId = this.requestsQueue.length
        this.requestsQueue.push(req)
        if(req.settings.requestSendTimeout < Infinity) {
          setTimeout(() => {
            if(queuedConnectionId == this.connectedCounter && this.requestsQueue[queuedId]) {
              req.handler('disconnected')
              this.requestsQueue[queuedId] = null
            }
          }, req.settings.requestSendTimeout || 2300)
        }
      } else {
        req.handler('disconnected')
      }
    }
    for(let observation of this.observations.values()) {
      observation.handleDisconnect()
    }

    this.waitingRequests = new Map()
    if(this.finished) return
    if(this.autoReconnect) {
      setTimeout((function(){
        this.emit('reconnect')
        this.initialize()
      }).bind(this), this.settings.autoReconnectDelay || 200)
    }
  }

  observation(what) {
    const whatId = JSON.stringify(what)
    let observation = this.observations.get(whatId)
    if(observation) return observation
    observation = new Observation(this, what, false)
    this.observations.set(whatId, observation)
    return observation
  }

  observable(what, observableclazz) {
    return this.observation(what).observable(observableclazz)
  }

  handleConnect() {
    this.remoteObserveSent = new Map()
    this.remoteUnobserveSent = new Map()

    this.connectedCounter++
    if(this.settings.logLevel > 0) debug("connected")
    this.connected = true
    this.send({
      type: 'initializeSession',
      sessionId: this.sessionId
    })
    /// REFRESH OBSERVABLES!
    for(let observation of this.observations.values()) {
      observation.handleConnect()
    }
    for(let request of this.requestsQueue) {
      if(!request) continue
      this.waitingRequests.set(request.msg.requestId, request)
      this.send(request.msg)
    }
    this.requestsQueue = []
    this.emit('connect')
  }

  sendPing(data = {}) {
    this.send({
      ...data,
      type: "ping"
    })
  }

  sendObserve(observation) {
    if(this.settings.unobserveDebug) {
      const observationKey = JSON.stringify(observation.what)
      let remoteObserves = this.remoteObserveSent.get(observationKey)
      if(remoteObserves) {
        console.error("Observe duplication! existing observations",
            remoteObserves.map(({ what, uid, disposed, observablbes }) =>
                ({ what, uid, disposed, observables: observablbes.length })),
            'new observation',
            { what: observation.what, uid: observation.uid })
        console.trace("OBSERVE DUPLICATION")
        throw new Error("OBSERVE DUPLICATION")
      } else {
        remoteObserves = []
        this.remoteObserveSent.set(observationKey, remoteObserves)
      }
      remoteObserves.push(observation)
      this.remoteUnobserveSent.delete(observationKey)
    }

    this.send({
      type: "observe",
      what: observation.what,
      pushed: observation.pushed
    })
  }

  sendUnobserve(observation) {
    if(this.settings.unobserveDebug) {
      const observationKey = JSON.stringify(observation.what)
      let remoteUnobserves = this.remoteUnobserveSent.get(observationKey)
      if(remoteUnobserves) {
        console.error("unobserve duplication! removed observations",
            { what: remoteUnobserves.what, uid: remoteUnobserves.uid },
            'next observation',
            { what: observation.what, uid: observation.uid })
        console.trace("UNOBSERVE DUPLICATION")
        throw new Error("UNOBSERVE DUPLICATION")
      } else {
        remoteUnobserves = []
        this.remoteUnobserveSent.set(observationKey, remoteUnobserves)
      }
      remoteUnobserves.push(observation)
      this.remoteObserveSent.delete(observationKey)
    }

    this.send({
      type: "unobserve",
      what: observation.what,
      pushed: observation.pushed
    })
  }

  sendTimeSync(timestamp) {
    this.send({
      clientTimestamp: timestamp,
      type: "timeSync"
    })
  }

}

module.exports = Connection
<|endoftext|>