Skip to content

Commit

Permalink
Merge pull request #36 from sudarshandhokale/7.6.0
Browse files Browse the repository at this point in the history
Bumped version to 7.6.0
  • Loading branch information
roybass authored Jul 21, 2020
2 parents 2d3c47b + 9e24a1e commit fea0ba5
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 165 deletions.
Binary file not shown.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "ob-kb-funnel",
"version": "7.5.0",
"version": "7.6.0",
"description": "Visualization of a funnel in Kibana",
"license": "MIT",
"main": "index.js",
"kibana": {
"version": "7.5.0",
"version": "7.6.0",
"templateVersion": "1.0.0"
},
"scripts": {
Expand Down
158 changes: 158 additions & 0 deletions public/funnel_vis_options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import React, { useCallback } from 'react';
import { EuiPanel, EuiTextArea } from '@elastic/eui';
import { VisOptionsProps } from '../../../src/legacy/ui/public/vis/editors/default';
import { SwitchOption, SelectOption, NumberInputOption } from '../../../src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common';

export interface FunnelVisParams {
absolute: boolean;
percent: boolean;
percentFromTop: boolean;
percentFromAbove: boolean;
dataTransformFromAbove: boolean;
sumOption: string | null;
funnelOptions: {
block: {
dynamicHeight: boolean;
minHeight: number | null;
highlight: boolean;
};
chart: {
curve: {
enabled: boolean;
};
};
};
funnelOptionsJson: string | null;
}

export function FunnelVisOption({
stateParams,
setValue,
}: VisOptionsProps<FunnelVisParams>) {

const aggTypeOptions: any = [
{ label: 'By Buckets', value: 'byBuckets', text: 'By Buckets' },
{ label: 'By Metrics', value: 'byMetrics', text: 'By Metrics' },
];

const setFunnelOptionsValue = useCallback( (paramName, value) =>
setValue('funnelOptions', {
...stateParams.funnelOptions,
[paramName]: value,
}),
[setValue, stateParams.funnelOptions]
);

const setFunnelChartOptionsValue = useCallback( (paramName, value) =>
setFunnelOptionsValue('chart', {
...stateParams.funnelOptions.chart,
[paramName]: value,
}),
[setValue, stateParams.funnelOptions.chart]
);

// Render the application DOM.
return (
<EuiPanel paddingSize="s">
<SelectOption
label='Select aggregation type:'
options={aggTypeOptions}
paramName="sumOption"
value={stateParams.sumOption}
setValue={setValue}
/>

<SwitchOption
label='Absolute value'
paramName="absolute"
value={stateParams.absolute}
setValue={setValue}
/>

<SwitchOption
label='Percent from sum'
paramName="percent"
value={stateParams.percent}
setValue={setValue}
/>

<SwitchOption
label='Percent from top item'
paramName="percentFromTop"
value={stateParams.percentFromTop}
setValue={setValue}
/>

<SwitchOption
label='Percent from above item'
paramName="percentFromAbove"
value={stateParams.percentFromAbove}
setValue={setValue}
/>

<SwitchOption
label='Dynamic height'
paramName="dynamicHeight"
value={stateParams.funnelOptions.block.dynamicHeight}
setValue={(paramName, value) =>
setFunnelOptionsValue('block', { ...stateParams.funnelOptions.block, [paramName]: value })
}
/>

<NumberInputOption
label='Min height'
min={1}
paramName="minHeight"
value={stateParams.funnelOptions.block.minHeight}
setValue={(paramName, value) =>
setFunnelOptionsValue('block', { ...stateParams.funnelOptions.block, [paramName]: value })
}
/>

<SwitchOption
label='Highlight on hover'
paramName="highlight"
value={stateParams.funnelOptions.block.highlight}
setValue={(paramName, value) =>
setFunnelOptionsValue('block', { ...stateParams.funnelOptions.block, [paramName]: value })
}
/>

<SwitchOption
label='Chart curve'
paramName="enabled"
value={stateParams.funnelOptions.chart.curve.enabled}
setValue={(paramName, value) =>
setFunnelChartOptionsValue('curve', { ...stateParams.funnelOptions.chart.curve, [paramName]: value })
}
/>

<EuiTextArea
autoFocus
aria-label='Funnel Options JSON:'
fullWidth={true}
value={stateParams.funnelOptionsJson}
onChange={(e) => setValue('funnelOptionsJson', e.target.value)}
/>
</EuiPanel>
);
};
110 changes: 47 additions & 63 deletions public/funnel_visualization.js → public/funnel_vis_provider.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { merge } from 'lodash';
import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter';
import { getFilterGenerator } from 'ui/filter_manager';
import { Notifier } from 'ui/notify';
import { merge, isEmpty, groupBy, forEach, sum, map, round, min, max } from 'lodash';
import { FilterBarQueryFilterProvider, generateFilters } from 'ui/filter_manager/query_filter';

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

export const FunnelVisualizationProvider = () => {
const queryFilter = FilterBarQueryFilterProvider;
const filterGen = getFilterGenerator(queryFilter);
// const notify = new Notifier({ location: 'Funnel' });

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

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

render(response) {
Expand Down Expand Up @@ -70,7 +65,7 @@ export const FunnelVisualizationProvider = () => {
},
},
};
const table = transformDataToTable(response);
const table = transformData(response, this.vis.params);
console.log("Transformed Data ", table);
const data = getDataForProcessing(table, this.vis.params);
console.log("Data - ", data);
Expand All @@ -82,7 +77,6 @@ export const FunnelVisualizationProvider = () => {
} catch (err) {
this.showMessage("Error rendering visualization")
console.log("Error rendering visualization", err);
// notify.error(err);
}
}

