[Metric] convert mocha tests to jest (#54054)

* Add fixtures/* alias to tsconfig and jest config
* Convert metric tests to jest
* Convert remaining js files to ts
This commit is contained in:
Nick Partridge 2020-01-17 12:00:35 -06:00 committed by GitHub
parent 56482ab043
commit 119eabe271
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 443 additions and 291 deletions

View file

@ -58,6 +58,7 @@ export default {
'^ui/(.*)': '<rootDir>/src/legacy/ui/public/$1',
'^uiExports/(.*)': '<rootDir>/src/dev/jest/mocks/file_mock.js',
'^test_utils/(.*)': '<rootDir>/src/test_utils/public/$1',
'^fixtures/(.*)': '<rootDir>/src/fixtures/$1',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/src/dev/jest/mocks/file_mock.js',
'\\.(css|less|scss)$': '<rootDir>/src/dev/jest/mocks/style_mock.js',

View file

@ -13,7 +13,7 @@ Object {
"metrics": undefined,
},
"metric": Object {
"colorSchema": "\\"Green to Red\\"",
"colorSchema": "Green to Red",
"colorsRange": "{range from=0 to=10000}",
"invertColors": false,
"labels": Object {

View file

@ -1,95 +0,0 @@
/*
* 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 $ from 'jquery';
import ngMock from 'ng_mock';
import expect from '@kbn/expect';
import { Vis } from 'ui/vis';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy';
describe('metric_vis - createMetricVisTypeDefinition', () => {
let setup = null;
let vis;
beforeEach(ngMock.module('kibana'));
beforeEach(
ngMock.inject(Private => {
setup = () => {
const metricVisType = visualizations.types.get('metric');
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' } }],
});
vis.params.dimensions = {
metrics: [
{
accessor: 0,
format: {
id: 'url',
params: {
urlTemplate: 'http://ip.info?address={{value}}',
labelTemplate: 'ip[{{value}}]',
},
},
},
],
};
const el = document.createElement('div');
const Controller = metricVisType.visualization;
const controller = new Controller(el, vis);
const render = esResponse => {
controller.render(esResponse, vis.params);
};
return { el, render };
};
})
);
it('renders html value from field formatter', () => {
const { el, render } = setup();
const ip = '235.195.237.208';
render({
columns: [{ id: 'col-0', name: 'ip' }],
rows: [{ 'col-0': 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}]`);
});
});

View file

@ -1,71 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { MetricVisComponent } from '../components/metric_vis_controller';
describe('metric_vis - controller', function() {
const vis = {
params: {
metric: {
colorSchema: 'Green to Red',
colorsRange: [{ from: 0, to: 1000 }],
style: {},
},
dimensions: {
metrics: [{ accessor: 0 }],
bucket: null,
},
},
};
let metricController;
beforeEach(() => {
metricController = new MetricVisComponent({ vis: vis, visParams: vis.params });
});
it('should set the metric label and value', function() {
const metrics = metricController._processTableGroups({
columns: [{ id: 'col-0', name: 'Count' }],
rows: [{ 'col-0': 4301021 }],
});
expect(metrics.length).to.be(1);
expect(metrics[0].label).to.be('Count');
expect(metrics[0].value).to.be('<span ng-non-bindable>4301021</span>');
});
it('should support multi-value metrics', function() {
vis.params.dimensions.metrics.push({ accessor: 1 });
const metrics = metricController._processTableGroups({
columns: [
{ id: 'col-0', name: '1st percentile of bytes' },
{ id: 'col-1', name: '99th percentile of bytes' },
],
rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }],
});
expect(metrics.length).to.be(2);
expect(metrics[0].label).to.be('1st percentile of bytes');
expect(metrics[0].value).to.be('<span ng-non-bindable>182</span>');
expect(metrics[1].label).to.be('99th percentile of bytes');
expect(metrics[1].value).to.be('<span ng-non-bindable>445842.4634666484</span>');
});
});

View file

@ -0,0 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MetricVisComponent should render correct structure for single metric 1`] = `
<div
className="mtrVis"
>
<MetricVisValue
key="0"
metric={
Object {
"bgColor": undefined,
"color": undefined,
"label": "Count",
"lightText": false,
"rowIndex": 0,
"value": "<span ng-non-bindable>4301021</span>",
}
}
showLabel={true}
/>
</div>
`;
exports[`MetricVisComponent should render correct structure for multi-value metrics 1`] = `
<div
className="mtrVis"
>
<MetricVisValue
key="0"
metric={
Object {
"bgColor": undefined,
"color": undefined,
"label": "1st percentile of bytes",
"lightText": false,
"rowIndex": 0,
"value": "<span ng-non-bindable>182</span>",
}
}
showLabel={true}
/>
<MetricVisValue
key="1"
metric={
Object {
"bgColor": undefined,
"color": undefined,
"label": "99th percentile of bytes",
"lightText": false,
"rowIndex": 0,
"value": "<span ng-non-bindable>445842.4634666484</span>",
}
}
showLabel={true}
/>
</div>
`;

View file

@ -0,0 +1,93 @@
/*
* 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 from 'react';
import { shallow } from 'enzyme';
import { MetricVisComponent } from './metric_vis_component';
import { Vis } from '../legacy_imports';
jest.mock('ui/new_platform');
type Props = MetricVisComponent['props'];
const baseVisData = {
columns: [{ id: 'col-0', name: 'Count' }],
rows: [{ 'col-0': 4301021 }],
} as any;
describe('MetricVisComponent', function() {
const vis: Vis = {
params: {
metric: {
colorSchema: 'Green to Red',
colorsRange: [{ from: 0, to: 1000 }],
style: {},
labels: {
show: true,
},
},
dimensions: {
metrics: [{ accessor: 0 }],
bucket: null,
},
},
} as any;
const getComponent = (propOverrides: Partial<Props> = {} as Partial<Props>) => {
const props: Props = {
vis,
visParams: vis.params,
visData: baseVisData,
renderComplete: jest.fn(),
...propOverrides,
};
return shallow(<MetricVisComponent {...props} />);
};
it('should render component', () => {
expect(getComponent().exists()).toBe(true);
});
it('should render correct structure for single metric', function() {
expect(getComponent()).toMatchSnapshot();
});
it('should render correct structure for multi-value metrics', function() {
const component = getComponent({
visData: {
columns: [
{ id: 'col-0', name: '1st percentile of bytes' },
{ id: 'col-1', name: '99th percentile of bytes' },
],
rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }],
},
visParams: {
...vis.params,
dimensions: {
...vis.params.dimensions,
metrics: [{ accessor: 0 }, { accessor: 1 }],
},
},
} as any);
expect(component).toMatchSnapshot();
});
});

View file

@ -19,20 +19,33 @@
import { last, findIndex, isNaN } from 'lodash';
import React, { Component } from 'react';
import { isColorDark } from '@elastic/eui';
import { getHeatmapColors } from 'ui/color_maps';
import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
import { getHeatmapColors, getFormat, Vis } from '../legacy_imports';
import { MetricVisValue } from './metric_vis_value';
import { FieldFormat, ContentType } from '../../../../../plugins/data/public';
import { Context } from '../metric_vis_fn';
import { KibanaDatatable } from '../../../../../plugins/expressions/public';
import { VisParams, MetricVisMetric } from '../types';
import { SchemaConfig } from '../../../visualizations/public';
export class MetricVisComponent extends Component {
_getLabels() {
interface MetricVisComponentProps {
visParams: VisParams;
visData: Context;
vis: Vis;
renderComplete: () => void;
}
export class MetricVisComponent extends Component<MetricVisComponentProps> {
private getLabels() {
const config = this.props.visParams.metric;
const isPercentageMode = config.percentageMode;
const colorsRange = config.colorsRange;
const max = last(colorsRange).to;
const labels = [];
colorsRange.forEach(range => {
const labels: string[] = [];
colorsRange.forEach((range: any) => {
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}`);
@ -41,13 +54,13 @@ export class MetricVisComponent extends Component {
return labels;
}
_getColors() {
private getColors() {
const config = this.props.visParams.metric;
const invertColors = config.invertColors;
const colorSchema = config.colorSchema;
const colorsRange = config.colorsRange;
const labels = this._getLabels();
const colors = {};
const labels = this.getLabels();
const colors: any = {};
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;
@ -56,9 +69,9 @@ export class MetricVisComponent extends Component {
return colors;
}
_getBucket(val) {
private getBucket(val: number) {
const config = this.props.visParams.metric;
let bucket = findIndex(config.colorsRange, range => {
let bucket = findIndex(config.colorsRange, (range: any) => {
return range.from <= val && range.to > val;
});
@ -70,59 +83,65 @@ export class MetricVisComponent extends Component {
return bucket;
}
_getColor(val, labels, colors) {
const bucket = this._getBucket(val);
private getColor(val: number, labels: string[], colors: { [label: string]: string }) {
const bucket = this.getBucket(val);
const label = labels[bucket];
return colors[label];
}
_needsLightText(bgColor) {
const color = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor);
if (!color) {
private needsLightText(bgColor: string) {
const colors = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor);
if (!colors) {
return false;
}
return isColorDark(parseInt(color[1]), parseInt(color[2]), parseInt(color[3]));
const [red, green, blue] = colors.slice(1).map(parseInt);
return isColorDark(red, green, blue);
}
_getFormattedValue = (fieldFormatter, value, format = 'text') => {
private getFormattedValue = (
fieldFormatter: FieldFormat,
value: any,
format: ContentType = 'text'
) => {
if (isNaN(value)) return '-';
return fieldFormatter.convert(value, format);
};
_processTableGroups(table) {
private processTableGroups(table: KibanaDatatable) {
const config = this.props.visParams.metric;
const dimensions = this.props.visParams.dimensions;
const isPercentageMode = config.percentageMode;
const min = config.colorsRange[0].from;
const max = last(config.colorsRange).to;
const colors = this._getColors();
const labels = this._getLabels();
const metrics = [];
const colors = this.getColors();
const labels = this.getLabels();
const metrics: MetricVisMetric[] = [];
let bucketColumnId;
let bucketFormatter;
let bucketColumnId: string;
let bucketFormatter: FieldFormat;
if (dimensions.bucket) {
bucketColumnId = table.columns[dimensions.bucket.accessor].id;
bucketFormatter = getFormat(dimensions.bucket.format);
}
dimensions.metrics.forEach(metric => {
dimensions.metrics.forEach((metric: SchemaConfig) => {
const columnIndex = metric.accessor;
const column = table.columns[columnIndex];
const column = table?.columns[columnIndex];
const formatter = getFormat(metric.format);
table.rows.forEach((row, rowIndex) => {
let title = column.name;
let value = row[column.id];
const color = this._getColor(value, labels, colors);
let value: any = row[column.id];
const color = this.getColor(value, labels, colors);
if (isPercentageMode) {
value = (value - min) / (max - min);
}
value = this._getFormattedValue(formatter, value, 'html');
value = this.getFormattedValue(formatter, value, 'html');
if (bucketColumnId) {
const bucketValue = this._getFormattedValue(bucketFormatter, row[bucketColumnId]);
const bucketValue = this.getFormattedValue(bucketFormatter, row[bucketColumnId]);
title = `${bucketValue} - ${title}`;
}
@ -130,11 +149,11 @@ export class MetricVisComponent extends Component {
metrics.push({
label: title,
value: value,
color: shouldColor && config.style.labelColor ? color : null,
bgColor: shouldColor && config.style.bgColor ? color : null,
lightText: shouldColor && config.style.bgColor && this._needsLightText(color),
rowIndex: rowIndex,
value,
color: shouldColor && config.style.labelColor ? color : undefined,
bgColor: shouldColor && config.style.bgColor ? color : undefined,
lightText: shouldColor && config.style.bgColor && this.needsLightText(color),
rowIndex,
});
});
});
@ -142,7 +161,7 @@ export class MetricVisComponent extends Component {
return metrics;
}
_filterBucket = metric => {
private filterBucket = (metric: MetricVisMetric) => {
const dimensions = this.props.visParams.dimensions;
if (!dimensions.bucket) {
return;
@ -155,27 +174,18 @@ export class MetricVisComponent extends Component {
});
};
_renderMetric = (metric, index) => {
private renderMetric = (metric: MetricVisMetric, index: number) => {
return (
<MetricVisValue
key={index}
metric={metric}
fontSize={this.props.visParams.metric.style.fontSize}
onFilter={this.props.visParams.dimensions.bucket ? this._filterBucket : null}
onFilter={this.props.visParams.dimensions.bucket ? this.filterBucket : undefined}
showLabel={this.props.visParams.metric.labels.show}
/>
);
};
render() {
let metricsHtml;
if (this.props.visData) {
const metrics = this._processTableGroups(this.props.visData);
metricsHtml = metrics.map(this._renderMetric);
}
return <div className="mtrVis">{metricsHtml}</div>;
}
componentDidMount() {
this.props.renderComplete();
}
@ -183,4 +193,13 @@ export class MetricVisComponent extends Component {
componentDidUpdate() {
this.props.renderComplete();
}
render() {
let metricsHtml;
if (this.props.visData) {
const metrics = this.processTableGroups(this.props.visData);
metricsHtml = metrics.map(this.renderMetric);
}
return <div className="mtrVis">{metricsHtml}</div>;
}
}

View file

@ -29,7 +29,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { VisOptionsProps } from 'ui/vis/editors/default';
import { VisOptionsProps } from '../legacy_imports';
import {
ColorRanges,
ColorSchemaOptions,
@ -39,6 +39,7 @@ import {
} from '../../../vis_type_vislib/public/components';
import { ColorModes } from '../../../vis_type_vislib/public/utils/collections';
import { MetricVisParam, VisParams } from '../types';
import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges';
function MetricVisOptions({
stateParams,
@ -135,7 +136,7 @@ function MetricVisOptions({
<ColorRanges
data-test-subj="metricColorRange"
colorsRange={stateParams.metric.colorsRange}
setValue={setMetricValue}
setValue={setMetricValue as SetColorRangeValue}
setTouched={setTouched}
setValidity={setValidity}
/>

View file

@ -21,34 +21,32 @@ import { shallow } from 'enzyme';
import { MetricVisValue } from './metric_vis_value';
const baseMetric = { label: 'Foo', value: 'foo' } as any;
describe('MetricVisValue', () => {
it('should be wrapped in EuiKeyboardAccessible if having a click listener', () => {
const component = shallow(
<MetricVisValue fontSize={12} metric={{ label: 'Foo', value: 'foo' }} onFilter={() => {}} />
<MetricVisValue fontSize={12} metric={baseMetric} onFilter={() => {}} />
);
expect(component.find('EuiKeyboardAccessible').exists()).toBe(true);
});
it('should not be wrapped in EuiKeyboardAccessible without having a click listener', () => {
const component = shallow(
<MetricVisValue fontSize={12} metric={{ label: 'Foo', value: 'foo' }} />
);
const component = shallow(<MetricVisValue fontSize={12} metric={baseMetric} />);
expect(component.find('EuiKeyboardAccessible').exists()).toBe(false);
});
it('should add -isfilterable class if onFilter is provided', () => {
const onFilter = jest.fn();
const component = shallow(
<MetricVisValue fontSize={12} metric={{ label: 'Foo', value: 'foo' }} onFilter={onFilter} />
<MetricVisValue fontSize={12} metric={baseMetric} onFilter={onFilter} />
);
component.simulate('click');
expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(1);
});
it('should not add -isfilterable class if onFilter is not provided', () => {
const component = shallow(
<MetricVisValue fontSize={12} metric={{ label: 'Foo', value: 'foo' }} onFilter={null} />
);
const component = shallow(<MetricVisValue fontSize={12} metric={baseMetric} />);
component.simulate('click');
expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(0);
});
@ -56,9 +54,9 @@ describe('MetricVisValue', () => {
it('should call onFilter callback if provided', () => {
const onFilter = jest.fn();
const component = shallow(
<MetricVisValue fontSize={12} metric={{ label: 'Foo', value: 'foo' }} onFilter={onFilter} />
<MetricVisValue fontSize={12} metric={baseMetric} onFilter={onFilter} />
);
component.find('.mtrVis__container-isfilterable').simulate('click');
expect(onFilter).toHaveBeenCalledWith({ label: 'Foo', value: 'foo' });
expect(onFilter).toHaveBeenCalledWith(baseMetric);
});
});

View file

@ -17,26 +17,36 @@
* under the License.
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import React, { Component, KeyboardEvent } from 'react';
import classNames from 'classnames';
import { EuiKeyboardAccessible, keyCodes } from '@elastic/eui';
class MetricVisValue extends Component {
import { MetricVisMetric } from '../types';
interface MetricVisValueProps {
metric: MetricVisMetric;
fontSize: number;
onFilter?: (metric: MetricVisMetric) => void;
showLabel?: boolean;
}
export class MetricVisValue extends Component<MetricVisValueProps> {
onClick = () => {
this.props.onFilter(this.props.metric);
if (this.props.onFilter) {
this.props.onFilter(this.props.metric);
}
};
onKeyPress = e => {
if (e.keyCode === keyCodes.ENTER) {
onKeyPress = (event: KeyboardEvent<HTMLDivElement>) => {
if (event.keyCode === keyCodes.ENTER) {
this.onClick();
}
};
render() {
const { fontSize, metric, onFilter, showLabel } = this.props;
const hasFilter = !!onFilter;
const hasFilter = Boolean(onFilter);
const metricValueStyle = {
fontSize: `${fontSize}pt`,
@ -52,10 +62,10 @@ class MetricVisValue extends Component {
<div
className={containerClassName}
style={{ backgroundColor: metric.bgColor }}
onClick={hasFilter ? this.onClick : null}
onKeyPress={hasFilter ? this.onKeyPress : null}
tabIndex={hasFilter ? 0 : null}
role={hasFilter ? 'button' : null}
onClick={hasFilter ? this.onClick : undefined}
onKeyPress={hasFilter ? this.onKeyPress : undefined}
tabIndex={hasFilter ? 0 : undefined}
role={hasFilter ? 'button' : undefined}
>
<div
className="mtrVis__value"
@ -68,7 +78,7 @@ class MetricVisValue extends Component {
* `metric.value` is set by the MetricVisComponent, so this component must make sure this value never contains
* any unsafe HTML (e.g. by bypassing the field formatter).
*/
dangerouslySetInnerHTML={{ __html: metric.value }} //eslint-disable-line react/no-danger
dangerouslySetInnerHTML={{ __html: metric.value }} // eslint-disable-line react/no-danger
/>
{showLabel && <div>{metric.label}</div>}
</div>
@ -81,12 +91,3 @@ class MetricVisValue extends Component {
return metricComponent;
}
}
MetricVisValue.propTypes = {
fontSize: PropTypes.number.isRequired,
metric: PropTypes.object.isRequired,
onFilter: PropTypes.func,
showLabel: PropTypes.bool,
};
export { MetricVisValue };

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
export { Vis, VisParams } from 'ui/vis';
export { vislibColorMaps, colorSchemas, ColorSchemas } from 'ui/color_maps';
export { getHeatmapColors } from 'ui/color_maps';
export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities';
export { VisOptionsProps } from 'ui/vis/editors/default';
// @ts-ignore
export { Schemas } from 'ui/vis/editors/default/schemas';
export { AggGroupNames } from 'ui/vis/editors/default';

