<commit_before>var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    Model = mongoose.Model,
    util = require('util');

/**
 * This code is taken from official mongoose repository
 * https://github.com/Automattic/mongoose/blob/master/lib/query.js#L1996-L2018
 */
/* istanbul ignore next */
function parseUpdateArguments (conditions, doc, options, callback) {
    if ('function' === typeof options) {
        // .update(conditions, doc, callback)
        callback = options;
        options = null;
    } else if ('function' === typeof doc) {
        // .update(doc, callback);
        callback = doc;
        doc = conditions;
        conditions = {};
        options = null;
    } else if ('function' === typeof conditions) {
        // .update(callback)
        callback = conditions;
        conditions = undefined;
        doc = undefined;
        options = undefined;
    } else if (typeof conditions === 'object' && !doc && !options && !callback) {
        // .update(doc)
        doc = conditions;
        conditions = undefined;
        options = undefined;
        callback = undefined;
    }

    var args = [];

    if (conditions) args.push(conditions);
    if (doc) args.push(doc);
    if (options) args.push(options);
    if (callback) args.push(callback);

    return args;
}

function parseIndexFields (options) {
    var indexFields = {
        deleted: false,
        deletedAt: false,
        deletedBy: false
    };

    if (!options.indexFields) {
        return indexFields;
    }

    if ((typeof options.indexFields === 'string' || options.indexFields instanceof String) && options.indexFields === 'all') {
        indexFields.deleted = indexFields.deletedAt = indexFields.deletedBy = true;
    }

    if (typeof(options.indexFields) === "boolean" && options.indexFields === true) {
        indexFields.deleted = indexFields.deletedAt = indexFields.deletedBy = true;
    }

    if (Array.isArray(options.indexFields)) {
        indexFields.deleted = options.indexFields.indexOf('deleted') > -1;
        indexFields.deletedAt = options.indexFields.indexOf('deletedAt') > -1;
        indexFields.deletedBy = options.indexFields.indexOf('deletedBy') > -1;
    }

    return indexFields;
}

module.exports = function (schema, options) {
    options = options || {};
    var indexFields = parseIndexFields(options)

    schema.add({
        deleted: {
            type: Boolean,
            default: false,
            index: indexFields.deleted
        }
    });

    if (options.deletedAt === true) {
        schema.add({ deletedAt: { type: Date, index: indexFields.deletedAt }});
    }

    if (options.deletedBy === true) {
        schema.add({ deletedBy: { type: Schema.Types.ObjectId, index: indexFields.deletedBy }});
    }

    schema.pre('save', function (next) {
        if (!this.deleted) {
            this.deleted = false;
        }
        next();
    });

    if (options.overrideMethods) {
        var overrideItems = options.overrideMethods;
        var overridableMethods = ['count', 'find', 'findOne', 'findOneAndUpdate', 'update'];
        var finalList = [];

        if ((typeof overrideItems === 'string' || overrideItems instanceof String) && overrideItems === 'all') {
            finalList = overridableMethods;
        }

        if (typeof(overrideItems) === "boolean" && overrideItems === true) {
            finalList = overridableMethods;
        }

        if (Array.isArray(overrideItems)) {
            overrideItems.forEach(function(method) {
                if (overridableMethods.indexOf(method) > -1) {
                    finalList.push(method);
                }
            });
        }

        finalList.forEach(function(method) {
            if (method === 'count' || method === 'find' || method === 'findOne') {
                schema.statics[method] = function () {
                    return Model[method].apply(this, arguments).where('deleted').ne(true);
                };
                schema.statics[method + 'Deleted'] = function () {
                    return Model[method].apply(this, arguments).where('deleted').ne(false);
                };
                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            } else {
                schema.statics[method] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': true};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'Deleted'] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': false};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            }
        });
    }

    schema.methods.delete = function (deletedBy, cb) {
        var callback = typeof deletedBy === 'function' ? deletedBy : cb,
            deletedBy = cb !== undefined ? deletedBy : null;

        this.deleted = true;

        if (schema.path('deletedAt')) {
            this.deletedAt = new Date();
        }

        if (schema.path('deletedBy')) {
            this.deletedBy = deletedBy;
        }

        if (options.validateBeforeDelete === false) {
            return this.save({ validateBeforeSave: false }, callback);
        }

        return this.save(callback);
    };

    schema.statics.delete =  function (conditions, deletedBy, callback) {
        if (typeof deletedBy === 'function') {
            callback = deletedBy;
            conditions = conditions;
            deletedBy = null;
        } else if (typeof conditions === 'function') {
            callback = conditions;
            conditions = {};
            deletedBy = null;
        }

        var doc = {
            deleted: true
        };

        if (schema.path('deletedAt')) {
            doc.deletedAt = new Date();
        }

        if (schema.path('deletedBy')) {
            doc.deletedBy = deletedBy;
        }

        if (this.updateWithDeleted) {
            return this.updateWithDeleted(conditions, doc, { multi: true }, callback);
        } else {
            return this.update(conditions, doc, { multi: true }, callback);
        }
    };

    schema.methods.restore = function (callback) {
        this.deleted = false;
        this.deletedAt = undefined;
        this.deletedBy = undefined;
        return this.save(callback);
    };

    schema.statics.restore =  function (conditions, callback) {
        if (typeof conditions === 'function') {
            callback = conditions;
            conditions = {};
        }

        var doc = {
            deleted: false,
            deletedAt: undefined,
            deletedBy: undefined
        };

        if (this.updateWithDeleted) {
            return this.updateWithDeleted(conditions, doc, { multi: true }, callback);
        } else {
            return this.update(conditions, doc, { multi: true }, callback);
        }
    };
};
<commit_msg>Support instance method delete promise<commit_after>var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    Model = mongoose.Model,
    util = require('util');

/**
 * This code is taken from official mongoose repository
 * https://github.com/Automattic/mongoose/blob/master/lib/query.js#L1996-L2018
 */
/* istanbul ignore next */
function parseUpdateArguments (conditions, doc, options, callback) {
    if ('function' === typeof options) {
        // .update(conditions, doc, callback)
        callback = options;
        options = null;
    } else if ('function' === typeof doc) {
        // .update(doc, callback);
        callback = doc;
        doc = conditions;
        conditions = {};
        options = null;
    } else if ('function' === typeof conditions) {
        // .update(callback)
        callback = conditions;
        conditions = undefined;
        doc = undefined;
        options = undefined;
    } else if (typeof conditions === 'object' && !doc && !options && !callback) {
        // .update(doc)
        doc = conditions;
        conditions = undefined;
        options = undefined;
        callback = undefined;
    }

    var args = [];

    if (conditions) args.push(conditions);
    if (doc) args.push(doc);
    if (options) args.push(options);
    if (callback) args.push(callback);

    return args;
}

function parseIndexFields (options) {
    var indexFields = {
        deleted: false,
        deletedAt: false,
        deletedBy: false
    };

    if (!options.indexFields) {
        return indexFields;
    }

    if ((typeof options.indexFields === 'string' || options.indexFields instanceof String) && options.indexFields === 'all') {
        indexFields.deleted = indexFields.deletedAt = indexFields.deletedBy = true;
    }

    if (typeof(options.indexFields) === "boolean" && options.indexFields === true) {
        indexFields.deleted = indexFields.deletedAt = indexFields.deletedBy = true;
    }

    if (Array.isArray(options.indexFields)) {
        indexFields.deleted = options.indexFields.indexOf('deleted') > -1;
        indexFields.deletedAt = options.indexFields.indexOf('deletedAt') > -1;
        indexFields.deletedBy = options.indexFields.indexOf('deletedBy') > -1;
    }

    return indexFields;
}

module.exports = function (schema, options) {
    options = options || {};
    var indexFields = parseIndexFields(options)

    schema.add({
        deleted: {
            type: Boolean,
            default: false,
            index: indexFields.deleted
        }
    });

    if (options.deletedAt === true) {
        schema.add({ deletedAt: { type: Date, index: indexFields.deletedAt }});
    }

    if (options.deletedBy === true) {
        schema.add({ deletedBy: { type: Schema.Types.ObjectId, index: indexFields.deletedBy }});
    }

    schema.pre('save', function (next) {
        if (!this.deleted) {
            this.deleted = false;
        }
        next();
    });

    if (options.overrideMethods) {
        var overrideItems = options.overrideMethods;
        var overridableMethods = ['count', 'find', 'findOne', 'findOneAndUpdate', 'update'];
        var finalList = [];

        if ((typeof overrideItems === 'string' || overrideItems instanceof String) && overrideItems === 'all') {
            finalList = overridableMethods;
        }

        if (typeof(overrideItems) === "boolean" && overrideItems === true) {
            finalList = overridableMethods;
        }

        if (Array.isArray(overrideItems)) {
            overrideItems.forEach(function(method) {
                if (overridableMethods.indexOf(method) > -1) {
                    finalList.push(method);
                }
            });
        }

        finalList.forEach(function(method) {
            if (method === 'count' || method === 'find' || method === 'findOne') {
                schema.statics[method] = function () {
                    return Model[method].apply(this, arguments).where('deleted').ne(true);
                };
                schema.statics[method + 'Deleted'] = function () {
                    return Model[method].apply(this, arguments).where('deleted').ne(false);
                };
                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            } else {
                schema.statics[method] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': true};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'Deleted'] = function () {
                    var args = parseUpdateArguments.apply(undefined, arguments);

                    args[0].deleted = {'$ne': false};

                    return Model[method].apply(this, args);
                };

                schema.statics[method + 'WithDeleted'] = function () {
                    return Model[method].apply(this, arguments);
                };
            }
        });
    }

    schema.methods.delete = function (deletedBy, cb) {
        if (typeof deletedBy === 'function') {
          cb = deletedBy
          deleteBy = null
        }

        this.deleted = true;

        if (schema.path('deletedAt')) {
            this.deletedAt = new Date();
        }

        if (schema.path('deletedBy')) {
            this.deletedBy = deletedBy;
        }

        if (options.validateBeforeDelete === false) {
            return this.save({ validateBeforeSave: false }, cb);
        }

        return this.save(cb);
    };

    schema.statics.delete =  function (conditions, deletedBy, callback) {
        if (typeof deletedBy === 'function') {
            callback = deletedBy;
            conditions = conditions;
            deletedBy = null;
        } else if (typeof conditions === 'function') {
            callback = conditions;
            conditions = {};
            deletedBy = null;
        }

        var doc = {
            deleted: true
        };

        if (schema.path('deletedAt')) {
            doc.deletedAt = new Date();
        }

        if (schema.path('deletedBy')) {
            doc.deletedBy = deletedBy;
        }

        if (this.updateWithDeleted) {
            return this.updateWithDeleted(conditions, doc, { multi: true }, callback);
        } else {
            return this.update(conditions, doc, { multi: true }, callback);
        }
    };

    schema.methods.restore = function (callback) {
        this.deleted = false;
        this.deletedAt = undefined;
        this.deletedBy = undefined;
        return this.save(callback);
    };

    schema.statics.restore =  function (conditions, callback) {
        if (typeof conditions === 'function') {
            callback = conditions;
            conditions = {};
        }

        var doc = {
            deleted: false,
            deletedAt: undefined,
            deletedBy: undefined
        };

        if (this.updateWithDeleted) {
            return this.updateWithDeleted(conditions, doc, { multi: true }, callback);
        } else {
            return this.update(conditions, doc, { multi: true }, callback);
        }
    };
};
<|endoftext|>


