diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4e2648 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Kibana Extended Metric Plugin + +This is a plugin for [Kibana 5.0.0+](https://www.elastic.co/products/kibana). +It is based on the core Metric-Plugin but gives you the ability to output custom aggregates on metric-results by using custom formula and/or JavaScript. + +![image](img/demo.gif) + +## Installation + +```sh +$ ./bin/kibana-plugin install https://github.com/ommsolutions/kibana_ext_metrics_vis/archive/0.1.0.zip +``` + +### Manual + +Extract the ZIP into a new folder in your `kibana/plugins`-directory. + +## Uninstall + +Simply delete that folder and restart kibana. \ No newline at end of file diff --git a/img/demo.gif b/img/demo.gif new file mode 100644 index 0000000..4331074 Binary files /dev/null and b/img/demo.gif differ diff --git a/index.js b/index.js new file mode 100644 index 0000000..993829a --- /dev/null +++ b/index.js @@ -0,0 +1,17 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +exports['default'] = function (kibana) { + return new kibana.Plugin({ + uiExports: { + visTypes: ['plugins/extended_metric_vis/extended_metric_vis'] + } + + }); +}; + +; +module.exports = exports['default']; diff --git a/package.json b/package.json new file mode 100644 index 0000000..1667d17 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "extended_metric_vis", + "version": "kibana", + "author": "Olaf Horstmann ", + "website": "http://www.omm-solutions.de" +} diff --git a/public/extended_metric_vis.html b/public/extended_metric_vis.html new file mode 100644 index 0000000..995338c --- /dev/null +++ b/public/extended_metric_vis.html @@ -0,0 +1,6 @@ +
+
+
{{output.value}}
+
{{output.label}}
+
+
diff --git a/public/extended_metric_vis.js b/public/extended_metric_vis.js new file mode 100644 index 0000000..3fa8bda --- /dev/null +++ b/public/extended_metric_vis.js @@ -0,0 +1,56 @@ +import 'plugins/extended_metric_vis/extended_metric_vis.less'; +import 'plugins/extended_metric_vis/extended_metric_vis_controller'; +import TemplateVisTypeTemplateVisTypeProvider from 'ui/template_vis_type/template_vis_type'; +import VisSchemasProvider from 'ui/vis/schemas'; +import extendedMetricVisTemplate from 'plugins/extended_metric_vis/extended_metric_vis.html'; +import metricVisParamsTemplate from 'plugins/extended_metric_vis/extended_metric_vis_params.html'; +// we need to load the css ourselves + +// we also need to load the controller and used by the template + +// register the provider with the visTypes registry +require('ui/registry/vis_types').register(ExtendedMetricVisProvider); + +function ExtendedMetricVisProvider(Private) { + const TemplateVisType = Private(TemplateVisTypeTemplateVisTypeProvider); + const Schemas = Private(VisSchemasProvider); + + // return the visType object, which kibana will use to display and configure new + // Vis object of this type. + return new TemplateVisType({ + name: 'extended_metric', + title: 'Extended Metric', + description: 'Based on the core Metric-Plugin but gives you the ability' + + 'to output custom aggregates on metric-results.', + icon: 'fa-calculator', + template: extendedMetricVisTemplate, + params: { + defaults: { + handleNoResults: true, + fontSize: 60, + outputs: [ + { + formula: 'metrics[0].value * metrics[0].value', + label: 'Count squared', + enabled: true + } + ] + }, + editor: metricVisParamsTemplate + }, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Metric', + min: 1, + defaults: [ + { type: 'count', schema: 'metric' } + ] + } + ]) + }); +} + +// export the provider so that the visType can be required with Private() +export default ExtendedMetricVisProvider; diff --git a/public/extended_metric_vis.less b/public/extended_metric_vis.less new file mode 100644 index 0000000..46c245d --- /dev/null +++ b/public/extended_metric_vis.less @@ -0,0 +1,35 @@ +@import (reference) "~ui/styles/mixins.less"; + +.output-vis { + width: 100%; + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: space-around; + align-items: center; + + .output-value { + font-weight: bold; + .ellipsis(); + } + + .output-container { + text-align: center; + } +} + +.vis-editor-agg-header.output { + .vis-editor-agg-header-title { + -webkit-box-flex: 1; + -webkit-flex: 1 1 auto; + -ms-flex: 1 1 auto; + flex: 1 0 auto; + padding-right: 5px; + } + + .vis-editor-agg-header-description { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} \ No newline at end of file diff --git a/public/extended_metric_vis_controller.js b/public/extended_metric_vis_controller.js new file mode 100644 index 0000000..38ec084 --- /dev/null +++ b/public/extended_metric_vis_controller.js @@ -0,0 +1,86 @@ +import _ from 'lodash'; +import AggResponseTabifyTabifyProvider from 'ui/agg_response/tabify/tabify'; +import uiModules from 'ui/modules'; +const module = uiModules.get('kibana/extended_metric_vis', ['kibana']); + +module.controller('KbnExtendedMetricVisController', function ($scope, Private) { + const tabifyAggResponse = Private(AggResponseTabifyTabifyProvider); + const metrics = $scope.metrics = []; + const calcOutputs = $scope.calcOutputs = []; + + function isInvalid(val) { + return _.isUndefined(val) || _.isNull(val) || _.isNaN(val); + } + + function updateOutputs() { + $scope.vis.params.outputs.forEach(function (output) { + try { + const func = Function("metrics", "return " + output.formula); + output.value = func(metrics) || "?"; + } catch (e) { + output.value = '?'; + } + }); + } + + $scope.processTableGroups = function (tableGroups) { + tableGroups.tables.forEach(function (table) { + table.columns.forEach(function (column, i) { + const fieldFormatter = table.aggConfig(column).fieldFormatter(); + let value = table.rows[0][i]; + let formattedValue = isInvalid(value) ? '?' : fieldFormatter(value); + + const metric = { + label: column.title, + value: value, + formattedValue: formattedValue + }; + metrics.push(metric); + metrics[column.title] = metric; + }); + }); + + updateOutputs(); + }; + + // watches + $scope.$watch('esResponse', function (resp) { + if (resp) { + calcOutputs.length = 0; + metrics.length = 0; + for (let key in metrics) { + if (metrics.hasOwnProperty(key)) { + delete metrics[key]; + } + } + $scope.processTableGroups(tabifyAggResponse($scope.vis, resp)); + } + }); + + $scope.$watchCollection('vis.params.outputs', updateOutputs); +}); + +module.controller('ExtendedMetricEditorController', function ($scope) { + // Output Related Methods: + $scope.addOutput = function (outputs) { + outputs.push({ + formula: 'metrics[0].value * metrics[0].value', + label: 'Count squared', + enabled: true + }); + }; + + $scope.removeOuput = function (output, outputs) { + if (outputs.length === 1) { + return; + } + const index = outputs.indexOf(output); + if (index >= 0) { + outputs.splice(index, 1); + } + + if (outputs.length === 1) { + outputs[0].enabled = true; + } + }; +}); diff --git a/public/extended_metric_vis_params.html b/public/extended_metric_vis_params.html new file mode 100644 index 0000000..ab2a19c --- /dev/null +++ b/public/extended_metric_vis_params.html @@ -0,0 +1,86 @@ +
+
+
+
+ + + + + + Output + + + + + {{output.label || output.formula}} + + + +
+ + + + + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+
+
+
+
+ Add output +
+
+
+
+
+
+ + +
\ No newline at end of file