Skip to content

Commit

Permalink
[KZL-291] pipes & hooks: wildcarded event (#1305)
Browse files Browse the repository at this point in the history
pipes & hooks can now trigger wildcarded events like this:
'foo:*'
'foo:after*'
'foo:before*'

'foo:error*'
  • Loading branch information
thomasarbona authored and Aschen committed May 27, 2019
1 parent db14d78 commit 3d06476
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 50 deletions.
63 changes: 41 additions & 22 deletions lib/api/core/plugins/pluginsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,35 @@ class PluginsManager {
}
}

/**
* For a specific event, return the correspondings wildcards
* @example
* getWildcardEvents('data:create') // return ['data:*']
* getWildcardEvents('data:beforeCreate') // return ['data:*', 'data:before*']
* @param {String} event
* @returns {Array<String>} wildcard events
*/
const getWildcardEvents = _.memoize(event => {
const delimIndex = event.lastIndexOf(':');

if (delimIndex === -1) {
return [];
}

const scope = event.slice(0, delimIndex);
const name = event.slice(delimIndex + 1);
const wildcardEvents = ['*'];

['before', 'after', 'error'].forEach(prefix => {
if (!name.startsWith(prefix)) {
return;
}
wildcardEvents.push(`${prefix}*`);
});

return wildcardEvents.map(e => `${scope}:${e}`);
});

/**
* Emit event
*
Expand All @@ -831,8 +860,12 @@ class PluginsManager {
* @returns {Promise}
*/
function triggerHooks(emitter, event, data) {
const wildcardEvents = getWildcardEvents(event);

emitter.emit(event, data);

wildcardEvents.forEach(wildcardEvent => emitter.emit(wildcardEvent, data));

return Bluebird.resolve(data);
}

