mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Uses the previous html/css rendering code instead of the svg-based rendering for simple metrics. This backport required manual edits and js-linting.
This commit is contained in:
parent
7d9d441377
commit
dc7345a558
15 changed files with 966 additions and 90 deletions
|
@ -8,7 +8,6 @@ import heatmapVisTypeProvider from 'plugins/kbn_vislib_vis_types/heatmap';
|
|||
import horizontalBarVisTypeProvider from 'plugins/kbn_vislib_vis_types/horizontal_bar';
|
||||
import gaugeVisTypeProvider from 'plugins/kbn_vislib_vis_types/gauge';
|
||||
import goalVisTypeProvider from 'plugins/kbn_vislib_vis_types/goal';
|
||||
import metricVisTypeProvider from 'plugins/kbn_vislib_vis_types/metric';
|
||||
|
||||
VisTypesRegistryProvider.register(histogramVisTypeProvider);
|
||||
VisTypesRegistryProvider.register(lineVisTypeProvider);
|
||||
|
@ -18,4 +17,3 @@ VisTypesRegistryProvider.register(heatmapVisTypeProvider);
|
|||
VisTypesRegistryProvider.register(horizontalBarVisTypeProvider);
|
||||
VisTypesRegistryProvider.register(gaugeVisTypeProvider);
|
||||
VisTypesRegistryProvider.register(goalVisTypeProvider);
|
||||
VisTypesRegistryProvider.register(metricVisTypeProvider);
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { VisFactoryProvider } from 'ui/vis/vis_factory';
|
||||
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
|
||||
import { CATEGORY } from 'ui/vis/vis_category';
|
||||
import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html';
|
||||
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
|
||||
import image from './images/icon-number.svg';
|
||||
|
||||
export default function MetricVisType(Private) {
|
||||
const VisFactory = Private(VisFactoryProvider);
|
||||
const Schemas = Private(VisSchemasProvider);
|
||||
|
||||
return VisFactory.createVislibVisualization({
|
||||
name: 'metric',
|
||||
title: 'Metric',
|
||||
image,
|
||||
description: 'Display a calculation as a single number',
|
||||
category: CATEGORY.DATA,
|
||||
visConfig: {
|
||||
defaults: {
|
||||
addTooltip: true,
|
||||
addLegend: false,
|
||||
type: 'gauge',
|
||||
gauge: {
|
||||
verticalSplit: false,
|
||||
autoExtend: false,
|
||||
percentageMode: false,
|
||||
gaugeType: 'Metric',
|
||||
gaugeStyle: 'Full',
|
||||
backStyle: 'Full',
|
||||
orientation: 'vertical',
|
||||
colorSchema: 'Green to Red',
|
||||
gaugeColorMode: 'None',
|
||||
useRange: false,
|
||||
colorsRange: [
|
||||
{ from: 0, to: 100 },
|
||||
],
|
||||
invertColors: false,
|
||||
labels: {
|
||||
show: true,
|
||||
color: 'black'
|
||||
},
|
||||
scale: {
|
||||
show: false,
|
||||
labels: false,
|
||||
color: '#333',
|
||||
width: 2
|
||||
},
|
||||
type: 'simple',
|
||||
style: {
|
||||
fontSize: 60,
|
||||
bgColor: false,
|
||||
labelColor: false,
|
||||
subText: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
editorConfig: {
|
||||
collections: {
|
||||
gaugeTypes: ['Arc', 'Circle', 'Metric'],
|
||||
gaugeColorMode: ['None', 'Labels', 'Background'],
|
||||
scales: ['linear', 'log', 'square root'],
|
||||
colorSchemas: Object.keys(vislibColorMaps),
|
||||
},
|
||||
optionsTemplate: gaugeTemplate,
|
||||
schemas: new Schemas([
|
||||
{
|
||||
group: 'metrics',
|
||||
name: 'metric',
|
||||
title: 'Metric',
|
||||
min: 1,
|
||||
aggFilter: ['!derivative', '!geo_centroid', '!geo_bounds'],
|
||||
defaults: [
|
||||
{ schema: 'metric', type: 'count' }
|
||||
]
|
||||
},
|
||||
{
|
||||
group: 'buckets',
|
||||
name: 'group',
|
||||
title: 'Split Group',
|
||||
min: 0,
|
||||
max: 1,
|
||||
aggFilter: ['!geohash_grid', '!filter']
|
||||
}
|
||||
])
|
||||
}
|
||||
});
|
||||
}
|
13
src/core_plugins/metric_vis/index.js
Normal file
13
src/core_plugins/metric_vis/index.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default function (kibana) {
|
||||
|
||||
return new kibana.Plugin({
|
||||
|
||||
uiExports: {
|
||||
visTypes: [
|
||||
'plugins/metric_vis/metric_vis'
|
||||
]
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
4
src/core_plugins/metric_vis/package.json
Normal file
4
src/core_plugins/metric_vis/package.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "metric_vis",
|
||||
"version": "kibana"
|
||||
}
|
60
src/core_plugins/metric_vis/public/__tests__/metric_vis.js
Normal file
60
src/core_plugins/metric_vis/public/__tests__/metric_vis.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
import $ from 'jquery';
|
||||
import ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
|
||||
import { VisProvider } from 'ui/vis';
|
||||
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
|
||||
import MetricVisProvider from '../metric_vis';
|
||||
|
||||
describe('metric_vis', () => {
|
||||
let setup = null;
|
||||
let vis;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject((Private, $rootScope) => {
|
||||
setup = () => {
|
||||
const Vis = Private(VisProvider);
|
||||
const metricVisType = Private(MetricVisProvider);
|
||||
const indexPattern = Private(LogstashIndexPatternStubProvider);
|
||||
|
||||
indexPattern.stubSetFieldFormat('ip', 'url', {
|
||||
urlTemplate: 'http://ip.info?address={{value}}',
|
||||
labelTemplate: 'ip[{{value}}]'
|
||||
});
|
||||
|
||||
vis = new Vis(indexPattern, {
|
||||
type: 'metric',
|
||||
aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
|
||||
});
|
||||
|
||||
const $el = $('<div>');
|
||||
const Controller = metricVisType.visualization;
|
||||
const controller = new Controller($el, vis);
|
||||
const render = (esResponse) => {
|
||||
controller.render(esResponse);
|
||||
$rootScope.$digest();
|
||||
};
|
||||
|
||||
return { $el, render };
|
||||
};
|
||||
}));
|
||||
|
||||
it('renders html value from field formatter', () => {
|
||||
const { $el, render } = setup();
|
||||
|
||||
const ip = '235.195.237.208';
|
||||
render({
|
||||
tables: [{
|
||||
columns: [{ title: 'ip', aggConfig: vis.aggs[0] }],
|
||||
rows: [[ ip ]]
|
||||
}]
|
||||
});
|
||||
|
||||
const $link = $el
|
||||
.find('a[href]')
|
||||
.filter(function () { return this.href.includes('ip.info'); });
|
||||
|
||||
expect($link).to.have.length(1);
|
||||
expect($link.text()).to.be(`ip[${ip}]`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
import ngMock from 'ng_mock';
|
||||
import expect from 'expect.js';
|
||||
import $ from 'jquery';
|
||||
|
||||
describe('metric vis', function () {
|
||||
let $scope;
|
||||
|
||||
const formatter = function (value) {
|
||||
return value.toFixed(3);
|
||||
};
|
||||
|
||||
const aggConfig = {
|
||||
fieldFormatter: () => {
|
||||
return formatter;
|
||||
},
|
||||
schema: {}
|
||||
};
|
||||
|
||||
beforeEach(ngMock.module('kibana/metric_vis'));
|
||||
beforeEach(ngMock.inject(function ($rootScope, $controller) {
|
||||
$scope = $rootScope.$new();
|
||||
$scope.vis = {
|
||||
params: {
|
||||
metric: {
|
||||
colorsRange: [
|
||||
{ from: 0, to: 1000 }
|
||||
],
|
||||
style: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const $element = $('<div>');
|
||||
$controller('KbnMetricVisController', { $scope, $element });
|
||||
$scope.$digest();
|
||||
}));
|
||||
|
||||
it('should set the metric label and value', function () {
|
||||
$scope.processTableGroups({
|
||||
tables: [{
|
||||
columns: [{ title: 'Count', aggConfig: { ...aggConfig, makeLabel: () => 'Count' } }],
|
||||
rows: [[ 4301021 ]]
|
||||
}]
|
||||
});
|
||||
|
||||
expect($scope.metrics.length).to.be(1);
|
||||
expect($scope.metrics[0].label).to.be('Count');
|
||||
expect($scope.metrics[0].value).to.be('4301021.000');
|
||||
});
|
||||
|
||||
it('should support multi-value metrics', function () {
|
||||
$scope.processTableGroups({
|
||||
tables: [{
|
||||
columns: [
|
||||
{ aggConfig: { ...aggConfig, makeLabel: () => '1st percentile of bytes' } },
|
||||
{ aggConfig: { ...aggConfig, makeLabel: () => '99th percentile of bytes' } }
|
||||
],
|
||||
rows: [[ 182, 445842.4634666484 ]]
|
||||
}]
|
||||
});
|
||||
|
||||
expect($scope.metrics.length).to.be(2);
|
||||
expect($scope.metrics[0].label).to.be('1st percentile of bytes');
|
||||
expect($scope.metrics[0].value).to.be('182.000');
|
||||
expect($scope.metrics[1].label).to.be('99th percentile of bytes');
|
||||
expect($scope.metrics[1].value).to.be('445842.463');
|
||||
});
|
||||
});
|
16
src/core_plugins/metric_vis/public/images/icon_number.svg
Normal file
16
src/core_plugins/metric_vis/public/images/icon_number.svg
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="63px" height="41px" viewBox="0 0 63 41" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 43.1 (39012) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>42</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Visualize" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Visualize-Create-New" transform="translate(-272.000000, -657.000000)" fill="#000000">
|
||||
<g id="data" transform="translate(74.000000, 576.000000)">
|
||||
<g id="number" transform="translate(162.000000, 44.000000)">
|
||||
<path d="M67.0898438,69.7148438 L62.2773438,69.7148438 L62.2773438,78 L54.0195312,78 L54.0195312,69.7148438 L36.9570312,69.7148438 L36.9570312,63.8359375 L54.484375,38.0234375 L62.2773438,38.0234375 L62.2773438,63.1523438 L67.0898438,63.1523438 L67.0898438,69.7148438 Z M54.0195312,63.1523438 L54.0195312,56.3710938 C54.0195312,55.2408798 54.0651037,53.6002712 54.15625,51.4492188 C54.2473963,49.2981663 54.3203122,48.0494809 54.375,47.703125 L54.15625,47.703125 C53.4817675,49.1979241 52.6705777,50.6562429 51.7226562,52.078125 L44.3945312,63.1523438 L54.0195312,63.1523438 Z M98.1523438,78 L70.2070312,78 L70.2070312,72.1210938 L80.2421875,61.9765625 C83.2135565,58.9322764 85.1549434,56.8222715 86.0664062,55.6464844 C86.9778691,54.4706972 87.6341126,53.3815154 88.0351562,52.3789062 C88.4361999,51.3762971 88.6367188,50.337245 88.6367188,49.2617188 C88.6367188,47.6575441 88.1946659,46.4635456 87.3105469,45.6796875 C86.4264279,44.8958294 85.2461011,44.5039062 83.7695312,44.5039062 C82.2200443,44.5039062 80.7161531,44.8593714 79.2578125,45.5703125 C77.7994719,46.2812536 76.2773517,47.2929622 74.6914062,48.6054688 L70.0976562,43.1640625 C72.0664161,41.4869708 73.6979102,40.3020868 74.9921875,39.609375 C76.2864648,38.9166632 77.6992111,38.3834654 79.2304688,38.0097656 C80.7617264,37.6360658 82.4752509,37.4492188 84.3710938,37.4492188 C86.8685021,37.4492188 89.0742092,37.9049434 90.9882812,38.8164062 C92.9023533,39.7278691 94.3880155,41.003898 95.4453125,42.6445312 C96.5026095,44.2851645 97.03125,46.1627498 97.03125,48.2773438 C97.03125,50.1184988 96.7076855,51.8456951 96.0605469,53.4589844 C95.4134082,55.0722737 94.4108141,56.726554 93.0527344,58.421875 C91.6946547,60.117196 89.3021005,62.5325364 85.875,65.6679688 L80.734375,70.5078125 L80.734375,70.890625 L98.1523438,70.890625 L98.1523438,78 Z" id="42"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
9
src/core_plugins/metric_vis/public/metric_vis.html
Normal file
9
src/core_plugins/metric_vis/public/metric_vis.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
<div ng-controller="KbnMetricVisController" class="metric-vis">
|
||||
<div class="metric-container" ng-repeat="metric in metrics" ng-style="{'background-color': metric.bgColor}">
|
||||
<div class="metric-value"
|
||||
ng-bind-html="metric.value"
|
||||
ng-style="{'font-size': vis.params.metric.style.fontSize+'pt', 'color': metric.color}"
|
||||
></div>
|
||||
<div ng-if="vis.params.metric.labels.show">{{metric.label}}</div>
|
||||
</div>
|
||||
</div>
|
91
src/core_plugins/metric_vis/public/metric_vis.js
Normal file
91
src/core_plugins/metric_vis/public/metric_vis.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
import 'plugins/metric_vis/metric_vis.less';
|
||||
import 'plugins/metric_vis/metric_vis_params';
|
||||
import 'plugins/metric_vis/metric_vis_controller';
|
||||
import { VisFactoryProvider } from 'ui/vis/vis_factory';
|
||||
import { CATEGORY } from 'ui/vis/vis_category';
|
||||
import { VisSchemasProvider } from 'ui/vis/editors/default/schemas';
|
||||
import metricVisTemplate from 'plugins/metric_vis/metric_vis.html';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
import { vislibColorMaps } from 'ui/vislib/components/color/colormaps';
|
||||
import image from './images/icon_number.svg';
|
||||
// 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
|
||||
VisTypesRegistryProvider.register(MetricVisProvider);
|
||||
|
||||
function MetricVisProvider(Private) {
|
||||
const Schemas = Private(VisSchemasProvider);
|
||||
const VisFactory = Private(VisFactoryProvider);
|
||||
|
||||
// return the visType object, which kibana will use to display and configure new
|
||||
// Vis object of this type.
|
||||
return VisFactory.createAngularVisualization({
|
||||
name: 'metric',
|
||||
title: 'Metric',
|
||||
image,
|
||||
description: 'Display a calculation as a single number',
|
||||
category: CATEGORY.DATA,
|
||||
visConfig: {
|
||||
defaults: {
|
||||
addTooltip: true,
|
||||
addLegend: false,
|
||||
type: 'metric',
|
||||
metric: {
|
||||
percentageMode: false,
|
||||
useRanges: false,
|
||||
colorSchema: 'Green to Red',
|
||||
metricColorMode: 'None',
|
||||
colorsRange: [
|
||||
{ from: 0, to: 10000 }
|
||||
],
|
||||
labels: {
|
||||
show: true
|
||||
},
|
||||
invertColors: false,
|
||||
style: {
|
||||
bgFill: '#000',
|
||||
bgColor: false,
|
||||
labelColor: false,
|
||||
subText: '',
|
||||
fontSize: 60,
|
||||
}
|
||||
}
|
||||
},
|
||||
template: metricVisTemplate,
|
||||
},
|
||||
editorConfig: {
|
||||
collections: {
|
||||
metricColorMode: ['None', 'Labels', 'Background'],
|
||||
colorSchemas: Object.keys(vislibColorMaps),
|
||||
},
|
||||
optionsTemplate: '<metric-vis-params></metric-vis-params>',
|
||||
schemas: new Schemas([
|
||||
{
|
||||
group: 'metrics',
|
||||
name: 'metric',
|
||||
title: 'Metric',
|
||||
min: 1,
|
||||
aggFilter: [
|
||||
'!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks',
|
||||
'!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum', '!geo_bounds'],
|
||||
defaults: [
|
||||
{ type: 'count', schema: 'metric' }
|
||||
]
|
||||
}, {
|
||||
group: 'buckets',
|
||||
name: 'group',
|
||||
title: 'Split Group',
|
||||
min: 0,
|
||||
max: 1,
|
||||
aggFilter: ['!geohash_grid', '!filter']
|
||||
}
|
||||
])
|
||||
},
|
||||
implementsRenderComplete: true,
|
||||
});
|
||||
}
|
||||
|
||||
// export the provider so that the visType can be required with Private()
|
||||
export default MetricVisProvider;
|
19
src/core_plugins/metric_vis/public/metric_vis.less
Normal file
19
src/core_plugins/metric_vis/public/metric_vis.less
Normal file
|
@ -0,0 +1,19 @@
|
|||
@import (reference) "~ui/styles/mixins.less";
|
||||
|
||||
.metric-vis {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.metric-value {
|
||||
font-weight: bold;
|
||||
.ellipsis();
|
||||
}
|
||||
|
||||
.metric-container {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
126
src/core_plugins/metric_vis/public/metric_vis_controller.js
Normal file
126
src/core_plugins/metric_vis/public/metric_vis_controller.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
import _ from 'lodash';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { getHeatmapColors } from 'ui/vislib/components/color/heatmap_color';
|
||||
// get the kibana/metric_vis module, and make sure that it requires the "kibana" module if it
|
||||
// didn't already
|
||||
const module = uiModules.get('kibana/metric_vis', ['kibana']);
|
||||
|
||||
module.controller('KbnMetricVisController', function ($scope, $element) {
|
||||
|
||||
const metrics = $scope.metrics = [];
|
||||
let labels = [];
|
||||
let colors = [];
|
||||
|
||||
const getLabels = () => {
|
||||
const config = $scope.vis.params.metric;
|
||||
const isPercentageMode = config.percentageMode;
|
||||
const colorsRange = config.colorsRange;
|
||||
const max = _.last(colorsRange).to;
|
||||
const labels = [];
|
||||
colorsRange.forEach(range => {
|
||||
const from = isPercentageMode ? Math.round(100 * range.from / max) : range.from;
|
||||
const to = isPercentageMode ? Math.round(100 * range.to / max) : range.to;
|
||||
labels.push(`${from} - ${to}`);
|
||||
});
|
||||
|
||||
return labels;
|
||||
};
|
||||
|
||||
const getColors = () => {
|
||||
const config = $scope.vis.params.metric;
|
||||
const invertColors = config.invertColors;
|
||||
const colorSchema = config.colorSchema;
|
||||
const colorsRange = config.colorsRange;
|
||||
const labels = getLabels();
|
||||
const colors = {};
|
||||
for (let i = 0; i < labels.length; i += 1) {
|
||||
const divider = Math.max(colorsRange.length - 1, 1);
|
||||
const val = invertColors ? 1 - i / divider : i / divider;
|
||||
colors[labels[i]] = getHeatmapColors(val, colorSchema);
|
||||
}
|
||||
return colors;
|
||||
};
|
||||
|
||||
const getBucket = (val) => {
|
||||
const config = $scope.vis.params.metric;
|
||||
let bucket = _.findIndex(config.colorsRange, range => {
|
||||
return range.from <= val && range.to > val;
|
||||
});
|
||||
|
||||
if (bucket === -1) {
|
||||
if (val < config.colorsRange[0].from) bucket = 0;
|
||||
else bucket = config.colorsRange.length - 1;
|
||||
}
|
||||
|
||||
return bucket;
|
||||
};
|
||||
|
||||
const getColor = (val) => {
|
||||
const bucket = getBucket(val);
|
||||
const label = labels[bucket];
|
||||
return colors[label];
|
||||
};
|
||||
|
||||
$scope.processTableGroups = function (tableGroups) {
|
||||
const config = $scope.vis.params.metric;
|
||||
const isPercentageMode = config.percentageMode;
|
||||
const min = config.colorsRange[0].from;
|
||||
const max = _.last(config.colorsRange).to;
|
||||
|
||||
tableGroups.tables.forEach(function (table) {
|
||||
let bucketAgg;
|
||||
|
||||
table.columns.forEach(function (column, i) {
|
||||
const aggConfig = column.aggConfig;
|
||||
|
||||
if (aggConfig && aggConfig.schema.group === 'buckets') {
|
||||
bucketAgg = aggConfig;
|
||||
return;
|
||||
}
|
||||
|
||||
table.rows.forEach(row => {
|
||||
|
||||
let title = column.title;
|
||||
let value = row[i];
|
||||
const color = getColor(value);
|
||||
|
||||
if (isPercentageMode) {
|
||||
const percentage = Math.round(100 * (value - min) / (max - min));
|
||||
value = `${percentage}%`;
|
||||
}
|
||||
|
||||
if (aggConfig) {
|
||||
if (!isPercentageMode) value = aggConfig.fieldFormatter('html')(value);
|
||||
if (bucketAgg) {
|
||||
const bucketValue = bucketAgg.fieldFormatter('text')(row[0]);
|
||||
title = `${bucketValue} - ${aggConfig.makeLabel()}`;
|
||||
} else {
|
||||
title = aggConfig.makeLabel();
|
||||
}
|
||||
}
|
||||
|
||||
const shouldColor = config.colorsRange.length > 1;
|
||||
|
||||
metrics.push({
|
||||
label: title,
|
||||
value: value,
|
||||
color: shouldColor && config.style.labelColor ? color : null,
|
||||
bgColor: shouldColor && config.style.bgColor ? color : null
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.$watch('esResponse', function (resp) {
|
||||
if (resp) {
|
||||
metrics.length = 0;
|
||||
labels.length = 0;
|
||||
colors.length = 0;
|
||||
colors = getColors();
|
||||
labels = getLabels();
|
||||
$scope.processTableGroups(resp);
|
||||
$element.trigger('renderComplete');
|
||||
}
|
||||
});
|
||||
});
|
206
src/core_plugins/metric_vis/public/metric_vis_params.html
Normal file
206
src/core_plugins/metric_vis/public/metric_vis_params.html
Normal file
|
@ -0,0 +1,206 @@
|
|||
<div class="kuiSideBarSection">
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="percentageMode">
|
||||
Percentage Mode
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input class="kuiCheckBox" id="percentageMode" type="checkbox" ng-model="vis.params.metric.percentageMode">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="showLabels">
|
||||
Show Labels
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input class="kuiCheckBox" id="showLabels" type="checkbox" ng-model="vis.params.metric.labels.show">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="kuiSideBarCollapsibleTitle">
|
||||
<div
|
||||
kbn-accessible-click
|
||||
aria-expanded="{{!!showColorRange}}"
|
||||
aria-controls="metricOptionsRanges"
|
||||
aria-label="Toggle range options"
|
||||
class="kuiSideBarCollapsibleTitle__label"
|
||||
ng-click="showColorRange = !showColorRange"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
ng-class="{ 'fa-caret-down': showColorRange, 'fa-caret-right': !showColorRange }"
|
||||
class="fa fa-caret-right kuiSideBarCollapsibleTitle__caret"
|
||||
></span>
|
||||
<span class="kuiSideBarCollapsibleTitle__text">
|
||||
Ranges
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="metricOptionsRanges" ng-show="showColorRange" class="kuiSideBarCollapsibleSection">
|
||||
<div class="kuiSideBarSection">
|
||||
<table class="vis-editor-agg-editor-ranges form-group" ng-show="vis.params.metric.colorsRange.length">
|
||||
<tr>
|
||||
<th>
|
||||
<label id="metricOptionsCustomRangeFrom">From</label>
|
||||
</th>
|
||||
<th colspan="2">
|
||||
<label id="metricOptionsCustomRangeTo">To</label>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="range in vis.params.metric.colorsRange track by $index">
|
||||
<td>
|
||||
<input
|
||||
aria-labelledby="metricOptionsCustomRangeFrom"
|
||||
ng-model="range.from"
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="range.from"
|
||||
greater-or-equal-than="{{getGreaterThan($index)}}"
|
||||
required
|
||||
step="any" />
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
aria-labelledby="metricOptionsCustomRangeTo"
|
||||
ng-model="range.to"
|
||||
type="number"
|
||||
class="form-control"
|
||||
name="range.to"
|
||||
greater-or-equal-than="{{range.from}}"
|
||||
required
|
||||
step="any" />
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
ng-click="removeRange($index)"
|
||||
ng-show="vis.params.metric.colorsRange.length > 1"
|
||||
class="kuiButton kuiButton--danger kuiButton--small">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="hintbox" ng-show="!vis.params.metric.colorsRange.length">
|
||||
<p>
|
||||
<i class="fa fa-danger text-danger"></i>
|
||||
<strong>Required:</strong> You must specify at least one range.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ng-click="addRange()"
|
||||
class="kuiButton kuiButton--primary kuiButton--fullWidth">
|
||||
Add Range
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="vis.params.metric.colorsRange.length > 1">
|
||||
<div class="kuiSideBarCollapsibleTitle">
|
||||
<div
|
||||
kbn-accessible-click
|
||||
aria-expanded="{{!!showColorOptions}}"
|
||||
aria-controls="metricOptionsColors"
|
||||
aria-label="Toggle color options"
|
||||
class="kuiSideBarCollapsibleTitle__label"
|
||||
ng-click="showColorOptions = !showColorOptions"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
ng-class="{
|
||||
'fa-caret-down': showColorOptions,
|
||||
'fa-caret-right': !showColorOptions
|
||||
}"
|
||||
class="fa fa-caret-right kuiSideBarCollapsibleTitle__caret"
|
||||
></span>
|
||||
<span class="kuiSideBarCollapsibleTitle__text">
|
||||
Color Options
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="metricOptionsColors" ng-if="showColorOptions" class="kuiSideBarCollapsibleSection">
|
||||
<div class="kuiSideBarSection">
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="colorSchema">
|
||||
Color Schema
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<select
|
||||
id="colorSchema"
|
||||
class="kuiSelect kuiSideBarSelect"
|
||||
ng-model="vis.params.metric.colorSchema"
|
||||
ng-options="mode for mode in collections.colorSchemas"
|
||||
></select>
|
||||
</div>
|
||||
<div class="text-info text-center" ng-show="customColors" ng-click="resetColors()">reset colors</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="invertColors">
|
||||
Reverse Color Schema
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input class="kuiCheckBox" id="invertColors" type="checkbox" ng-model="vis.params.metric.invertColors">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="kuiSideBarCollapsibleTitle">
|
||||
<div
|
||||
kbn-accessible-click
|
||||
aria-expanded="{{!!showStyle}}"
|
||||
aria-controls="metricOptionsStyle"
|
||||
aria-label="Toggle style options"
|
||||
class="kuiSideBarCollapsibleTitle__label"
|
||||
ng-click="showStyle = !showStyle"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
ng-class="{
|
||||
'fa-caret-down': showStyle,
|
||||
'fa-caret-right': !showStyle
|
||||
}"
|
||||
class="fa fa-caret-right kuiSideBarCollapsibleTitle__caret"
|
||||
></span>
|
||||
<span class="kuiSideBarCollapsibleTitle__text">
|
||||
Style
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="metricOptionsStyle" ng-if="showStyle" class="kuiSideBarCollapsibleSection">
|
||||
|
||||
<div class="kuiSideBarSection">
|
||||
<div class="kuiSideBarFormRow" ng-show="vis.params.metric.colorsRange.length > 1">
|
||||
<label class="kuiSideBarFormRow__label" for="metricColorMode">
|
||||
Color
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<select
|
||||
id="metricColorMode"
|
||||
class="kuiSelect kuiSideBarSelect"
|
||||
ng-model="vis.params.metric.metricColorMode"
|
||||
ng-options="mode for mode in collections.metricColorMode"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kuiSideBarFormRow">
|
||||
<label class="kuiSideBarFormRow__label" for="metricFontSize">
|
||||
Font Size (<span ng-bind="vis.params.metric.style.fontSize"></span>pt)
|
||||
</label>
|
||||
<div class="kuiSideBarFormRow__control">
|
||||
<input id="metricFontSize" type="range" ng-model="vis.params.metric.style.fontSize" class="form-control" min="12" max="120" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
66
src/core_plugins/metric_vis/public/metric_vis_params.js
Normal file
66
src/core_plugins/metric_vis/public/metric_vis_params.js
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { uiModules } from 'ui/modules';
|
||||
import metricVisParamsTemplate from './metric_vis_params.html';
|
||||
import _ from 'lodash';
|
||||
const module = uiModules.get('kibana');
|
||||
|
||||
module.directive('metricVisParams', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: metricVisParamsTemplate,
|
||||
replace: true,
|
||||
link: function ($scope) {
|
||||
$scope.collections = $scope.vis.type.editorConfig.collections;
|
||||
$scope.showColorRange = true;
|
||||
|
||||
$scope.$watch('vis.params.metric.metricColorMode', newValue => {
|
||||
switch (newValue) {
|
||||
case 'Labels':
|
||||
$scope.vis.params.metric.style.labelColor = true;
|
||||
$scope.vis.params.metric.style.bgColor = false;
|
||||
break;
|
||||
case 'Background':
|
||||
$scope.vis.params.metric.style.labelColor = false;
|
||||
$scope.vis.params.metric.style.bgColor = true;
|
||||
break;
|
||||
case 'None':
|
||||
$scope.vis.params.metric.style.labelColor = false;
|
||||
$scope.vis.params.metric.style.bgColor = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.resetColors = function () {
|
||||
$scope.uiState.set('vis.colors', null);
|
||||
$scope.customColors = false;
|
||||
};
|
||||
|
||||
$scope.getGreaterThan = function (index) {
|
||||
if (index === 0) return 0;
|
||||
return $scope.vis.params.metric.colorsRange[index - 1].to;
|
||||
};
|
||||
|
||||
$scope.addRange = function () {
|
||||
const previousRange = _.last($scope.vis.params.metric.colorsRange);
|
||||
const from = previousRange ? previousRange.to : 0;
|
||||
const to = previousRange ? from + (previousRange.to - previousRange.from) : 100;
|
||||
$scope.vis.params.metric.colorsRange.push({ from: from, to: to });
|
||||
};
|
||||
|
||||
$scope.removeRange = function (index) {
|
||||
$scope.vis.params.metric.colorsRange.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.getColor = function (index) {
|
||||
const defaultColors = this.uiState.get('vis.defaultColors');
|
||||
const overwriteColors = this.uiState.get('vis.colors');
|
||||
const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors;
|
||||
return colors ? Object.values(colors)[index] : 'transparent';
|
||||
};
|
||||
|
||||
$scope.uiState.on('colorChanged', () => {
|
||||
$scope.customColors = true;
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
|
@ -3,6 +3,29 @@
|
|||
// It should rather convert the raw saved object before starting to instantiate
|
||||
// any JavaScript classes from it.
|
||||
const updateVisualizationConfig = (stateConfig, config) => {
|
||||
|
||||
if (config.type === 'gauge' && config.fontSize) {
|
||||
config.gauge.style.fontSize = config.fontSize;
|
||||
delete config.fontSize;
|
||||
}
|
||||
|
||||
// update old metric to the new one
|
||||
if (config.type === 'gauge' && config.gauge.gaugeType === 'Metric') {
|
||||
config.type = 'metric';
|
||||
config.metric = config.gauge;
|
||||
delete config.gauge;
|
||||
delete config.metric.gaugeType;
|
||||
delete config.metric.gaugeStyle;
|
||||
delete config.metric.backStyle;
|
||||
delete config.metric.scale;
|
||||
delete config.metric.type;
|
||||
delete config.metric.orientation;
|
||||
delete config.metric.verticalSplit;
|
||||
delete config.metric.autoExtend;
|
||||
config.metric.metricColorMode = config.metric.gaugeColorMode;
|
||||
delete config.metric.gaugeColorMode;
|
||||
}
|
||||
|
||||
if (!stateConfig || stateConfig.seriesParams) return;
|
||||
if (!['line', 'area', 'histogram'].includes(config.type)) return;
|
||||
|
||||
|
|
263
test/functional/apps/visualize/_metric_chart.js
Normal file
263
test/functional/apps/visualize/_metric_chart.js
Normal file
|
@ -0,0 +1,263 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
export default function ({ getService, getPageObjects }) {
|
||||
const log = getService('log');
|
||||
const retry = getService('retry');
|
||||
const screenshots = getService('screenshots');
|
||||
const PageObjects = getPageObjects(['common', 'visualize', 'header']);
|
||||
|
||||
describe('visualize app', function describeIndexTests() {
|
||||
const fromTime = '2015-09-19 06:31:44.000';
|
||||
const toTime = '2015-09-23 18:31:44.000';
|
||||
|
||||
before(function () {
|
||||
log.debug('navigateToApp visualize');
|
||||
return PageObjects.common.navigateToUrl('visualize', 'new')
|
||||
.then(function () {
|
||||
log.debug('clickMetric');
|
||||
return PageObjects.visualize.clickMetric();
|
||||
})
|
||||
.then(function clickNewSearch() {
|
||||
return PageObjects.visualize.clickNewSearch();
|
||||
})
|
||||
.then(function setAbsoluteRange() {
|
||||
log.debug('Set absolute time range from \"' + fromTime + '\" to \"' + toTime + '\"');
|
||||
return PageObjects.header.setAbsoluteRange(fromTime, toTime);
|
||||
});
|
||||
});
|
||||
|
||||
describe('metric chart', function indexPatternCreation() {
|
||||
|
||||
it('should show Count', function () {
|
||||
const expectedCount = ['14,004', 'Count'];
|
||||
|
||||
// initial metric of "Count" is selected by default
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
screenshots.take('Visualize-metric-chart');
|
||||
expect(expectedCount).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Average', function () {
|
||||
const avgMachineRam = ['13,104,036,080.615', 'Average machine.ram'];
|
||||
return PageObjects.visualize.clickMetricEditor()
|
||||
.then(function () {
|
||||
log.debug('Aggregation = Average');
|
||||
return PageObjects.visualize.selectAggregation('Average');
|
||||
})
|
||||
.then(function selectField() {
|
||||
log.debug('Field = machine.ram');
|
||||
return PageObjects.visualize.selectField('machine.ram', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(avgMachineRam).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Sum', function () {
|
||||
const sumPhpMemory = ['85,865,880', 'Sum of phpmemory'];
|
||||
log.debug('Aggregation = Sum');
|
||||
return PageObjects.visualize.selectAggregation('Sum')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = phpmemory');
|
||||
return PageObjects.visualize.selectField('phpmemory', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(sumPhpMemory).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Median', function () {
|
||||
const medianBytes = ['5,565.263', '50th percentile of bytes'];
|
||||
// For now, only comparing the text label part of the metric
|
||||
log.debug('Aggregation = Median');
|
||||
return PageObjects.visualize.selectAggregation('Median')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = bytes');
|
||||
return PageObjects.visualize.selectField('bytes', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
// only comparing the text label!
|
||||
expect(medianBytes[1]).to.eql(metricValue.split('\n')[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Min', function () {
|
||||
const minTimestamp = ['September 20th 2015, 00:00:00.000', 'Min @timestamp'];
|
||||
log.debug('Aggregation = Min');
|
||||
return PageObjects.visualize.selectAggregation('Min')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = @timestamp');
|
||||
return PageObjects.visualize.selectField('@timestamp', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(minTimestamp).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Max', function () {
|
||||
const maxRelatedContentArticleModifiedTime = ['April 4th 2015, 00:54:41.000', 'Max relatedContent.article:modified_time'];
|
||||
log.debug('Aggregation = Max');
|
||||
return PageObjects.visualize.selectAggregation('Max')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = relatedContent.article:modified_time');
|
||||
return PageObjects.visualize.selectField('relatedContent.article:modified_time', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(maxRelatedContentArticleModifiedTime).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Standard Deviation', function () {
|
||||
const standardDeviationBytes = [
|
||||
'-1,435.138', 'Lower Standard Deviation of bytes',
|
||||
'12,889.766', 'Upper Standard Deviation of bytes'
|
||||
];
|
||||
log.debug('Aggregation = Standard Deviation');
|
||||
return PageObjects.visualize.selectAggregation('Standard Deviation')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = bytes');
|
||||
return PageObjects.visualize.selectField('bytes', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(standardDeviationBytes).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Unique Count', function () {
|
||||
const uniqueCountClientip = ['1,000', 'Unique count of clientip'];
|
||||
log.debug('Aggregation = Unique Count');
|
||||
return PageObjects.visualize.selectAggregation('Unique Count')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = clientip');
|
||||
return PageObjects.visualize.selectField('clientip', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(uniqueCountClientip).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
log.debug('metricValue=' + metricValue.split('\n'));
|
||||
expect(uniqueCountClientip).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Percentiles', function () {
|
||||
const percentileMachineRam = [
|
||||
'2,147,483,648', '1st percentile of machine.ram',
|
||||
'3,221,225,472', '5th percentile of machine.ram',
|
||||
'7,516,192,768', '25th percentile of machine.ram',
|
||||
'12,884,901,888', '50th percentile of machine.ram',
|
||||
'18,253,611,008', '75th percentile of machine.ram',
|
||||
'32,212,254,720', '95th percentile of machine.ram',
|
||||
'32,212,254,720', '99th percentile of machine.ram'
|
||||
];
|
||||
|
||||
log.debug('Aggregation = Percentiles');
|
||||
return PageObjects.visualize.selectAggregation('Percentiles')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = machine.ram');
|
||||
return PageObjects.visualize.selectField('machine.ram', 'metrics');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(percentileMachineRam).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Percentile Ranks', function () {
|
||||
const percentileRankBytes = [ '2.036%', 'Percentile rank 99 of "memory"'];
|
||||
log.debug('Aggregation = Percentile Ranks');
|
||||
return PageObjects.visualize.selectAggregation('Percentile Ranks')
|
||||
.then(function selectField() {
|
||||
log.debug('Field = bytes');
|
||||
return PageObjects.visualize.selectField('memory', 'metrics');
|
||||
})
|
||||
.then(function selectField() {
|
||||
log.debug('Values = 99');
|
||||
return PageObjects.visualize.setValue('99');
|
||||
})
|
||||
.then(function clickGo() {
|
||||
return PageObjects.visualize.clickGo();
|
||||
})
|
||||
.then(function () {
|
||||
return retry.try(function tryingForTime() {
|
||||
return PageObjects.visualize.getMetric()
|
||||
.then(function (metricValue) {
|
||||
expect(percentileRankBytes).to.eql(metricValue.split('\n'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue