Skip to content

Commit

Permalink
Merge pull request #21 from outbrain/v7.4.0
Browse files Browse the repository at this point in the history
Adding support for 7.4.0
  • Loading branch information
roybass authored Oct 27, 2019
2 parents 9e2376b + 39f76ce commit b19c20a
Show file tree
Hide file tree
Showing 8 changed files with 5,536 additions and 48 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Kibana Visualization plugin for displaying a funnel
Based on D3 Funnel lib - https://github.com/jakezatecky/d3-funnel

### Installation
Run `bin/kibana-plugin install https://github.com/outbrain/ob-kb-funnel/releases/download/v5.1.1/ob-kb-funnel.zip`
Run `bin/kibana-plugin install https://github.com/outbrain/ob-kb-funnel/releases/download/v7.4.0/ob-kb-funnel.zip`

Inside the plugin dir run `npm install`
Inside the plugin dir run `yarn kbn bootstrap`

### Usage
Once installed, you'll see an additional type of visualization, named "Funnel View". The funnel is created from the buckets of the aggregation or from metrics. Advanced D3-funnel options can be set in the Options tab, along with other funnel metrics.
Expand Down
Binary file added build/ob-kb-funnel-7.4.0.zip
Binary file not shown.
24 changes: 18 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
export default kibana => new kibana.Plugin({
id: 'ob-kb-funnel',
uiExports: {
visTypes: ['plugins/ob-kb-funnel/ob-kb-funnel'],
},
});

export default function (kibana) {
return new kibana.Plugin({
require: ['elasticsearch'],
name: 'ob-kb-funnel',
uiExports: {
visTypes: ['plugins/ob-kb-funnel/ob-kb-funnel']
},

config(Joi) {
return Joi.object({
enabled: Joi.boolean().default(true),
}).default();
},
});

}