Expand All @@ -845,16 +878,18 @@ function triggerHooks(emitter, event, data) {
* @returns {Promise}
*/
function triggerPipes(pipes, event, data) {
let preparedPipes = [];
const wildcardEvent = getWildcardEvent(event);
const preparedPipes = [];
const wildcardEvents = getWildcardEvents(event);

if (pipes && pipes[event] && pipes[event].length) {
preparedPipes = pipes[event];
preparedPipes.push(...pipes[event]);
}

if (wildcardEvent && pipes && pipes[wildcardEvent] && pipes[wildcardEvent].length) {
preparedPipes = preparedPipes.concat(pipes[wildcardEvent]);
}
wildcardEvents.forEach(wildcardEvent => {
if (pipes[wildcardEvent] && pipes[wildcardEvent].length) {
preparedPipes.push(...pipes[wildcardEvent]);
}
});

if (preparedPipes.length === 0) {
return Bluebird.resolve(data);
Expand All @@ -875,22 +910,6 @@ function triggerPipes(pipes, event, data) {
});
}

/**
* For a specific event, return the corresponding wildcard
* @example
* getWildcardEvent('data:create') // return 'data:*'
* @param {string} event
* @returns {String} wildcard event
*/
function getWildcardEvent (event) {
const indexDelimiter = event.indexOf(':');
if (indexDelimiter !== 1) {
return event.substring(0, indexDelimiter+1) + '*';
}

return null;
}

/**
* Test if the provided argument is a constructor or not
*
Expand Down
9 changes: 2 additions & 7 deletions lib/api/kuzzle.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
'use strict';

const
EventEmitter = require('eventemitter2').EventEmitter2,
EventEmitter = require('eventemitter3'),
config = require('../config'),
path = require('path'),
murmur = require('murmurhash-native').murmurHash128,
Expand Down Expand Up @@ -55,12 +55,7 @@ const
*/
class Kuzzle extends EventEmitter {
constructor() {
super({
verboseMemoryLeak: true,
wildcard: true,
maxListeners: 30,
delimiter: ':'
});
super();

/** @type {KuzzleConfiguration} */
this.config = config;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"dumpme": "^1.0.2",
"easy-circular-list": "^1.0.13",
"elasticsearch": "^15.4.1",
"eventemitter2": "^5.0.1",
"eventemitter3": "^3.1.2",
"fs-extra": "^7.0.1",
"glob": "^7.1.3",
"ioredis": "^4.9.0",
Expand Down
24 changes: 8 additions & 16 deletions test/api/core/pluginsManager/run.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ describe('PluginsManager.run', () => {
pluginMock.expects('bar').never();

return pluginsManager.run()
.then(() => {
kuzzle.emit('foo:bar');
pluginMock.verify();
});
.then(() => pluginsManager.trigger('foo:bar'))
.then(() => pluginMock.verify());
});

it('should attach event hook with function', () => {
Expand All @@ -78,9 +76,8 @@ describe('PluginsManager.run', () => {
};

return pluginsManager.run()
.then(() => pluginsManager.trigger('foo:bar'))
.then(() => {
kuzzle.emit('foo:bar');

should(bar).be.calledOnce();
should(foo).not.be.called();
});
Expand All @@ -101,10 +98,8 @@ describe('PluginsManager.run', () => {
pluginMock.expects('baz').never();

return pluginsManager.run()
.then(() => {
kuzzle.emit('foo:bar');
pluginMock.verify();
});
.then(() => pluginsManager.trigger('foo:bar'))
.then(() => pluginMock.verify());
});

it('should attach multi-target hook with function', () => {
Expand All @@ -119,9 +114,8 @@ describe('PluginsManager.run', () => {
};

return pluginsManager.run()
.then(() => pluginsManager.trigger('foo:bar'))
.then(() => {
kuzzle.emit('foo:bar');

should(bar).be.calledOnce();
should(foo).be.calledOnce();
should(baz).not.be.called();
Expand All @@ -142,10 +136,8 @@ describe('PluginsManager.run', () => {
pluginMock.expects('bar').never();

return pluginsManager.run()
.then(() => {
kuzzle.emit('foo:bar');
pluginMock.verify();
});
.then(() => pluginsManager.trigger('foo:bar'))
.then(() => pluginMock.verify());
});

it('should throw if a hook target is not a function and not a method name', () => {
Expand Down
117 changes: 116 additions & 1 deletion test/api/core/pluginsManager/trigger.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const
mockrequire = require('mock-require'),
should = require('should'),
sinon = require('sinon'),
ElasticsearchClientMock = require('../../../mocks/services/elasticsearchClient.mock'),
KuzzleMock = require('../../../mocks/kuzzle.mock'),
{
errors: {
PluginImplementationError
}
},
Request: KuzzleRequest
} = require('kuzzle-common-objects');

describe('Test plugins manager trigger', () => {
Expand Down Expand Up @@ -44,6 +46,119 @@ describe('Test plugins manager trigger', () => {
});
});

it('should trigger hooks with before wildcard event', done => {
pluginsManager.plugins = [{
object: {
init: () => {},
hooks: {
'foo:before*': 'myFunc'
},
myFunc: done
},
config: {},
activated: true,
manifest: {
name: 'foo'
}
}];

pluginsManager.run()
.then(() => {
pluginsManager.trigger('foo:beforeBar');
});
});

it('should trigger hooks with after wildcard event', done => {
pluginsManager.plugins = [{
object: {
init: () => {},
hooks: {
'foo:after*': 'myFunc'
},
myFunc: done
},
config: {},
activated: true,
manifest: {
name: 'foo'
}
}];

pluginsManager.run()
.then(() => {
pluginsManager.trigger('foo:afterBar');
});
});

it('should trigger pipes with wildcard event', () => {
pluginsManager.plugins = [{
object: {
init: () => {},
pipes: {
'foo:*': 'myFunc'
},
myFunc: sinon.stub().callsArgWith(1, null, new KuzzleRequest({}))
},
config: {},
activated: true,
manifest: {
name: 'foo'
}
}];

return pluginsManager.run()
.then(() => pluginsManager.trigger('foo:bar'))
.then(() => {
should(pluginsManager.plugins[0].object.myFunc).be.calledOnce();
});
});

it('should trigger pipes with before wildcard event', () => {
pluginsManager.plugins = [{
object: {
init: () => {},
pipes: {
'foo:before*': 'myFunc'
},
myFunc: sinon.stub().callsArgWith(1, null, new KuzzleRequest({}))
},
config: {},
activated: true,
manifest: {
name: 'foo'
}
}];

return pluginsManager.run()
.then(() => pluginsManager.trigger('foo:beforeBar'))
.then(() => {
should(pluginsManager.plugins[0].object.myFunc).be.calledOnce();
});
});

it('should trigger pipes with after wildcard event', () => {
pluginsManager.plugins = [{
object: {
init: () => {},
pipes: {
'foo:after*': 'myFunc'
},
myFunc: sinon.stub().callsArgWith(1, null, new KuzzleRequest({}))
},
config: {},
activated: true,
manifest: {
name: 'foo'
}
}];

return pluginsManager.run()
.then(() => pluginsManager.trigger('foo:afterBar'))
.then(() => {
should(pluginsManager.plugins[0].object.myFunc).be.calledOnce();
});
});

it('should reject a pipe returned value if it is not a Request instance', () => {
const pluginMock = {
object: {
Expand Down
6 changes: 3 additions & 3 deletions test/mocks/services/redisClient.mock.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const
EventEmitter = require('eventemitter2').EventEmitter2,
EventEmitter = require('eventemitter3'),
IORedis = require('ioredis'),
getBuiltinCommands = (new IORedis({lazyConnect: true})).getBuiltinCommands,
Bluebird = require('bluebird');
Expand All @@ -10,7 +10,7 @@ const
*/
class RedisClientMock extends EventEmitter {
constructor (err) {
super({verboseMemoryLeak: true});
super();

this.getBuiltinCommands = getBuiltinCommands;

Expand Down Expand Up @@ -46,7 +46,7 @@ class RedisClientMock extends EventEmitter {
this.emit('end', true);
}, 50);
};
Stream.prototype = new EventEmitter({verboseMemoryLeak: true});
Stream.prototype = new EventEmitter();

return new Stream();
}
Expand Down

0 comments on commit 3d06476

Please sign in to comment.