<commit_before>var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

http.listen(process.env.PORT || 5000, function(){
  console.log('listening on *:3000');
});<commit_msg>Console log msg<commit_after>var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
    console.log('msg: ' + msg);
  });
});

http.listen(process.env.PORT || 5000, function(){
  console.log('listening on *:' + process.env.PORT || 5000);
});<|endoftext|>


<commit_before>import { Observable } from 'rxjs';
import { evolve, merge, prop } from 'ramda';

/* t = time, c = content */
export const transformationExamples = {
  buffer: {
    label: 'buffer',
    inputs: [
      [{t:9, c:'A'}, {t:23, c:'B'}, {t:40, c:'C'}, {t:54, c:'D'}, {t:71, c:'E'}, {t:85, c:'F'}],
      [{t:33, c:0}, {t:66, c:0}, {t:90, c:0}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .buffer(inputs[1])
        .map(x => `[${x}]`);
    }
  },

  bufferCount: {
    label: 'bufferCount(3, 2)',
    inputs: [
      [{t:9, c:'A'}, {t:23, c:'B'}, {t:40, c:'C'}, {t:54, c:'D'}, {t:71, c:'E'}, {t:85, c:'F'}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .bufferCount(3, 2)
        .map(x => `[${x}]`);
    }
  },

  bufferTime: {
    label: 'bufferTime(30)',
    inputs: [
      [{t:0, c:'A'}, {t:10, c:'B'}, {t:22, c:'C'}, {t:61, c:'D'}, {t:71, c:'E'}, {t:95, c:'F'}],
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .bufferTime(30, scheduler)
        .map(x => `[${x}]`);
    }
  },

  bufferToggle: {
    label: 'bufferToggle(start$, x => Observable.timer(x))',
    inputs: [
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, {t:30, c:4}, {t:40, c:5}, {t:50, c:6}, {t:60, c:7}, {t:70, c:8}, {t:80, c:9}],
      [{t:15, c:10}, {t:45, c:30}],
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .bufferToggle(inputs[1], (x) => Observable.timer(x.content, scheduler))
        .map(x => `[${x}]`);
    }
  },

  bufferWhen: {
    label: 'bufferWhen',
    inputs: [
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, {t:30, c:4}, {t:40, c:5}, {t:50, c:6}, {t:60, c:7}, {t:70, c:8}, {t:80, c:9}],
      [{t:35, c:0}, {t:50, c:0}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .bufferWhen(() => inputs[1])
        .map(x => `[${x}]`);
    }
  },

  concatMap: {
    label: 'obs1$.concatMap(() => obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .concatMap(() => inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },

  concatMapTo: {
    label: 'obs1$.concatMapTo(obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .concatMapTo(inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },

  map: {
    label: 'map(x => 10 * x)',
    inputs: [
      [{t:10, c:1}, {t:20, c:2}, {t:50, c:3}]
    ],
    apply: function(inputs) {
      return inputs[0].map(evolve({ content: (c) => c * 10 }));
    }
  },

  mapTo: {
    label: 'mapTo("a")',
    inputs: [
      [{t:10, c:1}, {t:20, c:2}, {t:50, c:3}]
    ],
    apply: function(inputs) {
      return inputs[0].mapTo('a');
    }
  },

  mergeMap: {
    label: 'obs1$.mergeMap(() => obs2$, (x, y) => "" + x + y, 2)',
    inputs: [
      [{t:0, c:'A'}, {t:3, c:'B'}, {t:6, c:'C'}],
      [{t:0, c:1}, {t:12, c:2}, {t:24, c:3}, 28]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .mergeMap(() => inputs[1].pluck('content'), (x, y) => '' + x + y, 2);
    }
  },

  mergeMapTo: {
    label: 'obs1$.mergeMapTo(obs2$, (x, y) => "" + x + y, 2)',
    inputs: [
      [{t:0, c:'A'}, {t:3, c:'B'}, {t:6, c:'C'}],
      [{t:0, c:1}, {t:12, c:2}, {t:24, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .mergeMapTo(inputs[1].pluck('content'), (x, y) => '' + x + y, 2);
    }
  },

  pairwise: {
    label: 'pairwise',
    inputs: [
      [{t:9, c:'A'}, {t:23, c:'B'}, {t:40, c:'C'}, {t:54, c:'D'}, {t:71, c:'E'}, {t:85, c:'F'}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .pairwise().map(x => `[${x}]`);
    }
  },

  pluck: {
    label: 'pluck("a")',
    inputs: [
      [{t:10, c:'{a:1}'}, {t:20, c:'{a:2}'}, {t:50, c:'{a:5}'}]
    ],
    apply: function(inputs) {
      // simulated implementation
      return inputs[0].map(evolve({ content: c => c.match(/\d/)[0] }));
    }
  },

  repeat: {
    label: 'repeat(3)',
    inputs: [
      [{t:0, c:'A'}, {t:12, c: 'B'}, 26],
    ],
    apply: function(inputs) {
      return inputs[0].repeat(3);
    }
  },

  scan: {
    label: 'scan((x, y) => x + y)',
    inputs: [
      [{t:5, c:1}, {t:15, c:2}, {t:25, c:3}, {t:35, c:4}, {t:65, c:5}]
    ],
    apply: function(inputs) {
      return inputs[0].scan((x, y) =>
        merge(x, { content: x.content + y.content, id: x.id + y.id })
      );
    }
  },

  switchMap: {
    label: 'obs1$.switchMap(() => obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .switchMap(() => inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },

  switchMapTo: {
    label: 'obs1$.switchMap(obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .switchMapTo(inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },
};
<commit_msg>Correct switchMap to switchMapTo in code description<commit_after>import { Observable } from 'rxjs';
import { evolve, merge, prop } from 'ramda';

/* t = time, c = content */
export const transformationExamples = {
  buffer: {
    label: 'buffer',
    inputs: [
      [{t:9, c:'A'}, {t:23, c:'B'}, {t:40, c:'C'}, {t:54, c:'D'}, {t:71, c:'E'}, {t:85, c:'F'}],
      [{t:33, c:0}, {t:66, c:0}, {t:90, c:0}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .buffer(inputs[1])
        .map(x => `[${x}]`);
    }
  },

  bufferCount: {
    label: 'bufferCount(3, 2)',
    inputs: [
      [{t:9, c:'A'}, {t:23, c:'B'}, {t:40, c:'C'}, {t:54, c:'D'}, {t:71, c:'E'}, {t:85, c:'F'}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .bufferCount(3, 2)
        .map(x => `[${x}]`);
    }
  },

  bufferTime: {
    label: 'bufferTime(30)',
    inputs: [
      [{t:0, c:'A'}, {t:10, c:'B'}, {t:22, c:'C'}, {t:61, c:'D'}, {t:71, c:'E'}, {t:95, c:'F'}],
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .bufferTime(30, scheduler)
        .map(x => `[${x}]`);
    }
  },

  bufferToggle: {
    label: 'bufferToggle(start$, x => Observable.timer(x))',
    inputs: [
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, {t:30, c:4}, {t:40, c:5}, {t:50, c:6}, {t:60, c:7}, {t:70, c:8}, {t:80, c:9}],
      [{t:15, c:10}, {t:45, c:30}],
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .bufferToggle(inputs[1], (x) => Observable.timer(x.content, scheduler))
        .map(x => `[${x}]`);
    }
  },

  bufferWhen: {
    label: 'bufferWhen',
    inputs: [
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, {t:30, c:4}, {t:40, c:5}, {t:50, c:6}, {t:60, c:7}, {t:70, c:8}, {t:80, c:9}],
      [{t:35, c:0}, {t:50, c:0}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .bufferWhen(() => inputs[1])
        .map(x => `[${x}]`);
    }
  },

  concatMap: {
    label: 'obs1$.concatMap(() => obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .concatMap(() => inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },

  concatMapTo: {
    label: 'obs1$.concatMapTo(obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .concatMapTo(inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },

  map: {
    label: 'map(x => 10 * x)',
    inputs: [
      [{t:10, c:1}, {t:20, c:2}, {t:50, c:3}]
    ],
    apply: function(inputs) {
      return inputs[0].map(evolve({ content: (c) => c * 10 }));
    }
  },

  mapTo: {
    label: 'mapTo("a")',
    inputs: [
      [{t:10, c:1}, {t:20, c:2}, {t:50, c:3}]
    ],
    apply: function(inputs) {
      return inputs[0].mapTo('a');
    }
  },

  mergeMap: {
    label: 'obs1$.mergeMap(() => obs2$, (x, y) => "" + x + y, 2)',
    inputs: [
      [{t:0, c:'A'}, {t:3, c:'B'}, {t:6, c:'C'}],
      [{t:0, c:1}, {t:12, c:2}, {t:24, c:3}, 28]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .mergeMap(() => inputs[1].pluck('content'), (x, y) => '' + x + y, 2);
    }
  },

  mergeMapTo: {
    label: 'obs1$.mergeMapTo(obs2$, (x, y) => "" + x + y, 2)',
    inputs: [
      [{t:0, c:'A'}, {t:3, c:'B'}, {t:6, c:'C'}],
      [{t:0, c:1}, {t:12, c:2}, {t:24, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .mergeMapTo(inputs[1].pluck('content'), (x, y) => '' + x + y, 2);
    }
  },

  pairwise: {
    label: 'pairwise',
    inputs: [
      [{t:9, c:'A'}, {t:23, c:'B'}, {t:40, c:'C'}, {t:54, c:'D'}, {t:71, c:'E'}, {t:85, c:'F'}],
    ],
    apply: function(inputs) {
      return inputs[0].pluck('content')
        .pairwise().map(x => `[${x}]`);
    }
  },

  pluck: {
    label: 'pluck("a")',
    inputs: [
      [{t:10, c:'{a:1}'}, {t:20, c:'{a:2}'}, {t:50, c:'{a:5}'}]
    ],
    apply: function(inputs) {
      // simulated implementation
      return inputs[0].map(evolve({ content: c => c.match(/\d/)[0] }));
    }
  },

  repeat: {
    label: 'repeat(3)',
    inputs: [
      [{t:0, c:'A'}, {t:12, c: 'B'}, 26],
    ],
    apply: function(inputs) {
      return inputs[0].repeat(3);
    }
  },

  scan: {
    label: 'scan((x, y) => x + y)',
    inputs: [
      [{t:5, c:1}, {t:15, c:2}, {t:25, c:3}, {t:35, c:4}, {t:65, c:5}]
    ],
    apply: function(inputs) {
      return inputs[0].scan((x, y) =>
        merge(x, { content: x.content + y.content, id: x.id + y.id })
      );
    }
  },

  switchMap: {
    label: 'obs1$.switchMap(() => obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .switchMap(() => inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },

  switchMapTo: {
    label: 'obs1$.switchMapTo(obs2$, (x, y) => "" + x + y)',
    inputs: [
      [{t:0, c:'A'}, {t:42, c:'B'}, {t:55, c:'C'}],
      [{t:0, c:1}, {t:10, c:2}, {t:20, c:3}, 25]
    ],
    apply: function(inputs, scheduler) {
      return inputs[0].pluck('content')
        .switchMapTo(inputs[1].pluck('content'), (x, y) => '' + x + y);
    }
  },
};
<|endoftext|>


<commit_before>//import angular from 'angular';
import _ from 'lodash';
import * as dateMath from 'app/core/utils/datemath';
import * as utils from './utils';
import * as migrations from './migrations';
import * as metricFunctions from './metricFunctions';
import DataProcessor from './DataProcessor';
import './zabbixAPI.service.js';
import './zabbixCache.service.js';
import './queryProcessor.service.js';

export class ZabbixAPIDatasource {

  /** @ngInject */
  constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) {

    // General data source settings
    this.name             = instanceSettings.name;
    this.url              = instanceSettings.url;
    this.basicAuth        = instanceSettings.basicAuth;
    this.withCredentials  = instanceSettings.withCredentials;

    // Zabbix API credentials
    this.username         = instanceSettings.jsonData.username;
    this.password         = instanceSettings.jsonData.password;

    // Use trends instead history since specified time
    this.trends           = instanceSettings.jsonData.trends;
    this.trendsFrom       = instanceSettings.jsonData.trendsFrom || '7d';

    // Set cache update interval
    var ttl = instanceSettings.jsonData.cacheTTL || '1h';
    this.cacheTTL = utils.parseInterval(ttl);

    // Initialize Zabbix API
    var ZabbixAPI = zabbixAPIService;
    this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials);

    // Initialize cache service
    this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL);

    // Initialize query builder
    this.queryProcessor = new QueryProcessor(this.zabbixCache);

    // Dependencies
    this.q = $q;
    this.templateSrv = templateSrv;
    this.alertSrv = alertSrv;

    // Use custom format for template variables
    this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
  }

  ////////////////////////
  // Datasource methods //
  ////////////////////////

  /**
   * Query panel data. Calls for each panel in dashboard.
   * @param  {Object} options   Contains time range, targets and other info.
   * @return {Object} Grafana metrics object with timeseries data for each target.
   */
  query(options) {
    var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
    var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);

    var useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000);
    var useTrends = (timeFrom <= useTrendsFrom) && this.trends;

    // Create request for each target
    var promises = _.map(options.targets, target => {

      // Prevent changes of original object
      target = _.cloneDeep(target);

      if (target.mode !== 1) {

        // Migrate old targets
        target = migrations.migrate(target);

        // Don't request undefined and hidden targets
        if (target.hide || !target.group || !target.host || !target.item) {
          return [];
        }

        // Replace templated variables
        target.group.filter = this.replaceTemplateVars(target.group.filter, options.scopedVars);
        target.host.filter = this.replaceTemplateVars(target.host.filter, options.scopedVars);
        target.application.filter = this.replaceTemplateVars(target.application.filter, options.scopedVars);
        target.item.filter = this.replaceTemplateVars(target.item.filter, options.scopedVars);
        target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);

        _.forEach(target.functions, func => {
          func.params = _.map(func.params, param => {
            if (typeof param === 'number') {
              return +this.templateSrv.replace(param.toString(), options.scopedVars);
            } else {
              return this.templateSrv.replace(param, options.scopedVars);
            }
          });
        });

        // Query numeric data
        if (!target.mode || target.mode === 0) {
          return this.queryNumericData(target, timeFrom, timeTo, useTrends);
        }

        // Query text data
        else if (target.mode === 2) {
          return this.queryTextData(target, timeFrom, timeTo);
        }
      }

      // IT services mode
      else if (target.mode === 1) {
        // Don't show undefined and hidden targets
        if (target.hide || !target.itservice || !target.slaProperty) {
          return [];
        }

        return this.zabbixAPI
          .getSLA(target.itservice.serviceid, timeFrom, timeTo)
          .then(slaObject => {
            return this.queryProcessor
              .handleSLAResponse(target.itservice, target.slaProperty, slaObject);
          });
      }
    });

    // Data for panel (all targets)
    return this.q.all(_.flatten(promises))
      .then(_.flatten)
      .then(timeseries_data => {

        // Series downsampling
        var data = _.map(timeseries_data, timeseries => {
          if (timeseries.datapoints.length > options.maxDataPoints) {
            timeseries.datapoints = DataProcessor
              .groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints);
          }
          return timeseries;
        });
        return { data: data };
      });
  }

  queryNumericData(target, timeFrom, timeTo, useTrends) {
    // Build query in asynchronous manner
    return this.queryProcessor.build(target.group.filter,
                                     target.host.filter,
                                     target.application.filter,
                                     target.item.filter,
                                     'num')
      .then(items => {
        // Add hostname for items from multiple hosts
        var addHostName = utils.isRegex(target.host.filter);
        var getHistory;

        // Use trends
        if (useTrends) {

          // Find trendValue() function and get specified trend value
          var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name');
          var trendValueFunc = _.find(target.functions, func => {
            return _.includes(trendFunctions, func.def.name);
          });
          var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg";

          getHistory = this.zabbixAPI
            .getTrend(items, timeFrom, timeTo)
            .then(history => {
              return this.queryProcessor.handleTrends(history, items, addHostName, valueType);
            });
        }

        // Use history
        else {
          getHistory = this.zabbixCache
            .getHistory(items, timeFrom, timeTo)
            .then(history => {
              return this.queryProcessor.handleHistory(history, items, addHostName);
            });
        }

        return getHistory.then(timeseries_data => {
          let transformFunctions   = bindFunctionDefs(target.functions, 'Transform');
          let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate');
          let filterFunctions      = bindFunctionDefs(target.functions, 'Filter');
          let aliasFunctions       = bindFunctionDefs(target.functions, 'Alias');

          // Apply transformation functions
          timeseries_data = _.map(timeseries_data, timeseries => {
            timeseries.datapoints = sequence(transformFunctions)(timeseries.datapoints);
            return timeseries;
          });

          // Apply filter functions
          if (filterFunctions.length) {
            timeseries_data = sequence(filterFunctions)(timeseries_data);
          }

          // Apply aggregations
          if (aggregationFunctions.length) {
            let dp = _.map(timeseries_data, 'datapoints');
            dp = sequence(aggregationFunctions)(dp);

            let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name');
            let lastAgg = _.findLast(target.functions, func => {
              return _.includes(aggFuncNames, func.def.name);
            });

            timeseries_data = [
              {
                target: lastAgg.text,
                datapoints: dp
              }
            ];
          }

          // Apply alias functions
          _.each(timeseries_data, sequence(aliasFunctions));

          return timeseries_data;
        });
      });
  }

  queryTextData(target, timeFrom, timeTo) {
    return this.queryProcessor.build(target.group.filter,
                                     target.host.filter,
                                     target.application.filter,
                                     target.item.filter,
                                     'text')
      .then(items => {
        if (items.length) {
          return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
            .then(history => {
              return this.queryProcessor.convertHistory(history, items, false, (point) => {
                let value = point.value;

                // Regex-based extractor
                if (target.textFilter) {
                  value = extractText(point.value, target.textFilter, target.useCaptureGroups);
                }

                return [value, point.clock * 1000];
              });
            });
        } else {
          return this.q.when([]);
        }
      });
  }

  /**
   * Test connection to Zabbix API
   * @return {object} Connection status and Zabbix API version
   */
  testDatasource() {
    return this.zabbixAPI.getVersion()
      .then(version => {
        return this.zabbixAPI.login()
          .then(auth => {
            if (auth) {
              return {
                status: "success",
                title: "Success",
                message: "Zabbix API version: " + version
              };
            } else {
              return {
                status: "error",
                title: "Invalid user name or password",
                message: "Zabbix API version: " + version
              };
            }
          }, error => {
            return {
              status: "error",
              title: error.message,
              message: error.data
            };
          });
      }, error => {
        console.log(error);
        return {
          status: "error",
          title: "Connection failed",
          message: "Could not connect to given url"
        };
      });
  }

  ////////////////
  // Templating //
  ////////////////

  /**
   * Find metrics from templated request.
   *
   * @param  {string} query Query from Templating
   * @return {string}       Metric name - group, host, app or item or list
   *                        of metrics in "{metric1,metcic2,...,metricN}" format.
   */
  metricFindQuery(query) {
    let result;
    let parts = [];

    // Split query. Query structure: group.host.app.item
    _.each(query.split('.'), part => {
      part = this.replaceTemplateVars(part, {});

      // Replace wildcard to regex
      if (part === '*') {
        part = '/.*/';
      }
      parts.push(part);
    });
    let template = _.zipObject(['group', 'host', 'app', 'item'], parts);

    // Get items
    if (parts.length === 4) {
      // Search for all items, even it's not belong to any application
      if (template.app === '/.*/') {
        template.app = '';
      }
      result = this.queryProcessor.getItems(template.group, template.host, template.app);
    } else if (parts.length === 3) {
      // Get applications
      result = this.queryProcessor.getApps(template.group, template.host);
    } else if (parts.length === 2) {
      // Get hosts
      result = this.queryProcessor.getHosts(template.group);
    } else if (parts.length === 1) {
      // Get groups
      result = this.zabbixCache.getGroups(template.group);
    } else {
      result = this.q.when([]);
    }

    return result.then(metrics => {
      return _.map(metrics, formatMetric);
    });
  }

  /////////////////
  // Annotations //
  /////////////////

  annotationQuery(options) {
    var timeFrom = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000);
    var timeTo = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000);
    var annotation = options.annotation;
    var showOkEvents = annotation.showOkEvents ? [0, 1] : 1;

    // Show all triggers
    var showTriggers = [0, 1];

    var buildQuery = this.queryProcessor
      .buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}),
                         this.replaceTemplateVars(annotation.host, {}),
                         this.replaceTemplateVars(annotation.application, {}));

    return buildQuery.then(query => {
      return this.zabbixAPI
        .getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers)
        .then(triggers => {

          // Filter triggers by description
          if (utils.isRegex(annotation.trigger)) {
            triggers = _.filter(triggers, trigger => {
              return utils.buildRegex(annotation.trigger).test(trigger.description);
            });
          } else if (annotation.trigger) {
            triggers = _.filter(triggers, trigger => {
              return trigger.description === annotation.trigger;
            });
          }

          // Remove events below the chose severity
          triggers = _.filter(triggers, trigger => {
            return Number(trigger.priority) >= Number(annotation.minseverity);
          });

          var objectids = _.map(triggers, 'triggerid');
          return this.zabbixAPI
            .getEvents(objectids, timeFrom, timeTo, showOkEvents)
            .then(events => {
              var indexedTriggers = _.keyBy(triggers, 'triggerid');

              // Hide acknowledged events if option enabled
              if (annotation.hideAcknowledged) {
                events = _.filter(events, event => {
                  return !event.acknowledges.length;
                });
              }

              return _.map(events, event => {
                let tags;
                if (annotation.showHostname) {
                  tags = _.map(event.hosts, 'name');
                }

                // Show event type (OK or Problem)
                let title = Number(event.value) ? 'Problem' : 'OK';

                let formatted_acknowledges = utils.formatAcknowledges(event.acknowledges);
                return {
                  annotation: annotation,
                  time: event.clock * 1000,
                  title: title,
                  tags: tags,
                  text: indexedTriggers[event.objectid].description + formatted_acknowledges
                };
              });
            });
        });
    });
  }

}

function bindFunctionDefs(functionDefs, category) {
  var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name');
  var aggFuncDefs = _.filter(functionDefs, function(func) {
    return _.includes(aggregationFunctions, func.def.name);
  });

  return _.map(aggFuncDefs, function(func) {
    var funcInstance = metricFunctions.createFuncInstance(func.def, func.params);
    return funcInstance.bindFunction(DataProcessor.metricFunctions);
  });
}

function formatMetric(metricObj) {
  return {
    text: metricObj.name,
    expandable: false
  };
}

/**
 * Custom formatter for template variables.
 * Default Grafana "regex" formatter returns
 * value1|value2
 * This formatter returns
 * (value1|value2)
 * This format needed for using in complex regex with
 * template variables, for example
 * /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait
 */
export function zabbixTemplateFormat(value) {
  if (typeof value === 'string') {
    return utils.escapeRegex(value);
  }

  var escapedValues = _.map(value, utils.escapeRegex);
  return '(' + escapedValues.join('|') + ')';
}

/**
 * If template variables are used in request, replace it using regex format
 * and wrap with '/' for proper multi-value work. Example:
 * $variable selected as a, b, c
 * We use filter $variable
 * $variable    -> a|b|c    -> /a|b|c/
 * /$variable/  -> /a|b|c/  -> /a|b|c/
 */
function replaceTemplateVars(templateSrv, target, scopedVars) {
  var replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat);
  if (target !== replacedTarget && !utils.isRegex(replacedTarget)) {
    replacedTarget = '/^' + replacedTarget + '$/';
  }
  return replacedTarget;
}