35 changes: 32 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,40 @@
{
"name": "ob-kb-funnel",
"version": "6.0.0",
"version": "7.4.0",
"description": "description",
"main": "index.js",
"kibana": {
"version": "kibana"
"version": "7.4.0",
"templateVersion": "1.0.0"
},
"devDependencies": {
"scripts": {
"preinstall": "node ../../preinstall_check",
"kbn": "node ../../scripts/kbn",
"es": "node ../../scripts/es",
"lint": "eslint .",
"start": "plugin-helpers start",
"test:server": "plugin-helpers test:server",
"test:browser": "plugin-helpers test:browser",
"build": "plugin-helpers build"
},
"dependencies": {
"numeral": "1.5.3",
"d3-funnel": "^1.2.0"
},
"devDependencies": {
"@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana",
"@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana",
"@kbn/expect": "link:../../packages/kbn-expect",
"@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jest": "^22.4.1",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-mocha": "^5.3.0",
"eslint-plugin-no-unsanitized": "^3.0.2",
"eslint-plugin-prefer-object-spread": "^1.2.1",
"eslint-plugin-react": "^7.12.4"
}
}
122 changes: 91 additions & 31 deletions public/funnel_visualization.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { merge } from 'lodash';
import { TabifyResponseHandlerProvider } from 'ui/vis/response_handlers/tabify';
import { FilterManagerProvider } from 'ui/filter_manager';
import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
import { getFilterGenerator } from 'ui/filter_manager';
import { Notifier } from 'ui/notify';

import numeral from 'numeral';
import D3Funnel from 'd3-funnel';

export const FunnelVisualizationProvider = (Private) => {
const filterManager = Private(FilterManagerProvider);
const responseHandler = Private(TabifyResponseHandlerProvider).handler;
const notify = new Notifier({ location: 'Funnel' });
const queryFilter = Private(FilterBarQueryFilterProvider);
const filterGen = getFilterGenerator(queryFilter);
// const notify = new Notifier({ location: 'Funnel' });

return class FunnelVisualization {
containerClassName = 'funnelview';
Expand All @@ -25,27 +25,26 @@ export const FunnelVisualizationProvider = (Private) => {
this.processedData = {};

this.chart = new D3Funnel(this.container);
this.filterManager = filterManager;
// this.filterManager = filterManager;
}

async render(esResponse) {
async render(response) {
console.log("Renering ", response);
if (!this.container) return;
this.chart.destroy();
this.container.innerHTML = '';

let visData = {};
try {
visData = await responseHandler(this.vis, esResponse);
} catch (e) {
console.error(e);
if (!(Array.isArray(response.rows) && Array.isArray(response.columns)) ||
this.el.clientWidth === 0 || this.el.clientHeight === 0) {
this.showMessage('No data to display');
return;
}

if (!(Array.isArray(visData.tables) && visData.tables.length) ||
this.el.clientWidth === 0 || this.el.clientHeight === 0) {
if (response.columns.length < 2) {
this.showMessage('Data should include a label and a value');
return;
}

const table = visData.tables[0];
let funnelOptions = this.vis.params.funnelOptions;
let funnelOptionsJson = {};
try {
Expand All @@ -71,35 +70,35 @@ export const FunnelVisualizationProvider = (Private) => {
},
},
};

const table = transformDataToTable(response);
console.log("Transformed Data ", table);
const data = getDataForProcessing(table, this.vis.params);
console.log("Data - ", data);
this.processedData = processData(data, this.vis.params);
if (!(data.length && data[0].length >= 2)) {
return;
}
console.log("processedData ", this.processedData)

try {
this.chart.draw(data, funnelOptions);
} catch (err) {
notify.error(err);
this.showMessage("Error rendering visualization")
console.log("Error rendering visualization", err);
// notify.error(err);
}
}

_addFilter(label) {
const field = this.vis.aggs.bySchemaName['bucket'][0].params.field;
console.log(field);
if (!field) {
return;
}
this.filterManager.add(
// The field to filter for, we can get it from the config
field,
// The value to filter for, we will read out the bucket key from the tag
label,
// Whether the filter is negated. If you want to create a negated filter pass '-' here
null,
// The index pattern for the filter
this.vis.indexPattern.title,
);
//const indexPatternId = state.queryParameters.indexPatternId;
const newFilters = filterGen.add(field, [label], null, this.vis.indexPattern.title);
// this.filterManager.add(field,label,null,this.vis.indexPattern.title);
}

showMessage(msg) {
this.container.innerHTML = '<p>' + msg + '</p>';
}

destroy() {
Expand All @@ -111,12 +110,73 @@ export const FunnelVisualizationProvider = (Private) => {
};
};

function transformDataToTable(response) {
const result = [];
const colNames = []
response.columns.forEach(col => { colNames.push(col.id); });
response.rows.forEach(row => {
const data = [];
colNames.forEach(colName => {
data.push(row[colName]);
});
result.push(data);
});
return {rows: result, columns: response.columns};
}
/**
*
* @param {array} rows
* @param {object} params
* @returns {object}
*/
function _processData(rows, params) {
if (!(params && Array.isArray(rows) && rows.length)) {
return {};
}
const sum = rows.reduce((acc, row) => acc + row[1], 0);
const top = rows[0][1];
return rows.map((row, i) => {
let struct = {};
let value = row[1];
switch (params.selectValueDisplay) {
default:
case 'absolute':
struct.formattedValue = (numeral(value).format('0,0'));
struct.value = value;
break;
case 'percent':
value = row[1] / sum;
if (!value || isNaN(value)) {
value = 0;
}
struct.formattedValue = (numeral(value).format('0.[000]%'));
struct.value = value;
break;
case 'percentFromTop':
value = row[1] / top;
if (!value || isNaN(value)) {
value = 0;
}
struct.formattedValue = (numeral(value).format('0.[000]%'));
struct.value = value;
break;
case 'percentFromAbove':
value = i === 0 ? 1 : row[1] / rows[i - 1][1];
if (!value || isNaN(value)) {
value = 0;
}
struct.formattedValue = (numeral(value).format('0.[000]%'));
struct.value = value;
}

return {
label: row[0] + ' - ' + struct.formattedValue,
formattedValue: struct.formattedValue,
value: struct.value
};
}, {});
}

function processData(rows, params) {
if (!(params && Array.isArray(rows) && rows.length)) {
return {};
Expand Down Expand Up @@ -165,7 +225,7 @@ function getDataForProcessing(table, params) {
return table.rows;
} else if (params.sumOption === 'byMetrics') {
const row = table.rows[0];
return table.columns.map((column, i) => ([column.title, row[i]]));
return table.columns.map((column, i) => ([column.name, row[i]]));
} else {
return [];
}
Expand Down
18 changes: 14 additions & 4 deletions public/ob-kb-funnel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { CATEGORY } from 'ui/vis/vis_category';
import { Schemas, VisSchemasProvider } from 'ui/vis/editors/default/schemas';
import { Status } from 'ui/vis/update_status';

import { FunnelVisualizationProvider } from './funnel_visualization';

Expand All @@ -15,9 +15,8 @@ export function FunnelProvider(Private) {
return new VisFactory.createBaseVisualization({
name: 'ob-kb-funnel',
title: 'Funnel View',
icon: 'fa-toggle-down',
icon: 'logstashFilter',
description: 'Funnel visualization',
category: CATEGORY.OTHER,
visualization: Private(FunnelVisualizationProvider),
visConfig: {
defaults: {
Expand All @@ -41,7 +40,7 @@ export function FunnelProvider(Private) {
funnelOptionsJson: '{}',
},
},
responseHandler: 'none',

editorConfig: {
optionsTemplate: optionsTemplate,
schemas: new _Schemas([
Expand All @@ -61,6 +60,17 @@ export function FunnelProvider(Private) {
},
]),
},
defaults: [
{ schema: 'metric', type: 'count' }
],
requiresUpdateStatus: [
Status.AGGS,
Status.DATA,
Status.PARAMS,
Status.RESIZE,
Status.TIME,
Status.UI_STATE
]
});
}

Expand Down
5 changes: 3 additions & 2 deletions public/options_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
<option value="byMetrics">By Metrics</option>
</select>
</div>
<div class="checkbox">
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="editorState.params.absolute">
Absolute value
Expand Down Expand Up @@ -44,7 +45,7 @@
<div class="checkbox">
<label>
<input type="checkbox" ng-model="editorState.params.funnelOptions.block.highlight">
Highlight
Highlight on hover
</label>
</div>
<div class="checkbox">
Expand Down
Loading

0 comments on commit b19c20a

Please sign in to comment.