diff --git a/src/legacy/core_plugins/metric_vis/public/metric_vis_controller.js b/src/legacy/core_plugins/metric_vis/public/metric_vis_controller.js index e8c646646ec4b..ecbb9d917874c 100644 --- a/src/legacy/core_plugins/metric_vis/public/metric_vis_controller.js +++ b/src/legacy/core_plugins/metric_vis/public/metric_vis_controller.js @@ -119,11 +119,9 @@ export class MetricVisComponent extends Component { const color = this._getColor(value, labels, colors); if (isPercentageMode) { - const percentage = Math.round(100 * (value - min) / (max - min)); - value = `${percentage}%`; - } else { - value = this._getFormattedValue(formatter, value, 'html'); + value = (value - min) / (max - min); } + value = this._getFormattedValue(formatter, value, 'html'); if (bucketColumnId) { const bucketValue = this._getFormattedValue(bucketFormatter, row[bucketColumnId]); diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js index 4c0aa00441747..8bf1c763a58c4 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js +++ b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js @@ -49,12 +49,7 @@ export function PointSeriesTooltipFormatterProvider($compile, $rootScope) { } if (datum.y) { const value = datum.yScale ? datum.yScale * datum.y : datum.y; - if(event.isPercentageMode) { - const valueInPercent = Math.round(value * 10000) / 100; - addDetail(currentSeries.label, `${valueInPercent.toFixed(2)} %`); - } else { - addDetail(currentSeries.label, currentSeries.yAxisFormatter(value)); - } + addDetail(currentSeries.label, currentSeries.yAxisFormatter(value)); } if (datum.z) { addDetail(currentSeries.zLabel, currentSeries.zAxisFormatter(datum.z)); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js b/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js index e8b340229544c..ea3f3c5f26320 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js @@ -342,16 +342,6 @@ describe('Vislib yAxis Class Test Suite', function () { yAxis = buildYAxis(); }); - it('should use percentage format for percentages', function () { - yAxis = buildYAxis({ - scale: { - mode: 'percentage' - } - }); - const tickFormat = yAxis.getAxis().tickFormat(); - expect(tickFormat(1)).to.be('100%'); - }); - it('should use decimal format for small values', function () { yAxis.yMax = 1; const tickFormat = yAxis.getAxis().tickFormat(); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js b/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js index bb4aa5679b374..13fdb1f054371 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js +++ b/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js @@ -113,15 +113,6 @@ describe('Vislib Gauge Chart Test Suite', function () { expect($(chartEl).find('svg').length).to.equal(5); }); - it('creates gauge with percentage mode', function () { - generateVis({ - gauge: { - percentageMode: true - } - }); - expect($(chartEl).find('svg > g > g > text').text()).to.equal('94%77%61%24%45%'); - }); - it('creates gauge with automatic mode', function () { generateVis({ gauge: { diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_config.js b/src/legacy/ui/public/vislib/lib/axis/axis_config.js index 7c49d7d39d2a9..1e0ec01933b87 100644 --- a/src/legacy/ui/public/vislib/lib/axis/axis_config.js +++ b/src/legacy/ui/public/vislib/lib/axis/axis_config.js @@ -136,11 +136,6 @@ export class AxisConfig { } } - // override axisFormatter (to replicate current behaviour) - if (this.isPercentage()) { - this._values.labels.axisFormatter = d3.format('%'); - } - if (this.isLogScale()) { this._values.labels.filter = true; } diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js index 834d579b2d93f..aa2a92842d383 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js +++ b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js @@ -264,8 +264,8 @@ export class MeterGauge { .attr('y', -5) .text(d => { if (this.gaugeConfig.percentageMode) { - const percentage = Math.round(100 * (d.y - min) / (max - min)); - return `${percentage}%`; + const percentage = (d.y - min) / (max - min); + return data.yAxisFormatter(percentage); } return data.yAxisFormatter(d.y); }) diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap b/src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.ts.snap similarity index 67% rename from src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap rename to src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.ts.snap index a6300be4f84a2..3da7a91378827 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.ts.snap @@ -8,25 +8,27 @@ 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 without buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metrics/tsvb function 1`] = `"tsvb params='{\\"foo\\":\\"bar\\"}' uiState='{}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles pie function 1`] = `"kibana_pie visConfig='{\\"dimensions\\":{\\"metric\\":0,\\"buckets\\":[1,2]}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles pie function 1`] = `"kibana_pie visConfig='{\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"buckets\\":[1,2]}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":0,\\"bucket\\":1}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":0}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with showPartialRows=true and showMetricsAtAllLevels=false 1`] = `"kibana_table visConfig='{\\"showMetricsAtAllLevels\\":false,\\"showPartialRows\\":true,\\"dimensions\\":{\\"metrics\\":[4,5],\\"buckets\\":[0,3]}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with showPartialRows=true and showMetricsAtAllLevels=false 1`] = `"kibana_table visConfig='{\\"showMetricsAtAllLevels\\":false,\\"showPartialRows\\":true,\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":4,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":5,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[0,3]}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with showPartialRows=true and showMetricsAtAllLevels=true 1`] = `"kibana_table visConfig='{\\"showMetricsAtAllLevels\\":true,\\"showPartialRows\\":true,\\"dimensions\\":{\\"metrics\\":[1,2,4,5],\\"buckets\\":[0,3]}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with showPartialRows=true and showMetricsAtAllLevels=true 1`] = `"kibana_table visConfig='{\\"showMetricsAtAllLevels\\":true,\\"showPartialRows\\":true,\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":1,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":2,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":4,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":5,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[0,3]}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[0],\\"buckets\\":[],\\"splitRow\\":[1,2]}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[],\\"splitRow\\":[1,2]}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits and buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[0,1],\\"buckets\\":[3],\\"splitRow\\":[2,4]}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits and buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":1,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[3],\\"splitRow\\":[2,4]}}' "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function without splits or buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[0,1],\\"buckets\\":[]}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function without splits or buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\",\\"dimensions\\":{\\"metrics\\":[{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},{\\"accessor\\":1,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}],\\"buckets\\":[]}}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function with boolean param showLabel 1`] = `"tagcloud metric={visdimension 0} showLabel=false "`; @@ -34,7 +36,7 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function without buckets 1`] = `"tagcloud metric={visdimension 0} "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":0,\\"geohash\\":1,\\"geocentroid\\":3}}' "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function 1`] = `"tilemap visConfig='{\\"metric\\":{},\\"dimensions\\":{\\"metric\\":{\\"accessor\\":0,\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"geohash\\":1,\\"geocentroid\\":3}}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles timelion function 1`] = `"timelion_vis expression='foo' interval='bar' "`; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js deleted file mode 100644 index 2a3d4d0ddab07..0000000000000 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js +++ /dev/null @@ -1,262 +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 { prepareJson, prepareString, buildPipelineVisFunction, buildPipeline } from './build_pipeline'; - -jest.mock('ui/agg_types/buckets/date_histogram', () => ({})); - -describe('visualize loader pipeline helpers: build pipeline', () => { - describe('prepareJson', () => { - it('returns a correctly formatted key/value string', () => { - const expected = `foo='{}' `; // trailing space is expected - const actual = prepareJson('foo', {}); - expect(actual).toBe(expected); - }); - - it('stringifies provided data', () => { - const expected = `foo='{\"well\":\"hello\",\"there\":{\"friend\":true}}' `; - const actual = prepareJson('foo', { well: 'hello', there: { friend: true } }); - expect(actual).toBe(expected); - }); - - it('escapes single quotes', () => { - const expected = `foo='{\"well\":\"hello \\'hi\\'\",\"there\":{\"friend\":true}}' `; - const actual = prepareJson('foo', { well: `hello 'hi'`, there: { friend: true } }); - expect(actual).toBe(expected); - }); - }); - - describe('prepareString', () => { - it('returns a correctly formatted key/value string', () => { - const expected = `foo='bar' `; // trailing space is expected - const actual = prepareString('foo', 'bar'); - expect(actual).toBe(expected); - }); - - it('escapes single quotes', () => { - const expected = `foo='\\'bar\\'' `; - const actual = prepareString('foo', `'bar'`); - expect(actual).toBe(expected); - }); - }); - - describe('buildPipelineVisFunction', () => { - it('handles vega function', () => { - const params = { spec: 'this is a test' }; - const actual = buildPipelineVisFunction.vega({ params }); - expect(actual).toMatchSnapshot(); - }); - - it('handles input_control_vis function', () => { - const params = { - some: 'nested', - data: { - here: true - } - }; - const actual = buildPipelineVisFunction.input_control_vis({ params }); - expect(actual).toMatchSnapshot(); - }); - - it('handles metrics/tsvb function', () => { - const params = { foo: 'bar' }; - const actual = buildPipelineVisFunction.metrics({ params }); - expect(actual).toMatchSnapshot(); - }); - - it('handles timelion function', () => { - const params = { expression: 'foo', interval: 'bar' }; - const actual = buildPipelineVisFunction.timelion({ params }); - expect(actual).toMatchSnapshot(); - }); - - it('handles markdown function', () => { - const params = { markdown: '## hello _markdown_', fontSize: 12, openLinksInNewTab: true, foo: 'bar' }; - const actual = buildPipelineVisFunction.markdown({ params }); - expect(actual).toMatchSnapshot(); - }); - - it('handles undefined markdown function', () => { - const params = { fontSize: 12, openLinksInNewTab: true, foo: 'bar' }; - const actual = buildPipelineVisFunction.markdown({ params }); - expect(actual).toMatchSnapshot(); - }); - - describe('handles table function', () => { - it('without splits or buckets', () => { - const params = { foo: 'bar' }; - const schemas = { metric: [0, 1] }; - const actual = buildPipelineVisFunction.table({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with splits', () => { - const params = { foo: 'bar' }; - const schemas = { - metric: [0], - split_row: [1, 2], - }; - const actual = buildPipelineVisFunction.table({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with splits and buckets', () => { - const params = { foo: 'bar' }; - const schemas = { - metric: [0, 1], - split_row: [2, 4], - bucket: [3], - }; - const actual = buildPipelineVisFunction.table({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with showPartialRows=true and showMetricsAtAllLevels=true', () => { - const params = { - showMetricsAtAllLevels: true, - showPartialRows: true, - }; - const schemas = { - metric: [1, 2, 4, 5], - bucket: [0, 3], - }; - const actual = buildPipelineVisFunction.table({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with showPartialRows=true and showMetricsAtAllLevels=false', () => { - const params = { - showMetricsAtAllLevels: false, - showPartialRows: true, - }; - const schemas = { - metric: [1, 2, 4, 5], - bucket: [0, 3], - }; - const actual = buildPipelineVisFunction.table({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - }); - - describe('handles metric function', () => { - const params = { metric: {} }; - it('without buckets', () => { - const schemas = { metric: [{ accessor: 0 }, { accessor: 1 }] }; - const actual = buildPipelineVisFunction.metric({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with buckets', () => { - const schemas = { - metric: [{ accessor: 0 }, { accessor: 1 }], - group: [{ accessor: 2 }] - }; - const actual = buildPipelineVisFunction.metric({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - }); - - describe('handles tagcloud function', () => { - const params = {}; - - it('without buckets', () => { - const schemas = { metric: [{ accessor: 0 }] }; - const actual = buildPipelineVisFunction.tagcloud({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with buckets', () => { - const schemas = { - metric: [{ accessor: 0 }], - segment: [{ accessor: 1 }] - }; - const actual = buildPipelineVisFunction.tagcloud({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with boolean param showLabel', () => { - const schemas = { metric: [{ accessor: 0 }] }; - const params = { showLabel: false }; - const actual = buildPipelineVisFunction.tagcloud({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - }); - - describe('handles region_map function', () => { - const params = { metric: {} }; - it('without buckets', () => { - const schemas = { metric: [0] }; - const actual = buildPipelineVisFunction.region_map({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('with buckets', () => { - const schemas = { - metric: [0], - segment: [1, 2] - }; - const actual = buildPipelineVisFunction.region_map({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - }); - - it('handles tile_map function', () => { - const params = { metric: {} }; - const schemas = { - metric: [0], - segment: [1, 2], - geo_centroid: [3, 4] - }; - const actual = buildPipelineVisFunction.tile_map({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - - it('handles pie function', () => { - const params = {}; - const schemas = { - metric: [0], - segment: [1, 2] - }; - const actual = buildPipelineVisFunction.pie({ params }, schemas); - expect(actual).toMatchSnapshot(); - }); - }); - - describe('buildPipeline', () => { - it('calls toExpression on vis_type if it exists', async () => { - const vis = { - getCurrentState: () => {}, - getUiState: () => null, - isHierarchical: () => false, - aggs: { - getResponseAggs: () => [], - }, - type: { - toExpression: () => 'testing custom expressions', - } - }; - const searchSource = { - getField: () => null, - }; - const expression = await buildPipeline(vis, { searchSource }); - expect(expression).toMatchSnapshot(); - }); - }); -}); diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts new file mode 100644 index 0000000000000..d98872cb3402f --- /dev/null +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts @@ -0,0 +1,510 @@ +/* + * 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 { + prepareJson, + prepareString, + buildPipelineVisFunction, + buildVislibDimensions, + buildPipeline, + SchemaConfig, + Schemas, +} from './build_pipeline'; +import { Vis, VisState } from 'ui/vis'; +import { AggConfig } from 'ui/vis/agg_config'; +import { SearchSource } from 'ui/courier'; + +jest.mock('ui/agg_types/buckets/date_histogram', () => ({})); + +describe('visualize loader pipeline helpers: build pipeline', () => { + describe('prepareJson', () => { + it('returns a correctly formatted key/value string', () => { + const expected = `foo='{}' `; // trailing space is expected + const actual = prepareJson('foo', {}); + expect(actual).toBe(expected); + }); + + it('stringifies provided data', () => { + const expected = `foo='{\"well\":\"hello\",\"there\":{\"friend\":true}}' `; + const actual = prepareJson('foo', { well: 'hello', there: { friend: true } }); + expect(actual).toBe(expected); + }); + + it('escapes single quotes', () => { + const expected = `foo='{\"well\":\"hello \\'hi\\'\",\"there\":{\"friend\":true}}' `; + const actual = prepareJson('foo', { well: `hello 'hi'`, there: { friend: true } }); + expect(actual).toBe(expected); + }); + }); + + describe('prepareString', () => { + it('returns a correctly formatted key/value string', () => { + const expected = `foo='bar' `; // trailing space is expected + const actual = prepareString('foo', 'bar'); + expect(actual).toBe(expected); + }); + + it('escapes single quotes', () => { + const expected = `foo='\\'bar\\'' `; + const actual = prepareString('foo', `'bar'`); + expect(actual).toBe(expected); + }); + }); + + describe('buildPipelineVisFunction', () => { + let visStateDef: VisState; + let schemaConfig: SchemaConfig; + let schemasDef: Schemas; + let uiState: any; + + beforeEach(() => { + visStateDef = { + title: 'title', + // @ts-ignore + type: 'type', + params: {}, + }; + + schemaConfig = { + accessor: 0, + format: {}, + params: {}, + aggType: '', + }; + + schemasDef = { metric: [schemaConfig] }; + uiState = {}; + }); + + it('handles vega function', () => { + const vis = { + ...visStateDef, + params: { spec: 'this is a test' }, + }; + const actual = buildPipelineVisFunction.vega(vis, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('handles input_control_vis function', () => { + const visState = { + ...visStateDef, + params: { + some: 'nested', + data: { here: true }, + }, + }; + const actual = buildPipelineVisFunction.input_control_vis(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('handles metrics/tsvb function', () => { + const visState = { ...visStateDef, params: { foo: 'bar' } }; + const actual = buildPipelineVisFunction.metrics(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('handles timelion function', () => { + const visState = { + ...visStateDef, + params: { expression: 'foo', interval: 'bar' }, + }; + const actual = buildPipelineVisFunction.timelion(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('handles markdown function', () => { + const visState = { + ...visStateDef, + params: { + markdown: '## hello _markdown_', + fontSize: 12, + openLinksInNewTab: true, + foo: 'bar', + }, + }; + const actual = buildPipelineVisFunction.markdown(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('handles undefined markdown function', () => { + const visState = { + ...visStateDef, + params: { fontSize: 12, openLinksInNewTab: true, foo: 'bar' }, + }; + const actual = buildPipelineVisFunction.markdown(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + describe('handles table function', () => { + it('without splits or buckets', () => { + const visState = { ...visStateDef, params: { foo: 'bar' } }; + const schemas = { + ...schemasDef, + metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }], + }; + const actual = buildPipelineVisFunction.table(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with splits', () => { + const visState = { ...visStateDef, params: { foo: 'bar' } }; + const schemas = { + ...schemasDef, + split_row: [1, 2], + }; + const actual = buildPipelineVisFunction.table(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with splits and buckets', () => { + const visState = { ...visStateDef, params: { foo: 'bar' } }; + const schemas = { + ...schemasDef, + metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }], + split_row: [2, 4], + bucket: [3], + }; + const actual = buildPipelineVisFunction.table(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with showPartialRows=true and showMetricsAtAllLevels=true', () => { + const visState = { + ...visStateDef, + params: { + showMetricsAtAllLevels: true, + showPartialRows: true, + }, + }; + const schemas = { + ...schemasDef, + metric: [ + { ...schemaConfig, accessor: 1 }, + { ...schemaConfig, accessor: 2 }, + { ...schemaConfig, accessor: 4 }, + { ...schemaConfig, accessor: 5 }, + ], + bucket: [0, 3], + }; + const actual = buildPipelineVisFunction.table(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with showPartialRows=true and showMetricsAtAllLevels=false', () => { + const visState = { + ...visStateDef, + params: { + showMetricsAtAllLevels: false, + showPartialRows: true, + }, + }; + const schemas = { + ...schemasDef, + metric: [ + { ...schemaConfig, accessor: 1 }, + { ...schemaConfig, accessor: 2 }, + { ...schemaConfig, accessor: 4 }, + { ...schemaConfig, accessor: 5 }, + ], + bucket: [0, 3], + }; + const actual = buildPipelineVisFunction.table(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + }); + + describe('handles metric function', () => { + it('without buckets', () => { + const visState = { ...visStateDef, params: { metric: {} } }; + const schemas = { + ...schemasDef, + metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }], + }; + const actual = buildPipelineVisFunction.metric(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with buckets', () => { + const visState = { ...visStateDef, params: { metric: {} } }; + const schemas = { + ...schemasDef, + metric: [{ ...schemaConfig, accessor: 0 }, { ...schemaConfig, accessor: 1 }], + group: [{ accessor: 2 }], + }; + const actual = buildPipelineVisFunction.metric(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with percentage mode should have percentage format', () => { + const visState = { ...visStateDef, params: { metric: { percentageMode: true } } }; + const schemas = { ...schemasDef }; + const actual = buildPipelineVisFunction.metric(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + }); + + describe('handles tagcloud function', () => { + it('without buckets', () => { + const actual = buildPipelineVisFunction.tagcloud(visStateDef, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with buckets', () => { + const schemas = { + ...schemasDef, + segment: [{ accessor: 1 }], + }; + const actual = buildPipelineVisFunction.tagcloud(visStateDef, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with boolean param showLabel', () => { + const visState = { ...visStateDef, params: { showLabel: false } }; + const actual = buildPipelineVisFunction.tagcloud(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + }); + + describe('handles region_map function', () => { + it('without buckets', () => { + const visState = { ...visStateDef, params: { metric: {} } }; + const actual = buildPipelineVisFunction.region_map(visState, schemasDef, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('with buckets', () => { + const schemas = { + ...schemasDef, + segment: [1, 2], + }; + const actual = buildPipelineVisFunction.region_map(visStateDef, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + }); + + it('handles tile_map function', () => { + const visState = { ...visStateDef, params: { metric: {} } }; + const schemas = { + ...schemasDef, + segment: [1, 2], + geo_centroid: [3, 4], + }; + const actual = buildPipelineVisFunction.tile_map(visState, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + + it('handles pie function', () => { + const schemas = { + ...schemasDef, + segment: [1, 2], + }; + const actual = buildPipelineVisFunction.pie(visStateDef, schemas, uiState); + expect(actual).toMatchSnapshot(); + }); + }); + + describe('buildPipeline', () => { + it('calls toExpression on vis_type if it exists', async () => { + const vis: Vis = { + getCurrentState: () => {}, + getUiState: () => null, + isHierarchical: () => false, + aggs: { + getResponseAggs: () => [], + }, + // @ts-ignore + type: { + toExpression: () => 'testing custom expressions', + }, + }; + const searchSource: SearchSource = { + getField: () => null, + }; + const expression = await buildPipeline(vis, { searchSource }); + expect(expression).toMatchSnapshot(); + }); + }); + + describe('buildVislibDimensions', () => { + let aggs: AggConfig[]; + let visState: any; + let vis: Vis; + let params: any; + + beforeEach(() => { + aggs = [ + { + id: 0, + enabled: true, + type: { + type: 'metrics', + name: 'count', + }, + schema: { + name: 'metric', + }, + params: {}, + }, + ]; + + params = { + searchSource: null, + timeRange: null, + }; + }); + + // todo: cover basic buildVislibDimensions's functionalities + + describe('test y dimension format for histogram chart', () => { + beforeEach(() => { + visState = { + params: { + seriesParams: [ + { + data: { id: 0 }, + valueAxis: 'axis-y', + }, + ], + valueAxes: [ + { + id: 'axis-y', + scale: { + mode: 'normal', + }, + }, + ], + }, + }; + + vis = { + // @ts-ignore + type: { + name: 'histogram', + }, + aggs: { + getResponseAggs: () => { + return aggs; + }, + }, + isHierarchical: () => { + return false; + }, + getCurrentState: () => { + return visState; + }, + }; + }); + + it('with one numeric metric in regular moder', async () => { + const dimensions = await buildVislibDimensions(vis, params); + const expected = { id: 'number' }; + const actual = dimensions.y[0].format; + expect(actual).toEqual(expected); + }); + + it('with one numeric metric in percentage mode', async () => { + visState.params.valueAxes[0].scale.mode = 'percentage'; + const dimensions = await buildVislibDimensions(vis, params); + const expected = { id: 'percent' }; + const actual = dimensions.y[0].format; + expect(actual).toEqual(expected); + }); + + it('with two numeric metrics, mixed normal and percent mode should have corresponding formatters', async () => { + const aggConfig = aggs[0]; + aggs = [{ ...aggConfig }, { ...aggConfig, id: 5 }]; + + visState = { + params: { + seriesParams: [ + { + data: { id: 0 }, + valueAxis: 'axis-y-1', + }, + { + data: { id: 5 }, + valueAxis: 'axis-y-2', + }, + ], + valueAxes: [ + { + id: 'axis-y-1', + scale: { + mode: 'normal', + }, + }, + { + id: 'axis-y-2', + scale: { + mode: 'percentage', + }, + }, + ], + }, + }; + + const dimensions = await buildVislibDimensions(vis, params); + const expectedY1 = { id: 'number' }; + const expectedY2 = { id: 'percent' }; + expect(dimensions.y[0].format).toEqual(expectedY1); + expect(dimensions.y[1].format).toEqual(expectedY2); + }); + }); + + describe('test y dimension format for gauge chart', () => { + beforeEach(() => { + visState = { params: { gauge: {} } }; + + vis = { + // @ts-ignore + type: { + name: 'gauge', + }, + aggs: { + getResponseAggs: () => { + return aggs; + }, + }, + isHierarchical: () => { + return false; + }, + getCurrentState: () => { + return visState; + }, + }; + }); + + it('with percentageMode = false', async () => { + visState.params.gauge.percentageMode = false; + const dimensions = await buildVislibDimensions(vis, params); + const expected = { id: 'number' }; + const actual = dimensions.y[0].format; + expect(actual).toEqual(expected); + }); + + it('with percentageMode = true', async () => { + visState.params.gauge.percentageMode = true; + const dimensions = await buildVislibDimensions(vis, params); + const expected = { id: 'percent' }; + const actual = dimensions.y[0].format; + expect(actual).toEqual(expected); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts index 7ae6308668f1a..38a53b7078d4c 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts @@ -17,7 +17,7 @@ * under the License. */ -import { cloneDeep } from 'lodash'; +import { cloneDeep, get } from 'lodash'; // @ts-ignore import { setBounds } from 'ui/agg_types/buckets/date_histogram'; import { SearchSource } from 'ui/courier'; @@ -34,14 +34,14 @@ interface SchemaConfigParams { useGeocentroid?: boolean; } -interface SchemaConfig { +export interface SchemaConfig { accessor: number; format: SchemaFormat | {}; params: SchemaConfigParams; aggType: string; } -interface Schemas { +export interface Schemas { metric: SchemaConfig[]; bucket?: any[]; geo_centroid?: any[]; @@ -153,10 +153,6 @@ export const getSchemas = (vis: Vis, timeRange?: any): Schemas => { const isHierarchical = vis.isHierarchical(); const metrics = responseAggs.filter((agg: AggConfig) => agg.type.type === 'metrics'); responseAggs.forEach((agg: AggConfig) => { - if (!agg.enabled) { - cnt++; - return; - } let skipMetrics = false; let schemaName = agg.schema ? agg.schema.name || agg.schema : null; if (typeof schemaName === 'object') { @@ -244,6 +240,30 @@ export const prepareDimension = (variable: string, data: any) => { return expr; }; +const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): void => { + const visState = vis.getCurrentState(); + const visConfig = visState.params; + const responseAggs = vis.aggs.getResponseAggs().filter((agg: AggConfig) => agg.enabled); + + (dimensions.y || []).forEach(yDimension => { + const yAgg = responseAggs[yDimension.accessor]; + const seriesParam = (visConfig.seriesParams || []).find( + (param: any) => param.data.id === yAgg.id + ); + if (seriesParam) { + const usedValueAxis = (visConfig.valueAxes || []).find( + (valueAxis: any) => valueAxis.id === seriesParam.valueAxis + ); + if (get(usedValueAxis, 'scale.mode') === 'percentage') { + yDimension.format = { id: 'percent' }; + } + } + if (get(visConfig, 'gauge.percentageMode') === true) { + yDimension.format = { id: 'percent' }; + } + }); +}; + export const buildPipelineVisFunction: BuildPipelineVisFunction = { vega: visState => { return `vega ${prepareString('spec', visState.params.spec)}`; @@ -293,6 +313,13 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { } = visState.params.metric; const { metrics, bucket } = buildVisConfig.metric(schemas).dimensions; + // fix formatter for percentage mode + if (get(visState.params, 'metric.percentageMode') === true) { + metrics.forEach((metric: SchemaConfig) => { + metric.format = { id: 'percent' }; + }); + } + let expr = `metricvis `; expr += prepareValue('percentage', percentageMode); expr += prepareValue('colorScheme', colorSchema); @@ -452,6 +479,7 @@ export const buildVislibDimensions = async ( } } + adjustVislibDimensionFormmaters(vis, dimensions); return dimensions; }; diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index fb32de7832b4d..5eddd3d8abb31 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -189,7 +189,7 @@ export default function ({ getService, getPageObjects }) { }); it('goal and guages', async () => { - await dashboardExpect.goalAndGuageLabelsExist(['40%', '7,544']); + await dashboardExpect.goalAndGuageLabelsExist(['39.958%', '7,544']); }); it('tsvb time series', async () => { diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index 031348fa005e7..831622716f381 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -45,7 +45,7 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.timelionLegendCount(0); await dashboardExpect.markdownWithValuesExists(['I\'m a markdown!']); await dashboardExpect.vegaTextsExist(['5,000']); - await dashboardExpect.goalAndGuageLabelsExist(['63%', '56%', '11.915 GB']); + await dashboardExpect.goalAndGuageLabelsExist(['62.925%', '55.625%', '11.915 GB']); await dashboardExpect.dataTableRowCount(5); await dashboardExpect.tagCloudWithValuesFound(['CN', 'IN', 'US', 'BR', 'ID']); // TODO add test for 'region map viz' diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index 07f7cfd3fe159..6b14cabb54f99 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }) { const log = getService('log'); const retry = getService('retry'); const inspector = getService('inspector'); + const find = getService('find'); const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); describe('gauge chart', function indexPatternCreation() { @@ -30,15 +31,16 @@ export default function ({ getService, getPageObjects }) { const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; - before(async function () { + async function initGaugeVis() { log.debug('navigateToApp visualize'); await PageObjects.visualize.navigateToNewVisualization(); log.debug('clickGauge'); await PageObjects.visualize.clickGauge(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - }); + } + before(initGaugeVis); it('should have inspector enabled', async function () { await inspector.expectIsEnabled(); @@ -94,5 +96,26 @@ export default function ({ getService, getPageObjects }) { }); }); + it('should format the metric correctly in percentage mode', async function () { + await initGaugeVis(); + await PageObjects.visualize.clickMetricEditor(); + await PageObjects.visualize.selectAggregation('Average', 'metrics'); + await PageObjects.visualize.selectField('bytes', 'metrics'); + await PageObjects.visualize.clickOptionsTab(); + const table = await find.byClassName('visEditorAgg__rangesTable'); + const lastRow = await table.findByCssSelector('tr:last-child'); + const toCell = await lastRow.findByCssSelector('td:nth-child(2) input'); + await toCell.clearValue(); + await toCell.type('10000', { charByChar: true }); + await find.clickByCssSelector('#percentageMode'); + await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visualize.clickGo(); + + await retry.try(async function tryingForTime() { + const expectedTexts = [ '57.273%', 'Average bytes' ]; + const metricValue = await PageObjects.visualize.getGaugeValue(); + expect(expectedTexts).to.eql(metricValue); + }); + }); }); } diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index 0a0e64d8e8919..a2ff9d0419fc6 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -227,6 +227,19 @@ export default function ({ getService, getPageObjects }) { }); }); + describe('vertical bar in percent mode', async () => { + it('should show ticks with percentage values', async function () { + const axisId = 'ValueAxis-1'; + await PageObjects.visualize.clickMetricsAndAxes(); + await PageObjects.visualize.clickYAxisOptions(axisId); + await PageObjects.visualize.selectYAxisMode('percentage'); + await PageObjects.visualize.clickGo(); + const labels = await PageObjects.visualize.getYAxisLabels(); + expect(labels[0]).to.eql('0%'); + expect(labels[labels.length - 1]).to.eql('100%'); + }); + }); + describe('vertical bar with Split series', function () { before(initBarChart); diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index fe93c5999c90a..c84debcfecbde 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -707,6 +707,11 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli await selector.click(); } + async selectYAxisMode(mode) { + const selector = await find.byCssSelector(`#valueAxisMode0 > option[label="${mode}"]`); + await selector.click(); + } + async clickData() { await testSubjects.click('visualizeEditDataLink'); }