function extractText(str, pattern, useCaptureGroups) {
  let extractPattern = new RegExp(pattern);
  let extractedValue = extractPattern.exec(str);
  if (extractedValue) {
    if (useCaptureGroups) {
      extractedValue = extractedValue[1];
    } else {
      extractedValue = extractedValue[0];
    }
  }
  return extractedValue;
}

// Apply function one by one:
// sequence([a(), b(), c()]) = c(b(a()));
function sequence(funcsArray) {
  return function(result) {
    for (var i = 0; i < funcsArray.length; i++) {
      result = funcsArray[i].call(this, result);
    }
    return result;
  };
}

// Fix for backward compatibility with lodash 2.4
if (!_.includes) {_.includes = _.contains;}
if (!_.keyBy) {_.keyBy = _.indexBy;}
<commit_msg>Fixed zabbixTemplateFormat() error.<commit_after>//import angular from 'angular';
import _ from 'lodash';
import * as dateMath from 'app/core/utils/datemath';
import * as utils from './utils';
import * as migrations from './migrations';
import * as metricFunctions from './metricFunctions';
import DataProcessor from './DataProcessor';
import './zabbixAPI.service.js';
import './zabbixCache.service.js';
import './queryProcessor.service.js';

class ZabbixAPIDatasource {

  /** @ngInject */
  constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) {

    // General data source settings
    this.name             = instanceSettings.name;
    this.url              = instanceSettings.url;
    this.basicAuth        = instanceSettings.basicAuth;
    this.withCredentials  = instanceSettings.withCredentials;

    // Zabbix API credentials
    this.username         = instanceSettings.jsonData.username;
    this.password         = instanceSettings.jsonData.password;

    // Use trends instead history since specified time
    this.trends           = instanceSettings.jsonData.trends;
    this.trendsFrom       = instanceSettings.jsonData.trendsFrom || '7d';

    // Set cache update interval
    var ttl = instanceSettings.jsonData.cacheTTL || '1h';
    this.cacheTTL = utils.parseInterval(ttl);

    // Initialize Zabbix API
    var ZabbixAPI = zabbixAPIService;
    this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials);

    // Initialize cache service
    this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL);

    // Initialize query builder
    this.queryProcessor = new QueryProcessor(this.zabbixCache);

    // Dependencies
    this.q = $q;
    this.templateSrv = templateSrv;
    this.alertSrv = alertSrv;

    // Use custom format for template variables
    this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
  }

  ////////////////////////
  // Datasource methods //
  ////////////////////////

  /**
   * Query panel data. Calls for each panel in dashboard.
   * @param  {Object} options   Contains time range, targets and other info.
   * @return {Object} Grafana metrics object with timeseries data for each target.
   */
  query(options) {
    var timeFrom = Math.ceil(dateMath.parse(options.range.from) / 1000);
    var timeTo = Math.ceil(dateMath.parse(options.range.to) / 1000);

    var useTrendsFrom = Math.ceil(dateMath.parse('now-' + this.trendsFrom) / 1000);
    var useTrends = (timeFrom <= useTrendsFrom) && this.trends;

    // Create request for each target
    var promises = _.map(options.targets, target => {

      // Prevent changes of original object
      target = _.cloneDeep(target);

      if (target.mode !== 1) {

        // Migrate old targets
        target = migrations.migrate(target);

        // Don't request undefined and hidden targets
        if (target.hide || !target.group || !target.host || !target.item) {
          return [];
        }

        // Replace templated variables
        target.group.filter = this.replaceTemplateVars(target.group.filter, options.scopedVars);
        target.host.filter = this.replaceTemplateVars(target.host.filter, options.scopedVars);
        target.application.filter = this.replaceTemplateVars(target.application.filter, options.scopedVars);
        target.item.filter = this.replaceTemplateVars(target.item.filter, options.scopedVars);
        target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);

        _.forEach(target.functions, func => {
          func.params = _.map(func.params, param => {
            if (typeof param === 'number') {
              return +this.templateSrv.replace(param.toString(), options.scopedVars);
            } else {
              return this.templateSrv.replace(param, options.scopedVars);
            }
          });
        });

        // Query numeric data
        if (!target.mode || target.mode === 0) {
          return this.queryNumericData(target, timeFrom, timeTo, useTrends);
        }

        // Query text data
        else if (target.mode === 2) {
          return this.queryTextData(target, timeFrom, timeTo);
        }
      }

      // IT services mode
      else if (target.mode === 1) {
        // Don't show undefined and hidden targets
        if (target.hide || !target.itservice || !target.slaProperty) {
          return [];
        }

        return this.zabbixAPI
          .getSLA(target.itservice.serviceid, timeFrom, timeTo)
          .then(slaObject => {
            return this.queryProcessor
              .handleSLAResponse(target.itservice, target.slaProperty, slaObject);
          });
      }
    });

    // Data for panel (all targets)
    return this.q.all(_.flatten(promises))
      .then(_.flatten)
      .then(timeseries_data => {

        // Series downsampling
        var data = _.map(timeseries_data, timeseries => {
          if (timeseries.datapoints.length > options.maxDataPoints) {
            timeseries.datapoints = DataProcessor
              .groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints);
          }
          return timeseries;
        });
        return { data: data };
      });
  }

  queryNumericData(target, timeFrom, timeTo, useTrends) {
    // Build query in asynchronous manner
    return this.queryProcessor.build(target.group.filter,
                                     target.host.filter,
                                     target.application.filter,
                                     target.item.filter,
                                     'num')
      .then(items => {
        // Add hostname for items from multiple hosts
        var addHostName = utils.isRegex(target.host.filter);
        var getHistory;

        // Use trends
        if (useTrends) {

          // Find trendValue() function and get specified trend value
          var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name');
          var trendValueFunc = _.find(target.functions, func => {
            return _.includes(trendFunctions, func.def.name);
          });
          var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg";

          getHistory = this.zabbixAPI
            .getTrend(items, timeFrom, timeTo)
            .then(history => {
              return this.queryProcessor.handleTrends(history, items, addHostName, valueType);
            });
        }

        // Use history
        else {
          getHistory = this.zabbixCache
            .getHistory(items, timeFrom, timeTo)
            .then(history => {
              return this.queryProcessor.handleHistory(history, items, addHostName);
            });
        }

        return getHistory.then(timeseries_data => {
          let transformFunctions   = bindFunctionDefs(target.functions, 'Transform');
          let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate');
          let filterFunctions      = bindFunctionDefs(target.functions, 'Filter');
          let aliasFunctions       = bindFunctionDefs(target.functions, 'Alias');

          // Apply transformation functions
          timeseries_data = _.map(timeseries_data, timeseries => {
            timeseries.datapoints = sequence(transformFunctions)(timeseries.datapoints);
            return timeseries;
          });

          // Apply filter functions
          if (filterFunctions.length) {
            timeseries_data = sequence(filterFunctions)(timeseries_data);
          }

          // Apply aggregations
          if (aggregationFunctions.length) {
            let dp = _.map(timeseries_data, 'datapoints');
            dp = sequence(aggregationFunctions)(dp);

            let aggFuncNames = _.map(metricFunctions.getCategories()['Aggregate'], 'name');
            let lastAgg = _.findLast(target.functions, func => {
              return _.includes(aggFuncNames, func.def.name);
            });

            timeseries_data = [
              {
                target: lastAgg.text,
                datapoints: dp
              }
            ];
          }

          // Apply alias functions
          _.each(timeseries_data, sequence(aliasFunctions));

          return timeseries_data;
        });
      });
  }

  queryTextData(target, timeFrom, timeTo) {
    return this.queryProcessor.build(target.group.filter,
                                     target.host.filter,
                                     target.application.filter,
                                     target.item.filter,
                                     'text')
      .then(items => {
        if (items.length) {
          return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
            .then(history => {
              return this.queryProcessor.convertHistory(history, items, false, (point) => {
                let value = point.value;

                // Regex-based extractor
                if (target.textFilter) {
                  value = extractText(point.value, target.textFilter, target.useCaptureGroups);
                }

                return [value, point.clock * 1000];
              });
            });
        } else {
          return this.q.when([]);
        }
      });
  }

  /**
   * Test connection to Zabbix API
   * @return {object} Connection status and Zabbix API version
   */
  testDatasource() {
    return this.zabbixAPI.getVersion()
      .then(version => {
        return this.zabbixAPI.login()
          .then(auth => {
            if (auth) {
              return {
                status: "success",
                title: "Success",
                message: "Zabbix API version: " + version
              };
            } else {
              return {
                status: "error",
                title: "Invalid user name or password",
                message: "Zabbix API version: " + version
              };
            }
          }, error => {
            return {
              status: "error",
              title: error.message,
              message: error.data
            };
          });
      }, error => {
        console.log(error);
        return {
          status: "error",
          title: "Connection failed",
          message: "Could not connect to given url"
        };
      });
  }

  ////////////////
  // Templating //
  ////////////////

  /**
   * Find metrics from templated request.
   *
   * @param  {string} query Query from Templating
   * @return {string}       Metric name - group, host, app or item or list
   *                        of metrics in "{metric1,metcic2,...,metricN}" format.
   */
  metricFindQuery(query) {
    let result;
    let parts = [];

    // Split query. Query structure: group.host.app.item
    _.each(query.split('.'), part => {
      part = this.replaceTemplateVars(part, {});

      // Replace wildcard to regex
      if (part === '*') {
        part = '/.*/';
      }
      parts.push(part);
    });
    let template = _.zipObject(['group', 'host', 'app', 'item'], parts);

    // Get items
    if (parts.length === 4) {
      // Search for all items, even it's not belong to any application
      if (template.app === '/.*/') {
        template.app = '';
      }
      result = this.queryProcessor.getItems(template.group, template.host, template.app);
    } else if (parts.length === 3) {
      // Get applications
      result = this.queryProcessor.getApps(template.group, template.host);
    } else if (parts.length === 2) {
      // Get hosts
      result = this.queryProcessor.getHosts(template.group);
    } else if (parts.length === 1) {
      // Get groups
      result = this.zabbixCache.getGroups(template.group);
    } else {
      result = this.q.when([]);
    }

    return result.then(metrics => {
      return _.map(metrics, formatMetric);
    });
  }

  /////////////////
  // Annotations //
  /////////////////

  annotationQuery(options) {
    var timeFrom = Math.ceil(dateMath.parse(options.rangeRaw.from) / 1000);
    var timeTo = Math.ceil(dateMath.parse(options.rangeRaw.to) / 1000);
    var annotation = options.annotation;
    var showOkEvents = annotation.showOkEvents ? [0, 1] : 1;

    // Show all triggers
    var showTriggers = [0, 1];

    var buildQuery = this.queryProcessor
      .buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}),
                         this.replaceTemplateVars(annotation.host, {}),
                         this.replaceTemplateVars(annotation.application, {}));

    return buildQuery.then(query => {
      return this.zabbixAPI
        .getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers)
        .then(triggers => {

          // Filter triggers by description
          if (utils.isRegex(annotation.trigger)) {
            triggers = _.filter(triggers, trigger => {
              return utils.buildRegex(annotation.trigger).test(trigger.description);
            });
          } else if (annotation.trigger) {
            triggers = _.filter(triggers, trigger => {
              return trigger.description === annotation.trigger;
            });
          }

          // Remove events below the chose severity
          triggers = _.filter(triggers, trigger => {
            return Number(trigger.priority) >= Number(annotation.minseverity);
          });

          var objectids = _.map(triggers, 'triggerid');
          return this.zabbixAPI
            .getEvents(objectids, timeFrom, timeTo, showOkEvents)
            .then(events => {
              var indexedTriggers = _.keyBy(triggers, 'triggerid');

              // Hide acknowledged events if option enabled
              if (annotation.hideAcknowledged) {
                events = _.filter(events, event => {
                  return !event.acknowledges.length;
                });
              }

              return _.map(events, event => {
                let tags;
                if (annotation.showHostname) {
                  tags = _.map(event.hosts, 'name');
                }

                // Show event type (OK or Problem)
                let title = Number(event.value) ? 'Problem' : 'OK';

                let formatted_acknowledges = utils.formatAcknowledges(event.acknowledges);
                return {
                  annotation: annotation,
                  time: event.clock * 1000,
                  title: title,
                  tags: tags,
                  text: indexedTriggers[event.objectid].description + formatted_acknowledges
                };
              });
            });
        });
    });
  }

}