Expand All @@ -92,9 +86,7 @@ export const FunnelVisualizationProvider = () => {
if (!field) {
return;
}
//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);
const newFilters = generateFilters(queryFilter, field, [label], null, this.vis.indexPattern.title);
}

showMessage(msg) {
Expand All @@ -110,6 +102,14 @@ export const FunnelVisualizationProvider = () => {
};
};

function transformData(response, params) {
if(!isEmpty(params.dimensions.data_transform) && params.sumOption == 'byBuckets') {
return transformDataOnField(response, params);
} else {
return transformDataToTable(response);
}
}

function transformDataToTable(response) {
const result = [];
const colNames = []
Expand All @@ -123,58 +123,42 @@ function transformDataToTable(response) {
});
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 {};

function transformDataOnField(response, params){
const bucket_agg = response.columns.filter(col => col.name == params.dimensions.bucket?.[0]?.label)[0];
const data_transform_agg = response.columns.filter(col => col.name == params.dimensions.data_transform?.[0]?.label)[0];
const metric_agg = response.columns.filter(col => col.name == params.dimensions.metric?.[0]?.label)[0];
const data_rows = groupBy(response.rows, row => row[bucket_agg.id]);
const result = [];
let previous_key = undefined;
forEach(data_rows, (value, key) => {
const data_transform_ids = (data_rows[previous_key] || value).map(m => m[data_transform_agg.id]);
const metric_data = map(value.filter(v => data_transform_ids.includes(v[data_transform_agg.id])), metric_agg.id);
result.push([key, transformMetricData(params.dimensions.metric?.[0]?.aggType, metric_data)]);
previous_key = key;
});
return {rows: result, columns: response.columns};
}

function transformMetricData(type, data){
let result;
switch(type) {
case 'count': case 'cardinality': case 'sum':
result = sum(data);
break;
case 'avg':
result = round((sum(data)/data.length), 2);
break;
case 'min':
result = min(data);
break;
case 'max':
result = max(data);
break;
default:
result = data;
}
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
};
}, {});
return result;
}

function processData(rows, params) {
Expand Down
Loading

0 comments on commit fea0ba5

Please sign in to comment.