Overview
Turnilo lets you extend its behaviour in three ways:
- Request decorator for all Druid queries sent to Druid cluster
- Query decorator for all Plywood queries sent to Druid cluster
- Plugins for backend application
turniloMetadata
Turnilo adds turniloMetadata
object to every Request
object. It is a namespace for you to keep any values necessary that live with requests.
turniloMetadata
has special properties:
loggerContext
It is a Record
of values that will be added by logger for every message.
You could use it to log User Agent of browser that requested view with a simple plugin:
app.use(function(req, res, next) {
req.turniloMetadata.loggerContext.userAgent = req.get('User-Agent');
next();
});
Request decorator
In the cluster config add a key druidRequestDecorator
with property path
that points to a relative js file.
druidRequestDecorator:
path: './druid-request-decorator.js'
You can also pass parameters to your decorator using options
field. Content of this field will be read as json and passed to your druidRequestDecoratorFactory
under options
key in second parameter.
druidRequestDecorator:
path: './druid-request-decorator.js'
options:
base: Pancakes
extras:
- maple-syrup
- blueberries
The contract is that your module should export a function druidRequestDecoratorFactory
that has to return a decorator.
A decorator is a function that gets called on every request. It receives a Druid query and may return an object with the key headers
where you can set whatever headers you want.
Here is an example decorator:
exports.version = 1;
exports.druidRequestDecoratorFactory = function (logger, params) {
const options = params.options;
const extras = options.extras.join(", ");
const like = `${options.base} with ${extras}`;
return function () {
return {
headers: {
"X-I-Like": like
},
};
};
};
You can find this example with additional comments and example config in the example folder.
Please note that your object will be merged with Cluster Authorization headers.
Query decorator
In the data cube config add queryDecorator
field with key path
pointing to javascript file. This file should export function named decorator
. This function will be called before every Plywood query is sent to Druid from Turnilo. Function is called with four arguments:
- Plywood query
- Request object
- Decorator options
- Plywood library instance
Decorator function should return valid Plywood expression.
exports.decorator = function (expression, request, options, plywood) {
const userId = request.headers["x-user-id"]; // get userId from header, you need to set this value before Turnilo
const userColumnName = options.userColumnName; // get value from options, defined in config
const filterClause = plywood.$(userColumnName).in([userId]); // show only rows where `userColumnName` is equal to current user id.
return expression.substitute(e => {
if (e instanceof plywood.RefExpression && e.name === "main") { // filter all main expression references
return e.filter(filterClause);
}
return null;
});
}
And needed configuration:
...
dataCubes:
- name: cube
queryDecorator:
path: ./decorator.js
options:
userColumnName: "user_id"
Plugins
Most powerful way to extend turnilo are plugins. They are defined at top level in config and apply for whole Turnilo application. You need to add your plugin as entry under plugins
field. Plugin need to have two fields: - name
- name for debug purposes - path
- path to the js file It can define additional field settings
. Content of this field would be passed to plugin, so it is good place for additional parameters.
plugins:
- name: example_plugin
path: ./plugin.js
settings:
favourite_number: 42
Plugin file need to export function named plugin
. This function will be called at the start of application with following parameters:
app
- Express instance of Turnilo application. Remember that Turnilo routes are called after your plugins so be careful.pluginSettings
- object fromsettings
field in configurationserverSettings
- object representing server settings, like port, host, ready- and live- endpoints.appSettings
- function that returns promise with application settings - definitions of clusters, data sources and customization.logger
- logger object that you can use to log anything
Worth to look into !(express documentation)[https://expressjs.com/en/api.html#app]. Use get
, post
etc. to define new endpoints. Use use
to define middleware.
Use turniloMetadata
object on Request
to pass values between your plugins and not pollute headers.