function bindFunctionDefs(functionDefs, category) {
  var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name');
  var aggFuncDefs = _.filter(functionDefs, function(func) {
    return _.includes(aggregationFunctions, func.def.name);
  });

  return _.map(aggFuncDefs, function(func) {
    var funcInstance = metricFunctions.createFuncInstance(func.def, func.params);
    return funcInstance.bindFunction(DataProcessor.metricFunctions);
  });
}

function formatMetric(metricObj) {
  return {
    text: metricObj.name,
    expandable: false
  };
}

/**
 * Custom formatter for template variables.
 * Default Grafana "regex" formatter returns
 * value1|value2
 * This formatter returns
 * (value1|value2)
 * This format needed for using in complex regex with
 * template variables, for example
 * /CPU $cpu_item.*time/ where $cpu_item is system,user,iowait
 */
function zabbixTemplateFormat(value) {
  if (typeof value === 'string') {
    return utils.escapeRegex(value);
  }

  var escapedValues = _.map(value, utils.escapeRegex);
  return '(' + escapedValues.join('|') + ')';
}

/**
 * If template variables are used in request, replace it using regex format
 * and wrap with '/' for proper multi-value work. Example:
 * $variable selected as a, b, c
 * We use filter $variable
 * $variable    -> a|b|c    -> /a|b|c/
 * /$variable/  -> /a|b|c/  -> /a|b|c/
 */
