From d917e8018f7d97e03d48a007a65d30852f6c76ef Mon Sep 17 00:00:00 2001 From: Ran Magen Date: Tue, 18 Jun 2019 10:22:18 -0700 Subject: [PATCH] feat: add option and functionality that allows a user to hook into feature mutations (#457) * Add option and functionality that allows a user to hook into feature mutations. * Fix function argument to include the entire event. --- docs/getting-started.md | 1 + lib/event-hook.js | 23 +++++++++++++++++++++++ lib/event-hook.test.js | 32 ++++++++++++++++++++++++++++++++ lib/server-impl.js | 5 +++++ 4 files changed, 61 insertions(+) create mode 100644 lib/event-hook.js create mode 100644 lib/event-hook.test.js diff --git a/docs/getting-started.md b/docs/getting-started.md index c0a840b095..6eb8c7bddb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -59,6 +59,7 @@ Available unleash options include: - `custom` - use this when you implement your own custom authentication logic. - **ui** (object) - Set of UI specific overrides. You may set the following keys: `headerBackground`, `environment`, `slogan`. - **getLogger** (function) - Used to register a [custom log provider](#How do I configure the log output). +- **eventHook** (`function(event, data)`) - If provided, this function will be invoked whenever a feature is mutated. The possible values for `event` are `'feature-created'`, `'feature-updated'`, `'feature-archived'`, `'feature-revived'`. The `data` argument contains information about the mutation. Its fields are `type` (string) - the event type (same as `event`); `createdBy` (string) - the user who performed the mutation; `data` - the contents of the change. The contents in `data` differs based on the event type; For `'feature-archived'` and `'feature-revived'`, the only field will be `name` - the name of the feature. For `'feature-created'` and `'feature-updated'` the data follows a schema defined in the code [here](https://github.com/Unleash/unleash/blob/master/lib/routes/admin-api/feature-schema.js#L38-L59). ### 3. Docker diff --git a/lib/event-hook.js b/lib/event-hook.js new file mode 100644 index 0000000000..c5e8ec8c20 --- /dev/null +++ b/lib/event-hook.js @@ -0,0 +1,23 @@ +'use strict'; + +const { + FEATURE_CREATED, + FEATURE_UPDATED, + FEATURE_ARCHIVED, + FEATURE_REVIVED, +} = require('./event-type'); + +exports.addEventHook = (eventHook, eventStore) => { + eventStore.on(FEATURE_CREATED, data => { + eventHook(FEATURE_CREATED, data); + }); + eventStore.on(FEATURE_UPDATED, data => { + eventHook(FEATURE_UPDATED, data); + }); + eventStore.on(FEATURE_ARCHIVED, data => { + eventHook(FEATURE_ARCHIVED, data); + }); + eventStore.on(FEATURE_REVIVED, data => { + eventHook(FEATURE_REVIVED, data); + }); +}; diff --git a/lib/event-hook.test.js b/lib/event-hook.test.js new file mode 100644 index 0000000000..ed75d43fbd --- /dev/null +++ b/lib/event-hook.test.js @@ -0,0 +1,32 @@ +'use strict'; + +const test = require('ava'); +const { EventEmitter } = require('events'); +const eventStore = new EventEmitter(); +const { addEventHook } = require('./event-hook'); +const { + FEATURE_CREATED, + FEATURE_UPDATED, + FEATURE_ARCHIVED, + FEATURE_REVIVED, +} = require('./event-type'); + +const o = {}; + +function testHook(feature, data) { + o[feature] = data; +} + +test.before(() => { + addEventHook(testHook, eventStore); +}); + +[FEATURE_CREATED, FEATURE_UPDATED, FEATURE_ARCHIVED, FEATURE_REVIVED].forEach( + feature => { + test(`should invoke hook on ${feature}`, t => { + const data = { dataKey: feature }; + eventStore.emit(feature, data); + t.true(o[feature] === data); + }); + } +); diff --git a/lib/server-impl.js b/lib/server-impl.js index 8f0d078d1d..18c40681bb 100644 --- a/lib/server-impl.js +++ b/lib/server-impl.js @@ -11,6 +11,7 @@ const { createOptions } = require('./options'); const StateService = require('./state-service'); const User = require('./user'); const AuthenticationRequired = require('./authentication-required'); +const { addEventHook } = require('./event-hook'); async function createApp(options) { // Database dependencies (stateful) @@ -35,6 +36,10 @@ async function createApp(options) { stores.clientMetricsStore ); + if (typeof config.eventHook === 'function') { + addEventHook(config.eventHook, stores.eventStore); + } + const stateService = new StateService(config); config.stateService = stateService; if (config.importFile) {