From 705d2b07c894b9ffe8d8c8cf7cf3cbc209b5a2d6 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Wed, 8 Sep 2021 21:41:12 +0300 Subject: [PATCH] [7.x] [Vislib] Removes old implementation of xy chart (#110786) (#111558) * [Vislib] Removes old implementation of xy chart (#110786) * [Vislib] Remove xy chart * Update i18n * Remove uncecessary file * Fix types * More fixes * Fix functional tests part 1 * Fix functional tests part 2 * Fix bug with shard-delay * Fix functional tests part 3 * fix functional tests part4 * Fix async_serch FT * Fix functional dashboard async test * REplace screenshot area chart image * Cleanup vislib from xy charts * Remove unused fixtures * Address PR comments * Remove miaou :D * Address PR comments * Fix i18n Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # docs/management/advanced-options.asciidoc # test/functional/screenshots/baseline/area_chart.png * Fixes * Remove setting from docs Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/management/advanced-options.asciidoc | 4 - .../server/collectors/management/schema.ts | 4 - .../server/collectors/management/types.ts | 1 - src/plugins/telemetry/schema/oss_plugins.json | 6 - .../components/timelion_vis_component.tsx | 2 +- src/plugins/vis_types/vislib/public/area.ts | 18 - .../dispatch_bar_chart_config_normal.json | 31 -- .../dispatch_bar_chart_config_percentage.json | 40 -- .../fixtures/dispatch_bar_chart_d3.json | 19 - .../dispatch_bar_chart_data_point.json | 9 - .../vis_types/vislib/public/histogram.ts | 18 - .../vis_types/vislib/public/horizontal_bar.ts | 18 - src/plugins/vis_types/vislib/public/line.ts | 18 - src/plugins/vis_types/vislib/public/plugin.ts | 12 +- src/plugins/vis_types/vislib/public/types.ts | 5 - .../public/vis_type_vislib_vis_types.ts | 14 - .../vis_types/vislib/public/vislib/VISLIB.md | 11 +- .../vislib/public/vislib/_index.scss | 1 - .../public/vislib/lib/axis/axis.test.js | 2 +- .../public/vislib/lib/axis/axis_title.test.js | 2 +- .../public/vislib/lib/axis/x_axis.test.js | 2 +- .../public/vislib/lib/axis/y_axis.test.js | 2 +- .../public/vislib/lib/chart_title.test.js | 2 +- .../vislib/public/vislib/lib/dispatch.test.js | 68 ++- .../lib/dispatch_vertical_bar_chart.test.js | 48 -- .../vislib/public/vislib/lib/handler.test.js | 20 +- .../public/vislib/lib/layout/_layout.scss | 4 - .../public/vislib/lib/layout/layout.test.js | 24 +- .../vislib/public/vislib/lib/types/index.js | 5 - .../public/vislib/lib/types/point_series.js | 35 -- .../vislib/lib/types/point_series.test.js | 72 +-- .../types/testdata_linechart_percentile.json | 464 ------------------ ...data_linechart_percentile_float_value.json | 463 ----------------- ...nechart_percentile_float_value_result.json | 456 ----------------- .../testdata_linechart_percentile_result.json | 458 ----------------- .../public/vislib/lib/vis_config.test.js | 2 +- .../vislib/public/vislib/vis.test.js | 20 +- .../vislib/visualizations/_vis_fixture.js | 2 +- .../vislib/visualizations/chart.test.js | 26 +- .../vislib/visualizations/point_series.js | 8 +- .../visualizations/point_series/_index.scss | 1 - .../visualizations/point_series/_labels.scss | 20 - .../visualizations/point_series/area_chart.js | 247 ---------- .../point_series/area_chart.test.js | 264 ---------- .../point_series/column_chart.js | 383 --------------- .../point_series/column_chart.test.js | 401 --------------- .../visualizations/point_series/line_chart.js | 230 --------- .../point_series/line_chart.test.js | 225 --------- .../point_series/series_types.js | 19 - src/plugins/vis_types/xy/common/index.ts | 2 - src/plugins/vis_types/xy/kibana.json | 2 +- .../xy/public/editor/common_config.tsx | 54 +- .../components/common/validation_wrapper.tsx | 12 +- .../editor/components/options/index.tsx | 22 +- .../__snapshots__/index.test.tsx.snap | 1 - .../value_axes_panel.test.tsx.snap | 2 - .../options/metrics_axes/index.test.tsx | 24 +- .../components/options/metrics_axes/index.tsx | 14 +- .../options/metrics_axes/value_axes_panel.tsx | 2 - .../metrics_axes/value_axis_options.tsx | 4 +- .../options/point_series/grid_panel.tsx | 30 +- .../point_series/point_series.test.tsx | 52 +- .../options/point_series/point_series.tsx | 27 +- src/plugins/vis_types/xy/public/index.ts | 1 - src/plugins/vis_types/xy/public/plugin.ts | 29 +- .../vis_types/xy/public/utils/accessors.tsx | 3 +- .../vis_types/xy/public/vis_types/area.ts | 11 +- .../xy/public/vis_types/histogram.ts | 11 +- .../xy/public/vis_types/horizontal_bar.ts | 11 +- .../vis_types/xy/public/vis_types/index.ts | 29 +- .../vis_types/xy/public/vis_types/line.ts | 11 +- src/plugins/vis_types/xy/server/index.ts | 10 - src/plugins/vis_types/xy/server/plugin.ts | 56 --- .../components/deprecation_vis_warning.tsx | 66 --- .../components/visualize_editor_common.tsx | 13 - .../apps/dashboard/dashboard_state.ts | 45 +- test/functional/apps/dashboard/index.ts | 2 - .../apps/getting_started/_shakespeare.ts | 48 +- test/functional/apps/getting_started/index.ts | 2 - test/functional/apps/visualize/_area_chart.ts | 120 ++--- .../apps/visualize/_line_chart_split_chart.ts | 134 +++-- .../visualize/_line_chart_split_series.ts | 134 +++-- .../apps/visualize/_point_series_options.ts | 156 +++--- test/functional/apps/visualize/_timelion.ts | 82 +++- .../apps/visualize/_vertical_bar_chart.ts | 184 +++---- .../_vertical_bar_chart_nontimeindex.ts | 141 ++---- test/functional/apps/visualize/index.ts | 8 - test/functional/config.js | 1 - .../page_objects/visual_builder_page.ts | 4 - .../page_objects/visualize_chart_page.ts | 368 ++++---------- .../page_objects/visualize_editor_page.ts | 4 +- .../functional/page_objects/visualize_page.ts | 5 +- .../screenshots/baseline/area_chart.png | Bin 129876 -> 85012 bytes .../translations/translations/ja-JP.json | 16 +- .../translations/translations/zh-CN.json | 12 +- x-pack/test/functional/config.js | 1 - .../fixtures/kbn_archiver/rollup/rollup.json | 1 - .../dashboard/async_search/async_search.ts | 13 +- .../async_search/save_search_session.ts | 13 +- .../apps/dashboard/dashboard_smoke_tests.ts | 1 - 100 files changed, 883 insertions(+), 5317 deletions(-) delete mode 100644 src/plugins/vis_types/vislib/public/area.ts delete mode 100644 src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_normal.json delete mode 100644 src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_percentage.json delete mode 100644 src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_d3.json delete mode 100644 src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_data_point.json delete mode 100644 src/plugins/vis_types/vislib/public/histogram.ts delete mode 100644 src/plugins/vis_types/vislib/public/horizontal_bar.ts delete mode 100644 src/plugins/vis_types/vislib/public/line.ts delete mode 100644 src/plugins/vis_types/vislib/public/vislib/lib/dispatch_vertical_bar_chart.test.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile.json delete mode 100644 src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json delete mode 100644 src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json delete mode 100644 src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_index.scss delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_labels.scss delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.test.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.test.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.test.js delete mode 100644 src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/series_types.js delete mode 100644 src/plugins/vis_types/xy/server/index.ts delete mode 100644 src/plugins/vis_types/xy/server/plugin.ts delete mode 100644 src/plugins/visualize/public/application/components/deprecation_vis_warning.tsx diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 651a63fb8f3cc..26eeff0a8641b 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -548,10 +548,6 @@ The maximum geoHash precision displayed in tile maps. 7 is high, 10 is very high and 12 is the maximum. For more information, refer to {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[Cell dimensions at the equator]. -[[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`:: -**The legacy XY charts are deprecated and will not be supported as of 7.16.** -The visualize editor uses a new XY charts library with improved performance, color palettes, fill capacity, and more. Enable this option if you prefer to use the legacy charts library. - [[visualization-visualize-pieChartslibrary]]`visualization:visualize:legacyPieChartsLibrary`:: The visualize editor uses new pie charts with improved performance, color palettes, label positioning, and more. Enable this option if you prefer to use to the legacy charts library. diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index b8455b9547bad..79ebf69acc4fe 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -408,10 +408,6 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, - 'visualization:visualize:legacyChartsLibrary': { - type: 'boolean', - _meta: { description: 'Non-default value of setting.' }, - }, 'visualization:visualize:legacyPieChartsLibrary': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 2fdbd5825df8a..b27feda208c2c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -28,7 +28,6 @@ export interface UsageStats { 'autocomplete:useTimeRange': boolean; 'autocomplete:valueSuggestionMethod': string; 'search:timeout': number; - 'visualization:visualize:legacyChartsLibrary': boolean; 'visualization:visualize:legacyPieChartsLibrary': boolean; 'doc_table:legacy': boolean; 'discover:modifyColumnsOnSwitch': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 3a308b1b69c35..8dec1740e3cc8 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -7759,12 +7759,6 @@ "description": "Non-default value of setting." } }, - "visualization:visualize:legacyChartsLibrary": { - "type": "boolean", - "_meta": { - "description": "Non-default value of setting." - } - }, "visualization:visualize:legacyPieChartsLibrary": { "type": "boolean", "_meta": { diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index 858ba0ad64add..3cc335392b7c4 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -177,7 +177,7 @@ const TimelionVisComponent = ({ }, [chart]); return ( -
+
{title && (

{title}

diff --git a/src/plugins/vis_types/vislib/public/area.ts b/src/plugins/vis_types/vislib/public/area.ts deleted file mode 100644 index f4ac79e12bbe2..0000000000000 --- a/src/plugins/vis_types/vislib/public/area.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { xyVisTypes } from '../../xy/public'; -import { VisTypeDefinition } from '../../../visualizations/public'; - -import { toExpressionAst } from './to_ast'; -import { BasicVislibParams } from './types'; - -export const areaVisTypeDefinition = { - ...xyVisTypes.area(), - toExpressionAst, -} as VisTypeDefinition; diff --git a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_normal.json b/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_normal.json deleted file mode 100644 index a72cebcfdf2c8..0000000000000 --- a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_normal.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "seriesParams": [ - { - "data": { - "id": "1", - "label": "Count" - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "stacked", - "show": "true", - "showCircles": true, - "type": "histogram", - "valueAxis": "ValueAxis-1" - } - ], - "valueAxes": [ - { - "id": "ValueAxis-1", - "name": "LeftAxis-1", - "position": "left", - "scale": { - "type": "linear", - "mode": "normal" - }, - "show": true, - "style": {}, - "type": "value" - } - ] -} \ No newline at end of file diff --git a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_percentage.json b/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_percentage.json deleted file mode 100644 index 1fb4bc89bf4e9..0000000000000 --- a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_config_percentage.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "seriesParams": [ - { - "data": { - "id": "1", - "label": "Count" - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "stacked", - "show": "true", - "showCircles": true, - "type": "histogram", - "valueAxis": "ValueAxis-1" - } - ], - "valueAxes": [ - { - "id": "ValueAxis-1", - "labels": { - "show": true, - "rotate": 0, - "filter": false, - "truncate": 100 - }, - "name": "LeftAxis-1", - "position": "left", - "scale": { - "type": "linear", - "mode": "percentage" - }, - "show": true, - "style": {}, - "title": { - "text": "Count" - }, - "type": "value" - } - ] -} \ No newline at end of file diff --git a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_d3.json b/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_d3.json deleted file mode 100644 index f614ab64d7b34..0000000000000 --- a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_d3.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "series": [ - { - "id": "1", - "rawId": "Late Aircraft Delay-col-2-1", - "label": "Late Aircraft Delay" - }, - { - "id": "1", - "rawId": "No Delay-col-2-1", - "label": "No Delay" - }, - { - "id": "1", - "rawId": "NAS Delay-col-2-1", - "label": "NAS Delay" - } - ] -} \ No newline at end of file diff --git a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_data_point.json b/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_data_point.json deleted file mode 100644 index 19bb7b30d6e6a..0000000000000 --- a/src/plugins/vis_types/vislib/public/fixtures/dispatch_bar_chart_data_point.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "parent": { - "accessor": "col-1-3", - "column": 1, - "params": {} - }, - "series": "No Delay", - "seriesId": "No Delay-col-2-1" -} \ No newline at end of file diff --git a/src/plugins/vis_types/vislib/public/histogram.ts b/src/plugins/vis_types/vislib/public/histogram.ts deleted file mode 100644 index bb4f570c6a2d8..0000000000000 --- a/src/plugins/vis_types/vislib/public/histogram.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { xyVisTypes } from '../../xy/public'; -import { VisTypeDefinition } from '../../../visualizations/public'; - -import { toExpressionAst } from './to_ast'; -import { BasicVislibParams } from './types'; - -export const histogramVisTypeDefinition = { - ...xyVisTypes.histogram(), - toExpressionAst, -} as VisTypeDefinition; diff --git a/src/plugins/vis_types/vislib/public/horizontal_bar.ts b/src/plugins/vis_types/vislib/public/horizontal_bar.ts deleted file mode 100644 index 37aa79a0b1aee..0000000000000 --- a/src/plugins/vis_types/vislib/public/horizontal_bar.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { xyVisTypes } from '../../xy/public'; -import { VisTypeDefinition } from '../../../visualizations/public'; - -import { toExpressionAst } from './to_ast'; -import { BasicVislibParams } from './types'; - -export const horizontalBarVisTypeDefinition = { - ...xyVisTypes.horizontalBar(), - toExpressionAst, -} as VisTypeDefinition; diff --git a/src/plugins/vis_types/vislib/public/line.ts b/src/plugins/vis_types/vislib/public/line.ts deleted file mode 100644 index 0f33c393e0643..0000000000000 --- a/src/plugins/vis_types/vislib/public/line.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { xyVisTypes } from '../../xy/public'; -import { VisTypeDefinition } from '../../../visualizations/public'; - -import { toExpressionAst } from './to_ast'; -import { BasicVislibParams } from './types'; - -export const lineVisTypeDefinition = { - ...xyVisTypes.line(), - toExpressionAst, -} as VisTypeDefinition; diff --git a/src/plugins/vis_types/vislib/public/plugin.ts b/src/plugins/vis_types/vislib/public/plugin.ts index 24ba7741cab91..b0385475f7105 100644 --- a/src/plugins/vis_types/vislib/public/plugin.ts +++ b/src/plugins/vis_types/vislib/public/plugin.ts @@ -13,16 +13,11 @@ import { VisualizationsSetup } from '../../../visualizations/public'; import { ChartsPluginSetup } from '../../../charts/public'; import { DataPublicPluginStart } from '../../../data/public'; import { KibanaLegacyStart } from '../../../kibana_legacy/public'; -import { LEGACY_CHARTS_LIBRARY } from '../../xy/common/index'; import { LEGACY_PIE_CHARTS_LIBRARY } from '../../pie/common/index'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; import { createPieVisFn } from './pie_fn'; -import { - convertedTypeDefinitions, - pieVisTypeDefinition, - visLibVisTypeDefinitions, -} from './vis_type_vislib_vis_types'; +import { visLibVisTypeDefinitions, pieVisTypeDefinition } from './vis_type_vislib_vis_types'; import { setFormatService, setDataActions } from './services'; import { getVislibVisRenderer } from './vis_renderer'; @@ -51,11 +46,8 @@ export class VisTypeVislibPlugin core: VisTypeVislibCoreSetup, { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies ) { - const typeDefinitions = !core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false) - ? convertedTypeDefinitions - : visLibVisTypeDefinitions; // register vislib XY axis charts - typeDefinitions.forEach(visualizations.createBaseVisualization); + visLibVisTypeDefinitions.forEach(visualizations.createBaseVisualization); expressions.registerRenderer(getVislibVisRenderer(core, charts)); expressions.registerFunction(createVisTypeVislibVisFn()); diff --git a/src/plugins/vis_types/vislib/public/types.ts b/src/plugins/vis_types/vislib/public/types.ts index 5196f0e33f404..9184e2a4c884c 100644 --- a/src/plugins/vis_types/vislib/public/types.ts +++ b/src/plugins/vis_types/vislib/public/types.ts @@ -37,12 +37,7 @@ export const GaugeType = Object.freeze({ export type GaugeType = $Values; export const VislibChartType = Object.freeze({ - Histogram: 'histogram' as const, - HorizontalBar: 'horizontal_bar' as const, - Line: 'line' as const, Pie: 'pie' as const, - Area: 'area' as const, - PointSeries: 'point_series' as const, Heatmap: 'heatmap' as const, Gauge: 'gauge' as const, Goal: 'goal' as const, diff --git a/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_types.ts b/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_types.ts index 325c9256d7184..6ecb63ca31b37 100644 --- a/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_types.ts +++ b/src/plugins/vis_types/vislib/public/vis_type_vislib_vis_types.ts @@ -7,27 +7,13 @@ */ import { VisTypeDefinition } from 'src/plugins/visualizations/public'; -import { histogramVisTypeDefinition } from './histogram'; -import { lineVisTypeDefinition } from './line'; -import { areaVisTypeDefinition } from './area'; import { heatmapVisTypeDefinition } from './heatmap'; -import { horizontalBarVisTypeDefinition } from './horizontal_bar'; import { gaugeVisTypeDefinition } from './gauge'; import { goalVisTypeDefinition } from './goal'; export { pieVisTypeDefinition } from './pie'; export const visLibVisTypeDefinitions: Array> = [ - histogramVisTypeDefinition, - lineVisTypeDefinition, - areaVisTypeDefinition, - heatmapVisTypeDefinition, - horizontalBarVisTypeDefinition, - gaugeVisTypeDefinition, - goalVisTypeDefinition, -]; - -export const convertedTypeDefinitions: Array> = [ heatmapVisTypeDefinition, gaugeVisTypeDefinition, goalVisTypeDefinition, diff --git a/src/plugins/vis_types/vislib/public/vislib/VISLIB.md b/src/plugins/vis_types/vislib/public/vislib/VISLIB.md index 05ca9a51b19eb..1f17228dda7ab 100644 --- a/src/plugins/vis_types/vislib/public/vislib/VISLIB.md +++ b/src/plugins/vis_types/vislib/public/vislib/VISLIB.md @@ -1,4 +1,8 @@ -# Vislib general overview +# Charts supported + +Vislib supports the heatmap and gauge/goal charts from the aggregation-based visualizations. It also contains the legacy implemementation of the pie chart (enabled by the visualization:visualize:legacyPieChartsLibrary advanced setting). + +# General overview `vis.js` constructor accepts vis parameters and render method accepts data. it exposes event emitter interface so we can listen to certain events like 'renderComplete'. @@ -18,7 +22,4 @@ All base visualizations extend from `visualizations/_chart` ### Point series chart -`visualizations/point_series` takes care of drawing the point series chart (no axes or titles, just the chart itself). It creates all the series defined and calls render method on them. - -currently there are 3 series types available (line, area, bars), they all extend from `visualizations/point_series/_point_series`. - +`visualizations/point_series` takes care of drawing the point series chart (no axes or titles, just the chart itself). It creates all the series defined and calls render method on them. \ No newline at end of file diff --git a/src/plugins/vis_types/vislib/public/vislib/_index.scss b/src/plugins/vis_types/vislib/public/vislib/_index.scss index 78e16224a67a3..00b22df06f10d 100644 --- a/src/plugins/vis_types/vislib/public/vislib/_index.scss +++ b/src/plugins/vis_types/vislib/public/vislib/_index.scss @@ -5,5 +5,4 @@ @import './components/tooltip/index'; @import './components/legend/index'; -@import './visualizations/point_series/index'; @import './visualizations/gauges/index'; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis.test.js index f8dcf8edc6bee..ef4f08cac35f6 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis.test.js @@ -96,7 +96,7 @@ describe('Vislib Axis Class Test Suite', function () { const visConfig = new VisConfig( { - type: 'histogram', + type: 'heatmap', }, data, mockUiState, diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis_title.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis_title.test.js index 90e5a4ee6defb..b5a158e173b0d 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis_title.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/axis/axis_title.test.js @@ -103,7 +103,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { dataObj = new Data(data, getMockUiState(), () => undefined); visConfig = new VisConfig( { - type: 'histogram', + type: 'heatmap', }, data, getMockUiState(), diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/axis/x_axis.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/axis/x_axis.test.js index 5b2ff31727074..1ded9e48fcfd3 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/axis/x_axis.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/axis/x_axis.test.js @@ -101,7 +101,7 @@ describe('Vislib xAxis Class Test Suite', function () { const visConfig = new VisConfig( { - type: 'histogram', + type: 'heatmap', }, data, mockUiState, diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/axis/y_axis.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/axis/y_axis.test.js index c69a029fca18c..5bbfde01197e5 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/axis/y_axis.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/axis/y_axis.test.js @@ -81,7 +81,7 @@ function createData(seriesData) { buildYAxis = function (params) { const visConfig = new VisConfig( { - type: 'histogram', + type: 'heatmap', }, data, mockUiState, diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js index 291b2da81b8ce..54b326a292845 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js @@ -99,7 +99,7 @@ describe('Vislib ChartTitle Class Test Suite', function () { const visConfig = new VisConfig( { - type: 'histogram', + type: 'heatmap', title: { text: 'rows', }, diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js index dfc36a364e7ad..21a3dc069d8c6 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js @@ -8,6 +8,7 @@ import _ from 'lodash'; import d3 from 'd3'; +import $ from 'jquery'; import { setHTMLElementClientSizes, setSVGElementGetBBox, @@ -23,6 +24,7 @@ import { getVis } from '../visualizations/_vis_fixture'; let mockedHTMLElementClientSizes; let mockedSVGElementGetBBox; let mockedSVGElementGetComputedTextLength; +let mockWidth; describe('Vislib Dispatch Class Test Suite', function () { function destroyVis(vis) { @@ -37,22 +39,43 @@ describe('Vislib Dispatch Class Test Suite', function () { mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); mockedSVGElementGetBBox = setSVGElementGetBBox(100); mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); + mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(900); }); afterAll(() => { mockedHTMLElementClientSizes.mockRestore(); mockedSVGElementGetBBox.mockRestore(); mockedSVGElementGetComputedTextLength.mockRestore(); + mockWidth.mockRestore(); }); describe('', function () { let vis; let mockUiState; - beforeEach(() => { - vis = getVis(); + const vislibParams = { + type: 'heatmap', + addLegend: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], + }; + + function generateVis(opts = {}) { + const config = _.defaultsDeep({}, opts, vislibParams); + vis = getVis(config); mockUiState = getMockUiState(); + vis.on('brush', _.noop); vis.render(data, mockUiState); + } + + beforeEach(() => { + generateVis(); }); afterEach(function () { @@ -74,11 +97,29 @@ describe('Vislib Dispatch Class Test Suite', function () { let vis; let mockUiState; - beforeEach(() => { + const vislibParams = { + type: 'heatmap', + addLegend: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], + }; + + function generateVis(opts = {}) { + const config = _.defaultsDeep({}, opts, vislibParams); + vis = getVis(config); mockUiState = getMockUiState(); - vis = getVis(); vis.on('brush', _.noop); vis.render(data, mockUiState); + } + + beforeEach(() => { + generateVis(); }); afterEach(function () { @@ -183,9 +224,22 @@ describe('Vislib Dispatch Class Test Suite', function () { }); describe('Custom event handlers', function () { + const vislibParams = { + type: 'heatmap', + addLegend: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], + }; + const config = _.defaultsDeep({}, vislibParams); + const vis = getVis(config); + const mockUiState = getMockUiState(); test('should attach whatever gets passed on vis.on() to chart.events', function (done) { - const vis = getVis(); - const mockUiState = getMockUiState(); vis.on('someEvent', _.noop); vis.render(data, mockUiState); @@ -198,8 +252,6 @@ describe('Vislib Dispatch Class Test Suite', function () { }); test('can be added after rendering', function () { - const vis = getVis(); - const mockUiState = getMockUiState(); vis.render(data, mockUiState); vis.on('someEvent', _.noop); diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch_vertical_bar_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch_vertical_bar_chart.test.js deleted file mode 100644 index 0374f082f1676..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch_vertical_bar_chart.test.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import mockDispatchDataD3 from '../../fixtures/dispatch_bar_chart_d3.json'; -import { Dispatch } from './dispatch'; -import mockdataPoint from '../../fixtures/dispatch_bar_chart_data_point.json'; -import mockConfigPercentage from '../../fixtures/dispatch_bar_chart_config_percentage.json'; -import mockConfigNormal from '../../fixtures/dispatch_bar_chart_config_normal.json'; - -jest.mock('d3', () => ({ - event: { - target: { - nearestViewportElement: { - __data__: mockDispatchDataD3, - }, - }, - }, -})); - -function getHandlerMock(config = {}, data = {}) { - return { - visConfig: { get: (id, fallback) => config[id] || fallback }, - data, - }; -} - -describe('Vislib event responses dispatcher', () => { - test('return data for a vertical bars popover in percentage mode', () => { - const dataPoint = mockdataPoint; - const handlerMock = getHandlerMock(mockConfigPercentage); - const dispatch = new Dispatch(handlerMock); - const actual = dispatch.eventResponse(dataPoint, 0); - expect(actual.isPercentageMode).toBeTruthy(); - }); - - test('return data for a vertical bars popover in normal mode', () => { - const dataPoint = mockdataPoint; - const handlerMock = getHandlerMock(mockConfigNormal); - const dispatch = new Dispatch(handlerMock); - const actual = dispatch.eventResponse(dataPoint, 0); - expect(actual.isPercentageMode).toBeFalsy(); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js index 326a29f3690bc..60ffaf3f3d19c 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import _ from 'lodash'; import $ from 'jquery'; import { setHTMLElementClientSizes, @@ -26,6 +26,7 @@ const names = ['series', 'columns', 'rows', 'stackedSeries']; let mockedHTMLElementClientSizes; let mockedSVGElementGetBBox; let mockedSVGElementGetComputedTextLength; +let mockWidth; dateHistogramArray.forEach(function (data, i) { describe('Vislib Handler Test Suite for ' + names[i] + ' Data', function () { @@ -36,10 +37,24 @@ dateHistogramArray.forEach(function (data, i) { mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); mockedSVGElementGetBBox = setSVGElementGetBBox(100); mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); + mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(900); }); beforeEach(() => { - vis = getVis(); + const vislibParams = { + type: 'heatmap', + addLegend: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], + }; + const config = _.defaultsDeep({}, vislibParams); + vis = getVis(config); vis.render(data, getMockUiState()); }); @@ -51,6 +66,7 @@ dateHistogramArray.forEach(function (data, i) { mockedHTMLElementClientSizes.mockRestore(); mockedSVGElementGetBBox.mockRestore(); mockedSVGElementGetComputedTextLength.mockRestore(); + mockWidth.mockRestore(); }); describe('render Method', function () { diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/layout/_layout.scss b/src/plugins/vis_types/vislib/public/vislib/lib/layout/_layout.scss index a6896a9181b4e..7ead0b314c7ad 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/layout/_layout.scss +++ b/src/plugins/vis_types/vislib/public/vislib/lib/layout/_layout.scss @@ -187,10 +187,6 @@ fill: $visHoverBackgroundColor; } - .visAreaChart__overlapArea { - opacity: .8; - } - .series > path, .series > rect { stroke-opacity: 1; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js index f4ea2d3898d25..af59f011515d0 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import { @@ -30,6 +30,7 @@ const names = ['series', 'columns', 'rows', 'stackedSeries']; let mockedHTMLElementClientSizes; let mockedSVGElementGetBBox; let mockedSVGElementGetComputedTextLength; +let mockWidth; dateHistogramArray.forEach(function (data, i) { describe('Vislib Layout Class Test Suite for ' + names[i] + ' Data', function () { @@ -42,10 +43,24 @@ dateHistogramArray.forEach(function (data, i) { mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); mockedSVGElementGetBBox = setSVGElementGetBBox(100); mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); + mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(900); }); beforeEach(() => { - vis = getVis(); + const vislibParams = { + type: 'heatmap', + addLegend: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], + }; + const config = _.defaultsDeep({}, vislibParams); + vis = getVis(config); mockUiState = getMockUiState(); vis.render(data, mockUiState); numberOfCharts = vis.handler.charts.length; @@ -59,6 +74,7 @@ dateHistogramArray.forEach(function (data, i) { mockedHTMLElementClientSizes.mockRestore(); mockedSVGElementGetBBox.mockRestore(); mockedSVGElementGetComputedTextLength.mockRestore(); + mockWidth.mockRestore(); }); describe('createLayout Method', function () { @@ -81,7 +97,7 @@ dateHistogramArray.forEach(function (data, i) { beforeEach(function () { const visConfig = new VisConfig( { - type: 'histogram', + type: 'heatmap', }, data, mockUiState, @@ -125,7 +141,7 @@ dateHistogramArray.forEach(function (data, i) { expect(function () { testLayout.layout({ - parent: 'histogram', + parent: 'heatmap', type: 'div', }); }).toThrowError(); diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js b/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js index dcfff3618ab91..4a9dd0bd512ca 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/types/index.js @@ -11,12 +11,7 @@ import { vislibPieConfig } from './pie'; import { vislibGaugeConfig } from './gauge'; export const vislibTypesConfig = { - histogram: pointSeries.column, - horizontal_bar: pointSeries.column, - line: pointSeries.line, pie: vislibPieConfig, - area: pointSeries.area, - point_series: pointSeries.line, heatmap: pointSeries.heatmap, gauge: vislibGaugeConfig, goal: vislibGaugeConfig, diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.js b/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.js index 9753cbb78ea5c..2328a09205dd6 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.js @@ -193,41 +193,6 @@ function create(opts) { } export const vislibPointSeriesTypes = { - line: create(), - - column: create({ - expandLastBucket: true, - }), - - area: create({ - alerts: [ - { - type: 'warning', - msg: - 'Positive and negative values are not accurately represented by stacked ' + - 'area charts. Either changing the chart mode to "overlap" or using a ' + - 'bar chart is recommended.', - test: function (_, data) { - if (!data.shouldBeStacked() || data.maxNumberOfSeries() < 2) return; - - const hasPos = data.getYMax(data._getY) > 0; - const hasNeg = data.getYMin(data._getY) < 0; - return hasPos && hasNeg; - }, - }, - { - type: 'warning', - msg: - 'Parts of or the entire area chart might not be displayed due to null ' + - 'values in the data. A line chart is recommended when displaying data ' + - 'with null values.', - test: function (_, data) { - return data.hasNullValues(); - }, - }, - ], - }), - heatmap: (cfg, data) => { const defaults = create()(cfg, data); const hasCharts = defaults.charts.length; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.test.js index aa2fe39c34ec3..c0764e6a39ed6 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/types/point_series.test.js @@ -8,10 +8,6 @@ import stackedSeries from '../../../fixtures/mock_data/date_histogram/_stacked_series'; import { vislibPointSeriesTypes } from './point_series'; -import percentileTestdata from './testdata_linechart_percentile.json'; -import percentileTestdataResult from './testdata_linechart_percentile_result.json'; -import percentileTestdataFloatValue from './testdata_linechart_percentile_float_value.json'; -import percentileTestdataFloatValueResult from './testdata_linechart_percentile_float_value_result.json'; const maxBucketData = { get: (prop) => { @@ -84,7 +80,7 @@ describe('vislibPointSeriesTypes', () => { describe('axis formatters', () => { it('should create a value axis config with the default y axis formatter', () => { - const parsedConfig = vislibPointSeriesTypes.line({}, maxBucketData); + const parsedConfig = vislibPointSeriesTypes.heatmap({}, maxBucketData); expect(parsedConfig.valueAxes.length).toEqual(1); expect(parsedConfig.valueAxes[0].labels.axisFormatter).toBe( maxBucketData.data.yAxisFormatter @@ -95,7 +91,7 @@ describe('vislibPointSeriesTypes', () => { const axisFormatter1 = jest.fn(); const axisFormatter2 = jest.fn(); const axisFormatter3 = jest.fn(); - const parsedConfig = vislibPointSeriesTypes.line( + const parsedConfig = vislibPointSeriesTypes.heatmap( { valueAxes: [ { @@ -166,67 +162,3 @@ describe('vislibPointSeriesTypes', () => { }); }); }); - -describe('Point Series Config Type Class Test Suite', function () { - let parsedConfig; - const histogramConfig = { - type: 'histogram', - addLegend: true, - tooltip: { - show: true, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: 'category', - title: {}, - }, - ], - valueAxes: [ - { - id: 'ValueAxis-1', - type: 'value', - labels: {}, - title: {}, - }, - ], - }; - - describe('histogram chart', function () { - beforeEach(function () { - parsedConfig = vislibPointSeriesTypes.column(histogramConfig, maxBucketData); - }); - it('should not throw an error when more than 25 series are provided', function () { - expect(parsedConfig.error).toBeUndefined(); - }); - - it('should set axis title and formatter from data', () => { - expect(parsedConfig.categoryAxes[0].title.text).toEqual(maxBucketData.data.xAxisLabel); - expect(parsedConfig.valueAxes[0].labels.axisFormatter).toBeDefined(); - }); - }); - - describe('line chart', function () { - function prepareData({ cfg, data }) { - const percentileDataObj = { - get: (prop) => { - return maxBucketData[prop] || maxBucketData.data[prop] || null; - }, - getLabels: () => [], - data: data, - }; - const parsedConfig = vislibPointSeriesTypes.line(cfg, percentileDataObj); - return parsedConfig; - } - - it('should render a percentile line chart', function () { - const parsedConfig = prepareData(percentileTestdata); - expect(parsedConfig).toMatchObject(percentileTestdataResult); - }); - - it('should render a percentile line chart when value is float', function () { - const parsedConfig = prepareData(percentileTestdataFloatValue); - expect(parsedConfig).toMatchObject(percentileTestdataFloatValueResult); - }); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile.json b/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile.json deleted file mode 100644 index 50d6eab03e3f7..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile.json +++ /dev/null @@ -1,464 +0,0 @@ -{ - "cfg": { - "addLegend": true, - "addTimeMarker": false, - "addTooltip": true, - "categoryAxes": [ - { - "id": "CategoryAxis-1", - "labels": { - "show": true, - "truncate": 100 - }, - "position": "bottom", - "scale": { - "type": "linear" - }, - "show": true, - "style": {}, - "title": {}, - "type": "category" - } - ], - "dimensions": { - "x": { - "accessor": 0, - "format": { - "id": "date", - "params": { - "pattern": "YYYY-MM-DD" - } - }, - "params": { - "date": true, - "interval": 86400000, - "format": "YYYY-MM-DD", - "bounds": { - "min": "2019-05-10T04:00:00.000Z", - "max": "2019-05-12T10:18:57.342Z" - } - }, - "aggType": "date_histogram" - }, - "y": [ - { - "accessor": 1, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - }, - { - "accessor": 2, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - } - ] - }, - "grid": { - "categoryLines": false, - "style": { - "color": "#eee" - } - }, - "legendPosition": "right", - "seriesParams": [ - { - "data": { - "id": "1", - "label": "Percentiles of AvgTicketPrice" - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - } - ], - "times": [], - "type": "area", - "valueAxes": [ - { - "id": "ValueAxis-1", - "labels": { - "filter": false, - "rotate": 0, - "show": true, - "truncate": 100 - }, - "name": "LeftAxis-1", - "position": "left", - "scale": { - "mode": "normal", - "type": "linear" - }, - "show": true, - "style": {}, - "title": { - "text": "Percentiles of AvgTicketPrice" - }, - "type": "value" - } - ] - }, - "data": { - "uiState": {}, - "data": { - "xAxisOrderedValues": [ - 1557460800000, - 1557547200000 - ], - "xAxisFormat": { - "id": "date", - "params": { - "pattern": "YYYY-MM-DD" - } - }, - "xAxisLabel": "timestamp per day", - "ordered": { - "interval": 86400000, - "date": true, - "min": 1557460800000, - "max": 1557656337342 - }, - "yAxisFormat": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "yAxisLabel": "", - "hits": 2 - }, - "series": [ - { - "id": "1.1", - "rawId": "col-1-1.1", - "label": "1st percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 116.33676605224609, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 0, - "value": 116.33676605224609 - }, - "parent": null, - "series": "1st percentile of AvgTicketPrice", - "seriesId": "col-1-1.1" - }, - { - "x": 1557547200000, - "y": 223, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 1, - "value": 223 - }, - "parent": null, - "series": "1st percentile of AvgTicketPrice", - "seriesId": "col-1-1.1" - } - ] - }, - { - "id": "1.50", - "rawId": "col-2-1.50", - "label": "50th percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 658.8453063964844, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 0, - "value": 658 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - }, - { - "x": 1557547200000, - "y": 756, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 1, - "value": 756.2283554077148 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - } - ] - } - ], - "type": "series", - "labels": [ - "1st percentile of AvgTicketPrice", - "50th percentile of AvgTicketPrice" - ] - } - -} diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json b/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json deleted file mode 100644 index 1987c59f6722b..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value.json +++ /dev/null @@ -1,463 +0,0 @@ -{ - "cfg": { - "addLegend": true, - "addTimeMarker": false, - "addTooltip": true, - "categoryAxes": [ - { - "id": "CategoryAxis-1", - "labels": { - "show": true, - "truncate": 100 - }, - "position": "bottom", - "scale": { - "type": "linear" - }, - "show": true, - "style": {}, - "title": {}, - "type": "category" - } - ], - "dimensions": { - "x": { - "accessor": 0, - "format": { - "id": "date", - "params": { - "pattern": "YYYY-MM-DD" - } - }, - "params": { - "date": true, - "interval": 86400000, - "format": "YYYY-MM-DD", - "bounds": { - "min": "2019-05-10T04:00:00.000Z", - "max": "2019-05-12T10:18:57.342Z" - } - }, - "aggType": "date_histogram" - }, - "y": [ - { - "accessor": 1, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - }, - { - "accessor": 2, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - } - ] - }, - "grid": { - "categoryLines": false, - "style": { - "color": "#eee" - } - }, - "legendPosition": "right", - "seriesParams": [ - { - "data": { - "id": "1", - "label": "Percentiles of AvgTicketPrice" - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - } - ], - "times": [], - "type": "area", - "valueAxes": [ - { - "id": "ValueAxis-1", - "labels": { - "filter": false, - "rotate": 0, - "show": true, - "truncate": 100 - }, - "name": "LeftAxis-1", - "position": "left", - "scale": { - "mode": "normal", - "type": "linear" - }, - "show": true, - "style": {}, - "title": { - "text": "Percentiles of AvgTicketPrice" - }, - "type": "value" - } - ] - }, - "data": { - "uiState": {}, - "data": { - "xAxisOrderedValues": [ - 1557460800000, - 1557547200000 - ], - "xAxisFormat": { - "id": "date", - "params": { - "pattern": "YYYY-MM-DD" - } - }, - "xAxisLabel": "timestamp per day", - "ordered": { - "interval": 86400000, - "date": true, - "min": 1557460800000, - "max": 1557656337342 - }, - "yAxisFormat": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "yAxisLabel": "", - "hits": 2 - }, - "series": [ - { - "id": "1.['1.1']", - "rawId": "col-1-1.['1.1']", - "label": "1.1th percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 116.33676605224609, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 0, - "value": 116.33676605224609 - }, - "parent": null, - "series": "1.1th percentile of AvgTicketPrice", - "seriesId": "col-1-1.['1.1']" - }, - { - "x": 1557547200000, - "y": 223, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 1, - "value": 223 - }, - "parent": null, - "series": "1.1th percentile of AvgTicketPrice", - "seriesId": "col-1-1.['1.1']" - } - ] - }, - { - "id": "1.50", - "rawId": "col-2-1.50", - "label": "50th percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 658.8453063964844, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 0, - "value": 658 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - }, - { - "x": 1557547200000, - "y": 756, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 1, - "value": 756.2283554077148 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - } - ] - } - ], - "type": "series", - "labels": [ - "1.1th percentile of AvgTicketPrice", - "50th percentile of AvgTicketPrice" - ] - } -} diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json b/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json deleted file mode 100644 index ae1f3cbf24c33..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_float_value_result.json +++ /dev/null @@ -1,456 +0,0 @@ -{ - "addLegend": true, - "addTimeMarker": false, - "addTooltip": true, - "categoryAxes": [ - { - "id": "CategoryAxis-1", - "labels": { - "show": true, - "truncate": 100 - }, - "position": "bottom", - "scale": { - "type": "linear" - }, - "show": true, - "style": {}, - "title": { - "text": "Date Histogram" - }, - "type": "category" - } - ], - "dimensions": { - "x": { - "accessor": 0, - "format": { - "id": "date", - "params": { - "pattern": "YYYY-MM-DD" - } - }, - "params": { - "date": true, - "interval": 86400000, - "format": "YYYY-MM-DD", - "bounds": { - "min": "2019-05-10T04:00:00.000Z", - "max": "2019-05-12T10:18:57.342Z" - } - }, - "aggType": "date_histogram" - }, - "y": [ - { - "accessor": 1, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - }, - { - "accessor": 2, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - } - ] - }, - "grid": { - "categoryLines": false, - "style": { - "color": "#eee" - } - }, - "legendPosition": "right", - "seriesParams": [ - { - "data": { - "id": "1", - "label": "Percentiles of AvgTicketPrice" - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - } - ], - "times": [], - "type": "point_series", - "valueAxes": [ - { - "id": "ValueAxis-1", - "labels": { - "filter": false, - "rotate": 0, - "show": true, - "truncate": 100 - }, - "name": "LeftAxis-1", - "position": "left", - "scale": { - "mode": "normal", - "type": "linear" - }, - "show": true, - "style": {}, - "title": { - "text": "Percentiles of AvgTicketPrice" - }, - "type": "value" - } - ], - "chartTitle": {}, - "mode": "normal", - "tooltip": { - "show": true - }, - "charts": [ - { - "type": "point_series", - "addTimeMarker": false, - "series": [ - { - "show": true, - "type": "area", - "mode": "normal", - "drawLinesBetweenPoints": true, - "showCircles": true, - "data": { - "id": "1.['1.1']", - "rawId": "col-1-1.['1.1']", - "label": "1.1th percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 116.33676605224609, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 0, - "value": 116.33676605224609 - }, - "parent": null, - "series": "1.1th percentile of AvgTicketPrice", - "seriesId": "col-1-1.['1.1']" - }, - { - "x": 1557547200000, - "y": 223, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 1, - "value": 223 - }, - "parent": null, - "series": "1.1th percentile of AvgTicketPrice", - "seriesId": "col-1-1.['1.1']" - } - ] - } - }, - { - "data": { - "id": "1.50", - "rawId": "col-2-1.50", - "label": "50th percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 658.8453063964844, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 0, - "value": 658 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - }, - { - "x": 1557547200000, - "y": 756, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.['1.1']", - "name": "1.1th percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.['1.1']": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.['1.1']": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 1, - "value": 756.2283554077148 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - } - ] - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - } - ] - } - ], - "enableHover": true -} diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json b/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json deleted file mode 100644 index f2ee245a8431f..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/lib/types/testdata_linechart_percentile_result.json +++ /dev/null @@ -1,458 +0,0 @@ -{ - "addLegend": true, - "addTimeMarker": false, - "addTooltip": true, - "categoryAxes": [ - { - "id": "CategoryAxis-1", - "labels": { - "show": true, - "truncate": 100 - }, - "position": "bottom", - "scale": { - "type": "linear" - }, - "show": true, - "style": {}, - "title": { - "text": "Date Histogram" - }, - "type": "category" - } - ], - "dimensions": { - "x": { - "accessor": 0, - "format": { - "id": "date", - "params": { - "pattern": "YYYY-MM-DD" - } - }, - "params": { - "date": true, - "interval": 86400000, - "format": "YYYY-MM-DD", - "bounds": { - "min": "2019-05-10T04:00:00.000Z", - "max": "2019-05-12T10:18:57.342Z" - } - }, - "aggType": "date_histogram" - }, - "y": [ - { - "accessor": 1, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - }, - { - "accessor": 2, - "format": { - "id": "number", - "params": { - "pattern": "$0,0.[00]" - } - }, - "params": {}, - "aggType": "percentiles" - } - ] - }, - "grid": { - "categoryLines": false, - "style": { - "color": "#eee" - } - }, - "legendPosition": "right", - "seriesParams": [ - { - "data": { - "id": "1", - "label": "Percentiles of AvgTicketPrice" - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - } - ], - "times": [], - "type": "point_series", - "valueAxes": [ - { - "id": "ValueAxis-1", - "labels": { - "filter": false, - "rotate": 0, - "show": true, - "truncate": 100 - }, - "name": "LeftAxis-1", - "position": "left", - "scale": { - "mode": "normal", - "type": "linear" - }, - "show": true, - "style": {}, - "title": { - "text": "Percentiles of AvgTicketPrice" - }, - "type": "value" - } - ], - "chartTitle": {}, - "mode": "normal", - "tooltip": { - "show": true - }, - "charts": [ - { - "type": "point_series", - "addTimeMarker": false, - "series": [ - { - "data": { - "id": "1.1", - "rawId": "col-1-1.1", - "label": "1st percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 116.33676605224609, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 0, - "value": 116.33676605224609 - }, - "parent": null, - "series": "1st percentile of AvgTicketPrice", - "seriesId": "col-1-1.1" - }, - { - "x": 1557547200000, - "y": 223, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 1, - "row": 1, - "value": 223 - }, - "parent": null, - "series": "1st percentile of AvgTicketPrice", - "seriesId": "col-1-1.1" - } - ] - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - }, - { - "data": { - "id": "1.50", - "rawId": "col-2-1.50", - "label": "50th percentile of AvgTicketPrice", - "values": [ - { - "x": 1557460800000, - "y": 658.8453063964844, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 0, - "value": 1557460800000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 0, - "value": 658 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - }, - { - "x": 1557547200000, - "y": 756, - "extraMetrics": [], - "xRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 0, - "row": 1, - "value": 1557547200000 - }, - "yRaw": { - "table": { - "columns": [ - { - "id": "col-0-2", - "name": "timestamp per day" - }, - { - "id": "col-1-1.1", - "name": "1st percentile of AvgTicketPrice" - }, - { - "id": "col-2-1.50", - "name": "50th percentile of AvgTicketPrice" - } - ], - "rows": [ - { - "col-0-2": 1557460800000, - "col-1-1.1": 116, - "col-2-1.50": 658 - }, - { - "col-0-2": 1557547200000, - "col-1-1.1": 223, - "col-2-1.50": 756 - } - ] - }, - "column": 2, - "row": 1, - "value": 756.2283554077148 - }, - "parent": null, - "series": "50th percentile of AvgTicketPrice", - "seriesId": "col-2-1.50" - } - ] - }, - "drawLinesBetweenPoints": true, - "interpolate": "cardinal", - "mode": "normal", - "show": "true", - "showCircles": true, - "type": "line", - "valueAxis": "ValueAxis-1" - } - ] - } - ], - "enableHover": true -} diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.test.js index 441c5d9969c4f..1ae32aa4b5473 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/vis_config.test.js @@ -78,7 +78,7 @@ describe('Vislib VisConfig Class Test Suite', function () { visConfig = new VisConfig( { - type: 'point_series', + type: 'heatmap', }, data, getMockUiState(), diff --git a/src/plugins/vis_types/vislib/public/vislib/vis.test.js b/src/plugins/vis_types/vislib/public/vislib/vis.test.js index 1614175f7e2a4..46afbd1c62f69 100644 --- a/src/plugins/vis_types/vislib/public/vislib/vis.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/vis.test.js @@ -26,6 +26,7 @@ const names = ['series', 'columns', 'rows', 'stackedSeries']; let mockedHTMLElementClientSizes; let mockedSVGElementGetBBox; let mockedSVGElementGetComputedTextLength; +let mockWidth; dataArray.forEach(function (data, i) { describe('Vislib Vis Test Suite for ' + names[i] + ' Data', function () { @@ -35,16 +36,30 @@ dataArray.forEach(function (data, i) { let mockUiState; let secondVis; let numberOfCharts; + let config; beforeAll(() => { mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); mockedSVGElementGetBBox = setSVGElementGetBBox(100); mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); + mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(900); }); beforeEach(() => { - vis = getVis(); - secondVis = getVis(); + config = { + type: 'heatmap', + addLegend: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], + }; + vis = getVis(config); + secondVis = getVis(config); mockUiState = getMockUiState(); }); @@ -57,6 +72,7 @@ dataArray.forEach(function (data, i) { mockedHTMLElementClientSizes.mockRestore(); mockedSVGElementGetBBox.mockRestore(); mockedSVGElementGetComputedTextLength.mockRestore(); + mockWidth.mockRestore(); }); describe('render Method', function () { diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/_vis_fixture.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/_vis_fixture.js index f4e2e4b977b8f..7313d1c8f8eac 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/_vis_fixture.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/_vis_fixture.js @@ -51,7 +51,7 @@ export function getVis(vislibParams, element) { defaultYExtents: false, setYExtents: false, yAxis: {}, - type: 'histogram', + type: 'heatmap', }), coreMock.createSetup(), chartPluginMock.createStartContract() diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js index 97ea3313d81de..c105102dc6ab9 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js @@ -7,7 +7,12 @@ */ import d3 from 'd3'; -import { setHTMLElementClientSizes, setSVGElementGetBBox } from '@kbn/test/jest'; +import $ from 'jquery'; +import { + setHTMLElementClientSizes, + setSVGElementGetBBox, + setSVGElementGetComputedTextLength, +} from '@kbn/test/jest'; import { Chart } from './_chart'; import { getMockUiState } from '../../fixtures/mocks'; import { getVis } from './_vis_fixture'; @@ -96,22 +101,31 @@ describe('Vislib _chart Test Suite', function () { let mockedHTMLElementClientSizes; let mockedSVGElementGetBBox; + let mockedSVGElementGetComputedTextLength; + let mockWidth; beforeAll(() => { mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); mockedSVGElementGetBBox = setSVGElementGetBBox(100); + mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); + mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(900); }); beforeEach(() => { el = d3.select('body').append('div').attr('class', 'column-chart'); config = { - type: 'histogram', - addTooltip: true, + type: 'heatmap', addLegend: true, - zeroFill: true, + addTooltip: true, + colorsNumber: 4, + colorSchema: 'Greens', + setColorRange: false, + percentageMode: true, + percentageFormatPattern: '0.0%', + invertColors: false, + colorsRange: [], }; - vis = getVis(config, el[0][0]); vis.render(data, getMockUiState()); @@ -126,6 +140,8 @@ describe('Vislib _chart Test Suite', function () { afterAll(() => { mockedHTMLElementClientSizes.mockRestore(); mockedSVGElementGetBBox.mockRestore(); + mockedSVGElementGetComputedTextLength.mockRestore(); + mockWidth.mockRestore(); }); test('should be a constructor for visualization modules', function () { diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series.js index b4ab2ea2992c5..dae60fda47631 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series.js @@ -13,10 +13,8 @@ import $ from 'jquery'; import { Tooltip } from '../components/tooltip'; import { Chart } from './_chart'; import { TimeMarker } from './time_marker'; -import { seriesTypes } from './point_series/series_types'; import { touchdownTemplate } from '../partials/touchdown_template'; - -const seriTypes = seriesTypes; +import { HeatmapChart } from './point_series/heatmap_chart'; /** * Line Chart Visualization @@ -233,9 +231,7 @@ export class PointSeries extends Chart { self.series = []; _.each(self.chartConfig.series, (seriArgs, i) => { if (!seriArgs.show) return; - const SeriClass = - seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line; - const series = new SeriClass( + const series = new HeatmapChart( self.handler, svg, data.series[i], diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_index.scss b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_index.scss deleted file mode 100644 index 53fce786ecc15..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './labels'; diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_labels.scss b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_labels.scss deleted file mode 100644 index 8bcd17fd55ddf..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/_labels.scss +++ /dev/null @@ -1,20 +0,0 @@ -$visColumnChartBarLabelDarkColor: #000; // EUI doesn't yet have a variable for fully black in all themes; -$visColumnChartBarLabelLightColor: $euiColorGhost; - -.visColumnChart__barLabel { - font-size: 8pt; - pointer-events: none; -} - -.visColumnChart__barLabel--stack { - dominant-baseline: central; - text-anchor: middle; -} - -.visColumnChart__bar-label--dark { - fill: $visColumnChartBarLabelDarkColor; -} - -.visColumnChart__bar-label--light { - fill: $visColumnChartBarLabelLightColor; -} diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.js deleted file mode 100644 index 2e2ce79247c3d..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.js +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import { PointSeries } from './_point_series'; - -const defaults = { - mode: 'normal', - showCircles: true, - radiusRatio: 9, - showLines: true, - interpolate: 'linear', - color: undefined, - fillColor: undefined, -}; -/** - * Area chart visualization - * - * @class AreaChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific - * chart - */ -export class AreaChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, uiSettings) { - super(handler, chartEl, chartData, seriesConfigArgs, uiSettings); - - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); - this.isOverlapping = this.seriesConfig.mode !== 'stacked'; - if (this.isOverlapping) { - // Default opacity should return to 0.6 on mouseout - const defaultOpacity = 0.6; - this.seriesConfig.defaultOpacity = defaultOpacity; - handler.highlight = function (element) { - const label = this.getAttribute('data-label'); - if (!label) return; - - const highlightOpacity = 0.8; - const highlightElements = $('[data-label]', element.parentNode).filter(function (els, el) { - return `${$(el).data('label')}` === label; - }); - $('[data-label]', element.parentNode) - .not(highlightElements) - .css('opacity', defaultOpacity / 2); // half of the default opacity - highlightElements.css('opacity', highlightOpacity); - }; - handler.unHighlight = function (element) { - $('[data-label]', element).css('opacity', defaultOpacity); - - //The legend should keep max opacity - $('[data-label]', $(element).siblings()).css('opacity', 1); - }; - } - } - - addPath(svg, data) { - const ordered = this.handler.data.get('ordered'); - const isTimeSeries = ordered && ordered.date; - const isOverlapping = this.isOverlapping; - const color = this.handler.data.getColorFunc(); - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const interpolate = this.seriesConfig.interpolate; - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - - // Data layers - const layer = svg.append('g').attr('class', function (d, i) { - return 'series series-' + i; - }); - - // Append path - const path = layer - .append('path') - .attr('data-label', data.label) - .style('fill', () => color(data.label)) - .style('stroke', () => color(data.label)) - .classed('visAreaChart__overlapArea', function () { - return isOverlapping; - }) - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - function x(d) { - if (isTimeSeries) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } - - function y1(d) { - const y0 = d.y0 || 0; - const y = d.y || 0; - return yScale(y0 + y); - } - - function y0(d) { - const y0 = d.y0 || 0; - return yScale(y0); - } - - function getArea() { - if (isHorizontal) { - return d3.svg.area().x(x).y0(y0).y1(y1); - } else { - return d3.svg.area().y(x).x0(y0).x1(y1); - } - } - - // update - path - .attr('d', function () { - const area = getArea() - .defined(function (d) { - return !_.isNull(d.y); - }) - .interpolate(interpolate); - return area(data.values); - }) - .style('stroke-width', '1px'); - - return path; - } - - /** - * Adds SVG circles to area chart - * - * @method addCircles - * @param svg {HTMLElement} SVG to which circles are appended - * @param data {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with circles added - */ - addCircles(svg, data) { - const color = this.handler.data.getColorFunc(); - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const ordered = this.handler.data.get('ordered'); - const circleRadius = 12; - const circleStrokeWidth = 0; - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - const isOverlapping = this.isOverlapping; - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - - const layer = svg - .append('g') - .attr('class', 'points area') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - // append the circles - const circles = layer.selectAll('circles').data(function appendData() { - return data.values.filter(function isZeroOrNull(d) { - return d.y !== 0 && !_.isNull(d.y); - }); - }); - - // exit - circles.exit().remove(); - - // enter - circles - .enter() - .append('circle') - .attr('data-label', data.label) - .attr('stroke', () => { - return color(data.label); - }) - .attr('fill', 'transparent') - .attr('stroke-width', circleStrokeWidth); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } - - function cy(d) { - const y = d.y || 0; - if (isOverlapping) { - return yScale(y); - } - return yScale(d.y0 + y); - } - - // update - circles - .attr('cx', isHorizontal ? cx : cy) - .attr('cy', isHorizontal ? cy : cx) - .attr('r', circleRadius); - - // Add tooltip - if (isTooltip) { - circles.call(tooltip.render()); - } - - return circles; - } - - addPathEvents(path) { - const events = this.events; - if (this.handler.visConfig.get('enableHover')) { - const hover = events.addHoverEvent(); - const mouseout = events.addMouseoutEvent(); - path.call(hover).call(mouseout); - } - } - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the area chart - */ - draw() { - const self = this; - - return function (selection) { - selection.each(function () { - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); - - const path = self.addPath(svg, self.chartData); - self.addPathEvents(path); - const circles = self.addCircles(svg, self.chartData); - self.addCircleEvents(circles); - - if (self.thresholdLineOptions.show) { - self.addThresholdLine(self.chartEl); - } - self.events.emit('rendered', { - chart: self.chartData, - }); - - return svg; - }); - }; - } -} diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.test.js deleted file mode 100644 index 68b0728026498..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/area_chart.test.js +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import { - setHTMLElementClientSizes, - setSVGElementGetBBox, - setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; - -import { getMockUiState } from '../../../fixtures/mocks'; -import { getVis } from '../_vis_fixture'; - -const dataTypesArray = { - 'series pos': import('../../../fixtures/mock_data/date_histogram/_series'), - 'series pos neg': import('../../../fixtures/mock_data/date_histogram/_series_pos_neg'), - 'series neg': import('../../../fixtures/mock_data/date_histogram/_series_neg'), - 'term columns': import('../../../fixtures/mock_data/terms/_columns'), - 'range rows': import('../../../fixtures/mock_data/range/_rows'), - stackedSeries: import('../../../fixtures/mock_data/date_histogram/_stacked_series'), -}; - -const vislibParams = { - type: 'area', - addLegend: true, - addTooltip: true, - mode: 'stacked', -}; - -let mockedHTMLElementClientSizes; -let mockedSVGElementGetBBox; -let mockedSVGElementGetComputedTextLength; - -_.forOwn(dataTypesArray, function (dataType, dataTypeName) { - describe('Vislib Area Chart Test Suite for ' + dataTypeName + ' Data', function () { - let vis; - let mockUiState; - - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - }); - - beforeEach(async () => { - vis = getVis(vislibParams); - mockUiState = getMockUiState(); - vis.on('brush', _.noop); - vis.render(await dataType, mockUiState); - }); - - afterEach(function () { - vis.destroy(); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - }); - - describe('stackData method', function () { - let stackedData; - let isStacked; - - beforeEach(function () { - vis.handler.charts.forEach(function (chart) { - stackedData = chart.chartData; - - isStacked = stackedData.series.every(function (arr) { - return arr.values.every(function (d) { - return _.isNumber(d.y0); - }); - }); - }); - }); - - test('should append a d.y0 key to the data object', function () { - expect(isStacked).toBe(true); - }); - }); - - describe('addPath method', function () { - test('should append a area paths', function () { - vis.handler.charts.forEach(function (chart) { - expect($(chart.chartEl).find('path').length).toBeGreaterThan(0); - }); - }); - }); - - describe('addPathEvents method', function () { - let path; - let d3selectedPath; - let onMouseOver; - - beforeEach(function () { - vis.handler.charts.forEach(function (chart) { - path = $(chart.chartEl).find('path')[0]; - d3selectedPath = d3.select(path)[0][0]; - - // d3 instance of click and hover - onMouseOver = !!d3selectedPath.__onmouseover; - }); - }); - - test('should attach a hover event', function () { - vis.handler.charts.forEach(function () { - expect(onMouseOver).toBe(true); - }); - }); - }); - - describe('addCircleEvents method', function () { - let circle; - let brush; - let d3selectedCircle; - let onBrush; - let onClick; - let onMouseOver; - - beforeEach(() => { - vis.handler.charts.forEach(function (chart) { - circle = $(chart.chartEl).find('circle')[0]; - brush = $(chart.chartEl).find('.brush'); - d3selectedCircle = d3.select(circle)[0][0]; - - // d3 instance of click and hover - onBrush = !!brush; - onClick = !!d3selectedCircle.__onclick; - onMouseOver = !!d3selectedCircle.__onmouseover; - }); - }); - - // D3 brushing requires that a g element is appended that - // listens for mousedown events. This g element includes - // listeners, however, I was not able to test for the listener - // function being present. I will need to update this test - // in the future. - test('should attach a brush g element', function () { - vis.handler.charts.forEach(function () { - expect(onBrush).toBe(true); - }); - }); - - test('should attach a click event', function () { - vis.handler.charts.forEach(function () { - expect(onClick).toBe(true); - }); - }); - - test('should attach a hover event', function () { - vis.handler.charts.forEach(function () { - expect(onMouseOver).toBe(true); - }); - }); - }); - - describe('addCircles method', function () { - test('should append circles', function () { - vis.handler.charts.forEach(function (chart) { - expect($(chart.chartEl).find('circle').length).toBeGreaterThan(0); - }); - }); - - test('should not draw circles where d.y === 0', function () { - vis.handler.charts.forEach(function (chart) { - const series = chart.chartData.series; - const isZero = series.some(function (d) { - return d.y === 0; - }); - const circles = $.makeArray($(chart.chartEl).find('circle')); - const isNotDrawn = circles.some(function (d) { - return d.__data__.y === 0; - }); - - if (isZero) { - expect(isNotDrawn).toBe(false); - } - }); - }); - }); - - describe('draw method', function () { - test('should return a function', function () { - vis.handler.charts.forEach(function (chart) { - expect(_.isFunction(chart.draw())).toBe(true); - }); - }); - - test('should return a yMin and yMax', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const domain = yAxis.getScale().domain(); - - expect(domain[0]).not.toBe(undefined); - expect(domain[1]).not.toBe(undefined); - }); - }); - - test('should render a zero axis line', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - - if (yAxis.yMin < 0 && yAxis.yMax > 0) { - expect($(chart.chartEl).find('line.zero-line').length).toBe(1); - } - }); - }); - }); - - describe('defaultYExtents is true', function () { - beforeEach(async function () { - vis.visConfigArgs.defaultYExtents = true; - vis.render(await dataType, mockUiState); - }); - - test('should return yAxis extents equal to data extents', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const min = vis.handler.valueAxes[0].axisScale.getYMin(); - const max = vis.handler.valueAxes[0].axisScale.getYMax(); - const domain = yAxis.getScale().domain(); - expect(domain[0]).toEqual(min); - expect(domain[1]).toEqual(max); - }); - }); - }); - [0, 2, 4, 8].forEach(function (boundsMarginValue) { - describe('defaultYExtents is true and boundsMargin is defined', function () { - beforeEach(async function () { - vis.visConfigArgs.defaultYExtents = true; - vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(await dataType, mockUiState); - }); - - test('should return yAxis extents equal to data extents with boundsMargin', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const min = vis.handler.valueAxes[0].axisScale.getYMin(); - const max = vis.handler.valueAxes[0].axisScale.getYMax(); - const domain = yAxis.getScale().domain(); - if (min < 0 && max < 0) { - expect(domain[0]).toEqual(min); - expect(domain[1] - boundsMarginValue).toEqual(max); - } else if (min > 0 && max > 0) { - expect(domain[0] + boundsMarginValue).toEqual(min); - expect(domain[1]).toEqual(max); - } else { - expect(domain[0]).toEqual(min); - expect(domain[1]).toEqual(max); - } - }); - }); - }); - }); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.js deleted file mode 100644 index 1c543d06e9be9..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.js +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import d3 from 'd3'; -import { isColorDark } from '@elastic/eui/lib/services'; -import { PointSeries } from './_point_series'; - -const defaults = { - mode: 'normal', - showTooltip: true, - color: undefined, - fillColor: undefined, - showLabel: true, -}; - -/** - * Histogram intervals are not always equal widths, e.g, monthly time intervals. - * It is more visually appealing to vary bar width so that gutter width is constant. - */ -function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCount = 1) { - let datumWidth = defaultWidth; - if (nextDatum) { - datumWidth = (scale(nextDatum.x) - scale(datum.x) - gutterWidth) / groupCount; - // To handle data-sets with holes, do not let width be larger than default. - if (datumWidth > defaultWidth) { - datumWidth = defaultWidth; - } - } - return datumWidth; -} - -/** - * Vertical Bar Chart Visualization: renders vertical and/or stacked bars - * - * @class ColumnChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific chart - */ -export class ColumnChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, uiSettings) { - super(handler, chartEl, chartData, seriesConfigArgs, uiSettings); - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); - this.labelOptions = _.defaults(handler.visConfig.get('labels', {}), defaults.showLabel); - } - - addBars(svg, data) { - const self = this; - const color = this.handler.data.getColorFunc(); - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - - const layer = svg - .append('g') - .attr('class', 'series histogram') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - const bars = layer.selectAll('rect').data( - data.values.filter(function (d) { - return !_.isNull(d.y); - }) - ); - - bars.exit().remove(); - - bars - .enter() - .append('rect') - .attr('data-label', data.label) - .attr('fill', () => color(data.label)) - .attr('stroke', () => color(data.label)); - - self.updateBars(bars); - - // Add tooltip - if (isTooltip) { - bars.call(tooltip.render()); - } - - return bars; - } - - /** - * Determines whether bars are grouped or stacked and updates the D3 - * selection - * - * @method updateBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - updateBars(bars) { - if (this.seriesConfig.mode === 'stacked') { - return this.addStackedBars(bars); - } - return this.addGroupedBars(bars); - } - - /** - * Adds stacked bars to column chart visualization - * - * @method addStackedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - addStackedBars(bars) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); - const isLabels = this.labelOptions.show; - const yMin = yScale.domain()[0]; - const gutterSpacingPercentage = 0.15; - const chartData = this.chartData; - const getGroupedNum = this.getGroupedNum.bind(this); - const groupCount = this.getGroupedCount(); - - let barWidth; - let gutterWidth; - - if (isTimeScale) { - const { min, interval } = this.handler.data.get('ordered'); - let intervalWidth = xScale(min + interval) - xScale(min); - intervalWidth = Math.abs(intervalWidth); - - gutterWidth = intervalWidth * gutterSpacingPercentage; - barWidth = (intervalWidth - gutterWidth) / groupCount; - } - - function x(d, i) { - const groupNum = getGroupedNum(d.seriesId); - - if (isTimeScale) { - return ( - xScale(d.x) + - datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount) * groupNum - ); - } - return xScale(d.x) + (xScale.rangeBand() / groupCount) * groupNum; - } - - function y(d) { - if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { - return yScale(d.y0); - } - return yScale(d.y0 + d.y); - } - - function labelX(d, i) { - return x(d, i) + widthFunc(d, i) / 2; - } - - function labelY(d) { - return y(d) + heightFunc(d) / 2; - } - - function labelDisplay(d, i) { - if (isHorizontal && this.getBBox().width > widthFunc(d, i)) return 'none'; - if (!isHorizontal && this.getBBox().width > heightFunc(d)) return 'none'; - if (isHorizontal && this.getBBox().height > heightFunc(d)) return 'none'; - if (!isHorizontal && this.getBBox().height > widthFunc(d, i)) return 'none'; - return 'block'; - } - - function widthFunc(d, i) { - if (isTimeScale) { - return datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount); - } - return xScale.rangeBand() / groupCount; - } - - function heightFunc(d) { - // for split bars or for one series, - // last series will have d.y0 = 0 - if (d.y0 === 0 && yMin > 0) { - return yScale(yMin) - yScale(d.y); - } - return Math.abs(yScale(d.y0) - yScale(d.y0 + d.y)); - } - - function formatValue(d) { - return chartData.yAxisFormatter(d.y); - } - - // update - bars - .attr('x', isHorizontal ? x : y) - .attr('width', isHorizontal ? widthFunc : heightFunc) - .attr('y', isHorizontal ? y : x) - .attr('height', isHorizontal ? heightFunc : widthFunc); - - const layer = d3.select(bars[0].parentNode); - const barLabels = layer.selectAll('text').data( - chartData.values.filter(function (d) { - return !_.isNull(d.y); - }) - ); - - if (isLabels) { - const colorFunc = this.handler.data.getColorFunc(); - const d3Color = d3.rgb(colorFunc(chartData.label)); - let labelClass; - if (isColorDark(d3Color.r, d3Color.g, d3Color.b)) { - labelClass = 'visColumnChart__bar-label--light'; - } else { - labelClass = 'visColumnChart__bar-label--dark'; - } - - barLabels - .enter() - .append('text') - .text(formatValue) - .attr('class', `visColumnChart__barLabel visColumnChart__barLabel--stack ${labelClass}`) - .attr('x', isHorizontal ? labelX : labelY) - .attr('y', isHorizontal ? labelY : labelX) - - // display must apply last, because labelDisplay decision it based - // on text bounding box which depends on actual applied style. - .attr('display', labelDisplay); - } - - return bars; - } - - /** - * Adds grouped bars to column chart visualization - * - * @method addGroupedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - addGroupedBars(bars) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const chartData = this.chartData; - const groupCount = this.getGroupedCount(); - const gutterSpacingPercentage = 0.15; - const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const isLogScale = this.getValueAxis().axisConfig.isLogScale(); - const isLabels = this.labelOptions.show; - const getGroupedNum = this.getGroupedNum.bind(this); - - let barWidth; - let gutterWidth; - - if (isTimeScale) { - const { min, interval } = this.handler.data.get('ordered'); - let intervalWidth = xScale(min + interval) - xScale(min); - intervalWidth = Math.abs(intervalWidth); - - gutterWidth = intervalWidth * gutterSpacingPercentage; - barWidth = (intervalWidth - gutterWidth) / groupCount; - } - - function x(d, i) { - const groupNum = getGroupedNum(d.seriesId); - if (isTimeScale) { - return ( - xScale(d.x) + - datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount) * groupNum - ); - } - return xScale(d.x) + (xScale.rangeBand() / groupCount) * groupNum; - } - - function y(d) { - if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { - return yScale(0); - } - return yScale(d.y); - } - - function labelX(d, i) { - return x(d, i) + widthFunc(d, i) / 2; - } - - function labelY(d) { - if (isHorizontal) { - return d.y >= 0 ? y(d) - 4 : y(d) + heightFunc(d) + this.getBBox().height; - } - return d.y >= 0 ? y(d) + heightFunc(d) + 4 : y(d) - this.getBBox().width - 4; - } - - function labelDisplay(d, i) { - if (isHorizontal && this.getBBox().width > widthFunc(d, i)) { - return 'none'; - } - if (!isHorizontal && this.getBBox().height > widthFunc(d)) { - return 'none'; - } - return 'block'; - } - function widthFunc(d, i) { - if (isTimeScale) { - return datumWidth(barWidth, d, bars.data()[i + 1], xScale, gutterWidth, groupCount); - } - return xScale.rangeBand() / groupCount; - } - - function heightFunc(d) { - const baseValue = isLogScale ? 1 : 0; - return Math.abs(yScale(baseValue) - yScale(d.y)); - } - - function formatValue(d) { - return chartData.yAxisFormatter(d.y); - } - - // update - bars - .attr('x', isHorizontal ? x : y) - .attr('width', isHorizontal ? widthFunc : heightFunc) - .attr('y', isHorizontal ? y : x) - .attr('height', isHorizontal ? heightFunc : widthFunc); - - const layer = d3.select(bars[0].parentNode); - const barLabels = layer.selectAll('text').data( - chartData.values.filter(function (d) { - return !_.isNull(d.y); - }) - ); - - barLabels.exit().remove(); - - if (isLabels) { - const labelColor = this.handler.data.getColorFunc()(chartData.label); - - barLabels - .enter() - .append('text') - .text(formatValue) - .attr('class', 'visColumnChart__barLabel') - .attr('x', isHorizontal ? labelX : labelY) - .attr('y', isHorizontal ? labelY : labelX) - .attr('dominant-baseline', isHorizontal ? 'auto' : 'central') - .attr('text-anchor', isHorizontal ? 'middle' : 'start') - .attr('fill', labelColor) - - // display must apply last, because labelDisplay decision it based - // on text bounding box which depends on actual applied style. - .attr('display', labelDisplay); - } - return bars; - } - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the vertical bar chart - */ - draw() { - const self = this; - - return function (selection) { - selection.each(function () { - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); - - const bars = self.addBars(svg, self.chartData); - self.addCircleEvents(bars); - - if (self.thresholdLineOptions.show) { - self.addThresholdLine(self.chartEl); - } - - self.events.emit('rendered', { - chart: self.chartData, - }); - - return svg; - }); - }; - } -} diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.test.js deleted file mode 100644 index 8f0db3ab18393..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/column_chart.test.js +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import _ from 'lodash'; -import d3 from 'd3'; -import $ from 'jquery'; -import { - setHTMLElementClientSizes, - setSVGElementGetBBox, - setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; - -// Data -import series from '../../../fixtures/mock_data/date_histogram/_series'; -import seriesPosNeg from '../../../fixtures/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from '../../../fixtures/mock_data/date_histogram/_series_neg'; -import termsColumns from '../../../fixtures/mock_data/terms/_columns'; -import histogramRows from '../../../fixtures/mock_data/histogram/_rows'; -import stackedSeries from '../../../fixtures/mock_data/date_histogram/_stacked_series'; - -import { seriesMonthlyInterval } from '../../../fixtures/mock_data/date_histogram/_series_monthly_interval'; -import { rowsSeriesWithHoles } from '../../../fixtures/mock_data/date_histogram/_rows_series_with_holes'; -import rowsWithZeros from '../../../fixtures/mock_data/date_histogram/_rows'; -import { getMockUiState } from '../../../fixtures/mocks'; -import { getVis } from '../_vis_fixture'; - -// tuple, with the format [description, mode, data] -const dataTypesArray = [ - ['series', 'stacked', series], - ['series with positive and negative values', 'stacked', seriesPosNeg], - ['series with negative values', 'stacked', seriesNeg], - ['terms columns', 'grouped', termsColumns], - ['histogram rows', 'percentage', histogramRows], - ['stackedSeries', 'stacked', stackedSeries], -]; - -let mockedHTMLElementClientSizes; -let mockedSVGElementGetBBox; -let mockedSVGElementGetComputedTextLength; - -dataTypesArray.forEach(function (dataType) { - const name = dataType[0]; - const mode = dataType[1]; - const data = dataType[2]; - - describe('Vislib Column Chart Test Suite for ' + name + ' Data', function () { - let vis; - let mockUiState; - const vislibParams = { - type: 'histogram', - addLegend: true, - addTooltip: true, - mode: mode, - zeroFill: true, - grid: { - categoryLines: true, - valueAxis: 'ValueAxis-1', - }, - }; - - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - }); - - beforeEach(() => { - vis = getVis(vislibParams); - mockUiState = getMockUiState(); - vis.on('brush', _.noop); - vis.render(data, mockUiState); - }); - - afterEach(function () { - vis.destroy(); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - }); - - describe('stackData method', function () { - let stackedData; - let isStacked; - - beforeEach(function () { - vis.handler.charts.forEach(function (chart) { - stackedData = chart.chartData; - - isStacked = stackedData.series.every(function (arr) { - return arr.values.every(function (d) { - return _.isNumber(d.y0); - }); - }); - }); - }); - - test('should stack values when mode is stacked', function () { - if (mode === 'stacked') { - expect(isStacked).toBe(true); - } - }); - - test('should stack values when mode is percentage', function () { - if (mode === 'percentage') { - expect(isStacked).toBe(true); - } - }); - }); - - describe('addBars method', function () { - test('should append rects', function () { - let numOfSeries; - let numOfValues; - let product; - - vis.handler.charts.forEach(function (chart) { - numOfSeries = chart.chartData.series.length; - numOfValues = chart.chartData.series[0].values.length; - product = numOfSeries * numOfValues; - expect($(chart.chartEl).find('.series rect')).toHaveLength(product); - }); - }); - }); - - describe('addBarEvents method', function () { - function checkChart(chart) { - const rect = $(chart.chartEl).find('.series rect').get(0); - - // check for existence of stuff and things - return { - click: !!rect.__onclick, - mouseOver: !!rect.__onmouseover, - // D3 brushing requires that a g element is appended that - // listens for mousedown events. This g element includes - // listeners, however, I was not able to test for the listener - // function being present. I will need to update this test - // in the future. - brush: !!d3.select('.brush')[0][0], - }; - } - - test('should attach the brush if data is a set is ordered', function () { - vis.handler.charts.forEach(function (chart) { - const has = checkChart(chart); - const ordered = vis.handler.data.get('ordered'); - const allowBrushing = Boolean(ordered); - expect(has.brush).toBe(allowBrushing); - }); - }); - - test('should attach a click event', function () { - vis.handler.charts.forEach(function (chart) { - const has = checkChart(chart); - expect(has.click).toBe(true); - }); - }); - - test('should attach a hover event', function () { - vis.handler.charts.forEach(function (chart) { - const has = checkChart(chart); - expect(has.mouseOver).toBe(true); - }); - }); - }); - - describe('draw method', function () { - test('should return a function', function () { - vis.handler.charts.forEach(function (chart) { - expect(_.isFunction(chart.draw())).toBe(true); - }); - }); - - test('should return a yMin and yMax', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const domain = yAxis.getScale().domain(); - - expect(domain[0]).not.toBe(undefined); - expect(domain[1]).not.toBe(undefined); - }); - }); - - test('should render a zero axis line', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - - if (yAxis.yMin < 0 && yAxis.yMax > 0) { - expect($(chart.chartEl).find('line.zero-line').length).toBe(1); - } - }); - }); - }); - - describe('defaultYExtents is true', function () { - beforeEach(function () { - vis.visConfigArgs.defaultYExtents = true; - vis.render(data, mockUiState); - }); - - test('should return yAxis extents equal to data extents', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const min = vis.handler.valueAxes[0].axisScale.getYMin(); - const max = vis.handler.valueAxes[0].axisScale.getYMax(); - const domain = yAxis.getScale().domain(); - expect(domain[0]).toEqual(min); - expect(domain[1]).toEqual(max); - }); - }); - }); - [0, 2, 4, 8].forEach(function (boundsMarginValue) { - describe('defaultYExtents is true and boundsMargin is defined', function () { - beforeEach(function () { - vis.visConfigArgs.defaultYExtents = true; - vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(data, mockUiState); - }); - - test('should return yAxis extents equal to data extents with boundsMargin', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const min = vis.handler.valueAxes[0].axisScale.getYMin(); - const max = vis.handler.valueAxes[0].axisScale.getYMax(); - const domain = yAxis.getScale().domain(); - if (min < 0 && max < 0) { - expect(domain[0]).toEqual(min); - expect(domain[1] - boundsMarginValue).toEqual(max); - } else if (min > 0 && max > 0) { - expect(domain[0] + boundsMarginValue).toEqual(min); - expect(domain[1]).toEqual(max); - } else { - expect(domain[0]).toEqual(min); - expect(domain[1]).toEqual(max); - } - }); - }); - }); - }); - }); -}); - -describe('stackData method - data set with zeros in percentage mode', function () { - let vis; - let mockUiState; - const vislibParams = { - type: 'histogram', - addLegend: true, - addTooltip: true, - mode: 'percentage', - zeroFill: true, - }; - - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - }); - - beforeEach(() => { - vis = getVis(vislibParams); - mockUiState = getMockUiState(); - vis.on('brush', _.noop); - }); - - afterEach(function () { - vis.destroy(); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - }); - - test('should not mutate the injected zeros', function () { - vis.render(seriesMonthlyInterval, mockUiState); - - expect(vis.handler.charts).toHaveLength(1); - const chart = vis.handler.charts[0]; - expect(chart.chartData.series).toHaveLength(1); - const series = chart.chartData.series[0].values; - // with the interval set in seriesMonthlyInterval data, the point at x=1454309600000 does not exist - const point = _.find(series, ['x', 1454309600000]); - expect(point).not.toBe(undefined); - expect(point.y).toBe(0); - }); - - test('should not mutate zeros that exist in the data', function () { - vis.render(rowsWithZeros, mockUiState); - - expect(vis.handler.charts).toHaveLength(2); - const chart = vis.handler.charts[0]; - expect(chart.chartData.series).toHaveLength(5); - const series = chart.chartData.series[0].values; - const point = _.find(series, ['x', 1415826240000]); - expect(point).not.toBe(undefined); - expect(point.y).toBe(0); - }); -}); - -describe('datumWidth - split chart data set with holes', function () { - let vis; - let mockUiState; - const vislibParams = { - type: 'histogram', - addLegend: true, - addTooltip: true, - mode: 'stacked', - zeroFill: true, - }; - - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - }); - - beforeEach(() => { - vis = getVis(vislibParams); - mockUiState = getMockUiState(); - vis.on('brush', _.noop); - vis.render(rowsSeriesWithHoles, mockUiState); - }); - - afterEach(function () { - vis.destroy(); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - }); - - test('should not have bar widths that span multiple time bins', function () { - expect(vis.handler.charts.length).toEqual(1); - const chart = vis.handler.charts[0]; - const rects = $(chart.chartEl).find('.series rect'); - const MAX_WIDTH_IN_PIXELS = 27; - rects.each(function () { - const width = parseInt($(this).attr('width'), 10); - expect(width).toBeLessThan(MAX_WIDTH_IN_PIXELS); - }); - }); -}); - -describe('datumWidth - monthly interval', function () { - let vis; - let mockUiState; - const vislibParams = { - type: 'histogram', - addLegend: true, - addTooltip: true, - mode: 'stacked', - zeroFill: true, - }; - - let mockWidth; - - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - mockWidth = jest.spyOn($.prototype, 'width').mockReturnValue(900); - }); - - beforeEach(() => { - vis = getVis(vislibParams); - mockUiState = getMockUiState(); - vis.on('brush', _.noop); - vis.render(seriesMonthlyInterval, mockUiState); - }); - - afterEach(function () { - vis.destroy(); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - mockWidth.mockRestore(); - }); - - test('should vary bar width when date histogram intervals are not equal', function () { - expect(vis.handler.charts.length).toEqual(1); - const chart = vis.handler.charts[0]; - const rects = $(chart.chartEl).find('.series rect'); - const januaryBarWidth = parseInt($(rects.get(0)).attr('width'), 10); - const februaryBarWidth = parseInt($(rects.get(1)).attr('width'), 10); - expect(februaryBarWidth).toBeLessThan(januaryBarWidth); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.js deleted file mode 100644 index 4476574c940bc..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.js +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import d3 from 'd3'; -import _ from 'lodash'; -import { PointSeries } from './_point_series'; - -const defaults = { - mode: 'normal', - showCircles: true, - radiusRatio: 9, - showLines: true, - interpolate: 'linear', - lineWidth: 2, - color: undefined, - fillColor: undefined, -}; -/** - * Line Chart Visualization - * - * @class LineChart - * @constructor - * @extends Chart - * @param handler {Object} Reference to the Handler Class Constructor - * @param el {HTMLElement} HTML element to which the chart will be appended - * @param chartData {Object} Elasticsearch query results for this specific chart - */ -export class LineChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs, uiSettings) { - super(handler, chartEl, chartData, seriesConfigArgs, uiSettings); - this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); - } - - addCircles(svg, data) { - const self = this; - const showCircles = this.seriesConfig.showCircles; - const color = this.handler.data.getColorFunc(); - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const ordered = this.handler.data.get('ordered'); - const tooltip = this.baseChart.tooltip; - const isTooltip = this.handler.visConfig.get('tooltip.show'); - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - const lineWidth = this.seriesConfig.lineWidth; - - const radii = this.baseChart.radii; - - const radiusStep = - (radii.max - radii.min || radii.max * 100) / Math.pow(this.seriesConfig.radiusRatio, 2); - - const layer = svg - .append('g') - .attr('class', 'points line') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - const circles = layer.selectAll('circle').data(function appendData() { - return data.values.filter(function (d) { - return !_.isNull(d.y) && (d.y || !d.y0); - }); - }); - - circles.exit().remove(); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } - - function cy(d) { - const y0 = d.y0 || 0; - const y = d.y || 0; - return yScale(y0 + y); - } - - function cColor() { - return color(data.label); - } - - function colorCircle() { - const parent = d3.select(this).node().parentNode; - const lengthOfParent = d3.select(parent).data()[0].length; - const isVisible = lengthOfParent === 1; - - // If only 1 point exists, show circle - if (!showCircles && !isVisible) return 'none'; - return cColor(); - } - - function getCircleRadiusFn(modifier) { - return function getCircleRadius(d) { - const width = self.baseChart.chartConfig.width; - const height = self.baseChart.chartConfig.height; - const circleRadius = (d.z - radii.min) / radiusStep; - const baseMagicNumber = 2; - - const base = circleRadius - ? Math.sqrt(circleRadius + baseMagicNumber) + lineWidth - : lineWidth; - return _.min([base, width, height]) + (modifier || 0); - }; - } - - circles - .enter() - .append('circle') - .attr('r', getCircleRadiusFn()) - .attr('fill-opacity', this.seriesConfig.drawLinesBetweenPoints ? 1 : 0.7) - .attr('cx', isHorizontal ? cx : cy) - .attr('cy', isHorizontal ? cy : cx) - .attr('class', 'circle-decoration') - .attr('data-label', data.label) - .attr('fill', colorCircle); - - circles - .enter() - .append('circle') - .attr('r', getCircleRadiusFn(10)) - .attr('cx', isHorizontal ? cx : cy) - .attr('cy', isHorizontal ? cy : cx) - .attr('fill', 'transparent') - .attr('class', 'circle') - .attr('data-label', data.label) - .attr('stroke', cColor) - .attr('stroke-width', 0); - - if (isTooltip) { - circles.call(tooltip.render()); - } - - return circles; - } - - /** - * Adds path to SVG - * - * @method addLines - * @param svg {HTMLElement} SVG to which path are appended - * @param data {Array} Array of object data points - * @returns {D3.UpdateSelection} SVG with paths added - */ - addLine(svg, data) { - const xScale = this.getCategoryAxis().getScale(); - const yScale = this.getValueAxis().getScale(); - const color = this.handler.data.getColorFunc(); - const ordered = this.handler.data.get('ordered'); - const lineWidth = this.seriesConfig.lineWidth; - const interpolate = this.seriesConfig.interpolate; - const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); - - const line = svg - .append('g') - .attr('class', 'pathgroup lines') - .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } - - function cy(d) { - const y = d.y || 0; - const y0 = d.y0 || 0; - return yScale(y0 + y); - } - - line - .append('path') - .attr('data-label', data.label) - .attr('d', () => { - const d3Line = d3.svg - .line() - .defined(function (d) { - return !_.isNull(d.y); - }) - .interpolate(interpolate) - .x(isHorizontal ? cx : cy) - .y(isHorizontal ? cy : cx); - return d3Line(data.values); - }) - .attr('fill', 'none') - .attr('stroke', () => { - return color(data.label); - }) - .attr('stroke-width', lineWidth); - - return line; - } - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the line chart - */ - draw() { - const self = this; - - return function (selection) { - selection.each(function () { - const svg = self.chartEl.append('g'); - svg.data([self.chartData]); - - if (self.seriesConfig.drawLinesBetweenPoints) { - self.addLine(svg, self.chartData); - } - const circles = self.addCircles(svg, self.chartData); - self.addCircleEvents(circles); - - if (self.thresholdLineOptions.show) { - self.addThresholdLine(self.chartEl); - } - - self.events.emit('rendered', { - chart: self.chartData, - }); - - return svg; - }); - }; - } -} diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.test.js deleted file mode 100644 index f9843f1bc83a9..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/line_chart.test.js +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import d3 from 'd3'; -import $ from 'jquery'; -import _ from 'lodash'; -import { - setHTMLElementClientSizes, - setSVGElementGetBBox, - setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; - -// Data -import seriesPos from '../../../fixtures/mock_data/date_histogram/_series'; -import seriesPosNeg from '../../../fixtures/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from '../../../fixtures/mock_data/date_histogram/_series_neg'; -import histogramColumns from '../../../fixtures/mock_data/histogram/_columns'; -import rangeRows from '../../../fixtures/mock_data/range/_rows'; -import termSeries from '../../../fixtures/mock_data/terms/_series'; -import { getMockUiState } from '../../../fixtures/mocks'; -import { getVis } from '../_vis_fixture'; - -const dataTypes = [ - ['series pos', seriesPos], - ['series pos neg', seriesPosNeg], - ['series neg', seriesNeg], - ['histogram columns', histogramColumns], - ['range rows', rangeRows], - ['term series', termSeries], -]; - -let mockedHTMLElementClientSizes; -let mockedSVGElementGetBBox; -let mockedSVGElementGetComputedTextLength; - -describe('Vislib Line Chart', function () { - beforeAll(() => { - mockedHTMLElementClientSizes = setHTMLElementClientSizes(512, 512); - mockedSVGElementGetBBox = setSVGElementGetBBox(100); - mockedSVGElementGetComputedTextLength = setSVGElementGetComputedTextLength(100); - }); - - afterAll(() => { - mockedHTMLElementClientSizes.mockRestore(); - mockedSVGElementGetBBox.mockRestore(); - mockedSVGElementGetComputedTextLength.mockRestore(); - }); - - dataTypes.forEach(function (type) { - const name = type[0]; - const data = type[1]; - - describe(name + ' Data', function () { - let vis; - let mockUiState; - - beforeEach(() => { - const vislibParams = { - type: 'line', - addLegend: true, - addTooltip: true, - drawLinesBetweenPoints: true, - }; - - vis = getVis(vislibParams); - mockUiState = getMockUiState(); - vis.render(data, mockUiState); - vis.on('brush', _.noop); - }); - - afterEach(function () { - vis.destroy(); - }); - - describe('addCircleEvents method', function () { - let circle; - let brush; - let d3selectedCircle; - let onBrush; - let onClick; - let onMouseOver; - - beforeEach(function () { - vis.handler.charts.forEach(function (chart) { - circle = $(chart.chartEl).find('.circle')[0]; - brush = $(chart.chartEl).find('.brush'); - d3selectedCircle = d3.select(circle)[0][0]; - - // d3 instance of click and hover - onBrush = !!brush; - onClick = !!d3selectedCircle.__onclick; - onMouseOver = !!d3selectedCircle.__onmouseover; - }); - }); - - // D3 brushing requires that a g element is appended that - // listens for mousedown events. This g element includes - // listeners, however, I was not able to test for the listener - // function being present. I will need to update this test - // in the future. - test('should attach a brush g element', function () { - vis.handler.charts.forEach(function () { - expect(onBrush).toBe(true); - }); - }); - - test('should attach a click event', function () { - vis.handler.charts.forEach(function () { - expect(onClick).toBe(true); - }); - }); - - test('should attach a hover event', function () { - vis.handler.charts.forEach(function () { - expect(onMouseOver).toBe(true); - }); - }); - }); - - describe('addCircles method', function () { - test('should append circles', function () { - vis.handler.charts.forEach(function (chart) { - expect($(chart.chartEl).find('circle').length).toBeGreaterThan(0); - }); - }); - }); - - describe('addLines method', function () { - test('should append a paths', function () { - vis.handler.charts.forEach(function (chart) { - expect($(chart.chartEl).find('path').length).toBeGreaterThan(0); - }); - }); - }); - - // Cannot seem to get these tests to work on the box - // They however pass in the browsers - //describe('addClipPath method', function () { - // test('should append a clipPath', function () { - // vis.handler.charts.forEach(function (chart) { - // expect($(chart.chartEl).find('clipPath').length).to.be(1); - // }); - // }); - //}); - - describe('draw method', function () { - test('should return a function', function () { - vis.handler.charts.forEach(function (chart) { - expect(chart.draw()).toBeInstanceOf(Function); - }); - }); - - test('should return a yMin and yMax', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const domain = yAxis.getScale().domain(); - expect(domain[0]).not.toBe(undefined); - expect(domain[1]).not.toBe(undefined); - }); - }); - - test('should render a zero axis line', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - - if (yAxis.yMin < 0 && yAxis.yMax > 0) { - expect($(chart.chartEl).find('line.zero-line').length).toBe(1); - } - }); - }); - }); - - describe('defaultYExtents is true', function () { - beforeEach(function () { - vis.visConfigArgs.defaultYExtents = true; - vis.render(data, mockUiState); - }); - - test('should return yAxis extents equal to data extents', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const min = vis.handler.valueAxes[0].axisScale.getYMin(); - const max = vis.handler.valueAxes[0].axisScale.getYMax(); - const domain = yAxis.getScale().domain(); - expect(domain[0]).toEqual(min); - expect(domain[1]).toEqual(max); - }); - }); - }); - [0, 2, 4, 8].forEach(function (boundsMarginValue) { - describe('defaultYExtents is true and boundsMargin is defined', function () { - beforeEach(function () { - vis.visConfigArgs.defaultYExtents = true; - vis.visConfigArgs.boundsMargin = boundsMarginValue; - vis.render(data, mockUiState); - }); - - test('should return yAxis extents equal to data extents with boundsMargin', function () { - vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.valueAxes[0]; - const min = vis.handler.valueAxes[0].axisScale.getYMin(); - const max = vis.handler.valueAxes[0].axisScale.getYMax(); - const domain = yAxis.getScale().domain(); - if (min < 0 && max < 0) { - expect(domain[0]).toEqual(min); - expect(domain[1] - boundsMarginValue).toEqual(max); - } else if (min > 0 && max > 0) { - expect(domain[0] + boundsMarginValue).toEqual(min); - expect(domain[1]).toEqual(max); - } else { - expect(domain[0]).toEqual(min); - expect(domain[1]).toEqual(max); - } - }); - }); - }); - }); - }); - }); -}); diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/series_types.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/series_types.js deleted file mode 100644 index 6a87f7e32758a..0000000000000 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/series_types.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ColumnChart } from './column_chart'; -import { LineChart } from './line_chart'; -import { AreaChart } from './area_chart'; -import { HeatmapChart } from './heatmap_chart'; - -export const seriesTypes = { - histogram: ColumnChart, - line: LineChart, - area: AreaChart, - heatmap: HeatmapChart, -}; diff --git a/src/plugins/vis_types/xy/common/index.ts b/src/plugins/vis_types/xy/common/index.ts index a80946f7c62fa..f17bc8476d9a6 100644 --- a/src/plugins/vis_types/xy/common/index.ts +++ b/src/plugins/vis_types/xy/common/index.ts @@ -19,5 +19,3 @@ export enum ChartType { * Type of xy visualizations */ export type XyVisType = ChartType | 'horizontal_bar'; - -export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary'; diff --git a/src/plugins/vis_types/xy/kibana.json b/src/plugins/vis_types/xy/kibana.json index 1606af5944ad3..1666a346e3482 100644 --- a/src/plugins/vis_types/xy/kibana.json +++ b/src/plugins/vis_types/xy/kibana.json @@ -2,7 +2,7 @@ "id": "visTypeXy", "version": "kibana", "ui": true, - "server": true, + "server": false, "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection"], "requiredBundles": ["kibanaUtils", "visDefaultEditor"], "extraPublicDirs": ["common/index"], diff --git a/src/plugins/vis_types/xy/public/editor/common_config.tsx b/src/plugins/vis_types/xy/public/editor/common_config.tsx index bd9882a15c124..6c071969f0cd8 100644 --- a/src/plugins/vis_types/xy/public/editor/common_config.tsx +++ b/src/plugins/vis_types/xy/public/editor/common_config.tsx @@ -15,37 +15,23 @@ import type { VisParams } from '../types'; import { MetricsAxisOptions, PointSeriesOptions } from './components/options'; import { ValidationWrapper } from './components/common/validation_wrapper'; -export function getOptionTabs(showElasticChartsOptions = false) { - return [ - { - name: 'advanced', - title: i18n.translate('visTypeXy.area.tabs.metricsAxesTitle', { - defaultMessage: 'Metrics & axes', - }), - editor: (props: VisEditorOptionsProps) => ( - - ), - }, - { - name: 'options', - title: i18n.translate('visTypeXy.area.tabs.panelSettingsTitle', { - defaultMessage: 'Panel settings', - }), - editor: (props: VisEditorOptionsProps) => ( - - ), - }, - ]; -} +export const optionTabs = [ + { + name: 'advanced', + title: i18n.translate('visTypeXy.area.tabs.metricsAxesTitle', { + defaultMessage: 'Metrics & axes', + }), + editor: (props: VisEditorOptionsProps) => ( + + ), + }, + { + name: 'options', + title: i18n.translate('visTypeXy.area.tabs.panelSettingsTitle', { + defaultMessage: 'Panel settings', + }), + editor: (props: VisEditorOptionsProps) => ( + + ), + }, +]; diff --git a/src/plugins/vis_types/xy/public/editor/components/common/validation_wrapper.tsx b/src/plugins/vis_types/xy/public/editor/components/common/validation_wrapper.tsx index 2088878f963ae..4d50dcd20228f 100644 --- a/src/plugins/vis_types/xy/public/editor/components/common/validation_wrapper.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/common/validation_wrapper.tsx @@ -10,24 +10,22 @@ import React, { useEffect, useState, useCallback } from 'react'; import { VisEditorOptionsProps } from '../../../../../../visualizations/public'; -export interface ValidationVisOptionsProps extends VisEditorOptionsProps { +export interface ValidationVisOptionsProps extends VisEditorOptionsProps { setMultipleValidity(paramName: string, isValid: boolean): void; - extraProps?: E; } -interface ValidationWrapperProps extends VisEditorOptionsProps { - component: React.ComponentType>; - extraProps?: E; +interface ValidationWrapperProps extends VisEditorOptionsProps { + component: React.ComponentType>; } interface Item { isValid: boolean; } -function ValidationWrapper({ +function ValidationWrapper({ component: Component, ...rest -}: ValidationWrapperProps) { +}: ValidationWrapperProps) { const [panelState, setPanelState] = useState({} as { [key: string]: Item }); const isPanelValid = Object.values(panelState).every((item) => item.isValid); const { setValidity } = rest; diff --git a/src/plugins/vis_types/xy/public/editor/components/options/index.tsx b/src/plugins/vis_types/xy/public/editor/components/options/index.tsx index a3e20dd22dd5a..4e7d0e6412cb2 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/index.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/index.tsx @@ -14,20 +14,10 @@ import { ValidationVisOptionsProps } from '../common'; const PointSeriesOptionsLazy = lazy(() => import('./point_series')); const MetricsAxisOptionsLazy = lazy(() => import('./metrics_axes')); -export const PointSeriesOptions = ( - props: ValidationVisOptionsProps< - VisParams, - { - showElasticChartsOptions: boolean; - } - > -) => ; +export const PointSeriesOptions = (props: ValidationVisOptionsProps) => ( + +); -export const MetricsAxisOptions = ( - props: ValidationVisOptionsProps< - VisParams, - { - showElasticChartsOptions: boolean; - } - > -) => ; +export const MetricsAxisOptions = (props: ValidationVisOptionsProps) => ( + +); diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap index fa049199a55b6..05e2532073eaf 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap +++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/__snapshots__/index.test.tsx.snap @@ -75,7 +75,6 @@ exports[`MetricsAxisOptions component should init with the default set of props /> ({ describe('MetricsAxisOptions component', () => { let setValue: jest.Mock; - let defaultProps: ValidationVisOptionsProps< - VisParams, - { - showElasticChartsOptions: boolean; - } - >; + let defaultProps: ValidationVisOptionsProps; let axis: ValueAxis; let axisRight: ValueAxis; let chart: SeriesParam; @@ -86,9 +81,6 @@ describe('MetricsAxisOptions component', () => { defaultProps = { aggs: createAggs([aggCount]), isTabSelected: true, - extraProps: { - showElasticChartsOptions: false, - }, vis: { type: { type: ChartType.Area, @@ -244,12 +236,7 @@ describe('MetricsAxisOptions component', () => { const getProps = ( valuePosition1: Position = Position.Right, valuePosition2: Position = Position.Left - ): ValidationVisOptionsProps< - VisParams, - { - showElasticChartsOptions: boolean; - } - > => ({ + ): ValidationVisOptionsProps => ({ ...defaultProps, stateParams: { ...defaultProps.stateParams, @@ -387,12 +374,7 @@ describe('MetricsAxisOptions component', () => { describe('onCategoryAxisPositionChanged', () => { const getProps = ( position: Position = Position.Bottom - ): ValidationVisOptionsProps< - VisParams, - { - showElasticChartsOptions: boolean; - } - > => ({ + ): ValidationVisOptionsProps => ({ ...defaultProps, stateParams: { ...defaultProps.stateParams, diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx index 9b4e1c61a201f..c3eb659435b2d 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/index.tsx @@ -43,17 +43,8 @@ export type ChangeValueAxis = ( const VALUE_AXIS_PREFIX = 'ValueAxis-'; -function MetricsAxisOptions( - props: ValidationVisOptionsProps< - VisParams, - { - // TODO: Remove when vis_type_vislib is removed - // https://github.com/elastic/kibana/issues/56143 - showElasticChartsOptions: boolean; - } - > -) { - const { stateParams, setValue, aggs, vis, isTabSelected, extraProps } = props; +function MetricsAxisOptions(props: ValidationVisOptionsProps) { + const { stateParams, setValue, aggs, vis, isTabSelected } = props; const setParamByIndex: SetParamByIndex = useCallback( (axesName, index, paramName, value) => { @@ -335,7 +326,6 @@ function MetricsAxisOptions( setMultipleValidity={props.setMultipleValidity} seriesParams={stateParams.seriesParams} valueAxes={stateParams.valueAxes} - isNewLibrary={extraProps?.showElasticChartsOptions} /> void; - isNewLibrary?: boolean; } function ValueAxesPanel(props: ValueAxesPanelProps) { @@ -150,7 +149,6 @@ function ValueAxesPanel(props: ValueAxesPanelProps) { onValueAxisPositionChanged={props.onValueAxisPositionChanged} setParamByIndex={props.setParamByIndex} setMultipleValidity={props.setMultipleValidity} - isNewLibrary={props.isNewLibrary ?? false} /> diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axis_options.tsx b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axis_options.tsx index 751c61f3b1531..aa20eb84222bd 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axis_options.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axis_options.tsx @@ -36,7 +36,6 @@ export interface ValueAxisOptionsParams { setParamByIndex: SetParamByIndex; valueAxis: ValueAxis; setMultipleValidity: (paramName: string, isValid: boolean) => void; - isNewLibrary?: boolean; } export function ValueAxisOptions({ @@ -46,7 +45,6 @@ export function ValueAxisOptions({ onValueAxisPositionChanged, setParamByIndex, setMultipleValidity, - isNewLibrary = false, }: ValueAxisOptionsParams) { const setValueAxis = useCallback( (paramName: T, value: ValueAxis[T]) => @@ -193,7 +191,7 @@ export function ValueAxisOptions({ setMultipleValidity={setMultipleValidity} setValueAxisScale={setValueAxisScale} setValueAxis={setValueAxis} - disableAxisExtents={isNewLibrary && axis.scale.mode === 'percentage'} + disableAxisExtents={axis.scale.mode === 'percentage'} /> diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/grid_panel.tsx b/src/plugins/vis_types/xy/public/editor/components/options/point_series/grid_panel.tsx index 0bf5344ac7f26..c536d2866b8da 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/grid_panel.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/grid_panel.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useMemo, useEffect, useCallback } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -16,25 +16,15 @@ import { SelectOption, SwitchOption } from '../../../../../../../vis_default_edi import { VisParams, ValueAxis } from '../../../../types'; import { ValidationVisOptionsProps } from '../../common'; -type GridPanelOptions = ValidationVisOptionsProps< - VisParams, - { - showElasticChartsOptions: boolean; - } ->; +type GridPanelOptions = ValidationVisOptionsProps; -function GridPanel({ stateParams, setValue, hasHistogramAgg, extraProps }: GridPanelOptions) { +function GridPanel({ stateParams, setValue }: GridPanelOptions) { const setGrid = useCallback( (paramName: T, value: VisParams['grid'][T]) => setValue('grid', { ...stateParams.grid, [paramName]: value }), [stateParams.grid, setValue] ); - const disableCategoryGridLines = useMemo( - () => !extraProps?.showElasticChartsOptions && hasHistogramAgg, - [extraProps?.showElasticChartsOptions, hasHistogramAgg] - ); - const options = useMemo( () => [ ...stateParams.valueAxes.map(({ id, name }: ValueAxis) => ({ @@ -51,12 +41,6 @@ function GridPanel({ stateParams, setValue, hasHistogramAgg, extraProps }: GridP [stateParams.valueAxes] ); - useEffect(() => { - if (disableCategoryGridLines) { - setGrid('categoryLines', false); - } - }, [disableCategoryGridLines, setGrid]); - return ( @@ -71,18 +55,10 @@ function GridPanel({ stateParams, setValue, hasHistogramAgg, extraProps }: GridP { + it('renders the detailedTooltip option', async () => { component = mountWithIntl(); - await act(async () => { - expect(findTestSubject(component, 'detailedTooltip').length).toBe(0); - }); - }); - - it('renders the editor options that are specific for the es charts implementation if showElasticChartsOptions is true', async () => { - const newVisProps = ({ - ...props, - extraProps: { - showElasticChartsOptions: true, - }, - } as unknown) as PointSeriesOptionsProps; - component = mountWithIntl(); await act(async () => { expect(findTestSubject(component, 'detailedTooltip').length).toBe(1); }); }); - it('not renders the long legend options if showElasticChartsOptions is false', async () => { + it('renders the long legend options', async () => { component = mountWithIntl(); - await act(async () => { - expect(findTestSubject(component, 'xyLongLegendsOptions').length).toBe(0); - }); - }); - - it('renders the long legend options if showElasticChartsOptions is true', async () => { - const newVisProps = ({ - ...props, - extraProps: { - showElasticChartsOptions: true, - }, - } as unknown) as PointSeriesOptionsProps; - component = mountWithIntl(); await act(async () => { expect(findTestSubject(component, 'xyLongLegendsOptions').length).toBe(1); }); }); it('not renders the fitting function for a bar chart', async () => { - const newVisProps = ({ - ...props, - extraProps: { - showElasticChartsOptions: true, - }, - } as unknown) as PointSeriesOptionsProps; - component = mountWithIntl(); + component = mountWithIntl(); await act(async () => { expect(findTestSubject(component, 'fittingFunction').length).toBe(0); }); @@ -142,9 +107,6 @@ describe('PointSeries Editor', function () { const newVisProps = ({ ...props, stateParams: getStateParams(ChartType.Line, false), - extraProps: { - showElasticChartsOptions: true, - }, } as unknown) as PointSeriesOptionsProps; component = mountWithIntl(); await act(async () => { @@ -153,13 +115,7 @@ describe('PointSeries Editor', function () { }); it('renders the showCategoryLines switch', async () => { - const newVisProps = ({ - ...props, - extraProps: { - showElasticChartsOptions: true, - }, - } as unknown) as PointSeriesOptionsProps; - component = mountWithIntl(); + component = mountWithIntl(); await act(async () => { expect(findTestSubject(component, 'showValuesOnChart').length).toBe(1); }); diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.tsx b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.tsx index da7bdfb0d7986..62dbd94c516d7 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.tsx @@ -28,16 +28,7 @@ import { getPositions } from '../../../collections'; const legendPositions = getPositions(); -export function PointSeriesOptions( - props: ValidationVisOptionsProps< - VisParams, - { - // TODO: Remove when vis_type_vislib is removed - // https://github.com/elastic/kibana/issues/56143 - showElasticChartsOptions: boolean; - } - > -) { +export function PointSeriesOptions(props: ValidationVisOptionsProps) { const { stateParams, setValue, vis, aggs } = props; const hasBarChart = useMemo( () => @@ -62,14 +53,12 @@ export function PointSeriesOptions( - {props.extraProps?.showElasticChartsOptions && ( - - )} + {vis.data.aggs!.aggs.some( (agg) => agg.schema === 'segment' && agg.type.name === BUCKET_TYPES.DATE_HISTOGRAM @@ -109,7 +98,7 @@ export function PointSeriesOptions( /> )} - {props.extraProps?.showElasticChartsOptions && } + diff --git a/src/plugins/vis_types/xy/public/index.ts b/src/plugins/vis_types/xy/public/index.ts index 0953183fa1093..1ee96fab35253 100644 --- a/src/plugins/vis_types/xy/public/index.ts +++ b/src/plugins/vis_types/xy/public/index.ts @@ -30,7 +30,6 @@ export type { ValidationVisOptionsProps } from './editor/components/common/valid export { TruncateLabelsOption } from './editor/components/common/truncate_labels'; export { getPositions } from './editor/positions'; export { getScaleTypes } from './editor/scale_types'; -export { xyVisTypes } from './vis_types'; export { getAggId } from './config/get_agg_id'; // Export common types diff --git a/src/plugins/vis_types/xy/public/plugin.ts b/src/plugins/vis_types/xy/public/plugin.ts index 57736444f49fe..600e78b5b3949 100644 --- a/src/plugins/vis_types/xy/public/plugin.ts +++ b/src/plugins/vis_types/xy/public/plugin.ts @@ -24,7 +24,6 @@ import { } from './services'; import { visTypesDefinitions } from './vis_types'; -import { LEGACY_CHARTS_LIBRARY } from '../common/'; import { xyVisRenderer } from './vis_renderer'; import * as expressionFunctions from './expression_functions'; @@ -65,23 +64,21 @@ export class VisTypeXyPlugin core: VisTypeXyCoreSetup, { expressions, visualizations, charts, usageCollection }: VisTypeXyPluginSetupDependencies ) { - if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, false)) { - setUISettings(core.uiSettings); - setThemeService(charts.theme); - setPalettesService(charts.palettes); + setUISettings(core.uiSettings); + setThemeService(charts.theme); + setPalettesService(charts.palettes); - expressions.registerRenderer(xyVisRenderer); - expressions.registerFunction(expressionFunctions.visTypeXyVisFn); - expressions.registerFunction(expressionFunctions.categoryAxis); - expressions.registerFunction(expressionFunctions.timeMarker); - expressions.registerFunction(expressionFunctions.valueAxis); - expressions.registerFunction(expressionFunctions.seriesParam); - expressions.registerFunction(expressionFunctions.thresholdLine); - expressions.registerFunction(expressionFunctions.label); - expressions.registerFunction(expressionFunctions.visScale); + expressions.registerRenderer(xyVisRenderer); + expressions.registerFunction(expressionFunctions.visTypeXyVisFn); + expressions.registerFunction(expressionFunctions.categoryAxis); + expressions.registerFunction(expressionFunctions.timeMarker); + expressions.registerFunction(expressionFunctions.valueAxis); + expressions.registerFunction(expressionFunctions.seriesParam); + expressions.registerFunction(expressionFunctions.thresholdLine); + expressions.registerFunction(expressionFunctions.label); + expressions.registerFunction(expressionFunctions.visScale); - visTypesDefinitions.forEach(visualizations.createBaseVisualization); - } + visTypesDefinitions.forEach(visualizations.createBaseVisualization); setTrackUiMetric(usageCollection?.reportUiCounter.bind(usageCollection, 'vis_type_xy')); diff --git a/src/plugins/vis_types/xy/public/utils/accessors.tsx b/src/plugins/vis_types/xy/public/utils/accessors.tsx index 0356e921a9d5c..748430e3b16a6 100644 --- a/src/plugins/vis_types/xy/public/utils/accessors.tsx +++ b/src/plugins/vis_types/xy/public/utils/accessors.tsx @@ -13,6 +13,7 @@ import { Aspect } from '../types'; export const COMPLEX_X_ACCESSOR = '__customXAccessor__'; export const COMPLEX_SPLIT_ACCESSOR = '__complexSplitAccessor__'; +const SHARD_DELAY = 'shard_delay'; export const getXAccessor = (aspect: Aspect): Accessor | AccessorFn => { return ( @@ -39,7 +40,7 @@ export const getComplexAccessor = (fieldName: string, isComplex: boolean = false aspect: Aspect, index?: number ): Accessor | AccessorFn | undefined => { - if (!aspect.accessor) { + if (!aspect.accessor || aspect.aggType === SHARD_DELAY) { return; } diff --git a/src/plugins/vis_types/xy/public/vis_types/area.ts b/src/plugins/vis_types/xy/public/vis_types/area.ts index b377fd54753da..6ba197ceb9424 100644 --- a/src/plugins/vis_types/xy/public/vis_types/area.ts +++ b/src/plugins/vis_types/xy/public/vis_types/area.ts @@ -22,15 +22,12 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, - XyVisTypeDefinition, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; -import { getOptionTabs } from '../editor/common_config'; +import { optionTabs } from '../editor/common_config'; -export const getAreaVisTypeDefinition = ( - showElasticChartsOptions = false -): XyVisTypeDefinition => ({ +export const areaVisTypeDefinition = { name: 'area', title: i18n.translate('visTypeXy.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', @@ -128,7 +125,7 @@ export const getAreaVisTypeDefinition = ( }, }, editorConfig: { - optionTabs: getOptionTabs(showElasticChartsOptions), + optionTabs, schemas: [ { group: AggGroupNames.Metrics, @@ -183,4 +180,4 @@ export const getAreaVisTypeDefinition = ( ], }, requiresSearch: true, -}); +}; diff --git a/src/plugins/vis_types/xy/public/vis_types/histogram.ts b/src/plugins/vis_types/xy/public/vis_types/histogram.ts index 2d22b7566175c..bd549615fe7fd 100644 --- a/src/plugins/vis_types/xy/public/vis_types/histogram.ts +++ b/src/plugins/vis_types/xy/public/vis_types/histogram.ts @@ -20,17 +20,14 @@ import { ScaleType, AxisMode, ThresholdLineStyle, - XyVisTypeDefinition, InterpolationMode, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; -import { getOptionTabs } from '../editor/common_config'; +import { optionTabs } from '../editor/common_config'; import { defaultCountLabel, LabelRotation } from '../../../../charts/public'; -export const getHistogramVisTypeDefinition = ( - showElasticChartsOptions = false -): XyVisTypeDefinition => ({ +export const histogramVisTypeDefinition = { name: 'histogram', title: i18n.translate('visTypeXy.histogram.histogramTitle', { defaultMessage: 'Vertical bar', @@ -131,7 +128,7 @@ export const getHistogramVisTypeDefinition = ( }, }, editorConfig: { - optionTabs: getOptionTabs(showElasticChartsOptions), + optionTabs, schemas: [ { group: AggGroupNames.Metrics, @@ -186,4 +183,4 @@ export const getHistogramVisTypeDefinition = ( ], }, requiresSearch: true, -}); +}; diff --git a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts index 8916f3f94f6ff..5bd45fc2eb7a8 100644 --- a/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts +++ b/src/plugins/vis_types/xy/public/vis_types/horizontal_bar.ts @@ -20,17 +20,14 @@ import { ScaleType, AxisMode, ThresholdLineStyle, - XyVisTypeDefinition, InterpolationMode, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; -import { getOptionTabs } from '../editor/common_config'; +import { optionTabs } from '../editor/common_config'; import { defaultCountLabel, LabelRotation } from '../../../../charts/public'; -export const getHorizontalBarVisTypeDefinition = ( - showElasticChartsOptions = false -): XyVisTypeDefinition => ({ +export const horizontalBarVisTypeDefinition = { name: 'horizontal_bar', title: i18n.translate('visTypeXy.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal bar', @@ -130,7 +127,7 @@ export const getHorizontalBarVisTypeDefinition = ( }, }, editorConfig: { - optionTabs: getOptionTabs(showElasticChartsOptions), + optionTabs, schemas: [ { group: AggGroupNames.Metrics, @@ -185,4 +182,4 @@ export const getHorizontalBarVisTypeDefinition = ( ], }, requiresSearch: true, -}); +}; diff --git a/src/plugins/vis_types/xy/public/vis_types/index.ts b/src/plugins/vis_types/xy/public/vis_types/index.ts index a8dae74eb110c..93c973b5316c9 100644 --- a/src/plugins/vis_types/xy/public/vis_types/index.ts +++ b/src/plugins/vis_types/xy/public/vis_types/index.ts @@ -6,27 +6,14 @@ * Side Public License, v 1. */ -import { getAreaVisTypeDefinition } from './area'; -import { getLineVisTypeDefinition } from './line'; -import { getHistogramVisTypeDefinition } from './histogram'; -import { getHorizontalBarVisTypeDefinition } from './horizontal_bar'; -import { XyVisTypeDefinition } from '../types'; +import { areaVisTypeDefinition } from './area'; +import { lineVisTypeDefinition } from './line'; +import { histogramVisTypeDefinition } from './histogram'; +import { horizontalBarVisTypeDefinition } from './horizontal_bar'; export const visTypesDefinitions = [ - getAreaVisTypeDefinition(true), - getLineVisTypeDefinition(true), - getHistogramVisTypeDefinition(true), - getHorizontalBarVisTypeDefinition(true), + areaVisTypeDefinition, + lineVisTypeDefinition, + histogramVisTypeDefinition, + horizontalBarVisTypeDefinition, ]; - -// TODO: Remove when vis_type_vislib is removed -// https://github.com/elastic/kibana/issues/56143 -export const xyVisTypes: Record< - string, - (showElasticChartsOptions?: boolean) => XyVisTypeDefinition -> = { - area: getAreaVisTypeDefinition, - line: getLineVisTypeDefinition, - histogram: getHistogramVisTypeDefinition, - horizontalBar: getHorizontalBarVisTypeDefinition, -}; diff --git a/src/plugins/vis_types/xy/public/vis_types/line.ts b/src/plugins/vis_types/xy/public/vis_types/line.ts index af75c38d627df..747de1679c7c5 100644 --- a/src/plugins/vis_types/xy/public/vis_types/line.ts +++ b/src/plugins/vis_types/xy/public/vis_types/line.ts @@ -22,15 +22,12 @@ import { AxisMode, ThresholdLineStyle, InterpolationMode, - XyVisTypeDefinition, } from '../types'; import { toExpressionAst } from '../to_ast'; import { ChartType } from '../../common'; -import { getOptionTabs } from '../editor/common_config'; +import { optionTabs } from '../editor/common_config'; -export const getLineVisTypeDefinition = ( - showElasticChartsOptions = false -): XyVisTypeDefinition => ({ +export const lineVisTypeDefinition = { name: 'line', title: i18n.translate('visTypeXy.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', @@ -128,7 +125,7 @@ export const getLineVisTypeDefinition = ( }, }, editorConfig: { - optionTabs: getOptionTabs(showElasticChartsOptions), + optionTabs, schemas: [ { group: AggGroupNames.Metrics, @@ -177,4 +174,4 @@ export const getLineVisTypeDefinition = ( ], }, requiresSearch: true, -}); +}; diff --git a/src/plugins/vis_types/xy/server/index.ts b/src/plugins/vis_types/xy/server/index.ts deleted file mode 100644 index a27ac49c0ea49..0000000000000 --- a/src/plugins/vis_types/xy/server/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import { VisTypeXyServerPlugin } from './plugin'; - -export const plugin = () => new VisTypeXyServerPlugin(); diff --git a/src/plugins/vis_types/xy/server/plugin.ts b/src/plugins/vis_types/xy/server/plugin.ts deleted file mode 100644 index 46d6531204c24..0000000000000 --- a/src/plugins/vis_types/xy/server/plugin.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { schema } from '@kbn/config-schema'; - -import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server'; - -import { LEGACY_CHARTS_LIBRARY } from '../common'; - -export const getUiSettingsConfig: () => Record> = () => ({ - // TODO: Remove this when vis_type_vislib is removed - // https://github.com/elastic/kibana/issues/56143 - [LEGACY_CHARTS_LIBRARY]: { - name: i18n.translate('visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name', { - defaultMessage: 'XY axis legacy charts library', - }), - requiresPageReload: true, - value: false, - description: i18n.translate( - 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description', - { - defaultMessage: 'Enables legacy charts library for area, line and bar charts in visualize.', - } - ), - deprecation: { - message: i18n.translate( - 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.deprecation', - { - defaultMessage: - 'The legacy charts library for area, line and bar charts in visualize is deprecated and will not be supported as of 7.16.', - } - ), - docLinksKey: 'visualizationSettings', - }, - category: ['visualization'], - schema: schema.boolean(), - }, -}); - -export class VisTypeXyServerPlugin implements Plugin { - public setup(core: CoreSetup) { - core.uiSettings.register(getUiSettingsConfig()); - - return {}; - } - - public start() { - return {}; - } -} diff --git a/src/plugins/visualize/public/application/components/deprecation_vis_warning.tsx b/src/plugins/visualize/public/application/components/deprecation_vis_warning.tsx deleted file mode 100644 index 6389f52996926..0000000000000 --- a/src/plugins/visualize/public/application/components/deprecation_vis_warning.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiCallOut, EuiLink } from '@elastic/eui'; -import { useKibana } from '../../../../kibana_react/public'; -import { VisualizeServices } from '../types'; - -export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary'; - -export const DeprecationWarning = () => { - const { services } = useKibana(); - const canEditAdvancedSettings = services.application.capabilities.advancedSettings.save; - const advancedSettingsLink = services.application.getUrlForApp('management', { - path: `/kibana/settings?query=${LEGACY_CHARTS_LIBRARY}`, - }); - - return ( - - {canEditAdvancedSettings && ( - - - - ), - }} - /> - )} - {!canEditAdvancedSettings && ( - - )} - - ), - }} - /> - } - iconType="alert" - color="warning" - size="s" - /> - ); -}; diff --git a/src/plugins/visualize/public/application/components/visualize_editor_common.tsx b/src/plugins/visualize/public/application/components/visualize_editor_common.tsx index 22f635460c353..a03073e61f59c 100644 --- a/src/plugins/visualize/public/application/components/visualize_editor_common.tsx +++ b/src/plugins/visualize/public/application/components/visualize_editor_common.tsx @@ -13,14 +13,12 @@ import { EuiScreenReaderOnly } from '@elastic/eui'; import { AppMountParameters } from 'kibana/public'; import { VisualizeTopNav } from './visualize_top_nav'; import { ExperimentalVisInfo } from './experimental_vis_info'; -import { DeprecationWarning, LEGACY_CHARTS_LIBRARY } from './deprecation_vis_warning'; import { SavedVisInstance, VisualizeAppState, VisualizeAppStateContainer, VisualizeEditorVisInstance, } from '../types'; -import { getUISettings } from '../../services'; interface VisualizeEditorCommonProps { visInstance?: VisualizeEditorVisInstance; @@ -39,13 +37,6 @@ interface VisualizeEditorCommonProps { embeddableId?: string; } -const isXYAxis = (visType: string | undefined): boolean => { - if (!visType) { - return false; - } - return ['area', 'line', 'histogram', 'horizontal_bar', 'point_series'].includes(visType); -}; - export const VisualizeEditorCommon = ({ visInstance, appState, @@ -62,7 +53,6 @@ export const VisualizeEditorCommon = ({ embeddableId, visEditorRef, }: VisualizeEditorCommonProps) => { - const hasXYLegacyChartsEnabled = getUISettings().get(LEGACY_CHARTS_LIBRARY); return (
{visInstance && appState && currentAppState && ( @@ -83,9 +73,6 @@ export const VisualizeEditorCommon = ({ /> )} {visInstance?.vis?.type?.stage === 'experimental' && } - {/* Adds a deprecation warning for vislib xy axis charts */} - {/* Should be removed when this issue is closed https://github.com/elastic/kibana/issues/103209 */} - {isXYAxis(visInstance?.vis.type.name) && hasXYLegacyChartsEnabled && } {visInstance?.vis?.type?.getInfoMessage?.(visInstance.vis)} {visInstance && ( diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts index 8200ba8aae92e..b5cb0194a0a78 100644 --- a/test/functional/apps/dashboard/dashboard_state.ts +++ b/test/functional/apps/dashboard/dashboard_state.ts @@ -33,9 +33,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); + const xyChartSelector = 'visTypeXyChart'; - const enableNewChartLibraryDebug = async () => { - if (await PageObjects.visChart.isNewChartsLibraryEnabled()) { + const enableNewChartLibraryDebug = async (force = false) => { + if ((await PageObjects.visChart.isNewChartsLibraryEnabled()) || force) { await elasticChart.setNewChartUiDebugFlag(); await queryBar.submitQuery(); } @@ -52,7 +53,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { if (isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': false, 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); @@ -69,33 +69,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setHistoricalDataRange(); - const visName = await PageObjects.visChart.getExpectedValue( - AREA_CHART_VIS_NAME, - `${AREA_CHART_VIS_NAME} - new charts library` - ); + const visName = AREA_CHART_VIS_NAME; await dashboardAddPanel.addVisualization(visName); - const dashboarName = await PageObjects.visChart.getExpectedValue( - 'Overridden colors', - 'Overridden colors - new charts library' - ); - await PageObjects.dashboard.saveDashboard(dashboarName); + const dashboardName = 'Overridden colors - new charts library'; + await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.switchToEditMode(); await queryBar.clickQuerySubmitButton(); - await PageObjects.visChart.openLegendOptionColors('Count', `[data-title="${visName}"]`); - const overwriteColor = isNewChartsLibraryEnabled ? '#d36086' : '#EA6460'; + await PageObjects.visChart.openLegendOptionColorsForXY('Count', `[data-title="${visName}"]`); + const overwriteColor = '#d36086'; await PageObjects.visChart.selectNewLegendColorChoice(overwriteColor); - await PageObjects.dashboard.saveDashboard(dashboarName); + await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.gotoDashboardLandingPage(); - await PageObjects.dashboard.loadSavedDashboard(dashboarName); + await PageObjects.dashboard.loadSavedDashboard(dashboardName); - await enableNewChartLibraryDebug(); + await enableNewChartLibraryDebug(true); - const colorChoiceRetained = await PageObjects.visChart.doesSelectedLegendColorExist( - overwriteColor + const colorChoiceRetained = await PageObjects.visChart.doesSelectedLegendColorExistForXY( + overwriteColor, + xyChartSelector ); expect(colorChoiceRetained).to.be(true); @@ -210,7 +205,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.get(newUrl.toString()); const alert = await browser.getAlert(); await alert?.accept(); - await enableNewChartLibraryDebug(); + await enableNewChartLibraryDebug(true); await PageObjects.dashboard.waitForRenderComplete(); }; @@ -293,7 +288,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('updates a pie slice color on a hard refresh', async function () { - await PageObjects.visChart.openLegendOptionColors( + await PageObjects.visChart.openLegendOptionColorsForPie( '80,000', `[data-title="${PIE_CHART_VIS_NAME}"]` ); @@ -318,7 +313,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('and updates the pie slice legend color', async function () { await retry.try(async () => { - const colorExists = await PageObjects.visChart.doesSelectedLegendColorExist('#FFFFFF'); + const colorExists = await PageObjects.visChart.doesSelectedLegendColorExistForPie( + '#FFFFFF' + ); expect(colorExists).to.be(true); }); }); @@ -342,7 +339,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('resets the legend color as well', async function () { await retry.try(async () => { - const colorExists = await PageObjects.visChart.doesSelectedLegendColorExist('#57c17b'); + const colorExists = await PageObjects.visChart.doesSelectedLegendColorExistForPie( + '#57c17b' + ); expect(colorExists).to.be(true); }); }); diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index e4dc04282e4ac..8627a258869bb 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -122,7 +122,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { await loadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': false, 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); @@ -131,7 +130,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await unloadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': true, 'visualization:visualize:legacyPieChartsLibrary': true, }); await browser.refresh(); diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts index 98eeed7bcf53e..426713c912e88 100644 --- a/test/functional/apps/getting_started/_shakespeare.ts +++ b/test/functional/apps/getting_started/_shakespeare.ts @@ -28,6 +28,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'visChart', ]); + const xyChartSelector = 'visTypeXyChart'; + // https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html describe('Shakespeare', function describeIndexTests() { @@ -56,7 +58,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { if (isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': false, 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); @@ -92,11 +93,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Remove refresh click when vislib is removed // https://github.com/elastic/kibana/issues/56143 - await PageObjects.visualize.clickRefresh(); + await PageObjects.visualize.clickRefresh(true); const expectedChartValues = [111396]; await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData('Count'); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Count'); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data[0] - expectedChartValues[0]).to.be.lessThan(5); @@ -123,12 +124,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickGo(); const expectedChartValues = [935]; await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); }); - const title = await PageObjects.visChart.getYAxisTitle(); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Speaking Parts'); }); @@ -149,13 +150,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const expectedChartValues = [71, 65, 62, 55, 55]; await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); }); - const labels = await PageObjects.visChart.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); expect(labels).to.eql([ 'Richard III', 'Henry VI Part 2', @@ -187,8 +188,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const expectedChartValues = [71, 65, 62, 55, 55]; const expectedChartValues2 = [177, 106, 153, 132, 162]; await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); - const data2 = await PageObjects.visChart.getBarChartData('Max Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Speaking Parts'); + const data2 = await PageObjects.visChart.getBarChartData( + xyChartSelector, + 'Max Speaking Parts' + ); log.debug('data=' + data); log.debug('data.length=' + data.length); log.debug('data2=' + data2); @@ -197,7 +201,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(data2).to.eql(expectedChartValues2); }); - const labels = await PageObjects.visChart.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); expect(labels).to.eql([ 'Richard III', 'Henry VI Part 2', @@ -220,8 +224,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const expectedChartValues = [71, 65, 62, 55, 55]; const expectedChartValues2 = [177, 106, 153, 132, 162]; await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); - const data2 = await PageObjects.visChart.getBarChartData('Max Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Speaking Parts'); + const data2 = await PageObjects.visChart.getBarChartData( + xyChartSelector, + 'Max Speaking Parts' + ); log.debug('data=' + data); log.debug('data.length=' + data.length); log.debug('data2=' + data2); @@ -243,17 +250,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickGo(); // same values as previous test except scaled down by the 50 for Y-Axis min - const expectedChartValues = await PageObjects.visChart.getExpectedValue( - [21, 15, 12, 5, 5], - [71, 65, 62, 55, 55] // no scaled values in elastic-charts - ); - const expectedChartValues2 = await PageObjects.visChart.getExpectedValue( - [127, 56, 103, 82, 112], - [177, 106, 153, 132, 162] // no scaled values in elastic-charts - ); + const expectedChartValues = [71, 65, 62, 55, 55]; + const expectedChartValues2 = [177, 106, 153, 132, 162]; await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); - const data2 = await PageObjects.visChart.getBarChartData('Max Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Speaking Parts'); + const data2 = await PageObjects.visChart.getBarChartData( + xyChartSelector, + 'Max Speaking Parts' + ); log.debug('data=' + data); log.debug('data.length=' + data.length); log.debug('data2=' + data2); diff --git a/test/functional/apps/getting_started/index.ts b/test/functional/apps/getting_started/index.ts index 4c1c052ef15a2..ae7fdc3c1d4fa 100644 --- a/test/functional/apps/getting_started/index.ts +++ b/test/functional/apps/getting_started/index.ts @@ -23,7 +23,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('new charts library', function () { before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': false, 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); @@ -31,7 +30,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': true, 'visualization:visualize:legacyPieChartsLibrary': true, }); await browser.refresh(); diff --git a/test/functional/apps/visualize/_area_chart.ts b/test/functional/apps/visualize/_area_chart.ts index e88754823f6cb..4e4fe5e2902b9 100644 --- a/test/functional/apps/visualize/_area_chart.ts +++ b/test/functional/apps/visualize/_area_chart.ts @@ -26,18 +26,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'header', 'timePicker', ]); + const xyChartSelector = 'visTypeXyChart'; - const getVizName = async () => - await PageObjects.visChart.getExpectedValue( - 'Visualization AreaChart Name Test', - 'Visualization AreaChart Name Test - Charts library' - ); + const vizName = 'Visualization AreaChart Name Test - Charts library'; describe('area charts', function indexPatternCreation() { - let isNewChartsLibraryEnabled = false; before(async () => { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); - await PageObjects.visualize.initTests(isNewChartsLibraryEnabled); + await PageObjects.visualize.initTests(); }); const initAreaChart = async () => { log.debug('navigateToApp visualize'); @@ -58,7 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const intervalValue = await PageObjects.visEditor.getInterval(); log.debug('intervalValue = ' + intervalValue); expect(intervalValue[0]).to.be('Auto'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); }; before(async function () { @@ -75,49 +70,38 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should save and load with special characters', async function () { - const vizNamewithSpecialChars = (await getVizName()) + '/?&=%'; + const vizNamewithSpecialChars = vizName + '/?&=%'; await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb( vizNamewithSpecialChars ); }); it('should save and load with non-ascii characters', async function () { - const vizNamewithSpecialChars = `${await getVizName()} with Umlaut ä`; + const vizNamewithSpecialChars = `${vizName} with Umlaut ä`; await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb( vizNamewithSpecialChars ); }); it('should save and load', async function () { - await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(await getVizName()); - await PageObjects.visualize.loadSavedVisualization(await getVizName()); + await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName); + await PageObjects.visualize.loadSavedVisualization(vizName); await PageObjects.visChart.waitForVisualization(); }); - // Should be removed when this issue is closed https://github.com/elastic/kibana/issues/103209 - it('should show/hide a deprecation warning depending on the library selected', async () => { - await PageObjects.visualize.getDeprecationWarningStatus(); - }); - it('should have inspector enabled', async function () { await inspector.expectIsEnabled(); }); it('should show correct chart', async function () { - const xAxisLabels = await PageObjects.visChart.getExpectedValue( - ['2015-09-20 00:00', '2015-09-21 00:00', '2015-09-22 00:00', '2015-09-23 00:00'], - [ - '2015-09-19 12:00', - '2015-09-20 12:00', - '2015-09-21 12:00', - '2015-09-22 12:00', - '2015-09-23 12:00', - ] - ); - const yAxisLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + const xAxisLabels = [ + '2015-09-19 12:00', + '2015-09-20 12:00', + '2015-09-21 12:00', + '2015-09-22 12:00', + '2015-09-23 12:00', + ]; + const yAxisLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; const expectedAreaChartData = [ 37, 202, @@ -146,14 +130,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]; await retry.try(async function tryingForTime() { - const labels = await PageObjects.visChart.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); log.debug('X-Axis labels = ' + labels); expect(labels).to.eql(xAxisLabels); }); - const labels = await PageObjects.visChart.getYAxisLabels(); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug('Y-Axis labels = ' + labels); expect(labels).to.eql(yAxisLabels); - const paths = await PageObjects.visChart.getAreaChartData('Count'); + const paths = await PageObjects.visChart.getAreaChartData('Count', xyChartSelector); log.debug('expectedAreaChartData = ' + expectedAreaChartData); log.debug('actual chart data = ' + paths); expect(paths).to.eql(expectedAreaChartData); @@ -220,7 +204,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.toggleOpenEditor(2); await PageObjects.visEditor.setInterval('Second'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await inspector.open(); await inspector.expectTableData(expectedTableData); await inspector.close(); @@ -252,7 +236,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.toggleAdvancedParams('2'); await PageObjects.visEditor.toggleScaleMetrics(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await inspector.open(); await inspector.expectTableData(expectedTableData); await inspector.close(); @@ -286,7 +270,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Top Hit', 'metrics'); await PageObjects.visEditor.selectField('bytes', 'metrics'); await PageObjects.visEditor.selectAggregateWith('average'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await inspector.open(); await inspector.expectTableData(expectedTableData); await inspector.close(); @@ -320,10 +304,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -332,10 +316,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show filtered ticks on selecting log scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -345,47 +329,35 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show ticks on selecting square root scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting square root scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['200', '400', '600', '800', '1,000', '1,200', '1,400'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug(labels); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting linear scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['200', '400', '600', '800', '1,000', '1,200', '1,400'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); }); @@ -408,11 +380,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Date Histogram'); await PageObjects.visEditor.selectField('@timestamp'); await PageObjects.visEditor.setInterval('Year'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); // This svg area is composed by 7 years (2013 - 2019). // 7 points are used to draw the upper line (usually called y1) // 7 points compose the lower line (usually called y0) - const paths = await PageObjects.visChart.getAreaChartPaths('Count'); + const paths = await PageObjects.visChart.getAreaChartPaths('Count', xyChartSelector); log.debug('actual chart data = ' + paths); const numberOfSegments = 7 * 2; expect(paths.length).to.eql(numberOfSegments); @@ -431,12 +403,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Date Histogram'); await PageObjects.visEditor.selectField('@timestamp'); await PageObjects.visEditor.setInterval('Month'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); // This svg area is composed by 67 months 3 (2013) + 5 * 12 + 4 (2019) // 67 points are used to draw the upper line (usually called y1) // 67 points compose the lower line (usually called y0) const numberOfSegments = 67 * 2; - const paths = await PageObjects.visChart.getAreaChartPaths('Count'); + const paths = await PageObjects.visChart.getAreaChartPaths('Count', xyChartSelector); log.debug('actual chart data = ' + paths); expect(paths.length).to.eql(numberOfSegments); }); diff --git a/test/functional/apps/visualize/_line_chart_split_chart.ts b/test/functional/apps/visualize/_line_chart_split_chart.ts index 9b1c12de9666e..0e44c30499ed3 100644 --- a/test/functional/apps/visualize/_line_chart_split_chart.ts +++ b/test/functional/apps/visualize/_line_chart_split_chart.ts @@ -23,9 +23,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'visChart', 'timePicker', ]); + const xyChartSelector = 'visTypeXyChart'; describe('line charts - split chart', function () { - let isNewChartsLibraryEnabled = false; const initLineChart = async function () { log.debug('navigateToApp visualize'); await PageObjects.visualize.navigateToNewAggBasedVisualization(); @@ -41,12 +41,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectField('extension.raw'); log.debug('switch from Rows to Columns'); await PageObjects.visEditor.clickSplitDirection('Columns'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); }; before(async () => { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); - await PageObjects.visualize.initTests(isNewChartsLibraryEnabled); + await PageObjects.visualize.initTests(); await initLineChart(); }); @@ -61,7 +60,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // sleep a bit before trying to get the chart data await PageObjects.common.sleep(3000); - const data = await PageObjects.visChart.getLineChartData(); + const data = await PageObjects.visChart.getLineChartData(xyChartSelector); log.debug('data=' + data); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { @@ -92,9 +91,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Order By = Term'); await PageObjects.visEditor.selectOrderByMetric(2, '_key'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await retry.try(async function () { - const data = await PageObjects.visChart.getLineChartData(); + const data = await PageObjects.visChart.getLineChartData(xyChartSelector); log.debug('data=' + data); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { @@ -161,10 +160,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to save and load', async function () { - const vizName = await PageObjects.visChart.getExpectedValue( - 'Visualization Line split chart', - 'Visualization Line split chart - chart library' - ); + const vizName = 'Visualization Line split chart - chart library'; await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName); await PageObjects.visualize.loadSavedVisualization(vizName); @@ -180,10 +176,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 7000); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 7000; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -192,10 +188,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show filtered ticks on selecting log scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 7000); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 7000; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -205,48 +201,80 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show ticks on selecting square root scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '2,000', '4,000', '6,000', '8,000', '10,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting square root scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['2,000', '4,000', '6,000', '8,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug(labels); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '2,000', '4,000', '6,000', '8,000', '10,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting linear scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['2,000', '4,000', '6,000', '8,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); }); @@ -274,16 +302,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Date Histogram'); await PageObjects.visEditor.selectAggregation('Date Histogram'); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Serial Diff of Count'); }); it('should change y-axis label to custom', async () => { log.debug('set custom label of y-axis to "Custom"'); await PageObjects.visEditor.setCustomLabel('Custom', 1); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Custom'); }); @@ -297,24 +325,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should apply with selected bucket', async () => { log.debug('Metrics agg = Average Bucket'); await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics'); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Overall Average of Count'); }); it('should change sub metric custom label and calculate y-axis title', async () => { log.debug('set custom label of sub metric to "Cats"'); await PageObjects.visEditor.setCustomLabel('Cats', '1-metric'); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Overall Average of Cats'); }); it('should outer custom label', async () => { log.debug('set custom label to "Custom"'); await PageObjects.visEditor.setCustomLabel('Custom', 1); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Custom'); }); diff --git a/test/functional/apps/visualize/_line_chart_split_series.ts b/test/functional/apps/visualize/_line_chart_split_series.ts index 91d44a6fc40da..d10b4ebd9b312 100644 --- a/test/functional/apps/visualize/_line_chart_split_series.ts +++ b/test/functional/apps/visualize/_line_chart_split_series.ts @@ -23,9 +23,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'visChart', 'timePicker', ]); + const xyChartSelector = 'visTypeXyChart'; describe('line charts - split series', function () { - let isNewChartsLibraryEnabled = false; const initLineChart = async function () { log.debug('navigateToApp visualize'); await PageObjects.visualize.navigateToNewAggBasedVisualization(); @@ -39,12 +39,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Field = extension'); await PageObjects.visEditor.selectField('extension.raw'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); }; before(async () => { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); - await PageObjects.visualize.initTests(isNewChartsLibraryEnabled); + await PageObjects.visualize.initTests(); await initLineChart(); }); @@ -59,7 +58,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // sleep a bit before trying to get the chart data await PageObjects.common.sleep(3000); - const data = await PageObjects.visChart.getLineChartData(); + const data = await PageObjects.visChart.getLineChartData(xyChartSelector); log.debug('data=' + data); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { @@ -90,9 +89,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Order By = Term'); await PageObjects.visEditor.selectOrderByMetric(2, '_key'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await retry.try(async function () { - const data = await PageObjects.visChart.getLineChartData(); + const data = await PageObjects.visChart.getLineChartData(xyChartSelector); log.debug('data=' + data); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { @@ -159,10 +158,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should be able to save and load', async function () { - const vizName = await PageObjects.visChart.getExpectedValue( - 'Visualization Line split series', - 'Visualization Line split series - chart library' - ); + const vizName = 'Visualization Line split series'; await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName); @@ -179,10 +175,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -191,10 +187,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show filtered ticks on selecting log scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -204,47 +200,79 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show ticks on selecting square root scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '2,000', '4,000', '6,000', '8,000', '10,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting square root scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['2,000', '4,000', '6,000', '8,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug(labels); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '2,000', '4,000', '6,000', '8,000', '10,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting linear scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['2,000', '4,000', '6,000', '8,000'], - ['0', '1,000', '2,000', '3,000', '4,000', '5,000', '6,000', '7,000', '8,000', '9,000'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = [ + '0', + '1,000', + '2,000', + '3,000', + '4,000', + '5,000', + '6,000', + '7,000', + '8,000', + '9,000', + ]; expect(labels).to.eql(expectedLabels); }); }); @@ -272,16 +300,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Date Histogram'); await PageObjects.visEditor.selectAggregation('Date Histogram'); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Serial Diff of Count'); }); it('should change y-axis label to custom', async () => { log.debug('set custom label of y-axis to "Custom"'); await PageObjects.visEditor.setCustomLabel('Custom', 1); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Custom'); }); @@ -295,24 +323,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should apply with selected bucket', async () => { log.debug('Metrics agg = Average Bucket'); await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics'); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Overall Average of Count'); }); it('should change sub metric custom label and calculate y-axis title', async () => { log.debug('set custom label of sub metric to "Cats"'); await PageObjects.visEditor.setCustomLabel('Cats', '1-metric'); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Overall Average of Cats'); }); it('should outer custom label', async () => { log.debug('set custom label to "Custom"'); await PageObjects.visEditor.setCustomLabel('Custom', 1); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be('Custom'); }); diff --git a/test/functional/apps/visualize/_point_series_options.ts b/test/functional/apps/visualize/_point_series_options.ts index 08c26b1f3ee95..0d68ea4984ec2 100644 --- a/test/functional/apps/visualize/_point_series_options.ts +++ b/test/functional/apps/visualize/_point_series_options.ts @@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'common', ]); const inspector = getService('inspector'); + const xyChartSelector = 'visTypeXyChart'; async function initChart() { log.debug('navigateToApp visualize'); @@ -57,14 +58,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Average memory value axis - ValueAxis-2'); await PageObjects.visEditor.setSeriesAxis(1, 'ValueAxis-2'); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); } describe('point series', function describeIndexTests() { - let isNewChartsLibraryEnabled = false; before(async () => { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); - await PageObjects.visualize.initTests(isNewChartsLibraryEnabled); + await PageObjects.visualize.initTests(); await initChart(); }); @@ -126,7 +125,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]; await retry.try(async () => { - const data = await PageObjects.visChart.getLineChartData('Count'); + const data = await PageObjects.visChart.getLineChartData(xyChartSelector, 'Count'); log.debug('count data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues[0]); @@ -134,8 +133,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await retry.try(async () => { const avgMemoryData = await PageObjects.visChart.getLineChartData( - 'Average machine.ram', - 'ValueAxis-2' + xyChartSelector, + 'Average machine.ram' ); log.debug('average memory data=' + avgMemoryData); log.debug('data.length=' + avgMemoryData.length); @@ -151,7 +150,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should put secondary axis on the right', async function () { - const length = await PageObjects.visChart.getAxesCountByPosition('right'); + const length = await PageObjects.visChart.getAxesCountByPosition('right', xyChartSelector); expect(length).to.be(1); }); }); @@ -159,8 +158,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('multiple chart types', function () { it('should change average series type to histogram', async function () { await PageObjects.visEditor.setSeriesType(1, 'histogram'); - await PageObjects.visEditor.clickGo(); - const length = await PageObjects.visChart.getHistogramSeriesCount(); + await PageObjects.visEditor.clickGo(true); + const length = await PageObjects.visChart.getHistogramSeriesCount(xyChartSelector); expect(length).to.be(1); }); }); @@ -172,8 +171,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show category grid lines', async function () { await PageObjects.visEditor.toggleGridCategoryLines(); - await PageObjects.visEditor.clickGo(); - const gridLines = await PageObjects.visChart.getGridLines(); + await PageObjects.visEditor.clickGo(true); + const gridLines = await PageObjects.visChart.getGridLines(xyChartSelector); // FLAKY relaxing as depends on chart size/browser size and produce differences between local and CI // The objective here is to check whenever the grid lines are rendered, not the exact quantity expect(gridLines.length).to.be.greaterThan(0); @@ -185,8 +184,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show value axis grid lines', async function () { await PageObjects.visEditor.setGridValueAxis('ValueAxis-2'); await PageObjects.visEditor.toggleGridCategoryLines(); - await PageObjects.visEditor.clickGo(); - const gridLines = await PageObjects.visChart.getGridLines(); + await PageObjects.visEditor.clickGo(true); + const gridLines = await PageObjects.visChart.getGridLines(xyChartSelector); // FLAKY relaxing as depends on chart size/browser size and produce differences between local and CI // The objective here is to check whenever the grid lines are rendered, not the exact quantity expect(gridLines.length).to.be.greaterThan(0); @@ -208,22 +207,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Field = geo.src'); await PageObjects.visEditor.selectField('geo.src'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); log.debug('Open Options tab'); await PageObjects.visEditor.clickOptionsTab(); }); it('should show values on bar chart', async () => { await PageObjects.visEditor.toggleValuesOnChart(); - await PageObjects.visEditor.clickGo(); - const values = await PageObjects.visChart.getChartValues(); + await PageObjects.visEditor.clickGo(true); + const values = await PageObjects.visChart.getChartValues(xyChartSelector); expect(values).to.eql(['2,592', '2,373', '1,194', '489', '415']); }); it('should hide values on bar chart', async () => { await PageObjects.visEditor.toggleValuesOnChart(); - await PageObjects.visEditor.clickGo(); - const values = await PageObjects.visChart.getChartValues(); + await PageObjects.visEditor.clickGo(true); + const values = await PageObjects.visChart.getChartValues(xyChartSelector); expect(values.length).to.be(0); }); }); @@ -237,20 +236,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); await PageObjects.visEditor.selectYAxisAggregation('Average', 'bytes', customLabel, 1); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.clickYAxisOptions('ValueAxis-1'); }); it('should render a custom label when one is set', async function () { - const title = await PageObjects.visChart.getYAxisTitle(); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be(customLabel); }); it('should render a custom axis title when one is set, overriding the custom label', async function () { await PageObjects.visEditor.setAxisTitle(axisTitle); - await PageObjects.visEditor.clickGo(); - const title = await PageObjects.visChart.getYAxisTitle(); + await PageObjects.visEditor.clickGo(true); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be(axisTitle); }); @@ -262,43 +261,42 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickDataTab(); await PageObjects.visEditor.toggleOpenEditor(1); await PageObjects.visEditor.setCustomLabel('test', 1); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.clickYAxisOptions('ValueAxis-1'); - const title = await PageObjects.visChart.getYAxisTitle(); + const title = await PageObjects.visChart.getYAxisTitle(xyChartSelector); expect(title).to.be(axisTitle); }); }); describe('timezones', async function () { it('should show round labels in default timezone', async function () { - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['2015-09-20 00:00', '2015-09-21 00:00', '2015-09-22 00:00'], - ['2015-09-19 12:00', '2015-09-20 12:00', '2015-09-21 12:00', '2015-09-22 12:00'] - ); + const expectedLabels = [ + '2015-09-19 12:00', + '2015-09-20 12:00', + '2015-09-21 12:00', + '2015-09-22 12:00', + ]; await initChart(); - const labels = await PageObjects.visChart.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); expect(labels.join()).to.contain(expectedLabels.join()); }); it('should show round labels in different timezone', async function () { - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['2015-09-20 00:00', '2015-09-21 00:00', '2015-09-22 00:00'], - [ - '2015-09-19 12:00', - '2015-09-20 12:00', - '2015-09-21 12:00', - '2015-09-22 12:00', - '2015-09-23 12:00', - ] - ); + const expectedLabels = [ + '2015-09-19 12:00', + '2015-09-20 12:00', + '2015-09-21 12:00', + '2015-09-22 12:00', + '2015-09-23 12:00', + ]; await kibanaServer.uiSettings.update({ 'dateFormat:tz': 'America/Phoenix' }); await browser.refresh(); await PageObjects.header.awaitKibanaChrome(); await initChart(); - const labels = await PageObjects.visChart.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); expect(labels.join()).to.contain(expectedLabels.join()); }); @@ -314,28 +312,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'wait for x-axis labels to match expected for Phoenix', 5000, async () => { - const labels = (await PageObjects.visChart.getXAxisLabels()) ?? ''; + const labels = (await PageObjects.visChart.getXAxisLabels(xyChartSelector)) ?? ''; log.debug(`Labels: ${labels}`); - const xLabels = await PageObjects.visChart.getExpectedValue( - ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00'], - [ - '09:30', - '10:00', - '10:30', - '11:00', - '11:30', - '12:00', - '12:30', - '13:00', - '13:30', - '14:00', - '14:30', - '15:00', - '15:30', - '16:00', - ] - ); + const xLabels = [ + '09:30', + '10:00', + '10:30', + '11:00', + '11:30', + '12:00', + '12:30', + '13:00', + '13:30', + '14:00', + '14:30', + '15:00', + '15:30', + '16:00', + ]; return labels.toString() === xLabels.toString(); } ); @@ -375,7 +370,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.refresh(); // wait some time before trying to check for rendering count await PageObjects.header.awaitKibanaChrome(); - await PageObjects.visualize.clickRefresh(); + await PageObjects.visualize.clickRefresh(true); await PageObjects.visChart.waitForRenderingCount(); log.debug('getXAxisLabels'); @@ -383,28 +378,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'wait for x-axis labels to match expected for UTC', 5000, async () => { - const labels2 = (await PageObjects.visChart.getXAxisLabels()) ?? ''; + const labels2 = (await PageObjects.visChart.getXAxisLabels(xyChartSelector)) ?? ''; log.debug(`Labels: ${labels2}`); - const xLabels2 = await PageObjects.visChart.getExpectedValue( - ['17:00', '18:00', '19:00', '20:00', '21:00', '22:00'], - [ - '16:30', - '17:00', - '17:30', - '18:00', - '18:30', - '19:00', - '19:30', - '20:00', - '20:30', - '21:00', - '21:30', - '22:00', - '22:30', - '23:00', - ] - ); + const xLabels2 = [ + '16:30', + '17:00', + '17:30', + '18:00', + '18:30', + '19:00', + '19:30', + '20:00', + '20:30', + '21:00', + '21:30', + '22:00', + '22:30', + '23:00', + ]; return labels2.toString() === xLabels2.toString(); } ); diff --git a/test/functional/apps/visualize/_timelion.ts b/test/functional/apps/visualize/_timelion.ts index 589559c717842..a3f2c87424244 100644 --- a/test/functional/apps/visualize/_timelion.ts +++ b/test/functional/apps/visualize/_timelion.ts @@ -20,6 +20,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const elasticChart = getService('elasticChart'); const find = getService('find'); + const timelionChartSelector = 'timelionChart'; describe('Timelion visualization', () => { before(async () => { @@ -35,13 +36,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const initVisualization = async (expression: string, interval: string = '12h') => { await visEditor.setTimelionInterval(interval); await monacoEditor.setCodeEditorValue(expression); - await visEditor.clickGo(); + await visEditor.clickGo(true); }; it('should display correct data for specified index pattern and timefield', async () => { await initVisualization('.es(index=long-window-logstash-*,timefield=@timestamp)'); - const chartData = await visChart.getAreaChartData('q:* > count'); + const chartData = await visChart.getAreaChartData('q:* > count', timelionChartSelector); expect(chartData).to.eql([3, 5, 2, 6, 1, 6, 1, 7, 0, 0]); }); @@ -62,10 +63,22 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { '36h' ); - const firstAreaChartData = await visChart.getAreaChartData('q:* > avg(bytes)'); - const secondAreaChartData = await visChart.getAreaChartData('q:* > min(bytes)'); - const thirdAreaChartData = await visChart.getAreaChartData('q:* > max(bytes)'); - const forthAreaChartData = await visChart.getAreaChartData('q:* > cardinality(bytes)'); + const firstAreaChartData = await visChart.getAreaChartData( + 'q:* > avg(bytes)', + timelionChartSelector + ); + const secondAreaChartData = await visChart.getAreaChartData( + 'q:* > min(bytes)', + timelionChartSelector + ); + const thirdAreaChartData = await visChart.getAreaChartData( + 'q:* > max(bytes)', + timelionChartSelector + ); + const forthAreaChartData = await visChart.getAreaChartData( + 'q:* > cardinality(bytes)', + timelionChartSelector + ); expect(firstAreaChartData).to.eql([5732.783676366217, 5721.775973559419]); expect(secondAreaChartData).to.eql([0, 0]); @@ -84,10 +97,19 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { '.es(*).if(operator=gt,if=200,then=50,else=150).label("condition")' ); - const firstAreaChartData = await visChart.getAreaChartData('initial'); - const secondAreaChartData = await visChart.getAreaChartData('add multiply abs divide'); - const thirdAreaChartData = await visChart.getAreaChartData('query derivative min sum'); - const forthAreaChartData = await visChart.getAreaChartData('condition'); + const firstAreaChartData = await visChart.getAreaChartData('initial', timelionChartSelector); + const secondAreaChartData = await visChart.getAreaChartData( + 'add multiply abs divide', + timelionChartSelector + ); + const thirdAreaChartData = await visChart.getAreaChartData( + 'query derivative min sum', + timelionChartSelector + ); + const forthAreaChartData = await visChart.getAreaChartData( + 'condition', + timelionChartSelector + ); expect(firstAreaChartData).to.eql(firstAreaExpectedChartData); expect(secondAreaChartData).to.eql(firstAreaExpectedChartData); @@ -112,20 +134,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { '36h' ); - const leftAxesCount = await visChart.getAxesCountByPosition('left'); - const rightAxesCount = await visChart.getAxesCountByPosition('right'); - const firstAxesLabels = await visChart.getYAxisLabels(); - const secondAxesLabels = await visChart.getYAxisLabels(1); - const thirdAxesLabels = await visChart.getYAxisLabels(2); - const firstAreaChartData = await visChart.getAreaChartData('Average Machine RAM amount'); + const leftAxesCount = await visChart.getAxesCountByPosition('left', timelionChartSelector); + const rightAxesCount = await visChart.getAxesCountByPosition('right', timelionChartSelector); + const firstAxesLabels = await visChart.getYAxisLabels(timelionChartSelector); + const secondAxesLabels = await visChart.getYAxisLabels(timelionChartSelector, 1); + const thirdAxesLabels = await visChart.getYAxisLabels(timelionChartSelector, 2); + const firstAreaChartData = await visChart.getAreaChartData( + 'Average Machine RAM amount', + timelionChartSelector + ); const secondAreaChartData = await visChart.getAreaChartData( 'Average Bytes for request', - undefined, + timelionChartSelector, true ); const thirdAreaChartData = await visChart.getAreaChartData( 'Average Bytes for request with offset', - undefined, + timelionChartSelector, true ); @@ -144,9 +169,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should display correct chart data for split expression', async () => { await initVisualization('.es(index=logstash-*, split=geo.dest:3)', '1 day'); - const firstAreaChartData = await visChart.getAreaChartData('q:* > geo.dest:CN > count'); - const secondAreaChartData = await visChart.getAreaChartData('q:* > geo.dest:IN > count'); - const thirdAreaChartData = await visChart.getAreaChartData('q:* > geo.dest:US > count'); + const firstAreaChartData = await visChart.getAreaChartData( + 'q:* > geo.dest:CN > count', + timelionChartSelector + ); + const secondAreaChartData = await visChart.getAreaChartData( + 'q:* > geo.dest:IN > count', + timelionChartSelector + ); + const thirdAreaChartData = await visChart.getAreaChartData( + 'q:* > geo.dest:US > count', + timelionChartSelector + ); expect(firstAreaChartData).to.eql([0, 905, 910, 850, 0]); expect(secondAreaChartData).to.eql([0, 763, 699, 825, 0]); @@ -156,8 +190,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should display two areas and one bar chart items', async () => { await initVisualization('.es(*), .es(*), .es(*).bars(stack=true)'); - const areasChartsCount = await visChart.getAreaSeriesCount(); - const barsChartsCount = await visChart.getHistogramSeriesCount(); + const areasChartsCount = await visChart.getAreaSeriesCount(timelionChartSelector); + const barsChartsCount = await visChart.getHistogramSeriesCount(timelionChartSelector); expect(areasChartsCount).to.be(2); expect(barsChartsCount).to.be(1); @@ -167,7 +201,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should correctly display the legend items names and position', async () => { await initVisualization('.es(*).label("first series"), .es(*).label("second series")'); - const legendNames = await visChart.getLegendEntries(); + const legendNames = await visChart.getLegendEntriesXYCharts(timelionChartSelector); const legendElement = await find.byClassName('echLegend'); const isLegendTopPositioned = await legendElement.elementHasClass('echLegend--top'); const isLegendLeftPositioned = await legendElement.elementHasClass('echLegend--left'); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.ts b/test/functional/apps/visualize/_vertical_bar_chart.ts index a728757a485e1..93022b5d2f0e8 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.ts +++ b/test/functional/apps/visualize/_vertical_bar_chart.ts @@ -18,11 +18,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const filterBar = getService('filterBar'); const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); + const xyChartSelector = 'visTypeXyChart'; + describe('vertical bar chart', function () { - let isNewChartsLibraryEnabled = false; before(async () => { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); - await PageObjects.visualize.initTests(isNewChartsLibraryEnabled); + await PageObjects.visualize.initTests(); }); const vizName1 = 'Visualization VerticalBarChart'; @@ -41,21 +41,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Field = @timestamp'); await PageObjects.visEditor.selectField('@timestamp'); // leaving Interval set to Auto - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); }; describe('bar charts x axis tick labels', () => { it('should show tick labels also after rotation of the chart', async function () { await initBarChart(); - const bottomLabels = await PageObjects.visChart.getXAxisLabels(); + const bottomLabels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); log.debug(`${bottomLabels.length} tick labels on bottom x axis`); await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.selectXAxisPosition('left'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); // the getYAxisLabels helper always returns the labels on the left axis - const leftLabels = await PageObjects.visChart.getYAxisLabels(); + const leftLabels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug(`${leftLabels.length} tick labels on left x axis`); expect(leftLabels.length).to.be.greaterThan(bottomLabels.length * (2 / 3)); }); @@ -69,16 +69,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Date Range'); await PageObjects.visEditor.selectField('@timestamp'); - await PageObjects.visEditor.clickGo(); - const bottomLabels = await PageObjects.visChart.getXAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const bottomLabels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); expect(bottomLabels.length).to.be(1); await PageObjects.visEditor.clickMetricsAndAxes(); await PageObjects.visEditor.selectXAxisPosition('left'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); // the getYAxisLabels helper always returns the labels on the left axis - const leftLabels = await PageObjects.visChart.getYAxisLabels(); + const leftLabels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); expect(leftLabels.length).to.be(1); }); }); @@ -96,8 +96,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectField('@timestamp'); await PageObjects.visEditor.clickAddDateRange(); await PageObjects.visEditor.setDateRangeByIndex('1', 'now-2w/w', 'now-1w/w'); - await PageObjects.visEditor.clickGo(); - const bottomLabels = await PageObjects.visChart.getXAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const bottomLabels = await PageObjects.visChart.getXAxisLabels(xyChartSelector); expect(bottomLabels.length).to.be(2); }); }); @@ -146,7 +146,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -257,7 +257,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -265,7 +265,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.toggleOpenEditor(2); await PageObjects.visEditor.clickDropPartialBuckets(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); expectedChartValues = [ 218, @@ -333,7 +333,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -349,11 +349,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -362,11 +362,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show filtered ticks on selecting log scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -376,47 +376,35 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show ticks on selecting square root scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting square root scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['200', '400', '600', '800', '1,000', '1,200', '1,400'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug(labels); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting linear scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['200', '400', '600', '800', '1,000', '1,200', '1,400'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); }); @@ -429,8 +417,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectYAxisMode('percentage'); await PageObjects.visEditor.changeYAxisShowCheckbox(axisId, true); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); expect(labels[0]).to.eql('0%'); expect(labels[labels.length - 1]).to.eql('100%'); }); @@ -445,33 +433,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Terms'); await PageObjects.visEditor.selectField('response.raw'); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); - const expectedEntries = await PageObjects.visChart.getExpectedValue( - ['200', '404', '503'], - ['503', '404', '200'] // sorting aligned with rendered geometries - ); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const expectedEntries = ['503', '404', '200']; // sorting aligned with rendered geometries + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); it('should allow custom sorting of series', async () => { await PageObjects.visEditor.toggleOpenEditor(1, 'false'); await PageObjects.visEditor.selectCustomSortMetric(3, 'Min', 'bytes'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); - const expectedEntries = await PageObjects.visChart.getExpectedValue( - ['404', '200', '503'], - ['503', '200', '404'] // sorting aligned with rendered geometries - ); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const expectedEntries = ['503', '200', '404']; + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); it('should correctly filter by legend', async () => { - await PageObjects.visChart.filterLegend('200'); + await PageObjects.visChart.filterLegend('200', true); await PageObjects.visChart.waitForVisualization(); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); const expectedEntries = ['200']; expect(legendEntries).to.eql(expectedEntries); await filterBar.removeFilter('response.raw'); @@ -494,45 +476,26 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Terms'); await PageObjects.visEditor.selectField('machine.os'); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); - - const expectedEntries = await PageObjects.visChart.getExpectedValue( - [ - '200 - win 8', - '200 - win xp', - '200 - ios', - '200 - osx', - '200 - win 7', - '404 - ios', - '503 - ios', - '503 - osx', - '503 - win 7', - '503 - win 8', - '503 - win xp', - '404 - osx', - '404 - win 7', - '404 - win 8', - '404 - win xp', - ], - [ - '404 - win xp', - '404 - win 8', - '404 - win 7', - '404 - osx', - '503 - win xp', - '503 - win 8', - '503 - win 7', - '503 - osx', - '503 - ios', - '404 - ios', - '200 - win 7', - '200 - osx', - '200 - ios', - '200 - win xp', - '200 - win 8', - ] - ); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + await PageObjects.visEditor.clickGo(true); + + const expectedEntries = [ + '404 - win xp', + '404 - win 8', + '404 - win 7', + '404 - osx', + '503 - win xp', + '503 - win 8', + '503 - win 7', + '503 - osx', + '503 - ios', + '404 - ios', + '200 - win 7', + '200 - osx', + '200 - ios', + '200 - win xp', + '200 - win 8', + ]; + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); @@ -540,13 +503,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // this will avoid issues with the play tooltip covering the disable agg button await testSubjects.scrollIntoView('metricsAggGroup'); await PageObjects.visEditor.toggleDisabledAgg(3); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); - const expectedEntries = await PageObjects.visChart.getExpectedValue( - ['win 8', 'win xp', 'ios', 'osx', 'win 7'], - ['win 7', 'osx', 'ios', 'win xp', 'win 8'] - ); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const expectedEntries = ['win 7', 'osx', 'ios', 'win xp', 'win 8']; + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); }); @@ -559,10 +519,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.toggleOpenEditor(1); await PageObjects.visEditor.selectAggregation('Derivative', 'metrics'); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); const expectedEntries = ['Derivative of Count']; - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); diff --git a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.ts b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.ts index 97817315b5801..e9f39a45d7892 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.ts +++ b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.ts @@ -16,9 +16,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const inspector = getService('inspector'); const PageObjects = getPageObjects(['common', 'visualize', 'header', 'visEditor', 'visChart']); + const xyChartSelector = 'visTypeXyChart'; + describe('vertical bar chart with index without time filter', function () { const vizName1 = 'Visualization VerticalBarChart without time filter'; - let isNewChartsLibraryEnabled = false; const initBarChart = async () => { log.debug('navigateToApp visualize'); @@ -37,12 +38,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectField('@timestamp'); await PageObjects.visEditor.setInterval('3h', { type: 'custom' }); await PageObjects.visChart.waitForVisualizationRenderingStabilized(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); }; before(async () => { - isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); - await PageObjects.visualize.initTests(isNewChartsLibraryEnabled); + await PageObjects.visualize.initTests(); await initBarChart(); }); @@ -89,7 +89,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visChart.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -134,10 +134,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickYAxisOptions(axisId); await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -146,10 +146,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show filtered ticks on selecting log scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(); - const minLabel = await PageObjects.visChart.getExpectedValue(2, 1); - const maxLabel = await PageObjects.visChart.getExpectedValue(5000, 900); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabelsAsNumbers(xyChartSelector); + const minLabel = 1; + const maxLabel = 900; const numberOfLabels = 10; expect(labels.length).to.be.greaterThan(numberOfLabels); expect(labels[0]).to.eql(minLabel); @@ -159,47 +159,35 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show ticks on selecting square root scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting square root scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['200', '400', '600', '800', '1,000', '1,200', '1,400'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); log.debug(labels); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400', '1,600'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting linear scale', async () => { await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visEditor.clickGo(); - const labels = await PageObjects.visChart.getYAxisLabels(); - const expectedLabels = await PageObjects.visChart.getExpectedValue( - ['200', '400', '600', '800', '1,000', '1,200', '1,400'], - ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400'] - ); + await PageObjects.visEditor.clickGo(true); + const labels = await PageObjects.visChart.getYAxisLabels(xyChartSelector); + const expectedLabels = ['0', '200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); }); @@ -215,15 +203,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await PageObjects.header.waitUntilLoadingHasFinished(); - const expectedEntries = await PageObjects.visChart.getExpectedValue( - ['200', '404', '503'], - ['503', '404', '200'] // sorting aligned with rendered geometries - ); + const expectedEntries = ['503', '404', '200']; // sorting aligned with rendered geometries - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); }); @@ -245,59 +230,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await PageObjects.header.waitUntilLoadingHasFinished(); - const expectedEntries = await PageObjects.visChart.getExpectedValue( - [ - '200 - win 8', - '200 - win xp', - '200 - ios', - '200 - osx', - '200 - win 7', - '404 - ios', - '503 - ios', - '503 - osx', - '503 - win 7', - '503 - win 8', - '503 - win xp', - '404 - osx', - '404 - win 7', - '404 - win 8', - '404 - win xp', - ], - [ - '404 - win xp', - '404 - win 8', - '404 - win 7', - '404 - osx', - '503 - win xp', - '503 - win 8', - '503 - win 7', - '503 - osx', - '503 - ios', - '404 - ios', - '200 - win 7', - '200 - osx', - '200 - ios', - '200 - win xp', - '200 - win 8', - ] - ); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const expectedEntries = [ + '404 - win xp', + '404 - win 8', + '404 - win 7', + '404 - osx', + '503 - win xp', + '503 - win 8', + '503 - win 7', + '503 - osx', + '503 - ios', + '404 - ios', + '200 - win 7', + '200 - osx', + '200 - ios', + '200 - win xp', + '200 - win 8', + ]; + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); it('should show correct series when disabling first agg', async function () { await PageObjects.visEditor.toggleDisabledAgg(3); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await PageObjects.header.waitUntilLoadingHasFinished(); - const expectedEntries = await PageObjects.visChart.getExpectedValue( - ['win 8', 'win xp', 'ios', 'osx', 'win 7'], - ['win 7', 'osx', 'ios', 'win xp', 'win 8'] - ); - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const expectedEntries = ['win 7', 'osx', 'ios', 'win xp', 'win 8']; + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); }); @@ -312,11 +275,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(true); await PageObjects.header.waitUntilLoadingHasFinished(); const expectedEntries = ['Derivative of Count']; - const legendEntries = await PageObjects.visChart.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntriesXYCharts(xyChartSelector); expect(legendEntries).to.eql(expectedEntries); }); }); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 4af871bd9347d..bb5357fc456cb 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -30,7 +30,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': false, 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); @@ -38,7 +37,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': true, 'visualization:visualize:legacyPieChartsLibrary': true, }); await browser.refresh(); @@ -59,7 +57,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { this.tags('ciGroup9'); loadTestFile(require.resolve('./_embedding_chart')); - loadTestFile(require.resolve('./_area_chart')); loadTestFile(require.resolve('./_data_table')); loadTestFile(require.resolve('./_data_table_nontimeindex')); loadTestFile(require.resolve('./_data_table_notimeindex_filters')); @@ -81,10 +78,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('visualize ciGroup4', function () { this.tags('ciGroup4'); - loadTestFile(require.resolve('./_line_chart_split_series')); - loadTestFile(require.resolve('./_line_chart_split_chart')); loadTestFile(require.resolve('./_pie_chart')); - loadTestFile(require.resolve('./_point_series_options')); loadTestFile(require.resolve('./_markdown_vis')); loadTestFile(require.resolve('./_shared_item')); loadTestFile(require.resolve('./_lab_mode')); @@ -99,8 +93,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { this.tags('ciGroup12'); loadTestFile(require.resolve('./_tag_cloud')); - loadTestFile(require.resolve('./_vertical_bar_chart')); - loadTestFile(require.resolve('./_vertical_bar_chart_nontimeindex')); loadTestFile(require.resolve('./_tsvb_chart')); loadTestFile(require.resolve('./_tsvb_time_series')); loadTestFile(require.resolve('./_tsvb_markdown')); diff --git a/test/functional/config.js b/test/functional/config.js index 5d8a65d44a183..643f012205aea 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -57,7 +57,6 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', - 'visualization:visualize:legacyChartsLibrary': true, 'visualization:visualize:legacyPieChartsLibrary': true, }, }, diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 591cddd18a2b3..c96faab2dc321 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -30,7 +30,6 @@ export class VisualBuilderPageObject extends FtrService { private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly comboBox = this.ctx.getService('comboBox'); private readonly elasticChart = this.ctx.getService('elasticChart'); - private readonly kibanaServer = this.ctx.getService('kibanaServer'); private readonly common = this.ctx.getPageObject('common'); private readonly header = this.ctx.getPageObject('header'); private readonly timePicker = this.ctx.getPageObject('timePicker'); @@ -843,9 +842,6 @@ export class VisualBuilderPageObject extends FtrService { } public async toggleNewChartsLibraryWithDebug(enabled: boolean) { - await this.kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyChartsLibrary': !enabled, - }); await this.elasticChart.setNewChartUiDebugFlag(enabled); } diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index bcee77a21c0b0..c1056b58e22d4 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -11,7 +11,6 @@ import Color from 'color'; import { FtrService } from '../ftr_provider_context'; -const xyChartSelector = 'visTypeXyChart'; const pieChartSelector = 'visTypePieChart'; export class VisualizeChartPageObject extends FtrService { @@ -37,8 +36,7 @@ export class VisualizeChartPageObject extends FtrService { public async isNewChartsLibraryEnabled(): Promise { const legacyChartsLibrary = Boolean( - (await this.kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary')) && - (await this.kibanaServer.uiSettings.get('visualization:visualize:legacyPieChartsLibrary')) + await this.kibanaServer.uiSettings.get('visualization:visualize:legacyPieChartsLibrary') ) ?? true; const enabled = !legacyChartsLibrary; this.log.debug(`-- isNewChartsLibraryEnabled = ${enabled}`); @@ -78,143 +76,52 @@ export class VisualizeChartPageObject extends FtrService { return true; } - /** - * Helper method to get expected values that are slightly different - * between vislib and elastic-chart inplementations - * @param vislibValue value expected for vislib chart - * @param elasticChartsValue value expected for `@elastic/charts` chart - */ - public async getExpectedValue(vislibValue: T, elasticChartsValue: T): Promise { - if (await this.isNewLibraryChart(xyChartSelector)) { - return elasticChartsValue; - } - - return vislibValue; - } - - public async getYAxisTitle() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const xAxis = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return xAxis[0]?.title; - } - - const title = await this.find.byCssSelector('.y-axis-div .y-axis-title text'); - return await title.getVisibleText(); + public async getYAxisTitle(selector: string) { + const xAxis = (await this.getEsChartDebugState(selector))?.axes?.y ?? []; + return xAxis[0]?.title; } - public async getXAxisLabels() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const [xAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.x ?? []; - return xAxis?.labels; - } - - const xAxis = await this.find.byCssSelector('.visAxis--x.visAxis__column--bottom'); - const $ = await xAxis.parseDomContent(); - return $('.x > g > text') - .toArray() - .map((tick) => $(tick).text().trim()); + public async getXAxisLabels(selector: string) { + const [xAxis] = (await this.getEsChartDebugState(selector))?.axes?.x ?? []; + return xAxis?.labels; } - public async getYAxisLabels(nth = 0) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const yAxis = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return yAxis[nth]?.labels; - } - - const yAxis = await this.find.byCssSelector('.visAxis__column--y.visAxis__column--left'); - const $ = await yAxis.parseDomContent(); - return $('.y > g > text') - .toArray() - .map((tick) => $(tick).text().trim()); + public async getYAxisLabels(selector: string, nth = 0) { + const yAxis = (await this.getEsChartDebugState(selector))?.axes?.y ?? []; + return yAxis[nth]?.labels; } - public async getYAxisLabelsAsNumbers() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const [yAxis] = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return yAxis?.values; - } - - return (await this.getYAxisLabels()).map((label) => Number(label.replace(',', ''))); + public async getYAxisLabelsAsNumbers(selector: string) { + const [yAxis] = (await this.getEsChartDebugState(selector))?.axes?.y ?? []; + return yAxis?.values; } /** * Gets the chart data and scales it based on chart height and label. * @param dataLabel data-label value - * @param axis axis value, 'ValueAxis-1' by default + * @param selector chart selector * @param shouldContainXAxisData boolean value for mapping points, false by default * * Returns an array of height values */ public async getAreaChartData( dataLabel: string, - axis = 'ValueAxis-1', + selector: string, shouldContainXAxisData = false ) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - const points = areas.find(({ name }) => name === dataLabel)?.lines.y1.points ?? []; - return shouldContainXAxisData ? points.map(({ x, y }) => [x, y]) : points.map(({ y }) => y); - } - - const yAxisRatio = await this.getChartYAxisRatio(axis); - - const rectangle = await this.find.byCssSelector('rect.background'); - const yAxisHeight = Number(await rectangle.getAttribute('height')); - this.log.debug(`height --------- ${yAxisHeight}`); - - const path = await this.retry.try( - async () => - await this.find.byCssSelector( - `path[data-label="${dataLabel}"]`, - this.defaultFindTimeout * 2 - ) - ); - const data = await path.getAttribute('d'); - this.log.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - const tempArray = data - .replace('M ', '') - .replace('M', '') - .replace(/ L /g, 'L') - .replace(/ /g, ',') - .split('L'); - const chartSections = tempArray.length / 2; - const chartData = []; - for (let i = 0; i < chartSections; i++) { - chartData[i] = Math.round((yAxisHeight - Number(tempArray[i].split(',')[1])) * yAxisRatio); - this.log.debug('chartData[i] =' + chartData[i]); - } - return chartData; + const areas = (await this.getEsChartDebugState(selector))?.areas ?? []; + const points = areas.find(({ name }) => name === dataLabel)?.lines.y1.points ?? []; + return shouldContainXAxisData ? points.map(({ x, y }) => [x, y]) : points.map(({ y }) => y); } /** * Returns the paths that compose an area chart. * @param dataLabel data-label value */ - public async getAreaChartPaths(dataLabel: string) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - const path = areas.find(({ name }) => name === dataLabel)?.path ?? ''; - return path.split('L'); - } - - const path = await this.retry.try( - async () => - await this.find.byCssSelector( - `path[data-label="${dataLabel}"]`, - this.defaultFindTimeout * 2 - ) - ); - const data = await path.getAttribute('d'); - this.log.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - return data.split('L'); + public async getAreaChartPaths(dataLabel: string, selector: string) { + const areas = (await this.getEsChartDebugState(selector))?.areas ?? []; + const path = areas.find(({ name }) => name === dataLabel)?.path ?? ''; + return path.split('L'); } /** @@ -222,106 +129,38 @@ export class VisualizeChartPageObject extends FtrService { * @param dataLabel data-label value * @param axis axis value, 'ValueAxis-1' by default */ - public async getLineChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { - if (await this.isNewLibraryChart(xyChartSelector)) { - // For now lines are rendered as areas to enable stacking - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - const lines = areas.map(({ lines: { y1 }, name, color }) => ({ ...y1, name, color })); - const points = lines.find(({ name }) => name === dataLabel)?.points ?? []; - return points.map(({ y }) => y); - } - - // 1). get the range/pixel ratio - const yAxisRatio = await this.getChartYAxisRatio(axis); - // 2). find and save the y-axis pixel size (the chart height) - const rectangle = await this.find.byCssSelector('clipPath rect'); - const yAxisHeight = Number(await rectangle.getAttribute('height')); - // 3). get the visWrapper__chart elements - const chartTypes = await this.retry.try( - async () => - await this.find.allByCssSelector( - `.visWrapper__chart circle[data-label="${dataLabel}"][fill-opacity="1"]`, - this.defaultFindTimeout * 2 - ) - ); - // 4). for each chart element, find the green circle, then the cy position - const chartData = await Promise.all( - chartTypes.map(async (chart) => { - const cy = Number(await chart.getAttribute('cy')); - // the point_series_options test has data in the billions range and - // getting 11 digits of precision with these calculations is very hard - return Math.round(Number(((yAxisHeight - cy) * yAxisRatio).toPrecision(6))); - }) - ); - - return chartData; + public async getLineChartData(selector: string, dataLabel = 'Count') { + // For now lines are rendered as areas to enable stacking + const areas = (await this.getEsChartDebugState(selector))?.areas ?? []; + const lines = areas.map(({ lines: { y1 }, name, color }) => ({ ...y1, name, color })); + const points = lines.find(({ name }) => name === dataLabel)?.points ?? []; + return points.map(({ y }) => y); } /** * Returns bar chart data in pixels * @param dataLabel data-label value - * @param axis axis value, 'ValueAxis-1' by default */ - public async getBarChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { - if (await this.isNewLibraryChart(xyChartSelector)) { - const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; - const values = bars.find(({ name }) => name === dataLabel)?.bars ?? []; - return values.map(({ y }) => y); - } - - const yAxisRatio = await this.getChartYAxisRatio(axis); - const svg = await this.find.byCssSelector('div.chart'); - const $ = await svg.parseDomContent(); - const chartData = $(`g > g.series > rect[data-label="${dataLabel}"]`) - .toArray() - .map((chart) => { - const barHeight = Number($(chart).attr('height')); - return Math.round(barHeight * yAxisRatio); - }); - - return chartData; + public async getBarChartData(selector: string, dataLabel = 'Count') { + const bars = (await this.getEsChartDebugState(selector))?.bars ?? []; + const values = bars.find(({ name }) => name === dataLabel)?.bars ?? []; + return values.map(({ y }) => y); } - /** - * Returns the range/pixel ratio - * @param axis axis value, 'ValueAxis-1' by default - */ - private async getChartYAxisRatio(axis = 'ValueAxis-1') { - // 1). get the maximum chart Y-Axis marker value and Y position - const maxYAxisChartMarker = await this.retry.try( - async () => - await this.find.byCssSelector( - `div.visAxis__splitAxes--y > div > svg > g.${axis} > g:last-of-type.tick` - ) - ); - const maxYLabel = (await maxYAxisChartMarker.getVisibleText()).replace(/,/g, ''); - const maxYLabelYPosition = (await maxYAxisChartMarker.getPosition()).y; - this.log.debug(`maxYLabel = ${maxYLabel}, maxYLabelYPosition = ${maxYLabelYPosition}`); - - // 2). get the minimum chart Y-Axis marker value and Y position - const minYAxisChartMarker = await this.find.byCssSelector( - 'div.visAxis__column--y.visAxis__column--left > div > div > svg:nth-child(2) > g > g:nth-child(1).tick' - ); - const minYLabel = (await minYAxisChartMarker.getVisibleText()).replace(',', ''); - const minYLabelYPosition = (await minYAxisChartMarker.getPosition()).y; - return (Number(maxYLabel) - Number(minYLabel)) / (minYLabelYPosition - maxYLabelYPosition); - } - - public async toggleLegend(show = true) { - const isVisTypeXYChart = await this.isNewLibraryChart(xyChartSelector); + private async toggleLegend(force = false) { const isVisTypePieChart = await this.isNewLibraryChart(pieChartSelector); - const legendSelector = isVisTypeXYChart || isVisTypePieChart ? '.echLegend' : '.visLegend'; + const legendSelector = force || isVisTypePieChart ? '.echLegend' : '.visLegend'; await this.retry.try(async () => { const isVisible = await this.find.existsByCssSelector(legendSelector); - if ((show && !isVisible) || (!show && isVisible)) { + if (!isVisible) { await this.testSubjects.click('vislibToggleLegend'); } }); } - public async filterLegend(name: string) { - await this.toggleLegend(); + public async filterLegend(name: string, force = false) { + await this.toggleLegend(force); await this.testSubjects.click(`legend-${name}`); const filterIn = await this.testSubjects.find(`legend-${name}-filterIn`); await filterIn.click(); @@ -336,12 +175,12 @@ export class VisualizeChartPageObject extends FtrService { await this.testSubjects.click(`visColorPickerColor-${color}`); } - public async doesSelectedLegendColorExist(color: string) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const items = (await this.getEsChartDebugState(xyChartSelector))?.legend?.items ?? []; - return items.some(({ color: c }) => c === color); - } + public async doesSelectedLegendColorExistForXY(color: string, selector: string) { + const items = (await this.getEsChartDebugState(selector))?.legend?.items ?? []; + return items.some(({ color: c }) => c === color); + } + public async doesSelectedLegendColorExistForPie(color: string) { if (await this.isNewLibraryChart(pieChartSelector)) { const slices = (await this.getEsChartDebugState(pieChartSelector))?.partition?.[0]?.partitions ?? []; @@ -355,7 +194,7 @@ export class VisualizeChartPageObject extends FtrService { } public async expectError() { - if (!this.isNewLibraryChart(xyChartSelector)) { + if (!this.isNewLibraryChart(pieChartSelector)) { await this.testSubjects.existOrFail('vislibVisualizeError'); } } @@ -395,19 +234,15 @@ export class VisualizeChartPageObject extends FtrService { public async waitForVisualization() { await this.waitForVisualizationRenderingStabilized(); + } - if (!(await this.isNewLibraryChart(xyChartSelector))) { - await this.find.byCssSelector('.visualization'); - } + public async getLegendEntriesXYCharts(selector: string) { + const items = (await this.getEsChartDebugState(selector))?.legend?.items ?? []; + return items.map(({ name }) => name); } public async getLegendEntries() { - const isVisTypeXYChart = await this.isNewLibraryChart(xyChartSelector); const isVisTypePieChart = await this.isNewLibraryChart(pieChartSelector); - if (isVisTypeXYChart) { - const items = (await this.getEsChartDebugState(xyChartSelector))?.legend?.items ?? []; - return items.map(({ name }) => name); - } if (isVisTypePieChart) { const slices = @@ -424,13 +259,29 @@ export class VisualizeChartPageObject extends FtrService { ); } - public async openLegendOptionColors(name: string, chartSelector: string) { + public async openLegendOptionColorsForXY(name: string, chartSelector: string) { + await this.waitForVisualizationRenderingStabilized(); + await this.retry.try(async () => { + const chart = await this.find.byCssSelector(chartSelector); + const legendItemColor = await chart.findByCssSelector( + `[data-ech-series-name="${name}"] .echLegendItem__color` + ); + legendItemColor.click(); + + await this.waitForVisualizationRenderingStabilized(); + // arbitrary color chosen, any available would do + const arbitraryColor = '#d36086'; + const isOpen = await this.doesLegendColorChoiceExist(arbitraryColor); + if (!isOpen) { + throw new Error('legend color selector not open'); + } + }); + } + + public async openLegendOptionColorsForPie(name: string, chartSelector: string) { await this.waitForVisualizationRenderingStabilized(); await this.retry.try(async () => { - if ( - (await this.isNewLibraryChart(xyChartSelector)) || - (await this.isNewLibraryChart(pieChartSelector)) - ) { + if (await this.isNewLibraryChart(pieChartSelector)) { const chart = await this.find.byCssSelector(chartSelector); const legendItemColor = await chart.findByCssSelector( `[data-ech-series-name="${name}"] .echLegendItem__color` @@ -444,9 +295,7 @@ export class VisualizeChartPageObject extends FtrService { await this.waitForVisualizationRenderingStabilized(); // arbitrary color chosen, any available would do - const arbitraryColor = (await this.isNewLibraryChart(xyChartSelector)) - ? '#d36086' - : '#EF843C'; + const arbitraryColor = '#EF843C'; const isOpen = await this.doesLegendColorChoiceExist(arbitraryColor); if (!isOpen) { throw new Error('legend color selector not open'); @@ -561,13 +410,12 @@ export class VisualizeChartPageObject extends FtrService { return values.filter((item) => item.length > 0); } - public async getAxesCountByPosition(axesPosition: typeof Position[keyof typeof Position]) { - if (await this.isNewLibraryChart(xyChartSelector)) { - const yAxes = (await this.getEsChartDebugState(xyChartSelector))?.axes?.y ?? []; - return yAxes.filter(({ position }) => position === axesPosition).length; - } - const axes = await this.find.allByCssSelector(`.visAxis__column--${axesPosition} g.axis`); - return axes.length; + public async getAxesCountByPosition( + axesPosition: typeof Position[keyof typeof Position], + selector: string + ) { + const yAxes = (await this.getEsChartDebugState(selector))?.axes?.y ?? []; + return yAxes.filter(({ position }) => position === axesPosition).length; } public async clickOnGaugeByLabel(label: string) { @@ -581,62 +429,26 @@ export class VisualizeChartPageObject extends FtrService { await gauge.clickMouseButton({ xOffset: 0, yOffset }); } - public async getAreaSeriesCount() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const areas = (await this.getEsChartDebugState(xyChartSelector))?.areas ?? []; - return areas.filter((area) => area.lines.y1.visible).length; - } - - const series = await this.find.allByCssSelector('.points.area'); - return series.length; + public async getAreaSeriesCount(selector: string) { + const areas = (await this.getEsChartDebugState(selector))?.areas ?? []; + return areas.filter((area) => area.lines.y1.visible).length; } - public async getHistogramSeriesCount() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const bars = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; - return bars.filter(({ visible }) => visible).length; - } - - const series = await this.find.allByCssSelector('.series.histogram'); - return series.length; + public async getHistogramSeriesCount(selector: string) { + const bars = (await this.getEsChartDebugState(selector))?.bars ?? []; + return bars.filter(({ visible }) => visible).length; } - public async getGridLines(): Promise> { - if (await this.isNewLibraryChart(xyChartSelector)) { - const { x, y } = (await this.getEsChartDebugState(xyChartSelector))?.axes ?? { - x: [], - y: [], - }; - return [...x, ...y].flatMap(({ gridlines }) => gridlines); - } - - const grid = await this.find.byCssSelector('g.grid'); - const $ = await grid.parseDomContent(); - return $('path') - .toArray() - .map((line) => { - const dAttribute = $(line).attr('d'); - const firstPoint = dAttribute.split('L')[0].replace('M', '').split(','); - return { - x: parseFloat(firstPoint[0]), - y: parseFloat(firstPoint[1]), - }; - }); + public async getGridLines(selector: string): Promise> { + const { x, y } = (await this.getEsChartDebugState(selector))?.axes ?? { + x: [], + y: [], + }; + return [...x, ...y].flatMap(({ gridlines }) => gridlines); } - public async getChartValues() { - if (await this.isNewLibraryChart(xyChartSelector)) { - const barSeries = (await this.getEsChartDebugState(xyChartSelector))?.bars ?? []; - return barSeries.filter(({ visible }) => visible).flatMap((bars) => bars.labels); - } - - const elements = await this.find.allByCssSelector('.series.histogram text'); - const values = await Promise.all( - elements.map(async (element) => { - const text = await element.getVisibleText(); - return text; - }) - ); - return values; + public async getChartValues(selector: string) { + const barSeries = (await this.getEsChartDebugState(selector))?.bars ?? []; + return barSeries.filter(({ visible }) => visible).flatMap((bars) => bars.labels); } } diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 90fc320da3cda..50b275d04eabb 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -63,8 +63,8 @@ export class VisualizeEditorPageObject extends FtrService { await this.visChart.waitForVisualizationRenderingStabilized(); } - public async clickGo() { - if (await this.visChart.isNewChartsLibraryEnabled()) { + public async clickGo(isNewChartLibrary = false) { + if ((await this.visChart.isNewChartsLibraryEnabled()) || isNewChartLibrary) { await this.elasticChart.setNewChartUiDebugFlag(); } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 6c0c7463e4a5f..f851126a0e633 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -56,7 +56,6 @@ export class VisualizePageObject extends FtrService { await this.kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', [FORMATS_UI_SETTINGS.FORMAT_BYTES_DEFAULT_PATTERN]: '0,0.[000]b', - 'visualization:visualize:legacyChartsLibrary': !isNewLibrary, 'visualization:visualize:legacyPieChartsLibrary': !isNewLibrary, }); } @@ -113,8 +112,8 @@ export class VisualizePageObject extends FtrService { }); } - public async clickRefresh() { - if (await this.visChart.isNewChartsLibraryEnabled()) { + public async clickRefresh(isNewChartLibrary = false) { + if ((await this.visChart.isNewChartsLibraryEnabled()) || isNewChartLibrary) { await this.elasticChart.setNewChartUiDebugFlag(); } await this.queryBar.clickQuerySubmitButton(); diff --git a/test/functional/screenshots/baseline/area_chart.png b/test/functional/screenshots/baseline/area_chart.png index a2dd5e3b3465ff44d081e3489127cd19ab079bef..28ce63c1bc41b49dcac92377103aecc11c889edf 100644 GIT binary patch literal 85012 zcmd43Wmr{hw?C?)0wN_yBPAs*NQctWh$1P{ph!!H3c{ivxT7Elo>X^@g` z_>Z~7XYYN^e$REzxATE;vDTd99`_i(827vkP*ah^y+Co{)TvXrx84nOoo(@$+cK4yh>>f zm(GwUzp`JQu@7ohHTfxY?y|c2t((NZ&b7cuRnZ9AU&#yVUErJS$A5ZeTb63_;6D7>(@mb*qg>yCS%6ujdC*W4oX8!p zJvR2@`Xdv(_P(r+w(jN5%EO*pZLNz+PO~ajBWqW9@HyPh`j6CqFB+gdNrRm6tLIdB zm_*G6s!yJ9tUUSA8Ijc?FE9UaE5G7%gmi|wqcC;5u1$@CM&*+&cB2_C7Xtr=df(N7 zek~HQs<_4OWHOO03JP653YnaKyW*wf0rMK0T}`rrRf*lR(Ytm6MCm+6}d@7HTriAcl_SeE*RiXGdGliaPn zWoPt|5MRG}GyW_0NLpW?dYx3XHS>1A%VGdyRP1_rWTyOv%hTGyGFlkw&2S03+rAEgAokd_oJ zN$tU~Bmvt~Zq-*98A^Cpp17qduh^Kie<2?hHKtIIH8X$UeTWNNMS}c!sN80D?D*&f zHc`&+Uyp4*e;gQmKf)QccJo=0am(s}tF!&@pSNbZt9>df#+u+}4w+tudwqol278+e z9qIlwS^93eF`TZ!@M`*hjKg$$S=2g-FQgW(lcQNMvA+^T#y2N=>C&av*2UmE53W4y zZ3w%*J~Ph}tAFI~G_^eSiLgQFjj4MLPLk1WbQiYz|3nd>u|2 z8S~ALw`aL!Y&p3n+M7bz$cSEFWu>0drlzZG;2=J@v%BhOj}pq51Ib_1+S;k0D_&9Y z0;+OQ1{s2jP9BW=mCJtERR`_48}lPL6+=zo|9K+=WHZ;Ozr7Dsyu0 zzgwx`>iSFuU!wj0Yf1b;4pd~$+qVhXnK{q#Wax?AL_H2S1w>08;RbfhOKTr|zZAo+ zFFWStN#=1x8&jy&*2f&HVPL(?si4?F#$zOC03}s2+sQCvvpv#-N#iAb?69}mfG>VQ z?7=VH|NYZrqy`?hS;&m`O53PZ|CcKr?USz6`_uFm+R$9sS{(nl5+tY|M5Fr&nrLpQ z5Z|YVrg7~uoA%z6y%Cd2LNjx!NBeZFfkLWywN~|vPz;Cnh3WFa5;Xvcimnuy&eOH2 zcf48U)_*euxo%aG&W+X5Ncmr!Np?PJTXG&&_B8NVWlVdBn(t>KD%mMwWY_)>Tywa` zy>oC2*{dv7PUe|$Ig<572d>Yx-V%Oz3yhuk?U|3gqD3mZ{)+1>rNHxswPO|B_?MZkgEEb2W>X}XLNXMekx?s%kuzplb z43ANxZ4DHeSx>xNXM4_lyKS*;Lq!#{Pdk*a-@Y|!FD0)!J|cM+> zxnm{zamF%OmZ8PCWYEQa-gwSY7%rsp;DI>L%F%)tUsq?~v?5A2BWZQv0hMqgw)TFX zaGoZc!)&K)&Op(o559KAvT3$TyziYWSg$Vbb-5nQRGn6e7Y+aNg*b{elKDD7=64N^ zxa+R;JjZ*p=P#aTkmEJ{gsbN@$Y^3#KAUJ$GyQeo!R&%fOBp169h<;@wo`{q^VurEVC2jrU*HR`a(%=mhewy zyV2zLpiu61xsH%+T~ASF_V>>Vn=Iv1CI?4%i~@ta>!$6;Z3WdnJY5RBqj!M89P~f# zd=d}+pBA+^gyeoTGW^m?wYRa{C35T%J8R$)PA;I#z}V#1F-=dFH&u^0<3PXYB!Y>z zgYEwqC5u0~z5k|X+Me*D=Gsq;bxQ^QMBLiisDuQa^j;P(Z&v2^fBzO;8!_6pN{YZ+1_>%>n(e-%2z7V1u`#Cj^RIdX%&D@v$AGR{- z^f*cDA2Q;LbEjrh+D+WWqkcQxA12ZGsLWPpAcd-WTAX&@ny)4J|{|0-aaT{}z z)H8$<{{2RRA)x_iR8aFv6(Ou1@oiCPgDL!s!k^)CG_%RxRY77mqVMIVPJ?RY29=4+ zV>S2iTTN-oDiM>Npj(`LX6W7#_uhpot{ZlvyDc996|~Ftw7ZwQ@iJs*KF6KM6%GT= zjih8&$u3yt25~+ieQ3CGS#)h~vT8ld8M+PWzoemKD%p_ETZIGbv$etFKS7Zt3cu_t zz7qwwb!pO8@V86#uxF3wu(%Cg`)@*_q;FX`x%b7I!eLh~U&hofb|g3O+`Iv0<+sx? z$L2EC+u}M)vtp#GZ2I+=xlqNiP%uTj&!rIAH?IX-fdzMd91eESU5S&OuA*L!WzcwCO7SYP3-oog!syfj0?s?MOMaD?;pKPlkx}A zSC|o8xy98eNQEc*fXjKzfu^D-QOej}kjka%!E(*4xOJ)k=JbEg=81fWv1!m<5KO?e z$)B{~uPO_j>~%Q9q*_B5+13bbrmm~oq1>Ny-D`N$YP2f4j4R*#`)4|KFGoV$%>khb z`{l~nci=`WQp0amSnI!8$)49Bmsob=w;4qUhSzG2fJEX0s@Z9OSZHIZKM{CF-pR>a z*r|0wdW6sO+GLA<&rFvt;F+5rF7>AgInp$;i|12x*DqGPC3j7#vYQ_6-LKq;2=A<2 zu{K)%;J~S3PQi8H$uVw=2%M{3@y)%%&MW$v6;C>X9uL^{$}q?m4O*ZQvsFzG-Y*PR zxw1Vr(CK-XCZ%;SM{l(JahtPmsC>GqY~lS}I$`E_D|Dr+Z@7sa_RP!q<7u0in7q13 z_BnO3a-q9YFxj%|_jZ|Y!wtUjg+j`PrS%!xcf}E=9jWv@JmD>*5}p6h%@R2bBsYAFeX)TJWqt$9|oAau) z1P{k5-2d80yGQG>F}+b2QO9M!)8w?)7uWUN+T?>}iT1Z?9)igK*H9{VJ=-zxPIh36 zuBTn8t_XnkAY||>`B_u@$G-wF@!i0f@Yc@Pykc;`{nvO(JM=T0$O=hRq{@HUSFea( zi%iz}|8l6R3SamVBCh;&kv z`sZAA&O;sxWQ`opPa}6mX|@w>6lLU{nr`kdd}WikJavE3c(a1?_rTfj#oo+962yoWO4Y6RKFwR+bf?}87sABNt3^U#HE-fcTi}n%<*+x=idy>WXmCU zOOIAi_^eiyb_t1t<L>$t$gk0q9s>p2YGt<3_UhlMfKjGl*Tu({ko+xAQh! zyV3jCkT*-JXH@`Ekw??D<}gYr2OR%~;p3kfRhwH9JByRbU$&>#%(ZHdo<>Oo*Cee@ zHhh~BTVE$HRxol{ZM$YTGNwbx=R&$CENQ4xn8d!mXpSH+|AK!4$*hE zJP43Z1abb%wiGbpqj0rbV)LjJ33FeG_rIf|(SZu`?$|5KB|pq5VyPuhYTyu19WPdN zc%3WKSv=kI`v;{>h90*ER)4lqofUuD6sboO1`g@us)yWAIp@sbQxdTc&NAfQlgjMg zYlKllD^+2X%zAPkH_EBH@=LNEzQv1ybx`=_zFAfgCwL^R*UcA2*%CIL^|bK1V=HY! zN4i|fO(E~kH@yN+wuqDK9X@0Kgscz&Cv&Y4jpvY~9M);dz>1=b(o?FR<>VdFAK3e4y##$wH^YPbiGkNagPy2LHqO=be#|D#Qdp?xqa#I8k zS3D$CW!DDl%aqZXbR+hob*gf$SM~0Id!p-fw7`6CHJgGzUVLL|k3~z|0+Zc1Dosyf ziz_obTWw6+d~3=Aj9Ir}1|^SqQg8A;jYrfBKrdWL;Gu8xp+lHBn{ zm85thH+I#(gD}19w?77pN>?}emQ_@b0uF&>-LGn!60?=_Yit+Zyv2z$P;ZSn{1}(K zFPJ7zMrZ&9p!!?whMceqOQYKg6UCzm-vxP<@`xlW)8>uaRxYy!EP%v=$s$Dz+YV7( zy5c4~pX;eQC~d4Bb}z;QuwG?kyl%WFeN<+N!^4z8=xs?}R_0G8ZpFPm(@j59SkZOW z?BG35)j=NxI||D?-DU{1B?w;BmWlbevhW60DGlJCc z?ju_v%AksjgMtPEK!8x@DdTE$qN9XUx?3w(nT=1;dwJ-6NHiW)*Q|Tb8QV*+^dpNX zGCKOUr6oB}gI{t_Og@dBO6{n}PH&46nSh@$>!9~D2?LSmd$8|3K_sIcYdM;$-zPf+ zmr4zITqkgoyxd4bnS=)0Lnd3+7E;9GpkgkSNf*>S;%kYFFq-^XuRw(-(q7+A>*Ri| zi_v*@Rx2`}rBXy9$9bhh@Ctf`+7Pqu_KIXBlZgNNNOf!B_m*Ea0zc8qRk6Q*o>sbQ z-A_U7dgL$*f5uB6e@T}Y;=(%pgcEnC3$b5i#%qv|7?LSKagV+V3uzicY9J>&eZolC z`4?(p)62hq&@V1c>b%_{uEzVhPlly7V|YMX#4d$dWw*> zdd#89>2E`>>t(eLeCOP2%m&xYSy%^q4r)j0U){pkVNDixdbquO<~%ONvtoB=acU8h zz9jHIxLE+EjjSC8L^p5q=0rsDoC>;?0hqm^yx=#1AIl(J)ltYIZD;9Z^x^w;b%5Fc zf-~&rL-vvqs0mZ|a+2$dHZ1!?{B+6>$gi>a+|@kqIycVze&^G8<9mG-Jx*p-PIo^X zKiq6_<$e~03Ul0SS90k^)vD4rk zDlY!`Myz(lX!qwdAD8jU95OzOQ@D1!rb6ucM|_wESzvWUD-XyEc9t0VVn?gH)7Ks{ zMq{$gk1VkN!Im6RSd4g%nH=idC!&os!5bnx@g$^`rtYoy#Pp4CIxSUXfbG7kYu-%d zANhUO(~A9vUxdkLg4O9jJgM&w_89A>zJ4+1)!Fg%Pvi6LwLpevS>#ioNk;9&|Prf+!mr_=4eYmX@&llRkMcMX#q0q3cFZy7-z3A@!`~4;6 z_%p-87)_wz8=ekudp18g>vIc{TBEgH4B4O8sd>y(cOTCjFX1UMF-Ww$Wbj*E8kIb9 z{Q4Bq;&uG718L8$EU3Y`k$rXw1++9FEO^66@lv`A$R_E+!MJ_4*B(K8W52f?tO+T~ z1awi_Dm&pJ%uAn=Kf2Eky~fK|jbBZzAK=P&Eb}OX@XCS!5t1m4ub--BkvX+9+%Ld{V|*N%19#+Kb{RMiw-ENpINx>9TY{MASpsfJ<4P5kZ)4s`YRU`q9gGUDT4y zFgFj|V*(;*Sfo)J@d8sB9|~fKGorU{b4Dqacu7HDN>Z_sU9_9GDU_}>nRaerbDQt2ElKy!Y13mX$mzxG& z=jLW;_$w7qd13=D9(gZRuc&M4KG$`yeX=EAb8&Gxd1A>?rMvFUAR+yz>kDz>#NHP% z4>M~x+qA11oSdRA69oDqQbO?Z6VAZ^d%tSlv2)uQzVu(Q?D)Vc;#XApSj2%e;RcqW zP*A19Ixl6lFBqGc^Ps2GQsDOr7Y@rlPo03fSk&S+?`%wfp2Jrg*VX9?wQtqM&n1qm zuR2)AOLG&`7yhJ#3OdL+2$bX}v5~8MJyOZSSwfvftA--Tf6hE51sD*T$w&r2q7DK* zvFxs0kojaaQj3#i{)k^&Qy`FeP715{V59N0Pwz7o%rbvG!Z*26Jc-I4n_c4F;w6~E z$)Qg3){h=N!g~33r%3c>OWqbsb@yU@O5rv*@`+<#dc&AyS8~&5xy;Mbn1Ld_&nF78 zVev>>e8h?Grzd9!HKc^BX+(c6HyJ zjsG$r-u5!Wpa`FCA{kcGR$Mx5@!@KP+@QJ+RAqWT<43jbch0i#y3FcXPb|8G)aQb> z@2{Y;Ce2@+BXZeU&6$6yi62VL7#Mm?e9mzWZlZCI<&s&oGm!{r>w&Y}3(CIu z1FwQU$t-nG%R}O1?qLG-Y&_|wrkjWt(0Vb!Cn89Z*c;eoG5#}+Amvq9=r>1S!FwK> zF=4e#&$=wq6zxei@5y{Z?w=y4c2`ewKiHA41T_B5S(SK?`x_XnQBFmCPUIsU}U&=&VNI^9EoZ9t2wJ;EDEm+}{?mkw-e57$eeW|V-fUhSme%z4Wh zeB1n{BDX!ezEwO6a4@t;TTFyLi)Y^l!pQh3SwKyYwi04?b@4N=UcdKk@W&?#k==!+ zu+0{lbJH=mcV1!wR?_dq*UBS_xI|=^RW?54`2ZT!pf5w06!!b>6!sp=>))?B7ws-{ z;q6i4xG6JrB`I``OD*CpPRm<&J)gjhp_LY0_w`^Ykj8B3Z~EJ;FD3N40g0lnuntlw zvs{wJ>aDuic-lO{r>5GwE4?KZ4%+P8VpUAaZ2bG^N~qUqbOPz>>+X415u0wesTxNJp~ z3jDUn!f}&lL77T9ri^1ulVlusuP%hZ?hPN+O}oc=))@Z?NYTM6Z{~YX4FQ}xuL{Vu7k^-VEBuUk4iN@Wc43IY%+~~n^!(u{rCsfe zQQ|CK-&gpc*?_3dtn_n^vOs8a)}T6QJ}km8$#8DyFev9~7k*;T#;`I>bz!X^>36@r zbY)pN2ZZHo!2uGtt&n93pinyFbFT9J(41?RWRnv1wrJV|bZU$)GfRP3C~#lSu{lmF zy$VzJiMJAAlkjD{8+h-J;?ybAb<^weaqU5zC$(_W%w4?zJiqd#h9VQDN_DV_z9_lT zfAA3^m;|HI0)odiZ%ipEAzAHeCvYCr;yc(tS!XgoXab^*RkhLeoyTVmQd#`*2(|HwuqeIX(Vdy($wr%}yx>9Lgb0yifkzFB)CU9>k%^o9pY5&l)dC$3} zIR&Zrd?J{irl;-`_lac@Nj64S)rk{3r~6F45z-6leQtmB>iNm;R8waGSw;-?tHqNDAn7=_)r(FCkYJ*v25P? z?up)_3Di)OLAk^jsLNX_pE8|(R;hK7qCLMdY!Wy6-A5xXjAZjW2GDw0uRi&pBx-(L zbF=n%S)9Plqgc!Fb9x15_h5oeUR5cb?g-E*&)_44>9_oKmq*hSOrtJh4Ssk=gMsEK zX?mDpi4PS;a_SeuVCeh7XyV3_L=-;14XxQ`iMP4)1M!7#1)nUq?LnN17MYwA=!pxy z?(j`-`GY6_;ojzq!OD}F==-CYa@z$g)WRm0`o%M-jSCVRL9y8%ey{x}Awkm9Jb5rO z#sXweFpYqpUmpNX`n~nDo>2FIC_q~j?QaIvtLmXH1x8=tH<^|@h^mxOM~=$h(~o=U z+EW4pi?8ki@4y2?dI-~R%*FH(2Hj%FR@D)ZUU2ML){pw?E7bQqf!ld$^tP08k}Y*x zn`T60erKF`0Pe~GPSd;+!$${Zrzx+{V%HA;i!{P<#;%hgxSjE0k-&wSnbKxTsjapA zPw1LW_$IhGGRn6I8vJq1Mu)W<4>rr=8nzgr(H-el;WA4tTP*o(uSY_?@*+{gbt`~5V^Xbp-mkjysrM?gZl&FUme)uLW{GvOTzX9F*ZU#aj2+On-%QvPmb-iA zK~#sfpgVY}q|YOYvwZ7-3bY7Q@@a47Q{L~X5P|IS53IBg;9Uxg4=(iaiX@|lW`gR{ zxG-ne0q%Y04+J0|`jZrlUXXh$KZBj@XDA~A8b=y{v+Fs5oW@-}rCAkf^{UYoTj3!B zUKolL)EZtO)fWujS{fR6%(i2=|I!J-W!qxF3I9A%b|L)tfo}y=utgETxamUA_|4nd#`oc|RSaiD->Zx(Y-F z3o8>FZgl*V*{gM>VI4}xy1~$Xg7h1__d(n-j!GD);@k(`r^KW>jtIph5f zrjcw&)juzTuH?YPVB9v2bfD&wCk{#4Ssyyi!=>m&wvAW)^J;hlUkQ1=>*>m!h`QRa%kfk60WAYhJIOA}c+_KG# zRk$bms2p#1OP7kTcMB%Q)^KI!F)4X+mNZkFZ=}<~Q7LICS@)d814ZR091NUf?VAJo zQl1U|$vq4~sZ+}$HW@XE*(0rG1IkA2nRal@Dc`K`jjI?_Fol0VGz%UNN)dDCp8ZD7 z0)m~g;G^F>uQdbN#l9jblI^1>S9o5gte}MUI}Lb`ZJZxoC*KviAU39MkTxswi*3V0 zPU9DprQ5c6kYAt7G3A7}@8d@SvUKXa2nW|ABcdV~k?J8wQ;Q+-1HG~POS4U%oabZu z!bQ5PPSZ`)|AEfmIVz<8#81u4wjSEyCwhpy7o5~9=(-XBmw*`*2yI*S=E@DFu57xR z!XG_BFINy^a!zu^ps(3^oBRIpw#oypwL7FC_A+L2%YqrC^8vT{VpJu%Oq<$op4Ms154YMc%4Zs2-G6^eZ@cA9J@mnaDaop%vK*_t6AUT_eHUI^FYio;xM1Uzt z%9}xyh7@p_>Xuj-^L~n)#?ESs{kH<|XgFX$wc2mi9Tyz&aoxcy5G_F>Kze}yi-3hm zTVTst$>Ja`1QpW_8Vy*N`BNrudXygqRxbrO`_Sukn~%LhLC`1w&(i(EIWMKRH^ipS zN$yHFk;CCOg-s&1b{5wV4QEmJ)lrDOIJg9K<@($cVQ)nKam0fY0{l+Ga)!PFghRQ+ z0#yNSF(p3-%ZAlUmZynBW9^FB%MIgULx6w2D0HlMyj~G7cKnDj_>??%6R{7yeJ8et z$dp${4bed;^98X)wLQU)X^dpMl>muqpo<74`S6i>lTH=_AWKC02bVazLML$d2+|o5 z2gW|yY@W3XdXFGvP3j)&_y?WRvqTcHf7}CP71Ra6DobtpKVa;XgfXhAM+hYwF_GE3 z4mAu{VfYS)Q>3?^EBueH#{y^3+s6(zVRKX&E_;BVjHmj!C865Jf&Pw?Cb24l?T=tO zr$A#aP0W?`R95*BE7dj zQ?H%bpG)b(rqj9Ltt^DdcA%0puusV~mgm5a2Ko5EIM9O>fwT9;!y+U_v?V_Ar9j^{ z{6z+ZDDiG0ej`5AEGGz>|U*+cY}1L2;^0&f*U*FQmDVTC{X5L>cTTBS?HlfrtzhsG{7xZ!}$ zmx4j*$D&JD0ZKP3h$FK3U0Vf4mTICk;@L>elN5pCY+n0z?K{&qwmB(=NjTUwSCTnm zvye#9i`@}f&j}^9O-ITYY>;HB2|#fc9LDSkYYp|0A%3$qoq$fdpBElHO#cdH2gSw` zf7{yY%}VZ;hWicy%!#b$?QST5B~gcwL<{A{b4oguYeBare=LSW-G^SE-xym}w8UnL zQp=>E(x*JY}kVSx^kVjcrlKBoo_&qzxNs%LyGTD^pLHmQgkAKh7T9k*=as zn&Z2283+IX1swp`{|S6d=nS;^cP zHH^DRmc3HI$-)8Gha4w*2{jPV&zu7*BBQ66noOppv*PkfRqfU9K7BSQlKKh`+#m)X z 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

QA> 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 literal 129876 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%0Dfqf4XY$R&T$X9lYXwikfXYWzUaoo+jm1F^pO7Y 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 MboFyt=akR{0EeZ<7XSbN diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 5724ad7ebc783..241bb838d7a01 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -5994,9 +5994,6 @@ "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", "visTypeVislib.vislib.tooltip.valueLabel": "値", - "visTypeXy.advancedSettings.visualization.legacyChartsLibrary.deprecation": "Visualizeのエリアグラフ、折れ線グラフ、棒グラフのレガシーグラフライブラリは廃止予定であり、8.0以降ではサポートされません。", - "visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description": "Visualizeでエリア、折れ線、棒グラフのレガシーグラフライブラリを有効にします。", - "visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name": "XY軸レガシーグラフライブラリ", "visTypeXy.aggResponse.allDocsTitle": "すべてのドキュメント", "visTypeXy.area.areaDescription": "軸と線の間のデータを強調します。", "visTypeXy.area.areaTitle": "エリア", @@ -6029,7 +6026,6 @@ "visTypeXy.controls.pointSeries.gridAxis.dontShowLabel": "非表示", "visTypeXy.controls.pointSeries.gridAxis.gridText": "グリッド", "visTypeXy.controls.pointSeries.gridAxis.xAxisLinesLabel": "X 軸線を表示", - "visTypeXy.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "ヒストグラムに X 軸線は表示できません。", "visTypeXy.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 軸線を表示", "visTypeXy.controls.pointSeries.series.chartTypeLabel": "チャートタイプ", "visTypeXy.controls.pointSeries.series.circlesRadius": "点のサイズ", @@ -6255,10 +6251,6 @@ "visualize.editor.defaultEditBreadcrumbText": "ビジュアライゼーションを編集", "visualize.experimentalVisInfoText": "このビジュアライゼーションはまだ実験段階であり、オフィシャルGA機能のサポートSLAが適用されません。フィードバックがある場合は、{githubLink}で問題を報告してください。", "visualize.helpMenu.appName": "Visualizeライブラリ", - "visualize.legacyCharts.conditionalMessage.advanced settings link": "高度な設定", - "visualize.legacyCharts.conditionalMessage.newLibrary": "{link}で新しいライブラリに切り替える", - "visualize.legacyCharts.conditionalMessage.noPermissions": "新しいライブラリに切り替えるには、システム管理者に連絡してください。", - "visualize.legacyCharts.notificationMessage": "レガシーグラフライブラリを使用しています。これは7.16で削除されます。{conditionalMessage}", "visualize.linkedToSearch.unlinkSuccessNotificationText": "保存された検索「{searchTitle}」からリンクが解除されました", "visualize.listing.betaTitle": "ベータ", "visualize.listing.betaTooltip": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャルGA機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャルGA機能のSLAが適用されません", @@ -17427,7 +17419,7 @@ "xpack.ml.ruleEditor.scopeSection.noPermissionToViewFilterListsTitle": "フィルターリストを表示するパーミッションがありません", "xpack.ml.ruleEditor.scopeSection.scopeTitle": "範囲", "xpack.ml.ruleEditor.selectRuleAction.createRuleLinkText": "ルールを作成", - "xpack.ml.ruleEditor.selectRuleAction.orText": "OR ", + "xpack.ml.ruleEditor.selectRuleAction.orText": "OR ", "xpack.ml.ruleEditor.typicalAppliesTypeText": "通常", "xpack.ml.sampleDataLinkLabel": "ML ジョブ", "xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "異常検知", @@ -23214,9 +23206,9 @@ "xpack.securitySolution.open.timeline.showingLabel": "表示中:", "xpack.securitySolution.open.timeline.singleTemplateLabel": "テンプレート", "xpack.securitySolution.open.timeline.singleTimelineLabel": "タイムライン", - "xpack.securitySolution.open.timeline.successfullyDeletedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}の削除が正常に完了しました", + "xpack.securitySolution.open.timeline.successfullyDeletedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}の削除が正常に完了しました", "xpack.securitySolution.open.timeline.successfullyDeletedTimelineTemplatesTitle": "{totalTimelineTemplates, plural, =0 {すべてのタイムライン} other {{totalTimelineTemplates}個のタイムラインテンプレート}}が正常に削除されました", - "xpack.securitySolution.open.timeline.successfullyExportedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}のエクスポートが正常に完了しました", + "xpack.securitySolution.open.timeline.successfullyExportedTimelinesTitle": "{totalTimelines, plural, =0 {すべてのタイムライン} other {{totalTimelines} 個のタイムライン}}のエクスポートが正常に完了しました", "xpack.securitySolution.open.timeline.successfullyExportedTimelineTemplatesTitle": "{totalTimelineTemplates, plural, =0 {すべてのタイムライン} other {{totalTimelineTemplates} タイムラインテンプレート}}が正常にエクスポートされました", "xpack.securitySolution.open.timeline.timelineNameTableHeader": "タイムライン名", "xpack.securitySolution.open.timeline.timelineTemplateNameTableHeader": "テンプレート名", @@ -27190,4 +27182,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f54e290cf8bb4..1f7f7a18939ac 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6041,9 +6041,6 @@ "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}, 切换选项", "visTypeVislib.vislib.tooltip.fieldLabel": "字段", "visTypeVislib.vislib.tooltip.valueLabel": "值", - "visTypeXy.advancedSettings.visualization.legacyChartsLibrary.deprecation": "在 Visualize 中面积图、折线图和条形图的旧版图表库已弃用,自 7.16 起将不受支持。", - "visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description": "在 Visualize 中启用面积图、折线图和条形图的旧版图表库。", - "visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name": "XY 轴旧版图表库", "visTypeXy.aggResponse.allDocsTitle": "所有文档", "visTypeXy.area.areaDescription": "突出轴与线之间的数据。", "visTypeXy.area.areaTitle": "面积图", @@ -6076,7 +6073,6 @@ "visTypeXy.controls.pointSeries.gridAxis.dontShowLabel": "不显示", "visTypeXy.controls.pointSeries.gridAxis.gridText": "网格", "visTypeXy.controls.pointSeries.gridAxis.xAxisLinesLabel": "显示 X 轴线", - "visTypeXy.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "直方图的 X 轴线无法显示。", "visTypeXy.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 轴线", "visTypeXy.controls.pointSeries.series.chartTypeLabel": "图表类型", "visTypeXy.controls.pointSeries.series.circlesRadius": "点大小", @@ -6303,10 +6299,6 @@ "visualize.editor.defaultEditBreadcrumbText": "编辑可视化", "visualize.experimentalVisInfoText": "此可视化为试验性功能,不受正式发行版功能支持 SLA 的约束。如欲提供反馈,请在 {githubLink} 中创建问题。", "visualize.helpMenu.appName": "Visualize 库", - "visualize.legacyCharts.conditionalMessage.advanced settings link": "免费的 API 密钥。", - "visualize.legacyCharts.conditionalMessage.newLibrary": "切换到{link}中的新库", - "visualize.legacyCharts.conditionalMessage.noPermissions": "请联系您的系统管理员以切换到新库。", - "visualize.legacyCharts.notificationMessage": "您正在使用旧版图表库,7.16 将会移除该库。{conditionalMessage}", "visualize.linkedToSearch.unlinkSuccessNotificationText": "已取消与已保存搜索“{searchTitle}”的链接", "visualize.listing.betaTitle": "公测版", "visualize.listing.betaTooltip": "此可视化为公测版,可能会进行更改。设计和代码相对于正式发行版功能还不够成熟,将按原样提供,且不提供任何保证。公测版功能不受正式发行版功能支持 SLA 的约束", @@ -17687,7 +17679,7 @@ "xpack.ml.ruleEditor.scopeSection.noPermissionToViewFilterListsTitle": "您无权查看筛选列表", "xpack.ml.ruleEditor.scopeSection.scopeTitle": "范围", "xpack.ml.ruleEditor.selectRuleAction.createRuleLinkText": "创建规则", - "xpack.ml.ruleEditor.selectRuleAction.orText": "或 ", + "xpack.ml.ruleEditor.selectRuleAction.orText": "或 ", "xpack.ml.ruleEditor.typicalAppliesTypeText": "典型", "xpack.ml.sampleDataLinkLabel": "ML 作业", "xpack.ml.settings.anomalyDetection.anomalyDetectionTitle": "异常检测", @@ -27636,4 +27628,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 9698ab5ee5137..174870b374e82 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -105,7 +105,6 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', - 'visualization:visualize:legacyChartsLibrary': true, 'visualization:visualize:legacyPieChartsLibrary': true, }, }, diff --git a/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json b/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json index 00596d865f03d..deb14fe74b187 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json @@ -4,7 +4,6 @@ "buildNum": 9007199254740991, "dateFormat:tz": "UTC", "defaultIndex": "rollup", - "visualization:visualize:legacyChartsLibrary": true, "visualization:visualize:legacyPieChartsLibrary": true }, "coreMigrationVersion": "7.15.0", diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/async_search.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/async_search.ts index 00656c4aa8f6a..3428071684900 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/async_search.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/async_search.ts @@ -15,6 +15,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'dashboard', 'visChart']); const dashboardPanelActions = getService('dashboardPanelActions'); const queryBar = getService('queryBar'); + const elasticChart = getService('elasticChart'); + const xyChartSelector = 'visTypeXyChart'; + + const enableNewChartLibraryDebug = async () => { + await elasticChart.setNewChartUiDebugFlag(); + await queryBar.submitQuery(); + }; describe('dashboard with async search', () => { before(async function () { @@ -30,7 +37,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.missingOrFail('embeddableErrorLabel'); - const data = await PageObjects.visChart.getBarChartData('Sum of bytes'); + await enableNewChartLibraryDebug(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Sum of bytes'); expect(data.length).to.be(5); }); @@ -39,7 +47,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.loadSavedDashboard('Delayed 5s'); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.missingOrFail('embeddableErrorLabel'); - const data = await PageObjects.visChart.getBarChartData(''); + await enableNewChartLibraryDebug(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Sum of bytes'); expect(data.length).to.be(5); }); diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session.ts index f7ea80de57bda..bab93ad0483d3 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session.ts @@ -17,6 +17,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const browser = getService('browser'); const searchSessions = getService('searchSessions'); const queryBar = getService('queryBar'); + const elasticChart = getService('elasticChart'); + + const enableNewChartLibraryDebug = async () => { + await elasticChart.setNewChartUiDebugFlag(); + await queryBar.submitQuery(); + }; describe('save a search sessions', () => { before(async function () { @@ -92,14 +98,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Check that session is restored await searchSessions.expectState('restored'); await testSubjects.missingOrFail('embeddableErrorLabel'); - const data = await PageObjects.visChart.getBarChartData('Sum of bytes'); - expect(data.length).to.be(5); // switching dashboard to edit mode (or any other non-fetch required) state change // should leave session state untouched await PageObjects.dashboard.switchToEditMode(); await searchSessions.expectState('restored'); + const xyChartSelector = 'visTypeXyChart'; + await enableNewChartLibraryDebug(); + const data = await PageObjects.visChart.getBarChartData(xyChartSelector, 'Sum of bytes'); + expect(data.length).to.be(5); + // navigating to a listing page clears the session await PageObjects.dashboard.gotoDashboardLandingPage(); await searchSessions.missingOrFail(); diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index 0bc3cd7c2610e..2c79191f6269d 100644 --- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts @@ -40,7 +40,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await kibanaServer.uiSettings.update( { - 'visualization:visualize:legacyChartsLibrary': true, 'visualization:visualize:legacyPieChartsLibrary': true, }, { space }