function replaceTemplateVars(templateSrv, target, scopedVars) {
  var replacedTarget = templateSrv.replace(target, scopedVars, zabbixTemplateFormat);
  if (target !== replacedTarget && !utils.isRegex(replacedTarget)) {
    replacedTarget = '/^' + replacedTarget + '$/';
  }
  return replacedTarget;
}

function extractText(str, pattern, useCaptureGroups) {
  let extractPattern = new RegExp(pattern);
  let extractedValue = extractPattern.exec(str);
  if (extractedValue) {
    if (useCaptureGroups) {
      extractedValue = extractedValue[1];
    } else {
      extractedValue = extractedValue[0];
    }
  }
  return extractedValue;
}

// Apply function one by one:
// sequence([a(), b(), c()]) = c(b(a()));
function sequence(funcsArray) {
  return function(result) {
    for (var i = 0; i < funcsArray.length; i++) {
      result = funcsArray[i].call(this, result);
    }
    return result;
  };
}

export {ZabbixAPIDatasource, zabbixTemplateFormat};

// Fix for backward compatibility with lodash 2.4
if (!_.includes) {_.includes = _.contains;}
if (!_.keyBy) {_.keyBy = _.indexBy;}
<|endoftext|>


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

var makeTable = require('./makeTable.js')
var fs = require('fs')

var key = process.argv[2]

var save = false

if (process.argv[3] && process.argv[3].match("--save")) {
  save = true
}

if (key && !save) {
  makeTable(key, function callback(err, table) {
    if (err != "null") return console.log(err)

    console.log(table)
  })
}

if (key && save) {
  makeTable(key, function callback(err, table) {
    if (err != "null") return console.log(err)

    fs.writeFile('table.md', table.toString(), function (err) {
      if (err) return console.log(err)
    })
    console.log('table has been created and saved')
  })
}

if (!key) {
  return console.log("To use: `$ sheetdown SPREADSHEETKEY --save`\n" +
                     "Or: `$ sheetdown SPREADSHEETKEY | pbcopy")
}
<commit_msg>fix #8<commit_after>#!/usr/bin/env node

var makeTable = require('./makeTable.js')
var fs = require('fs')

var key = process.argv[2]

var save = false

if (process.argv[3] && process.argv[3].match("--save")) {
  save = true
}

if (key && !save) {
  makeTable(key, function callback(err, table) {
    if (err != "null") return console.error(err)

    console.log(table)
  })
}

if (key && save) {
  makeTable(key, function callback(err, table) {
    if (err != "null") return console.error(err)

    fs.writeFile('table.md', table.toString(), function (err) {
      if (err) return console.error(err)
    })
    console.log('table has been created and saved')
  })
}

if (!key) {
  return console.log("To use: `$ sheetdown SPREADSHEETKEY --save`\n" +
                     "Or: `$ sheetdown SPREADSHEETKEY | pbcopy")
}
<|endoftext|>


<commit_before>var uuid = require('uuid');

module.exports = function logRequest(options) {
  var logger = options.logger;
  var headerName = options.headerName || 'x-request-id';

  return function (req, res, next) {
    var id = req.headers[headerName] || uuid.v4();
    var now = Date.now();
    var startOpts = {req: req};

    req.log = logger.child({
      type: 'request',
      id: id,
      serializers: logger.constructor.stdSerializers
    });

    if (req.body) {
      startOpts.body = req.body;
    }

    res.setHeader(headerName, id);

    req.log.info(startOpts, 'start request');
    res.on('finish', function responseSent() {
      req.log.info({res: res, duration: Date.now() - now}, 'end request');
    });

    next();
  };
};
<commit_msg>More accurate duration<commit_after>var uuid = require('uuid');

module.exports = function logRequest(options) {
  var logger = options.logger;
  var headerName = options.headerName || 'x-request-id';

  return function (req, res, next) {
    var id = req.headers[headerName] || uuid.v4();
    var now = Date.now();
    var startOpts = {req: req};

    req.log = logger.child({
      type: 'request',
      id: id,
      serializers: logger.constructor.stdSerializers
    });

    if (req.body) {
      startOpts.body = req.body;
    }

    res.setHeader(headerName, id);

    req.log.info(startOpts, 'start request');

    var time = process.hrtime();
    res.on('finish', function responseSent() {
      var diff = process.hrtime(time);
      req.log.info({res: res, duration: diff[0] * 1e3 + diff[1] * 1e-6}, 'end request');
    });

    next();
  };
};
<|endoftext|>