View file

@ -19,7 +19,7 @@
import { i18n } from '@kbn/i18n';
import { vislibColorMaps, ColorSchemas } from 'ui/color_maps';
import { vislibColorMaps, ColorSchemas } from './legacy_imports';
import {
ExpressionFunction,
KibanaDatatable,
@ -30,13 +30,13 @@ import {
import { ColorModes } from '../../vis_type_vislib/public/utils/collections';
import { visType, DimensionsVisParam, VisParams } from './types';
type Context = KibanaDatatable;
export type Context = KibanaDatatable;
const name = 'metricVis';
interface Arguments {
percentage: boolean;
colorScheme: ColorSchemas;
percentageMode: boolean;
colorSchema: ColorSchemas;
colorMode: ColorModes;
useRanges: boolean;
invertColors: boolean;
@ -73,19 +73,19 @@ export const createMetricVisFn = (): ExpressionFunction<
defaultMessage: 'Metric visualization',
}),
args: {
percentage: {
percentageMode: {
types: ['boolean'],
default: false,
help: i18n.translate('visTypeMetric.function.percentage.help', {
help: i18n.translate('visTypeMetric.function.percentageMode.help', {
defaultMessage: 'Shows metric in percentage mode. Requires colorRange to be set.',
}),
},
colorScheme: {
colorSchema: {
types: ['string'],
default: '"Green to Red"',
options: Object.values(vislibColorMaps).map((value: any) => value.id),
help: i18n.translate('visTypeMetric.function.colorScheme.help', {
defaultMessage: 'Color scheme to use',
help: i18n.translate('visTypeMetric.function.colorSchema.help', {
defaultMessage: 'Color schema to use',
}),
},
colorMode: {
@ -174,8 +174,8 @@ export const createMetricVisFn = (): ExpressionFunction<
dimensions.bucket = args.bucket;
}
if (args.percentage && (!args.colorRange || args.colorRange.length === 0)) {
throw new Error('colorRange must be provided when using percentage');
if (args.percentageMode && (!args.colorRange || args.colorRange.length === 0)) {
throw new Error('colorRange must be provided when using percentageMode');
}
const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10);
@ -188,9 +188,9 @@ export const createMetricVisFn = (): ExpressionFunction<
visType,
visConfig: {
metric: {
percentageMode: args.percentage,
percentageMode: args.percentageMode,
useRanges: args.useRanges,
colorSchema: args.colorScheme,
colorSchema: args.colorSchema,
metricColorMode: args.colorMode,
colorsRange: args.colorRange,
labels: {

View file

@ -0,0 +1,106 @@
/*
* 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 $ from 'jquery';
import { npStart } from 'ui/new_platform';
// @ts-ignore
import getStubIndexPattern from 'fixtures/stubbed_logstash_index_pattern';
import { Vis } from '../../visualizations/public';
import { UrlFormat } from '../../../../plugins/data/public';
import {
setup as visualizationsSetup,
start as visualizationsStart,
} from '../../visualizations/public/np_ready/public/legacy';
import { metricVisTypeDefinition } from './metric_vis_type';
jest.mock('ui/new_platform');
describe('metric_vis - createMetricVisTypeDefinition', () => {
let vis: Vis;
beforeAll(() => {
visualizationsSetup.types.createReactVisualization(metricVisTypeDefinition);
(npStart.plugins.data.fieldFormats.getType as jest.Mock).mockImplementation(() => {
return UrlFormat;
});
});
const setup = () => {
const stubIndexPattern = getStubIndexPattern();
stubIndexPattern.stubSetFieldFormat('ip', 'url', {
urlTemplate: 'http://ip.info?address={{value}}',
labelTemplate: 'ip[{{value}}]',
});
// TODO: remove when Vis is converted to typescript. Only importing Vis as type
// @ts-ignore
vis = new Vis(stubIndexPattern, {
type: 'metric',
aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
});
vis.params.dimensions = {
metrics: [
{
accessor: 0,
format: {
id: 'url',
params: {
urlTemplate: 'http://ip.info?address={{value}}',
labelTemplate: 'ip[{{value}}]',
},
},
},
],
};
const el = document.createElement('div');
const metricVisType = visualizationsStart.types.get('metric');
const Controller = metricVisType.visualization;
const controller = new Controller(el, vis);
const render = (esResponse: any) => {
controller.render(esResponse, vis.params);
};
return { el, render };
};
it('renders html value from field formatter', () => {
const { el, render } = setup();
const ip = '235.195.237.208';
render({
columns: [{ id: 'col-0', name: 'ip' }],
rows: [{ 'col-0': ip }],
});
const links = $(el)
.find('a[href]')
.filter(function() {
// @ts-ignore
return this.href.includes('ip.info');
});
expect(links.length).toBe(1);
expect(links.text()).toBe(`ip[${ip}]`);
});
});

View file

@ -19,19 +19,12 @@
import { i18n } from '@kbn/i18n';
// @ts-ignore
import { Schemas } from 'ui/vis/editors/default/schemas';
import { AggGroupNames } from 'ui/vis/editors/default';
import { colorSchemas, ColorSchemas } from 'ui/color_maps';
// @ts-ignore
import { MetricVisComponent } from './components/metric_vis_controller';
import { MetricVisComponent } from './components/metric_vis_component';
import { MetricVisOptions } from './components/metric_vis_options';
import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports';
import { ColorModes } from '../../vis_type_vislib/public/utils/collections';
export const metricVisDefinition = {
export const metricVisTypeDefinition = {
name: 'metric',
title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }),
icon: 'visMetric',

View file

@ -22,7 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio
import { VisualizationsSetup } from '../../visualizations/public';
import { createMetricVisFn } from './metric_vis_fn';
import { metricVisDefinition } from './metric_vis_type';
import { metricVisTypeDefinition } from './metric_vis_type';
/** @internal */
export interface MetricVisPluginSetupDependencies {
@ -40,7 +40,7 @@ export class MetricVisPlugin implements Plugin<void, void> {
public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) {
expressions.registerFunction(createMetricVisFn);
visualizations.types.createReactVisualization(metricVisDefinition);
visualizations.types.createReactVisualization(metricVisTypeDefinition);
}
public start(core: CoreStart) {

View file

@ -17,8 +17,8 @@
* under the License.
*/
import { ColorSchemas } from 'ui/color_maps';
import { RangeValues } from 'ui/vis/editors/default/controls/ranges';
import { ColorSchemas } from './legacy_imports';
import { Range } from '../../../../plugins/expressions/public';
import { SchemaConfig } from '../../visualizations/public';
import { ColorModes } from '../../vis_type_vislib/public/utils/collections';
import { Labels, Style } from '../../vis_type_vislib/public/types';
@ -27,7 +27,7 @@ export const visType = 'metric';
export interface DimensionsVisParam {
metrics: SchemaConfig[];
bucket?: SchemaConfig[];
bucket?: SchemaConfig;
}
export interface MetricVisParam {
@ -35,7 +35,7 @@ export interface MetricVisParam {
useRanges: boolean;
colorSchema: ColorSchemas;
metricColorMode: ColorModes;
colorsRange: RangeValues[];
colorsRange: Range[];
labels: Labels;
invertColors: boolean;
style: Style;
@ -48,3 +48,12 @@ export interface VisParams {
metric: MetricVisParam;
type: typeof visType;
}
export interface MetricVisMetric {
value: any;
label: string;
color?: string;
bgColor?: string;
lightText: boolean;
rowIndex: number;
}

View file

@ -24,10 +24,12 @@ import { i18n } from '@kbn/i18n';
import { RangeValues, RangesParamEditor } from '../../legacy_imports';
export type SetColorRangeValue = (paramName: string, value: RangeValues[]) => void;
interface ColorRangesProps {
'data-test-subj'?: string;
colorsRange: RangeValues[];
setValue(paramName: string, value: RangeValues[]): void;
setValue: SetColorRangeValue;
setValidity?(isValid: boolean): void;
setTouched?(isTouched: boolean): void;
}

View file

@ -27,6 +27,7 @@ import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common';
import { GaugeOptionsInternalProps } from '.';
import { ColorSchemaVislibParams } from '../../../types';
import { Gauge } from '../../../gauge';
import { SetColorRangeValue } from '../../common/color_ranges';
function RangesPanel({
setGaugeValue,
@ -68,7 +69,7 @@ function RangesPanel({
<ColorRanges
data-test-subj="gaugeColorRange"
colorsRange={stateParams.gauge.colorsRange}
setValue={setGaugeValue}
setValue={setGaugeValue as SetColorRangeValue}
setTouched={setTouched}
setValidity={setValidity}
/>

View file

@ -36,6 +36,7 @@ import { SetColorSchemaOptionsValue } from '../../common/color_schema';
import { HeatmapVisParams } from '../../../heatmap';
import { ValueAxis } from '../../../types';
import { LabelsPanel } from './labels_panel';
import { SetColorRangeValue } from '../../common/color_ranges';
function HeatmapOptions(props: VisOptionsProps<HeatmapVisParams>) {
const { stateParams, vis, uiState, setValue, setValidity, setTouched } = props;
@ -171,7 +172,7 @@ function HeatmapOptions(props: VisOptionsProps<HeatmapVisParams>) {
<ColorRanges
data-test-subj="heatmapColorRange"
colorsRange={stateParams.colorsRange}
setValue={setValue}
setValue={setValue as SetColorRangeValue}
setTouched={setTouched}
setValidity={setIsColorRangesValid}
/>

View file

@ -23,3 +23,5 @@ import { KbnVislibVisTypesPlugin as Plugin } from './plugin';
export function plugin(initializerContext: PluginInitializerContext) {
return new Plugin(initializerContext);
}
export { ColorModes } from './utils/collections';

View file

@ -8,7 +8,7 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentage=true metric={visdimension 0 format='percent' } "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentageMode=true metric={visdimension 0 format='percent' } "`;
exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function without buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`;

View file

@ -305,8 +305,8 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = {
}
let expr = `metricvis `;
expr += prepareValue('percentage', percentageMode);
expr += prepareValue('colorScheme', colorSchema);
expr += prepareValue('percentageMode', percentageMode);
expr += prepareValue('colorSchema', colorSchema);
expr += prepareValue('colorMode', metricColorMode);
expr += prepareValue('useRanges', useRanges);
expr += prepareValue('invertColors', invertColors);

View file

@ -81,11 +81,15 @@ export default function({
).toMatchScreenshot();
});
it('with percentage option', async () => {
it('with percentageMode option', async () => {
const expression =
'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}';
'metricVis metric={visdimension 0} percentageMode=true colorRange={range from=0 to=1000}';
await (
await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot()
await expectExpression(
'metric_percentage_mode',
expression,
dataContext
).toMatchSnapshot()
).toMatchScreenshot();
});
});

View file

@ -12,7 +12,8 @@
],
"test_utils/*": [
"src/test_utils/public/*"
]
],
"fixtures/*": ["src/fixtures/*"]
},
// Support .tsx files and transform JSX into calls to React.createElement
"jsx": "react",

View file

@ -17,6 +17,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) {
moduleFileExtensions: ['js', 'json', 'ts', 'tsx'],
moduleNameMapper: {
'^ui/(.*)': `${kibanaDirectory}/src/legacy/ui/public/$1`,
'^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`,
'uiExports/(.*)': fileMockPath,
'^src/core/(.*)': `${kibanaDirectory}/src/core/$1`,
'^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`,

View file

@ -3141,12 +3141,12 @@
"visTypeMetric.function.bucket.help": "バケットディメンションの構成です。",
"visTypeMetric.function.colorMode.help": "色を変更するメトリックの部分",
"visTypeMetric.function.colorRange.help": "別の色が適用される値のグループを指定する範囲オブジェクト。",
"visTypeMetric.function.colorScheme.help": "使用する配色",
"visTypeMetric.function.colorSchema.help": "使用する配色",
"visTypeMetric.function.font.help": "フォント設定です。",
"visTypeMetric.function.help": "メトリックビジュアライゼーション",
"visTypeMetric.function.invertColors.help": "色範囲を反転します",
"visTypeMetric.function.metric.help": "メトリックディメンションの構成です。",
"visTypeMetric.function.percentage.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。",
"visTypeMetric.function.percentageMode.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。",
"visTypeMetric.function.showLabels.help": "メトリック値の下にラベルを表示します。",
"visTypeMetric.function.subText.help": "メトリックの下に表示するカスタムテキスト",
"visTypeMetric.function.useRanges.help": "有効な色範囲です。",

View file

@ -3141,12 +3141,12 @@
"visTypeMetric.function.bucket.help": "存储桶维度配置",
"visTypeMetric.function.colorMode.help": "指标的哪部分要上色",
"visTypeMetric.function.colorRange.help": "指定应将不同颜色应用到的值组的范围对象。",
"visTypeMetric.function.colorScheme.help": "要使用的颜色方案",
"visTypeMetric.function.colorSchema.help": "要使用的颜色方案",
"visTypeMetric.function.font.help": "字体设置。",
"visTypeMetric.function.help": "指标可视化",
"visTypeMetric.function.invertColors.help": "反转颜色范围",
"visTypeMetric.function.metric.help": "指标维度配置",
"visTypeMetric.function.percentage.help": "以百分比模式显示指标。需要设置 colorRange。",
"visTypeMetric.function.percentageMode.help": "以百分比模式显示指标。需要设置 colorRange。",
"visTypeMetric.function.showLabels.help": "在指标值下显示标签。",
"visTypeMetric.function.subText.help": "要在指标下显示的定制文本",
"visTypeMetric.function.useRanges.help": "已启用颜色范围。",

View file

@ -38,7 +38,8 @@
"monitoring/common/*": [
"x-pack/monitoring/common/*"
],
"plugins/*": ["src/legacy/core_plugins/*/public/"]
"plugins/*": ["src/legacy/core_plugins/*/public/"],
"fixtures/*": ["src/fixtures/*"]
},
"types": [
"node",