<commit_before>/**
 * CouchDB Proxy middleware for Express
 *
 * @author Amir Malik
 */

var express = require('express'),
      https = require('https'),
       http = require('http'),
    mod_url = require('url');

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

module.exports = function(options, cb) {
  var app = express();

  // parse credentials from Authorization header into req.remote_*
  app.use(function(req, res, next) {
    var creds = utils.authorization(req.headers['authorization']);

    if(!creds)
      return utils.unauthorized(res, options.realm);

    req.remote_user = creds[0];
    req.remote_pass = creds[1];

    next();
  });

  // CouchDB replication base endpoint
  app.all('/:db/*', function(req, res) {
    req.pause();

    cb(req.params.db, req.remote_user, req.remote_pass, function(err, url) {
      if(err)
        return utils.unauthorized(res, options.realm, 'forbidden');

      var remoteHeaders = {};
      for(var header in req.headers) {
        if(req.headers.hasOwnProperty(header)) {
          remoteHeaders[header] = req.headers[header];
        }
      }

      delete remoteHeaders['authorization'];
      delete remoteHeaders['host'];

      var remoteURL = mod_url.parse(url);
      remoteURL.path += req.url.slice(req.params.db.length + 1);

      var request = 'https:' == remoteURL.protocol ? https.request : http.request;

      var remoteReq = request({
        method: req.method,
        hostname: remoteURL.hostname,
        port: remoteURL.port || ('https:' == remoteURL.protocol ? 443 : 80),
        path: remoteURL.path,
        headers: remoteHeaders,
        auth: remoteURL.auth,
      }, function(remoteRes) {
        // node's HTTP parser has already parsed any chunked encoding
        delete remoteRes.headers['transfer-encoding'];

        // CouchDB replication fails unless we use a properly-cased header
        remoteRes.headers['Content-Type'] = remoteRes.headers['content-type'];
        delete remoteRes.headers['content-type'];

        res.writeHead(remoteRes.statusCode, remoteRes.headers);
        remoteRes.pipe(res);
      });

      remoteReq.on('error', function(err) {
        res.json(503, {error: 'db_unavailable', reason: err.syscall + ' ' + err.errno});
      });

      req.setEncoding('utf8');
      req.resume();
      req.pipe(remoteReq);
    });
  });

  return app;
};
<commit_msg>Relay error message to response.<commit_after>/**
 * CouchDB Proxy middleware for Express
 *
 * @author Amir Malik
 */

var express = require('express'),
      https = require('https'),
       http = require('http'),
    mod_url = require('url');

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

module.exports = function(options, cb) {
  var app = express();

  // parse credentials from Authorization header into req.remote_*
  app.use(function(req, res, next) {
    var creds = utils.authorization(req.headers['authorization']);

    if(!creds)
      return utils.unauthorized(res, options.realm);

    req.remote_user = creds[0];
    req.remote_pass = creds[1];

    next();
  });

  // CouchDB replication base endpoint
  app.all('/:db/*', function(req, res) {
    req.pause();

    cb(req.params.db, req.remote_user, req.remote_pass, function(err, url) {
      if(err)
        return utils.unauthorized(res, options.realm, err.message);

      var remoteHeaders = {};
      for(var header in req.headers) {
        if(req.headers.hasOwnProperty(header)) {
          remoteHeaders[header] = req.headers[header];
        }
      }

      delete remoteHeaders['authorization'];
      delete remoteHeaders['host'];

      var remoteURL = mod_url.parse(url);
      remoteURL.path += req.url.slice(req.params.db.length + 1);

      var request = 'https:' == remoteURL.protocol ? https.request : http.request;

      var remoteReq = request({
        method: req.method,
        hostname: remoteURL.hostname,
        port: remoteURL.port || ('https:' == remoteURL.protocol ? 443 : 80),
        path: remoteURL.path,
        headers: remoteHeaders,
        auth: remoteURL.auth,
      }, function(remoteRes) {
        // node's HTTP parser has already parsed any chunked encoding
        delete remoteRes.headers['transfer-encoding'];

        // CouchDB replication fails unless we use a properly-cased header
        remoteRes.headers['Content-Type'] = remoteRes.headers['content-type'];
        delete remoteRes.headers['content-type'];

        res.writeHead(remoteRes.statusCode, remoteRes.headers);
        remoteRes.pipe(res);
      });

      remoteReq.on('error', function(err) {
        res.json(503, {error: 'db_unavailable', reason: err.syscall + ' ' + err.errno});
      });

      req.setEncoding('utf8');
      req.resume();
      req.pipe(remoteReq);
    });
  });

  return app;
};
<|endoftext|>


<commit_before>/*global JSONPath, getJSON, exports*/
/*jslint vars:true, todo:true*/
(function (undef) {'use strict';

var jsonpath = JSONPath; // Satisfy JSLint

/**
* @private
* @static
*/
function _triggerEqualPriorityError (config) {
    if (config.errorOnEqualPriority) {
        throw "You have configured JSONPathTransformer to throw errors on finding templates of equal priority and these have been found.";
    }
}

/**
* @private
* @static
*/
function _makeAbsolute (select) {
    // See todo in JSONPath to avoid need for '$' (but may still need to add ".")
    return select[0] !== '$' ? '$' + (select[0] === '[' ? '$' : '$.') + select : select;
}

function _getPriorityBySpecificity (path) {
    // Todo: Implement

}

// Todo: utilize
function JSONTransformerEvaluator (config, templates) {
    this._config = config;
    this._templates = templates;
}

JSONTransformerEvaluator.prototype.applyTemplates = function (select, mode) {
    // Todo: adapt this code to only find (and apply) templates per context
    var that = this;
    if (select && typeof select === 'object') {
        mode = select.mode;
        select = select.select;
    }
    if (!this.hasOwnProperty('_contextNode')) {
        this._contextNode = this.config.data;
        this._parent = this.config.parent || null;
        this._parentProperty = this.config.parentProperty || null;
        select = select || '$';
    }
    else {
        select = select || '*';
    }
    select = _makeAbsolute(select);
    var results;
    var found = jsonpath({path: select, json: this._contextNode, wrap: false, returnType: 'all', callback: function (preferredOutput) {
        /*
        // Utilize
        preferredOutput.value;
        that._contextNode;
        that._parent;
        that._parentProperty;
        */

        var matchedTemplates = this.templates.filter(function (template) {
            if (!((mode && mode === template.mode) && (!mode && !template.mode))) {
                return false;
            }
            var matches = jsonpath({path: _makeAbsolute(template.path), json: that._contextNode, resultType: 'path', wrap: true});
            return matches.length && matches.indexOf(preferredOutput.path) > -1;
        });

        if (!matchedTemplates) {
            // Todo: deal with any default templates (by default, should have all defined), including the object and array ones containing this.applyTemplates('*', mode); and this.getDefaultPriority(preferredOutput.path);

            return;
        }

        var matched = matchedTemplates.sort(function (a, b) {

            // Todo: deal with priority, specificity, order

            var aPriority = typeof a.priority === 'number' ? a.priority : _getPriorityBySpecificity(a.path);
            var bPriority = typeof b.priority === 'number' ? b.priority : _getPriorityBySpecificity(a.path);

            if (aPriority === bPriority) {
                _triggerEqualPriorityError(this._config);
            }

            return (aPriority > bPriority) ? -1 : 1; // We want equal conditions to go in favor of the later (b)
        }).some(function (templateObj) {
            var path = templateObj.path;
            var json = that._contextNode;
            var values = jsonpath({json: json, path: path, resultType: 'value', wrap: false, callback: function (parent, property, value, path) {

            }});
            if (values) {
                templateObj.template(values, path, json);
            }
            return true;
        });

        if (!matched) { // Should not get here with default template rules in place
            throw "No template rules matched";
        }

        results.add(matched.shift());
    }});
    if (!found) {

    }
    return results;
};
JSONTransformerEvaluator.prototype.callTemplate = function (name, withParam) {
    var value;
    if (name && typeof name === 'object') {
        withParam = name.withParam;
        name = name.name;
    }
    var template = this.templates.find(function (template) {
        value = withParam.value; // Todo: Support withParam.select (with reference to current context)
        return template.name === name;
    });
    if (!template) {
        throw "Template, " + name + ", cannot be called as it was not found.";
    }
    return template(value); // Todo: provide context
};
JSONTransformerEvaluator.prototype.forEach = function () {

};
JSONTransformerEvaluator.prototype.valueOf = function () {

};

/**
* @param {boolean} config.errorOnEqualPriority
*/
function JSONPathTransformer (config) {
    if (!(this instanceof JSONPathTransformer)) {
        return new JSONPathTransformer(config);
    }
    var that = this;
    var map = {};
    this.config = config;
    this.rootTemplates = [];
    this.templates.forEach(function (template, i, templates) {
        if (template.name && map[template.name]) {
            throw "Templates must all have different names.";
        }
        map[template.name] = true;
        if (template.path === '$') {
            that.rootTemplates.concat(templates.splice(i, 1));
        }
    });
    map = null;
    this.transform();
}
JSONPathTransformer.prototype.getDefaultPriority = function (path) {
    if (typeof path === 'string') {
        path = JSONPath.toPathArray(path);
    }
    // Todo: Path specificity
    // Let's also, unlike XSLT, give higher priority to absolute fixed paths over recursive descent and priority to longer paths and lower to wildcard terminal points

    // -.5 = *, @string() (etc.)
    // -.25 = Namespace (not relevant without Jamilih)
    // 0 = single name (i.e., $..someName or someName if allowing such relative paths)
    // .5 = ., .., [], [()], [(?)]

};

JSONPathTransformer.prototype.defaultRootTemplate = function () {
    return this.applyTemplates();
};

JSONPathTransformer.prototype.transform = function () {
    var jte = new JSONTransformerEvaluator(this.config, this.templates);
    var len = this.rootTemplates.length;
    if (!len) {
        return this.defaultRootTemplate.call(jte);
    }
    if (len === 1) {
        _triggerEqualPriorityError(this.config);
    }
    var templateObj = this.rootTemplates.pop();
    var path = templateObj.path;
    var json = this.config.data;
    return templateObj.template.call(jte, json, path, json);
};

JSONPathTransformer.DefaultTemplateRules = [
    // Todo: Apply default template rules
    /*
    / | *                                : apply-templates          (this.applyTemplates())
    / | * mode                           : apply-templates mode     (this.config.mode)
    text() | @*                          : value-of select="."      (this.valueOf({select: '.'}) or this.valueOf('.'))
    processing-instruction() | comment() : (nothing); optionally allow JSON equivalents or Jamilih
    (an XSLT processor is not to copy any part of the namespace node to the output); optionally allow JSON equivalents or Jamilih
    */
];

/**
* For templates/queries, one may choose among config.query, config.template, or config.templates, but one must be present and of valid type. For the source json, one must use either a valid config.ajaxData or config.data parameter.
* @param {function} config.success A callback supplied with a single argument that is the result of this instance's transform() method.
* @param {array} [config.templates] An array of template objects
* @param {object|function} [config.template] A function assumed to be a root template or a single, complete template object
* @param {function} [config.query] A function assumed to be a root template
* @param {object} [config.data] A JSON object
* @param {string} [config.ajaxData] URL of a JSON file to retrieve for evaluation
* @param {boolean} [config.errorOnEqualPriority=false]
* @param {boolean} [config.autostart=true] Whether to begin transform() immediately.
* @param {string} [config.mode=''] The mode in which to begin the transform.
* @param {function} [config.engine=JSONPathTransformer] Will be based the same config as passed to this instance. Defaults to a transforming function based on JSONPath and with its own set of priorities for processing templates.
* @returns {JTLT} A JTLT instance object
*/
function JTLT (config) {
    if (!(this instanceof JTLT)) {
        return new JTLT(config);
    }

    this.setDefaults(config);
    var that = this;
    if (this.config.ajaxData) {
        getJSON(this.config.ajaxData, function (json) {
            that.config.data = json;
            that._autoStart();
        });
        return this;
    }
    this._autoStart();
}
JTLT.prototype._autoStart = function () {
    if (this.config.autostart !== false || this.ready) {
        this.config.success(this.transform());
    }
};
JTLT.prototype.setDefaults = function (config) {
    var query = config.query || (typeof config.templates === 'function' ? config.templates : (typeof config.template === 'function' ? config.template : null));
    this.templates = query ? [{name: 'root', path: '$', template: query}] : (config.templates || [config.template]);
    this.config.errorOnEqualPriority = config.errorOnEqualPriority || false;
    this.config = config || {};
    this.config.engine = this.config.engine || JSONPathTransformer;
    return this;
};
/**
* @returns {any}
*/
JTLT.prototype.transform = function () {
    if (this.config.data === undef) {
        if (this.config.ajaxData) {
            this.ready = true;
            return;
        }
        throw "You must supply a 'data' or 'ajaxData' property";
    }
    if (typeof this.config.success !== 'function') {
        throw "You must supply a 'success' callback";
    }

    return this.config.engine({templates: this.templates, json: this.config.data});
};

if (typeof exports !== 'undefined') {
    exports.JTLT = JTLT;
    exports.JSONPathTransformer = JSONPathTransformer;
}
else {
    window.JTLT = JTLT;
    window.JSONPathTransformer = JSONPathTransformer;
}

}());<commit_msg>Optimize<commit_after>/*global JSONPath, getJSON, exports*/
/*jslint vars:true, todo:true*/
(function (undef) {'use strict';

var jsonpath = JSONPath; // Satisfy JSLint

/**
* @private
* @static
*/
function _triggerEqualPriorityError (config) {
    if (config.errorOnEqualPriority) {
        throw "You have configured JSONPathTransformer to throw errors on finding templates of equal priority and these have been found.";
    }
}

/**
* @private
* @static
*/
function _makeAbsolute (select) {
    // See todo in JSONPath to avoid need for '$' (but may still need to add ".")
    return select[0] !== '$' ? '$' + (select[0] === '[' ? '$' : '$.') + select : select;
}

function _getPriorityBySpecificity (path) {
    // Todo: Implement

}

// Todo: utilize
function JSONTransformerEvaluator (config, templates) {
    this._config = config;
    this._templates = templates;
}

JSONTransformerEvaluator.prototype.applyTemplates = function (select, mode) {
    // Todo: adapt this code to only find (and apply) templates per context
    var that = this;
    if (select && typeof select === 'object') {
        mode = select.mode;
        select = select.select;
    }
    if (!this.hasOwnProperty('_contextNode')) {
        this._contextNode = this.config.data;
        this._parent = this.config.parent || null;
        this._parentProperty = this.config.parentProperty || null;
        select = select || '$';
    }
    else {
        select = select || '*';
    }
    select = _makeAbsolute(select);
    var results;
    var modeMatchedTemplates = this.templates.filter(function (template) {
        return ((mode && mode === template.mode) && (!mode && !template.mode));
    });
    var found = jsonpath({path: select, json: this._contextNode, wrap: false, returnType: 'all', callback: function (preferredOutput) {
        /*
        // Utilize
        preferredOutput.value;
        that._contextNode;
        that._parent;
        that._parentProperty;
        */

        var pathMatchedTemplates = modeMatchedTemplates.filter(function (template) {
            return jsonpath({path: _makeAbsolute(template.path), json: that._contextNode, resultType: 'path', wrap: true}).includes(preferredOutput.path);
        });

        if (!pathMatchedTemplates) {
            // Todo: deal with any default templates (by default, should have all defined), including the object and array ones containing this.applyTemplates('*', mode); and this.getDefaultPriority(preferredOutput.path);

            return;
        }

        var matched = pathMatchedTemplates.sort(function (a, b) {

            // Todo: deal with priority, specificity, order

            var aPriority = typeof a.priority === 'number' ? a.priority : _getPriorityBySpecificity(a.path);
            var bPriority = typeof b.priority === 'number' ? b.priority : _getPriorityBySpecificity(a.path);

            if (aPriority === bPriority) {
                _triggerEqualPriorityError(this._config);
            }

            return (aPriority > bPriority) ? -1 : 1; // We want equal conditions to go in favor of the later (b)
        }).some(function (templateObj) {
            var path = templateObj.path;
            var json = that._contextNode;
            var values = jsonpath({json: json, path: path, resultType: 'value', wrap: false, callback: function (parent, property, value, path) {

            }});
            if (values) {
                templateObj.template(values, path, json);
            }
            return true;
        });

        if (!matched) { // Should not get here with default template rules in place
            throw "No template rules matched";
        }

        results.add(matched.shift());
    }});
    if (!found) {

    }
    return results;
};
JSONTransformerEvaluator.prototype.callTemplate = function (name, withParam) {
    var value;
    if (name && typeof name === 'object') {
        withParam = name.withParam;
        name = name.name;
    }
    var template = this.templates.find(function (template) {
        value = withParam.value; // Todo: Support withParam.select (with reference to current context)
        return template.name === name;
    });
    if (!template) {
        throw "Template, " + name + ", cannot be called as it was not found.";
    }
    return template(value); // Todo: provide context
};
JSONTransformerEvaluator.prototype.forEach = function () {

};
JSONTransformerEvaluator.prototype.valueOf = function () {

};

/**
* @param {boolean} config.errorOnEqualPriority
*/
function JSONPathTransformer (config) {
    if (!(this instanceof JSONPathTransformer)) {
        return new JSONPathTransformer(config);
    }
    var that = this;
    var map = {};
    this.config = config;
    this.rootTemplates = [];
    this.templates.forEach(function (template, i, templates) {
        if (template.name && map[template.name]) {
            throw "Templates must all have different names.";
        }
        map[template.name] = true;
        if (template.path === '$') {
            that.rootTemplates.concat(templates.splice(i, 1));
        }
    });
    map = null;
    this.transform();
}
JSONPathTransformer.prototype.getDefaultPriority = function (path) {
    if (typeof path === 'string') {
        path = JSONPath.toPathArray(path);
    }
    // Todo: Path specificity
    // Let's also, unlike XSLT, give higher priority to absolute fixed paths over recursive descent and priority to longer paths and lower to wildcard terminal points

    // -.5 = *, @string() (etc.)
    // -.25 = Namespace (not relevant without Jamilih)
    // 0 = single name (i.e., $..someName or someName if allowing such relative paths)
    // .5 = ., .., [], [()], [(?)]

};

JSONPathTransformer.prototype.defaultRootTemplate = function () {
    return this.applyTemplates();
};

JSONPathTransformer.prototype.transform = function () {
    var jte = new JSONTransformerEvaluator(this.config, this.templates);
    var len = this.rootTemplates.length;
    if (!len) {
        return this.defaultRootTemplate.call(jte);
    }
    if (len === 1) {
        _triggerEqualPriorityError(this.config);
    }
    var templateObj = this.rootTemplates.pop();
    var path = templateObj.path;
    var json = this.config.data;
    return templateObj.template.call(jte, json, path, json);
};

JSONPathTransformer.DefaultTemplateRules = [
    // Todo: Apply default template rules
    /*
    / | *                                : apply-templates          (this.applyTemplates())
    / | * mode                           : apply-templates mode     (this.config.mode)
    text() | @*                          : value-of select="."      (this.valueOf({select: '.'}) or this.valueOf('.'))
    processing-instruction() | comment() : (nothing); optionally allow JSON equivalents or Jamilih
    (an XSLT processor is not to copy any part of the namespace node to the output); optionally allow JSON equivalents or Jamilih
    */
];

/**
* For templates/queries, one may choose among config.query, config.template, or config.templates, but one must be present and of valid type. For the source json, one must use either a valid config.ajaxData or config.data parameter.
* @param {function} config.success A callback supplied with a single argument that is the result of this instance's transform() method.
* @param {array} [config.templates] An array of template objects
* @param {object|function} [config.template] A function assumed to be a root template or a single, complete template object
* @param {function} [config.query] A function assumed to be a root template
* @param {object} [config.data] A JSON object
* @param {string} [config.ajaxData] URL of a JSON file to retrieve for evaluation
* @param {boolean} [config.errorOnEqualPriority=false]
* @param {boolean} [config.autostart=true] Whether to begin transform() immediately.
* @param {string} [config.mode=''] The mode in which to begin the transform.
* @param {function} [config.engine=JSONPathTransformer] Will be based the same config as passed to this instance. Defaults to a transforming function based on JSONPath and with its own set of priorities for processing templates.
* @returns {JTLT} A JTLT instance object
*/
function JTLT (config) {
    if (!(this instanceof JTLT)) {
        return new JTLT(config);
    }

    this.setDefaults(config);
    var that = this;
    if (this.config.ajaxData) {
        getJSON(this.config.ajaxData, function (json) {
            that.config.data = json;
            that._autoStart();
        });
        return this;
    }
    this._autoStart();
}
JTLT.prototype._autoStart = function () {
    if (this.config.autostart !== false || this.ready) {
        this.config.success(this.transform());
    }
};
JTLT.prototype.setDefaults = function (config) {
    var query = config.query || (typeof config.templates === 'function' ? config.templates : (typeof config.template === 'function' ? config.template : null));
    this.templates = query ? [{name: 'root', path: '$', template: query}] : (config.templates || [config.template]);
    this.config.errorOnEqualPriority = config.errorOnEqualPriority || false;
    this.config = config || {};
    this.config.engine = this.config.engine || JSONPathTransformer;
    return this;
};
/**
* @returns {any}
*/
JTLT.prototype.transform = function () {
    if (this.config.data === undef) {
        if (this.config.ajaxData) {
            this.ready = true;
            return;
        }
        throw "You must supply a 'data' or 'ajaxData' property";
    }
    if (typeof this.config.success !== 'function') {
        throw "You must supply a 'success' callback";
    }

    return this.config.engine({templates: this.templates, json: this.config.data});
};

if (typeof exports !== 'undefined') {
    exports.JTLT = JTLT;
    exports.JSONPathTransformer = JSONPathTransformer;
}
else {
    window.JTLT = JTLT;
    window.JSONPathTransformer = JSONPathTransformer;
}

}());<|endoftext|>


<commit_before>// virtual_mta
//------------

//TODO: Uncomment this two lines in the real life-----
//var outbound    = require('./outbound');
//var constants = require('haraka-constants');
//------------------------------------------------
var ip          = require('ip').address(); //Main ip of local server
var host        = require('os').hostname().replace(/\\/, '\\057').replace(/:/, '\\072'); //Server hostname
var vmta        = null;
var cfg;

exports.register = function () {
    var plugin = this;

    plugin.load_vmta_ini();

    plugin.register_hook('init_master', 'init_interfaces');
    plugin.register_hook('init_child', 'init_interfaces');
    plugin.register_hook('queue_outbound', 'outbound');
    plugin.register_hook('pre_send_trans_email', 'before_send');
};

exports.load_vmta_ini = function () {
    var plugin = this;

    plugin.loginfo("VMTA configs are fully loaded from 'vmta.ini'.");

    cfg = plugin.config.get("vmta.ini", function () {
        plugin.register();
    });

    plugin.loginfo(cfg);
};

//Define localAddresses at start-up
exports.init_interfaces = function (next)  {
    server.notes.interfaces = localAddresses();
    return next();
};

exports.outbound = function (next, connection) {
    checkVmtaParams(next, this, connection, "outbound");
};

exports.before_send = function (next, connection) {
    if ( !connection.transaction.notes.hasOwnProperty("vmta_checked") )
    {
        checkVmtaParams(next, this, connection);
    }
};

//Deny with passed message
var denyWithMsg = function (context, next, msg) {
    context.logerror(msg);
    context.loginfo("----------- VMTA plugin LOG END -----------");
    context.loginfo("");

    return next(DENY, msg);
};

//Get list of local addresses
var localAddresses = function () {
    var os = require("os");

    var interfaces = os.networkInterfaces();
    var addresses = [];

    for (var k in interfaces)
    {
        for (var k2 in interfaces[k])
        {
            var address = interfaces[k][k2];

            if (address.family === "IPv4" && !address.internal) {
                addresses.push(address.address);
            }
        }
    }

    return addresses;
};

//Check if the 'x-vmta' parameter is passed then retrieve the 'ip/host' else return the default ones
var checkVmtaParams = function (next, plugin, connection, type){
    var transaction = connection.transaction;

    plugin.loginfo("");
    plugin.loginfo("----------- VMTA plugin LOG START -----------");

    //Set the flag param 'vmta_checked' to avoid duplicate check in the both hooks
    connection.transaction.notes.vmta_checked = true;

    if ( transaction.header.headers.hasOwnProperty("x-vmta") )
    {
        //Get 'x-vmta' from the header
        vmta = transaction.header.headers["x-vmta"][0].replace("\n", "");

        //Check if The specified VMTA is defined in the config file
        if ( cfg.hasOwnProperty(vmta) )
        {
            var vmta_entry = cfg[vmta];

            //Check if the VMTA entry from the config file has the property 'ip'
            if ( vmta_entry.hasOwnProperty("ip") )
            {
                //Check if the specified ip belong to the server network interfaces
                if ( server.notes.interfaces.indexOf(vmta_entry.ip) > -1 ) {
                    connection.transaction.notes.outbound_ip = vmta_entry.ip;
                } else {
                    return denyWithMsg(plugin, next, "Please correct any configuration file errors (The specified 'ip' doesn't belong to this server).");
                }
            } else {
                return denyWithMsg(plugin, next, "Please correct any configuration file errors (The 'ip' property does not exist).");
            }

            //Check if the VMTA entry from the config file has the property 'host'
            if ( vmta_entry.hasOwnProperty("host") ) {
                connection.transaction.notes.outbound_helo = vmta_entry.host;
            } else {
                return denyWithMsg(plugin, next, "Please correct any configuration file errors (The 'host' property does not exist).");
            }

            //Remove parameter from the header
            transaction.remove_header("x-vmta");

            plugin.loginfo("'x-vmta' Found '"+vmta+"'");
        } else {
            return denyWithMsg(plugin, next, "The specified Virtual VMTA '"+vmta+"' does not exist.");
        }
    } else {
        connection.transaction.notes.outbound_ip   = ip;
        connection.transaction.notes.outbound_helo = host;

        plugin.loginfo("No 'x-vmta' Found.");
    }

    plugin.loginfo("Outbound IP : "+connection.transaction.notes.outbound_ip);
    plugin.loginfo("Outbound HOST : "+connection.transaction.notes.outbound_helo);

    //Setting the header to notes before sent, we may need it in 'delivered/bounce/deferred' hooks
    connection.transaction.notes.header = connection.transaction.header;

    if( type == 'outbound' ) {
        outbound.send_email(connection.transaction, next);

        this.loginfo("----------- VMTA plugin LOG END -----------");
        this.loginfo("");
    } else {
        this.loginfo("----------- VMTA plugin LOG END -----------");
        this.loginfo("");

        return next();
    }

};<commit_msg>Replacing the "this" by "plugin"<commit_after>// virtual_mta
//------------

//TODO: Uncomment this two lines in the real life-----
//var outbound    = require('./outbound');
//var constants = require('haraka-constants');
//------------------------------------------------
var ip          = require('ip').address(); //Main ip of local server
var host        = require('os').hostname().replace(/\\/, '\\057').replace(/:/, '\\072'); //Server hostname
var vmta        = null;
var cfg;

exports.register = function () {
    var plugin = this;

    plugin.load_vmta_ini();

    plugin.register_hook('init_master', 'init_interfaces');
    plugin.register_hook('init_child', 'init_interfaces');
    plugin.register_hook('queue_outbound', 'outbound');
    plugin.register_hook('pre_send_trans_email', 'before_send');
};

exports.load_vmta_ini = function () {
    var plugin = this;

    plugin.loginfo("VMTA configs are fully loaded from 'vmta.ini'.");

    cfg = plugin.config.get("vmta.ini", function () {
        plugin.register();
    });

    plugin.loginfo(cfg);
};

//Define localAddresses at start-up
exports.init_interfaces = function (next)  {
    server.notes.interfaces = localAddresses();
    return next();
};

exports.outbound = function (next, connection) {
    checkVmtaParams(next, this, connection, "outbound");
};

exports.before_send = function (next, connection) {
    if ( !connection.transaction.notes.hasOwnProperty("vmta_checked") )
    {
        checkVmtaParams(next, this, connection);
    }
};

//Deny with passed message
var denyWithMsg = function (context, next, msg) {
    context.logerror(msg);
    context.loginfo("----------- VMTA plugin LOG END -----------");
    context.loginfo("");

    return next(DENY, msg);
};

//Get list of local addresses
var localAddresses = function () {
    var os = require("os");

    var interfaces = os.networkInterfaces();
    var addresses = [];

    for (var k in interfaces)
    {
        for (var k2 in interfaces[k])
        {
            var address = interfaces[k][k2];

            if (address.family === "IPv4" && !address.internal) {
                addresses.push(address.address);
            }
        }
    }

    return addresses;
};

//Check if the 'x-vmta' parameter is passed then retrieve the 'ip/host' else return the default ones
var checkVmtaParams = function (next, plugin, connection, type){
    var transaction = connection.transaction;

    plugin.loginfo("");
    plugin.loginfo("----------- VMTA plugin LOG START -----------");

    //Set the flag param 'vmta_checked' to avoid duplicate check in the both hooks
    connection.transaction.notes.vmta_checked = true;

    if ( transaction.header.headers.hasOwnProperty("x-vmta") )
    {
        //Get 'x-vmta' from the header
        vmta = transaction.header.headers["x-vmta"][0].replace("\n", "");

        //Check if The specified VMTA is defined in the config file
        if ( cfg.hasOwnProperty(vmta) )
        {
            var vmta_entry = cfg[vmta];

            //Check if the VMTA entry from the config file has the property 'ip'
            if ( vmta_entry.hasOwnProperty("ip") )
            {
                //Check if the specified ip belong to the server network interfaces
                if ( server.notes.interfaces.indexOf(vmta_entry.ip) > -1 ) {
                    connection.transaction.notes.outbound_ip = vmta_entry.ip;
                } else {
                    return denyWithMsg(plugin, next, "Please correct any configuration file errors (The specified 'ip' doesn't belong to this server).");
                }
            } else {
                return denyWithMsg(plugin, next, "Please correct any configuration file errors (The 'ip' property does not exist).");
            }

            //Check if the VMTA entry from the config file has the property 'host'
            if ( vmta_entry.hasOwnProperty("host") ) {
                connection.transaction.notes.outbound_helo = vmta_entry.host;
            } else {
                return denyWithMsg(plugin, next, "Please correct any configuration file errors (The 'host' property does not exist).");
            }

            //Remove parameter from the header
            transaction.remove_header("x-vmta");

            plugin.loginfo("'x-vmta' Found '"+vmta+"'");
        } else {
            return denyWithMsg(plugin, next, "The specified Virtual VMTA '"+vmta+"' does not exist.");
        }
    } else {
        connection.transaction.notes.outbound_ip   = ip;
        connection.transaction.notes.outbound_helo = host;

        plugin.loginfo("No 'x-vmta' Found.");
    }

    plugin.loginfo("Outbound IP : "+connection.transaction.notes.outbound_ip);
    plugin.loginfo("Outbound HOST : "+connection.transaction.notes.outbound_helo);

    //Setting the header to notes before sent, we may need it in 'delivered/bounce/deferred' hooks
    connection.transaction.notes.header = connection.transaction.header;

    if ( type == 'outbound' ) {
        outbound.send_email(connection.transaction, next);

        plugin.loginfo("----------- VMTA plugin LOG END -----------");
        plugin.loginfo("");
    } else {
        plugin.loginfo("----------- VMTA plugin LOG END -----------");
        plugin.loginfo("");

        return next();
    }

};<|endoftext|>