From 5b61fc346bd77d924128a28bf5da798708236979 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Mon, 5 Dec 2016 09:38:47 -0500 Subject: [PATCH 01/17] Tag Cloud should deal with empty responses correctly (#9354) tag cloud did not work with empty responses correctly. One of the side-effects is that the back button did not work properly. For example, pressing the back button would not empty out the screen and show stale clouds. This also caused a type-error. Empty configurations meant that we could not access any aggregation-configs to produce a label. --- src/core_plugins/tagcloud/public/tag_cloud.js | 17 ++++++++++++++++- .../tagcloud/public/tag_cloud_controller.js | 17 +++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/core_plugins/tagcloud/public/tag_cloud.js b/src/core_plugins/tagcloud/public/tag_cloud.js index e1e34691b8e0b..96a59108a8c3f 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud.js +++ b/src/core_plugins/tagcloud/public/tag_cloud.js @@ -129,8 +129,23 @@ class TagCloud extends EventEmitter { const job = this._queue.pop(); this._inFlight = true; - this._onLayoutEnd(job); + if (job.words.length) { + this._onLayoutEnd(job); + } else { + this._emptyCloud(job); + } + + } + + _emptyCloud(job) { + this._svgGroup.selectAll('text').remove(); + this._cloudWidth = 0; + this._cloudHeight = 0; + this._allInViewBox = true; + this._inFlight = false; + this._currentJob = job; + this._processQueue(); } _onLayoutEnd(job) { diff --git a/src/core_plugins/tagcloud/public/tag_cloud_controller.js b/src/core_plugins/tagcloud/public/tag_cloud_controller.js index 4b5ac87773c69..1f9f50e72bb6d 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_controller.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_controller.js @@ -21,13 +21,23 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private, clickHandler({point: {aggConfigResult: aggConfigResult}}); }); tagCloud.on('renderComplete', () => { + + const truncatedMessage = containerNode.querySelector('.tagcloud-truncated-message'); + const incompleteMessage = containerNode.querySelector('.tagcloud-incomplete-message'); + + if (!$scope.vis.aggs[0] || !$scope.vis.aggs[1]) { + incompleteMessage.style.display = 'none'; + truncatedMessage.style.display = 'none'; + return; + } + const bucketName = containerNode.querySelector('.tagcloud-custom-label'); bucketName.innerHTML = `${$scope.vis.aggs[0].makeLabel()} - ${$scope.vis.aggs[1].makeLabel()}`; - const truncatedMessage = containerNode.querySelector('.tagcloud-truncated-message'); + truncatedMessage.style.display = truncated ? 'block' : 'none'; - const incompleteMessage = containerNode.querySelector('.tagcloud-incomplete-message'); + const status = tagCloud.getStatus(); if (TagCloud.STATUS.COMPLETE === status) { @@ -36,17 +46,20 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private, incompleteMessage.style.display = 'block'; } + $element.trigger('renderComplete'); }); $scope.$watch('esResponse', async function (response) { if (!response) { + tagCloud.setData([]); return; } const tagsAggId = _.first(_.pluck($scope.vis.aggs.bySchemaName.segment, 'id')); if (!tagsAggId || !response.aggregations) { + tagCloud.setData([]); return; } From b19794585b3de29b801033b9f6ec4bfa9e1c1150 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Tue, 6 Dec 2016 12:55:17 +0100 Subject: [PATCH 02/17] Vislib Point Series updates (#9044) Multiple changes to the Vislib point series charts. Before each of the chart (bar/area/line) would be an independent unit responsible for drawing all of its parts (axes, titles). This change splits things up so we can have greater control and better code reuse. converting to ES6 moving axes out of each chart joining both axes types (x and y) into a common axis type allowing multiple axes allowing top/bottom/left/right axis positioning introducing new chart type 'point_series' which can combine bar/line/area series making each of the series (bar/line/area) direction independent (vertical/horizontal charts) --- .../kbn_vislib_vis_types/public/area.js | 4 +- .../kbn_vislib_vis_types/public/histogram.js | 4 +- .../kbn_vislib_vis_types/public/line.js | 4 +- .../kbn_vislib_vis_types/public/pie.js | 1 - src/fixtures/vislib/_vis_fixture.js | 1 - src/ui/public/vis/__tests__/_vis.js | 1 - src/ui/public/vislib/VISLIB.md | 24 + .../__tests__/components/zero_injection.js | 405 +++++--------- .../public/vislib/__tests__/lib/axis_title.js | 52 +- .../vislib/__tests__/lib/chart_title.js | 14 +- src/ui/public/vislib/__tests__/lib/data.js | 92 +--- .../public/vislib/__tests__/lib/dispatch.js | 2 +- .../vislib/__tests__/lib/handler/handler.js | 2 +- .../vislib/__tests__/lib/layout/layout.js | 26 +- .../__tests__/lib/layout/layout_types.js | 2 +- .../lib/layout/types/column_layout.js | 4 +- .../public/vislib/__tests__/lib/vis_config.js | 115 ++++ src/ui/public/vislib/__tests__/lib/x_axis.js | 86 +-- src/ui/public/vislib/__tests__/lib/y_axis.js | 148 ++--- src/ui/public/vislib/__tests__/vis.js | 2 +- .../__tests__/visualizations/area_chart.js | 57 +- .../vislib/__tests__/visualizations/chart.js | 16 +- .../__tests__/visualizations/column_chart.js | 45 +- .../__tests__/visualizations/line_chart.js | 23 +- .../__tests__/visualizations/pie_chart.js | 7 +- .../__tests__/visualizations/tile_maps/map.js | 2 +- .../visualizations/tile_maps/tile_map.js | 9 +- .../__tests__/visualizations/vis_types.js | 2 +- .../public/vislib/components/color/color.js | 2 +- .../vislib/components/color/color_palette.js | 2 +- .../vislib/components/color/mapped_colors.js | 2 +- .../vislib/components/labels/data_array.js | 2 +- .../public/vislib/components/labels/labels.js | 6 +- .../components/labels/pie/get_pie_names.js | 2 +- .../components/labels/pie/pie_labels.js | 4 +- .../components/zero_injection/inject_zeros.js | 34 +- .../zero_injection/ordered_x_keys.js | 2 +- .../components/zero_injection/uniq_keys.js | 2 +- .../zero_injection/zero_filled_array.js | 5 +- src/ui/public/vislib/lib/axis/axis.js | 324 +++++++++++ src/ui/public/vislib/lib/axis/axis_config.js | 187 +++++++ src/ui/public/vislib/lib/axis/axis_labels.js | 133 +++++ src/ui/public/vislib/lib/axis/axis_scale.js | 203 +++++++ src/ui/public/vislib/lib/axis/axis_title.js | 53 ++ src/ui/public/vislib/lib/axis/scale_modes.js | 10 + src/ui/public/vislib/lib/axis_title.js | 73 --- src/ui/public/vislib/lib/chart_title.js | 51 +- src/ui/public/vislib/lib/data.js | 393 +++----------- src/ui/public/vislib/lib/dispatch.js | 78 +-- .../vislib/lib/{handler => }/handler.js | 56 +- .../vislib/lib/handler/handler_types.js | 20 - src/ui/public/vislib/lib/handler/types/pie.js | 17 - .../vislib/lib/handler/types/point_series.js | 99 ---- .../vislib/lib/handler/types/tile_map.js | 24 - src/ui/public/vislib/lib/layout/layout.js | 51 +- .../public/vislib/lib/layout/layout_types.js | 12 +- .../layout/splits/column_chart/chart_split.js | 27 +- .../splits/column_chart/x_axis_split.js | 14 +- .../splits/column_chart/y_axis_split.js | 35 +- .../vislib/lib/layout/types/column_layout.js | 66 ++- .../vislib/lib/layout/types/map_layout.js | 2 +- .../vislib/lib/layout/types/pie_layout.js | 4 +- src/ui/public/vislib/lib/types/index.js | 21 + src/ui/public/vislib/lib/types/pie.js | 13 + .../public/vislib/lib/types/point_series.js | 142 +++++ src/ui/public/vislib/lib/types/tile_map.js | 19 + src/ui/public/vislib/lib/vis_config.js | 46 ++ src/ui/public/vislib/lib/x_axis.js | 513 ------------------ src/ui/public/vislib/lib/y_axis.js | 236 -------- src/ui/public/vislib/styles/_layout.less | 51 +- src/ui/public/vislib/styles/_svg.less | 4 - src/ui/public/vislib/vis.js | 36 +- src/ui/public/vislib/vislib.js | 20 +- src/ui/public/vislib/visualizations/_chart.js | 7 +- .../visualizations/_point_series_chart.js | 176 ------ .../vislib/visualizations/area_chart.js | 379 ------------- .../vislib/visualizations/column_chart.js | 329 ----------- .../vislib/visualizations/line_chart.js | 353 ------------ .../marker_types/geohash_grid.js | 2 +- .../visualizations/marker_types/heatmap.js | 2 +- .../marker_types/scaled_circles.js | 2 +- .../marker_types/shaded_circles.js | 2 +- .../public/vislib/visualizations/pie_chart.js | 15 +- .../vislib/visualizations/point_series.js | 248 +++++++++ .../point_series/_point_series.js | 102 ++++ .../visualizations/point_series/area_chart.js | 238 ++++++++ .../point_series/column_chart.js | 245 +++++++++ .../visualizations/point_series/line_chart.js | 216 ++++++++ .../point_series/series_types.js | 12 + .../public/vislib/visualizations/tile_map.js | 8 +- .../public/vislib/visualizations/vis_types.js | 14 +- .../__tests__/_vislib_renderbot.js | 7 +- src/ui/public/visualize/visualize_legend.js | 8 +- test/functional/apps/discover/_discover.js | 47 +- test/support/page_objects/discover_page.js | 60 +- test/support/page_objects/visualize_page.js | 4 +- 96 files changed, 3279 insertions(+), 3443 deletions(-) create mode 100644 src/ui/public/vislib/VISLIB.md create mode 100644 src/ui/public/vislib/__tests__/lib/vis_config.js create mode 100644 src/ui/public/vislib/lib/axis/axis.js create mode 100644 src/ui/public/vislib/lib/axis/axis_config.js create mode 100644 src/ui/public/vislib/lib/axis/axis_labels.js create mode 100644 src/ui/public/vislib/lib/axis/axis_scale.js create mode 100644 src/ui/public/vislib/lib/axis/axis_title.js create mode 100644 src/ui/public/vislib/lib/axis/scale_modes.js delete mode 100644 src/ui/public/vislib/lib/axis_title.js rename src/ui/public/vislib/lib/{handler => }/handler.js (79%) delete mode 100644 src/ui/public/vislib/lib/handler/handler_types.js delete mode 100644 src/ui/public/vislib/lib/handler/types/pie.js delete mode 100644 src/ui/public/vislib/lib/handler/types/point_series.js delete mode 100644 src/ui/public/vislib/lib/handler/types/tile_map.js create mode 100644 src/ui/public/vislib/lib/types/index.js create mode 100644 src/ui/public/vislib/lib/types/pie.js create mode 100644 src/ui/public/vislib/lib/types/point_series.js create mode 100644 src/ui/public/vislib/lib/types/tile_map.js create mode 100644 src/ui/public/vislib/lib/vis_config.js delete mode 100644 src/ui/public/vislib/lib/x_axis.js delete mode 100644 src/ui/public/vislib/lib/y_axis.js delete mode 100644 src/ui/public/vislib/visualizations/_point_series_chart.js delete mode 100644 src/ui/public/vislib/visualizations/area_chart.js delete mode 100644 src/ui/public/vislib/visualizations/column_chart.js delete mode 100644 src/ui/public/vislib/visualizations/line_chart.js create mode 100644 src/ui/public/vislib/visualizations/point_series.js create mode 100644 src/ui/public/vislib/visualizations/point_series/_point_series.js create mode 100644 src/ui/public/vislib/visualizations/point_series/area_chart.js create mode 100644 src/ui/public/vislib/visualizations/point_series/column_chart.js create mode 100644 src/ui/public/vislib/visualizations/point_series/line_chart.js create mode 100644 src/ui/public/vislib/visualizations/point_series/series_types.js diff --git a/src/core_plugins/kbn_vislib_vis_types/public/area.js b/src/core_plugins/kbn_vislib_vis_types/public/area.js index cf33aaef09a04..b2fb930eb613a 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/area.js @@ -16,7 +16,6 @@ export default function HistogramVisType(Private) { 'effect on the series above it.', params: { defaults: { - shareYAxis: true, addTooltip: true, addLegend: true, legendPosition: 'right', @@ -27,8 +26,7 @@ export default function HistogramVisType(Private) { times: [], addTimeMarker: false, defaultYExtents: false, - setYExtents: false, - yAxis: {} + setYExtents: false }, legendPositions: [{ value: 'left', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js index 3b3db63fddcdc..a91c510fe72d5 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js @@ -14,7 +14,6 @@ export default function HistogramVisType(Private) { 'exact numbers or percentages. If you are not sure which chart you need, you could do worse than to start here.', params: { defaults: { - shareYAxis: true, addTooltip: true, addLegend: true, legendPosition: 'right', @@ -23,8 +22,7 @@ export default function HistogramVisType(Private) { times: [], addTimeMarker: false, defaultYExtents: false, - setYExtents: false, - yAxis: {} + setYExtents: false }, legendPositions: [{ value: 'left', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/line.js b/src/core_plugins/kbn_vislib_vis_types/public/line.js index c061d6183237d..6d52d4ea9421b 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/line.js @@ -14,7 +14,6 @@ export default function HistogramVisType(Private) { 'Be careful with sparse sets as the connection between points can be misleading.', params: { defaults: { - shareYAxis: true, addTooltip: true, addLegend: true, legendPosition: 'right', @@ -27,8 +26,7 @@ export default function HistogramVisType(Private) { times: [], addTimeMarker: false, defaultYExtents: false, - setYExtents: false, - yAxis: {} + setYExtents: false }, legendPositions: [{ value: 'left', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/pie.js b/src/core_plugins/kbn_vislib_vis_types/public/pie.js index 9a66fd9924ccb..670b6b290b869 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/pie.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/pie.js @@ -14,7 +14,6 @@ export default function HistogramVisType(Private) { 'Pro Tip: Pie charts are best used sparingly, and with no more than 7 slices per pie.', params: { defaults: { - shareYAxis: true, addTooltip: true, addLegend: true, legendPosition: 'right', diff --git a/src/fixtures/vislib/_vis_fixture.js b/src/fixtures/vislib/_vis_fixture.js index 79a82dfe52414..611bec8d7d35a 100644 --- a/src/fixtures/vislib/_vis_fixture.js +++ b/src/fixtures/vislib/_vis_fixture.js @@ -35,7 +35,6 @@ module.exports = function VislibFixtures(Private) { return function (visLibParams) { let Vis = Private(VislibVisProvider); return new Vis($visCanvas.new(), _.defaults({}, visLibParams || {}, { - shareYAxis: true, addTooltip: true, addLegend: true, defaultYExtents: false, diff --git a/src/ui/public/vis/__tests__/_vis.js b/src/ui/public/vis/__tests__/_vis.js index ad4aa0ad837e5..882f48f0b4ea0 100644 --- a/src/ui/public/vis/__tests__/_vis.js +++ b/src/ui/public/vis/__tests__/_vis.js @@ -93,7 +93,6 @@ describe('Vis Class', function () { expect(vis.params).to.have.property('addLegend', true); expect(vis.params).to.have.property('addTooltip', true); expect(vis.params).to.have.property('mode', 'stacked'); - expect(vis.params).to.have.property('shareYAxis', true); }); }); diff --git a/src/ui/public/vislib/VISLIB.md b/src/ui/public/vislib/VISLIB.md new file mode 100644 index 0000000000000..96dcc2ab8242d --- /dev/null +++ b/src/ui/public/vislib/VISLIB.md @@ -0,0 +1,24 @@ +# Vislib 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'. + +`vis.render` will create 'lib/vis_config' to handle configuration (applying defaults etc) and then create 'lib/handler' which will take the work over. + +`vis/handler` will init all parts of the chart (based on visualization type) and call render method on each of the building blocks. + +## Visualizations + +Each base vis type (`lib/types`) can have a different layout defined (`lib/layout`) and different building blocks (pie charts dont have axes for example) + +All base visualizations extend from `visualizations/_chart` + +### Pie chart + +### Map + +### 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 `vislualizations/point_series/_point_series`. + diff --git a/src/ui/public/vislib/__tests__/components/zero_injection.js b/src/ui/public/vislib/__tests__/components/zero_injection.js index aefb1ab70452a..10ea2601e3784 100644 --- a/src/ui/public/vislib/__tests__/components/zero_injection.js +++ b/src/ui/public/vislib/__tests__/components/zero_injection.js @@ -11,157 +11,73 @@ import VislibComponentsZeroInjectionZeroFilledArrayProvider from 'ui/vislib/comp import VislibComponentsZeroInjectionZeroFillDataArrayProvider from 'ui/vislib/components/zero_injection/zero_fill_data_array'; describe('Vislib Zero Injection Module Test Suite', function () { - const dateHistogramRows = { - 'rows': [ - { - 'label': 'Top 5 @tags: success', - 'ordered': { - 'date': true, - 'interval': 60000, - 'min': 1418410540548, - 'max': 1418410936568 - }, - 'series': [ - { - 'label': 'jpg', - 'values': [ - { 'x': 1418410560000, 'y': 2 }, - { 'x': 1418410620000, 'y': 4 }, - { 'x': 1418410680000, 'y': 1 }, - { 'x': 1418410740000, 'y': 5 }, - { 'x': 1418410800000, 'y': 2 }, - { 'x': 1418410860000, 'y': 3 }, - { 'x': 1418410920000, 'y': 2 } - ] - }, - { - 'label': 'css', - 'values': [ - { 'x': 1418410560000, 'y': 1 }, - { 'x': 1418410620000, 'y': 3 }, - { 'x': 1418410680000, 'y': 1 }, - { 'x': 1418410740000, 'y': 4 }, - { 'x': 1418410800000, 'y': 2 } - ] - }, - { - 'label': 'gif', - 'values': [ - { 'x': 1418410500000, 'y': 1 }, - { 'x': 1418410680000, 'y': 3 }, - { 'x': 1418410740000, 'y': 2 } - ] - } - ] - }, - { - 'label': 'Top 5 @tags: info', - 'ordered': { - 'date': true, - 'interval': 60000, - 'min': 1418410540548, - 'max': 1418410936568 - }, - 'series': [ - { - 'label': 'jpg', - 'values': [ - { 'x': 1418410560000, 'y': 4 }, - { 'x': 1418410620000, 'y': 2 }, - { 'x': 1418410680000, 'y': 1 }, - { 'x': 1418410740000, 'y': 5 }, - { 'x': 1418410800000, 'y': 2 }, - { 'x': 1418410860000, 'y': 3 }, - { 'x': 1418410920000, 'y': 2 } - ] - }, - { - 'label': 'css', - 'values': [ - { 'x': 1418410620000, 'y': 3 }, - { 'x': 1418410680000, 'y': 1 }, - { 'x': 1418410740000, 'y': 4 }, - { 'x': 1418410800000, 'y': 2 } - ] - }, - { - 'label': 'gif', - 'values': [ - { 'x': 1418410500000, 'y': 1 } - ] - } - ] - }, - { - 'label': 'Top 5 @tags: security', - 'ordered': { - 'date': true, - 'interval': 60000, - 'min': 1418410540548, - 'max': 1418410936568 - }, - 'series': [ - { - 'label': 'jpg', - 'values': [ - { 'x': 1418410560000, 'y': 1 }, - { 'x': 1418410620000, 'y': 3 }, - { 'x': 1418410920000, 'y': 2 } - ] - }, - { - 'label': 'gif', - 'values': [ - { 'x': 1418410680000, 'y': 3 }, - { 'x': 1418410740000, 'y': 1 } - ] - } - ] - }, + const dateHistogramRows = [ + { + 'label': 'html', + 'values': [ + { 'x': 1418410560000, 'y': 2 }, + { 'x': 1418410620000, 'y': 4 }, + { 'x': 1418410680000, 'y': 1 }, + { 'x': 1418410740000, 'y': 5 }, + { 'x': 1418410800000, 'y': 2 }, + { 'x': 1418410860000, 'y': 3 }, + { 'x': 1418410920000, 'y': 2 } + ] + }, + { + 'label': 'css', + 'values': [ + { 'x': 1418410560000, 'y': 1 }, + { 'x': 1418410620000, 'y': 3 }, + { 'x': 1418410680000, 'y': 1 }, + { 'x': 1418410740000, 'y': 4 }, + { 'x': 1418410800000, 'y': 2 } + ] + } + ]; + + const dateHistogramRowsObj = { + series: [ { - 'label': 'Top 5 @tags: login', - 'ordered': { - 'date': true, - 'interval': 60000, - 'min': 1418410540548, - 'max': 1418410936568 - }, - 'series': [ - { - 'label': 'jpg', - 'values': [ - { 'x': 1418410740000, 'y': 1 } - ] - }, - { - 'label': 'css', - 'values': [ - { 'x': 1418410560000, 'y': 1 } - ] - } + 'label': 'html', + 'values': [ + {'x': 1418410560000, 'y': 2}, + {'x': 1418410620000, 'y': 4}, + {'x': 1418410680000, 'y': 1}, + {'x': 1418410740000, 'y': 5}, + {'x': 1418410800000, 'y': 2}, + {'x': 1418410860000, 'y': 3}, + {'x': 1418410920000, 'y': 2} ] }, { - 'label': 'Top 5 @tags: warning', - 'ordered': { - 'date': true, - 'interval': 60000, - 'min': 1418410540548, - 'max': 1418410936568 - }, - 'series': [ - { - 'label': 'jpg', - 'values': [ - { 'x': 1418410860000, 'y': 2 } - ] - } + 'label': 'css', + 'values': [ + {'x': 1418410560000, 'y': 1}, + {'x': 1418410620000, 'y': 3}, + {'x': 1418410680000, 'y': 1}, + {'x': 1418410740000, 'y': 4}, + {'x': 1418410800000, 'y': 2} ] } ] }; - const seriesData = { + + const seriesData = [ + { + label: '200', + values: [ + {x: 'v1', y: 234}, + {x: 'v2', y: 34}, + {x: 'v3', y: 834}, + {x: 'v4', y: 1234}, + {x: 'v5', y: 4} + ] + } + ]; + + const seriesDataObj = { series: [ { label: '200', @@ -176,7 +92,34 @@ describe('Vislib Zero Injection Module Test Suite', function () { ] }; - const multiSeriesData = { + const multiSeriesData = [ + { + label: '200', + values: [ + {x: '1', y: 234}, + {x: '2', y: 34}, + {x: '3', y: 834}, + {x: '4', y: 1234}, + {x: '5', y: 4} + ] + }, + { + label: '404', + values: [ + {x: '1', y: 1234}, + {x: '3', y: 234}, + {x: '5', y: 34} + ] + }, + { + label: '503', + values: [ + {x: '3', y: 834} + ] + } + ]; + + const multiSeriesDataObj = { series: [ { label: '200', @@ -205,7 +148,34 @@ describe('Vislib Zero Injection Module Test Suite', function () { ] }; - const multiSeriesNumberedData = { + const multiSeriesNumberedData = [ + { + label: '200', + values: [ + {x: 1, y: 234}, + {x: 2, y: 34}, + {x: 3, y: 834}, + {x: 4, y: 1234}, + {x: 5, y: 4} + ] + }, + { + label: '404', + values: [ + {x: 1, y: 1234}, + {x: 3, y: 234}, + {x: 5, y: 34} + ] + }, + { + label: '503', + values: [ + {x: 3, y: 834} + ] + } + ]; + + const multiSeriesNumberedDataObj = { series: [ { label: '200', @@ -263,101 +233,51 @@ describe('Vislib Zero Injection Module Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); - sample1 = injectZeros(seriesData); - sample2 = injectZeros(multiSeriesData); - sample3 = injectZeros(multiSeriesNumberedData); + sample1 = injectZeros(seriesData, seriesDataObj); + sample2 = injectZeros(multiSeriesData, multiSeriesDataObj); + sample3 = injectZeros(multiSeriesNumberedData, multiSeriesNumberedDataObj); })); - it('should throw an error if the input is not an object', function () { - expect(function () { - injectZeros(str); - }).to.throwError(); - - expect(function () { - injectZeros(number); - }).to.throwError(); - - expect(function () { - injectZeros(boolean); - }).to.throwError(); - - expect(function () { - injectZeros(emptyArray); - }).to.throwError(); - - expect(function () { - injectZeros(nullValue); - }).to.throwError(); - - expect(function () { - injectZeros(notAValue); - }).to.throwError(); - }); - - it('should throw an error if property series, rows, or columns is not ' + - 'present', function () { - - expect(function () { - injectZeros(childrenObject); - }).to.throwError(); - }); - - it('should not throw an error if object has property series, rows, or ' + - 'columns', function () { - - expect(function () { - injectZeros(seriesObject); - }).to.not.throwError(); - - expect(function () { - injectZeros(rowsObject); - }).to.not.throwError(); - - expect(function () { - injectZeros(columnsObject); - }).to.not.throwError(); - }); - it('should be a function', function () { expect(_.isFunction(injectZeros)).to.be(true); }); it('should return an object with series[0].values', function () { expect(_.isObject(sample1)).to.be(true); - expect(_.isObject(sample1.series[0].values)).to.be(true); + expect(_.isObject(sample1[0].values)).to.be(true); }); it('should return the same array of objects when the length of the series array is 1', function () { - expect(sample1.series[0].values[0].x).to.be(seriesData.series[0].values[0].x); - expect(sample1.series[0].values[1].x).to.be(seriesData.series[0].values[1].x); - expect(sample1.series[0].values[2].x).to.be(seriesData.series[0].values[2].x); - expect(sample1.series[0].values[3].x).to.be(seriesData.series[0].values[3].x); - expect(sample1.series[0].values[4].x).to.be(seriesData.series[0].values[4].x); + expect(sample1[0].values[0].x).to.be(seriesData[0].values[0].x); + expect(sample1[0].values[1].x).to.be(seriesData[0].values[1].x); + expect(sample1[0].values[2].x).to.be(seriesData[0].values[2].x); + expect(sample1[0].values[3].x).to.be(seriesData[0].values[3].x); + expect(sample1[0].values[4].x).to.be(seriesData[0].values[4].x); }); it('should inject zeros in the input array', function () { - expect(sample2.series[1].values[1].y).to.be(0); - expect(sample2.series[2].values[0].y).to.be(0); - expect(sample2.series[2].values[1].y).to.be(0); - expect(sample2.series[2].values[4].y).to.be(0); - expect(sample3.series[1].values[1].y).to.be(0); - expect(sample3.series[2].values[0].y).to.be(0); - expect(sample3.series[2].values[1].y).to.be(0); - expect(sample3.series[2].values[4].y).to.be(0); + expect(sample2[1].values[1].y).to.be(0); + expect(sample2[2].values[0].y).to.be(0); + expect(sample2[2].values[1].y).to.be(0); + expect(sample2[2].values[4].y).to.be(0); + expect(sample3[1].values[1].y).to.be(0); + expect(sample3[2].values[0].y).to.be(0); + expect(sample3[2].values[1].y).to.be(0); + expect(sample3[2].values[4].y).to.be(0); }); it('should return values arrays with the same x values', function () { - expect(sample2.series[1].values[0].x).to.be(sample2.series[2].values[0].x); - expect(sample2.series[1].values[1].x).to.be(sample2.series[2].values[1].x); - expect(sample2.series[1].values[2].x).to.be(sample2.series[2].values[2].x); - expect(sample2.series[1].values[3].x).to.be(sample2.series[2].values[3].x); - expect(sample2.series[1].values[4].x).to.be(sample2.series[2].values[4].x); + expect(sample2[1].values[0].x).to.be(sample2[2].values[0].x); + expect(sample2[1].values[1].x).to.be(sample2[2].values[1].x); + expect(sample2[1].values[2].x).to.be(sample2[2].values[2].x); + expect(sample2[1].values[3].x).to.be(sample2[2].values[3].x); + expect(sample2[1].values[4].x).to.be(sample2[2].values[4].x); }); it('should return values arrays of the same length', function () { - expect(sample2.series[0].values.length).to.be(sample2.series[1].values.length); - expect(sample2.series[0].values.length).to.be(sample2.series[2].values.length); - expect(sample2.series[1].values.length).to.be(sample2.series[2].values.length); + expect(sample2[0].values.length).to.be(sample2[1].values.length); + expect(sample2[0].values.length).to.be(sample2[2].values.length); + expect(sample2[1].values.length).to.be(sample2[2].values.length); }); }); @@ -369,36 +289,10 @@ describe('Vislib Zero Injection Module Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { orderXValues = Private(VislibComponentsZeroInjectionOrderedXKeysProvider); - results = orderXValues(multiSeriesData); - numberedResults = orderXValues(multiSeriesNumberedData); + results = orderXValues(multiSeriesDataObj); + numberedResults = orderXValues(multiSeriesNumberedDataObj); })); - it('should throw an error if input is not an object', function () { - expect(function () { - orderXValues(str); - }).to.throwError(); - - expect(function () { - orderXValues(number); - }).to.throwError(); - - expect(function () { - orderXValues(boolean); - }).to.throwError(); - - expect(function () { - orderXValues(nullValue); - }).to.throwError(); - - expect(function () { - orderXValues(emptyArray); - }).to.throwError(); - - expect(function () { - orderXValues(notAValue); - }).to.throwError(); - }); - it('should return a function', function () { expect(_.isFunction(orderXValues)).to.be(true); }); @@ -422,8 +316,8 @@ describe('Vislib Zero Injection Module Test Suite', function () { it('should return an array of values ordered by their sum when orderBucketsBySum is true', function () { const orderBucketsBySum = true; - results = orderXValues(multiSeriesData, orderBucketsBySum); - numberedResults = orderXValues(multiSeriesNumberedData, orderBucketsBySum); + results = orderXValues(multiSeriesDataObj, orderBucketsBySum); + numberedResults = orderXValues(multiSeriesNumberedDataObj, orderBucketsBySum); expect(results[0]).to.be('3'); expect(results[1]).to.be('1'); @@ -445,7 +339,7 @@ describe('Vislib Zero Injection Module Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { uniqueKeys = Private(VislibComponentsZeroInjectionUniqKeysProvider); - results = uniqueKeys(multiSeriesData); + results = uniqueKeys(multiSeriesDataObj); })); it('should throw an error if input is not an object', function () { @@ -494,7 +388,7 @@ describe('Vislib Zero Injection Module Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { flattenData = Private(VislibComponentsZeroInjectionFlattenDataProvider); - results = flattenData(multiSeriesData); + results = flattenData(multiSeriesDataObj); })); it('should return a function', function () { @@ -666,24 +560,23 @@ describe('Vislib Zero Injection Module Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); - results = injectZeros(dateHistogramRows); + results = injectZeros(dateHistogramRows, dateHistogramRowsObj); })); it('should return an array of objects', function () { - results.rows.forEach(function (row) { - expect(_.isArray(row.series[0].values)).to.be(true); + results.forEach(function (row) { + expect(_.isArray(row.values)).to.be(true); }); }); it('should return ordered x values', function () { - const values = results.rows[0].series[0].values; + const values = results[0].values; expect(values[0].x).to.be.lessThan(values[1].x); expect(values[1].x).to.be.lessThan(values[2].x); expect(values[2].x).to.be.lessThan(values[3].x); expect(values[3].x).to.be.lessThan(values[4].x); expect(values[4].x).to.be.lessThan(values[5].x); expect(values[5].x).to.be.lessThan(values[6].x); - expect(values[6].x).to.be.lessThan(values[7].x); }); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/axis_title.js b/src/ui/public/vislib/__tests__/lib/axis_title.js index e542392cef20a..37fb41aa87605 100644 --- a/src/ui/public/vislib/__tests__/lib/axis_title.js +++ b/src/ui/public/vislib/__tests__/lib/axis_title.js @@ -4,12 +4,16 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from 'expect.js'; import $ from 'jquery'; -import VislibLibAxisTitleProvider from 'ui/vislib/lib/axis_title'; +import VislibLibAxisTitleProvider from 'ui/vislib/lib/axis/axis_title'; +import VislibLibAxisConfigProvider from 'ui/vislib/lib/axis/axis_config'; +import VislibLibVisConfigProvider from 'ui/vislib/lib/vis_config'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; describe('Vislib AxisTitle Class Test Suite', function () { let AxisTitle; + let AxisConfig; + let VisConfig; let Data; let PersistedState; let axisTitle; @@ -79,6 +83,8 @@ describe('Vislib AxisTitle Class Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { AxisTitle = Private(VislibLibAxisTitleProvider); + AxisConfig = Private(VislibLibAxisConfigProvider); + VisConfig = Private(VislibLibVisConfigProvider); Data = Private(VislibLibDataProvider); PersistedState = Private(PersistedStatePersistedStateProvider); @@ -86,20 +92,39 @@ describe('Vislib AxisTitle Class Test Suite', function () { .attr('class', 'vis-wrapper'); el.append('div') - .attr('class', 'y-axis-title') - .style('height', '20px') - .style('width', '20px'); + .attr('class', 'axis-wrapper-bottom') + .append('div') + .attr('class', 'axis-title y-axis-title') + .style('height', '20px') + .style('width', '20px'); el.append('div') - .attr('class', 'x-axis-title') - .style('height', '20px') - .style('width', '20px'); + .attr('class', 'axis-wrapper-left') + .append('div') + .attr('class', 'axis-title x-axis-title') + .style('height', '20px') + .style('width', '20px'); - dataObj = new Data(data, {}, new PersistedState()); - xTitle = dataObj.get('xAxisLabel'); - yTitle = dataObj.get('yAxisLabel'); - axisTitle = new AxisTitle($('.vis-wrapper')[0], xTitle, yTitle); + dataObj = new Data(data, new PersistedState()); + const visConfig = new VisConfig({ + type: 'histogram', + el: el.node() + }, data, new PersistedState()); + const xAxisConfig = new AxisConfig(visConfig, { + position: 'bottom', + title: { + text: dataObj.get('xAxisLabel') + } + }); + const yAxisConfig = new AxisConfig(visConfig, { + position: 'left', + title: { + text: dataObj.get('yAxisLabel') + } + }); + xTitle = new AxisTitle(xAxisConfig); + yTitle = new AxisTitle(yAxisConfig); })); afterEach(function () { @@ -108,7 +133,8 @@ describe('Vislib AxisTitle Class Test Suite', function () { describe('render Method', function () { beforeEach(function () { - axisTitle.render(); + xTitle.render(); + yTitle.render(); }); it('should append an svg to div', function () { @@ -129,7 +155,7 @@ describe('Vislib AxisTitle Class Test Suite', function () { describe('draw Method', function () { it('should be a function', function () { - expect(_.isFunction(axisTitle.draw())).to.be(true); + expect(_.isFunction(xTitle.draw())).to.be(true); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/chart_title.js b/src/ui/public/vislib/__tests__/lib/chart_title.js index ef8a34b35641a..b309820541d36 100644 --- a/src/ui/public/vislib/__tests__/lib/chart_title.js +++ b/src/ui/public/vislib/__tests__/lib/chart_title.js @@ -6,11 +6,13 @@ import expect from 'expect.js'; import $ from 'jquery'; import VislibLibChartTitleProvider from 'ui/vislib/lib/chart_title'; import VislibLibDataProvider from 'ui/vislib/lib/data'; +import VislibLibVisConfigProvider from 'ui/vislib/lib/vis_config'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; describe('Vislib ChartTitle Class Test Suite', function () { let ChartTitle; let Data; + let VisConfig; let persistedState; let chartTitle; let el; @@ -78,6 +80,7 @@ describe('Vislib ChartTitle Class Test Suite', function () { beforeEach(ngMock.inject(function (Private) { ChartTitle = Private(VislibLibChartTitleProvider); Data = Private(VislibLibDataProvider); + VisConfig = Private(VislibLibVisConfigProvider); persistedState = new (Private(PersistedStatePersistedStateProvider))(); el = d3.select('body').append('div') @@ -88,8 +91,15 @@ describe('Vislib ChartTitle Class Test Suite', function () { .attr('class', 'chart-title') .style('height', '20px'); - dataObj = new Data(data, {}, persistedState); - chartTitle = new ChartTitle($('.vis-wrapper')[0], 'rows'); + dataObj = new Data(data, persistedState); + const visConfig = new VisConfig({ + type: 'histogram', + title: { + 'text': 'rows' + }, + el: el.node() + }, data, persistedState); + chartTitle = new ChartTitle(visConfig); })); afterEach(function () { diff --git a/src/ui/public/vislib/__tests__/lib/data.js b/src/ui/public/vislib/__tests__/lib/data.js index 8e108a06080f3..a1e5e51fa4d94 100644 --- a/src/ui/public/vislib/__tests__/lib/data.js +++ b/src/ui/public/vislib/__tests__/lib/data.js @@ -117,7 +117,7 @@ describe('Vislib Data Class Test Suite', function () { }); it('should return an object', function () { - const rowIn = new Data(rowsData, {}, persistedState); + const rowIn = new Data(rowsData, persistedState); expect(_.isObject(rowIn)).to.be(true); }); }); @@ -136,7 +136,7 @@ describe('Vislib Data Class Test Suite', function () { }; beforeEach(function () { - data = new Data(pieData, {}, persistedState); + data = new Data(pieData, persistedState); }); it('should remove zero values', function () { @@ -154,9 +154,9 @@ describe('Vislib Data Class Test Suite', function () { let colOut; beforeEach(function () { - serIn = new Data(seriesData, {}, persistedState); - rowIn = new Data(rowsData, {}, persistedState); - colIn = new Data(colsData, {}, persistedState); + serIn = new Data(seriesData, persistedState); + rowIn = new Data(rowsData, persistedState); + colIn = new Data(colsData, persistedState); serOut = serIn.flatten(); rowOut = rowIn.flatten(); colOut = colIn.flatten(); @@ -172,7 +172,7 @@ describe('Vislib Data Class Test Suite', function () { function testLength(inputData) { return function () { - const data = new Data(inputData, {}, persistedState); + const data = new Data(inputData, persistedState); const len = _.reduce(data.chartData(), function (sum, chart) { return sum + chart.series.reduce(function (sum, series) { return sum + series.values.length; @@ -184,80 +184,6 @@ describe('Vislib Data Class Test Suite', function () { } }); - describe('getYMin method', function () { - let visData; - let visDataNeg; - let visDataStacked; - const minValue = 4; - const minValueNeg = -41; - const minValueStacked = 15; - - beforeEach(function () { - visData = new Data(dataSeries, {}, persistedState); - visDataNeg = new Data(dataSeriesNeg, {}, persistedState); - visDataStacked = new Data(dataStacked, { type: 'histogram' }, persistedState); - }); - - // The first value in the time series is less than the min date in the - // date range. It also has the largest y value. This value should be excluded - // when calculating the Y max value since it falls outside of the range. - it('should return the Y domain min value', function () { - expect(visData.getYMin()).to.be(minValue); - expect(visDataNeg.getYMin()).to.be(minValueNeg); - expect(visDataStacked.getYMin()).to.be(minValueStacked); - }); - - it('should have a minimum date value that is greater than the max value within the date range', function () { - const series = _.pluck(visData.chartData(), 'series'); - const stackedSeries = _.pluck(visDataStacked.chartData(), 'series'); - expect(_.min(series.values, function (d) { return d.x; })).to.be.greaterThan(minValue); - expect(_.min(stackedSeries.values, function (d) { return d.x; })).to.be.greaterThan(minValueStacked); - }); - - it('allows passing a value getter for manipulating the values considered', function () { - const realMin = visData.getYMin(); - const multiplier = 13.2; - expect(visData.getYMin(function (d) { return d.y * multiplier; })).to.be(realMin * multiplier); - }); - }); - - describe('getYMax method', function () { - let visData; - let visDataNeg; - let visDataStacked; - const maxValue = 41; - const maxValueNeg = -4; - const maxValueStacked = 115; - - beforeEach(function () { - visData = new Data(dataSeries, {}, persistedState); - visDataNeg = new Data(dataSeriesNeg, {}, persistedState); - visDataStacked = new Data(dataStacked, { type: 'histogram' }, persistedState); - }); - - // The first value in the time series is less than the min date in the - // date range. It also has the largest y value. This value should be excluded - // when calculating the Y max value since it falls outside of the range. - it('should return the Y domain min value', function () { - expect(visData.getYMax()).to.be(maxValue); - expect(visDataNeg.getYMax()).to.be(maxValueNeg); - expect(visDataStacked.getYMax()).to.be(maxValueStacked); - }); - - it('should have a minimum date value that is greater than the max value within the date range', function () { - const series = _.pluck(visData.chartData(), 'series'); - const stackedSeries = _.pluck(visDataStacked.chartData(), 'series'); - expect(_.min(series, function (d) { return d.x; })).to.be.greaterThan(maxValue); - expect(_.min(stackedSeries, function (d) { return d.x; })).to.be.greaterThan(maxValueStacked); - }); - - it('allows passing a value getter for manipulating the values considered', function () { - const realMax = visData.getYMax(); - const multiplier = 13.2; - expect(visData.getYMax(function (d) { return d.y * multiplier; })).to.be(realMax * multiplier); - }); - }); - describe('geohashGrid methods', function () { let data; const geohashGridData = { @@ -298,7 +224,7 @@ describe('Vislib Data Class Test Suite', function () { }; beforeEach(function () { - data = new Data(geohashGridData, {}, persistedState); + data = new Data(geohashGridData, persistedState); }); describe('getVisData', function () { @@ -319,7 +245,7 @@ describe('Vislib Data Class Test Suite', function () { describe('null value check', function () { it('should return false', function () { - const data = new Data(rowsData, {}, persistedState); + const data = new Data(rowsData, persistedState); expect(data.hasNullValues()).to.be(false); }); @@ -335,7 +261,7 @@ describe('Vislib Data Class Test Suite', function () { ] }); - const data = new Data(nullRowData, {}, persistedState); + const data = new Data(nullRowData, persistedState); expect(data.hasNullValues()).to.be(true); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/dispatch.js b/src/ui/public/vislib/__tests__/lib/dispatch.js index 5cd43aad46aa3..e5ba351dd3ddb 100644 --- a/src/ui/public/vislib/__tests__/lib/dispatch.js +++ b/src/ui/public/vislib/__tests__/lib/dispatch.js @@ -88,7 +88,7 @@ describe('Vislib Dispatch Class Test Suite', function () { it('returns a function that binds ' + event + ' events to a selection', function () { const chart = _.first(vis.handler.charts); - const apply = chart.events[name](d3.select(document.createElement('svg'))); + const apply = chart.events[name](chart.series[0].chartEl); expect(apply).to.be.a('function'); const els = getEls(vis.el, 3, 'div'); diff --git a/src/ui/public/vislib/__tests__/lib/handler/handler.js b/src/ui/public/vislib/__tests__/lib/handler/handler.js index b143ead5589e2..de24cf4349d6b 100644 --- a/src/ui/public/vislib/__tests__/lib/handler/handler.js +++ b/src/ui/public/vislib/__tests__/lib/handler/handler.js @@ -7,7 +7,7 @@ import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; -import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; +import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; const dateHistogramArray = [ diff --git a/src/ui/public/vislib/__tests__/lib/layout/layout.js b/src/ui/public/vislib/__tests__/lib/layout/layout.js index fa89a1ff34b1f..39c4d4e83294f 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/layout.js +++ b/src/ui/public/vislib/__tests__/lib/layout/layout.js @@ -12,6 +12,8 @@ import $ from 'jquery'; import VislibLibLayoutLayoutProvider from 'ui/vislib/lib/layout/layout'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; +import VislibVisConfig from 'ui/vislib/lib/vis_config'; + const dateHistogramArray = [ series, columns, @@ -32,6 +34,7 @@ dateHistogramArray.forEach(function (data, i) { let persistedState; let numberOfCharts; let testLayout; + let VisConfig; beforeEach(ngMock.module('kibana')); @@ -40,6 +43,7 @@ dateHistogramArray.forEach(function (data, i) { Layout = Private(VislibLibLayoutLayoutProvider); vis = Private(FixturesVislibVisFixtureProvider)(); persistedState = new (Private(PersistedStatePersistedStateProvider))(); + VisConfig = Private(VislibVisConfig); vis.render(data, persistedState); numberOfCharts = vis.handler.charts.length; }); @@ -52,22 +56,26 @@ dateHistogramArray.forEach(function (data, i) { describe('createLayout Method', function () { it('should append all the divs', function () { expect($(vis.el).find('.vis-wrapper').length).to.be(1); - expect($(vis.el).find('.y-axis-col-wrapper').length).to.be(1); + expect($(vis.el).find('.y-axis-col-wrapper').length).to.be(2); expect($(vis.el).find('.vis-col-wrapper').length).to.be(1); - expect($(vis.el).find('.y-axis-col').length).to.be(1); - expect($(vis.el).find('.y-axis-title').length).to.be(1); - expect($(vis.el).find('.y-axis-div-wrapper').length).to.be(1); - expect($(vis.el).find('.y-axis-spacer-block').length).to.be(1); + expect($(vis.el).find('.y-axis-col').length).to.be(2); + expect($(vis.el).find('.y-axis-title').length).to.be(2); + expect($(vis.el).find('.y-axis-div-wrapper').length).to.be(2); + expect($(vis.el).find('.y-axis-spacer-block').length).to.be(4); expect($(vis.el).find('.chart-wrapper').length).to.be(numberOfCharts); - expect($(vis.el).find('.x-axis-wrapper').length).to.be(1); - expect($(vis.el).find('.x-axis-div-wrapper').length).to.be(1); - expect($(vis.el).find('.x-axis-title').length).to.be(1); + expect($(vis.el).find('.x-axis-wrapper').length).to.be(2); + expect($(vis.el).find('.x-axis-div-wrapper').length).to.be(2); + expect($(vis.el).find('.x-axis-title').length).to.be(2); }); }); describe('layout Method', function () { beforeEach(function () { - testLayout = new Layout(vis.el, vis.data, 'histogram'); + let visConfig = new VisConfig({ + el: vis.el, + type: 'histogram' + }, data, persistedState); + testLayout = new Layout(visConfig); }); it('should append a div with the correct class name', function () { diff --git a/src/ui/public/vislib/__tests__/lib/layout/layout_types.js b/src/ui/public/vislib/__tests__/lib/layout/layout_types.js index 9d0b10c6d0986..6be0e20534c78 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/layout_types.js +++ b/src/ui/public/vislib/__tests__/lib/layout/layout_types.js @@ -11,7 +11,7 @@ describe('Vislib Layout Types Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { layoutType = Private(VislibLibLayoutLayoutTypesProvider); - layoutFunc = layoutType.histogram; + layoutFunc = layoutType.point_series; })); it('should be an object', function () { diff --git a/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js b/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js index 578cd854cdcfc..3bd8fd9ac496f 100644 --- a/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js +++ b/src/ui/public/vislib/__tests__/lib/layout/types/column_layout.js @@ -72,7 +72,7 @@ describe('Vislib Column Layout Test Suite', function () { beforeEach(ngMock.inject(function (Private) { layoutType = Private(VislibLibLayoutLayoutTypesProvider); el = d3.select('body').append('div').attr('class', 'visualization'); - columnLayout = layoutType.histogram(el, data); + columnLayout = layoutType.point_series(el, data); })); afterEach(function () { @@ -85,6 +85,6 @@ describe('Vislib Column Layout Test Suite', function () { }); it('should throw an error when the wrong number or no arguments provided', function () { - expect(function () { layoutType.histogram(el); }).to.throwError(); + expect(function () { layoutType.point_series(el); }).to.throwError(); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/vis_config.js b/src/ui/public/vislib/__tests__/lib/vis_config.js new file mode 100644 index 0000000000000..19497f6de03a6 --- /dev/null +++ b/src/ui/public/vislib/__tests__/lib/vis_config.js @@ -0,0 +1,115 @@ +import d3 from 'd3'; +import _ from 'lodash'; +import ngMock from 'ng_mock'; +import expect from 'expect.js'; +import VislibLibVisConfigProvider from 'ui/vislib/lib/vis_config'; +import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; + +describe('Vislib VisConfig Class Test Suite', function () { + let visConfig; + let el; + const data = { + hits: 621, + ordered: { + date: true, + interval: 30000, + max: 1408734982458, + min: 1408734082458 + }, + series: [ + { + label: 'Count', + values: [ + { + x: 1408734060000, + y: 8 + }, + { + x: 1408734090000, + y: 23 + }, + { + x: 1408734120000, + y: 30 + }, + { + x: 1408734150000, + y: 28 + }, + { + x: 1408734180000, + y: 36 + }, + { + x: 1408734210000, + y: 30 + }, + { + x: 1408734240000, + y: 26 + }, + { + x: 1408734270000, + y: 22 + }, + { + x: 1408734300000, + y: 29 + }, + { + x: 1408734330000, + y: 24 + } + ] + } + ], + xAxisLabel: 'Date Histogram', + yAxisLabel: 'Count' + }; + + beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.inject(function (Private) { + const VisConfig = Private(VislibLibVisConfigProvider); + const PersistedState = Private(PersistedStatePersistedStateProvider); + el = d3.select('body') + .attr('class', 'vis-wrapper') + .node(); + + visConfig = new VisConfig({ + type: 'point_series', + el: el + }, data, new PersistedState()); + })); + + describe('get Method', function () { + it('should be a function', function () { + expect(typeof visConfig.get).to.be('function'); + }); + + it('should get the property', function () { + expect(visConfig.get('el')).to.be(el); + expect(visConfig.get('type')).to.be('point_series'); + }); + + it('should return defaults if property does not exist', function () { + expect(visConfig.get('this.does.not.exist', 'defaults')).to.be('defaults'); + }); + + it('should throw an error if property does not exist and defaults were not provided', function () { + expect(function () { + visConfig.get('this.does.not.exist'); + }).to.throwError(); + }); + }); + + describe('set Method', function () { + it('should be a function', function () { + expect(typeof visConfig.set).to.be('function'); + }); + + it('should set a property', function () { + visConfig.set('this.does.not.exist', 'it.does.now'); + expect(visConfig.get('this.does.not.exist')).to.be('it.does.now'); + }); + }); +}); diff --git a/src/ui/public/vislib/__tests__/lib/x_axis.js b/src/ui/public/vislib/__tests__/lib/x_axis.js index 7b76513cf15a9..f5a37207c3ff2 100644 --- a/src/ui/public/vislib/__tests__/lib/x_axis.js +++ b/src/ui/public/vislib/__tests__/lib/x_axis.js @@ -6,16 +6,18 @@ import expect from 'expect.js'; import $ from 'jquery'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -import VislibLibXAxisProvider from 'ui/vislib/lib/x_axis'; +import VislibLibAxisProvider from 'ui/vislib/lib/axis'; +import VislibVisConfig from 'ui/vislib/lib/vis_config'; describe('Vislib xAxis Class Test Suite', function () { - let XAxis; + let Axis; let Data; let persistedState; let xAxis; let el; let fixture; let dataObj; + let VisConfig; const data = { hits: 621, ordered: { @@ -82,7 +84,8 @@ describe('Vislib xAxis Class Test Suite', function () { beforeEach(ngMock.inject(function (Private) { Data = Private(VislibLibDataProvider); persistedState = new (Private(PersistedStatePersistedStateProvider))(); - XAxis = Private(VislibLibXAxisProvider); + Axis = Private(VislibLibAxisProvider); + VisConfig = Private(VislibVisConfig); el = d3.select('body').append('div') .attr('class', 'x-axis-wrapper') @@ -91,15 +94,13 @@ describe('Vislib xAxis Class Test Suite', function () { fixture = el.append('div') .attr('class', 'x-axis-div'); - dataObj = new Data(data, {}, persistedState); - xAxis = new XAxis({ + let visConfig = new VisConfig({ el: $('.x-axis-div')[0], - xValues: dataObj.xValues(), - ordered: dataObj.get('ordered'), - xAxisFormatter: dataObj.get('xAxisFormatter'), - _attr: { - margin: { top: 0, right: 0, bottom: 0, left: 0 } - } + type: 'histogram' + }, data, persistedState); + xAxis = new Axis(visConfig, { + type: 'category', + id: 'CategoryAxis-1' }); })); @@ -126,7 +127,7 @@ describe('Vislib xAxis Class Test Suite', function () { }); }); - describe('getScale, getDomain, getTimeDomain, getOrdinalDomain, and getRange Methods', function () { + describe('getScale, getDomain, getTimeDomain, and getRange Methods', function () { let ordered; let timeScale; let timeDomain; @@ -136,28 +137,45 @@ describe('Vislib xAxis Class Test Suite', function () { let range; beforeEach(function () { - timeScale = xAxis.getScale(); - timeDomain = xAxis.getDomain(timeScale); - range = xAxis.getRange(timeDomain, width); - xAxis.ordered = {}; - ordinalScale = xAxis.getScale(); - ordinalDomain = ordinalScale.domain(['this', 'should', 'be', 'an', 'array']); width = $('.x-axis-div').width(); + xAxis.getAxis(width); + timeScale = xAxis.getScale(); + timeDomain = xAxis.axisScale.getExtents(); + range = xAxis.axisScale.getRange(width); }); it('should return a function', function () { expect(_.isFunction(timeScale)).to.be(true); - expect(_.isFunction(ordinalScale)).to.be(true); }); it('should return the correct domain', function () { - expect(_.isDate(timeDomain.domain()[0])).to.be(true); - expect(_.isDate(timeDomain.domain()[1])).to.be(true); + expect(_.isDate(timeScale.domain()[0])).to.be(true); + expect(_.isDate(timeScale.domain()[1])).to.be(true); }); it('should return the min and max dates', function () { - expect(timeDomain.domain()[0].toDateString()).to.be(new Date(1408734060000).toDateString()); - expect(timeDomain.domain()[1].toDateString()).to.be(new Date(1408734330000).toDateString()); + expect(timeScale.domain()[0].toDateString()).to.be(new Date(1408734060000).toDateString()); + expect(timeScale.domain()[1].toDateString()).to.be(new Date(1408734330000).toDateString()); + }); + + it('should return the correct range', function () { + expect(range[0]).to.be(0); + expect(range[1]).to.be(width); + }); + }); + + describe('getOrdinalDomain Method', function () { + let ordinalScale; + let ordinalDomain; + let width; + + beforeEach(function () { + width = $('.x-axis-div').width(); + xAxis.ordered = null; + xAxis.axisConfig.ordered = null; + xAxis.getAxis(width); + ordinalScale = xAxis.getScale(); + ordinalDomain = ordinalScale.domain(['this', 'should', 'be', 'an', 'array']); }); it('should return an ordinal scale', function () { @@ -168,11 +186,6 @@ describe('Vislib xAxis Class Test Suite', function () { it('should return an array of values', function () { expect(_.isArray(ordinalDomain.domain())).to.be(true); }); - - it('should return the correct range', function () { - expect(range.range()[0]).to.be(0); - expect(range.range()[1]).to.be(width); - }); }); describe('getXScale Method', function () { @@ -181,7 +194,8 @@ describe('Vislib xAxis Class Test Suite', function () { beforeEach(function () { width = $('.x-axis-div').width(); - xScale = xAxis.getXScale(width); + xAxis.getAxis(width); + xScale = xAxis.getScale(); }); it('should return a function', function () { @@ -205,19 +219,11 @@ describe('Vislib xAxis Class Test Suite', function () { beforeEach(function () { width = $('.x-axis-div').width(); - xAxis.getXAxis(width); - }); - - it('should create an xAxis function on the xAxis class', function () { - expect(_.isFunction(xAxis.xAxis)).to.be(true); - }); - - it('should create an xScale function on the xAxis class', function () { - expect(_.isFunction(xAxis.xScale)).to.be(true); + xAxis.getAxis(width); }); - it('should create an xAxisFormatter function on the xAxis class', function () { - expect(_.isFunction(xAxis.xAxisFormatter)).to.be(true); + it('should create an getScale function on the xAxis class', function () { + expect(_.isFunction(xAxis.getScale())).to.be(true); }); }); diff --git a/src/ui/public/vislib/__tests__/lib/y_axis.js b/src/ui/public/vislib/__tests__/lib/y_axis.js index 19e5ab0883146..90e177baf4508 100644 --- a/src/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/ui/public/vislib/__tests__/lib/y_axis.js @@ -5,7 +5,8 @@ import expect from 'expect.js'; import $ from 'jquery'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -import VislibLibYAxisProvider from 'ui/vislib/lib/y_axis'; +import VislibLibYAxisProvider from 'ui/vislib/lib/axis'; +import VislibVisConfig from 'ui/vislib/lib/vis_config'; let YAxis; let Data; @@ -14,6 +15,7 @@ let el; let buildYAxis; let yAxis; let yAxisDiv; +let VisConfig; const timeSeries = [ 1408734060000, @@ -72,22 +74,19 @@ function createData(seriesData) { yAxisDiv = el.append('div') .attr('class', 'y-axis-div'); - const dataObj = new Data(data, { - defaultYMin: true - }, persistedState); - buildYAxis = function (params) { - return new YAxis(_.merge({}, params, { + let visConfig = new VisConfig({ el: node, - yMin: dataObj.getYMin(), - yMax: dataObj.getYMax(), - _attr: { - margin: { top: 0, right: 0, bottom: 0, left: 0 }, + type: 'histogram' + }, data, persistedState); + return new YAxis(visConfig, _.merge({}, { + id: 'ValueAxis-1', + type: 'value', + scale: { defaultYMin: true, setYExtents: false, - yAxis: {} } - })); + }, params)); }; yAxis = buildYAxis(); @@ -100,19 +99,21 @@ describe('Vislib yAxis Class Test Suite', function () { Data = Private(VislibLibDataProvider); persistedState = new (Private(PersistedStatePersistedStateProvider))(); YAxis = Private(VislibLibYAxisProvider); + VisConfig = Private(VislibVisConfig); expect($('.y-axis-wrapper')).to.have.length(0); })); afterEach(function () { - el.remove(); - yAxisDiv.remove(); + if (el) { + el.remove(); + yAxisDiv.remove(); + } }); describe('render Method', function () { beforeEach(function () { createData(defaultGraphData); - expect(d3.select(yAxis.el).selectAll('.y-axis-div')).to.have.length(1); yAxis.render(); }); @@ -150,7 +151,8 @@ describe('Vislib yAxis Class Test Suite', function () { describe('API', function () { beforeEach(function () { createData(defaultGraphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should return a function', function () { @@ -158,25 +160,12 @@ describe('Vislib yAxis Class Test Suite', function () { }); }); - describe('should return log values', function () { - let domain; - let extents; - - it('should return 1', function () { - yAxis._attr.scale = 'log'; - extents = [0, 400]; - domain = yAxis._getExtents(extents); - - // Log scales have a yMin value of 1 - expect(domain[0]).to.be(1); - }); - }); - describe('positive values', function () { beforeEach(function () { graphData = defaultGraphData; createData(graphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); @@ -196,7 +185,8 @@ describe('Vislib yAxis Class Test Suite', function () { [ -22, -8, -30, -4, 0, 0, -3, -22, -14, -24 ] ]; createData(graphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should have domain between min value and 0', function () { @@ -215,7 +205,8 @@ describe('Vislib yAxis Class Test Suite', function () { [ 22, 8, -30, -4, 0, 0, 3, -22, 14, 24 ] ]; createData(graphData); - yScale = yAxis.getYScale(height); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should have domain between min and max values', function () { @@ -230,9 +221,11 @@ describe('Vislib yAxis Class Test Suite', function () { describe('validate user defined values', function () { beforeEach(function () { - yAxis._attr.mode = 'stacked'; - yAxis._attr.setYExtents = false; - yAxis._attr.yAxis = {}; + createData(defaultGraphData); + yAxis.axisConfig.set('scale.stacked', true); + yAxis.axisConfig.set('scale.setYExtents', false); + yAxis.getAxis(height); + yScale = yAxis.getScale(); }); it('should throw a NaN error', function () { @@ -240,17 +233,18 @@ describe('Vislib yAxis Class Test Suite', function () { const max = 12; expect(function () { - yAxis._validateUserExtents(min, max); + yAxis.axisScale.validateUserExtents(min, max); }).to.throwError(); }); it('should return a decimal value', function () { - yAxis._attr.mode = 'percentage'; - yAxis._attr.setYExtents = true; + yAxis.axisConfig.set('scale.mode', 'percentage'); + yAxis.axisConfig.set('scale.setYExtents', true); + yAxis.getAxis(height); domain = []; - domain[0] = yAxis._attr.yAxis.min = 20; - domain[1] = yAxis._attr.yAxis.max = 80; - const newDomain = yAxis._validateUserExtents(domain); + domain[0] = 20; + domain[1] = 80; + const newDomain = yAxis.axisScale.validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0] / 100); expect(newDomain[1]).to.be(domain[1] / 100); @@ -258,7 +252,7 @@ describe('Vislib yAxis Class Test Suite', function () { it('should return the user defined value', function () { domain = [20, 50]; - const newDomain = yAxis._validateUserExtents(domain); + const newDomain = yAxis.axisScale.validateUserExtents(domain); expect(newDomain[0]).to.be(domain[0]); expect(newDomain[1]).to.be(domain[1]); @@ -271,7 +265,7 @@ describe('Vislib yAxis Class Test Suite', function () { const max = 12; expect(function () { - yAxis._validateAxisExtents(min, max); + yAxis.axisScale.validateAxisExtents(min, max); }).to.throwError(); }); @@ -280,7 +274,7 @@ describe('Vislib yAxis Class Test Suite', function () { const max = 10; expect(function () { - yAxis._validateAxisExtents(min, max); + yAxis.axisScale.validateAxisExtents(min, max); }).to.throwError(); }); }); @@ -291,16 +285,16 @@ describe('Vislib yAxis Class Test Suite', function () { it('should return a function', function () { fnNames.forEach(function (fnName) { - expect(yAxis._getScaleType(fnName)).to.be.a(Function); + expect(yAxis.axisScale.getD3Scale(fnName)).to.be.a(Function); }); // if no value is provided to the function, scale should default to a linear scale - expect(yAxis._getScaleType()).to.be.a(Function); + expect(yAxis.axisScale.getD3Scale()).to.be.a(Function); }); it('should throw an error if function name is undefined', function () { expect(function () { - yAxis._getScaleType('square'); + yAxis.axisScale.getD3Scale('square'); }).to.throwError(); }); }); @@ -308,18 +302,18 @@ describe('Vislib yAxis Class Test Suite', function () { describe('_logDomain method', function () { it('should throw an error', function () { expect(function () { - yAxis._logDomain(-10, -5); + yAxis.axisScale.logDomain(-10, -5); }).to.throwError(); expect(function () { - yAxis._logDomain(-10, 5); + yAxis.axisScale.logDomain(-10, 5); }).to.throwError(); expect(function () { - yAxis._logDomain(0, -5); + yAxis.axisScale.logDomain(0, -5); }).to.throwError(); }); it('should return a yMin value of 1', function () { - const yMin = yAxis._logDomain(0, 200)[0]; + const yMin = yAxis.axisScale.logDomain(0, 200)[0]; expect(yMin).to.be(1); }); }); @@ -330,35 +324,30 @@ describe('Vislib yAxis Class Test Suite', function () { let yScale; beforeEach(function () { createData(defaultGraphData); - mode = yAxis._attr.mode; yMax = yAxis.yMax; - yScale = yAxis.getYScale; }); afterEach(function () { - yAxis._attr.mode = mode; yAxis.yMax = yMax; - yAxis.getYScale = yScale; + yAxis = buildYAxis(); }); it('should use percentage format for percentages', function () { - yAxis._attr.mode = 'percentage'; - const tickFormat = yAxis.getYAxis().tickFormat(); + yAxis = buildYAxis({ + scale: { + mode: 'percentage' + } + }); + const tickFormat = yAxis.getAxis().tickFormat(); expect(tickFormat(1)).to.be('100%'); }); it('should use decimal format for small values', function () { yAxis.yMax = 1; - const tickFormat = yAxis.getYAxis().tickFormat(); + const tickFormat = yAxis.getAxis().tickFormat(); expect(tickFormat(0.8)).to.be('0.8'); }); - it('should throw an error if yScale is NaN', function () { - yAxis.getYScale = function () { return NaN; }; - expect(function () { - yAxis.getYAxis(); - }).to.throwError(); - }); }); describe('draw Method', function () { @@ -382,33 +371,4 @@ describe('Vislib yAxis Class Test Suite', function () { expect(yAxis.tickScale(20)).to.be(0); }); }); - - describe('#tickFormat()', function () { - const formatter = function () {}; - - it('returns a basic number formatter by default', function () { - const yAxis = buildYAxis(); - expect(yAxis.tickFormat()).to.not.be(formatter); - expect(yAxis.tickFormat()(1)).to.be('1'); - }); - - it('returns the yAxisFormatter when passed', function () { - const yAxis = buildYAxis({ - yAxisFormatter: formatter - }); - expect(yAxis.tickFormat()).to.be(formatter); - }); - - it('returns a percentage formatter when the vis is in percentage mode', function () { - const yAxis = buildYAxis({ - yAxisFormatter: formatter, - _attr: { - mode: 'percentage' - } - }); - - expect(yAxis.tickFormat()).to.not.be(formatter); - expect(yAxis.tickFormat()(1)).to.be('100%'); - }); - }); }); diff --git a/src/ui/public/vislib/__tests__/vis.js b/src/ui/public/vislib/__tests__/vis.js index 46a5d5b9903c9..e12a1ba3d1160 100644 --- a/src/ui/public/vislib/__tests__/vis.js +++ b/src/ui/public/vislib/__tests__/vis.js @@ -122,7 +122,7 @@ dataArray.forEach(function (data, i) { it('should get attribue values', function () { expect(vis.get('addLegend')).to.be(true); expect(vis.get('addTooltip')).to.be(true); - expect(vis.get('type')).to.be('histogram'); + expect(vis.get('type')).to.be('point_series'); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/area_chart.js b/src/ui/public/vislib/__tests__/visualizations/area_chart.js index c99fd0af82efc..310df97bcdc9c 100644 --- a/src/ui/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/area_chart.js @@ -8,7 +8,7 @@ import notQuiteEnoughVariables from 'fixtures/vislib/mock_data/not_enough_data/_ import $ from 'jquery'; import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -const someOtherVariables = { +const dataTypesArray = { 'series pos': require('fixtures/vislib/mock_data/date_histogram/_series'), 'series pos neg': require('fixtures/vislib/mock_data/date_histogram/_series_pos_neg'), 'series neg': require('fixtures/vislib/mock_data/date_histogram/_series_neg'), @@ -20,12 +20,13 @@ const someOtherVariables = { const visLibParams = { type: 'area', addLegend: true, - addTooltip: true + addTooltip: true, + mode: 'stacked' }; -_.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { - describe('Vislib Area Chart Test Suite for ' + imaVariable + ' Data', function () { +_.forOwn(dataTypesArray, function (dataType, dataTypeName) { + describe('Vislib Area Chart Test Suite for ' + dataTypeName + ' Data', function () { let vis; let persistedState; @@ -34,7 +35,7 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); persistedState = new (Private(PersistedStatePersistedStateProvider))(); vis.on('brush', _.noop); - vis.render(variablesAreCool, persistedState); + vis.render(dataType, persistedState); })); afterEach(function () { @@ -50,9 +51,11 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should throw a Not Enough Data Error', function () { vis.handler.charts.forEach(function (chart) { - expect(function () { - chart.checkIfEnoughData(); - }).to.throwError(); + chart.series.forEach(function (series) { + expect(function () { + series.checkIfEnoughData(); + }).to.throwError(); + }); }); }); }); @@ -66,9 +69,11 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should not throw a Not Enough Data Error', function () { vis.handler.charts.forEach(function (chart) { - expect(function () { - chart.checkIfEnoughData(); - }).to.not.throwError(); + chart.series.forEach(function (series) { + expect(function () { + series.checkIfEnoughData(); + }).to.not.throwError(); + }); }); }); }); @@ -79,10 +84,10 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { beforeEach(function () { vis.handler.charts.forEach(function (chart) { - stackedData = chart.stackData(chart.chartData); + stackedData = chart.chartData; - isStacked = stackedData.every(function (arr) { - return arr.every(function (d) { + isStacked = stackedData.series.every(function (arr) { + return arr.values.every(function (d) { return _.isNumber(d.y0); }); }); @@ -181,16 +186,17 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; + const domain = yAxis.getScale().domain(); - expect(yAxis.domain[0]).to.not.be(undefined); - expect(yAxis.domain[1]).to.not.be(undefined); + expect(domain[0]).to.not.be(undefined); + expect(domain[1]).to.not.be(undefined); }); }); it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -216,17 +222,18 @@ _.forOwn(someOtherVariables, function (variablesAreCool, imaVariable) { describe('defaultYExtents is true', function () { beforeEach(function () { - vis._attr.defaultYExtents = true; - vis.render(variablesAreCool, persistedState); + vis.visConfigArgs.defaultYExtents = true; + vis.render(dataType, persistedState); }); it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; - const yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; - - expect(yAxis.domain[0]).to.equal(yVals[0]); - expect(yAxis.domain[1]).to.equal(yVals[1]); + 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]).to.equal(min); + expect(domain[1]).to.equal(max); }); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/chart.js b/src/ui/public/vislib/__tests__/visualizations/chart.js index 5cfcb0ae62596..32dcb76af4a57 100644 --- a/src/ui/public/vislib/__tests__/visualizations/chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/chart.js @@ -4,11 +4,11 @@ import ngMock from 'ng_mock'; import VislibVisProvider from 'ui/vislib/vis'; import VislibLibDataProvider from 'ui/vislib/lib/data'; import PersistedStatePersistedStateProvider from 'ui/persisted_state/persisted_state'; -import VislibVisualizationsColumnChartProvider from 'ui/vislib/visualizations/column_chart'; +import VislibVisualizationsPieChartProvider from 'ui/vislib/visualizations/pie_chart'; import VislibVisualizationsChartProvider from 'ui/vislib/visualizations/_chart'; describe('Vislib _chart Test Suite', function () { - let ColumnChart; + let PieChart; let Chart; let Data; let persistedState; @@ -88,23 +88,23 @@ describe('Vislib _chart Test Suite', function () { Vis = Private(VislibVisProvider); Data = Private(VislibLibDataProvider); persistedState = new (Private(PersistedStatePersistedStateProvider))(); - ColumnChart = Private(VislibVisualizationsColumnChartProvider); + PieChart = Private(VislibVisualizationsPieChartProvider); Chart = Private(VislibVisualizationsChartProvider); el = d3.select('body').append('div').attr('class', 'column-chart'); config = { type: 'histogram', - shareYAxis: true, addTooltip: true, addLegend: true, - stack: d3.layout.stack(), + hasTimeField: true, + zeroFill: true }; vis = new Vis(el[0][0], config); - vis.data = new Data(data, config, persistedState); + vis.render(data, persistedState); - myChart = new ColumnChart(vis, el, chartData); + myChart = vis.handler.charts[0]; })); afterEach(function () { @@ -125,7 +125,7 @@ describe('Vislib _chart Test Suite', function () { myChart.destroy(); expect(function () { - myChart.draw(); + myChart.render(); }).to.throwError(); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/column_chart.js b/src/ui/public/vislib/__tests__/visualizations/column_chart.js index dc45e737cbde9..35466967c8df9 100644 --- a/src/ui/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/column_chart.js @@ -37,7 +37,8 @@ dataTypesArray.forEach(function (dataType, i) { hasTimeField: true, addLegend: true, addTooltip: true, - mode: mode + mode: mode, + zeroFill: true }; beforeEach(ngMock.module('kibana')); @@ -58,17 +59,17 @@ dataTypesArray.forEach(function (dataType, i) { beforeEach(function () { vis.handler.charts.forEach(function (chart) { - stackedData = chart.stackData(chart.chartData); + stackedData = chart.chartData; - isStacked = stackedData.every(function (arr) { - return arr.every(function (d) { + isStacked = stackedData.series.every(function (arr) { + return arr.values.every(function (d) { return _.isNumber(d.y0); }); }); }); }); - it('should append a d.y0 key to the data object', function () { + it('should stack values', function () { expect(isStacked).to.be(true); }); }); @@ -88,17 +89,6 @@ dataTypesArray.forEach(function (dataType, i) { }); }); - describe('updateBars method', function () { - beforeEach(function () { - vis.handler._attr.mode = 'grouped'; - vis.render(vis.data, persistedState); - }); - - it('should returned grouped bars', function () { - vis.handler.charts.forEach(function (chart) {}); - }); - }); - describe('addBarEvents method', function () { function checkChart(chart) { const rect = $(chart.chartEl).find('.series rect').get(0); @@ -149,16 +139,17 @@ dataTypesArray.forEach(function (dataType, i) { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; + const domain = yAxis.getScale().domain(); - expect(yAxis.domain[0]).to.not.be(undefined); - expect(yAxis.domain[1]).to.not.be(undefined); + expect(domain[0]).to.not.be(undefined); + expect(domain[1]).to.not.be(undefined); }); }); it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -184,18 +175,18 @@ dataTypesArray.forEach(function (dataType, i) { describe('defaultYExtents is true', function () { beforeEach(function () { - vis._attr.defaultYExtents = true; + vis.visConfigArgs.defaultYExtents = true; vis.render(data, persistedState); }); it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; - const min = vis.handler.data.getYMin(); - const max = vis.handler.data.getYMax(); - - expect(yAxis.domain[0]).to.equal(min); - expect(yAxis.domain[1]).to.equal(max); + 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]).to.equal(min); + expect(domain[1]).to.equal(max); }); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/line_chart.js b/src/ui/public/vislib/__tests__/visualizations/line_chart.js index 0b4049850e1b6..66ee574611fbf 100644 --- a/src/ui/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/line_chart.js @@ -132,16 +132,16 @@ describe('Vislib Line Chart', function () { it('should return a yMin and yMax', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; - - expect(yAxis.domain[0]).to.not.be(undefined); - expect(yAxis.domain[1]).to.not.be(undefined); + const yAxis = chart.handler.valueAxes[0]; + const domain = yAxis.getScale().domain(); + expect(domain[0]).to.not.be(undefined); + expect(domain[1]).to.not.be(undefined); }); }); it('should render a zero axis line', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; + const yAxis = chart.handler.valueAxes[0]; if (yAxis.yMin < 0 && yAxis.yMax > 0) { expect($(chart.chartEl).find('line.zero-line').length).to.be(1); @@ -167,17 +167,18 @@ describe('Vislib Line Chart', function () { describe('defaultYExtents is true', function () { beforeEach(function () { - vis._attr.defaultYExtents = true; + vis.visConfigArgs.defaultYExtents = true; vis.render(data, persistedState); }); it('should return yAxis extents equal to data extents', function () { vis.handler.charts.forEach(function (chart) { - const yAxis = chart.handler.yAxis; - const yVals = [vis.handler.data.getYMin(), vis.handler.data.getYMax()]; - - expect(yAxis.domain[0]).to.equal(yVals[0]); - expect(yAxis.domain[1]).to.equal(yVals[1]); + 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]).to.equal(min); + expect(domain[1]).to.equal(max); }); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/pie_chart.js b/src/ui/public/vislib/__tests__/visualizations/pie_chart.js index ad4ece255a76f..4ddf502f744b9 100644 --- a/src/ui/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/ui/public/vislib/__tests__/visualizations/pie_chart.js @@ -1,5 +1,4 @@ import d3 from 'd3'; -import angular from 'angular'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import _ from 'lodash'; @@ -140,16 +139,16 @@ describe('No global chart settings', function () { it('should throw an error when all charts contain zeros', function () { expect(function () { - chart1.ChartClass.prototype._validatePieData(allZeros); + chart1.handler.ChartClass.prototype._validatePieData(allZeros); }).to.throwError(); }); it('should not throw an error when only some or no charts contain zeros', function () { expect(function () { - chart1.ChartClass.prototype._validatePieData(someZeros); + chart1.handler.ChartClass.prototype._validatePieData(someZeros); }).to.not.throwError(); expect(function () { - chart1.ChartClass.prototype._validatePieData(noZeros); + chart1.handler.ChartClass.prototype._validatePieData(noZeros); }).to.not.throwError(); }); }); diff --git a/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js b/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js index 3366a4a21200e..777296976d58a 100644 --- a/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js +++ b/src/ui/public/vislib/__tests__/visualizations/tile_maps/map.js @@ -16,7 +16,7 @@ import VislibVisualizationsMapProvider from 'ui/vislib/visualizations/_map'; // ['rows', require('fixtures/vislib/mock_data/geohash/_rows')], // ]; -// // TODO: Test the specific behavior of each these +// TODO: Test the specific behavior of each these // const mapTypes = [ // 'Scaled Circle Markers', // 'Shaded Circle Markers', diff --git a/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js b/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js index 0803ad328bebd..8500ca9cd790f 100644 --- a/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js +++ b/src/ui/public/vislib/__tests__/visualizations/tile_maps/tile_map.js @@ -1,4 +1,3 @@ -import angular from 'angular'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import _ from 'lodash'; @@ -14,7 +13,13 @@ let TileMap; let extentsStub; function createTileMap(handler, chartEl, chartData) { - handler = handler || {}; + handler = handler || { + visConfig: { + get: function () { + return ''; + } + } + }; chartEl = chartEl || mockChartEl; chartData = chartData || geoJsonData; diff --git a/src/ui/public/vislib/__tests__/visualizations/vis_types.js b/src/ui/public/vislib/__tests__/visualizations/vis_types.js index a025fe13a7285..761e5a8aaf99d 100644 --- a/src/ui/public/vislib/__tests__/visualizations/vis_types.js +++ b/src/ui/public/vislib/__tests__/visualizations/vis_types.js @@ -11,7 +11,7 @@ describe('Vislib Vis Types Test Suite', function () { beforeEach(ngMock.module('kibana')); beforeEach(ngMock.inject(function (Private) { visTypes = Private(VislibVisualizationsVisTypesProvider); - visFunc = visTypes.histogram; + visFunc = visTypes.point_series; })); it('should be an object', function () { diff --git a/src/ui/public/vislib/components/color/color.js b/src/ui/public/vislib/components/color/color.js index 3a7d4543fb7f9..70e53a33c7439 100644 --- a/src/ui/public/vislib/components/color/color.js +++ b/src/ui/public/vislib/components/color/color.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import VislibComponentsColorMappedColorsProvider from 'ui/vislib/components/color/mapped_colors'; +import VislibComponentsColorMappedColorsProvider from './mapped_colors'; export default function ColorUtilService(Private) { const mappedColors = Private(VislibComponentsColorMappedColorsProvider); diff --git a/src/ui/public/vislib/components/color/color_palette.js b/src/ui/public/vislib/components/color/color_palette.js index 5b4586a2642d8..3b8a95f6d29f2 100644 --- a/src/ui/public/vislib/components/color/color_palette.js +++ b/src/ui/public/vislib/components/color/color_palette.js @@ -1,6 +1,6 @@ import d3 from 'd3'; import _ from 'lodash'; -import VislibComponentsColorSeedColorsProvider from 'ui/vislib/components/color/seed_colors'; +import VislibComponentsColorSeedColorsProvider from './seed_colors'; export default function ColorPaletteUtilService(Private) { const seedColors = Private(VislibComponentsColorSeedColorsProvider); diff --git a/src/ui/public/vislib/components/color/mapped_colors.js b/src/ui/public/vislib/components/color/mapped_colors.js index d665dd5cae5a0..67644163bedb5 100644 --- a/src/ui/public/vislib/components/color/mapped_colors.js +++ b/src/ui/public/vislib/components/color/mapped_colors.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import d3 from 'd3'; -import VislibComponentsColorColorPaletteProvider from 'ui/vislib/components/color/color_palette'; +import VislibComponentsColorColorPaletteProvider from './color_palette'; define((require) => (Private, config, $rootScope) => { const createColorPalette = Private(VislibComponentsColorColorPaletteProvider); diff --git a/src/ui/public/vislib/components/labels/data_array.js b/src/ui/public/vislib/components/labels/data_array.js index 1aae7dbe9816a..8872bd8993a30 100644 --- a/src/ui/public/vislib/components/labels/data_array.js +++ b/src/ui/public/vislib/components/labels/data_array.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import VislibComponentsLabelsFlattenSeriesProvider from 'ui/vislib/components/labels/flatten_series'; +import VislibComponentsLabelsFlattenSeriesProvider from './flatten_series'; export default function GetArrayUtilService(Private) { const flattenSeries = Private(VislibComponentsLabelsFlattenSeriesProvider); diff --git a/src/ui/public/vislib/components/labels/labels.js b/src/ui/public/vislib/components/labels/labels.js index dab5a5e1486a0..761f1ecfdbc7a 100644 --- a/src/ui/public/vislib/components/labels/labels.js +++ b/src/ui/public/vislib/components/labels/labels.js @@ -1,7 +1,7 @@ import _ from 'lodash'; -import VislibComponentsLabelsDataArrayProvider from 'ui/vislib/components/labels/data_array'; -import VislibComponentsLabelsUniqLabelsProvider from 'ui/vislib/components/labels/uniq_labels'; -import VislibComponentsLabelsPiePieLabelsProvider from 'ui/vislib/components/labels/pie/pie_labels'; +import VislibComponentsLabelsDataArrayProvider from './data_array'; +import VislibComponentsLabelsUniqLabelsProvider from './uniq_labels'; +import VislibComponentsLabelsPiePieLabelsProvider from './pie/pie_labels'; export default function LabelUtilService(Private) { const createArr = Private(VislibComponentsLabelsDataArrayProvider); diff --git a/src/ui/public/vislib/components/labels/pie/get_pie_names.js b/src/ui/public/vislib/components/labels/pie/get_pie_names.js index 22ac25a686fcf..c667f6822966d 100644 --- a/src/ui/public/vislib/components/labels/pie/get_pie_names.js +++ b/src/ui/public/vislib/components/labels/pie/get_pie_names.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import VislibComponentsLabelsPieReturnPieNamesProvider from 'ui/vislib/components/labels/pie/return_pie_names'; +import VislibComponentsLabelsPieReturnPieNamesProvider from './return_pie_names'; export default function GetPieNames(Private) { const returnNames = Private(VislibComponentsLabelsPieReturnPieNamesProvider); diff --git a/src/ui/public/vislib/components/labels/pie/pie_labels.js b/src/ui/public/vislib/components/labels/pie/pie_labels.js index 250563c6586e5..956882e40c652 100644 --- a/src/ui/public/vislib/components/labels/pie/pie_labels.js +++ b/src/ui/public/vislib/components/labels/pie/pie_labels.js @@ -1,6 +1,6 @@ import _ from 'lodash'; -import VislibComponentsLabelsPieRemoveZeroSlicesProvider from 'ui/vislib/components/labels/pie/remove_zero_slices'; -import VislibComponentsLabelsPieGetPieNamesProvider from 'ui/vislib/components/labels/pie/get_pie_names'; +import VislibComponentsLabelsPieRemoveZeroSlicesProvider from './remove_zero_slices'; +import VislibComponentsLabelsPieGetPieNamesProvider from './get_pie_names'; export default function PieLabels(Private) { const removeZeroSlices = Private(VislibComponentsLabelsPieRemoveZeroSlicesProvider); diff --git a/src/ui/public/vislib/components/zero_injection/inject_zeros.js b/src/ui/public/vislib/components/zero_injection/inject_zeros.js index 5c475de3a13cc..36a80d4b17fad 100644 --- a/src/ui/public/vislib/components/zero_injection/inject_zeros.js +++ b/src/ui/public/vislib/components/zero_injection/inject_zeros.js @@ -1,7 +1,7 @@ import _ from 'lodash'; -import VislibComponentsZeroInjectionOrderedXKeysProvider from 'ui/vislib/components/zero_injection/ordered_x_keys'; -import VislibComponentsZeroInjectionZeroFilledArrayProvider from 'ui/vislib/components/zero_injection/zero_filled_array'; -import VislibComponentsZeroInjectionZeroFillDataArrayProvider from 'ui/vislib/components/zero_injection/zero_fill_data_array'; +import VislibComponentsZeroInjectionOrderedXKeysProvider from './ordered_x_keys'; +import VislibComponentsZeroInjectionZeroFilledArrayProvider from './zero_filled_array'; +import VislibComponentsZeroInjectionZeroFillDataArrayProvider from './zero_fill_data_array'; export default function ZeroInjectionUtilService(Private) { const orderXValues = Private(VislibComponentsZeroInjectionOrderedXKeysProvider); @@ -19,30 +19,12 @@ export default function ZeroInjectionUtilService(Private) { * and injects zeros where needed. */ - function getDataArray(obj) { - if (obj.rows) { - return obj.rows; - } else if (obj.columns) { - return obj.columns; - } else if (obj.series) { - return [obj]; - } - } + return function (obj, data, orderBucketsBySum = false) { + const keys = orderXValues(data, orderBucketsBySum); - return function (obj, orderBucketsBySum = false) { - if (!_.isObject(obj) || !obj.rows && !obj.columns && !obj.series) { - throw new TypeError('ZeroInjectionUtilService expects an object with a series, rows, or columns key'); - } - - const keys = orderXValues(obj, orderBucketsBySum); - const arr = getDataArray(obj); - - arr.forEach(function (object) { - object.series.forEach(function (series) { - const zeroArray = createZeroFilledArray(keys); - - series.values = zeroFillDataArray(zeroArray, series.values); - }); + obj.forEach(function (series) { + const zeroArray = createZeroFilledArray(keys, series.label); + series.values = zeroFillDataArray(zeroArray, series.values); }); return obj; diff --git a/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js b/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js index 5754e8a07043f..c77cec73f2260 100644 --- a/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js +++ b/src/ui/public/vislib/components/zero_injection/ordered_x_keys.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import moment from 'moment'; -import VislibComponentsZeroInjectionUniqKeysProvider from 'ui/vislib/components/zero_injection/uniq_keys'; +import VislibComponentsZeroInjectionUniqKeysProvider from './uniq_keys'; export default function OrderedXKeysUtilService(Private) { const getUniqKeys = Private(VislibComponentsZeroInjectionUniqKeysProvider); diff --git a/src/ui/public/vislib/components/zero_injection/uniq_keys.js b/src/ui/public/vislib/components/zero_injection/uniq_keys.js index 1f6bf9187b36b..7a4e17a47c99c 100644 --- a/src/ui/public/vislib/components/zero_injection/uniq_keys.js +++ b/src/ui/public/vislib/components/zero_injection/uniq_keys.js @@ -1,5 +1,5 @@ import _ from 'lodash'; -import VislibComponentsZeroInjectionFlattenDataProvider from 'ui/vislib/components/zero_injection/flatten_data'; +import VislibComponentsZeroInjectionFlattenDataProvider from './flatten_data'; export default function UniqueXValuesUtilService(Private) { const flattenDataArray = Private(VislibComponentsZeroInjectionFlattenDataProvider); diff --git a/src/ui/public/vislib/components/zero_injection/zero_filled_array.js b/src/ui/public/vislib/components/zero_injection/zero_filled_array.js index ceeb4afc0c2f1..c8edf68912036 100644 --- a/src/ui/public/vislib/components/zero_injection/zero_filled_array.js +++ b/src/ui/public/vislib/components/zero_injection/zero_filled_array.js @@ -7,7 +7,7 @@ define(function () { * Returns a zero filled array. */ - return function (arr) { + return function (arr, label) { if (!_.isArray(arr)) { throw new Error('ZeroFilledArrayUtilService expects an array of strings or numbers'); } @@ -18,7 +18,8 @@ define(function () { zeroFilledArray.push({ x: val, xi: Infinity, - y: 0 + y: 0, + series: label }); }); diff --git a/src/ui/public/vislib/lib/axis/axis.js b/src/ui/public/vislib/lib/axis/axis.js new file mode 100644 index 0000000000000..19ec6bd6d3e5d --- /dev/null +++ b/src/ui/public/vislib/lib/axis/axis.js @@ -0,0 +1,324 @@ +import d3 from 'd3'; +import _ from 'lodash'; +import $ from 'jquery'; +import ErrorHandlerProvider from '../_error_handler'; +import AxisTitleProvider from './axis_title'; +import AxisLabelsProvider from './axis_labels'; +import AxisScaleProvider from './axis_scale'; +import AxisConfigProvider from './axis_config'; +import errors from 'ui/errors'; + +export default function AxisFactory(Private) { + const ErrorHandler = Private(ErrorHandlerProvider); + const AxisTitle = Private(AxisTitleProvider); + const AxisLabels = Private(AxisLabelsProvider); + const AxisScale = Private(AxisScaleProvider); + const AxisConfig = Private(AxisConfigProvider); + + class Axis extends ErrorHandler { + constructor(visConfig, axisConfigArgs) { + super(); + this.visConfig = visConfig; + + this.axisConfig = new AxisConfig(this.visConfig, axisConfigArgs); + if (this.axisConfig.get('type') === 'category') { + this.values = this.axisConfig.values; + this.ordered = this.axisConfig.ordered; + } + this.axisScale = new AxisScale(this.axisConfig, visConfig); + this.axisTitle = new AxisTitle(this.axisConfig); + this.axisLabels = new AxisLabels(this.axisConfig, this.axisScale); + + this.stack = d3.layout.stack() + .x(d => { + return d.x; + }) + .y(d => { + if (this.axisConfig.get('scale.offset') === 'expand') { + return Math.abs(d.y); + } + return d.y; + }) + .offset(this.axisConfig.get('scale.offset', 'zero')); + + const stackedMode = ['normal', 'grouped'].includes(this.axisConfig.get('scale.mode')); + if (stackedMode) { + this.stack.out((d, y0, y) => { + return this._stackNegAndPosVals(d, y0, y); + }); + } + } + + /** + * Returns true for positive numbers + */ + _isPositive(num) { + return num >= 0; + }; + + /** + * Returns true for negative numbers + */ + _isNegative(num) { + return num < 0; + }; + + /** + * Adds two input values + */ + _addVals(a, b) { + return a + b; + }; + + /** + * Returns the results of the addition of numbers in a filtered array. + */ + _sumYs(arr, callback) { + const filteredArray = arr.filter(callback); + + return (filteredArray.length) ? filteredArray.reduce(this._addVals) : 0; + }; + + /** + * Calculates the d.y0 value for stacked data in D3. + */ + _calcYZero(y, arr) { + if (y === 0 && this._lastY0) return this._sumYs(arr, this._lastY0 > 0 ? this._isPositive : this._isNegative); + if (y >= 0) return this._sumYs(arr, this._isPositive); + return this._sumYs(arr, this._isNegative); + }; + + _getCounts(i, j) { + const data = this.visConfig.data.chartData(); + const dataLengths = {}; + + dataLengths.charts = data.length; + dataLengths.stacks = dataLengths.charts ? data[i].series.length : 0; + dataLengths.values = dataLengths.stacks ? data[i].series[j].values.length : 0; + + return dataLengths; + }; + + _createCache() { + const cache = { + index: { + chart: 0, + stack: 0, + value: 0 + }, + yValsArr: [] + }; + + cache.count = this._getCounts(cache.index.chart, cache.index.stack); + + return cache; + }; + /** + * Stacking function passed to the D3 Stack Layout `.out` API. + * See: https://github.com/mbostock/d3/wiki/Stack-Layout + * It is responsible for calculating the correct d.y0 value for + * mixed datasets containing both positive and negative values. + */ + _stackNegAndPosVals(d, y0, y) { + const data = this.visConfig.data.chartData(); + + // Storing counters and data characteristics needed to stack values properly + if (!this._cache) { + this._cache = this._createCache(); + } + + d.y0 = this._calcYZero(y, this._cache.yValsArr); + if (d.y0 > 0) this._lastY0 = 1; + if (d.y0 < 0) this._lastY0 = -1; + ++this._cache.index.stack; + + + // last stack, or last value, reset the stack count and y value array + const lastStack = (this._cache.index.stack >= this._cache.count.stacks); + if (lastStack) { + this._cache.index.stack = 0; + ++this._cache.index.value; + this._cache.yValsArr = []; + // still building the stack collection, push v value to array + } else if (y !== 0) { + this._cache.yValsArr.push(y); + } + + // last value, prepare for the next chart, if one exists + const lastValue = (this._cache.index.value >= this._cache.count.values); + if (lastValue) { + this._cache.index.value = 0; + ++this._cache.index.chart; + + // no more charts, reset the queue and finish + if (this._cache.index.chart >= this._cache.count.charts) { + this._cache = this._createCache(); + return; + } + + // get stack and value count for next chart + const chartSeries = data[this._cache.index.chart].series; + this._cache.count.stacks = chartSeries.length; + this._cache.count.values = chartSeries.length ? chartSeries[this._cache.index.stack].values.length : 0; + } + }; + + render() { + const elSelector = this.axisConfig.get('elSelector'); + const rootEl = this.axisConfig.get('rootEl'); + d3.select(rootEl).selectAll(elSelector).call(this.draw()); + } + + destroy() { + const elSelector = this.axisConfig.get('elSelector'); + const rootEl = this.axisConfig.get('rootEl'); + $(rootEl).find(elSelector).find('svg').remove(); + } + + getAxis(length) { + const scale = this.axisScale.getScale(length); + const position = this.axisConfig.get('position'); + const axisFormatter = this.axisConfig.get('labels.axisFormatter'); + + return d3.svg.axis() + .scale(scale) + .tickFormat(axisFormatter) + .ticks(this.tickScale(length)) + .orient(position); + } + + getScale() { + return this.axisScale.scale; + } + + addInterval(interval) { + return this.axisScale.addInterval(interval); + } + + substractInterval(interval) { + return this.axisScale.substractInterval(interval); + } + + tickScale(length) { + const yTickScale = d3.scale.linear() + .clamp(true) + .domain([20, 40, 1000]) + .range([0, 3, 11]); + + return Math.ceil(yTickScale(length)); + } + + getLength(el) { + if (this.axisConfig.isHorizontal()) { + return $(el).width(); + } else { + return $(el).height(); + } + } + + adjustSize() { + const config = this.axisConfig; + const style = config.get('style'); + const margin = this.visConfig.get('style.margin'); + const chartEl = this.visConfig.get('el'); + const position = config.get('position'); + const axisPadding = 5; + + return function (selection) { + const text = selection.selectAll('.tick text'); + const lengths = []; + + text.each(function textWidths() { + lengths.push((() => { + if (config.isHorizontal()) { + return d3.select(this.parentNode).node().getBBox().height; + } else { + return d3.select(this.parentNode).node().getBBox().width; + } + })()); + }); + let length = lengths.length > 0 ? _.max(lengths) : 0; + length += axisPadding; + + if (config.isHorizontal()) { + selection.attr('height', Math.ceil(length)); + if (position === 'top') { + selection.select('g') + .attr('transform', `translate(0, ${length - parseInt(style.lineWidth)})`); + selection.select('path') + .attr('transform', 'translate(1,0)'); + } + if (config.get('type') === 'value') { + const spacerNodes = $(chartEl).find(`.y-axis-spacer-block-${position}`); + const elHeight = $(chartEl).find(`.axis-wrapper-${position}`).height(); + spacerNodes.height(elHeight); + } + } else { + const axisWidth = Math.ceil(length); + selection.attr('width', axisWidth); + if (position === 'left') { + selection.select('g') + .attr('transform', `translate(${axisWidth},0)`); + } + } + }; + } + + validate() { + if (this.axisConfig.isLogScale() && this.axisConfig.isPercentage()) { + throw new errors.VislibError(`Can't mix percentage mode with log scale.`); + } + } + + draw() { + const self = this; + const config = this.axisConfig; + const style = config.get('style'); + + return function (selection) { + const n = selection[0].length; + if (self.axisTitle) { + self.axisTitle.render(selection); + } + selection.each(function () { + const el = this; + const div = d3.select(el); + const width = $(el).width(); + const height = $(el).height(); + const length = self.getLength(el, n); + + self.validate(); + + const axis = self.getAxis(length); + + if (config.get('show')) { + const svg = div.append('svg') + .attr('width', width) + .attr('height', height); + + const axisClass = self.axisConfig.isHorizontal() ? 'x' : 'y'; + svg.append('g') + .attr('class', `${axisClass} axis ${config.get('id')}`) + .call(axis); + + const container = svg.select('g.axis').node(); + if (container) { + svg.select('path') + .style('stroke', style.color) + .style('stroke-width', style.lineWidth) + .style('stroke-opacity', style.opacity); + svg.selectAll('line') + .style('stroke', style.tickColor) + .style('stroke-width', style.tickWidth) + .style('stroke-opacity', style.opacity); + } + if (self.axisLabels) self.axisLabels.render(svg); + svg.call(self.adjustSize()); + } + }); + }; + } + } + + return Axis; +}; diff --git a/src/ui/public/vislib/lib/axis/axis_config.js b/src/ui/public/vislib/lib/axis/axis_config.js new file mode 100644 index 0000000000000..8c02f45211752 --- /dev/null +++ b/src/ui/public/vislib/lib/axis/axis_config.js @@ -0,0 +1,187 @@ +import _ from 'lodash'; +import d3 from 'd3'; +import SCALE_MODES from './scale_modes'; + +export default function AxisConfigFactory() { + + const defaults = { + show: true, + type: 'value', + elSelector: '.axis-wrapper-{pos} .axis-div', + position: 'left', + scale: { + type: 'linear', + expandLastBucket: true, + inverted: false, + setYExtents: null, + defaultYExtents: null, + min: null, + max: null, + mode: SCALE_MODES.NORMAL + }, + style: { + color: '#ddd', + lineWidth: '1px', + opacity: 1, + tickColor: '#ddd', + tickWidth: '1px', + tickLength: '6px' + }, + labels: { + axisFormatter: null, + show: true, + rotate: 0, + rotateAnchor: 'center', + filter: false, + color: '#ddd', + font: '"Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif', + fontSize: '8pt', + truncate: 100 + }, + title: { + text: '', + elSelector: '.axis-wrapper-{pos} .axis-title' + } + }; + + const categoryDefaults = { + type: 'category', + position: 'bottom', + labels: { + rotate: 0, + rotateAnchor: 'end', + filter: true, + truncate: 0, + } + }; + + const valueDefaults = { + labels: { + axisFormatter: d3.format('n') + } + }; + + class AxisConfig { + constructor(chartConfig, axisConfigArgs) { + const typeDefaults = axisConfigArgs.type === 'category' ? categoryDefaults : valueDefaults; + // _.defaultsDeep mutates axisConfigArgs nested values so we clone it first + const axisConfigArgsClone = _.cloneDeep(axisConfigArgs); + this._values = _.defaultsDeep({}, axisConfigArgsClone, typeDefaults, defaults); + + this._values.elSelector = this._values.elSelector.replace('{pos}', this._values.position); + this._values.rootEl = chartConfig.get('el'); + + this.data = chartConfig.data; + if (this._values.type === 'category') { + if (!this._values.values) { + this.values = this.data.xValues(chartConfig.get('orderBucketsBySum', false)); + this.ordered = this.data.get('ordered'); + } else { + this.values = this._values.values; + } + if (!this._values.labels.axisFormatter) { + this._values.labels.axisFormatter = this.data.data.xAxisFormatter || this.data.get('xAxisFormatter'); + } + } + + if (this.get('type') === 'value') { + const isWiggleOrSilhouette = + this.get('scale.mode') === SCALE_MODES.WIGGLE || + this.get('scale.mode') === SCALE_MODES.SILHOUETTE; + // if show was not explicitly set and wiggle or silhouette option was checked + if (isWiggleOrSilhouette) { + this._values.scale.defaultYExtents = false; + + if (!axisConfigArgs.show) { + this._values.show = false; + this._values.title.show = true; + } + } + + // override axisFormatter (to replicate current behaviour) + if (this.isPercentage()) { + this._values.labels.axisFormatter = d3.format('%'); + this._values.scale.defaultYExtents = true; + } + + if (this.isLogScale()) { + this._values.labels.filter = true; + } + } + + // horizontal axis with ordinal scale should have labels rotated (so we can fit more) + // unless explicitly overriden by user + if (this.isHorizontal() && this.isOrdinal()) { + this._values.labels.filter = _.get(axisConfigArgs, 'labels.filter', false); + this._values.labels.rotate = _.get(axisConfigArgs, 'labels.rotate', 90); + } + + let offset; + let stacked = true; + switch (this.get('scale.mode')) { + case SCALE_MODES.NORMAL: + offset = 'zero'; + stacked = false; + break; + case SCALE_MODES.GROUPED: + offset = 'group'; + break; + case SCALE_MODES.PERCENTAGE: + offset = 'expand'; + break; + default: + offset = this.get('scale.mode'); + } + this.set('scale.offset', _.get(axisConfigArgs, 'scale.offset', offset)); + /* axis.scale.stacked means that axis stacking function should be run */ + this.set('scale.stacked', stacked); + }; + + get(property, defaults) { + if (typeof defaults !== 'undefined' || _.has(this._values, property)) { + return _.get(this._values, property, defaults); + } else { + throw new Error(`Accessing invalid config property: ${property}`); + return defaults; + } + }; + + set(property, value) { + return _.set(this._values, property, value); + }; + + isHorizontal() { + return (this._values.position === 'top' || this._values.position === 'bottom'); + }; + + isOrdinal() { + return !!this.values && (!this.isTimeDomain()); + }; + + isTimeDomain() { + return this.ordered && this.ordered.date; + }; + + isPercentage() { + return this._values.scale.mode === SCALE_MODES.PERCENTAGE; + }; + + isUserDefined() { + return this._values.scale.setYExtents; + }; + + isYExtents() { + return this._values.scale.defaultYExtents; + }; + + isLogScale() { + return this.getScaleType() === 'log'; + }; + + getScaleType() { + return this._values.scale.type; + }; + } + + return AxisConfig; +} diff --git a/src/ui/public/vislib/lib/axis/axis_labels.js b/src/ui/public/vislib/lib/axis/axis_labels.js new file mode 100644 index 0000000000000..3d666ee05b637 --- /dev/null +++ b/src/ui/public/vislib/lib/axis/axis_labels.js @@ -0,0 +1,133 @@ +import d3 from 'd3'; +import $ from 'jquery'; +import _ from 'lodash'; +export default function AxisLabelsFactory(Private) { + class AxisLabels { + constructor(axisConfig, scale) { + this.axisConfig = axisConfig; + this.axisScale = scale; + } + + render(selection) { + selection.call(this.draw()); + }; + + rotateAxisLabels() { + const config = this.axisConfig; + return function (selection) { + const text = selection.selectAll('.tick text'); + + if (config.get('labels.rotate')) { + text + .style('text-anchor', function () { + return config.get('labels.rotateAnchor') === 'center' ? 'center' : 'end'; + }) + .attr('dy', function () { + if (config.isHorizontal()) { + if (config.get('position') === 'top') return '-0.9em'; + else return '0.3em'; + } + return '0'; + }) + .attr('dx', function () { + return config.isHorizontal() ? '-0.9em' : '0'; + }) + .attr('transform', function rotate(d, j) { + let rotateDeg = config.get('labels.rotate'); + if (config.get('labels.rotateAnchor') === 'center') { + const coord = text[0][j].getBBox(); + const transX = ((coord.x) + (coord.width / 2)); + const transY = ((coord.y) + (coord.height / 2)); + return `rotate(${rotateDeg}, ${transX}, ${transY})`; + } else { + rotateDeg = config.get('position') === 'top' ? rotateDeg : -rotateDeg; + return `rotate(${rotateDeg})`; + } + }); + } + }; + }; + + truncateLabel(text, size) { + const node = d3.select(text).node(); + let str = $(node).text(); + const width = node.getBBox().width; + const chars = str.length; + const pxPerChar = width / chars; + let endChar = 0; + const ellipsesPad = 4; + + if (width > size) { + endChar = Math.floor((size / pxPerChar) - ellipsesPad); + while (str[endChar - 1] === ' ' || str[endChar - 1] === '-' || str[endChar - 1] === ',') { + endChar = endChar - 1; + } + str = str.substr(0, endChar) + '...'; + } + return str; + }; + + truncateLabels() { + const self = this; + const config = this.axisConfig; + return function (selection) { + if (!config.get('labels.truncate')) return; + + selection.selectAll('.tick text') + .text(function () { + return self.truncateLabel(this, config.get('labels.truncate')); + }); + }; + }; + + filterAxisLabels() { + const self = this; + const config = this.axisConfig; + let startPos = 0; + let padding = 1.1; + + return function (selection) { + if (!config.get('labels.filter')) return; + + selection.selectAll('.tick text') + .text(function (d) { + const par = d3.select(this.parentNode).node(); + const el = $(config.get('rootEl')).find(config.get('elSelector')); + const maxSize = config.isHorizontal() ? el.width() : el.height(); + const myPos = config.isHorizontal() ? self.axisScale.scale(d) : maxSize - self.axisScale.scale(d); + const mySize = (config.isHorizontal() ? par.getBBox().width : par.getBBox().height) * padding; + const halfSize = mySize / 2; + + if ((startPos + halfSize) < myPos && maxSize > (myPos + halfSize)) { + startPos = myPos + halfSize; + return this.innerHTML; + } else { + d3.select(this.parentNode).remove(); + } + }); + }; + }; + + draw() { + const self = this; + const config = this.axisConfig; + + return function (selection) { + selection.each(function () { + selection.selectAll('text') + .attr('style', function () { + const currentStyle = d3.select(this).attr('style'); + return `${currentStyle} font-size: ${config.get('labels.fontSize')};`; + }); + if (!config.get('labels.show')) selection.selectAll('text').attr('style', 'display: none;'); + + selection.call(self.truncateLabels()); + selection.call(self.rotateAxisLabels()); + selection.call(self.filterAxisLabels()); + }); + }; + }; + } + + return AxisLabels; +}; diff --git a/src/ui/public/vislib/lib/axis/axis_scale.js b/src/ui/public/vislib/lib/axis/axis_scale.js new file mode 100644 index 0000000000000..60fd583450d5e --- /dev/null +++ b/src/ui/public/vislib/lib/axis/axis_scale.js @@ -0,0 +1,203 @@ +import d3 from 'd3'; +import _ from 'lodash'; +import moment from 'moment'; +import errors from 'ui/errors'; + +export default function AxisScaleFactory(Private) { + class AxisScale { + constructor(axisConfig, visConfig) { + this.axisConfig = axisConfig; + this.visConfig = visConfig; + + if (this.axisConfig.get('type') === 'category') { + this.values = this.axisConfig.values; + this.ordered = this.axisConfig.ordered; + } + }; + + getScaleType() { + return this.axisConfig.getScaleType(); + }; + + validateUserExtents(domain) { + const config = this.axisConfig; + return domain.map((val) => { + val = parseInt(val, 10); + if (isNaN(val)) throw new Error(val + ' is not a valid number'); + if (config.isPercentage() && config.isUserDefined()) return val / 100; + return val; + }); + }; + + getTimeDomain(data) { + return [this.minExtent(data), this.maxExtent(data)]; + }; + + minExtent(data) { + return this.calculateExtent(data || this.values, 'min'); + }; + + maxExtent(data) { + return this.calculateExtent(data || this.values, 'max'); + }; + + calculateExtent(data, extent) { + const ordered = this.ordered; + const opts = [ordered[extent]]; + + let point = d3[extent](data); + if (this.axisConfig.get('scale.expandLastBucket') && extent === 'max') { + point = this.addInterval(point); + } + opts.push(point); + + return d3[extent](opts.reduce(function (opts, v) { + if (!_.isNumber(v)) v = +v; + if (!isNaN(v)) opts.push(v); + return opts; + }, [])); + }; + + addInterval(x) { + return this.modByInterval(x, +1); + }; + + subtractInterval(x) { + return this.modByInterval(x, -1); + }; + + modByInterval(x, n) { + const ordered = this.ordered; + if (!ordered) return x; + const interval = ordered.interval; + if (!interval) return x; + + if (!ordered.date) { + return x += (ordered.interval * n); + } + + const y = moment(x); + const method = n > 0 ? 'add' : 'subtract'; + + _.times(Math.abs(n), function () { + y[method](interval); + }); + + return y.valueOf(); + }; + + getAllPoints() { + const config = this.axisConfig; + const data = this.visConfig.data.chartData(); + const chartPoints = _.reduce(data, (chartPoints, chart, chartIndex) => { + const points = chart.series.reduce((points, seri, seriIndex) => { + const seriConfig = this.visConfig.get(`charts[${chartIndex}].series[${seriIndex}]`); + const matchingValueAxis = !!seriConfig.valueAxis && seriConfig.valueAxis === config.get('id'); + const isFirstAxis = config.get('id') === this.visConfig.get('valueAxes[0].id'); + + if (matchingValueAxis || (!seriConfig.valueAxis && isFirstAxis)) { + const axisPoints = seri.values.map(val => { + if (val.y0) { + return val.y0 + val.y; + } + return val.y; + }); + return points.concat(axisPoints); + } + return points; + }, []); + return chartPoints.concat(points); + }, []); + + return chartPoints; + }; + + getYMin() { + return d3.min(this.getAllPoints()); + }; + + getYMax() { + return d3.max(this.getAllPoints()); + }; + + getExtents() { + if (this.axisConfig.get('type') === 'category') { + if (this.axisConfig.isTimeDomain()) return this.getTimeDomain(this.values); + if (this.axisConfig.isOrdinal()) return this.values; + } + + const min = this.axisConfig.get('scale.min') || this.getYMin(); + const max = this.axisConfig.get('scale.max') || this.getYMax(); + const domain = [min, max]; + if (this.axisConfig.isUserDefined()) return this.validateUserExtents(domain); + if (this.axisConfig.isYExtents()) return domain; + if (this.axisConfig.isLogScale()) return this.logDomain(min, max); + return [Math.min(0, min), Math.max(0, max)]; + }; + + getRange(length) { + if (this.axisConfig.isHorizontal()) { + return !this.axisConfig.get('scale.inverted') ? [0, length] : [length, 0]; + } else { + return this.axisConfig.get('scale.inverted') ? [0, length] : [length, 0]; + } + }; + + throwCustomError(message) { + throw new Error(message); + }; + + throwLogScaleValuesError() { + throw new errors.InvalidLogScaleValues(); + }; + + logDomain(min, max) { + if (min < 0 || max < 0) return this.throwLogScaleValuesError(); + return [1, max]; + }; + + getD3Scale(scaleTypeArg) { + let scaleType = scaleTypeArg || 'linear'; + if (scaleType === 'square root') scaleType = 'sqrt'; + + if (this.axisConfig.isTimeDomain()) return d3.time.scale.utc(); // allow time scale + if (this.axisConfig.isOrdinal()) return d3.scale.ordinal(); + if (typeof d3.scale[scaleType] !== 'function') { + return this.throwCustomError(`Axis.getScaleType: ${scaleType} is not a function`); + } + + return d3.scale[scaleType](); + }; + + canApplyNice() { + const config = this.axisConfig; + return (!config.isUserDefined() && !config.isYExtents() && !config.isOrdinal() && !config.isTimeDomain()); + } + + getScale(length) { + const config = this.axisConfig; + const scale = this.getD3Scale(config.getScaleType()); + const domain = this.getExtents(); + const range = this.getRange(length); + this.scale = scale.domain(domain); + if (config.isOrdinal()) { + this.scale.rangeBands(range, 0.1); + } else { + this.scale.range(range); + } + + if (this.canApplyNice()) this.scale.nice(); + // Prevents bars from going off the chart when the y extents are within the domain range + if (this.visConfig.get('type') === 'histogram' && this.scale.clamp) this.scale.clamp(true); + + this.validateScale(this.scale); + + return this.scale; + }; + + validateScale(scale) { + if (!scale || _.isNaN(scale)) throw new Error('scale is ' + scale); + }; + } + return AxisScale; +}; diff --git a/src/ui/public/vislib/lib/axis/axis_title.js b/src/ui/public/vislib/lib/axis/axis_title.js new file mode 100644 index 0000000000000..22f0784fa05d9 --- /dev/null +++ b/src/ui/public/vislib/lib/axis/axis_title.js @@ -0,0 +1,53 @@ +import d3 from 'd3'; +import $ from 'jquery'; +export default function AxisTitleFactory(Private) { + + class AxisTitle { + constructor(axisConfig) { + this.axisConfig = axisConfig; + this.elSelector = this.axisConfig.get('title.elSelector').replace('{pos}', this.axisConfig.get('position')); + } + + render() { + d3.select(this.axisConfig.get('rootEl')).selectAll(this.elSelector).call(this.draw()); + }; + + draw() { + const config = this.axisConfig; + + return function (selection) { + selection.each(function () { + if (!config.get('show') && !config.get('title.show', false)) return; + + const el = this; + const div = d3.select(el); + const width = $(el).width(); + const height = $(el).height(); + + const svg = div.append('svg') + .attr('width', width) + .attr('height', height); + + const bbox = svg.append('text') + .attr('transform', function () { + if (config.isHorizontal()) { + return 'translate(' + width / 2 + ',11)'; + } + return 'translate(11,' + height / 2 + ') rotate(270)'; + }) + .attr('text-anchor', 'middle') + .text(config.get('title.text')) + .node() + .getBBox(); + + if (config.isHorizontal()) { + svg.attr('height', bbox.height); + } else { + svg.attr('width', bbox.height); + } + }); + }; + }; + } + return AxisTitle; +}; diff --git a/src/ui/public/vislib/lib/axis/scale_modes.js b/src/ui/public/vislib/lib/axis/scale_modes.js new file mode 100644 index 0000000000000..ce3aede11c67f --- /dev/null +++ b/src/ui/public/vislib/lib/axis/scale_modes.js @@ -0,0 +1,10 @@ +const SCALE_MODES = { + NORMAL: 'normal', + PERCENTAGE: 'percentage', + WIGGLE: 'wiggle', + SILHOUETTE: 'silhouette', + GROUPED: 'grouped', // this should not be a scale mode but it is at this point to make it compatible with old charts + ALL: ['normal', 'percentage', 'wiggle', 'silhouette'] +}; + +export default SCALE_MODES; diff --git a/src/ui/public/vislib/lib/axis_title.js b/src/ui/public/vislib/lib/axis_title.js deleted file mode 100644 index 7fa2f54230d23..0000000000000 --- a/src/ui/public/vislib/lib/axis_title.js +++ /dev/null @@ -1,73 +0,0 @@ -import d3 from 'd3'; -import $ from 'jquery'; -import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; -export default function AxisTitleFactory(Private) { - - const ErrorHandler = Private(VislibLibErrorHandlerProvider); - - /** - * Appends axis title(s) to the visualization - * - * @class AxisTitle - * @constructor - * @param el {HTMLElement} DOM element - * @param xTitle {String} X-axis title - * @param yTitle {String} Y-axis title - */ - class AxisTitle extends ErrorHandler { - constructor(el, xTitle, yTitle) { - super(); - this.el = el; - this.xTitle = xTitle; - this.yTitle = yTitle; - } - - /** - * Renders both x and y axis titles - * - * @method render - * @returns {HTMLElement} DOM Element with axis titles - */ - render() { - d3.select(this.el).select('.x-axis-title').call(this.draw(this.xTitle)); - d3.select(this.el).select('.y-axis-title').call(this.draw(this.yTitle)); - }; - - /** - * Appends an SVG with title text - * - * @method draw - * @param title {String} Axis title - * @returns {Function} Appends axis title to a D3 selection - */ - draw(title) { - const self = this; - - return function (selection) { - selection.each(function () { - const el = this; - const div = d3.select(el); - const width = $(el).width(); - const height = $(el).height(); - - self.validateWidthandHeight(width, height); - - div.append('svg') - .attr('width', width) - .attr('height', height) - .append('text') - .attr('transform', function () { - if (div.attr('class') === 'x-axis-title') { - return 'translate(' + width / 2 + ',11)'; - } - return 'translate(11,' + height / 2 + ')rotate(270)'; - }) - .attr('text-anchor', 'middle') - .text(title); - }); - }; - }; - } - - return AxisTitle; -}; diff --git a/src/ui/public/vislib/lib/chart_title.js b/src/ui/public/vislib/lib/chart_title.js index 1159f148eeb29..53ca787c8d5cd 100644 --- a/src/ui/public/vislib/lib/chart_title.js +++ b/src/ui/public/vislib/lib/chart_title.js @@ -1,34 +1,20 @@ import d3 from 'd3'; import _ from 'lodash'; -import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; -import VislibComponentsTooltipProvider from 'ui/vislib/components/tooltip'; +import ErrorHandlerProvider from './_error_handler'; +import TooltipProvider from '../components/tooltip'; export default function ChartTitleFactory(Private) { + const ErrorHandler = Private(ErrorHandlerProvider); + const Tooltip = Private(TooltipProvider); - const ErrorHandler = Private(VislibLibErrorHandlerProvider); - const Tooltip = Private(VislibComponentsTooltipProvider); - - /** - * Appends chart titles to the visualization - * - * @class ChartTitle - * @constructor - * @param el {HTMLElement} Reference to DOM element - */ class ChartTitle extends ErrorHandler { - constructor(el) { + constructor(visConfig) { super(); - this.el = el; - this.tooltip = new Tooltip('chart-title', el, function (d) { + this.el = visConfig.get('el'); + this.tooltip = new Tooltip('chart-title', this.el, function (d) { return '

' + _.escape(d.label) + '

'; }); } - /** - * Renders chart titles - * - * @method render - * @returns {D3.Selection|D3.Transition.Transition} DOM element with chart titles - */ render() { const el = d3.select(this.el).select('.chart-title').node(); const width = el ? el.clientWidth : 0; @@ -37,13 +23,6 @@ export default function ChartTitleFactory(Private) { return d3.select(this.el).selectAll('.chart-title').call(this.draw(width, height)); }; - /** - * Truncates chart title text - * - * @method truncate - * @param size {Number} Height or width of the HTML Element - * @returns {Function} Truncates text - */ truncate(size) { const self = this; @@ -72,25 +51,12 @@ export default function ChartTitleFactory(Private) { }; }; - /** - * Adds tooltip events on truncated chart titles - * - * @method addMouseEvents - * @param target {HTMLElement} DOM element to attach event listeners - * @returns {*} DOM element with event listeners attached - */ addMouseEvents(target) { if (this.tooltip) { return target.call(this.tooltip.render()); } }; - /** - * Appends chart titles to the visualization - * - * @method draw - * @returns {Function} Appends chart titles to a D3 selection - */ draw(width, height) { const self = this; @@ -119,8 +85,7 @@ export default function ChartTitleFactory(Private) { }); // truncate long chart titles - div.selectAll('text') - .call(self.truncate(size)); + div.selectAll('text').call(self.truncate(size)); }); }; }; diff --git a/src/ui/public/vislib/lib/data.js b/src/ui/public/vislib/lib/data.js index 9e23c01aa0cd7..d459573439daa 100644 --- a/src/ui/public/vislib/lib/data.js +++ b/src/ui/public/vislib/lib/data.js @@ -1,9 +1,9 @@ import d3 from 'd3'; import _ from 'lodash'; -import VislibComponentsZeroInjectionInjectZerosProvider from 'ui/vislib/components/zero_injection/inject_zeros'; -import VislibComponentsZeroInjectionOrderedXKeysProvider from 'ui/vislib/components/zero_injection/ordered_x_keys'; -import VislibComponentsLabelsLabelsProvider from 'ui/vislib/components/labels/labels'; -import VislibComponentsColorColorProvider from 'ui/vislib/components/color/color'; +import VislibComponentsZeroInjectionInjectZerosProvider from '../components/zero_injection/inject_zeros'; +import VislibComponentsZeroInjectionOrderedXKeysProvider from '../components/zero_injection/ordered_x_keys'; +import VislibComponentsLabelsLabelsProvider from '../components/labels/labels'; +import VislibComponentsColorColorProvider from '../components/color/color'; export default function DataFactory(Private) { const injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); @@ -21,172 +21,61 @@ export default function DataFactory(Private) { * @param attr {Object|*} Visualization options */ class Data { - constructor(data, attr, uiState) { + constructor(data, uiState) { this.uiState = uiState; - - const self = this; - let offset; - - if (attr.mode === 'stacked') { - offset = 'zero'; - } else if (attr.mode === 'percentage') { - offset = 'expand'; - } else if (attr.mode === 'grouped') { - offset = 'group'; - } else { - offset = attr.mode; - } - - this.data = data; + this.data = this.copyDataObj(data); this.type = this.getDataType(); this.labels = this._getLabels(this.data); this.color = this.labels ? color(this.labels, uiState.get('vis.colors')) : undefined; this._normalizeOrdered(); + } - this._attr = _.defaults(attr || {}, { - stack: d3.layout.stack() - .x(function (d) { - return d.x; - }) - .y(function (d) { - if (offset === 'expand') { - return Math.abs(d.y); + copyDataObj(data) { + const copyChart = data => { + const newData = {}; + Object.keys(data).forEach(key => { + if (key !== 'series') { + newData[key] = data[key]; + } else { + newData[key] = data[key].map(seri => { + return { + label: seri.label, + values: seri.values.map(val => { + const newVal = _.clone(val); + newVal.aggConfig = val.aggConfig; + newVal.aggConfigResult = val.aggConfigResult; + newVal.extraMetrics = val.extraMetrics; + return newVal; + }) + }; + }); } - return d.y; - }) - .offset(offset || 'zero') - }); + }); + return newData; + }; - if (attr.mode === 'stacked' && attr.type === 'histogram') { - this._attr.stack.out(function (d, y0, y) { - return self._stackNegAndPosVals(d, y0, y); + if (!data.series) { + const newData = {}; + Object.keys(data).forEach(key => { + if (!['rows', 'columns'].includes(key)) { + newData[key] = data[key]; + } + else { + newData[key] = data[key].map(chart => { + return copyChart(chart); + }); + } }); + return newData; } + return copyChart(data); } _getLabels(data) { return this.type === 'series' ? getLabels(data) : this.pieNames(); }; - /** - * Returns true for positive numbers - */ - _isPositive(num) { - return num >= 0; - }; - - /** - * Returns true for negative numbers - */ - _isNegative(num) { - return num < 0; - }; - - /** - * Adds two input values - */ - _addVals(a, b) { - return a + b; - }; - - /** - * Returns the results of the addition of numbers in a filtered array. - */ - _sumYs(arr, callback) { - const filteredArray = arr.filter(callback); - - return (filteredArray.length) ? filteredArray.reduce(this._addVals) : 0; - }; - - /** - * Calculates the d.y0 value for stacked data in D3. - */ - _calcYZero(y, arr) { - if (y >= 0) return this._sumYs(arr, this._isPositive); - return this._sumYs(arr, this._isNegative); - }; - - /** - * - */ - _getCounts(i, j) { - const data = this.chartData(); - const dataLengths = {}; - - dataLengths.charts = data.length; - dataLengths.stacks = dataLengths.charts ? data[i].series.length : 0; - dataLengths.values = dataLengths.stacks ? data[i].series[j].values.length : 0; - - return dataLengths; - }; - - /** - * - */ - _createCache() { - const cache = { - index: { - chart: 0, - stack: 0, - value: 0 - }, - yValsArr: [] - }; - - cache.count = this._getCounts(cache.index.chart, cache.index.stack); - - return cache; - }; - - /** - * Stacking function passed to the D3 Stack Layout `.out` API. - * See: https://github.com/mbostock/d3/wiki/Stack-Layout - * It is responsible for calculating the correct d.y0 value for - * mixed datasets containing both positive and negative values. - */ - _stackNegAndPosVals(d, y0, y) { - const data = this.chartData(); - - // Storing counters and data characteristics needed to stack values properly - if (!this._cache) { - this._cache = this._createCache(); - } - - d.y0 = this._calcYZero(y, this._cache.yValsArr); - ++this._cache.index.stack; - - - // last stack, or last value, reset the stack count and y value array - const lastStack = (this._cache.index.stack >= this._cache.count.stacks); - if (lastStack) { - this._cache.index.stack = 0; - ++this._cache.index.value; - this._cache.yValsArr = []; - // still building the stack collection, push v value to array - } else if (y !== 0) { - this._cache.yValsArr.push(y); - } - - // last value, prepare for the next chart, if one exists - const lastValue = (this._cache.index.value >= this._cache.count.values); - if (lastValue) { - this._cache.index.value = 0; - ++this._cache.index.chart; - - // no more charts, reset the queue and finish - if (this._cache.index.chart >= this._cache.count.charts) { - this._cache = this._createCache(); - return; - } - - // get stack and value count for next chart - const chartSeries = data[this._cache.index.chart].series; - this._cache.count.stacks = chartSeries.length; - this._cache.count.values = chartSeries.length ? chartSeries[this._cache.index.stack].values.length : 0; - } - }; - getDataType() { const data = this.getVisData(); let type; @@ -219,6 +108,48 @@ export default function DataFactory(Private) { return [this.data]; }; + shouldBeStacked(seriesConfig) { + const isHistogram = (seriesConfig.type === 'histogram'); + const isArea = (seriesConfig.type === 'area'); + const stacked = (seriesConfig.mode === 'stacked'); + + return (isHistogram || isArea) && stacked; + }; + + getStackedSeries(chartConfig, axis, series, first = false) { + const matchingSeries = []; + chartConfig.series.forEach((seriArgs, i) => { + const matchingAxis = seriArgs.valueAxis === axis.axisConfig.get('id') || (!seriArgs.valueAxis && first); + if (matchingAxis && (this.shouldBeStacked(seriArgs) || axis.axisConfig.get('scale.stacked'))) { + matchingSeries.push(series[i]); + } + }); + return matchingSeries; + }; + + stackChartData(handler, data, chartConfig) { + const stackedData = {}; + handler.valueAxes.forEach((axis, i) => { + const id = axis.axisConfig.get('id'); + stackedData[id] = this.getStackedSeries(chartConfig, axis, data, i === 0); + stackedData[id] = this.injectZeros(stackedData[id], handler.visConfig.get('orderBucketsBySum', false)); + axis.stack(_.map(stackedData[id], 'values')); + }); + return stackedData; + }; + + stackData(handler) { + const data = this.data; + if (data.rows || data.columns) { + const charts = data.rows ? data.rows : data.columns; + charts.forEach((chart, i) => { + this.stackChartData(handler, chart.series, handler.visConfig.get(`charts[${i}]`)); + }); + } else { + this.stackChartData(handler, data.series, handler.visConfig.get('charts[0]')); + } + } + /** * Returns an array of chart data objects * @@ -317,25 +248,6 @@ export default function DataFactory(Private) { .value(); }; - /** - * Determines whether histogram charts should be stacked - * TODO: need to make this more generic - * - * @method shouldBeStacked - * @returns {boolean} - */ - shouldBeStacked() { - const isHistogram = (this._attr.type === 'histogram'); - const isArea = (this._attr.type === 'area'); - const isOverlapping = (this._attr.mode === 'overlap'); - const grouped = (this._attr.mode === 'grouped'); - - const stackedHisto = isHistogram && !grouped; - const stackedArea = isArea && !isOverlapping; - - return stackedHisto || stackedArea; - }; - /** * Validates that the Y axis min value defined by user input * is a number. @@ -350,135 +262,6 @@ export default function DataFactory(Private) { return val; }; - /** - * Calculates the lowest Y value across all charts, taking - * stacking into consideration. - * - * @method getYMin - * @param {function} [getValue] - optional getter that will receive a - * point and should return the value that should - * be considered - * @returns {Number} Min y axis value - */ - getYMin(getValue) { - const self = this; - - if (this._attr.mode === 'percentage' || this._attr.mode === 'wiggle' || - this._attr.mode === 'silhouette') { - return 0; - } - - const flat = this.flatten(); - // if there is only one data point and its less than zero, - // return 0 as the yMax value. - if (!flat.length || flat.length === 1 && flat[0].y > 0) { - return 0; - } - - let min = Infinity; - - // for each object in the dataArray, - // push the calculated y value to the initialized array (arr) - _.each(this.chartData(), function (chart) { - const calculatedMin = self._getYExtent(chart, 'min', getValue); - if (!_.isUndefined(calculatedMin)) { - min = Math.min(min, calculatedMin); - } - }); - - return min; - }; - - /** - * Calculates the highest Y value across all charts, taking - * stacking into consideration. - * - * @method getYMax - * @param {function} [getValue] - optional getter that will receive a - * point and should return the value that should - * be considered - * @returns {Number} Max y axis value - */ - getYMax(getValue) { - const self = this; - - if (self._attr.mode === 'percentage') { - return 1; - } - - const flat = this.flatten(); - // if there is only one data point and its less than zero, - // return 0 as the yMax value. - if (!flat.length || flat.length === 1 && flat[0].y < 0) { - return 0; - } - - let max = -Infinity; - - // for each object in the dataArray, - // push the calculated y value to the initialized array (arr) - _.each(this.chartData(), function (chart) { - const calculatedMax = self._getYExtent(chart, 'max', getValue); - if (!_.isUndefined(calculatedMax)) { - max = Math.max(max, calculatedMax); - } - }); - - return max; - }; - - /** - * Calculates the stacked values for each data object - * - * @method stackData - * @param series {Array} Array of data objects - * @returns {*} Array of data objects with x, y, y0 keys - */ - stackData(series) { - // Should not stack values on line chart - if (this._attr.type === 'line') return series; - return this._attr.stack(series); - }; - - /** - * Returns the max Y axis value for a `series` array based on - * a specified callback function (calculation). - * @param {function} [getValue] - Optional getter that will be used to read - * values from points when calculating the extent. - * default is either this._getYStack or this.getY - * based on this.shouldBeStacked(). - */ - _getYExtent(chart, extent, getValue) { - if (this.shouldBeStacked()) { - this.stackData(_.pluck(chart.series, 'values')); - getValue = getValue || this._getYStack; - } else { - getValue = getValue || this._getY; - } - - const points = chart.series - .reduce(function (points, series) { - return points.concat(series.values); - }, []) - .map(getValue); - - return d3[extent](points); - }; - - /** - * Calculates the y stack value for each data object - */ - _getYStack(d) { - return d.y0 + d.y; - }; - - /** - * Calculates the Y max value - */ - _getY(d) { - return d.y; - }; - /** * Helper function for getNames * Returns an array of objects with a name (key) value and an index value. @@ -590,8 +373,8 @@ export default function DataFactory(Private) { * @method injectZeros * @returns {Object} Data object with zeros injected */ - injectZeros() { - return injectZeros(this.data); + injectZeros(data, orderBucketsBySum = false) { + return injectZeros(data, this.data, orderBucketsBySum); }; /** @@ -600,8 +383,8 @@ export default function DataFactory(Private) { * @method xValues * @returns {Array} Array of x axis values */ - xValues() { - return orderKeys(this.data, this._attr.orderBucketsBySum); + xValues(orderBucketsBySum = false) { + return orderKeys(this.data, orderBucketsBySum); }; /** diff --git a/src/ui/public/vislib/lib/dispatch.js b/src/ui/public/vislib/lib/dispatch.js index 55da4ef9ff2f8..555899713358d 100644 --- a/src/ui/public/vislib/lib/dispatch.js +++ b/src/ui/public/vislib/lib/dispatch.js @@ -26,21 +26,21 @@ export default function DispatchClass(Private, config) { * @param d {Object} Data point * @param i {Number} Index number of data point * @returns {{value: *, point: *, label: *, color: *, pointIndex: *, - * series: *, config: *, data: (Object|*), - * e: (d3.event|*), handler: (Object|*)}} Event response object + * series: *, config: *, data: (Object|*), + * e: (d3.event|*), handler: (Object|*)}} Event response object */ eventResponse(d, i) { const datum = d._input || d; const data = d3.event.target.nearestViewportElement ? d3.event.target.nearestViewportElement.__data__ : d3.event.target.__data__; - const label = d.label ? d.label : d.name; + const label = d.label ? d.label : (d.series || 'Count'); const isSeries = !!(data && data.series); const isSlices = !!(data && data.slices); const series = isSeries ? data.series : undefined; const slices = isSlices ? data.slices : undefined; const handler = this.handler; const color = _.get(handler, 'data.color'); - const isPercentage = (handler && handler._attr.mode === 'percentage'); + const isPercentage = (handler && handler.visConfig.get('mode') === 'percentage'); const eventData = { value: d.y, @@ -51,7 +51,7 @@ export default function DispatchClass(Private, config) { pointIndex: i, series: series, slices: slices, - config: handler && handler._attr, + config: handler && handler.visConfig, data: data, e: d3.event, handler: handler @@ -59,12 +59,14 @@ export default function DispatchClass(Private, config) { if (isSeries) { // Find object with the actual d value and add it to the point object - const object = _.find(series, {'label': d.label}); - eventData.value = +object.values[i].y; + const object = _.find(series, {'label': label}); + if (object) { + eventData.value = +object.values[i].y; - if (isPercentage) { - // Add the formatted percentage to the point object - eventData.percent = (100 * d.y).toFixed(1) + '%'; + if (isPercentage) { + // Add the formatted percentage to the point object + eventData.percent = (100 * d.y).toFixed(1) + '%'; + } } } @@ -161,7 +163,7 @@ export default function DispatchClass(Private, config) { * @returns {Boolean} */ allowBrushing() { - const xAxis = this.handler.xAxis; + const xAxis = this.handler.categoryAxes[0]; //Allow brushing for ordered axis - date histogram and histogram return Boolean(xAxis.ordered); @@ -186,7 +188,7 @@ export default function DispatchClass(Private, config) { if (!this.isBrushable()) return; const self = this; - const xScale = this.handler.xAxis.xScale; + const xScale = this.handler.categoryAxes[0].getScale(); const brush = this.createBrush(xScale, svg); function simulateClickWithBrushEnabled(d, i) { @@ -237,7 +239,7 @@ export default function DispatchClass(Private, config) { const dimming = config.get('visualization:dimmingOpacity'); $(element).parent().find('[data-label]') .css('opacity', 1)//Opacity 1 is needed to avoid the css application - .not((els, el) => $(el).data('label') === label) + .not((els, el) => String($(el).data('label')) === label) .css('opacity', justifyOpacity(dimming)); } @@ -260,14 +262,19 @@ export default function DispatchClass(Private, config) { */ createBrush(xScale, svg) { const self = this; - const attr = self.handler._attr; - const height = attr.height; - const margin = attr.margin; + const visConfig = self.handler.visConfig; + const {width, height} = svg.node().getBBox(); + const isHorizontal = self.handler.categoryAxes[0].axisConfig.isHorizontal(); // Brush scale - const brush = d3.svg.brush() - .x(xScale) - .on('brushend', function brushEnd() { + const brush = d3.svg.brush(); + if (isHorizontal) { + brush.x(xScale); + } else { + brush.y(xScale); + } + + brush.on('brushend', function brushEnd() { // Assumes data is selected at the chart level // In this case, the number of data objects should always be 1 @@ -282,7 +289,7 @@ export default function DispatchClass(Private, config) { return self.emit('brush', { range: range, - config: attr, + config: visConfig, e: d3.event, data: data }); @@ -290,19 +297,24 @@ export default function DispatchClass(Private, config) { // if `addBrushing` is true, add brush canvas if (self.listenerCount('brush')) { - svg.insert('g', 'g') - .attr('class', 'brush') - .call(brush) - .call(function (brushG) { - // hijack the brush start event to filter out right/middle clicks - const brushHandler = brushG.on('mousedown.brush'); - if (!brushHandler) return; // touch events in use - brushG.on('mousedown.brush', function () { - if (validBrushClick(d3.event)) brushHandler.apply(this, arguments); - }); - }) - .selectAll('rect') - .attr('height', height - margin.top - margin.bottom); + const rect = svg.insert('g', 'g') + .attr('class', 'brush') + .call(brush) + .call(function (brushG) { + // hijack the brush start event to filter out right/middle clicks + const brushHandler = brushG.on('mousedown.brush'); + if (!brushHandler) return; // touch events in use + brushG.on('mousedown.brush', function () { + if (validBrushClick(d3.event)) brushHandler.apply(this, arguments); + }); + }) + .selectAll('rect'); + + if (isHorizontal) { + rect.attr('height', height); + } else { + rect.attr('width', width); + } return brush; } diff --git a/src/ui/public/vislib/lib/handler/handler.js b/src/ui/public/vislib/lib/handler.js similarity index 79% rename from src/ui/public/vislib/lib/handler/handler.js rename to src/ui/public/vislib/lib/handler.js index 6d5885020f024..c531f62284b0c 100644 --- a/src/ui/public/vislib/lib/handler/handler.js +++ b/src/ui/public/vislib/lib/handler.js @@ -3,12 +3,18 @@ import _ from 'lodash'; import $ from 'jquery'; import errors from 'ui/errors'; import Binder from 'ui/binder'; -import VislibLibDataProvider from 'ui/vislib/lib/data'; -import VislibLibLayoutLayoutProvider from 'ui/vislib/lib/layout/layout'; -export default function HandlerBaseClass(Private) { +import VislibLibLayoutLayoutProvider from './layout/layout'; +import VislibLibChartTitleProvider from './chart_title'; +import VislibLibAlertsProvider from './alerts'; +import VislibAxisProvider from './axis/axis'; +import VislibVisualizationsVisTypesProvider from '../visualizations/vis_types'; - const Data = Private(VislibLibDataProvider); +export default function HandlerBaseClass(Private) { + const chartTypes = Private(VislibVisualizationsVisTypesProvider); const Layout = Private(VislibLibLayoutLayoutProvider); + const ChartTitle = Private(VislibLibChartTitleProvider); + const Alerts = Private(VislibLibAlertsProvider); + const Axis = Private(VislibAxisProvider); /** * Handles building all the components of the visualization @@ -20,34 +26,40 @@ export default function HandlerBaseClass(Private) { * create the visualization */ class Handler { - constructor(vis, opts) { - this.data = opts.data || new Data(vis.data, vis._attr, vis.uiState); - this.vis = vis; - this.el = vis.el; - this.ChartClass = vis.ChartClass; + constructor(vis, visConfig) { + this.el = visConfig.get('el'); + this.ChartClass = chartTypes[visConfig.get('type')]; this.charts = []; - this._attr = _.defaults(vis._attr || {}, { - 'margin': {top: 10, right: 3, bottom: 5, left: 3} - }); + this.vis = vis; + this.visConfig = visConfig; + this.data = visConfig.data; - this.xAxis = opts.xAxis; - this.yAxis = opts.yAxis; - this.chartTitle = opts.chartTitle; - this.axisTitle = opts.axisTitle; - this.alerts = opts.alerts; + this.categoryAxes = visConfig.get('categoryAxes').map(axisArgs => new Axis(visConfig, axisArgs)); + this.valueAxes = visConfig.get('valueAxes').map(axisArgs => new Axis(visConfig, axisArgs)); + this.chartTitle = new ChartTitle(visConfig); + this.alerts = new Alerts(this, visConfig.get('alerts')); - this.layout = new Layout(vis.el, vis.data, vis._attr.type, opts); + if (visConfig.get('type') === 'point_series') { + this.data.stackData(this); + } + + if (visConfig.get('resize', false)) { + this.resize = visConfig.get('resize'); + } + + this.layout = new Layout(visConfig); this.binder = new Binder(); this.renderArray = _.filter([ this.layout, - this.axisTitle, this.chartTitle, - this.alerts, - this.xAxis, - this.yAxis, + this.alerts ], Boolean); + this.renderArray = this.renderArray + .concat(this.valueAxes) + .concat(this.categoryAxes); + // memoize so that the same function is returned every time, // allowing us to remove/re-add the same function this.getProxyHandler = _.memoize(function (event) { diff --git a/src/ui/public/vislib/lib/handler/handler_types.js b/src/ui/public/vislib/lib/handler/handler_types.js deleted file mode 100644 index 5473181a97f68..0000000000000 --- a/src/ui/public/vislib/lib/handler/handler_types.js +++ /dev/null @@ -1,20 +0,0 @@ -import VislibLibHandlerTypesPointSeriesProvider from 'ui/vislib/lib/handler/types/point_series'; -import VislibLibHandlerTypesPieProvider from 'ui/vislib/lib/handler/types/pie'; -import VislibLibHandlerTypesTileMapProvider from 'ui/vislib/lib/handler/types/tile_map'; - -export default function HandlerTypeFactory(Private) { - const pointSeries = Private(VislibLibHandlerTypesPointSeriesProvider); - - /** - * Handles the building of each visualization - * - * @return {Function} Returns an Object of Handler types - */ - return { - histogram: pointSeries.column, - line: pointSeries.line, - pie: Private(VislibLibHandlerTypesPieProvider), - area: pointSeries.area, - tile_map: Private(VislibLibHandlerTypesTileMapProvider) - }; -}; diff --git a/src/ui/public/vislib/lib/handler/types/pie.js b/src/ui/public/vislib/lib/handler/types/pie.js deleted file mode 100644 index f119f21f60042..0000000000000 --- a/src/ui/public/vislib/lib/handler/types/pie.js +++ /dev/null @@ -1,17 +0,0 @@ -import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; -import VislibLibChartTitleProvider from 'ui/vislib/lib/chart_title'; - -export default function PieHandler(Private) { - const Handler = Private(VislibLibHandlerHandlerProvider); - const ChartTitle = Private(VislibLibChartTitleProvider); - - /* - * Handler for Pie visualizations. - */ - - return function (vis) { - return new Handler(vis, { - chartTitle: new ChartTitle(vis.el) - }); - }; -}; diff --git a/src/ui/public/vislib/lib/handler/types/point_series.js b/src/ui/public/vislib/lib/handler/types/point_series.js deleted file mode 100644 index bfbc57f714dc6..0000000000000 --- a/src/ui/public/vislib/lib/handler/types/point_series.js +++ /dev/null @@ -1,99 +0,0 @@ -import VislibComponentsZeroInjectionInjectZerosProvider from 'ui/vislib/components/zero_injection/inject_zeros'; -import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; -import VislibLibDataProvider from 'ui/vislib/lib/data'; -import VislibLibXAxisProvider from 'ui/vislib/lib/x_axis'; -import VislibLibYAxisProvider from 'ui/vislib/lib/y_axis'; -import VislibLibAxisTitleProvider from 'ui/vislib/lib/axis_title'; -import VislibLibChartTitleProvider from 'ui/vislib/lib/chart_title'; -import VislibLibAlertsProvider from 'ui/vislib/lib/alerts'; - -export default function ColumnHandler(Private) { - const injectZeros = Private(VislibComponentsZeroInjectionInjectZerosProvider); - const Handler = Private(VislibLibHandlerHandlerProvider); - const Data = Private(VislibLibDataProvider); - const XAxis = Private(VislibLibXAxisProvider); - const YAxis = Private(VislibLibYAxisProvider); - const AxisTitle = Private(VislibLibAxisTitleProvider); - const ChartTitle = Private(VislibLibChartTitleProvider); - const Alerts = Private(VislibLibAlertsProvider); - - function getData(vis, opts) { - if (opts.zeroFill) { - return new Data(injectZeros(vis.data, vis._attr.orderBucketsBySum), vis._attr, vis.uiState); - } else { - return new Data(vis.data, vis._attr, vis.uiState); - } - } - /* - * Create handlers for Area, Column, and Line charts which - * are all nearly the same minus a few details - */ - function create(opts) { - opts = opts || {}; - - return function (vis) { - const isUserDefinedYAxis = vis._attr.setYExtents; - const data = getData(vis, opts); - - return new Handler(vis, { - data: data, - axisTitle: new AxisTitle(vis.el, data.get('xAxisLabel'), data.get('yAxisLabel')), - chartTitle: new ChartTitle(vis.el), - xAxis: new XAxis({ - el : vis.el, - xValues : data.xValues(), - ordered : data.get('ordered'), - xAxisFormatter : data.get('xAxisFormatter'), - expandLastBucket : opts.expandLastBucket, - _attr : vis._attr - }), - alerts: new Alerts(vis, data, opts.alerts), - yAxis: new YAxis({ - el : vis.el, - yMin : isUserDefinedYAxis ? vis._attr.yAxis.min : data.getYMin(), - yMax : isUserDefinedYAxis ? vis._attr.yAxis.max : data.getYMax(), - yAxisFormatter: data.get('yAxisFormatter'), - _attr: vis._attr - }) - }); - - }; - } - - return { - line: create(), - - column: create({ - zeroFill: true, - expandLastBucket: true - }), - - area: create({ - zeroFill: true, - 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 (vis, 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 (vis, data) { - return data.hasNullValues(); - } - } - ] - }) - }; -}; diff --git a/src/ui/public/vislib/lib/handler/types/tile_map.js b/src/ui/public/vislib/lib/handler/types/tile_map.js deleted file mode 100644 index c6ac20996a4fe..0000000000000 --- a/src/ui/public/vislib/lib/handler/types/tile_map.js +++ /dev/null @@ -1,24 +0,0 @@ -import VislibLibHandlerHandlerProvider from 'ui/vislib/lib/handler/handler'; -import VislibLibDataProvider from 'ui/vislib/lib/data'; -export default function MapHandlerProvider(Private) { - - const Handler = Private(VislibLibHandlerHandlerProvider); - const Data = Private(VislibLibDataProvider); - - return function (vis) { - const data = new Data(vis.data, vis._attr, vis.uiState); - - const MapHandler = new Handler(vis, { - data: data - }); - - MapHandler.resize = function () { - this.charts.forEach(function (chart) { - chart.resizeArea(); - }); - }; - - return MapHandler; - }; -}; - diff --git a/src/ui/public/vislib/lib/layout/layout.js b/src/ui/public/vislib/lib/layout/layout.js index 3e71225a00d92..d689e0742fe22 100644 --- a/src/ui/public/vislib/lib/layout/layout.js +++ b/src/ui/public/vislib/lib/layout/layout.js @@ -1,10 +1,12 @@ import d3 from 'd3'; import _ from 'lodash'; -import VislibLibLayoutLayoutTypesProvider from 'ui/vislib/lib/layout/layout_types'; +import $ from 'jquery'; +import VislibLibLayoutLayoutTypesProvider from './layout_types'; +import AxisProvider from 'ui/vislib/lib/axis'; export default function LayoutFactory(Private) { const layoutType = Private(VislibLibLayoutLayoutTypesProvider); - + const Axis = Private(AxisProvider); /** * Builds the visualization DOM layout * @@ -22,11 +24,11 @@ export default function LayoutFactory(Private) { * @param chartType {Object} Reference to chart functions, i.e. Pie */ class Layout { - constructor(el, data, chartType, opts) { - this.el = el; - this.data = data; - this.opts = opts; - this.layoutType = layoutType[chartType](this.el, this.data); + constructor(config) { + this.el = config.get('el'); + this.data = config.data.data; + this.opts = config; + this.layoutType = layoutType[config.get('type')](this.el, this.data); } // Render the layout @@ -39,6 +41,10 @@ export default function LayoutFactory(Private) { render() { this.removeAll(this.el); this.createLayout(this.layoutType); + // update y-axis-spacer height based on precalculated horizontal axis heights + if (this.opts.get('type') === 'point_series') { + this.updateCategoryAxisSize(); + } }; /** @@ -50,13 +56,36 @@ export default function LayoutFactory(Private) { * @returns {*} Creates the visualization layout */ createLayout(arr) { - const self = this; - - return _.each(arr, function (obj) { - self.layout(obj); + return _.each(arr, (obj) => { + this.layout(obj); }); }; + updateCategoryAxisSize() { + const visConfig = this.opts; + const axisConfig = visConfig.get('categoryAxes[0]'); + const axis = new Axis(visConfig, axisConfig); + const position = axis.axisConfig.get('position'); + + const el = $(this.el).find(`.axis-wrapper-${position}`); + + el.css('visibility', 'hidden'); + axis.render(); + const width = el.width(); + const height = el.height(); + axis.destroy(); + el.css('visibility', ''); + + if (axis.axisConfig.isHorizontal()) { + const spacerNodes = $(this.el).find(`.y-axis-spacer-block-${position}`); + el.height(`${height}px`); + spacerNodes.height(el.height()); + } else { + el.find('.y-axis-div-wrapper').width(`${width}px`); + } + }; + + /** * Appends a DOM element based on the object keys * check to see if reference to DOM element is string but not class selector diff --git a/src/ui/public/vislib/lib/layout/layout_types.js b/src/ui/public/vislib/lib/layout/layout_types.js index b06acdc4c7520..cb2dd59165b32 100644 --- a/src/ui/public/vislib/lib/layout/layout_types.js +++ b/src/ui/public/vislib/lib/layout/layout_types.js @@ -1,6 +1,6 @@ -import VislibLibLayoutTypesColumnLayoutProvider from 'ui/vislib/lib/layout/types/column_layout'; -import VislibLibLayoutTypesPieLayoutProvider from 'ui/vislib/lib/layout/types/pie_layout'; -import VislibLibLayoutTypesMapLayoutProvider from 'ui/vislib/lib/layout/types/map_layout'; +import VislibLibLayoutTypesColumnLayoutProvider from './types/column_layout'; +import VislibLibLayoutTypesPieLayoutProvider from './types/pie_layout'; +import VislibLibLayoutTypesMapLayoutProvider from './types/map_layout'; export default function LayoutTypeFactory(Private) { @@ -13,10 +13,8 @@ export default function LayoutTypeFactory(Private) { * @return {Function} Returns an Object of HTML layouts for each visualization class */ return { - histogram: Private(VislibLibLayoutTypesColumnLayoutProvider), - line: Private(VislibLibLayoutTypesColumnLayoutProvider), - area: Private(VislibLibLayoutTypesColumnLayoutProvider), pie: Private(VislibLibLayoutTypesPieLayoutProvider), - tile_map: Private(VislibLibLayoutTypesMapLayoutProvider) + tile_map: Private(VislibLibLayoutTypesMapLayoutProvider), + point_series: Private(VislibLibLayoutTypesColumnLayoutProvider) }; }; diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js index f32fc7a3ce364..838d7a3b339b4 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js @@ -7,7 +7,7 @@ define(function () { * For example, if the data has rows, it returns the same number of * `.chart` elements as row objects. */ - return function split(selection) { + return function split(selection, parent) { selection.each(function (data) { const div = d3.select(this) .attr('class', function () { @@ -16,29 +16,42 @@ define(function () { } else if (data.columns) { return 'chart-wrapper-column'; } else { - return 'chart-wrapper'; + if (parent) { + return 'chart-first chart-last chart-wrapper'; + } + return this.className + ' chart-wrapper'; } }); - let divClass; + let divClass = ''; + let chartsNumber; const charts = div.selectAll('charts') .append('div') .data(function (d) { if (d.rows) { - divClass = 'chart-row'; + chartsNumber = d.rows.length; return d.rows; } else if (d.columns) { - divClass = 'chart-column'; + chartsNumber = d.columns.length; return d.columns; } else { divClass = 'chart'; + chartsNumber = 1; return [d]; } }) .enter() .append('div') - .attr('class', function () { - return divClass; + .attr('class', function (d, i) { + let fullDivClass = divClass; + if (chartsNumber > 1) { + if (i === 0) { + fullDivClass += ' chart-first'; + } else if (i === chartsNumber - 1) { + fullDivClass += ' chart-last'; + } + } + return fullDivClass; }); if (!data.series) { diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js index 2d99d4bbd25bc..98290729082c6 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js @@ -11,15 +11,25 @@ define(function () { return function (selection) { selection.each(function () { const div = d3.select(this); - + let columns; div.selectAll('.x-axis-div') .append('div') .data(function (d) { + columns = d.columns ? d.columns.length : 1; return d.columns ? d.columns : [d]; }) .enter() .append('div') - .attr('class', 'x-axis-div'); + .attr('class', (d, i) => { + let divClass = ''; + if (i === 0) { + divClass += ' chart-first'; + } + if (i === columns - 1) { + divClass += ' chart-last'; + } + return 'x-axis-div axis-div' + divClass; + }); }); }; }; diff --git a/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js b/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js index 93d8188073a18..2fbc55bcae522 100644 --- a/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js +++ b/src/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js @@ -9,40 +9,31 @@ define(function () { */ // render and get bounding box width - return function (selection, parent, opts) { - const yAxis = opts && opts.yAxis; + return function (selection) { selection.each(function () { const div = d3.select(this); - - div.call(setWidth, yAxis); + let rows; div.selectAll('.y-axis-div') .append('div') .data(function (d) { + rows = d.rows ? d.rows.length : 1; return d.rows ? d.rows : [d]; }) .enter() .append('div') - .attr('class', 'y-axis-div'); + .attr('class', (d, i) => { + let divClass = ''; + if (i === 0) { + divClass += ' chart-first'; + } + if (i === rows - 1) { + divClass += ' chart-last'; + } + return 'y-axis-div axis-div' + divClass; + }); }); }; - - function setWidth(el, yAxis) { - if (!yAxis) return; - - const padding = 5; - const height = parseInt(el.node().clientHeight, 10); - - // render svg and get the width of the bounding box - const svg = d3.select('body') - .append('svg') - .attr('style', 'position:absolute; top:-10000; left:-10000'); - const width = svg.append('g') - .call(yAxis.getYAxis(height)).node().getBBox().width + padding; - svg.remove(); - - el.style('width', (width + padding) + 'px'); - } }; }); diff --git a/src/ui/public/vislib/lib/layout/types/column_layout.js b/src/ui/public/vislib/lib/layout/types/column_layout.js index b4bd968d2ae9c..d74d031501109 100644 --- a/src/ui/public/vislib/lib/layout/types/column_layout.js +++ b/src/ui/public/vislib/lib/layout/types/column_layout.js @@ -1,7 +1,7 @@ -import VislibLibLayoutSplitsColumnChartChartSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/chart_split'; -import VislibLibLayoutSplitsColumnChartYAxisSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/y_axis_split'; -import VislibLibLayoutSplitsColumnChartXAxisSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/x_axis_split'; -import VislibLibLayoutSplitsColumnChartChartTitleSplitProvider from 'ui/vislib/lib/layout/splits/column_chart/chart_title_split'; +import VislibLibLayoutSplitsColumnChartChartSplitProvider from '../splits/column_chart/chart_split'; +import VislibLibLayoutSplitsColumnChartYAxisSplitProvider from '../splits/column_chart/y_axis_split'; +import VislibLibLayoutSplitsColumnChartXAxisSplitProvider from '../splits/column_chart/x_axis_split'; +import VislibLibLayoutSplitsColumnChartChartTitleSplitProvider from '../splits/column_chart/chart_title_split'; export default function ColumnLayoutFactory(Private) { const chartSplit = Private(VislibLibLayoutSplitsColumnChartChartSplitProvider); @@ -44,11 +44,15 @@ export default function ColumnLayoutFactory(Private) { children: [ { type: 'div', - class: 'y-axis-col', + class: 'y-axis-spacer-block y-axis-spacer-block-top' + }, + { + type: 'div', + class: 'y-axis-col axis-wrapper-left', children: [ { type: 'div', - class: 'y-axis-title' + class: 'y-axis-title axis-title' }, { type: 'div', @@ -64,7 +68,7 @@ export default function ColumnLayoutFactory(Private) { }, { type: 'div', - class: 'y-axis-spacer-block' + class: 'y-axis-spacer-block y-axis-spacer-block-bottom' } ] }, @@ -72,6 +76,21 @@ export default function ColumnLayoutFactory(Private) { type: 'div', class: 'vis-col-wrapper', children: [ + { + type: 'div', + class: 'x-axis-wrapper axis-wrapper-top', + children: [ + { + type: 'div', + class: 'x-axis-title axis-title' + }, + { + type: 'div', + class: 'x-axis-div-wrapper', + splits: xAxisSplit + } + ] + }, { type: 'div', class: 'chart-wrapper', @@ -83,7 +102,7 @@ export default function ColumnLayoutFactory(Private) { }, { type: 'div', - class: 'x-axis-wrapper', + class: 'x-axis-wrapper axis-wrapper-bottom', children: [ { type: 'div', @@ -97,11 +116,40 @@ export default function ColumnLayoutFactory(Private) { }, { type: 'div', - class: 'x-axis-title' + class: 'x-axis-title axis-title' } ] } ] + }, + { + type: 'div', + class: 'y-axis-col-wrapper', + children: [ + { + type: 'div', + class: 'y-axis-spacer-block y-axis-spacer-block-top' + }, + { + type: 'div', + class: 'y-axis-col axis-wrapper-right', + children: [ + { + type: 'div', + class: 'y-axis-div-wrapper', + splits: yAxisSplit + }, + { + type: 'div', + class: 'y-axis-title axis-title' + } + ] + }, + { + type: 'div', + class: 'y-axis-spacer-block y-axis-spacer-block-bottom' + } + ] } ] } diff --git a/src/ui/public/vislib/lib/layout/types/map_layout.js b/src/ui/public/vislib/lib/layout/types/map_layout.js index 64106cdfe1350..79e12b04730ea 100644 --- a/src/ui/public/vislib/lib/layout/types/map_layout.js +++ b/src/ui/public/vislib/lib/layout/types/map_layout.js @@ -1,4 +1,4 @@ -import VislibLibLayoutSplitsTileMapMapSplitProvider from 'ui/vislib/lib/layout/splits/tile_map/map_split'; +import VislibLibLayoutSplitsTileMapMapSplitProvider from '../splits/tile_map/map_split'; export default function ColumnLayoutFactory(Private) { const mapSplit = Private(VislibLibLayoutSplitsTileMapMapSplitProvider); diff --git a/src/ui/public/vislib/lib/layout/types/pie_layout.js b/src/ui/public/vislib/lib/layout/types/pie_layout.js index ea7bedf61405e..59617fd6660bc 100644 --- a/src/ui/public/vislib/lib/layout/types/pie_layout.js +++ b/src/ui/public/vislib/lib/layout/types/pie_layout.js @@ -1,5 +1,5 @@ -import VislibLibLayoutSplitsPieChartChartSplitProvider from 'ui/vislib/lib/layout/splits/pie_chart/chart_split'; -import VislibLibLayoutSplitsPieChartChartTitleSplitProvider from 'ui/vislib/lib/layout/splits/pie_chart/chart_title_split'; +import VislibLibLayoutSplitsPieChartChartSplitProvider from '../splits/pie_chart/chart_split'; +import VislibLibLayoutSplitsPieChartChartTitleSplitProvider from '../splits/pie_chart/chart_title_split'; export default function ColumnLayoutFactory(Private) { const chartSplit = Private(VislibLibLayoutSplitsPieChartChartSplitProvider); const chartTitleSplit = Private(VislibLibLayoutSplitsPieChartChartTitleSplitProvider); diff --git a/src/ui/public/vislib/lib/types/index.js b/src/ui/public/vislib/lib/types/index.js new file mode 100644 index 0000000000000..aef9872308cee --- /dev/null +++ b/src/ui/public/vislib/lib/types/index.js @@ -0,0 +1,21 @@ +import VislibLibTypesPointSeriesProvider from './point_series'; +import VislibLibTypesPieProvider from './pie'; +import VislibLibTypesTileMapProvider from './tile_map'; + +export default function TypeFactory(Private) { + const pointSeries = Private(VislibLibTypesPointSeriesProvider); + + /** + * Handles the building of each visualization + * + * @return {Function} Returns an Object of Handler types + */ + return { + histogram: pointSeries.column, + line: pointSeries.line, + pie: Private(VislibLibTypesPieProvider), + area: pointSeries.area, + tile_map: Private(VislibLibTypesTileMapProvider), + point_series: pointSeries.line + }; +}; diff --git a/src/ui/public/vislib/lib/types/pie.js b/src/ui/public/vislib/lib/types/pie.js new file mode 100644 index 0000000000000..f763d80ff8bf3 --- /dev/null +++ b/src/ui/public/vislib/lib/types/pie.js @@ -0,0 +1,13 @@ +import _ from 'lodash'; + +export default function PieConfig(Private) { + + return function (config) { + if (!config.chart) { + config.chart = _.defaults({}, config, { + type: 'pie' + }); + } + return config; + }; +}; diff --git a/src/ui/public/vislib/lib/types/point_series.js b/src/ui/public/vislib/lib/types/point_series.js new file mode 100644 index 0000000000000..f8d01f435fe99 --- /dev/null +++ b/src/ui/public/vislib/lib/types/point_series.js @@ -0,0 +1,142 @@ +import _ from 'lodash'; + +export default function ColumnHandler(Private) { + + const createSeries = (cfg, series) => { + const stacked = ['stacked', 'percentage', 'wiggle', 'silhouette'].includes(cfg.mode); + return { + type: 'point_series', + series: _.map(series, (seri) => { + return { + show: true, + type: cfg.type || 'line', + mode: stacked ? 'stacked' : 'normal', + interpolate: cfg.interpolate, + smoothLines: cfg.smoothLines, + drawLinesBetweenPoints: cfg.drawLinesBetweenPoints, + showCircles: cfg.showCircles, + radiusRatio: cfg.radiusRatio, + data: seri + }; + }) + }; + }; + + const createCharts = (cfg, data) => { + if (data.rows || data.columns) { + const charts = data.rows ? data.rows : data.columns; + return charts.map(chart => { + return createSeries(cfg, chart.series); + }); + } + + return [createSeries(cfg, data.series)]; + }; + /* + * Create handlers for Area, Column, and Line charts which + * are all nearly the same minus a few details + */ + function create(opts) { + opts = opts || {}; + + return function (cfg, data) { + const isUserDefinedYAxis = cfg.setYExtents; + const config = _.defaults({}, cfg, { + chartTitle: {}, + mode: 'normal' + }, opts); + + config.type = 'point_series'; + + if (!config.tooltip) { + config.tooltip = { + show: cfg.addTooltip + }; + } + + if (!config.valueAxes) { + let mode = config.mode; + if (['stacked', 'overlap'].includes(mode)) mode = 'normal'; + config.valueAxes = [ + { + id: 'ValueAxis-1', + type: 'value', + scale: { + type: config.scale, + setYExtents: config.setYExtents, + defaultYExtents: config.defaultYExtents, + min : isUserDefinedYAxis ? config.yAxis.min : undefined, + max : isUserDefinedYAxis ? config.yAxis.max : undefined, + mode : mode + }, + labels: { + axisFormatter: data.data.yAxisFormatter || data.get('yAxisFormatter') + }, + title: { + text: data.get('yAxisLabel') + } + } + ]; + } + + if (!config.categoryAxes) { + config.categoryAxes = [ + { + id: 'CategoryAxis-1', + type: 'category', + labels: { + axisFormatter: data.data.xAxisFormatter || data.get('xAxisFormatter') + }, + scale: { + expandLastBucket: opts.expandLastBucket + }, + title: { + text: data.get('xAxisLabel') + } + } + ]; + } + + if (!config.charts) { + config.charts = createCharts(cfg, data.data); + } + + return config; + }; + } + + return { + 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 (vis, 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 (vis, data) { + return data.hasNullValues(); + } + } + ] + }) + }; +}; diff --git a/src/ui/public/vislib/lib/types/tile_map.js b/src/ui/public/vislib/lib/types/tile_map.js new file mode 100644 index 0000000000000..7d94f2bcfdc8a --- /dev/null +++ b/src/ui/public/vislib/lib/types/tile_map.js @@ -0,0 +1,19 @@ +import _ from 'lodash'; +export default function MapHandlerProvider(Private) { + return function (config) { + if (!config.chart) { + config.chart = _.defaults({}, config, { + type: 'tile_map' + }); + } + + config.resize = function () { + this.charts.forEach(function (chart) { + chart.resizeArea(); + }); + }; + + return config; + }; +}; + diff --git a/src/ui/public/vislib/lib/vis_config.js b/src/ui/public/vislib/lib/vis_config.js new file mode 100644 index 0000000000000..f33e2b38c45c6 --- /dev/null +++ b/src/ui/public/vislib/lib/vis_config.js @@ -0,0 +1,46 @@ +/** + * Provides vislib configuration, throws error if invalid property is accessed without providing defaults + */ +import _ from 'lodash'; +import VisTypesProvider from './types'; +import VislibLibDataProvider from './data'; + +export default function VisConfigFactory(Private) { + + const Data = Private(VislibLibDataProvider); + const visTypes = Private(VisTypesProvider); + const DEFAULT_VIS_CONFIG = { + style: { + margin : { top: 10, right: 3, bottom: 5, left: 3 } + }, + alerts: {}, + categoryAxes: [], + valueAxes: [] + }; + + + class VisConfig { + constructor(visConfigArgs, data, uiState) { + this.data = new Data(data, uiState); + + const visType = visTypes[visConfigArgs.type]; + const typeDefaults = visType(visConfigArgs, this.data); + this._values = _.defaultsDeep({}, typeDefaults, DEFAULT_VIS_CONFIG); + }; + + get(property, defaults) { + if (_.has(this._values, property) || typeof defaults !== 'undefined') { + return _.get(this._values, property, defaults); + } else { + throw new Error(`Accessing invalid config property: ${property}`); + return defaults; + } + }; + + set(property, value) { + return _.set(this._values, property, value); + }; + } + + return VisConfig; +} diff --git a/src/ui/public/vislib/lib/x_axis.js b/src/ui/public/vislib/lib/x_axis.js deleted file mode 100644 index b7bdcbc0dc5bd..0000000000000 --- a/src/ui/public/vislib/lib/x_axis.js +++ /dev/null @@ -1,513 +0,0 @@ -import d3 from 'd3'; -import $ from 'jquery'; -import _ from 'lodash'; -import moment from 'moment'; -import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; -export default function XAxisFactory(Private) { - - const ErrorHandler = Private(VislibLibErrorHandlerProvider); - - /** - * Adds an x axis to the visualization - * - * @class XAxis - * @constructor - * @param args {{el: (HTMLElement), xValues: (Array), ordered: (Object|*), - * xAxisFormatter: (Function), _attr: (Object|*)}} - */ - class XAxis extends ErrorHandler { - constructor(args) { - super(); - this.el = args.el; - this.xValues = args.xValues; - this.ordered = args.ordered; - this.xAxisFormatter = args.xAxisFormatter; - this.expandLastBucket = args.expandLastBucket == null ? true : args.expandLastBucket; - this._attr = _.defaults(args._attr || {}); - } - - /** - * Renders the x axis - * - * @method render - * @returns {D3.UpdateSelection} Appends x axis to visualization - */ - render() { - d3.select(this.el).selectAll('.x-axis-div').call(this.draw()); - }; - - /** - * Returns d3 x axis scale function. - * If time, return time scale, else return d3 ordinal scale for nominal data - * - * @method getScale - * @returns {*} D3 scale function - */ - getScale() { - const ordered = this.ordered; - - if (ordered && ordered.date) { - return d3.time.scale.utc(); - } - return d3.scale.ordinal(); - }; - - /** - * Add domain to the x axis scale. - * if time, return a time domain, and calculate the min date, max date, and time interval - * else, return a nominal (d3.scale.ordinal) domain, i.e. array of x axis values - * - * @method getDomain - * @param scale {Function} D3 scale - * @returns {*} D3 scale function - */ - getDomain(scale) { - const ordered = this.ordered; - - if (ordered && ordered.date) { - return this.getTimeDomain(scale, this.xValues); - } - return this.getOrdinalDomain(scale, this.xValues); - }; - - /** - * Returns D3 time domain - * - * @method getTimeDomain - * @param scale {Function} D3 scale function - * @param data {Array} - * @returns {*} D3 scale function - */ - getTimeDomain(scale, data) { - return scale.domain([this.minExtent(data), this.maxExtent(data)]); - }; - - minExtent(data) { - return this._calculateExtent(data || this.xValues, 'min'); - }; - - maxExtent(data) { - return this._calculateExtent(data || this.xValues, 'max'); - }; - - /** - * - * @param data - * @param extent - */ - _calculateExtent(data, extent) { - const ordered = this.ordered; - const opts = [ordered[extent]]; - - let point = d3[extent](data); - if (this.expandLastBucket && extent === 'max') { - point = this.addInterval(point); - } - opts.push(point); - - return d3[extent](opts.reduce(function (opts, v) { - if (!_.isNumber(v)) v = +v; - if (!isNaN(v)) opts.push(v); - return opts; - }, [])); - }; - - /** - * Add the interval to a point on the x axis, - * this properly adds dates if needed. - * - * @param {number} x - a value on the x-axis - * @returns {number} - x + the ordered interval - */ - addInterval(x) { - return this.modByInterval(x, +1); - }; - - /** - * Subtract the interval to a point on the x axis, - * this properly subtracts dates if needed. - * - * @param {number} x - a value on the x-axis - * @returns {number} - x - the ordered interval - */ - subtractInterval(x) { - return this.modByInterval(x, -1); - }; - - /** - * Modify the x value by n intervals, properly - * handling dates if needed. - * - * @param {number} x - a value on the x-axis - * @param {number} n - the number of intervals - * @returns {number} - x + n intervals - */ - modByInterval(x, n) { - const ordered = this.ordered; - if (!ordered) return x; - const interval = ordered.interval; - if (!interval) return x; - - if (!ordered.date) { - return x += (ordered.interval * n); - } - - const y = moment(x); - const method = n > 0 ? 'add' : 'subtract'; - - _.times(Math.abs(n), function () { - y[method](interval); - }); - - return y.valueOf(); - }; - - /** - * Return a nominal(d3 ordinal) domain - * - * @method getOrdinalDomain - * @param scale {Function} D3 scale function - * @param xValues {Array} Array of x axis values - * @returns {*} D3 scale function - */ - getOrdinalDomain(scale, xValues) { - return scale.domain(xValues); - }; - - /** - * Return the range for the x axis scale - * if time, return a normal range, else if nominal, return rangeBands with a default (0.1) spacer specified - * - * @method getRange - * @param scale {Function} D3 scale function - * @param width {Number} HTML Element width - * @returns {*} D3 scale function - */ - getRange(domain, width) { - const ordered = this.ordered; - - if (ordered && ordered.date) { - return domain.range([0, width]); - } - return domain.rangeBands([0, width], 0.1); - }; - - /** - * Return the x axis scale - * - * @method getXScale - * @param width {Number} HTML Element width - * @returns {*} D3 x scale function - */ - getXScale(width) { - const domain = this.getDomain(this.getScale()); - - return this.getRange(domain, width); - }; - - /** - * Creates d3 xAxis function - * - * @method getXAxis - * @param width {Number} HTML Element width - */ - getXAxis(width) { - this.xScale = this.getXScale(width); - - if (!this.xScale || _.isNaN(this.xScale)) { - throw new Error('xScale is ' + this.xScale); - } - - this.xAxis = d3.svg.axis() - .scale(this.xScale) - .ticks(10) - .tickFormat(this.xAxisFormatter) - .orient('bottom'); - }; - - /** - * Renders the x axis - * - * @method draw - * @returns {Function} Renders the x axis to a D3 selection - */ - draw() { - const self = this; - this._attr.isRotated = false; - - return function (selection) { - const n = selection[0].length; - const parentWidth = $(self.el) - .find('.x-axis-div-wrapper') - .width(); - - selection.each(function () { - - const div = d3.select(this); - const width = parentWidth / n; - const height = $(this.parentElement).height(); - - self.validateWidthandHeight(width, height); - - self.getXAxis(width); - - const svg = div.append('svg') - .attr('width', width) - .attr('height', height); - - svg.append('g') - .attr('class', 'x axis') - .attr('transform', 'translate(0,0)') - .call(self.xAxis); - }); - - selection.call(self.filterOrRotate()); - }; - }; - - /** - * Returns a function that evaluates scale type and - * applies filter to tick labels on time scales - * rotates and truncates tick labels on nominal/ordinal scales - * - * @method filterOrRotate - * @returns {Function} Filters or rotates x axis tick labels - */ - filterOrRotate() { - const self = this; - const ordered = self.ordered; - - return function (selection) { - selection.each(function () { - const axis = d3.select(this); - if (ordered && ordered.date) { - axis.call(self.filterAxisLabels()); - } else { - axis.call(self.rotateAxisLabels()); - } - }); - - self.updateXaxisHeight(); - - selection.call(self.fitTitles()); - - }; - }; - - /** - * Rotate the axis tick labels within selection - * - * @returns {Function} Rotates x axis tick labels of a D3 selection - */ - rotateAxisLabels() { - const self = this; - const barWidth = self.xScale.rangeBand(); - const maxRotatedLength = 120; - const xAxisPadding = 15; - const lengths = []; - self._attr.isRotated = false; - - return function (selection) { - const text = selection.selectAll('.tick text'); - - text.each(function textWidths() { - lengths.push(d3.select(this).node().getBBox().width); - }); - const length = _.max(lengths); - self._attr.xAxisLabelHt = length + xAxisPadding; - - // if longer than bar width, rotate - if (length > barWidth) { - self._attr.isRotated = true; - } - - // if longer than maxRotatedLength, truncate - if (length > maxRotatedLength) { - self._attr.xAxisLabelHt = maxRotatedLength; - } - - if (self._attr.isRotated) { - text - .text(function truncate() { - return self.truncateLabel(this, self._attr.xAxisLabelHt); - }) - .style('text-anchor', 'end') - .attr('dx', '-.8em') - .attr('dy', '-.60em') - .attr('transform', function rotate() { - return 'rotate(-90)'; - }) - .append('title') - .text(text => text); - - selection.select('svg') - .attr('height', self._attr.xAxisLabelHt); - } - }; - }; - - /** - * Returns a string that is truncated to fit size - * - * @method truncateLabel - * @param text {HTMLElement} - * @param size {Number} - * @returns {*|jQuery} - */ - truncateLabel(text, size) { - const node = d3.select(text).node(); - let str = $(node).text(); - const width = node.getBBox().width; - const chars = str.length; - const pxPerChar = width / chars; - let endChar = 0; - const ellipsesPad = 4; - - if (width > size) { - endChar = Math.floor((size / pxPerChar) - ellipsesPad); - while (str[endChar - 1] === ' ' || str[endChar - 1] === '-' || str[endChar - 1] === ',') { - endChar = endChar - 1; - } - str = str.substr(0, endChar) + '...'; - } - return str; - }; - - /** - * Filter out text labels by width and position on axis - * trims labels that would overlap each other - * or extend past left or right edges - * if prev label pos (or 0) + half of label width is < label pos - * and label pos + half width is not > width of axis - * - * @method filterAxisLabels - * @returns {Function} - */ - filterAxisLabels() { - const self = this; - let startX = 0; - let maxW; - let par; - let myX; - let myWidth; - let halfWidth; - const padding = 1.1; - - return function (selection) { - selection.selectAll('.tick text') - .text(function (d) { - par = d3.select(this.parentNode).node(); - myX = self.xScale(d); - myWidth = par.getBBox().width * padding; - halfWidth = myWidth / 2; - maxW = $(self.el).find('.x-axis-div').width(); - - if ((startX + halfWidth) < myX && maxW > (myX + halfWidth)) { - startX = myX + halfWidth; - return self.xAxisFormatter(d); - } else { - d3.select(this.parentNode).remove(); - } - }); - }; - }; - - /** - * Returns a function that adjusts axis titles and - * chart title transforms to fit axis label divs. - * Sets transform of x-axis-title to fit .x-axis-title div width - * if x-axis-chart-titles, set transform of x-axis-chart-titles - * to fit .chart-title div width - * - * @method fitTitles - * @returns {Function} - */ - fitTitles() { - const visEls = $('.vis-wrapper'); - let xAxisChartTitle; - let yAxisChartTitle; - let text; - let titles; - - return function () { - - visEls.each(function () { - const visEl = d3.select(this); - const $visEl = $(this); - const xAxisTitle = $visEl.find('.x-axis-title'); - const yAxisTitle = $visEl.find('.y-axis-title'); - let titleWidth = xAxisTitle.width(); - let titleHeight = yAxisTitle.height(); - - text = visEl.select('.x-axis-title') - .select('svg') - .attr('width', titleWidth) - .select('text') - .attr('transform', 'translate(' + (titleWidth / 2) + ',11)'); - - text = visEl.select('.y-axis-title') - .select('svg') - .attr('height', titleHeight) - .select('text') - .attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)'); - - if ($visEl.find('.x-axis-chart-title').length) { - xAxisChartTitle = $visEl.find('.x-axis-chart-title'); - titleWidth = xAxisChartTitle.find('.chart-title').width(); - - titles = visEl.select('.x-axis-chart-title').selectAll('.chart-title'); - titles.each(function () { - text = d3.select(this) - .select('svg') - .attr('width', titleWidth) - .select('text') - .attr('transform', 'translate(' + (titleWidth / 2) + ',11)'); - }); - } - - if ($visEl.find('.y-axis-chart-title').length) { - yAxisChartTitle = $visEl.find('.y-axis-chart-title'); - titleHeight = yAxisChartTitle.find('.chart-title').height(); - - titles = visEl.select('.y-axis-chart-title').selectAll('.chart-title'); - titles.each(function () { - text = d3.select(this) - .select('svg') - .attr('height', titleHeight) - .select('text') - .attr('transform', 'translate(11,' + (titleHeight / 2) + ')rotate(-90)'); - }); - } - - }); - - }; - }; - - /** - * Appends div to make .y-axis-spacer-block - * match height of .x-axis-wrapper - * - * @method updateXaxisHeight - */ - updateXaxisHeight() { - const selection = d3.select(this.el).selectAll('.vis-wrapper'); - - selection.each(function () { - const visEl = d3.select(this); - - if (visEl.select('.inner-spacer-block').node() === null) { - visEl.select('.y-axis-spacer-block') - .append('div') - .attr('class', 'inner-spacer-block'); - } - const xAxisHt = visEl.select('.x-axis-wrapper').style('height'); - - visEl.select('.inner-spacer-block').style('height', xAxisHt); - }); - - }; - } - - return XAxis; -}; diff --git a/src/ui/public/vislib/lib/y_axis.js b/src/ui/public/vislib/lib/y_axis.js deleted file mode 100644 index f5d7d55ff0e77..0000000000000 --- a/src/ui/public/vislib/lib/y_axis.js +++ /dev/null @@ -1,236 +0,0 @@ -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import errors from 'ui/errors'; -import VislibLibErrorHandlerProvider from 'ui/vislib/lib/_error_handler'; -export default function YAxisFactory(Private) { - - const ErrorHandler = Private(VislibLibErrorHandlerProvider); - - /** - * Appends y axis to the visualization - * - * @class YAxis - * @constructor - * @param args {{el: (HTMLElement), yMax: (Number), _attr: (Object|*)}} - */ - class YAxis extends ErrorHandler { - constructor(args) { - super(); - this.el = args.el; - this.scale = null; - this.domain = [args.yMin, args.yMax]; - this.yAxisFormatter = args.yAxisFormatter; - this._attr = args._attr || {}; - } - - /** - * Renders the y axis - * - * @method render - * @return {D3.UpdateSelection} Renders y axis to visualization - */ - render() { - d3.select(this.el).selectAll('.y-axis-div').call(this.draw()); - }; - - _isPercentage() { - return (this._attr.mode === 'percentage'); - }; - - _isUserDefined() { - return (this._attr.setYExtents); - }; - - _isYExtents() { - return (this._attr.defaultYExtents); - }; - - _validateUserExtents(domain) { - const self = this; - - return domain.map(function (val) { - val = parseInt(val, 10); - - if (isNaN(val)) throw new Error(val + ' is not a valid number'); - if (self._isPercentage() && self._attr.setYExtents) return val / 100; - return val; - }); - }; - - _getExtents(domain) { - const min = domain[0]; - const max = domain[1]; - - if (this._isUserDefined()) return this._validateUserExtents(domain); - if (this._isYExtents()) return domain; - if (this._attr.scale === 'log') return this._logDomain(min, max); // Negative values cannot be displayed with a log scale. - if (!this._isYExtents() && !this._isUserDefined()) return [Math.min(0, min), Math.max(0, max)]; - return domain; - }; - - _throwCustomError(message) { - throw new Error(message); - }; - - _throwLogScaleValuesError() { - throw new errors.InvalidLogScaleValues(); - }; - - /** - * Returns the appropriate D3 scale - * - * @param fnName {String} D3 scale - * @returns {*} - */ - _getScaleType(fnName) { - if (fnName === 'square root') fnName = 'sqrt'; // Rename 'square root' to 'sqrt' - fnName = fnName || 'linear'; - - if (typeof d3.scale[fnName] !== 'function') return this._throwCustomError('YAxis.getScaleType: ' + fnName + ' is not a function'); - - return d3.scale[fnName](); - }; - - /** - * Return the domain for log scale, i.e. the extent of the log scale. - * Log scales must begin at 1 since the log(0) = -Infinity - * - * @param {Number} min - * @param {Number} max - * @returns {Array} - */ - _logDomain(min, max) { - if (min < 0 || max < 0) return this._throwLogScaleValuesError(); - return [1, max]; - }; - - /** - * Creates the d3 y scale function - * - * @method getYScale - * @param height {Number} DOM Element height - * @returns {D3.Scale.QuantitiveScale|*} D3 yScale function - */ - getYScale(height) { - const scale = this._getScaleType(this._attr.scale); - const domain = this._getExtents(this.domain); - - this.yScale = scale - .domain(domain) - .range([height, 0]); - - if (!this._isUserDefined()) this.yScale.nice(); // round extents when not user defined - // Prevents bars from going off the chart when the y extents are within the domain range - if (this._attr.type === 'histogram') this.yScale.clamp(true); - return this.yScale; - }; - - getScaleType() { - return this._attr.scale; - }; - - tickFormat() { - const isPercentage = this._attr.mode === 'percentage'; - if (isPercentage) return d3.format('%'); - if (this.yAxisFormatter) return this.yAxisFormatter; - return d3.format('n'); - }; - - _validateYScale(yScale) { - if (!yScale || _.isNaN(yScale)) throw new Error('yScale is ' + yScale); - }; - - /** - * Creates the d3 y axis function - * - * @method getYAxis - * @param height {Number} DOM Element height - * @returns {D3.Svg.Axis|*} D3 yAxis function - */ - getYAxis(height) { - const yScale = this.getYScale(height); - this._validateYScale(yScale); - - // Create the d3 yAxis function - this.yAxis = d3.svg.axis() - .scale(yScale) - .tickFormat(this.tickFormat(this.domain)) - .ticks(this.tickScale(height)) - .orient('left'); - - return this.yAxis; - }; - - /** - * Create a tick scale for the y axis that modifies the number of ticks - * based on the height of the wrapping DOM element - * Avoid using even numbers in the yTickScale.range - * Causes the top most tickValue in the chart to be missing - * - * @method tickScale - * @param height {Number} DOM element height - * @returns {number} Number of y axis ticks - */ - tickScale(height) { - const yTickScale = d3.scale.linear() - .clamp(true) - .domain([20, 40, 1000]) - .range([0, 3, 11]); - - return Math.ceil(yTickScale(height)); - }; - - /** - * Renders the y axis to the visualization - * - * @method draw - * @returns {Function} Renders y axis to visualization - */ - draw() { - const self = this; - const margin = this._attr.margin; - const mode = this._attr.mode; - const isWiggleOrSilhouette = (mode === 'wiggle' || mode === 'silhouette'); - - return function (selection) { - selection.each(function () { - const el = this; - - const div = d3.select(el); - const width = $(el).parent().width(); - const height = $(el).height(); - const adjustedHeight = height - margin.top - margin.bottom; - - // Validate whether width and height are not 0 or `NaN` - self.validateWidthandHeight(width, adjustedHeight); - - const yAxis = self.getYAxis(adjustedHeight); - - // The yAxis should not appear if mode is set to 'wiggle' or 'silhouette' - if (!isWiggleOrSilhouette) { - // Append svg and y axis - const svg = div.append('svg') - .attr('width', width) - .attr('height', height); - - svg.append('g') - .attr('class', 'y axis') - .attr('transform', 'translate(' + (width - 2) + ',' + margin.top + ')') - .call(yAxis); - - const container = svg.select('g.y.axis').node(); - if (container) { - const cWidth = Math.max(width, container.getBBox().width); - svg.attr('width', cWidth); - svg.select('g') - .attr('transform', 'translate(' + (cWidth - 2) + ',' + margin.top + ')'); - } - } - }); - }; - }; - } - - return YAxis; -}; diff --git a/src/ui/public/vislib/styles/_layout.less b/src/ui/public/vislib/styles/_layout.less index e704d18745ccf..3b6076c9333b9 100644 --- a/src/ui/public/vislib/styles/_layout.less +++ b/src/ui/public/vislib/styles/_layout.less @@ -12,6 +12,11 @@ min-height: 0; min-width: 0; overflow: hidden; + padding: 10px 0; +} + +.vis-wrapper svg { + overflow: visible; } /* YAxis logic */ @@ -31,7 +36,7 @@ } .y-axis-spacer-block { - min-height: 45px; + min-height: 0px; } .y-axis-div-wrapper { @@ -43,13 +48,14 @@ .y-axis-div { flex: 1 1 25px; - min-width: 14px; + min-width: 1px; min-height: 14px; + margin: 5px 0px; } .y-axis-title { min-height: 14px; - min-width: 14px; + min-width: 1px; } .y-axis-chart-title { @@ -57,7 +63,6 @@ flex-direction: column; min-height: 14px; min-width: 0; - width: 14px; } .y-axis-title text, .x-axis-title text { @@ -83,7 +88,6 @@ flex-direction: column; min-height: 0; min-width: 0; - margin-right: 8px; } .chart-wrapper { @@ -93,6 +97,17 @@ margin: 0; min-height: 0; min-width: 0; + margin: 5px; +} + +.chart-wrapper-row .chart-wrapper { + margin-left: 0px; + margin-right: 0px; +} + +.chart-wrapper-column .chart-wrapper { + margin-top: 0px; + margin-bottom: 0px; } .chart-wrapper-column { @@ -138,7 +153,7 @@ .x-axis-wrapper { display: flex; flex-direction: column; - min-height: 45px; + min-height: 0px; min-width: 0; overflow: visible; } @@ -146,27 +161,41 @@ .x-axis-div-wrapper { display: flex; flex-direction: row; - min-height: 20px; + min-height: 0px; min-width: 0; } .x-axis-chart-title { display: flex; flex-direction: row; - min-height: 15px; + min-height: 1px; max-height: 15px; min-width: 20px; } .x-axis-title { - min-height: 15px; + min-height: 0px; max-height: 15px; min-width: 20px; overflow: hidden; } .x-axis-div { - margin-top: -5px; - min-height: 20px; + min-height: 0px; min-width: 20px; + margin: 0px 5px; + width: 100%; +} + +.axis-wrapper-top .axis-div svg { + margin-bottom: -5px; +} + +.chart-first { + margin-top: 0px; + margin-left: 0px +} +.chart-last { + margin-bottom: 0px; + margin-right: 0px; } diff --git a/src/ui/public/vislib/styles/_svg.less b/src/ui/public/vislib/styles/_svg.less index 941d1b38372a7..ae2fd14a73792 100644 --- a/src/ui/public/vislib/styles/_svg.less +++ b/src/ui/public/vislib/styles/_svg.less @@ -12,10 +12,6 @@ } } -.x.axis path { - display: none; -} - .tick text { font-size: 8pt; fill: @svg-tick-text-color; diff --git a/src/ui/public/vislib/vis.js b/src/ui/public/vislib/vis.js index 7b60f64cebfa2..17383c203ecea 100644 --- a/src/ui/public/vislib/vis.js +++ b/src/ui/public/vislib/vis.js @@ -2,18 +2,17 @@ import _ from 'lodash'; import d3 from 'd3'; import Binder from 'ui/binder'; import errors from 'ui/errors'; -import 'ui/vislib/styles/main.less'; -import VislibLibResizeCheckerProvider from 'ui/vislib/lib/resize_checker'; import EventsProvider from 'ui/events'; -import VislibLibHandlerHandlerTypesProvider from 'ui/vislib/lib/handler/handler_types'; -import VislibVisualizationsVisTypesProvider from 'ui/vislib/visualizations/vis_types'; -export default function VisFactory(Private) { - +import './styles/main.less'; +import VislibLibResizeCheckerProvider from './lib/resize_checker'; +import VisConifgProvider from './lib/vis_config'; +import VisHandlerProvider from './lib/handler'; +export default function VisFactory(Private) { const ResizeChecker = Private(VislibLibResizeCheckerProvider); const Events = Private(EventsProvider); - const handlerTypes = Private(VislibLibHandlerHandlerTypesProvider); - const chartTypes = Private(VislibVisualizationsVisTypesProvider); + const VisConfig = Private(VisConifgProvider); + const Handler = Private(VisHandlerProvider); /** * Creates the visualizations. @@ -24,14 +23,12 @@ export default function VisFactory(Private) { * @param config {Object} Parameters that define the chart type and chart options */ class Vis extends Events { - constructor($el, config) { + constructor($el, visConfigArgs) { super(arguments); this.el = $el.get ? $el.get(0) : $el; this.binder = new Binder(); - this.ChartClass = chartTypes[config.type]; - this._attr = _.defaults({}, config || {}, { - legendOpen: true - }); + this.visConfigArgs = visConfigArgs; + this.visConfigArgs.el = this.el; // bind the resize function so it can be used as an event handler this.resize = _.bind(this.resize, this); @@ -39,6 +36,9 @@ export default function VisFactory(Private) { this.binder.on(this.resizeChecker, 'resize', this.resize); } + hasLegend() { + return this.visConfigArgs.addLegend; + } /** * Renders the visualization * @@ -46,8 +46,6 @@ export default function VisFactory(Private) { * @param data {Object} Elasticsearch query results */ render(data, uiState) { - const chartType = this._attr.type; - if (!data) { throw new Error('No valid data!'); } @@ -69,7 +67,8 @@ export default function VisFactory(Private) { uiState.on('change', this._uiStateChangeHandler); } - this.handler = handlerTypes[chartType](this) || handlerTypes.column(this); + this.visConfig = new VisConfig(this.visConfigArgs, this.data, this.uiState); + this.handler = new Handler(this, this.visConfig); this._runWithoutResizeChecker('render'); }; @@ -80,7 +79,6 @@ export default function VisFactory(Private) { */ resize() { if (!this.data) { - // TODO: need to come up with a solution for resizing when no data is available return; } @@ -140,7 +138,7 @@ export default function VisFactory(Private) { * @param val {*} Value to which the attribute name is set */ set(name, val) { - this._attr[name] = val; + this.visConfigArgs[name] = val; this.render(this.data, this.uiState); }; @@ -152,7 +150,7 @@ export default function VisFactory(Private) { * @returns {*} The value of the attribute name */ get(name) { - return this._attr[name]; + return this.visConfig.get(name); }; /** diff --git a/src/ui/public/vislib/vislib.js b/src/ui/public/vislib/vislib.js index 5f7a7ebd71100..0c0a91164f1c4 100644 --- a/src/ui/public/vislib/vislib.js +++ b/src/ui/public/vislib/vislib.js @@ -1,13 +1,13 @@ -import 'ui/vislib/lib/handler/types/pie'; -import 'ui/vislib/lib/handler/types/point_series'; -import 'ui/vislib/lib/handler/types/tile_map'; -import 'ui/vislib/lib/handler/handler_types'; -import 'ui/vislib/lib/layout/layout_types'; -import 'ui/vislib/lib/data'; -import 'ui/vislib/visualizations/_map.js'; -import 'ui/vislib/visualizations/vis_types'; -import 'ui/vislib/styles/main.less'; -import VislibVisProvider from 'ui/vislib/vis'; +import './lib/types/pie'; +import './lib/types/point_series'; +import './lib/types/tile_map'; +import './lib/types'; +import './lib/layout/layout_types'; +import './lib/data'; +import './visualizations/_map.js'; +import './visualizations/vis_types'; +import './styles/main.less'; +import VislibVisProvider from './vis'; // prefetched for faster optimization runs // end prefetching diff --git a/src/ui/public/vislib/visualizations/_chart.js b/src/ui/public/vislib/visualizations/_chart.js index 7f9248b08c3fd..522de3b7a88d9 100644 --- a/src/ui/public/vislib/visualizations/_chart.js +++ b/src/ui/public/vislib/visualizations/_chart.js @@ -1,8 +1,8 @@ import d3 from 'd3'; import _ from 'lodash'; import dataLabel from 'ui/vislib/lib/_data_label'; -import VislibLibDispatchProvider from 'ui/vislib/lib/dispatch'; -import VislibComponentsTooltipProvider from 'ui/vislib/components/tooltip'; +import VislibLibDispatchProvider from '../lib/dispatch'; +import VislibComponentsTooltipProvider from '../components/tooltip'; export default function ChartBaseClass(Private) { const Dispatch = Private(VislibLibDispatchProvider); @@ -26,7 +26,7 @@ export default function ChartBaseClass(Private) { const events = this.events = new Dispatch(handler); - if (_.get(this.handler, '_attr.addTooltip')) { + if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) { const $el = this.handler.el; const formatter = this.handler.data.get('tooltipFormatter'); @@ -35,7 +35,6 @@ export default function ChartBaseClass(Private) { this.tooltips.push(this.tooltip); } - this._attr = _.defaults(this.handler._attr || {}, {}); this._addIdentifier = _.bind(this._addIdentifier, this); } diff --git a/src/ui/public/vislib/visualizations/_point_series_chart.js b/src/ui/public/vislib/visualizations/_point_series_chart.js deleted file mode 100644 index 7cad5cda2dadc..0000000000000 --- a/src/ui/public/vislib/visualizations/_point_series_chart.js +++ /dev/null @@ -1,176 +0,0 @@ -import d3 from 'd3'; -import _ from 'lodash'; -import VislibVisualizationsChartProvider from 'ui/vislib/visualizations/_chart'; -import VislibComponentsTooltipProvider from 'ui/vislib/components/tooltip'; -import errors from 'ui/errors'; - -export default function PointSeriesChartProvider(Private) { - - const Chart = Private(VislibVisualizationsChartProvider); - const Tooltip = Private(VislibComponentsTooltipProvider); - const touchdownTmpl = _.template(require('ui/vislib/partials/touchdown.tmpl.html')); - - class PointSeriesChart extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); - } - - _stackMixedValues(stackCount) { - let currentStackOffsets = [0, 0]; - let currentStackIndex = 0; - - return function (d, y0, y) { - const firstStack = currentStackIndex % stackCount === 0; - const lastStack = ++currentStackIndex === stackCount; - - if (firstStack) { - currentStackOffsets = [0, 0]; - } - - if (lastStack) currentStackIndex = 0; - - if (y >= 0) { - d.y0 = currentStackOffsets[1]; - currentStackOffsets[1] += y; - } else { - d.y0 = currentStackOffsets[0]; - currentStackOffsets[0] += y; - } - }; - }; - - /** - * Stacks chart data values - * - * @method stackData - * @param data {Object} Elasticsearch query result for this chart - * @returns {Array} Stacked data objects with x, y, and y0 values - */ - stackData(data) { - const self = this; - const isHistogram = (this._attr.type === 'histogram' && this._attr.mode === 'stacked'); - const stack = this._attr.stack; - - if (isHistogram) stack.out(self._stackMixedValues(data.series.length)); - - return stack(data.series.map(function (d) { - const label = d.label; - return d.values.map(function (e, i) { - return { - _input: e, - label: label, - x: self._attr.xValue.call(d.values, e, i), - y: self._attr.yValue.call(d.values, e, i) - }; - }); - })); - }; - - - validateDataCompliesWithScalingMethod(data) { - const valuesSmallerThanOne = function (d) { - return d.values && d.values.some(e => e.y < 1); - }; - - const invalidLogScale = data.series && data.series.some(valuesSmallerThanOne); - if (this._attr.scale === 'log' && invalidLogScale) { - throw new errors.InvalidLogScaleValues(); - } - }; - - /** - * Creates rects to show buckets outside of the ordered.min and max, returns rects - * - * @param xScale {Function} D3 xScale function - * @param svg {HTMLElement} Reference to SVG - * @method createEndZones - * @returns {D3.Selection} - */ - createEndZones(svg) { - const self = this; - const xAxis = this.handler.xAxis; - const xScale = xAxis.xScale; - const ordered = xAxis.ordered; - const missingMinMax = !ordered || _.isUndefined(ordered.min) || _.isUndefined(ordered.max); - - if (missingMinMax || ordered.endzones === false) return; - - const attr = this.handler._attr; - const height = attr.height; - const width = attr.width; - const margin = attr.margin; - - // we don't want to draw endzones over our min and max values, they - // are still a part of the dataset. We want to start the endzones just - // outside of them so we will use these values rather than ordered.min/max - const oneUnit = (ordered.units || _.identity)(1); - - // points on this axis represent the amount of time they cover, - // so draw the endzones at the actual time bounds - const leftEndzone = { - x: 0, - w: Math.max(xScale(ordered.min), 0) - }; - - const rightLastVal = xAxis.expandLastBucket ? ordered.max : Math.min(ordered.max, _.last(xAxis.xValues)); - const rightStart = rightLastVal + oneUnit; - const rightEndzone = { - x: xScale(rightStart), - w: Math.max(width - xScale(rightStart), 0) - }; - - this.endzones = svg.selectAll('.layer') - .data([leftEndzone, rightEndzone]) - .enter() - .insert('g', '.brush') - .attr('class', 'endzone') - .append('rect') - .attr('class', 'zone') - .attr('x', function (d) { - return d.x; - }) - .attr('y', 0) - .attr('height', height - margin.top - margin.bottom) - .attr('width', function (d) { - return d.w; - }); - - function callPlay(event) { - const boundData = event.target.__data__; - const mouseChartXCoord = event.clientX - self.chartEl.getBoundingClientRect().left; - const wholeBucket = boundData && boundData.x != null; - - // the min and max that the endzones start in - const min = leftEndzone.w; - const max = rightEndzone.x; - - // bounds of the cursor to consider - let xLeft = mouseChartXCoord; - let xRight = mouseChartXCoord; - if (wholeBucket) { - xLeft = xScale(boundData.x); - xRight = xScale(xAxis.addInterval(boundData.x)); - } - - return { - wholeBucket: wholeBucket, - touchdown: min > xLeft || max < xRight - }; - } - - function textFormatter() { - return touchdownTmpl(callPlay(d3.event)); - } - - const endzoneTT = new Tooltip('endzones', this.handler.el, textFormatter, null); - this.tooltips.push(endzoneTT); - endzoneTT.order = 0; - endzoneTT.showCondition = function inEndzone() { - return callPlay(d3.event).touchdown; - }; - endzoneTT.render()(svg); - }; - } - - return PointSeriesChart; -}; diff --git a/src/ui/public/vislib/visualizations/area_chart.js b/src/ui/public/vislib/visualizations/area_chart.js deleted file mode 100644 index 04528482c3776..0000000000000 --- a/src/ui/public/vislib/visualizations/area_chart.js +++ /dev/null @@ -1,379 +0,0 @@ -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import errors from 'ui/errors'; -import VislibVisualizationsPointSeriesChartProvider from 'ui/vislib/visualizations/_point_series_chart'; -import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; -export default function AreaChartFactory(Private) { - - const PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); - const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); - - /** - * 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 - */ - class AreaChart extends PointSeriesChart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); - - this.isOverlapping = (handler._attr.mode === 'overlap'); - - if (this.isOverlapping) { - - // Default opacity should return to 0.6 on mouseout - const defaultOpacity = 0.6; - handler._attr.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); - }; - } - - this.checkIfEnoughData(); - - this._attr = _.defaults(handler._attr || {}, { - xValue: function (d) { - return d.x; - }, - yValue: function (d) { - return d.y; - } - }); - } - - /** - * Adds SVG path to area chart - * - * @method addPath - * @param svg {HTMLElement} SVG to which rect are appended - * @param layers {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with path added - */ - addPath(svg, layers) { - 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.handler.xAxis.xScale; - const yScale = this.handler.yAxis.yScale; - const interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; - const area = d3.svg.area() - .x(function (d) { - if (isTimeSeries) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - }) - .y0(function (d) { - if (isOverlapping) { - return yScale(0); - } - - return yScale(d.y0); - }) - .y1(function (d) { - if (isOverlapping) { - return yScale(d.y); - } - - return yScale(d.y0 + d.y); - }) - .defined(function (d) { - return !_.isNull(d.y); - }) - .interpolate(interpolate); - - // Data layers - const layer = svg.selectAll('.layer') - .data(layers) - .enter() - .append('g') - .attr('class', function (d, i) { - return 'pathgroup ' + i; - }); - - // Append path - const path = layer.append('path') - .call(this._addIdentifier) - .style('fill', function (d) { - return color(d[0].label); - }) - .classed('overlap_area', function () { - return isOverlapping; - }); - - // update - path.attr('d', function (d) { - return area(d); - }); - - return path; - }; - - /** - * Adds Events to SVG circles - * - * @method addCircleEvents - * @param element {D3.UpdateSelection} SVG circles - * @returns {D3.Selection} circles with event listeners attached - */ - addCircleEvents(element, svg) { - const events = this.events; - const isBrushable = events.isBrushable(); - const brush = isBrushable ? events.addBrushEvent(svg) : undefined; - const hover = events.addHoverEvent(); - const mouseout = events.addMouseoutEvent(); - const click = events.addClickEvent(); - const attachedEvents = element.call(hover).call(mouseout).call(click); - - if (isBrushable) { - attachedEvents.call(brush); - } - - return attachedEvents; - }; - - /** - * 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.handler.xAxis.xScale; - const yScale = this.handler.yAxis.yScale; - const ordered = this.handler.data.get('ordered'); - const circleRadius = 12; - const circleStrokeWidth = 0; - const tooltip = this.tooltip; - const isTooltip = this._attr.addTooltip; - const isOverlapping = this.isOverlapping; - - const layer = svg.selectAll('.points') - .data(data) - .enter() - .append('g') - .attr('class', 'points area'); - - // append the circles - const circles = layer - .selectAll('circles') - .data(function appendData(data) { - return data.filter(function isZeroOrNull(d) { - return d.y !== 0 && !_.isNull(d.y); - }); - }); - - // exit - circles.exit().remove(); - - // enter - circles - .enter() - .append('circle') - .call(this._addIdentifier) - .attr('stroke', function strokeColor(d) { - return color(d.label); - }) - .attr('fill', 'transparent') - .attr('stroke-width', circleStrokeWidth); - - // update - circles - .attr('cx', function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - }) - .attr('cy', function cy(d) { - if (isOverlapping) { - return yScale(d.y); - } - return yScale(d.y0 + d.y); - }) - .attr('r', circleRadius); - - // Add tooltip - if (isTooltip) { - circles.call(tooltip.render()); - } - - return circles; - }; - - /** - * Adds SVG clipPath - * - * @method addClipPath - * @param svg {HTMLElement} SVG to which clipPath is appended - * @param width {Number} SVG width - * @param height {Number} SVG height - * @returns {D3.UpdateSelection} SVG with clipPath added - */ - addClipPath(svg, width, height) { - // Prevents circles from being clipped at the top of the chart - const startX = 0; - const startY = 0; - const id = 'chart-area' + _.uniqueId(); - - // Creating clipPath - return svg - .attr('clip-path', 'url(#' + id + ')') - .append('clipPath') - .attr('id', id) - .append('rect') - .attr('x', startX) - .attr('y', startY) - .attr('width', width) - .attr('height', height); - }; - - checkIfEnoughData() { - const series = this.chartData.series; - const message = 'Area charts require more than one data point. Try adding ' + - 'an X-Axis Aggregation'; - - const notEnoughData = series.some(function (obj) { - return obj.values.length < 2; - }); - - if (notEnoughData) { - throw new errors.NotEnoughData(message); - } - }; - - validateWiggleSelection() { - const isWiggle = this._attr.mode === 'wiggle'; - const ordered = this.handler.data.get('ordered'); - - if (isWiggle && !ordered) throw new errors.InvalidWiggleSelection(); - }; - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the area chart - */ - draw() { - // Attributes - const self = this; - const xScale = this.handler.xAxis.xScale; - const $elem = $(this.chartEl); - const margin = this._attr.margin; - const elWidth = this._attr.width = $elem.width(); - const elHeight = this._attr.height = $elem.height(); - const yMin = this.handler.yAxis.yMin; - const yScale = this.handler.yAxis.yScale; - const minWidth = 20; - const minHeight = 20; - const addTimeMarker = this._attr.addTimeMarker; - const times = this._attr.times || []; - let timeMarker; - - return function (selection) { - selection.each(function (data) { - // Stack data - const layers = self.stackData(data); - - // Get the width and height - const width = elWidth; - const height = elHeight - margin.top - margin.bottom; - - if (addTimeMarker) { - timeMarker = new TimeMarker(times, xScale, height); - } - - if (width < minWidth || height < minHeight) { - throw new errors.ContainerTooSmall(); - } - self.validateWiggleSelection(); - - // Select the current DOM element - const div = d3.select(this); - - // Create the canvas for the visualization - const svg = div.append('svg') - .attr('width', width) - .attr('height', height + margin.top + margin.bottom) - .append('g') - .attr('transform', 'translate(0,' + margin.top + ')'); - - // add clipPath to hide circles when they go out of bounds - self.addClipPath(svg, width, height); - self.createEndZones(svg); - - // add path - self.addPath(svg, layers); - - if (yMin < 0 && self._attr.mode !== 'wiggle' && self._attr.mode !== 'silhouette') { - - // Draw line at yScale 0 value - svg.append('line') - .attr('class', 'zero-line') - .attr('x1', 0) - .attr('y1', yScale(0)) - .attr('x2', width) - .attr('y2', yScale(0)) - .style('stroke', '#ddd') - .style('stroke-width', 1); - } - - // add circles - const circles = self.addCircles(svg, layers); - - // add click and hover events to circles - self.addCircleEvents(circles, svg); - - // chart base line - svg.append('line') - .attr('class', 'base-line') - .attr('x1', 0) - .attr('y1', yScale(0)) - .attr('x2', width) - .attr('y2', yScale(0)) - .style('stroke', '#ddd') - .style('stroke-width', 1); - - if (addTimeMarker) { - timeMarker.render(svg); - } - - self.events.emit('rendered', { - chart: data - }); - - return svg; - }); - }; - }; - } - - return AreaChart; -}; diff --git a/src/ui/public/vislib/visualizations/column_chart.js b/src/ui/public/vislib/visualizations/column_chart.js deleted file mode 100644 index c587025f85564..0000000000000 --- a/src/ui/public/vislib/visualizations/column_chart.js +++ /dev/null @@ -1,329 +0,0 @@ -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import moment from 'moment'; -import errors from 'ui/errors'; -import VislibVisualizationsPointSeriesChartProvider from 'ui/vislib/visualizations/_point_series_chart'; -import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; -export default function ColumnChartFactory(Private) { - - const PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); - const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); - - /** - * 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 - */ - class ColumnChart extends PointSeriesChart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); - - // Column chart specific attributes - this._attr = _.defaults(handler._attr || {}, { - xValue: function (d) { - return d.x; - }, - yValue: function (d) { - return d.y; - } - }); - } - - /** - * Adds SVG rect to Vertical Bar Chart - * - * @method addBars - * @param svg {HTMLElement} SVG to which rect are appended - * @param layers {Array} Chart data array - * @returns {D3.UpdateSelection} SVG with rect added - */ - addBars(svg, layers) { - const self = this; - const color = this.handler.data.getColorFunc(); - const tooltip = this.tooltip; - const isTooltip = this._attr.addTooltip; - - const layer = svg.selectAll('.layer') - .data(layers) - .enter().append('g') - .attr('class', function (d, i) { - return 'series ' + i; - }); - - const bars = layer.selectAll('rect') - .data(function (d) { - return d; - }); - - bars - .exit() - .remove(); - - bars - .enter() - .append('rect') - .call(this._addIdentifier) - .attr('fill', function (d) { - return color(d.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) { - const offset = this._attr.mode; - - if (offset === 'grouped') { - return this.addGroupedBars(bars); - } - return this.addStackedBars(bars); - }; - - /** - * Adds stacked bars to column chart visualization - * - * @method addStackedBars - * @param bars {D3.UpdateSelection} SVG with rect added - * @returns {D3.UpdateSelection} - */ - addStackedBars(bars) { - const data = this.chartData; - const xScale = this.handler.xAxis.xScale; - const yScale = this.handler.yAxis.yScale; - const height = yScale.range()[0]; - const yMin = this.handler.yAxis.yScale.domain()[0]; - - let barWidth; - if (data.ordered && data.ordered.date) { - const start = data.ordered.min; - const end = moment(data.ordered.min).add(data.ordered.interval).valueOf(); - - barWidth = xScale(end) - xScale(start); - barWidth = barWidth - Math.min(barWidth * 0.25, 15); - } - - // update - bars - .attr('x', function (d) { - return xScale(d.x); - }) - .attr('width', function () { - return barWidth || xScale.rangeBand(); - }) - .attr('y', function (d) { - if (d.y < 0) { - return yScale(d.y0); - } - - return yScale(d.y0 + d.y); - }) - .attr('height', function (d) { - if (d.y < 0) { - return Math.abs(yScale(d.y0 + d.y) - yScale(d.y0)); - } - - // Due to an issue with D3 not returning zeros correctly when using - // an offset='expand', need to add conditional statement to handle zeros - // appropriately - if (d._input.y === 0) { - return 0; - } - - // 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 yScale(d.y0) - yScale(d.y0 + d.y); - }); - - 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.handler.xAxis.xScale; - const yScale = this.handler.yAxis.yScale; - const data = this.chartData; - const n = data.series.length; - const height = yScale.range()[0]; - const groupSpacingPercentage = 0.15; - const isTimeScale = (data.ordered && data.ordered.date); - const minWidth = 1; - let barWidth; - - // update - bars - .attr('x', function (d, i, j) { - if (isTimeScale) { - const groupWidth = xScale(data.ordered.min + data.ordered.interval) - - xScale(data.ordered.min); - const groupSpacing = groupWidth * groupSpacingPercentage; - - barWidth = (groupWidth - groupSpacing) / n; - - return xScale(d.x) + barWidth * j; - } - return xScale(d.x) + xScale.rangeBand() / n * j; - }) - .attr('width', function () { - if (barWidth < minWidth) { - throw new errors.ContainerTooSmall(); - } - - if (isTimeScale) { - return barWidth; - } - return xScale.rangeBand() / n; - }) - .attr('y', function (d) { - if (d.y < 0) { - return yScale(0); - } - - return yScale(d.y); - }) - .attr('height', function (d) { - return Math.abs(yScale(0) - yScale(d.y)); - }); - - return bars; - }; - - /** - * Adds Events to SVG rect - * Visualization is only brushable when a brush event is added - * If a brush event is added, then a function should be returned. - * - * @method addBarEvents - * @param element {D3.UpdateSelection} target - * @param svg {D3.UpdateSelection} chart SVG - * @returns {D3.Selection} rect with event listeners attached - */ - addBarEvents(element, svg) { - const events = this.events; - const isBrushable = events.isBrushable(); - const brush = isBrushable ? events.addBrushEvent(svg) : undefined; - const hover = events.addHoverEvent(); - const mouseout = events.addMouseoutEvent(); - const click = events.addClickEvent(); - const attachedEvents = element.call(hover).call(mouseout).call(click); - - if (isBrushable) { - attachedEvents.call(brush); - } - - return attachedEvents; - }; - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the vertical bar chart - */ - draw() { - const self = this; - const $elem = $(this.chartEl); - const margin = this._attr.margin; - const elWidth = this._attr.width = $elem.width(); - const elHeight = this._attr.height = $elem.height(); - const yScale = this.handler.yAxis.yScale; - const xScale = this.handler.xAxis.xScale; - const minWidth = 20; - const minHeight = 20; - const addTimeMarker = this._attr.addTimeMarker; - const times = this._attr.times || []; - let timeMarker; - - return function (selection) { - selection.each(function (data) { - const layers = self.stackData(data); - - const width = elWidth; - const height = elHeight - margin.top - margin.bottom; - if (width < minWidth || height < minHeight) { - throw new errors.ContainerTooSmall(); - } - self.validateDataCompliesWithScalingMethod(data); - - if (addTimeMarker) { - timeMarker = new TimeMarker(times, xScale, height); - } - - if ( - data.series.length > 1 && - (self._attr.scale === 'log' || self._attr.scale === 'square root') && - (self._attr.mode === 'stacked' || self._attr.mode === 'percentage') - ) { - throw new errors.StackedBarChartConfig(`Cannot display ${self._attr.mode} bar charts for multiple data series \ - with a ${self._attr.scale} scaling method. Try 'linear' scaling instead.`); - } - - const div = d3.select(this); - - const svg = div.append('svg') - .attr('width', width) - .attr('height', height + margin.top + margin.bottom) - .append('g') - .attr('transform', 'translate(0,' + margin.top + ')'); - - const bars = self.addBars(svg, layers); - self.createEndZones(svg); - - // Adds event listeners - self.addBarEvents(bars, svg); - - svg.append('line') - .attr('class', 'base-line') - .attr('x1', 0) - .attr('y1', yScale(0)) - .attr('x2', width) - .attr('y2', yScale(0)) - .style('stroke', '#ddd') - .style('stroke-width', 1); - - if (addTimeMarker) { - timeMarker.render(svg); - } - - self.events.emit('rendered', { - chart: data - }); - - return svg; - }); - }; - }; - } - - return ColumnChart; -}; diff --git a/src/ui/public/vislib/visualizations/line_chart.js b/src/ui/public/vislib/visualizations/line_chart.js deleted file mode 100644 index d1721c59dc63b..0000000000000 --- a/src/ui/public/vislib/visualizations/line_chart.js +++ /dev/null @@ -1,353 +0,0 @@ -import d3 from 'd3'; -import _ from 'lodash'; -import $ from 'jquery'; -import errors from 'ui/errors'; -import VislibVisualizationsPointSeriesChartProvider from 'ui/vislib/visualizations/_point_series_chart'; -import VislibVisualizationsTimeMarkerProvider from 'ui/vislib/visualizations/time_marker'; -export default function LineChartFactory(Private) { - - const PointSeriesChart = Private(VislibVisualizationsPointSeriesChartProvider); - const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); - - /** - * 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 - */ - class LineChart extends PointSeriesChart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); - - // Line chart specific attributes - this._attr = _.defaults(handler._attr || {}, { - interpolate: 'linear', - xValue: function (d) { - return d.x; - }, - yValue: function (d) { - return d.y; - } - }); - } - - /** - * Adds Events to SVG circle - * - * @method addCircleEvents - * @param element{D3.UpdateSelection} Reference to SVG circle - * @returns {D3.Selection} SVG circles with event listeners attached - */ - addCircleEvents(element, svg) { - const events = this.events; - const isBrushable = events.isBrushable(); - const brush = isBrushable ? events.addBrushEvent(svg) : undefined; - const hover = events.addHoverEvent(); - const mouseout = events.addMouseoutEvent(); - const click = events.addClickEvent(); - const attachedEvents = element.call(hover).call(mouseout).call(click); - - if (isBrushable) { - attachedEvents.call(brush); - } - - return attachedEvents; - }; - - /** - * Adds circles to SVG - * - * @method addCircles - * @param svg {HTMLElement} SVG to which rect are appended - * @param data {Array} Array of object data points - * @returns {D3.UpdateSelection} SVG with circles added - */ - addCircles(svg, data) { - const self = this; - const showCircles = this._attr.showCircles; - const color = this.handler.data.getColorFunc(); - const xScale = this.handler.xAxis.xScale; - const yScale = this.handler.yAxis.yScale; - const ordered = this.handler.data.get('ordered'); - const tooltip = this.tooltip; - const isTooltip = this._attr.addTooltip; - - const radii = _(data) - .map(function (series) { - return _.pluck(series, '_input.z'); - }) - .flattenDeep() - .reduce(function (result, val) { - if (result.min > val) result.min = val; - if (result.max < val) result.max = val; - return result; - }, { - min: Infinity, - max: -Infinity - }); - - const radiusStep = ((radii.max - radii.min) || (radii.max * 100)) / Math.pow(this._attr.radiusRatio, 2); - - const layer = svg.selectAll('.points') - .data(data) - .enter() - .append('g') - .attr('class', 'points line'); - - const circles = layer - .selectAll('circle') - .data(function appendData(data) { - return data.filter(function (d) { - return !_.isNull(d.y); - }); - }); - - circles - .exit() - .remove(); - - function cx(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - } - - function cy(d) { - return yScale(d.y); - } - - function cColor(d) { - return color(d.label); - } - - function colorCircle(d) { - 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(d); - } - - function getCircleRadiusFn(modifier) { - return function getCircleRadius(d) { - const margin = self._attr.margin; - const width = self._attr.width - margin.left - margin.right; - const height = self._attr.height - margin.top - margin.bottom; - const circleRadius = (d._input.z - radii.min) / radiusStep; - - return _.min([Math.sqrt((circleRadius || 2) + 2), width, height]) + (modifier || 0); - }; - } - - - circles - .enter() - .append('circle') - .attr('r', getCircleRadiusFn()) - .attr('fill-opacity', (this._attr.drawLinesBetweenPoints ? 1 : 0.7)) - .attr('cx', cx) - .attr('cy', cy) - .attr('class', 'circle-decoration') - .call(this._addIdentifier) - .attr('fill', colorCircle); - - circles - .enter() - .append('circle') - .attr('r', getCircleRadiusFn(10)) - .attr('cx', cx) - .attr('cy', cy) - .attr('fill', 'transparent') - .attr('class', 'circle') - .call(this._addIdentifier) - .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 - */ - addLines(svg, data) { - const xScale = this.handler.xAxis.xScale; - const yScale = this.handler.yAxis.yScale; - const xAxisFormatter = this.handler.data.get('xAxisFormatter'); - const color = this.handler.data.getColorFunc(); - const ordered = this.handler.data.get('ordered'); - const interpolate = (this._attr.smoothLines) ? 'cardinal' : this._attr.interpolate; - const line = d3.svg.line() - .defined(function (d) { - return !_.isNull(d.y); - }) - .interpolate(interpolate) - .x(function x(d) { - if (ordered && ordered.date) { - return xScale(d.x); - } - return xScale(d.x) + xScale.rangeBand() / 2; - }) - .y(function y(d) { - return yScale(d.y); - }); - - const lines = svg - .selectAll('.lines') - .data(data) - .enter() - .append('g') - .attr('class', 'pathgroup lines'); - - lines.append('path') - .call(this._addIdentifier) - .attr('d', function lineD(d) { - return line(d.values); - }) - .attr('fill', 'none') - .attr('stroke', function lineStroke(d) { - return color(d.label); - }) - .attr('stroke-width', 2); - - return lines; - }; - - /** - * Adds SVG clipPath - * - * @method addClipPath - * @param svg {HTMLElement} SVG to which clipPath is appended - * @param width {Number} SVG width - * @param height {Number} SVG height - * @returns {D3.UpdateSelection} SVG with clipPath added - */ - addClipPath(svg, width, height) { - const clipPathBuffer = 5; - const startX = 0; - const startY = 0 - clipPathBuffer; - const id = 'chart-area' + _.uniqueId(); - - return svg - .attr('clip-path', 'url(#' + id + ')') - .append('clipPath') - .attr('id', id) - .append('rect') - .attr('x', startX) - .attr('y', startY) - .attr('width', width) - // Adding clipPathBuffer to height so it doesn't - // cutoff the lower part of the chart - .attr('height', height + clipPathBuffer); - }; - - /** - * Renders d3 visualization - * - * @method draw - * @returns {Function} Creates the line chart - */ - draw() { - const self = this; - const $elem = $(this.chartEl); - const margin = this._attr.margin; - const elWidth = this._attr.width = $elem.width(); - const elHeight = this._attr.height = $elem.height(); - const scaleType = this.handler.yAxis.getScaleType(); - const yScale = this.handler.yAxis.yScale; - const xScale = this.handler.xAxis.xScale; - const minWidth = 20; - const minHeight = 20; - const startLineX = 0; - const lineStrokeWidth = 1; - const addTimeMarker = this._attr.addTimeMarker; - const times = this._attr.times || []; - let timeMarker; - - return function (selection) { - selection.each(function (data) { - const el = this; - - const layers = data.series.map(function mapSeries(d) { - const label = d.label; - return d.values.map(function mapValues(e, i) { - return { - _input: e, - label: label, - x: self._attr.xValue.call(d.values, e, i), - y: self._attr.yValue.call(d.values, e, i) - }; - }); - }); - - const width = elWidth - margin.left - margin.right; - const height = elHeight - margin.top - margin.bottom; - if (width < minWidth || height < minHeight) { - throw new errors.ContainerTooSmall(); - } - self.validateDataCompliesWithScalingMethod(data); - - if (addTimeMarker) { - timeMarker = new TimeMarker(times, xScale, height); - } - - - const div = d3.select(el); - - const svg = div.append('svg') - .attr('width', width + margin.left + margin.right) - .attr('height', height + margin.top + margin.bottom) - .append('g') - .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); - - self.addClipPath(svg, width, height); - if (self._attr.drawLinesBetweenPoints) { - self.addLines(svg, data.series); - } - const circles = self.addCircles(svg, layers); - self.addCircleEvents(circles, svg); - self.createEndZones(svg); - - const scale = (scaleType === 'log') ? yScale(1) : yScale(0); - if (scale) { - svg.append('line') - .attr('class', 'base-line') - .attr('x1', startLineX) - .attr('y1', scale) - .attr('x2', width) - .attr('y2', scale) - .style('stroke', '#ddd') - .style('stroke-width', lineStrokeWidth); - } - - if (addTimeMarker) { - timeMarker.render(svg); - } - - self.events.emit('rendered', { - chart: data - }); - - return svg; - }); - }; - }; - } - - return LineChart; -}; diff --git a/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js b/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js index 824160fa78471..f377deaa500b2 100644 --- a/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js +++ b/src/ui/public/vislib/visualizations/marker_types/geohash_grid.js @@ -1,5 +1,5 @@ import L from 'leaflet'; -import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; +import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker'; export default function GeohashGridMarkerFactory(Private) { const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); diff --git a/src/ui/public/vislib/visualizations/marker_types/heatmap.js b/src/ui/public/vislib/visualizations/marker_types/heatmap.js index d5878dec75d7a..c630898210c2f 100644 --- a/src/ui/public/vislib/visualizations/marker_types/heatmap.js +++ b/src/ui/public/vislib/visualizations/marker_types/heatmap.js @@ -1,7 +1,7 @@ import d3 from 'd3'; import _ from 'lodash'; import L from 'leaflet'; -import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; +import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker'; export default function HeatmapMarkerFactory(Private) { const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); diff --git a/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js b/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js index 9e6afcbd49ac8..7a368ce50e61b 100644 --- a/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js +++ b/src/ui/public/vislib/visualizations/marker_types/scaled_circles.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import L from 'leaflet'; -import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; +import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker'; export default function ScaledCircleMarkerFactory(Private) { const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); diff --git a/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js b/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js index 2d31cdc6585d0..dc26c203d1831 100644 --- a/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js +++ b/src/ui/public/vislib/visualizations/marker_types/shaded_circles.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import L from 'leaflet'; -import VislibVisualizationsMarkerTypesBaseMarkerProvider from 'ui/vislib/visualizations/marker_types/base_marker'; +import VislibVisualizationsMarkerTypesBaseMarkerProvider from './base_marker'; export default function ShadedCircleMarkerFactory(Private) { const BaseMarker = Private(VislibVisualizationsMarkerTypesBaseMarkerProvider); diff --git a/src/ui/public/vislib/visualizations/pie_chart.js b/src/ui/public/vislib/visualizations/pie_chart.js index fd2ccb8a9c841..2e5e2342e98d6 100644 --- a/src/ui/public/vislib/visualizations/pie_chart.js +++ b/src/ui/public/vislib/visualizations/pie_chart.js @@ -2,11 +2,19 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; import errors from 'ui/errors'; -import VislibVisualizationsChartProvider from 'ui/vislib/visualizations/_chart'; +import VislibVisualizationsChartProvider from './_chart'; export default function PieChartFactory(Private) { const Chart = Private(VislibVisualizationsChartProvider); + const defaults = { + isDonut: false, + showTooltip: true, + color: undefined, + fillColor: undefined, + xValue: d => d.x, + yValue: d => d.y + }; /** * Pie Chart Visualization * @@ -24,11 +32,10 @@ export default function PieChartFactory(Private) { const charts = this.handler.data.getVisData(); this._validatePieData(charts); - this._attr = _.defaults(handler._attr || {}, { - isDonut: handler._attr.isDonut || false - }); + this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults); } + /** * Checks whether pie slices have all zero values. * If so, an error is thrown. diff --git a/src/ui/public/vislib/visualizations/point_series.js b/src/ui/public/vislib/visualizations/point_series.js new file mode 100644 index 0000000000000..f682e222141fe --- /dev/null +++ b/src/ui/public/vislib/visualizations/point_series.js @@ -0,0 +1,248 @@ +import d3 from 'd3'; +import _ from 'lodash'; +import $ from 'jquery'; +import errors from 'ui/errors'; +import TooltipProvider from '../components/tooltip'; +import VislibVisualizationsChartProvider from './_chart'; +import VislibVisualizationsTimeMarkerProvider from './time_marker'; +import VislibVisualizationsSeriTypesProvider from './point_series/series_types'; + +export default function PointSeriesFactory(Private) { + + const Chart = Private(VislibVisualizationsChartProvider); + const Tooltip = Private(TooltipProvider); + const TimeMarker = Private(VislibVisualizationsTimeMarkerProvider); + const seriTypes = Private(VislibVisualizationsSeriTypesProvider); + const touchdownTmpl = _.template(require('../partials/touchdown.tmpl.html')); + /** + * Line Chart Visualization + * + * @class PointSeries + * @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 + */ + class PointSeries extends Chart { + constructor(handler, chartEl, chartData) { + super(handler, chartEl, chartData); + + this.handler = handler; + this.chartData = chartData; + this.chartEl = chartEl; + this.chartConfig = this.findChartConfig(); + this.handler.pointSeries = this; + } + + findChartConfig() { + const charts = this.handler.visConfig.get('charts'); + const chartIndex = this.handler.data.chartData().indexOf(this.chartData); + return charts[chartIndex]; + } + + addBackground(svg, width, height) { + const startX = 0; + const startY = 0; + + return svg + .append('rect') + .attr('x', startX) + .attr('y', startY) + .attr('width', width) + .attr('height', height) + .attr('fill', 'transparent') + .attr('class', 'background'); + }; + + addClipPath(svg) { + const {width, height} = svg.node().getBBox(); + const startX = 0; + const startY = 0; + this.clipPathId = 'chart-area' + _.uniqueId(); + + // Creating clipPath + return svg + .append('clipPath') + .attr('id', this.clipPathId) + .append('rect') + .attr('x', startX) + .attr('y', startY) + .attr('width', width) + .attr('height', height); + }; + + addEvents(svg) { + const isBrushable = this.events.isBrushable(); + if (isBrushable) { + const brush = this.events.addBrushEvent(svg); + return svg.call(brush); + } + }; + + createEndZones(svg) { + const self = this; + const xAxis = this.handler.categoryAxes[0]; + const xScale = xAxis.getScale(); + const ordered = xAxis.ordered; + const isHorizontal = xAxis.axisConfig.isHorizontal(); + const missingMinMax = !ordered || _.isUndefined(ordered.min) || _.isUndefined(ordered.max); + + if (missingMinMax || ordered.endzones === false) return; + + const {width, height} = svg.node().getBBox(); + + // we don't want to draw endzones over our min and max values, they + // are still a part of the dataset. We want to start the endzones just + // outside of them so we will use these values rather than ordered.min/max + const oneUnit = (ordered.units || _.identity)(1); + + // points on this axis represent the amount of time they cover, + // so draw the endzones at the actual time bounds + const leftEndzone = { + x: isHorizontal ? 0 : Math.max(xScale(ordered.min), 0), + w: isHorizontal ? Math.max(xScale(ordered.min), 0) : height - Math.max(xScale(ordered.min), 0) + }; + + const expandLastBucket = xAxis.axisConfig.get('scale.expandLastBucket'); + const rightLastVal = expandLastBucket ? ordered.max : Math.min(ordered.max, _.last(xAxis.values)); + const rightStart = rightLastVal + oneUnit; + const rightEndzone = { + x: isHorizontal ? xScale(rightStart) : 0, + w: isHorizontal ? Math.max(width - xScale(rightStart), 0) : xScale(rightStart) + }; + + this.endzones = svg.selectAll('.layer') + .data([leftEndzone, rightEndzone]) + .enter() + .insert('g', '.brush') + .attr('class', 'endzone') + .append('rect') + .attr('class', 'zone') + .attr('x', function (d) { + return isHorizontal ? d.x : 0; + }) + .attr('y', function (d) { + return isHorizontal ? 0 : d.x; + }) + .attr('height', function (d) { + return isHorizontal ? height : d.w; + }) + .attr('width', function (d) { + return isHorizontal ? d.w : width; + }); + + function callPlay(event) { + const boundData = event.target.__data__; + const mouseChartXCoord = event.clientX - self.chartEl.getBoundingClientRect().left; + const mouseChartYCoord = event.clientY - self.chartEl.getBoundingClientRect().top; + const wholeBucket = boundData && boundData.x != null; + + // the min and max that the endzones start in + const min = isHorizontal ? leftEndzone.w : rightEndzone.w; + const max = isHorizontal ? rightEndzone.x : leftEndzone.x; + + // bounds of the cursor to consider + let xLeft = isHorizontal ? mouseChartXCoord : mouseChartYCoord; + let xRight = isHorizontal ? mouseChartXCoord : mouseChartYCoord; + if (wholeBucket) { + xLeft = xScale(boundData.x); + xRight = xScale(xAxis.addInterval(boundData.x)); + } + + return { + wholeBucket: wholeBucket, + touchdown: min > xLeft || max < xRight + }; + } + + function textFormatter() { + return touchdownTmpl(callPlay(d3.event)); + } + + const endzoneTT = new Tooltip('endzones', this.handler.el, textFormatter, null); + this.tooltips.push(endzoneTT); + endzoneTT.order = 0; + endzoneTT.showCondition = function inEndzone() { + return callPlay(d3.event).touchdown; + }; + endzoneTT.render()(svg); + }; + + calculateRadiusLimits(data) { + this.radii = _(data.series) + .map(function (series) { + return _.map(series.values, 'z'); + }) + .flattenDeep() + .reduce(function (result, val) { + if (result.min > val) result.min = val; + if (result.max < val) result.max = val; + return result; + }, { + min: Infinity, + max: -Infinity + }); + } + + draw() { + let self = this; + let $elem = $(this.chartEl); + let margin = this.handler.visConfig.get('style.margin'); + const width = this.chartConfig.width = $elem.width(); + const height = this.chartConfig.height = $elem.height(); + let xScale = this.handler.categoryAxes[0].getScale(); + let minWidth = 50; + let minHeight = 50; + let addTimeMarker = this.chartConfig.addTimeMarker; + let times = this.chartConfig.times || []; + let timeMarker; + let div; + let svg; + + return function (selection) { + selection.each(function (data) { + const el = this; + + if (width < minWidth || height < minHeight) { + throw new errors.ContainerTooSmall(); + } + + if (addTimeMarker) { + timeMarker = new TimeMarker(times, xScale, height); + } + + div = d3.select(el); + + svg = div.append('svg') + .attr('width', width) + .attr('height', height); + + self.addBackground(svg, width, height); + self.addClipPath(svg); + self.addEvents(svg); + self.createEndZones(svg); + self.calculateRadiusLimits(data); + + self.series = []; + _.each(self.chartConfig.series, (seriArgs, i) => { + if (!seriArgs.show) return; + const SeriClass = seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')]; + const series = new SeriClass(self.handler, svg, data.series[i], seriArgs); + series.events = self.events; + svg.call(series.draw()); + self.series.push(series); + }); + + if (addTimeMarker) { + timeMarker.render(svg); + } + + return svg; + }); + }; + }; + } + + return PointSeries; +}; diff --git a/src/ui/public/vislib/visualizations/point_series/_point_series.js b/src/ui/public/vislib/visualizations/point_series/_point_series.js new file mode 100644 index 0000000000000..a15b35eadc844 --- /dev/null +++ b/src/ui/public/vislib/visualizations/point_series/_point_series.js @@ -0,0 +1,102 @@ +import _ from 'lodash'; +import errors from 'ui/errors'; + +export default function PointSeriesProvider(Private) { + + class PointSeries { + constructor(handler, seriesEl, seriesData, seriesConfig) { + this.handler = handler; + this.baseChart = handler.pointSeries; + this.chartEl = seriesEl; + this.chartData = seriesData; + this.seriesConfig = seriesConfig; + + this.validateDataCompliesWithScalingMethod(this.chartData); + } + + validateDataCompliesWithScalingMethod(data) { + const invalidLogScale = data.values && data.values.some(d => d.y < 1); + if (this.getValueAxis().axisConfig.isLogScale() && invalidLogScale) { + throw new errors.InvalidLogScaleValues(); + } + }; + + getStackedCount() { + return this.baseChart.chartConfig.series.reduce(function (sum, series) { + return series.mode === 'stacked' ? sum + 1 : sum; + }, 0); + }; + + getGroupedCount() { + const stacks = []; + return this.baseChart.chartConfig.series.reduce(function (sum, series) { + const valueAxis = series.valueAxis; + const isStacked = series.mode === 'stacked'; + const isHistogram = series.type === 'histogram'; + if (!isHistogram) return sum; + if (isStacked && stacks.includes(valueAxis)) return sum; + if (isStacked) stacks.push(valueAxis); + return sum + 1; + }, 0); + }; + + getStackedNum(data) { + let i = 0; + for (const seri of this.baseChart.chartConfig.series) { + if (seri.data === data) return i; + if (seri.mode === 'stacked') i++; + } + return 0; + }; + + getGroupedNum(data) { + let i = 0; + const stacks = []; + for (const seri of this.baseChart.chartConfig.series) { + const valueAxis = seri.valueAxis; + const isStacked = seri.mode === 'stacked'; + if (!isStacked) { + if (seri.data === data) return i; + i++; + } else { + if (!(valueAxis in stacks)) stacks[valueAxis] = i++; + if (seri.data === data) return stacks[valueAxis]; + } + } + return 0; + }; + + getValueAxis() { + return _.find(this.handler.valueAxes, axis => { + return axis.axisConfig.get('id') === this.seriesConfig.valueAxis; + }) || this.handler.valueAxes[0]; + }; + + getCategoryAxis() { + return _.find(this.handler.categoryAxes, axis => { + return axis.axisConfig.get('id') === this.seriesConfig.categoryAxis; + }) || this.handler.categoryAxes[0]; + }; + + addCircleEvents(element) { + const events = this.events; + const hover = events.addHoverEvent(); + const mouseout = events.addMouseoutEvent(); + const click = events.addClickEvent(); + return element.call(hover).call(mouseout).call(click); + }; + + checkIfEnoughData() { + const message = 'Area charts require more than one data point. Try adding ' + + 'an X-Axis Aggregation'; + + const notEnoughData = this.chartData.values.length < 2; + + if (notEnoughData) { + throw new errors.NotEnoughData(message); + } + }; + } + + return PointSeries; +}; diff --git a/src/ui/public/vislib/visualizations/point_series/area_chart.js b/src/ui/public/vislib/visualizations/point_series/area_chart.js new file mode 100644 index 0000000000000..a7b4a9ec51dac --- /dev/null +++ b/src/ui/public/vislib/visualizations/point_series/area_chart.js @@ -0,0 +1,238 @@ +import d3 from 'd3'; +import _ from 'lodash'; +import $ from 'jquery'; +import VislibVisualizationsPointSeriesProvider from './_point_series'; +export default function AreaChartFactory(Private) { + + const PointSeries = Private(VislibVisualizationsPointSeriesProvider); + + const defaults = { + mode: 'normal', + showCircles: true, + radiusRatio: 9, + showLines: true, + smoothLines: false, + 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 + */ + class AreaChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + + 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); + }; + } + + this.checkIfEnoughData(); + } + + 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.smoothLines) ? 'cardinal' : 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', () => { + return color(data.label); + }) + .classed('overlap_area', 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; + return yScale(y0 + d.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 (d) { + const area = getArea() + .defined(function (d) { + return !_.isNull(d.y); + }) + .interpolate(interpolate); + return area(data.values); + }); + + 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) { + if (isOverlapping) { + return yScale(d.y); + } + return yScale(d.y0 + d.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; + }; + + /** + * 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]); + + self.addPath(svg, self.chartData); + const circles = self.addCircles(svg, self.chartData); + self.addCircleEvents(circles); + + self.events.emit('rendered', { + chart: self.chartData + }); + + return svg; + }); + }; + }; + } + + return AreaChart; +}; diff --git a/src/ui/public/vislib/visualizations/point_series/column_chart.js b/src/ui/public/vislib/visualizations/point_series/column_chart.js new file mode 100644 index 0000000000000..4a81ad874470a --- /dev/null +++ b/src/ui/public/vislib/visualizations/point_series/column_chart.js @@ -0,0 +1,245 @@ +import _ from 'lodash'; +import moment from 'moment'; +import errors from 'ui/errors'; +import VislibVisualizationsPointSeriesProvider from './_point_series'; +export default function ColumnChartFactory(Private) { + + const PointSeries = Private(VislibVisualizationsPointSeriesProvider); + + const defaults = { + mode: 'normal', + showTooltip: true, + color: undefined, + fillColor: undefined + }; + /** + * 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 + */ + class ColumnChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); + } + + 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') + .attr('clip-path', 'url(#' + this.baseChart.clipPathId + ')'); + + const bars = layer.selectAll('rect') + .data(data.values); + + bars + .exit() + .remove(); + + bars + .enter() + .append('rect') + .attr('data-label', data.label) + .attr('fill', () => { + return 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 height = yScale.range()[0]; + const yMin = yScale.domain()[0]; + const groupSpacingPercentage = 0.15; + const groupCount = this.getGroupedCount(); + const groupNum = this.getGroupedNum(this.chartData); + + let barWidth; + if (isTimeScale) { + const {min, interval} = this.handler.data.get('ordered'); + let groupWidth = xScale(min + interval) - xScale(min); + if (!isHorizontal) groupWidth *= -1; + const groupSpacing = groupWidth * groupSpacingPercentage; + + barWidth = (groupWidth - groupSpacing) / groupCount; + } + + function x(d) { + const groupPosition = isTimeScale ? barWidth * groupNum : xScale.rangeBand() / groupCount * groupNum; + return xScale(d.x) + groupPosition; + } + + function y(d) { + if ((isHorizontal && d.y < 0) || (!isHorizontal && d.y > 0)) { + return yScale(d.y0); + } + /*if (!isHorizontal && d.y < 0) return yScale(d.y);*/ + return yScale(d.y0 + d.y); + } + + function widthFunc() { + return isTimeScale ? barWidth : 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)); + } + + // update + bars + .attr('x', isHorizontal ? x : y) + .attr('width', isHorizontal ? widthFunc : heightFunc) + .attr('y', isHorizontal ? y : x) + .attr('height', isHorizontal ? heightFunc : widthFunc); + + 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 groupCount = this.getGroupedCount(); + const groupNum = this.getGroupedNum(this.chartData); + const height = yScale.range()[0]; + const groupSpacingPercentage = 0.15; + const isTimeScale = this.getCategoryAxis().axisConfig.isTimeDomain(); + const isHorizontal = this.getCategoryAxis().axisConfig.isHorizontal(); + const isLogScale = this.getValueAxis().axisConfig.isLogScale(); + const minWidth = 1; + let barWidth; + + if (isTimeScale) { + const {min, interval} = this.handler.data.get('ordered'); + let groupWidth = xScale(min + interval) - xScale(min); + if (!isHorizontal) groupWidth *= -1; + const groupSpacing = groupWidth * groupSpacingPercentage; + + barWidth = (groupWidth - groupSpacing) / groupCount; + } + + function x(d) { + if (isTimeScale) { + return xScale(d.x) + barWidth * 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 widthFunc() { + if (barWidth < minWidth) { + throw new errors.ContainerTooSmall(); + } + + if (isTimeScale) { + return barWidth; + } + return xScale.rangeBand() / groupCount; + } + + function heightFunc(d) { + const baseValue = isLogScale ? 1 : 0; + return Math.abs(yScale(baseValue) - yScale(d.y)); + } + + // update + bars + .attr('x', isHorizontal ? x : y) + .attr('width', isHorizontal ? widthFunc : heightFunc) + .attr('y', isHorizontal ? y : x) + .attr('height', isHorizontal ? heightFunc : widthFunc); + + 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); + + self.events.emit('rendered', { + chart: self.chartData + }); + + return svg; + }); + }; + }; + } + + return ColumnChart; +}; diff --git a/src/ui/public/vislib/visualizations/point_series/line_chart.js b/src/ui/public/vislib/visualizations/point_series/line_chart.js new file mode 100644 index 0000000000000..3005d58be6d98 --- /dev/null +++ b/src/ui/public/vislib/visualizations/point_series/line_chart.js @@ -0,0 +1,216 @@ +import d3 from 'd3'; +import _ from 'lodash'; +import VislibVisualizationsPointSeriesProvider from './_point_series'; +export default function LineChartFactory(Private) { + + const PointSeries = Private(VislibVisualizationsPointSeriesProvider); + + const defaults = { + mode: 'normal', + showCircles: true, + radiusRatio: 9, + showLines: true, + smoothLines: false, + interpolate: 'linear', + 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 + */ + class LineChart extends PointSeries { + constructor(handler, chartEl, chartData, seriesConfigArgs) { + super(handler, chartEl, chartData, seriesConfigArgs); + 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 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); + }); + }); + + circles + .exit() + .remove(); + + function cx(d) { + if (ordered && ordered.date) { + return xScale(d.x); + } + return xScale(d.x) + xScale.rangeBand() / 2; + } + + function cy(d) { + return yScale(d.y); + } + + function cColor(d) { + return color(d.series); + } + + function colorCircle(d) { + 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(d); + } + + function getCircleRadiusFn(modifier) { + return function getCircleRadius(d) { + const margin = self.handler.visConfig.get('style.margin'); + const width = self.baseChart.chartConfig.width; + const height = self.baseChart.chartConfig.height; + const circleRadius = (d.z - radii.min) / radiusStep; + + return _.min([Math.sqrt((circleRadius || 2) + 2), 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 xAxisFormatter = this.handler.data.get('xAxisFormatter'); + const color = this.handler.data.getColorFunc(); + const ordered = this.handler.data.get('ordered'); + const interpolate = (this.seriesConfig.smoothLines) ? 'cardinal' : 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) { + return yScale(d.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', 2); + + 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); + + self.events.emit('rendered', { + chart: self.chartData + }); + + return svg; + }); + }; + }; + } + + return LineChart; +}; diff --git a/src/ui/public/vislib/visualizations/point_series/series_types.js b/src/ui/public/vislib/visualizations/point_series/series_types.js new file mode 100644 index 0000000000000..ba21c8a89ad90 --- /dev/null +++ b/src/ui/public/vislib/visualizations/point_series/series_types.js @@ -0,0 +1,12 @@ +import VislibVisualizationsColumnChartProvider from './column_chart'; +import VislibVisualizationsLineChartProvider from './line_chart'; +import VislibVisualizationsAreaChartProvider from './area_chart'; + +export default function SeriesTypeFactory(Private) { + + return { + histogram: Private(VislibVisualizationsColumnChartProvider), + line: Private(VislibVisualizationsLineChartProvider), + area: Private(VislibVisualizationsAreaChartProvider) + }; +}; diff --git a/src/ui/public/vislib/visualizations/tile_map.js b/src/ui/public/vislib/visualizations/tile_map.js index 3978cfdaa65e0..4f0dc1b13fe9e 100644 --- a/src/ui/public/vislib/visualizations/tile_map.js +++ b/src/ui/public/vislib/visualizations/tile_map.js @@ -1,8 +1,8 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import VislibVisualizationsChartProvider from 'ui/vislib/visualizations/_chart'; -import VislibVisualizationsMapProvider from 'ui/vislib/visualizations/_map'; +import VislibVisualizationsChartProvider from './_chart'; +import VislibVisualizationsMapProvider from './_map'; export default function TileMapFactory(Private) { const Chart = Private(VislibVisualizationsChartProvider); @@ -106,10 +106,10 @@ export default function TileMapFactory(Private) { center: params.mapCenter, zoom: params.mapZoom, events: this.events, - markerType: this._attr.mapType, + markerType: this.handler.visConfig.get('mapType'), tooltipFormatter: this.tooltipFormatter, valueFormatter: this.valueFormatter, - attr: this._attr + attr: this.handler.visConfig._values }); // add title for splits diff --git a/src/ui/public/vislib/visualizations/vis_types.js b/src/ui/public/vislib/visualizations/vis_types.js index 0cb1a8dcf0201..b8c2244f6a336 100644 --- a/src/ui/public/vislib/visualizations/vis_types.js +++ b/src/ui/public/vislib/visualizations/vis_types.js @@ -1,8 +1,6 @@ -import VislibVisualizationsColumnChartProvider from 'ui/vislib/visualizations/column_chart'; -import VislibVisualizationsPieChartProvider from 'ui/vislib/visualizations/pie_chart'; -import VislibVisualizationsLineChartProvider from 'ui/vislib/visualizations/line_chart'; -import VislibVisualizationsAreaChartProvider from 'ui/vislib/visualizations/area_chart'; -import VislibVisualizationsTileMapProvider from 'ui/vislib/visualizations/tile_map'; +import VislibVisualizationsPointSeriesProvider from './point_series'; +import VislibVisualizationsPieChartProvider from './pie_chart'; +import VislibVisualizationsTileMapProvider from './tile_map'; export default function VisTypeFactory(Private) { @@ -15,10 +13,8 @@ export default function VisTypeFactory(Private) { * @return {Function} Returns an Object of Visualization classes */ return { - histogram: Private(VislibVisualizationsColumnChartProvider), pie: Private(VislibVisualizationsPieChartProvider), - line: Private(VislibVisualizationsLineChartProvider), - area: Private(VislibVisualizationsAreaChartProvider), - tile_map: Private(VislibVisualizationsTileMapProvider) + tile_map: Private(VislibVisualizationsTileMapProvider), + point_series: Private(VislibVisualizationsPointSeriesProvider) }; }; diff --git a/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js b/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js index 654bb5f01d5f9..5cd644a5f78e5 100644 --- a/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js +++ b/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js @@ -84,7 +84,8 @@ describe('renderbot', function exportWrapper() { }); describe('param update', function () { - let params = { one: 'fish', two: 'fish' }; + let $el = $('
testing
'); + let params = { el: $el[0], one: 'fish', two: 'fish' }; let vis = { type: _.defaults({ params: { @@ -92,15 +93,11 @@ describe('renderbot', function exportWrapper() { } }, mockVisType) }; - let $el = $('
testing
'); let createVisSpy; - let getParamsStub; let renderbot; beforeEach(function () { createVisSpy = sinon.spy(VislibRenderbot.prototype, '_createVis'); - // getParamsStub = sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _identity); - // getParamsStub.returns(params); renderbot = new VislibRenderbot(vis, $el, persistedState); }); diff --git a/src/ui/public/visualize/visualize_legend.js b/src/ui/public/visualize/visualize_legend.js index 76047c1fe26e5..d8b297ef1c2a5 100644 --- a/src/ui/public/visualize/visualize_legend.js +++ b/src/ui/public/visualize/visualize_legend.js @@ -55,7 +55,7 @@ uiModules.get('kibana') }; $scope.toggleLegend = function () { - let bwcAddLegend = $scope.renderbot.vislibVis._attr.addLegend; + let bwcAddLegend = $scope.vis.params.addLegend; let bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; $scope.open = !$scope.uiState.get('vis.legendOpen', bwcLegendStateDefault); $scope.uiState.set('vis.legendOpen', $scope.open); @@ -100,11 +100,11 @@ uiModules.get('kibana') function refresh() { let vislibVis = $scope.renderbot.vislibVis; - if ($scope.uiState.get('vis.legendOpen') == null && vislibVis._attr.addLegend != null) { - $scope.open = vislibVis._attr.addLegend; + if ($scope.uiState.get('vis.legendOpen') == null && $scope.vis.params.addLegend != null) { + $scope.open = $scope.vis.params.addLegend; } - $scope.labels = getLabels($scope.data, vislibVis._attr.type); + $scope.labels = getLabels($scope.data, vislibVis.visConfigArgs.type); $scope.getColor = colorPalette(_.pluck($scope.labels, 'label'), $scope.uiState.get('vis.colors')); } diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 26af3c9846764..e1d451b1ba054 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -71,12 +71,8 @@ bdd.describe('discover app', function describeIndexTests() { }); bdd.it('should show the correct bar chart', async function () { - const expectedBarChartData = [ '3.237', - '17.674', '64.75', '125.737', '119.962', '65.712', '16.449', - '2.712', '3.675', '17.674', '59.762', '119.087', '123.812', - '61.862', '15.487', '2.362', '2.800', '15.312', '61.862', '123.2', - '118.562', '63.524', '17.587', '2.537' - ]; + const expectedBarChartData = [ 35, 189, 694, 1347, 1285, 704, 176, 29, 39, 189, 640, + 1276, 1327, 663, 166, 25, 30, 164, 663, 1320, 1270, 681, 188, 27 ]; await verifyChartData(expectedBarChartData); }); @@ -97,27 +93,16 @@ bdd.describe('discover app', function describeIndexTests() { bdd.it('should show correct data for chart interval Hourly', async function () { await PageObjects.discover.setChartInterval('Hourly'); - const expectedBarChartData = [ '1.527', '2.290', - '5.599', '7.890', '13.236', '30.290', '46.072', '55.490', '86.8', - '112', '122.181', '131.6', '132.872', '113.527', '102.581', - '81.709', '65.672', '43.781', '24.181', '14', '9.672', '6.109', - '0.763', '1.018', '2.800', '3.563', '4.327', '9.672', '12.472', - '29.272', '38.690', '54.981', '80.181', '102.327', '113.527', - '130.581', '132.363', '120.654', '107.163', '78.145', '58.545', - '43.272', '25.199', '12.218', '7.636', '3.818', '2.545', '0.509', - '2.036', '1.781', '4.327', '8.654', '9.418', '26.472', '38.945', - '61.345', '79.672', '102.836', '125.236', '130.327', '128.036', - '120.4', '96.472', '74.581', '70.509', '39.709', '25.199', '13.490', - '12.472', '4.072', '2.290', '1.018' - ]; + const expectedBarChartData = [ 4, 7, 16, 23, 38, 87, 132, 159, 248, 320, 349, 376, 380, + 324, 293, 233, 188, 125, 69, 40, 28, 17, 2, 3, 8, 10, 12, 28, 36, 84, 111, 157, 229, 292, + 324, 373, 378, 345, 306, 223, 167, 124, 72, 35, 22, 11, 7, 1, 6, 5, 12, 25, 27, 76, 111, 175, + 228, 294, 358, 372, 366, 344, 276, 213, 201, 113, 72, 39, 36, 12, 7, 3 ]; await verifyChartData(expectedBarChartData); }); bdd.it('should show correct data for chart interval Daily', async function () { const chartInterval = 'Daily'; - const expectedBarChartData = [ - '133.196', '129.192', '129.724' - ]; + const expectedBarChartData = [ 4757, 4614, 4633 ]; await PageObjects.discover.setChartInterval(chartInterval); await PageObjects.common.try(async () => { await verifyChartData(expectedBarChartData); @@ -126,7 +111,7 @@ bdd.describe('discover app', function describeIndexTests() { bdd.it('should show correct data for chart interval Weekly', async function () { const chartInterval = 'Weekly'; - const expectedBarChartData = [ '66.598', '129.458']; + const expectedBarChartData = [ 4757, 9247 ]; await PageObjects.discover.setChartInterval(chartInterval); await PageObjects.common.try(async () => { @@ -136,9 +121,7 @@ bdd.describe('discover app', function describeIndexTests() { bdd.it('browser back button should show previous interval Daily', async function () { const expectedChartInterval = 'Daily'; - const expectedBarChartData = [ - '133.196', '129.192', '129.724' - ]; + const expectedBarChartData = [ 4757, 4614, 4633 ]; await this.remote.goBack(); await PageObjects.common.try(async function tryingForTime() { @@ -150,7 +133,7 @@ bdd.describe('discover app', function describeIndexTests() { bdd.it('should show correct data for chart interval Monthly', async function () { const chartInterval = 'Monthly'; - const expectedBarChartData = [ '122.535']; + const expectedBarChartData = [ 13129 ]; await PageObjects.discover.setChartInterval(chartInterval); await verifyChartData(expectedBarChartData); @@ -158,7 +141,7 @@ bdd.describe('discover app', function describeIndexTests() { bdd.it('should show correct data for chart interval Yearly', async function () { const chartInterval = 'Yearly'; - const expectedBarChartData = [ '122.535']; + const expectedBarChartData = [ 13129 ]; await PageObjects.discover.setChartInterval(chartInterval); await verifyChartData(expectedBarChartData); @@ -166,12 +149,8 @@ bdd.describe('discover app', function describeIndexTests() { bdd.it('should show correct data for chart interval Auto', async function () { const chartInterval = 'Auto'; - const expectedBarChartData = [ '3.237', - '17.674', '64.75', '125.737', '119.962', '65.712', '16.449', - '2.712', '3.675', '17.674', '59.762', '119.087', '123.812', - '61.862', '15.487', '2.362', '2.800', '15.312', '61.862', '123.2', - '118.562', '63.524', '17.587', '2.537' - ]; + const expectedBarChartData = [ 35, 189, 694, 1347, 1285, 704, 176, 29, 39, 189, + 640, 1276, 1327, 663, 166, 25, 30, 164, 663, 1320, 1270, 681, 188, 27 ]; await PageObjects.discover.setChartInterval(chartInterval); await verifyChartData(expectedBarChartData); diff --git a/test/support/page_objects/discover_page.js b/test/support/page_objects/discover_page.js index 647dcbc2b2b0c..f8bb3720189e2 100644 --- a/test/support/page_objects/discover_page.js +++ b/test/support/page_objects/discover_page.js @@ -77,23 +77,59 @@ export default class DiscoverPage { } getBarChartData() { + var self = this; + var yAxisLabel = 0; + var yAxisHeight; + return PageObjects.header.isGlobalLoadingIndicatorHidden() .then(() => { return this.findTimeout - .findAllByCssSelector('rect[data-label="Count"]'); + .findByCssSelector('div.y-axis-div-wrapper > div > svg > g > g:last-of-type'); }) - .then(function (chartData) { - - function getChartData(chart) { - return chart - .getAttribute('height'); - } - - var getChartDataPromises = chartData.map(getChartData); - return Promise.all(getChartDataPromises); + .then(function setYAxisLabel(y) { + return y + .getVisibleText() + .then(function (yLabel) { + yAxisLabel = yLabel.replace(',', ''); + PageObjects.common.debug('yAxisLabel = ' + yAxisLabel); + return yLabel; + }); + }) + // 2). find and save the y-axis pixel size (the chart height) + .then(function getRect() { + return self + .findTimeout + .findByCssSelector('rect.background') + .then(function getRectHeight(chartAreaObj) { + return chartAreaObj + .getAttribute('height') + .then(function (theHeight) { + yAxisHeight = theHeight; // - 5; // MAGIC NUMBER - clipPath extends a bit above the top of the y-axis and below x-axis + PageObjects.common.debug('theHeight = ' + theHeight); + return theHeight; + }); + }); }) - .then(function (bars) { - return bars; + // 3). get the chart-wrapper elements + .then(function () { + return self + .findTimeout + // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) + .findAllByCssSelector('svg > g > g.series > rect') // rect + .then(function (chartTypes) { + function getChartType(chart) { + return chart + .getAttribute('height') + .then(function (barHeight) { + return Math.round(barHeight / yAxisHeight * yAxisLabel); + }); + } + var getChartTypesPromises = chartTypes.map(getChartType); + return Promise.all(getChartTypesPromises); + }) + .then(function (bars) { + return bars; + }); }); } diff --git a/test/support/page_objects/visualize_page.js b/test/support/page_objects/visualize_page.js index c048aa9fa95c6..f345f53ea7703 100644 --- a/test/support/page_objects/visualize_page.js +++ b/test/support/page_objects/visualize_page.js @@ -502,7 +502,7 @@ export default class VisualizePage { .findByCssSelector('clipPath rect') .getAttribute('height') .then(function (theHeight) { - yAxisHeight = theHeight - 5; // MAGIC NUMBER - clipPath extends a bit above the top of the y-axis and below x-axis + yAxisHeight = theHeight; PageObjects.common.debug('theHeight = ' + theHeight); return theHeight; }); @@ -581,7 +581,7 @@ export default class VisualizePage { return self .setFindTimeout(defaultFindTimeout * 2) // #kibana-body > div.content > div > div > div > div.vis-editor-canvas > visualize > div.visualize-chart > div > div.vis-col-wrapper > div.chart-wrapper > div > svg > g > g.series.\30 > rect:nth-child(1) - .findAllByCssSelector('svg > g > g.series.\\30 > rect') // rect + .findAllByCssSelector('svg > g > g.series > rect') // rect .then(function (chartTypes) { function getChartType(chart) { return chart From e14c3fcf20d4a64c3e9ca35424cb47b6b29404ef Mon Sep 17 00:00:00 2001 From: Arthur Date: Tue, 6 Dec 2016 11:00:30 -0200 Subject: [PATCH 03/17] Update connect-to-elasticsearch.asciidoc (#9361) Update documentation to show how to select an index as default. --- docs/setup/connect-to-elasticsearch.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 6ce6f540f91f9..18c22978e890a 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -32,8 +32,8 @@ sophisticated date parsing APIs that Kibana uses to determine date information, specify dates in the index pattern name. + . Click *Create* to add the index pattern. This first pattern is automatically configured as the default. -When you have more than one index pattern, you can designate which one to use as the default from -*Settings > Indices*. +When you have more than one index pattern, you can designate which one to use as the default by clicking +on the star icon above the index pattern title from *Management > Index Patterns*. All done! Kibana is now connected to your Elasticsearch data. Kibana displays a read-only list of fields configured for the matching index. From 7a5f34a044a1139731c39f1fb6d2ace1eb196e4b Mon Sep 17 00:00:00 2001 From: Steven Chen Date: Tue, 6 Dec 2016 21:07:04 +0800 Subject: [PATCH 04/17] Correct typo in 'Sharing a Dashboard' section (#9366) Change 'You can can..' to 'You can either...' --- docs/dashboard.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dashboard.asciidoc b/docs/dashboard.asciidoc index b4311a84af9a7..4b748f4ba5871 100644 --- a/docs/dashboard.asciidoc +++ b/docs/dashboard.asciidoc @@ -133,7 +133,7 @@ to open *Management/Kibana/Saved Objects/Dashboards*. [[sharing-dashboards]] == Sharing a Dashboard -You can can share a direct link to a Kibana dashboard with another user, +You can either share a direct link to a Kibana dashboard with another user, or embed the dashboard in a web page. Users must have Kibana access to view embedded dashboards. From 2ea5915ca378279836c7f50795756bfe1a1138d1 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Tue, 6 Dec 2016 10:36:27 -0500 Subject: [PATCH 05/17] Use this instead of self (#9380) --- .../courier/saved_object/saved_object.js | 295 +++++++++--------- 1 file changed, 146 insertions(+), 149 deletions(-) diff --git a/src/ui/public/courier/saved_object/saved_object.js b/src/ui/public/courier/saved_object/saved_object.js index 56e9f765dceae..68adc76233461 100644 --- a/src/ui/public/courier/saved_object/saved_object.js +++ b/src/ui/public/courier/saved_object/saved_object.js @@ -28,9 +28,6 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif function SavedObject(config) { if (!_.isObject(config)) config = {}; - // save an easy reference to this - let self = this; - /************ * Initialize config vars ************/ @@ -40,7 +37,7 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif // type name for this object, used as the ES-type const type = config.type; - self.getDisplayName = function () { + this.getDisplayName = function () { return type; }; @@ -49,8 +46,8 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif * completes. * @type {boolean} */ - self.isSaving = false; - self.defaults = config.defaults || {}; + this.isSaving = false; + this.defaults = config.defaults || {}; // Create a notifier for sending alerts let notify = new Notifier({ @@ -64,14 +61,64 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif let customInit = config.init || _.noop; // optional search source which this object configures - self.searchSource = config.searchSource ? new SearchSource() : undefined; + this.searchSource = config.searchSource ? new SearchSource() : undefined; // the id of the document - self.id = config.id || void 0; + this.id = config.id || void 0; // Whether to create a copy when the object is saved. This should eventually go away // in favor of a better rename/save flow. - self.copyOnSave = false; + this.copyOnSave = false; + + const parseSearchSource = (searchSourceJson) => { + if (!this.searchSource) return; + + // if we have a searchSource, set its state based on the searchSourceJSON field + let state; + try { + state = JSON.parse(searchSourceJson); + } catch (e) { + state = {}; + } + + let oldState = this.searchSource.toJSON(); + let fnProps = _.transform(oldState, function (dynamic, val, name) { + if (_.isFunction(val)) dynamic[name] = val; + }, {}); + + this.searchSource.set(_.defaults(state, fnProps)); + }; + + /** + * After creation or fetching from ES, ensure that the searchSources index indexPattern + * is an bonafide IndexPattern object. + * + * @return {Promise} + */ + const hydrateIndexPattern = () => { + if (!this.searchSource) { return Promise.resolve(null); } + + if (config.clearSavedIndexPattern) { + this.searchSource.set('index', undefined); + return Promise.resolve(null); + } + + let index = config.indexPattern || this.searchSource.getOwn('index'); + + if (!index) { return Promise.resolve(null); } + + // If index is not an IndexPattern object at this point, then it's a string id of an index. + if (!(index instanceof indexPatterns.IndexPattern)) { + index = indexPatterns.get(index); + } + + // At this point index will either be an IndexPattern, if cached, or a promise that + // will return an IndexPattern, if not cached. + return Promise.resolve(index) + .then((indexPattern) => { + this.searchSource.set('index', indexPattern); + }); + }; /** * Asynchronously initialize this object - will only run @@ -80,7 +127,7 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif * @return {Promise} * @resolved {SavedObject} */ - self.init = _.once(function () { + this.init = _.once(() => { // ensure that the type is defined if (!type) throw new Error('You must define a type name to use SavedObject objects.'); @@ -88,157 +135,107 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif docSource .index(kbnIndex) .type(type) - .id(self.id); + .id(this.id); // check that the mapping for this type is defined return mappingSetup.isDefined(type) - .then(function (defined) { - // if it is already defined skip this step - if (defined) return true; - - mapping.kibanaSavedObjectMeta = { - properties: { - // setup the searchSource mapping, even if it is not used but this type yet - searchSourceJSON: { - type: 'keyword' + .then(function (defined) { + // if it is already defined skip this step + if (defined) return true; + + mapping.kibanaSavedObjectMeta = { + properties: { + // setup the searchSource mapping, even if it is not used but this type yet + searchSourceJSON: { + type: 'keyword' + } } - } - }; + }; - // tell mappingSetup to set type - return mappingSetup.setup(type, mapping); - }) - .then(function () { - // If there is not id, then there is no document to fetch from elasticsearch - if (!self.id) { - // just assign the defaults and be done - _.assign(self, self.defaults); - return hydrateIndexPattern().then(() => { - return afterESResp.call(self); - }); - } + // tell mappingSetup to set type + return mappingSetup.setup(type, mapping); + }) + .then(() => { + // If there is not id, then there is no document to fetch from elasticsearch + if (!this.id) { + // just assign the defaults and be done + _.assign(this, this.defaults); + return hydrateIndexPattern().then(() => { + return afterESResp.call(this); + }); + } - // fetch the object from ES - return docSource.fetch().then(self.applyESResp); - }) - .then(function () { - return customInit.call(self); - }) - .then(function () { - // return our obj as the result of init() - return self; - }); + // fetch the object from ES + return docSource.fetch().then(this.applyESResp); + }) + .then(() => { + return customInit.call(this); + }) + .then(() => { + // return our obj as the result of init() + return this; + }); }); - self.applyESResp = function (resp) { - self._source = _.cloneDeep(resp._source); + this.applyESResp = (resp) => { + this._source = _.cloneDeep(resp._source); - if (resp.found != null && !resp.found) throw new errors.SavedObjectNotFound(type, self.id); + if (resp.found != null && !resp.found) throw new errors.SavedObjectNotFound(type, this.id); let meta = resp._source.kibanaSavedObjectMeta || {}; delete resp._source.kibanaSavedObjectMeta; - if (!config.indexPattern && self._source.indexPattern) { - config.indexPattern = self._source.indexPattern; - delete self._source.indexPattern; + if (!config.indexPattern && this._source.indexPattern) { + config.indexPattern = this._source.indexPattern; + delete this._source.indexPattern; } // assign the defaults to the response - _.defaults(self._source, self.defaults); + _.defaults(this._source, this.defaults); // transform the source using _deserializers - _.forOwn(mapping, function ittr(fieldMapping, fieldName) { + _.forOwn(mapping, (fieldMapping, fieldName) => { if (fieldMapping._deserialize) { - self._source[fieldName] = fieldMapping._deserialize(self._source[fieldName], resp, fieldName, fieldMapping); + this._source[fieldName] = fieldMapping._deserialize(this._source[fieldName], resp, fieldName, fieldMapping); } }); // Give obj all of the values in _source.fields - _.assign(self, self._source); - self.lastSavedTitle = self.title; + _.assign(this, this._source); + this.lastSavedTitle = this.title; return Promise.try(() => { parseSearchSource(meta.searchSourceJSON); return hydrateIndexPattern(); }) - .then(() => { - return Promise.cast(afterESResp.call(self, resp)); - }) - .then(() => { - // Any time obj is updated, re-call applyESResp - docSource.onUpdate().then(self.applyESResp, notify.fatal); - }); - }; - - function parseSearchSource(searchSourceJson) { - if (!self.searchSource) return; - - // if we have a searchSource, set its state based on the searchSourceJSON field - let state; - try { - state = JSON.parse(searchSourceJson); - } catch (e) { - state = {}; - } - - let oldState = self.searchSource.toJSON(); - let fnProps = _.transform(oldState, function (dynamic, val, name) { - if (_.isFunction(val)) dynamic[name] = val; - }, {}); - - self.searchSource.set(_.defaults(state, fnProps)); - } - - /** - * After creation or fetching from ES, ensure that the searchSources index indexPattern - * is an bonafide IndexPattern object. - * - * @return {Promise} - */ - function hydrateIndexPattern() { - if (!self.searchSource) { return Promise.resolve(null); } - - if (config.clearSavedIndexPattern) { - self.searchSource.set('index', undefined); - return Promise.resolve(null); - } - - let index = config.indexPattern || self.searchSource.getOwn('index'); - - if (!index) { return Promise.resolve(null); } - - // If index is not an IndexPattern object at this point, then it's a string id of an index. - if (!(index instanceof indexPatterns.IndexPattern)) { - index = indexPatterns.get(index); - } - - // At this point index will either be an IndexPattern, if cached, or a promise that - // will return an IndexPattern, if not cached. - return Promise.resolve(index) - .then((indexPattern) => { - self.searchSource.set('index', indexPattern); + .then(() => { + return Promise.cast(afterESResp.call(this, resp)); + }) + .then(() => { + // Any time obj is updated, re-call applyESResp + docSource.onUpdate().then(this.applyESResp, notify.fatal); }); - } + }; /** * Serialize this object * * @return {Object} */ - self.serialize = function () { + this.serialize = () => { let body = {}; - _.forOwn(mapping, function (fieldMapping, fieldName) { - if (self[fieldName] != null) { + _.forOwn(mapping, (fieldMapping, fieldName) => { + if (this[fieldName] != null) { body[fieldName] = (fieldMapping._serialize) - ? fieldMapping._serialize(self[fieldName]) - : self[fieldName]; + ? fieldMapping._serialize(this[fieldName]) + : this[fieldName]; } }); - if (self.searchSource) { + if (this.searchSource) { body.kibanaSavedObjectMeta = { - searchSourceJSON: angular.toJson(_.omit(self.searchSource.toJSON(), ['sort', 'size'])) + searchSourceJSON: angular.toJson(_.omit(this.searchSource.toJSON(), ['sort', 'size'])) }; } @@ -249,19 +246,27 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif * Returns true if the object's original title has been changed. New objects return false. * @return {boolean} */ - self.isTitleChanged = function () { - return self._source && self._source.title !== self.title; + this.isTitleChanged = () => { + return this._source && this._source.title !== this.title; }; + /** + * Queries es to refresh the index. + * @returns {Promise} + */ + function refreshIndex() { + return es.indices.refresh({ index: kbnIndex }); + } + /** * Saves this object. * * @return {Promise} * @resolved {String} - The id of the doc */ - self.save = function () { + this.save = () => { // Save the original id in case the save fails. - let originalId = self.id; + let originalId = this.id; // Read https://github.com/elastic/kibana/issues/9056 and // https://github.com/elastic/kibana/issues/9012 for some background into why this copyOnSave variable // exists. @@ -269,59 +274,51 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif // to expect a 'save as' flow during a rename, we are keeping the logic the same until a better // UI/UX can be worked out. if (this.copyOnSave) { - self.id = null; + this.id = null; } // Create a unique id for this object if it doesn't have one already. - self.id = this.id || uuid.v1(); + this.id = this.id || uuid.v1(); // ensure that the docSource has the current id - docSource.id(self.id); + docSource.id(this.id); - let source = self.serialize(); + let source = this.serialize(); - self.isSaving = true; + this.isSaving = true; return docSource.doIndex(source) - .then((id) => { self.id = id; }) - .then(self.refreshIndex) + .then((id) => { this.id = id; }) + .then(refreshIndex) .then(() => { - self.isSaving = false; - self.lastSavedTitle = self.title; - return self.id; + this.isSaving = false; + this.lastSavedTitle = this.title; + return this.id; }) - .catch(function (err) { - self.isSaving = false; - self.id = originalId; + .catch((err) => { + this.isSaving = false; + this.id = originalId; return Promise.reject(err); }); }; - self.destroy = function () { + this.destroy = () => { docSource.cancelQueued(); - if (self.searchSource) { - self.searchSource.cancelQueued(); + if (this.searchSource) { + this.searchSource.cancelQueued(); } }; - /** - * Queries es to refresh the index. - * @returns {Promise} - */ - self.refreshIndex = function () { - return es.indices.refresh({ index: kbnIndex }); - }; - /** * Delete this object from Elasticsearch * @return {promise} */ - self.delete = function () { + this.delete = () => { return es.delete( { index: kbnIndex, type: type, id: this.id }) - .then(() => { return this.refreshIndex(); }); + .then(() => { return refreshIndex(); }); }; } From e1428f72892e39a27d08941015e54cbba9d05347 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 6 Dec 2016 08:49:56 -0800 Subject: [PATCH 06/17] Remove loading message from Welcome loader. Slightly lower the loading message. (#9383) --- src/ui/views/ui_app.jade | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/src/ui/views/ui_app.jade b/src/ui/views/ui_app.jade index 83541bf027825..fae9982f1c22f 100644 --- a/src/ui/views/ui_app.jade +++ b/src/ui/views/ui_app.jade @@ -12,10 +12,6 @@ block content margin: 0; } - /** - * 1. The kibanaLoadingMessage will push the loader up. This top margin pushes it back down so - * it's in the same position as the login form. - */ .kibanaLoader { display: -webkit-box; display: -webkit-flex; @@ -24,7 +20,6 @@ block content width: 620px; height: 185px; padding: 0; - margin-top: 130px; /* 1 */ text-align: center; background: #3caed2; } @@ -60,7 +55,7 @@ block content font-size: 38px; font-weight: 300; font-family: "Open Sans", "Lato", "Helvetica Neue", Helvetica, Arial, sans-serif; - padding-bottom: 16px; + padding-bottom: 12px; } @-webkit-keyframes colorShift { @@ -93,43 +88,15 @@ block content } } - /** - * 1. If we change the height or top margin, we'll need to increase the top margin on - * kibanaLoader too. - */ - .kibanaLoadingMessage { - font-family: "Open Sans", Helvetica, Arial, sans-serif; - color: #8c8c8c; - max-width: 540px; - height: 50px; /* 1 */ - margin-top: 80px; /* 1 */ - text-align: center; - font-size: 18px; - line-height: 1.4; - opacity: 0.8; - } - .kibanaWelcomeView .kibanaLoader .kibanaLoader__logo .kibanaWelcomeLogo .kibanaLoader__content | Loading Kibana - .kibanaLoadingMessage( - data-remove-message-when-embedded - ) - | Give me a moment here. I’m loading a whole bunch of code. Don’t worry, all this - | good stuff will be cached up for next time! script. window.onload = function () { - - var hideLoadingMessage = /#.*[?&]embed(&|$|=true)/.test(window.location.href); - if (hideLoadingMessage) { - var loadingMessage = document.querySelector('[data-remove-message-when-embedded]'); - loadingMessage.parentNode.removeChild(loadingMessage); - } - var buildNum = #{kibanaPayload.buildNum}; var cacheParam = buildNum ? '?v=' + buildNum : ''; function bundleFile(filename) { From 0a577f1ce81e4edc6e982b250bdecec0b82d5cf7 Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Wed, 7 Dec 2016 13:54:19 -0700 Subject: [PATCH 07/17] Change mapping of index-pattern fields to text (#9353) * Change mapping of index-pattern fields to text * Update kibana index mappings from keyword to text to allow longer values * Update search source to text mapping instead of keyword * fix mapping setup test * Change from string to text --- .../kibana/public/dashboard/services/_saved_dashboard.js | 6 +++--- .../public/visualize/saved_visualizations/_saved_vis.js | 2 +- src/core_plugins/timelion/public/services/_saved_sheet.js | 2 +- src/ui/public/courier/saved_object/saved_object.js | 2 +- src/ui/public/index_patterns/_index_pattern.js | 2 +- src/ui/public/utils/__tests__/mapping_setup.js | 4 ++-- src/ui/public/utils/mapping_setup.js | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core_plugins/kibana/public/dashboard/services/_saved_dashboard.js b/src/core_plugins/kibana/public/dashboard/services/_saved_dashboard.js index 8712720e65cf0..0ab8d09b14dbe 100644 --- a/src/core_plugins/kibana/public/dashboard/services/_saved_dashboard.js +++ b/src/core_plugins/kibana/public/dashboard/services/_saved_dashboard.js @@ -49,9 +49,9 @@ module.factory('SavedDashboard', function (courier, config) { title: 'text', hits: 'integer', description: 'text', - panelsJSON: 'keyword', - optionsJSON: 'keyword', - uiStateJSON: 'keyword', + panelsJSON: 'text', + optionsJSON: 'text', + uiStateJSON: 'text', version: 'integer', timeRestore: 'boolean', timeTo: 'keyword', diff --git a/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js index 205a53e862931..596d0bd7ac6c7 100644 --- a/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js +++ b/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js @@ -55,7 +55,7 @@ uiModules SavedVis.mapping = { title: 'text', visState: 'json', - uiStateJSON: 'keyword', + uiStateJSON: 'text', description: 'text', savedSearchId: 'keyword', version: 'integer' diff --git a/src/core_plugins/timelion/public/services/_saved_sheet.js b/src/core_plugins/timelion/public/services/_saved_sheet.js index a59e3f4c8c403..d3a4636493207 100644 --- a/src/core_plugins/timelion/public/services/_saved_sheet.js +++ b/src/core_plugins/timelion/public/services/_saved_sheet.js @@ -41,7 +41,7 @@ module.factory('SavedSheet', function (courier, config) { title: 'text', hits: 'integer', description: 'text', - timelion_sheet: 'keyword', + timelion_sheet: 'text', timelion_interval: 'keyword', timelion_other_interval: 'keyword', timelion_chart_height: 'integer', diff --git a/src/ui/public/courier/saved_object/saved_object.js b/src/ui/public/courier/saved_object/saved_object.js index 68adc76233461..d2a3c11d23ef8 100644 --- a/src/ui/public/courier/saved_object/saved_object.js +++ b/src/ui/public/courier/saved_object/saved_object.js @@ -147,7 +147,7 @@ export default function SavedObjectFactory(es, kbnIndex, Promise, Private, Notif properties: { // setup the searchSource mapping, even if it is not used but this type yet searchSourceJSON: { - type: 'keyword' + type: 'text' } } }; diff --git a/src/ui/public/index_patterns/_index_pattern.js b/src/ui/public/index_patterns/_index_pattern.js index 1fa06d4cf6eeb..b12ce6e57844c 100644 --- a/src/ui/public/index_patterns/_index_pattern.js +++ b/src/ui/public/index_patterns/_index_pattern.js @@ -45,7 +45,7 @@ export default function IndexPatternFactory(Private, Notifier, config, kbnIndex, fields: 'json', sourceFilters: 'json', fieldFormatMap: { - type: 'keyword', + type: 'text', _serialize(map = {}) { const serialized = _.transform(map, serialize); return _.isEmpty(serialized) ? undefined : angular.toJson(serialized); diff --git a/src/ui/public/utils/__tests__/mapping_setup.js b/src/ui/public/utils/__tests__/mapping_setup.js index fbb23bdd3435a..218c17c58efd9 100644 --- a/src/ui/public/utils/__tests__/mapping_setup.js +++ b/src/ui/public/utils/__tests__/mapping_setup.js @@ -23,9 +23,9 @@ describe('ui/utils/mapping_setup', function () { }); context('when type is json', function () { - it('returned object is type keyword', function () { + it('returned object is type text', function () { const mapping = mappingSetup.expandShorthand({ foo: 'json' }); - expect(mapping.foo.type).to.be('keyword'); + expect(mapping.foo.type).to.be('text'); }); it('returned object has _serialize function', function () { diff --git a/src/ui/public/utils/mapping_setup.js b/src/ui/public/utils/mapping_setup.js index 79078224c0ca3..47ef8e10d4a83 100644 --- a/src/ui/public/utils/mapping_setup.js +++ b/src/ui/public/utils/mapping_setup.js @@ -45,7 +45,7 @@ define(function () { if (typeof val === 'string') val = { type: val }; if (val.type === 'json') { - val.type = 'keyword'; + val.type = 'text'; val._serialize = json._serialize; val._deserialize = json._deserialize; } From 050fc87a7068e3430b71a443f97dfc7cb9d7ceca Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Wed, 7 Dec 2016 16:04:14 -0500 Subject: [PATCH 08/17] Make panel scope isolate, and some cleanup (#9335) * make scope isolate * Make panel scope isolate Also clean up and simplify scope destroying Fix sorting on scripted date and boolean fields (#9261) The elasticsearch API only [supports][1][2] sort scripts of type `number` and `string`. Since dates need to be returned as millis since the epoch for visualizations to work anyway, we can simply add a condition to send dates as type number in the sort API. ES will cast booleans if we tell them its a string, so we can add a similar condition there as well. [1]: https://www.elastic.co/guide/en/elasticsearch/reference/5.0/search-request-sort.html#_script_based_sorting [2]: https://github.com/elastic/elasticsearch/blob/aeb97ff41298e26b107a733837dfe17f123c0c9b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java#L359 Fixes: https://github.com/elastic/kibana/issues/9257 Add docs on all available console server config options (#9288) settings: do not query ES for settings in non-green status (#9308) If the ui settings status is not green, that means there is at least one dependency (so elasticsearch at the moment) that is not met in order for it to function correctly, so we shouldn't attempt to determine user settings at all. This ensures that when something like the version check fails in the elasticsearch plugin, Kibana correctly behaves by not attempting requests to elasticsearch, which prevents 500 errors and allows users to see the error status on the status page. We also now periodically check for compatible elasticsearch versions so that Kibana can automatically recover if the elasticsearch node is upgraded to the appropriate version. Change loading screen background to white to make it less distracting when navigating between apps. (#9313) more refactoring Fix sorting on scripted date and boolean fields (#9261) The elasticsearch API only [supports][1][2] sort scripts of type `number` and `string`. Since dates need to be returned as millis since the epoch for visualizations to work anyway, we can simply add a condition to send dates as type number in the sort API. ES will cast booleans if we tell them its a string, so we can add a similar condition there as well. [1]: https://www.elastic.co/guide/en/elasticsearch/reference/5.0/search-request-sort.html#_script_based_sorting [2]: https://github.com/elastic/elasticsearch/blob/aeb97ff41298e26b107a733837dfe17f123c0c9b/core/src/main/java/org/elasticsearch/search/sort/ScriptSortBuilder.java#L359 Fixes: https://github.com/elastic/kibana/issues/9257 Add docs on all available console server config options (#9288) settings: do not query ES for settings in non-green status (#9308) If the ui settings status is not green, that means there is at least one dependency (so elasticsearch at the moment) that is not met in order for it to function correctly, so we shouldn't attempt to determine user settings at all. This ensures that when something like the version check fails in the elasticsearch plugin, Kibana correctly behaves by not attempting requests to elasticsearch, which prevents 500 errors and allows users to see the error status on the status page. We also now periodically check for compatible elasticsearch versions so that Kibana can automatically recover if the elasticsearch node is upgraded to the appropriate version. Change loading screen background to white to make it less distracting when navigating between apps. (#9313) Skip assertion when the test blips. (#9251) Tests fails intermittently, and for identical reasons. When this occurs, skip the test. This only occurs in CI and cannot be reproduced locally. Additional changes: - Use async/await to improve legibility. - Move async behaviour to beforeEach and keep test stubs synchronous to improve labeling of tests. [git] ignore extra files in the root config/ directory (#9296) upgrade makelogs (#9295) build: remove deepModules hackery (#9327) The deepModules hacks in the build system were added to support the long paths that resulted from npm2, but npm3 fundamentally addresses that problem, so deepModules is no longer necessary. In practical terms, npm3 shouldn't ever cause path lengths to become so long that they trigger path length problems on certain operating systems. more cleanup and tests Save/rename flow fix (#9087) * Save/rename flow fix - Use auto generated ids instead of coupling the id to the title which creates problems. - Adjust UI to make the save flow more understandable. - Remove confirmation on overwrite since we will now be creating duplicate objects even if they have the same title. * use undefined instead of null * Change titleChanged function name * address code review comments * Add isSaving flag to avoid checkbox flicker, fix regression bug from refactor. Added tests and fixed a couple bugs Updated info message * Update doc and nav title with new name on rename don't hardcode Dashboard * namespace panel factory cleanup * Fix parameter name in html * Address comments - Get rid of factory function and Panel class. - Rename panel.js to panel_state.js - Rename dashboard_panel_directive to dashboard_panel * Fix file path reference in tests. * Panel => PanelState --- .../dashboard/__tests__/dashboard_panels.js | 33 +++++- .../components/panel/lib/panel_state.js | 35 ++++++ .../components/panel/lib/panel_utils.js | 45 ++++++++ .../dashboard/components/panel/panel.html | 8 +- .../dashboard/components/panel/panel.js | 85 -------------- .../dashboard/directives/dashboard_panel.js | 104 ++++++++++++++++++ .../public/dashboard/directives/grid.js | 93 +++++++--------- .../kibana/public/dashboard/index.js | 30 ++--- 8 files changed, 269 insertions(+), 164 deletions(-) create mode 100644 src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js create mode 100644 src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js delete mode 100644 src/core_plugins/kibana/public/dashboard/components/panel/panel.js create mode 100644 src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js diff --git a/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js b/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js index 0611c8eba587a..fd7d982cd7194 100644 --- a/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js +++ b/src/core_plugins/kibana/public/dashboard/__tests__/dashboard_panels.js @@ -2,12 +2,13 @@ import angular from 'angular'; import expect from 'expect.js'; import ngMock from 'ng_mock'; import 'plugins/kibana/dashboard/services/_saved_dashboard'; +import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../components/panel/lib/panel_state'; describe('dashboard panels', function () { let $scope; let $el; - const compile = (dashboard) => { + function compile(dashboard) { ngMock.inject(($rootScope, $controller, $compile, $route) => { $scope = $rootScope.$new(); $route.current = { @@ -19,12 +20,16 @@ describe('dashboard panels', function () { $el = angular.element(` - `); + `); $compile($el)($scope); $scope.$digest(); }); }; + function findPanelWithVisualizationId(id) { + return $scope.state.panels.find((panel) => { return panel.id === id; }); + } + beforeEach(() => { ngMock.module('kibana'); }); @@ -77,10 +82,30 @@ describe('dashboard panels', function () { compile(dash); }); expect($scope.state.panels.length).to.be(16); - const foo8Panel = $scope.state.panels.find( - (panel) => { return panel.id === 'foo8'; }); + const foo8Panel = findPanelWithVisualizationId('foo8'); expect(foo8Panel).to.not.be(null); expect(foo8Panel.row).to.be(8); expect(foo8Panel.col).to.be(1); }); + + it('initializes visualizations with the default size', function () { + ngMock.inject((SavedDashboard) => { + let dash = new SavedDashboard(); + dash.init(); + dash.panelsJSON = `[ + {"col":3,"id":"foo1","row":1,"type":"visualization"}, + {"col":5,"id":"foo2","row":1,"size_x":5,"size_y":9,"type":"visualization"}]`; + compile(dash); + }); + expect($scope.state.panels.length).to.be(2); + const foo1Panel = findPanelWithVisualizationId('foo1'); + expect(foo1Panel).to.not.be(null); + expect(foo1Panel.size_x).to.be(DEFAULT_PANEL_WIDTH); + expect(foo1Panel.size_y).to.be(DEFAULT_PANEL_HEIGHT); + + const foo2Panel = findPanelWithVisualizationId('foo2'); + expect(foo2Panel).to.not.be(null); + expect(foo2Panel.size_x).to.be(5); + expect(foo2Panel.size_y).to.be(9); + }); }); diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js new file mode 100644 index 0000000000000..55d58c88d7e21 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_state.js @@ -0,0 +1,35 @@ +export const DEFAULT_PANEL_WIDTH = 3; +export const DEFAULT_PANEL_HEIGHT = 2; + +/** + * Represents a panel on a grid. Keeps track of position in the grid and what visualization it + * contains. + * + * @typedef {Object} PanelState + * @property {number} id - Id of the visualization contained in the panel. + * @property {Element} $el - A reference to the gridster widget holding this panel. Used to + * update the size and column attributes. TODO: move out of panel state as this couples state to ui. + * @property {string} type - Type of the visualization in the panel. + * @property {number} panelId - Unique id to represent this panel in the grid. + * @property {number} size_x - Width of the panel. + * @property {number} size_y - Height of the panel. + * @property {number} col - Column index in the grid. + * @property {number} row - Row index in the grid. + */ + +/** + * Creates and initializes a basic panel state. + * @param {number} id + * @param {string} type + * @param {number} panelId + * @return {PanelState} + */ +export function createPanelState(id, type, panelId) { + return { + size_x: DEFAULT_PANEL_WIDTH, + size_y: DEFAULT_PANEL_HEIGHT, + panelId: panelId, + type: type, + id: id + }; +} diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js new file mode 100644 index 0000000000000..5856d71f884f6 --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/components/panel/lib/panel_utils.js @@ -0,0 +1,45 @@ +import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from 'plugins/kibana/dashboard/components/panel/lib/panel_state'; + +export class PanelUtils { + /** + * Fills in default parameters where not specified. + * @param {PanelState} panel + */ + static initializeDefaults(panel) { + panel.size_x = panel.size_x || DEFAULT_PANEL_WIDTH; + panel.size_y = panel.size_y || DEFAULT_PANEL_HEIGHT; + + if (!panel.id) { + // In the interest of backwards comparability + if (panel.visId) { + panel.id = panel.visId; + panel.type = 'visualization'; + delete panel.visId; + } else { + throw new Error('Missing object id on panel'); + } + } + } + + /** + * Ensures that the panel object has the latest size/pos info. + * @param {PanelState} panel + */ + static refreshSizeAndPosition(panel) { + const data = panel.$el.coords().grid; + panel.size_x = data.size_x; + panel.size_y = data.size_y; + panel.col = data.col; + panel.row = data.row; + } + + /** + * $el is a circular structure because it contains a reference to it's parent panel, + * so it needs to be removed before it can be serialized (we also don't + * want it to show up in the url). + * @param {PanelState} panel + */ + static makeSerializeable(panel) { + delete panel.$el; + } +} diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/panel.html b/src/core_plugins/kibana/public/dashboard/components/panel/panel.html index 874a62b75522a..1cba6ed1d7ca5 100644 --- a/src/core_plugins/kibana/public/dashboard/components/panel/panel.html +++ b/src/core_plugins/kibana/public/dashboard/components/panel/panel.html @@ -4,13 +4,13 @@ {{::savedObj.title}} @@ -26,7 +26,7 @@ ng-switch-when="visualization" vis="savedObj.vis" search-source="savedObj.searchSource" - show-spy-panel="chrome.getVisible()" + show-spy-panel="!isFullScreenMode" ui-state="uiState" render-counter class="panel-content"> diff --git a/src/core_plugins/kibana/public/dashboard/components/panel/panel.js b/src/core_plugins/kibana/public/dashboard/components/panel/panel.js deleted file mode 100644 index b386335eb096d..0000000000000 --- a/src/core_plugins/kibana/public/dashboard/components/panel/panel.js +++ /dev/null @@ -1,85 +0,0 @@ -import _ from 'lodash'; -import 'ui/visualize'; -import 'ui/doc_table'; -import { loadPanelProvider } from 'plugins/kibana/dashboard/components/panel/lib/load_panel'; -import FilterManagerProvider from 'ui/filter_manager'; -import uiModules from 'ui/modules'; -import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html'; - -uiModules -.get('app/dashboard') -.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector) { - const loadPanel = Private(loadPanelProvider); - const filterManager = Private(FilterManagerProvider); - - const services = require('plugins/kibana/management/saved_object_registry').all().map(function (serviceObj) { - const service = $injector.get(serviceObj.service); - return { - type: service.type, - name: serviceObj.service - }; - }); - - const getPanelId = function (panel) { - return ['P', panel.panelIndex].join('-'); - }; - - return { - restrict: 'E', - template: panelTemplate, - link: function ($scope) { - // using $scope inheritance, panels are available in AppState - const $state = $scope.state; - - // receives $scope.panel from the dashboard grid directive, seems like should be isolate? - $scope.$watch('id', function () { - if (!$scope.panel.id || !$scope.panel.type) return; - - loadPanel($scope.panel, $scope) - .then(function (panelConfig) { - // These could be done in loadPanel, putting them here to make them more explicit - $scope.savedObj = panelConfig.savedObj; - $scope.editUrl = panelConfig.editUrl; - $scope.$on('$destroy', function () { - panelConfig.savedObj.destroy(); - $scope.parentUiState.removeChild(getPanelId(panelConfig.panel)); - }); - - // create child ui state from the savedObj - const uiState = panelConfig.uiState || {}; - $scope.uiState = $scope.parentUiState.createChild(getPanelId(panelConfig.panel), uiState, true); - const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef - if (panelSavedVis) { - panelSavedVis.setUiState($scope.uiState); - } - - $scope.filter = function (field, value, operator) { - const index = $scope.savedObj.searchSource.get('index').id; - filterManager.add(field, value, operator, index); - }; - }) - .catch(function (e) { - $scope.error = e.message; - - // If the savedObjectType matches the panel type, this means the object itself has been deleted, - // so we shouldn't even have an edit link. If they don't match, it means something else is wrong - // with the object (but the object still exists), so we link to the object editor instead. - const objectItselfDeleted = e.savedObjectType === $scope.panel.type; - if (objectItselfDeleted) return; - - const type = $scope.panel.type; - const id = $scope.panel.id; - const service = _.find(services, { type: type }); - if (!service) return; - - $scope.editUrl = '#management/kibana/objects/' + service.name + '/' + id + '?notFound=' + e.savedObjectType; - }); - - }); - - $scope.remove = function () { - _.pull($state.panels, $scope.panel); - }; - } - }; -}); diff --git a/src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js b/src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js new file mode 100644 index 0000000000000..ed1736349991d --- /dev/null +++ b/src/core_plugins/kibana/public/dashboard/directives/dashboard_panel.js @@ -0,0 +1,104 @@ +import _ from 'lodash'; +import 'ui/visualize'; +import 'ui/doc_table'; +import { loadPanelProvider } from 'plugins/kibana/dashboard/components/panel/lib/load_panel'; +import FilterManagerProvider from 'ui/filter_manager'; +import uiModules from 'ui/modules'; +import panelTemplate from 'plugins/kibana/dashboard/components/panel/panel.html'; + +uiModules +.get('app/dashboard') +.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $injector) { + const loadPanel = Private(loadPanelProvider); + const filterManager = Private(FilterManagerProvider); + + const services = require('plugins/kibana/management/saved_object_registry').all().map(function (serviceObj) { + const service = $injector.get(serviceObj.service); + return { + type: service.type, + name: serviceObj.service + }; + }); + + /** + * Returns a unique id for storing the panel state in the persistent ui. + * @param {PanelState} panel + * @returns {string} + */ + const getPersistedStateId = function (panel) { + return `P-${panel.panelId}`; + }; + + return { + restrict: 'E', + template: panelTemplate, + scope: { + /** + * Whether or not the dashboard this panel is contained on is in 'full screen mode'. + * @type {boolean} + */ + isFullScreenMode: '=', + /** + * The parent's persisted state is used to create a child persisted state for the + * panel. + * @type {PersistedState} + */ + parentUiState: '=', + /** + * Contains information about this panel. + * @type {PanelState} + */ + panel: '=', + /** + * Handles removing this panel from the grid. + * @type {() => void} + */ + remove: '&' + }, + link: function ($scope, element) { + if (!$scope.panel.id || !$scope.panel.type) return; + + loadPanel($scope.panel, $scope) + .then(function (panelConfig) { + // These could be done in loadPanel, putting them here to make them more explicit + $scope.savedObj = panelConfig.savedObj; + $scope.editUrl = panelConfig.editUrl; + + element.on('$destroy', function () { + panelConfig.savedObj.destroy(); + $scope.parentUiState.removeChild(getPersistedStateId(panelConfig.panel)); + $scope.$destroy(); + }); + + // create child ui state from the savedObj + const uiState = panelConfig.uiState || {}; + $scope.uiState = $scope.parentUiState.createChild(getPersistedStateId(panelConfig.panel), uiState, true); + const panelSavedVis = _.get(panelConfig, 'savedObj.vis'); // Sometimes this will be a search, and undef + if (panelSavedVis) { + panelSavedVis.setUiState($scope.uiState); + } + + $scope.filter = function (field, value, operator) { + const index = $scope.savedObj.searchSource.get('index').id; + filterManager.add(field, value, operator, index); + }; + }) + .catch(function (e) { + $scope.error = e.message; + + // If the savedObjectType matches the panel type, this means the object itself has been deleted, + // so we shouldn't even have an edit link. If they don't match, it means something else is wrong + // with the object (but the object still exists), so we link to the object editor instead. + const objectItselfDeleted = e.savedObjectType === $scope.panel.type; + if (objectItselfDeleted) return; + + const type = $scope.panel.type; + const id = $scope.panel.id; + const service = _.find(services, { type: type }); + if (!service) return; + + $scope.editUrl = '#management/kibana/objects/' + service.name + '/' + id + '?notFound=' + e.savedObjectType; + }); + } + }; +}); diff --git a/src/core_plugins/kibana/public/dashboard/directives/grid.js b/src/core_plugins/kibana/public/dashboard/directives/grid.js index 2204ff6d2779e..2dc6822ef0751 100644 --- a/src/core_plugins/kibana/public/dashboard/directives/grid.js +++ b/src/core_plugins/kibana/public/dashboard/directives/grid.js @@ -3,6 +3,7 @@ import $ from 'jquery'; import Binder from 'ui/binder'; import 'gridster'; import uiModules from 'ui/modules'; +import { PanelUtils } from 'plugins/kibana/dashboard/components/panel/lib/panel_utils'; const app = uiModules.get('app/dashboard'); @@ -33,6 +34,24 @@ app.directive('dashboardGrid', function ($compile, Notifier) { // debounced layout function is safe to call as much as possible const safeLayout = _.debounce(layout, 200); + $scope.removePanelFromState = (panelId) => { + _.remove($scope.state.panels, function (panel) { + return panel.panelId === panelId; + }); + }; + + /** + * Removes the panel with the given id from the $scope.state.panels array. Does not + * remove the ui element from gridster - that is triggered by a watcher that is + * triggered on changes made to $scope.state.panels. + * @param panelId {number} + */ + $scope.getPanelByPanelId = (panelId) => { + return _.find($scope.state.panels, function (panel) { + return panel.panelId === panelId; + }); + }; + function init() { $el.addClass('gridster'); @@ -90,7 +109,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { }; // ensure that every panel can be serialized now that we are done - $state.panels.forEach(makePanelSerializeable); + $state.panels.forEach(PanelUtils.makeSerializeable); // alert interested parties that we have finished processing changes to the panels // TODO: change this from event based to calling a method on dashboardApp @@ -108,7 +127,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { panel.$el.stop(); removePanel(panel, true); // not that we will, but lets be safe - makePanelSerializeable(panel); + PanelUtils.makeSerializeable(panel); }); }); @@ -121,81 +140,44 @@ app.directive('dashboardGrid', function ($compile, Notifier) { // return the panel object for an element. // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // ALWAYS CALL makePanelSerializeable AFTER YOU ARE DONE WITH IT + // ALWAYS CALL PanelUtils.makeSerializeable AFTER YOU ARE DONE WITH IT // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function getPanelFor(el) { const $panel = el.jquery ? el : $(el); const panel = $panel.data('panel'); - panel.$el = $panel; - panel.$scope = $panel.data('$scope'); - return panel; } - // since the $el and $scope are circular structures, they need to be - // removed from panel before it can be serialized (we also wouldn't - // want them to show up in the url) - function makePanelSerializeable(panel) { - delete panel.$el; - delete panel.$scope; - } - // tell gridster to remove the panel, and cleanup our metadata function removePanel(panel, silent) { // remove from grister 'silently' (don't reorganize after) gridster.remove_widget(panel.$el, silent); - - // destroy the scope - panel.$scope.$destroy(); - panel.$el.removeData('panel'); - panel.$el.removeData('$scope'); } // tell gridster to add the panel, and create additional meatadata like $scope function addPanel(panel) { - _.defaults(panel, { - size_x: 3, - size_y: 2 - }); - - // ignore panels that don't have vis id's - if (!panel.id) { - // In the interest of backwards compat - if (panel.visId) { - panel.id = panel.visId; - panel.type = 'visualization'; - delete panel.visId; - } else { - throw new Error('missing object id on panel'); - } - } + PanelUtils.initializeDefaults(panel); - panel.$scope = $scope.$new(); - panel.$scope.panel = panel; - panel.$scope.parentUiState = $scope.uiState; - - panel.$el = $compile('
  • ')(panel.$scope); + const panelHtml = ` +
  • + +
  • `; + panel.$el = $compile(panelHtml)($scope); // tell gridster to use the widget gridster.add_widget(panel.$el, panel.size_x, panel.size_y, panel.col, panel.row); - // update size/col/etc. - refreshPanelStats(panel); + // Gridster may change the position of the widget when adding it, make sure the panel + // contains the latest info. + PanelUtils.refreshSizeAndPosition(panel); - // stash the panel and it's scope in the element's data + // stash the panel in the element's data panel.$el.data('panel', panel); - panel.$el.data('$scope', panel.$scope); - } - - // ensure that the panel object has the latest size/pos info - function refreshPanelStats(panel) { - const data = panel.$el.coords().grid; - panel.size_x = data.size_x; - panel.size_y = data.size_y; - panel.col = data.col; - panel.row = data.row; } // when gridster tell us it made a change, update each of the panel objects @@ -203,9 +185,8 @@ app.directive('dashboardGrid', function ($compile, Notifier) { // ensure that our panel objects keep their size in sync gridster.$widgets.each(function (i, el) { const panel = getPanelFor(el); - refreshPanelStats(panel); - panel.$scope.$broadcast('resize'); - makePanelSerializeable(panel); + PanelUtils.refreshSizeAndPosition(panel); + PanelUtils.makeSerializeable(panel); $scope.$root.$broadcast('change:vis'); }); } diff --git a/src/core_plugins/kibana/public/dashboard/index.js b/src/core_plugins/kibana/public/dashboard/index.js index 05bdabc73ba43..607633f747aff 100644 --- a/src/core_plugins/kibana/public/dashboard/index.js +++ b/src/core_plugins/kibana/public/dashboard/index.js @@ -7,7 +7,7 @@ import 'ui/notify'; import 'ui/typeahead'; import 'ui/share'; import 'plugins/kibana/dashboard/directives/grid'; -import 'plugins/kibana/dashboard/components/panel/panel'; +import 'plugins/kibana/dashboard/directives/dashboard_panel'; import 'plugins/kibana/dashboard/services/saved_dashboards'; import 'plugins/kibana/dashboard/styles/main.less'; import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter'; @@ -17,6 +17,7 @@ import uiRoutes from 'ui/routes'; import uiModules from 'ui/modules'; import indexTemplate from 'plugins/kibana/dashboard/index.html'; import { savedDashboardRegister } from 'plugins/kibana/dashboard/services/saved_dashboard_register'; +import { createPanelState } from 'plugins/kibana/dashboard/components/panel/lib/panel_state'; require('ui/saved_objects/saved_object_registry').register(savedDashboardRegister); const app = uiModules.get('app/dashboard', [ @@ -152,7 +153,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, docTitle.change(dash.title); } - initPanelIndices(); + initPanelIds(); // watch for state changes and update the appStatus.dirty value stateMonitor = stateMonitorFactory.create($state, stateDefaults); @@ -171,24 +172,23 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, $scope.$emit('application.load'); } - function initPanelIndices() { - // find the largest panelIndex in all the panels - let maxIndex = getMaxPanelIndex(); + function initPanelIds() { + // find the largest panelId in all the panels + let maxIndex = getMaxPanelId(); - // ensure that all panels have a panelIndex + // ensure that all panels have a panelId $scope.state.panels.forEach(function (panel) { - if (!panel.panelIndex) { - panel.panelIndex = maxIndex++; + if (!panel.panelId) { + panel.panelId = maxIndex++; } }); } - function getMaxPanelIndex() { - let index = $scope.state.panels.reduce(function (idx, panel) { - // if panel is missing an index, add one and increment the index - return Math.max(idx, panel.panelIndex || idx); + function getMaxPanelId() { + let maxId = $scope.state.panels.reduce(function (id, panel) { + return Math.max(id, panel.panelId || id); }, 0); - return ++index; + return ++maxId; } function updateQueryOnRootSource() { @@ -272,12 +272,12 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, // called by the saved-object-finder when a user clicks a vis $scope.addVis = function (hit) { pendingVis++; - $state.panels.push({ id: hit.id, type: 'visualization', panelIndex: getMaxPanelIndex() }); + $state.panels.push(createPanelState(hit.id, 'visualization', getMaxPanelId())); }; $scope.addSearch = function (hit) { pendingVis++; - $state.panels.push({ id: hit.id, type: 'search', panelIndex: getMaxPanelIndex() }); + $state.panels.push(createPanelState(hit.id, 'search', getMaxPanelId())); }; // Setup configurable values for config directive, after objects are initialized From cebf8343216eb17a11d9d221f13769f9a50c7ffc Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Wed, 7 Dec 2016 13:20:02 -0800 Subject: [PATCH 09/17] Fix markdown typo in CSS style guide. (#9408) --- style_guides/css_style_guide.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/style_guides/css_style_guide.md b/style_guides/css_style_guide.md index f617f5a62ee7f..61822d3217e3f 100644 --- a/style_guides/css_style_guide.md +++ b/style_guides/css_style_guide.md @@ -43,10 +43,10 @@ This kind of code makes the selector name really difficult to grep for: .chart { // styles - &-content { + &Content { // styles - &-title { + &Title { // styles } } @@ -61,11 +61,11 @@ This is better: // styles } -.chart-content { +.chartContent { // styles } -.chart-content-title { +.chartContentTitle { // styles } ``` @@ -95,6 +95,7 @@ This is better: .specialMenu__item { // styles } +``` ## Naming convention @@ -234,19 +235,19 @@ pretty hairy. Consider a table component: // ======================== Bad! ======================== // These styles are complex and the multiple double-underscores increases noise // without providing much useful information. -.kbTable { +.kuiTable { /* ... */ } - .kbTable__body { + .kuiTable__body { /* ... */ } - .kbTable__body__row { + .kuiTable__body__row { /* ... */ } - .kbTable__body__row__cell { + .kuiTable__body__row__cell { /* ... */ } ``` @@ -257,25 +258,25 @@ indicates their relationship, by incorporating the name of the root base class. ```less // kbTable.less -.kbTable { +.kuiTable { /* ... */ } ``` ```less // kbTableBody.less -.kbTableBody { +.kuiTableBody { /* ... */ } ``` ```less // kbTableRow.less -.kbTableRow { +.kuiTableRow { /* ... */ } - .kbTableRow__cell { + .kuiTableRow__cell { /* ... */ } ``` From 2860b120799ca22686ad702924c62611cb5e9319 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 8 Dec 2016 13:55:57 -0800 Subject: [PATCH 10/17] Add form and page components. (#8910) - Third party overrides. - kuiList - kuiListItem - kuiPage - kuiPageView - kuiSubHeader - kuiNotice - kuiNoticeTitle - kuiNoticeText - kuiCard - kuiCardGroup - kuiButton - kuiIcon - kuiTitle - kuiBadge - kuiFormSection - kuiFormSubSection - kuiColumn - kuiFormLabel - kuiFormSubLabel - kuiTextArea - kuiInput - kuiStaticInput - kuiOptionLabel - kuiCheckbox - kuiFormPanel - kuiFormPanelLayout - kuiMenuItem - kuiInputNote - kuiTableControls - kuiTableControlAction - fullWidth - noPadding --- .../kibana/public/management/styles/main.less | 4 + src/ui/public/styles/base.less | 448 +++++++++++++++++- 2 files changed, 451 insertions(+), 1 deletion(-) diff --git a/src/core_plugins/kibana/public/management/styles/main.less b/src/core_plugins/kibana/public/management/styles/main.less index c122e935239d7..191d2831c9b4a 100644 --- a/src/core_plugins/kibana/public/management/styles/main.less +++ b/src/core_plugins/kibana/public/management/styles/main.less @@ -19,6 +19,10 @@ kbn-management-objects-view { min-height: 70px; /* 1 */ } +.tab-account { + background-color: @kibanaGray6; +} + .tab-management { background-color: @kibanaGray6; } diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less index 9c3ac595de46b..942f51838b037 100644 --- a/src/ui/public/styles/base.less +++ b/src/ui/public/styles/base.less @@ -611,6 +611,8 @@ fieldset { } } +// TODO: Extract these styles into the UI Framework. + .page-row { padding: 0 10px; margin: 10px 0; @@ -621,12 +623,406 @@ fieldset { font-size: 14px; } +.kuiPage { + padding: 20px 30px 28px; + margin: 20px; + background-color: white; + max-width: 1200px; +} + +/** + * 1. Center content. + * 2. Expand to fill container. + */ +.kuiPageView { + display: flex; + flex-direction: column; + align-items: center; // 1 + justify-content: center; // 1 + flex-grow: 1; // 2 + background-color: #f6f6f6; +} + + .kuiPageView__content { + max-width: 1100px; + } + +.kuiSubHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 18px; +} + +.kuiNotice { + padding: 40px 60px 48px; + margin: 20px; + background-color: white; +} + +.kuiNotice__header { + margin-bottom: 18px; +} + +/** + * 1. Override h1 styles. + */ +.kuiNoticeTitle { + font-size: 22px; + margin-bottom: 12px; + margin-top: 0; // 1 +} + +.kuiNoticeText { + font-size: 14px; + margin-bottom: 12px; +} + +.kuiCard { + display: flex; + flex-direction: column; + border: 1px solid #E0E0E0; + border-radius: 4px; + overflow: hidden; +} + + .kuiCard__description { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 18px 0; + } + + .kuiCard__descriptionTitle { + font-size: 14px; + font-weight: bold; + margin-bottom: 10px; + text-align: center; + max-width: ~"calc(100% - 48px)"; + } + + .kuiCard__descriptionText { + font-size: 14px; + max-width: ~"calc(100% - 48px)"; + } + + .kuiCard__footer { + text-align: center; + font-size: 14px; + padding: 0 15px 20px; + } + +.kuiCard--fixedWidth { + max-width: 120px; +} + +.kuiCardGroup { + display: flex; + border: 1px solid #E0E0E0; + border-radius: 4px; + overflow: hidden; + margin-bottom: 18px; +} + + .kuiCardGroup__card { + flex: 1 1 0%; + border: none; + border-radius: 0; + + & + & { + border-left: 1px solid #E0E0E0; + } + } + + .kuiCardGroup__cardDescription { + flex: 1 1 auto; + } + +/** + * 1. Disable for Angular. + */ +.kuiButton { + display: inline-block; + padding: 4px 24px; + color: #00a9e5; + border: 1px solid; + text-decoration: none; + border-radius: 3px; + background-color: transparent; + + &[disabled] { + pointer-events: none; // 1 + opacity: 0.4; + } +} + +.kuiButton--default, +.kuiButton--default:focus { + color: #2D2D2D; + border-color: #D4D4D4; + + &:hover, + &:active { + color: #111111; + background-color: #FAFAFA; + } +} + +.kuiButton--primary, +.kuiButton--primary:focus { + color: #00a9e5; + border-color: #00a9e5; + + &:hover, + &:active { + color: #00a9e5; + background-color: #ECF9FF; + } +} + +.kuiButton--danger, +.kuiButton--danger:focus { + color: #d76051; + border-color: #d76051; + + &:hover, + &:active { + color: #d76051; + background-color: #ffe4e1; + } +} + +.kuiIcon--success { + color: #80c383; +} + +.kuiIcon--danger { + color: #d76051; +} + +/** + * 1. Override h1. + */ +.kuiTitle { + margin: 0; // 1 + font-size: 22px; +} + +.kuiBadge { + display: inline-block; + margin-left: 0.5em; + padding: 0.1em 0.7em; + vertical-align: middle; + font-size: 11px; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.kuiBadge--default { + background-color: rgba(0, 0, 0, 0.1); +} + +.kuiFormSection { + margin-bottom: 16px; +} + +.kuiFormSubSection { + margin-bottom: 8px; +} + +.kuiFormLabel { + display: block; + margin-bottom: 5px; + font-weight: 700; +} + +.kuiFormSubLabel { + display: block; + font-weight: normal; +} + + .kuiFormSubLabel__note { + opacity: 0.5; + margin-left: 4px; + font-size: 12px; + } + +.kuiTextArea, +.kuiInput, +.kuiStaticInput { + display: block; + width: 100%; + font-size: 14px; + color: #2d2d2d; + border: 1px solid; + border-radius: 4px; +} + +.kuiStaticInput { + padding: 5px 0; + border-color: transparent; +} + +.kuiInput, +.kuiTextArea { + padding: 5px 15px; + border-color: #D4D4D4; +} + +.kuiTextArea--nonResizable { + resize: none; +} + +.kuiInputNote { + margin: 5px 0 10px; +} + +.kuiInputNote--danger { + color: #E74C3C; +} + +.kuiInputNote--warning { + color: #F39C12; +} + +/** + * 1. Override Bootstrap ui-select component styles. If side padding is > 5px, the component breaks. + */ +.ui-select-multiple.ui-select-bootstrap { + padding: 3px 5px 2px !important; // 1 + border: 1px solid #D4D4D4; // 1 + background-color: #ffffff !important; // 1 + + &.kuiInputError { + border-color: #E74C3C; // 1 + } +} + +/** + * 1. Override label styles. + */ +.kuiOptionLabel { + font-weight: normal; // 1 + cursor: pointer; +} + +/** + * 1. Override checkbox styles. + */ +.kuiCheckbox { + cursor: default; +} + +.kuiFormPanel { + border: 1px solid #D4D4D4; + padding: 12px; +} + +/** + * 1. Stack title and content children. Necessary for scrollable content; + */ +.kiuFormPanel--scrollable { + display: flex; // 1 + flex-direction: column; // 1 +} + +.kuiFormPanel--verticalLayout { + & + & { + border-top: 0; + } +} + +.kuiFormPanel--horizontalLayout { + flex: 1 1 auto; + + & + & { + border-left: 0; + } +} + + .kuiFormPanel__title { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + font-size: 14px; + line-height: 12px; + font-weight: 300; + color: #2d2d2d; + border-bottom: 1px solid #efefef; + } + + .kuiFormPanel__label { + font-weight: 700; + } + + .kuiFormPanel__content { + overflow-y: auto; + } + +.kuiFormPanelLayout { + display: flex; + align-items: stretch; + height: 100%; +} + +.kuiList { + margin-bottom: 10px; +} + +.kuiListItem { + padding: 12px 0; + + & + & { + border-top: 2px dashed rgba(212, 212, 212, 0.5); + } + + &:first-child { + padding-top: 0; + } + + &:last-child { + padding-bottom: 0; + } +} + +.kuiMenuItem { + padding: 10px; + font-size: 14px; + font-weight: 400; + color: #2d2d2d; + border-bottom: 1px solid #efefef; + + &.kuiMenuItem-isSelected { + background-color: #e4e4e4; + + &:hover { + background-color: #e4e4e4; + cursor: default; + } + } + + &:hover { + background-color: @list-group-menu-item-active-bg; + cursor: pointer; + } +} + /** * Utility class. * TODO: Move to UI Framework. */ .fullWidth { - width: 100%; + width: 100% !important; +} + +/** + * Utility class. + * TODO: Move to UI Framework. + */ +.noPadding { + padding: 0 !important; } /** @@ -641,4 +1037,54 @@ fieldset { flex: 1 1 auto; } +.kuiTableControls { + display: flex; + justify-content: space-between; + align-items: center; +} + + .kuiTableControls__input { + display: flex; + align-items: center; + flex: 1 1 auto; + } + + .kuiTableControls__actions { + align-items: center; + display: flex; + } + + .kuiTableControlAction { + margin-right: 5px; + } + + +.kuiColumn + .kuiColumn { + padding-left: 10px; +} + +/** + * 1. Use inline-block instead of flexbox so that content doesn't overflow. + * 2. Content can be aligned by offsetting from the top. + */ +// $numColumns: 12; +// @for $i from 1 through $numColumns { +// .kiuColumn--#{$i} { +// display: inline-block; /* 1 */ +// vertical-align: top; /* 2 */ +// width: $i / $numColumns * 100%; +// } +// } + +.makeKuiColumns(12); + +.makeKuiColumns(@n, @i: 1) when (@i =< @n) { + .kuiColumn--@{i} { + display: inline-block; /* 1 */ + vertical-align: top; /* 2 */ + width: (@i * 100% / @n); + } + .makeKuiColumns(@n, (@i + 1)); +} + @import "~dragula/dist/dragula.css"; From 6e819c11b87c7c6a96759ba018026e7bb159c1a9 Mon Sep 17 00:00:00 2001 From: Jim Unger Date: Thu, 8 Dec 2016 16:05:18 -0600 Subject: [PATCH 11/17] Remove legacy pipelines code (#9397) * Removes legacy pipelines code from kibana core_plugin * removes pipelines tests --- .../directives/output_preview.js | 50 -- .../directives/pipeline_output.js | 20 - .../directives/pipeline_setup.js | 94 ---- .../directives/processor_ui_container.js | 34 -- .../processor_ui_container_header.js | 17 - .../pipeline_setup/directives/source_data.js | 45 -- .../add_data_steps/pipeline_setup/index.js | 1 - .../__tests__/create_multi_select_model.js | 74 --- .../pipeline_setup/lib/__tests__/keys_deep.js | 86 ---- .../pipeline_setup/lib/__tests__/pipeline.js | 480 ------------------ .../lib/create_multi_select_model.js | 21 - .../pipeline_setup/lib/keys_deep.js | 21 - .../pipeline_setup/lib/pipeline.js | 176 ------- .../processors/append/directive.js | 38 -- .../processors/append/view.html | 8 - .../processors/append/view_model.js | 23 - .../processors/base/view_model.js | 23 - .../processors/convert/directive.js | 43 -- .../processors/convert/view.html | 24 - .../processors/convert/view_model.js | 28 - .../processors/date/directive.js | 58 --- .../processors/date/styles.less | 5 - .../pipeline_setup/processors/date/view.html | 74 --- .../processors/date/view_model.js | 32 -- .../processors/geoip/directive.js | 61 --- .../processors/geoip/styles.less | 13 - .../pipeline_setup/processors/geoip/view.html | 42 -- .../processors/geoip/view_model.js | 28 - .../processors/grok/directive.js | 40 -- .../pipeline_setup/processors/grok/view.html | 16 - .../processors/grok/view_model.js | 30 -- .../processors/gsub/directive.js | 41 -- .../pipeline_setup/processors/gsub/view.html | 20 - .../processors/gsub/view_model.js | 25 - .../pipeline_setup/processors/index.js | 14 - .../processors/join/directive.js | 41 -- .../pipeline_setup/processors/join/view.html | 16 - .../processors/join/view_model.js | 24 - .../processors/lowercase/directive.js | 39 -- .../processors/lowercase/view.html | 12 - .../processors/lowercase/view_model.js | 21 - .../processors/remove/directive.js | 38 -- .../processors/remove/view.html | 12 - .../processors/remove/view_model.js | 21 - .../processors/rename/directive.js | 40 -- .../processors/rename/view.html | 16 - .../processors/rename/view_model.js | 24 - .../processors/set/directive.js | 23 - .../pipeline_setup/processors/set/view.html | 8 - .../processors/set/view_model.js | 23 - .../processors/split/directive.js | 41 -- .../pipeline_setup/processors/split/view.html | 16 - .../processors/split/view_model.js | 24 - .../processors/trim/directive.js | 39 -- .../pipeline_setup/processors/trim/view.html | 12 - .../processors/trim/view_model.js | 21 - .../processors/uppercase/directive.js | 39 -- .../processors/uppercase/view.html | 12 - .../processors/uppercase/view_model.js | 21 - .../pipeline_setup/processors/view_models.js | 14 - .../styles/_output_preview.less | 28 - .../styles/_pipeline_output.less | 22 - .../styles/_pipeline_setup.less | 74 --- .../styles/_processor_ui_container.less | 42 -- .../_processor_ui_container_header.less | 47 -- .../pipeline_setup/styles/_source_data.less | 17 - .../pipeline_setup/views/output_preview.html | 24 - .../pipeline_setup/views/pipeline_output.html | 18 - .../pipeline_setup/views/pipeline_setup.html | 74 --- .../views/processor_ui_container.html | 25 - .../views/processor_ui_container_header.html | 61 --- .../pipeline_setup/views/source_data.html | 20 - ...est_simulate_api_kibana_to_es_converter.js | 87 ---- .../process_es_ingest_processors_response.js | 90 ---- .../process_es_ingest_simulate_error.js | 19 - .../process_es_ingest_simulate_response.js | 73 --- ...est_pipeline_api_kibana_to_es_converter.js | 10 - ...est_simulate_api_kibana_to_es_converter.js | 13 - .../process_es_ingest_processors_response.js | 16 - .../lib/process_es_ingest_simulate_error.js | 23 - .../process_es_ingest_simulate_response.js | 24 - .../append/kibana_to_es_converter.js | 9 - .../server/lib/processors/append/schema.js | 8 - .../server/lib/processors/base/schema.js | 5 - .../convert/kibana_to_es_converter.js | 24 - .../server/lib/processors/convert/schema.js | 9 - .../server/lib/processors/converters.js | 14 - .../processors/date/kibana_to_es_converter.js | 23 - .../server/lib/processors/date/schema.js | 12 - .../geoip/kibana_to_es_converter.js | 21 - .../server/lib/processors/geoip/schema.js | 10 - .../processors/grok/kibana_to_es_converter.js | 9 - .../server/lib/processors/grok/schema.js | 8 - .../processors/gsub/kibana_to_es_converter.js | 10 - .../server/lib/processors/gsub/schema.js | 9 - .../processors/join/kibana_to_es_converter.js | 9 - .../server/lib/processors/join/schema.js | 8 - .../lowercase/kibana_to_es_converter.js | 8 - .../server/lib/processors/lowercase/schema.js | 7 - .../remove/kibana_to_es_converter.js | 8 - .../server/lib/processors/remove/schema.js | 7 - .../rename/kibana_to_es_converter.js | 9 - .../server/lib/processors/rename/schema.js | 8 - .../kibana/server/lib/processors/schemas.js | 14 - .../processors/set/kibana_to_es_converter.js | 9 - .../server/lib/processors/set/schema.js | 8 - .../split/kibana_to_es_converter.js | 9 - .../server/lib/processors/split/schema.js | 8 - .../processors/trim/kibana_to_es_converter.js | 8 - .../server/lib/processors/trim/schema.js | 7 - .../uppercase/kibana_to_es_converter.js | 8 - .../server/lib/processors/uppercase/schema.js | 7 - .../lib/schemas/resources/pipeline_schema.js | 5 - .../lib/schemas/simulate_request_schema.js | 8 - .../kibana/server/routes/api/ingest/index.js | 4 - .../routes/api/ingest/register_processors.js | 24 - .../routes/api/ingest/register_simulate.js | 37 -- test/unit/api/ingest/_processors.js | 19 - test/unit/api/ingest/_simulate.js | 157 ------ test/unit/api/ingest/index.js | 6 - test/unit/api/ingest/processors/_append.js | 67 --- test/unit/api/ingest/processors/_convert.js | 70 --- test/unit/api/ingest/processors/_date.js | 89 ---- test/unit/api/ingest/processors/_geoip.js | 71 --- test/unit/api/ingest/processors/_grok.js | 72 --- test/unit/api/ingest/processors/_gsub.js | 70 --- test/unit/api/ingest/processors/_join.js | 67 --- test/unit/api/ingest/processors/_lowercase.js | 64 --- test/unit/api/ingest/processors/_remove.js | 64 --- test/unit/api/ingest/processors/_rename.js | 67 --- test/unit/api/ingest/processors/_set.js | 67 --- test/unit/api/ingest/processors/_split.js | 67 --- test/unit/api/ingest/processors/_trim.js | 64 --- test/unit/api/ingest/processors/_uppercase.js | 64 --- test/unit/api/ingest/processors/index.js | 34 -- 135 files changed, 4779 deletions(-) delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_output.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container_header.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/index.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/create_multi_select_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/keys_deep.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/create_multi_select_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/keys_deep.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/base/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/styles.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/styles.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/index.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/directive.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view_model.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/view_models.js delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_output_preview.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_output.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_setup.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container_header.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_source_data.less delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/output_preview.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_output.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html delete mode 100644 src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/source_data.html delete mode 100644 src/core_plugins/kibana/server/lib/__tests__/converters/ingest_simulate_api_kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_processors_response.js delete mode 100644 src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js delete mode 100644 src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js delete mode 100644 src/core_plugins/kibana/server/lib/converters/ingest_pipeline_api_kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/converters/ingest_simulate_api_kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/process_es_ingest_processors_response.js delete mode 100644 src/core_plugins/kibana/server/lib/process_es_ingest_simulate_error.js delete mode 100644 src/core_plugins/kibana/server/lib/process_es_ingest_simulate_response.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/append/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/append/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/base/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/convert/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/convert/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/converters.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/date/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/date/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/geoip/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/geoip/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/grok/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/grok/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/gsub/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/gsub/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/join/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/join/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/lowercase/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/lowercase/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/remove/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/remove/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/rename/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/rename/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/schemas.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/set/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/set/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/split/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/split/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/trim/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/trim/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/uppercase/kibana_to_es_converter.js delete mode 100644 src/core_plugins/kibana/server/lib/processors/uppercase/schema.js delete mode 100644 src/core_plugins/kibana/server/lib/schemas/resources/pipeline_schema.js delete mode 100644 src/core_plugins/kibana/server/lib/schemas/simulate_request_schema.js delete mode 100644 src/core_plugins/kibana/server/routes/api/ingest/register_processors.js delete mode 100644 src/core_plugins/kibana/server/routes/api/ingest/register_simulate.js delete mode 100644 test/unit/api/ingest/_processors.js delete mode 100644 test/unit/api/ingest/_simulate.js delete mode 100644 test/unit/api/ingest/processors/_append.js delete mode 100644 test/unit/api/ingest/processors/_convert.js delete mode 100644 test/unit/api/ingest/processors/_date.js delete mode 100644 test/unit/api/ingest/processors/_geoip.js delete mode 100644 test/unit/api/ingest/processors/_grok.js delete mode 100644 test/unit/api/ingest/processors/_gsub.js delete mode 100644 test/unit/api/ingest/processors/_join.js delete mode 100644 test/unit/api/ingest/processors/_lowercase.js delete mode 100644 test/unit/api/ingest/processors/_remove.js delete mode 100644 test/unit/api/ingest/processors/_rename.js delete mode 100644 test/unit/api/ingest/processors/_set.js delete mode 100644 test/unit/api/ingest/processors/_split.js delete mode 100644 test/unit/api/ingest/processors/_trim.js delete mode 100644 test/unit/api/ingest/processors/_uppercase.js delete mode 100644 test/unit/api/ingest/processors/index.js diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js deleted file mode 100644 index 8135fca9e1b4a..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/output_preview.js +++ /dev/null @@ -1,50 +0,0 @@ -import uiModules from 'ui/modules'; -import jsondiffpatch from '@bigfunger/jsondiffpatch'; -import '../styles/_output_preview.less'; -import outputPreviewTemplate from '../views/output_preview.html'; - -const htmlFormat = jsondiffpatch.formatters.html.format; -const app = uiModules.get('kibana'); - -app.directive('outputPreview', function () { - return { - restrict: 'E', - template: outputPreviewTemplate, - scope: { - oldObject: '=', - newObject: '=', - error: '=' - }, - link: function ($scope, $el) { - const div = $el.find('.visual')[0]; - - $scope.diffpatch = jsondiffpatch.create({ - arrays: { - detectMove: false - }, - textDiff: { - minLength: 120 - } - }); - - $scope.updateUi = function () { - let left = $scope.oldObject; - let right = $scope.newObject; - let delta = $scope.diffpatch.diff(left, right); - if (!delta || $scope.error) delta = {}; - - div.innerHTML = htmlFormat(delta, left); - }; - }, - controller: function ($scope, debounce) { - $scope.collapsed = false; - - const updateOutput = debounce(function () { - $scope.updateUi(); - }, 200); - - $scope.$watch('oldObject', updateOutput); - $scope.$watch('newObject', updateOutput); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_output.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_output.js deleted file mode 100644 index 71a27e67b963e..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_output.js +++ /dev/null @@ -1,20 +0,0 @@ -import uiModules from 'ui/modules'; -import '../styles/_pipeline_output.less'; -import pipelineOutputTemplate from '../views/pipeline_output.html'; - -const app = uiModules.get('kibana'); - -app.directive('pipelineOutput', function () { - return { - restrict: 'E', - template: pipelineOutputTemplate, - scope: { - pipeline: '=', - samples: '=', - sample: '=' - }, - controller: function ($scope) { - $scope.collapsed = true; - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js deleted file mode 100644 index 667849fa7aa50..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/pipeline_setup.js +++ /dev/null @@ -1,94 +0,0 @@ -import uiModules from 'ui/modules'; -import _ from 'lodash'; -import Pipeline from '../lib/pipeline'; -import angular from 'angular'; -import * as ProcessorTypes from '../processors/view_models'; -import IngestProvider from 'ui/ingest'; -import '../styles/_pipeline_setup.less'; -import './pipeline_output'; -import './source_data'; -import './processor_ui_container'; -import '../processors'; -import pipelineSetupTemplate from '../views/pipeline_setup.html'; - -const app = uiModules.get('kibana'); - -function buildProcessorTypeList(enabledProcessorTypeIds) { - return _(ProcessorTypes) - .map(Type => { - const instance = new Type(); - return { - typeId: instance.typeId, - title: instance.title, - Type - }; - }) - .compact() - .filter((processorType) => enabledProcessorTypeIds.includes(processorType.typeId)) - .sortBy('title') - .value(); -} - -app.directive('pipelineSetup', function () { - return { - restrict: 'E', - template: pipelineSetupTemplate, - scope: { - samples: '=', - pipeline: '=' - }, - controller: function ($scope, debounce, Private, Notifier) { - const ingest = Private(IngestProvider); - const notify = new Notifier({ location: `Ingest Pipeline Setup` }); - $scope.sample = {}; - - //determines which processors are available on the cluster - ingest.getProcessors() - .then((enabledProcessorTypeIds) => { - $scope.processorTypes = buildProcessorTypeList(enabledProcessorTypeIds); - }) - .catch(notify.error); - - const pipeline = new Pipeline(); - // Loads pre-existing pipeline which will exist if the user returns from - // a later step in the wizard - if ($scope.pipeline) { - pipeline.load($scope.pipeline); - $scope.sample = $scope.pipeline.input; - } - $scope.pipeline = pipeline; - - //initiates the simulate call if the pipeline is dirty - const simulatePipeline = debounce((event, message) => { - if (pipeline.processors.length === 0) { - pipeline.updateOutput(); - return; - } - - return ingest.simulate(pipeline.model) - .then((results) => { pipeline.applySimulateResults(results); }) - .catch(notify.error); - }, 200); - - $scope.$watchCollection('pipeline.processors', (newVal, oldVal) => { - pipeline.updateParents(); - }); - - $scope.$watch('sample', (newVal) => { - pipeline.input = $scope.sample; - pipeline.updateParents(); - }); - - $scope.$watch('processorType', (newVal) => { - if (!newVal) return; - - pipeline.add(newVal.Type); - $scope.processorType = ''; - }); - - $scope.$watch('pipeline.dirty', simulatePipeline); - - $scope.expandContext = 1; - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container.js deleted file mode 100644 index 4f53e822bf088..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container.js +++ /dev/null @@ -1,34 +0,0 @@ -import uiModules from 'ui/modules'; -import _ from 'lodash'; -import '../styles/_processor_ui_container.less'; -import './output_preview'; -import './processor_ui_container_header'; -import template from '../views/processor_ui_container.html'; - -const app = uiModules.get('kibana'); - -app.directive('processorUiContainer', function ($compile) { - return { - restrict: 'E', - scope: { - pipeline: '=', - processor: '=' - }, - template: template, - link: function ($scope, $el) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - const $container = $el.find('.processor-ui-content'); - const typeId = processor.typeId; - - const newScope = $scope.$new(); - newScope.pipeline = pipeline; - newScope.processor = processor; - - const template = ``; - const $innerEl = $compile(template)(newScope); - - $innerEl.appendTo($container); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container_header.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container_header.js deleted file mode 100644 index a1e2b26e917cf..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/processor_ui_container_header.js +++ /dev/null @@ -1,17 +0,0 @@ -import uiModules from 'ui/modules'; -import '../styles/_processor_ui_container_header.less'; -import processorUiContainerHeaderTemplate from '../views/processor_ui_container_header.html'; - -const app = uiModules.get('kibana'); - -app.directive('processorUiContainerHeader', function () { - return { - restrict: 'E', - scope: { - processor: '=', - field: '=', - pipeline: '=' - }, - template: processorUiContainerHeaderTemplate - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js deleted file mode 100644 index bbf5f604eb7a7..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/directives/source_data.js +++ /dev/null @@ -1,45 +0,0 @@ -import uiModules from 'ui/modules'; -import angular from 'angular'; -import '../styles/_source_data.less'; -import sourceDataTemplate from '../views/source_data.html'; - -const app = uiModules.get('kibana'); - -app.directive('sourceData', function () { - return { - restrict: 'E', - scope: { - samples: '=', - sample: '=', - disabled: '=' - }, - template: sourceDataTemplate, - controller: function ($scope) { - const samples = $scope.samples; - - if (samples.length > 0) { - $scope.selectedSample = samples[0]; - } - - $scope.$watch('selectedSample', (newValue) => { - //the added complexity of this directive is to strip out the properties - //that angular adds to array objects that are bound via ng-options - $scope.sample = angular.copy(newValue); - }); - - $scope.previousLine = function () { - let currentIndex = samples.indexOf($scope.selectedSample); - if (currentIndex <= 0) currentIndex = samples.length; - - $scope.selectedSample = samples[currentIndex - 1]; - }; - - $scope.nextLine = function () { - let currentIndex = samples.indexOf($scope.selectedSample); - if (currentIndex >= samples.length - 1) currentIndex = -1; - - $scope.selectedSample = samples[currentIndex + 1]; - }; - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/index.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/index.js deleted file mode 100644 index 63e0035c1ca72..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/index.js +++ /dev/null @@ -1 +0,0 @@ -import './directives/pipeline_setup'; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/create_multi_select_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/create_multi_select_model.js deleted file mode 100644 index 75ee3ed792a5c..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/create_multi_select_model.js +++ /dev/null @@ -1,74 +0,0 @@ -import expect from 'expect.js'; -import sinon from 'sinon'; -import createMultiSelectModel from '../create_multi_select_model'; - -describe('createMultiSelectModel', function () { - - it('should throw an error if the first argument is not an array', () => { - expect(createMultiSelectModel).withArgs('foo', []).to.throwError(); - expect(createMultiSelectModel).withArgs(1234, []).to.throwError(); - expect(createMultiSelectModel).withArgs(undefined, []).to.throwError(); - expect(createMultiSelectModel).withArgs(null, []).to.throwError(); - expect(createMultiSelectModel).withArgs([], []).to.not.throwError(); - }); - - it('should throw an error if the second argument is not an array', () => { - expect(createMultiSelectModel).withArgs([], 'foo').to.throwError(); - expect(createMultiSelectModel).withArgs([], 1234).to.throwError(); - expect(createMultiSelectModel).withArgs([], undefined).to.throwError(); - expect(createMultiSelectModel).withArgs([], null).to.throwError(); - expect(createMultiSelectModel).withArgs([], []).to.not.throwError(); - }); - - it('should output an array with an item for each passed in', () => { - const items = [ 'foo', 'bar', 'baz' ]; - const expected = [ - { title: 'foo', selected: false }, - { title: 'bar', selected: false }, - { title: 'baz', selected: false } - ]; - const actual = createMultiSelectModel(items, []); - - expect(actual).to.eql(expected); - }); - - it('should set the selected property in the output', () => { - const items = [ 'foo', 'bar', 'baz' ]; - const selectedItems = [ 'bar', 'baz' ]; - const expected = [ - { title: 'foo', selected: false }, - { title: 'bar', selected: true }, - { title: 'baz', selected: true } - ]; - const actual = createMultiSelectModel(items, selectedItems); - - expect(actual).to.eql(expected); - }); - - it('should trim values when comparing for selected', () => { - const items = [ 'foo', 'bar', 'baz' ]; - const selectedItems = [ ' bar ', ' baz ' ]; - const expected = [ - { title: 'foo', selected: false }, - { title: 'bar', selected: true }, - { title: 'baz', selected: true } - ]; - const actual = createMultiSelectModel(items, selectedItems); - - expect(actual).to.eql(expected); - }); - - it('should be case insensitive when comparing for selected', () => { - const items = [ 'foo', 'bar', 'baz' ]; - const selectedItems = [ ' Bar ', ' BAZ ' ]; - const expected = [ - { title: 'foo', selected: false }, - { title: 'bar', selected: true }, - { title: 'baz', selected: true } - ]; - const actual = createMultiSelectModel(items, selectedItems); - - expect(actual).to.eql(expected); - }); - -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/keys_deep.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/keys_deep.js deleted file mode 100644 index 05f06927a4351..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/keys_deep.js +++ /dev/null @@ -1,86 +0,0 @@ -import expect from 'expect.js'; -import sinon from 'sinon'; -import keysDeep from '../keys_deep'; - -describe('keys deep', function () { - - it('should list first level properties', function () { - let object = { - property1: 'value1', - property2: 'value2' - }; - let expected = [ - 'property1', - 'property2' - ]; - - const keys = keysDeep(object); - - expect(keys).to.eql(expected); - }); - - it('should list nested properties', function () { - let object = { - property1: 'value1', - property2: 'value2', - property3: { - subProperty1: 'value1.1' - } - }; - let expected = [ - 'property1', - 'property2', - 'property3.subProperty1', - 'property3' - ]; - - const keys = keysDeep(object); - - expect(keys).to.eql(expected); - }); - - it('should recursivly list nested properties', function () { - let object = { - property1: 'value1', - property2: 'value2', - property3: { - subProperty1: 'value1.1', - subProperty2: { - prop1: 'value1.2.1', - prop2: 'value2.2.2' - }, - subProperty3: 'value1.3' - } - }; - let expected = [ - 'property1', - 'property2', - 'property3.subProperty1', - 'property3.subProperty2.prop1', - 'property3.subProperty2.prop2', - 'property3.subProperty2', - 'property3.subProperty3', - 'property3' - ]; - - const keys = keysDeep(object); - - expect(keys).to.eql(expected); - }); - - it('should list array properties, but not contents', function () { - let object = { - property1: 'value1', - property2: [ 'item1', 'item2' ] - }; - let expected = [ - 'property1', - 'property2' - ]; - - const keys = keysDeep(object); - - expect(keys).to.eql(expected); - }); - -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js deleted file mode 100644 index b3901529c6133..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/__tests__/pipeline.js +++ /dev/null @@ -1,480 +0,0 @@ -import _ from 'lodash'; -import expect from 'expect.js'; -import sinon from 'sinon'; -import Pipeline from '../pipeline'; -import * as processorTypes from '../../processors/view_models'; - -describe('processor pipeline', function () { - - function getProcessorIds(pipeline) { - return pipeline.processors.map(p => p.processorId); - } - - describe('model', function () { - - it('should only contain the clean data properties', function () { - const pipeline = new Pipeline(); - const actual = pipeline.model; - const expectedKeys = [ 'input', 'processors' ]; - - expect(_.keys(actual)).to.eql(expectedKeys); - }); - - it('should access the model property of each processor', function () { - const pipeline = new Pipeline(); - pipeline.input = { foo: 'bar' }; - pipeline.add(processorTypes.Set); - - const actual = pipeline.model; - const expected = { - input: pipeline.input, - processors: [ pipeline.processors[0].model ] - }; - - expect(actual).to.eql(expected); - }); - - }); - - describe('load', function () { - - it('should remove existing processors from the pipeline', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const oldProcessors = [ pipeline.processors[0], pipeline.processors[1], pipeline.processors[2] ]; - - const newPipeline = new Pipeline(); - newPipeline.add(processorTypes.Set); - newPipeline.add(processorTypes.Set); - newPipeline.add(processorTypes.Set); - - pipeline.load(newPipeline); - - expect(_.find(pipeline.processors, oldProcessors[0])).to.be(undefined); - expect(_.find(pipeline.processors, oldProcessors[1])).to.be(undefined); - expect(_.find(pipeline.processors, oldProcessors[2])).to.be(undefined); - }); - - it('should call addExisting for each of the imported processors', function () { - const pipeline = new Pipeline(); - sinon.stub(pipeline, 'addExisting'); - - const newPipeline = new Pipeline(); - newPipeline.add(processorTypes.Set); - newPipeline.add(processorTypes.Set); - newPipeline.add(processorTypes.Set); - - pipeline.load(newPipeline); - - expect(pipeline.addExisting.calledWith(newPipeline.processors[0])).to.be(true); - expect(pipeline.addExisting.calledWith(newPipeline.processors[1])).to.be(true); - expect(pipeline.addExisting.calledWith(newPipeline.processors[2])).to.be(true); - }); - - }); - - describe('remove', function () { - - it('remove the specified processor from the processors collection', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - const processorIds = getProcessorIds(pipeline); - - pipeline.remove(pipeline.processors[1]); - - expect(pipeline.processors[0].processorId).to.be(processorIds[0]); - expect(pipeline.processors[1].processorId).to.be(processorIds[2]); - }); - - }); - - describe('add', function () { - - it('should append new items to the processors collection', function () { - const pipeline = new Pipeline(); - - expect(pipeline.processors.length).to.be(0); - - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - expect(pipeline.processors.length).to.be(3); - }); - - it('should append assign each new processor a unique processorId', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - const ids = pipeline.processors.map((p) => { return p.processorId; }); - expect(_.uniq(ids).length).to.be(3); - }); - - it('added processors should be an instance of the type supplied', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - expect(pipeline.processors[0] instanceof processorTypes.Set).to.be(true); - expect(pipeline.processors[1] instanceof processorTypes.Set).to.be(true); - expect(pipeline.processors[2] instanceof processorTypes.Set).to.be(true); - }); - - }); - - describe('addExisting', function () { - - it('should append new items to the processors collection', function () { - const pipeline = new Pipeline(); - - expect(pipeline.processors.length).to.be(0); - - const testProcessor = new processorTypes.Set('foo'); - - pipeline.addExisting(testProcessor); - - expect(pipeline.processors.length).to.be(1); - }); - - it('should instantiate an object of the same class as the object passed in', function () { - const pipeline = new Pipeline(); - - const testProcessor = new processorTypes.Set('foo'); - - pipeline.addExisting(testProcessor); - - expect(pipeline.processors[0] instanceof processorTypes.Set).to.be(true); - }); - - it('the object added should be a different instance than the object passed in', function () { - const pipeline = new Pipeline(); - - const testProcessor = new processorTypes.Set('foo'); - - pipeline.addExisting(testProcessor); - - expect(pipeline.processors[0]).to.not.be(testProcessor); - }); - - it('the object added should have the same property values as the object passed in (except id)', function () { - const pipeline = new Pipeline(); - - const testProcessor = new processorTypes.Set('foo'); - testProcessor.foo = 'bar'; - testProcessor.bar = 'baz'; - - pipeline.addExisting(testProcessor); - - expect(pipeline.processors[0].foo).to.be('bar'); - expect(pipeline.processors[0].bar).to.be('baz'); - expect(pipeline.processors[0].processorId).to.not.be('foo'); - }); - - }); - - describe('moveUp', function () { - - it('should be able to move an item up in the array', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[1]; - pipeline.moveUp(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[1]); - expect(pipeline.processors[1].processorId).to.be(processorIds[0]); - expect(pipeline.processors[2].processorId).to.be(processorIds[2]); - }); - - it('should be able to move the same item move than once', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[2]; - pipeline.moveUp(target); - pipeline.moveUp(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[2]); - expect(pipeline.processors[1].processorId).to.be(processorIds[0]); - expect(pipeline.processors[2].processorId).to.be(processorIds[1]); - }); - - it('should not move the selected item past the top', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[2]; - pipeline.moveUp(target); - pipeline.moveUp(target); - pipeline.moveUp(target); - pipeline.moveUp(target); - pipeline.moveUp(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[2]); - expect(pipeline.processors[1].processorId).to.be(processorIds[0]); - expect(pipeline.processors[2].processorId).to.be(processorIds[1]); - }); - - it('should not allow the top item to be moved up', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[0]; - pipeline.moveUp(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[0]); - expect(pipeline.processors[1].processorId).to.be(processorIds[1]); - expect(pipeline.processors[2].processorId).to.be(processorIds[2]); - }); - - }); - - describe('moveDown', function () { - - it('should be able to move an item down in the array', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[1]; - pipeline.moveDown(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[0]); - expect(pipeline.processors[1].processorId).to.be(processorIds[2]); - expect(pipeline.processors[2].processorId).to.be(processorIds[1]); - }); - - it('should be able to move the same item move than once', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[0]; - pipeline.moveDown(target); - pipeline.moveDown(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[1]); - expect(pipeline.processors[1].processorId).to.be(processorIds[2]); - expect(pipeline.processors[2].processorId).to.be(processorIds[0]); - }); - - it('should not move the selected item past the bottom', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[0]; - pipeline.moveDown(target); - pipeline.moveDown(target); - pipeline.moveDown(target); - pipeline.moveDown(target); - pipeline.moveDown(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[1]); - expect(pipeline.processors[1].processorId).to.be(processorIds[2]); - expect(pipeline.processors[2].processorId).to.be(processorIds[0]); - }); - - it('should not allow the bottom item to be moved down', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const target = pipeline.processors[2]; - pipeline.moveDown(target); - - expect(pipeline.processors[0].processorId).to.be(processorIds[0]); - expect(pipeline.processors[1].processorId).to.be(processorIds[1]); - expect(pipeline.processors[2].processorId).to.be(processorIds[2]); - }); - - }); - - describe('updateParents', function () { - - it('should set the first processors parent to pipeline.input', function () { - const pipeline = new Pipeline(); - pipeline.input = { foo: 'bar' }; - - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - pipeline.processors.forEach(p => sinon.stub(p, 'setParent')); - - pipeline.updateParents(); - - expect(pipeline.processors[0].setParent.calledWith(pipeline.input)).to.be(true); - }); - - it('should set non-first processors parent to previous processor', function () { - const pipeline = new Pipeline(); - pipeline.input = { foo: 'bar' }; - - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - pipeline.processors.forEach(p => sinon.stub(p, 'setParent')); - - pipeline.updateParents(); - - expect(pipeline.processors[1].setParent.calledWith(pipeline.processors[0])).to.be(true); - expect(pipeline.processors[2].setParent.calledWith(pipeline.processors[1])).to.be(true); - expect(pipeline.processors[3].setParent.calledWith(pipeline.processors[2])).to.be(true); - }); - - it('should set pipeline.dirty', function () { - const pipeline = new Pipeline(); - pipeline.updateParents(); - - expect(pipeline.dirty).to.be(true); - }); - - }); - - describe('getProcessorById', function () { - - it('should return a processor when suppied its id', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - const processorIds = getProcessorIds(pipeline); - - const actual = pipeline.getProcessorById(processorIds[2]); - const expected = pipeline.processors[2]; - - expect(actual).to.be(expected); - }); - - it('should throw an error if given an unknown id', function () { - const pipeline = new Pipeline(); - - expect(pipeline.getProcessorById).withArgs('foo').to.throwError(); - }); - - }); - - describe('updateOutput', function () { - - it('should set the output to input if first processor has error', function () { - const pipeline = new Pipeline(); - pipeline.input = { bar: 'baz' }; - pipeline.add(processorTypes.Set); - - pipeline.processors[0].outputObject = { field1: 'value1' }; - pipeline.processors[0].error = {}; //define an error - - pipeline.updateOutput(); - expect(pipeline.output).to.be(pipeline.input); - }); - - it('should set the output to the processor before the error on a compile error', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - pipeline.processors[0].outputObject = { field1: 'value1' }; - pipeline.processors[1].outputObject = { field1: 'value2' }; - pipeline.processors[2].outputObject = { field1: 'value3' }; - - pipeline.updateOutput(); - expect(pipeline.output).to.eql({ field1: 'value3' }); - - pipeline.processors[1].error = { compile: true }; //define a compile error - pipeline.processors[0].locked = true; //all other processors get locked. - pipeline.processors[2].locked = true; //all other processors get locked. - - pipeline.updateOutput(); - expect(pipeline.output).to.eql({ field1: 'value1' }); - }); - - it('should set the output to the last processor with valid output if a processor has an error', function () { - const pipeline = new Pipeline(); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - pipeline.add(processorTypes.Set); - - pipeline.processors[0].outputObject = { field1: 'value1' }; - pipeline.processors[1].outputObject = { field1: 'value2' }; - pipeline.processors[2].outputObject = { field1: 'value3' }; - - pipeline.updateOutput(); - expect(pipeline.output).to.eql({ field1: 'value3' }); - - pipeline.processors[2].error = {}; //define an error - pipeline.updateOutput(); - expect(pipeline.output).to.eql({ field1: 'value2' }); - - pipeline.processors[1].error = {}; //define an error - pipeline.processors[2].error = undefined; //if processor[1] has an error, - pipeline.processors[2].locked = true; //subsequent processors will be locked. - pipeline.updateOutput(); - expect(pipeline.output).to.eql({ field1: 'value1' }); - }); - - it('should set output to be last processor output if processors exist', function () { - const pipeline = new Pipeline(); - pipeline.input = { bar: 'baz' }; - pipeline.add(processorTypes.Set); - - const expected = { foo: 'bar' }; - pipeline.processors[0].outputObject = expected; - - pipeline.updateOutput(); - expect(pipeline.output).to.be(expected); - }); - - it('should set output to be equal to input if no processors exist', function () { - const pipeline = new Pipeline(); - pipeline.input = { bar: 'baz' }; - - pipeline.updateOutput(); - expect(pipeline.output).to.be(pipeline.input); - }); - - it('should set pipeline.dirty', function () { - const pipeline = new Pipeline(); - pipeline.updateParents(); - expect(pipeline.dirty).to.be(true); - - pipeline.updateOutput(); - expect(pipeline.dirty).to.be(false); - }); - - }); - - // describe('applySimulateResults', function () { }); - - -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/create_multi_select_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/create_multi_select_model.js deleted file mode 100644 index 5be0dfeed4e65..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/create_multi_select_model.js +++ /dev/null @@ -1,21 +0,0 @@ -import _ from 'lodash'; - -export default function selectableArray(items, selectedItems) { - if (!_.isArray(items)) throw new Error('First argument must be an array'); - if (!_.isArray(selectedItems)) throw new Error('Second argument must be an array'); - - return items.map((item) => { - const selected = _.find(selectedItems, (selectedItem) => { - return cleanItem(selectedItem) === cleanItem(item); - }); - - return { - title: item, - selected: !_.isUndefined(selected) - }; - }); -}; - -function cleanItem(item) { - return _.trim(item).toUpperCase(); -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/keys_deep.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/keys_deep.js deleted file mode 100644 index 6485f55344d81..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/keys_deep.js +++ /dev/null @@ -1,21 +0,0 @@ -import _ from 'lodash'; - -export default function keysDeep(object, base) { - let result = []; - let delimitedBase = base ? base + '.' : ''; - - _.forEach(object, (value, key) => { - var fullKey = delimitedBase + key; - if (_.isPlainObject(value)) { - result = result.concat(keysDeep(value, fullKey)); - } else { - result.push(fullKey); - } - }); - - if (base) { - result.push(base); - } - - return result; -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js deleted file mode 100644 index 17f562e677ade..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/lib/pipeline.js +++ /dev/null @@ -1,176 +0,0 @@ -import _ from 'lodash'; - -function updateProcessorOutputs(pipeline, simulateResults) { - simulateResults.forEach((result) => { - const processor = pipeline.getProcessorById(result.processorId); - - processor.outputObject = _.get(result, 'output'); - processor.error = _.get(result, 'error'); - }); -} - -//Updates the error state of the pipeline and its processors -//If a pipeline compile error is returned, lock all processors but the error -//If a pipeline data error is returned, lock all processors after the error -function updateErrorState(pipeline) { - pipeline.hasCompileError = _.some(pipeline.processors, (processor) => { - return _.get(processor, 'error.compile'); - }); - _.forEach(pipeline.processors, processor => { - processor.locked = false; - }); - - const errorIndex = _.findIndex(pipeline.processors, 'error'); - if (errorIndex === -1) return; - - _.forEach(pipeline.processors, (processor, index) => { - if (pipeline.hasCompileError && index !== errorIndex) { - processor.locked = true; - } - if (!pipeline.hasCompileError && index > errorIndex) { - processor.locked = true; - } - }); -} - -function updateProcessorInputs(pipeline) { - pipeline.processors.forEach((processor) => { - //we don't want to change the inputObject if the parent processor - //is in error because that can cause us to lose state. - if (!_.get(processor, 'parent.error')) { - //the parent property of the first processor is set to the pipeline.input. - //In all other cases it is set to processor[index-1] - if (!processor.parent.processorId) { - processor.inputObject = _.cloneDeep(processor.parent); - } else { - processor.inputObject = _.cloneDeep(processor.parent.outputObject); - } - } - }); -} - - -export default class Pipeline { - - constructor() { - this.processors = []; - this.processorCounter = 0; - this.input = {}; - this.output = undefined; - this.dirty = false; - this.hasCompileError = false; - } - - get model() { - const pipeline = { - input: this.input, - processors: _.map(this.processors, processor => processor.model) - }; - return pipeline; - } - - setDirty() { - this.dirty = true; - } - - load(pipeline) { - this.processors = []; - pipeline.processors.forEach((processor) => { - this.addExisting(processor); - }); - } - - remove(processor) { - const processors = this.processors; - const index = processors.indexOf(processor); - - processors.splice(index, 1); - } - - moveUp(processor) { - const processors = this.processors; - const index = processors.indexOf(processor); - - if (index === 0) return; - - const temp = processors[index - 1]; - processors[index - 1] = processors[index]; - processors[index] = temp; - } - - moveDown(processor) { - const processors = this.processors; - const index = processors.indexOf(processor); - - if (index === processors.length - 1) return; - - const temp = processors[index + 1]; - processors[index + 1] = processors[index]; - processors[index] = temp; - } - - addExisting(existingProcessor) { - const Type = existingProcessor.constructor; - const newProcessor = this.add(Type); - _.assign(newProcessor, _.omit(existingProcessor, 'processorId')); - - return newProcessor; - } - - add(ProcessorType) { - const processors = this.processors; - - this.processorCounter += 1; - const processorId = `processor_${this.processorCounter}`; - const newProcessor = new ProcessorType(processorId); - processors.push(newProcessor); - - return newProcessor; - } - - updateParents() { - const processors = this.processors; - - processors.forEach((processor, index) => { - let newParent; - if (index === 0) { - newParent = this.input; - } else { - newParent = processors[index - 1]; - } - - processor.setParent(newParent); - }); - this.dirty = true; - } - - getProcessorById(processorId) { - const result = _.find(this.processors, { processorId }); - - if (!result) { - throw new Error(`Could not find processor by id [${processorId}]`); - } - - return result; - } - - updateOutput() { - const processors = this.processors; - - const errorIndex = _.findIndex(processors, 'error'); - const goodProcessor = errorIndex === -1 ? _.last(processors) : processors[errorIndex - 1]; - this.output = goodProcessor ? goodProcessor.outputObject : this.input; - - this.dirty = false; - } - - // Updates the state of the pipeline and processors with the results - // from an ingest simulate call. - applySimulateResults(simulateResults) { - updateProcessorOutputs(this, simulateResults); - updateErrorState(this); - updateProcessorInputs(this); - this.updateOutput(); - } - -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/directive.js deleted file mode 100644 index 4736f64e125c6..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/directive.js +++ /dev/null @@ -1,38 +0,0 @@ -import uiModules from 'ui/modules'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiAppend', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function processorUiChanged() { - pipeline.setDirty(); - } - - function splitValues(delimitedList) { - return delimitedList.split('\n'); - } - - function joinValues(valueArray) { - return valueArray.join('\n'); - } - - function updateValues() { - processor.values = splitValues($scope.values); - } - - $scope.values = joinValues(processor.values); - - $scope.$watch('values', updateValues); - $scope.$watch('processor.targetField', processorUiChanged); - $scope.$watchCollection('processor.values', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view.html deleted file mode 100644 index b71c1d5f00c46..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view.html +++ /dev/null @@ -1,8 +0,0 @@ -
    - - -
    -
    - (line delimited) - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view_model.js deleted file mode 100644 index a0a9711367a42..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/append/view_model.js +++ /dev/null @@ -1,23 +0,0 @@ -import Processor from '../base/view_model'; - -export class Append extends Processor { - constructor(processorId) { - super(processorId, 'append', 'Append'); - this.targetField = ''; - this.values = []; - } - - get description() { - const target = this.targetField || '?'; - return `[${target}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - targetField: this.targetField || '', - values: this.values || [] - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/base/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/base/view_model.js deleted file mode 100644 index ed04ef321aa5c..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/base/view_model.js +++ /dev/null @@ -1,23 +0,0 @@ -export default class Processor { - constructor(processorId, typeId, title) { - if (!typeId || !title) { - throw new Error('Cannot instantiate the base Processor class.'); - } - - this.processorId = processorId; - this.title = title; - this.typeId = typeId; - this.collapsed = false; - this.parent = undefined; - this.inputObject = undefined; - this.outputObject = undefined; - this.error = undefined; - } - - setParent(newParent) { - const oldParent = this.parent; - this.parent = newParent; - - return (oldParent !== this.parent); - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/directive.js deleted file mode 100644 index 0aab15cf1a6ec..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/directive.js +++ /dev/null @@ -1,43 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiConvert', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.types = ['auto', 'number', 'string', 'boolean']; - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.type', processorUiChanged); - $scope.$watch('processor.targetField', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view.html deleted file mode 100644 index a7cb637fa2d9e..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view.html +++ /dev/null @@ -1,24 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view_model.js deleted file mode 100644 index ada158dc34454..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/convert/view_model.js +++ /dev/null @@ -1,28 +0,0 @@ -import _ from 'lodash'; -import Processor from '../base/view_model'; - -export class Convert extends Processor { - constructor(processorId) { - super(processorId, 'convert', 'Convert'); - this.sourceField = ''; - this.targetField = ''; - this.type = 'auto'; - } - - get description() { - const source = this.sourceField || '?'; - const type = this.type || '?'; - const target = this.targetField ? ` -> [${this.targetField}]` : ''; - return `[${source}] to ${type}${target}`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - targetField: this.targetField || '', - type: this.type || 'auto' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/directive.js deleted file mode 100644 index f9e42bfa399ea..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/directive.js +++ /dev/null @@ -1,58 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import createMultiSelectModel from '../../lib/create_multi_select_model'; -import template from './view.html'; -import './styles.less'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiDate', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope, debounce) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - const updateFormats = debounce(() => { - processor.formats = _($scope.formats) - .filter('selected') - .map('title') - .value(); - - $scope.customFormatSelected = _.includes(processor.formats, 'Custom'); - processorUiChanged(); - }, 200); - - $scope.updateFormats = updateFormats; - $scope.formats = createMultiSelectModel(['ISO8601', 'UNIX', 'UNIX_MS', 'TAI64N', 'Custom'], processor.formats); - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.customFormat', updateFormats); - $scope.$watch('processor.targetField', processorUiChanged); - $scope.$watch('processor.timezone', processorUiChanged); - $scope.$watch('processor.locale', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/styles.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/styles.less deleted file mode 100644 index 9b28eb3adc868..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/styles.less +++ /dev/null @@ -1,5 +0,0 @@ -processor-ui-date { - .custom-date-format { - display: flex; - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view.html deleted file mode 100644 index da30a479993c1..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view.html +++ /dev/null @@ -1,74 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    -
    -
    - -
    - -
    - -
    - diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view_model.js deleted file mode 100644 index 0186675e3e229..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/date/view_model.js +++ /dev/null @@ -1,32 +0,0 @@ -import Processor from '../base/view_model'; - -export class Date extends Processor { - constructor(processorId) { - super(processorId, 'date', 'Date'); - this.sourceField = ''; - this.targetField = '@timestamp'; - this.formats = []; - this.timezone = 'Etc/UTC'; - this.locale = 'ENGLISH'; - this.customFormat = ''; - } - - get description() { - const source = this.sourceField || '?'; - const target = this.targetField || '?'; - return `[${source}] -> [${target}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - targetField: this.targetField || '', - formats: this.formats || [], - timezone: this.timezone || '', - locale: this.locale || '', - customFormat: this.customFormat || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/directive.js deleted file mode 100644 index 3f7b9ff7edf4f..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/directive.js +++ /dev/null @@ -1,61 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; -import './styles.less'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiGeoip', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - function splitValues(delimitedList) { - return delimitedList.split('\n'); - } - - function joinValues(valueArray) { - return valueArray.join('\n'); - } - - function updateDatabaseFields() { - const fieldsString = $scope.databaseFields.replace(/,/g, '\n'); - processor.databaseFields = _(splitValues(fieldsString)).map(_.trim).compact().value(); - $scope.databaseFields = joinValues(processor.databaseFields); - } - - $scope.databaseFields = joinValues(processor.databaseFields); - - $scope.$watch('databaseFields', updateDatabaseFields); - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.targetField', processorUiChanged); - $scope.$watch('processor.databaseFile', processorUiChanged); - $scope.$watchCollection('processor.databaseFields', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/styles.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/styles.less deleted file mode 100644 index fef99d4c0d9a0..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/styles.less +++ /dev/null @@ -1,13 +0,0 @@ -processor-ui-geoip { - .advanced-section { - margin-top: 15px; - } - - .advanced-section-heading { - .btn { - background-color: transparent; - color: black; - border: transparent; - } - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view.html deleted file mode 100644 index 8807ecde2ae82..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view.html +++ /dev/null @@ -1,42 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    - -
    -
    - - -
    -
    -
    - - -
    -
    - (line delimited) - -
    -
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view_model.js deleted file mode 100644 index 7041b92cb4a44..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/geoip/view_model.js +++ /dev/null @@ -1,28 +0,0 @@ -import Processor from '../base/view_model'; - -export class GeoIp extends Processor { - constructor(processorId) { - super(processorId, 'geoip', 'Geo IP'); - this.sourceField = ''; - this.targetField = ''; - this.databaseFile = ''; - this.databaseFields = []; - } - - get description() { - const source = this.sourceField || '?'; - const target = this.targetField || '?'; - return `[${source}] -> [${target}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - targetField: this.targetField || '', - databaseFile: this.databaseFile || '', - databaseFields: this.databaseFields || [] - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/directive.js deleted file mode 100644 index 60bf7d6522613..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/directive.js +++ /dev/null @@ -1,40 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiGrok', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.pattern', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view.html deleted file mode 100644 index 13170570605ee..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view_model.js deleted file mode 100644 index 548b2d52f2a2f..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/grok/view_model.js +++ /dev/null @@ -1,30 +0,0 @@ -import _ from 'lodash'; -import keysDeep from '../../lib/keys_deep'; -import Processor from '../base/view_model'; - -export class Grok extends Processor { - constructor(processorId) { - super(processorId, 'grok', 'Grok'); - this.sourceField = ''; - this.pattern = ''; - } - - get description() { - const inputKeys = keysDeep(this.inputObject); - const outputKeys = keysDeep(this.outputObject); - const addedKeys = _.difference(outputKeys, inputKeys); - const added = addedKeys.sort().map(field => `[${field}]`).join(', '); - const source = this.sourceField || '?'; - - return `[${source}] -> ${added}`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - pattern: this.pattern || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/directive.js deleted file mode 100644 index abef4cddde24f..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/directive.js +++ /dev/null @@ -1,41 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiGsub', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.pattern', processorUiChanged); - $scope.$watch('processor.replacement', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view.html deleted file mode 100644 index 5ef22f7b5047b..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view.html +++ /dev/null @@ -1,20 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view_model.js deleted file mode 100644 index 32559f9d8ea59..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/gsub/view_model.js +++ /dev/null @@ -1,25 +0,0 @@ -import Processor from '../base/view_model'; - -export class Gsub extends Processor { - constructor(processorId) { - super(processorId, 'gsub', 'Gsub'); - this.sourceField = ''; - this.pattern = ''; - this.replacement = ''; - } - - get description() { - const source = this.sourceField || '?'; - return `[${source}] - /${this.pattern}/ -> '${this.replacement}'`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - pattern: this.pattern || '', - replacement: this.replacement || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/index.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/index.js deleted file mode 100644 index f490163a41286..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import './append/directive'; -import './convert/directive'; -import './date/directive'; -import './geoip/directive'; -import './grok/directive'; -import './gsub/directive'; -import './join/directive'; -import './lowercase/directive'; -import './remove/directive'; -import './rename/directive'; -import './set/directive'; -import './split/directive'; -import './trim/directive'; -import './uppercase/directive'; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/directive.js deleted file mode 100644 index 765467afb39f0..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/directive.js +++ /dev/null @@ -1,41 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiJoin', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - const allKeys = keysDeep(processor.inputObject); - $scope.fields = _.filter(allKeys, (key) => { return _.isArray(_.get(processor.inputObject, key)); }); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.separator', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view.html deleted file mode 100644 index f28b1ca30a788..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view_model.js deleted file mode 100644 index a480b749d8680..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/join/view_model.js +++ /dev/null @@ -1,24 +0,0 @@ -import Processor from '../base/view_model'; - -export class Join extends Processor { - constructor(processorId) { - super(processorId, 'join', 'Join'); - this.sourceField = ''; - this.separator = ''; - } - - get description() { - const source = this.sourceField || '?'; - const separator = this.separator ? ` on '${this.separator}'` : ''; - return `[${source}]${separator}`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - separator: this.separator || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/directive.js deleted file mode 100644 index b661b0af8faa7..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/directive.js +++ /dev/null @@ -1,39 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiLowercase', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - const allKeys = keysDeep(processor.inputObject); - $scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); }); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view.html deleted file mode 100644 index 0b32379788f64..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view.html +++ /dev/null @@ -1,12 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view_model.js deleted file mode 100644 index 4cc12cd0437df..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/lowercase/view_model.js +++ /dev/null @@ -1,21 +0,0 @@ -import Processor from '../base/view_model'; - -export class Lowercase extends Processor { - constructor(processorId) { - super(processorId, 'lowercase', 'Lowercase'); - this.sourceField = ''; - } - - get description() { - const source = this.sourceField || '?'; - return `[${source}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/directive.js deleted file mode 100644 index 30e84055ef6de..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/directive.js +++ /dev/null @@ -1,38 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiRemove', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view.html deleted file mode 100644 index 0b32379788f64..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view.html +++ /dev/null @@ -1,12 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view_model.js deleted file mode 100644 index fb33cfc5e4cef..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/remove/view_model.js +++ /dev/null @@ -1,21 +0,0 @@ -import Processor from '../base/view_model'; - -export class Remove extends Processor { - constructor(processorId) { - super(processorId, 'remove', 'Remove'); - this.sourceField = ''; - } - - get description() { - const source = this.sourceField || '?'; - return `[${source}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/directive.js deleted file mode 100644 index 9496c9caa3c5a..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/directive.js +++ /dev/null @@ -1,40 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiRename', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - $scope.fields = keysDeep(processor.inputObject); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.targetField', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view.html deleted file mode 100644 index 2a107071354d9..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view_model.js deleted file mode 100644 index 1899fcfc3c2a9..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/rename/view_model.js +++ /dev/null @@ -1,24 +0,0 @@ -import Processor from '../base/view_model'; - -export class Rename extends Processor { - constructor(processorId) { - super(processorId, 'rename', 'Rename'); - this.sourceField = ''; - this.targetField = ''; - } - - get description() { - const source = this.sourceField || '?'; - const target = this.targetField || '?'; - return `[${source}] -> [${target}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - targetField: this.targetField || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/directive.js deleted file mode 100644 index b7a2521f37187..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/directive.js +++ /dev/null @@ -1,23 +0,0 @@ -import uiModules from 'ui/modules'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiSet', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.targetField', processorUiChanged); - $scope.$watch('processor.value', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view.html deleted file mode 100644 index c6459b62f3842..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view.html +++ /dev/null @@ -1,8 +0,0 @@ -
    - - -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view_model.js deleted file mode 100644 index 4c7f31352c2d9..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/set/view_model.js +++ /dev/null @@ -1,23 +0,0 @@ -import Processor from '../base/view_model'; - -export class Set extends Processor { - constructor(processorId) { - super(processorId, 'set', 'Set'); - this.targetField = ''; - this.value = ''; - } - - get description() { - const target = this.targetField || '?'; - return `[${target}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - targetField: this.targetField || '', - value: this.value || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/directive.js deleted file mode 100644 index 9f1bd9280f5af..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/directive.js +++ /dev/null @@ -1,41 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiSplit', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - const allKeys = keysDeep(processor.inputObject); - $scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); }); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - - $scope.$watch('processor.separator', processorUiChanged); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view.html deleted file mode 100644 index f67996d3cd5e2..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view.html +++ /dev/null @@ -1,16 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    -
    - - -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view_model.js deleted file mode 100644 index 30e7c8d81473c..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/split/view_model.js +++ /dev/null @@ -1,24 +0,0 @@ -import Processor from '../base/view_model'; - -export class Split extends Processor { - constructor(processorId) { - super(processorId, 'split', 'Split'); - this.sourceField = ''; - this.separator = ''; - } - - get description() { - const source = this.sourceField || '?'; - const separator = this.separator || '?'; - return `[${source}] on '${separator}'`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '', - separator: this.separator || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/directive.js deleted file mode 100644 index d016bde569033..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/directive.js +++ /dev/null @@ -1,39 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiTrim', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - const allKeys = keysDeep(processor.inputObject); - $scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); }); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view.html deleted file mode 100644 index 0b32379788f64..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view.html +++ /dev/null @@ -1,12 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view_model.js deleted file mode 100644 index c0a52f2a899fc..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/trim/view_model.js +++ /dev/null @@ -1,21 +0,0 @@ -import Processor from '../base/view_model'; - -export class Trim extends Processor { - constructor(processorId) { - super(processorId, 'trim', 'Trim'); - this.sourceField = ''; - } - - get description() { - const source = this.sourceField || '?'; - return `[${source}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/directive.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/directive.js deleted file mode 100644 index f8d8bb69695b4..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/directive.js +++ /dev/null @@ -1,39 +0,0 @@ -import _ from 'lodash'; -import uiModules from 'ui/modules'; -import keysDeep from '../../lib/keys_deep'; -import template from './view.html'; - -const app = uiModules.get('kibana'); - -//scope.processor, scope.pipeline are attached by the process_container. -app.directive('processorUiUppercase', function () { - return { - restrict: 'E', - template: template, - controller : function ($scope) { - const processor = $scope.processor; - const pipeline = $scope.pipeline; - - function consumeNewInputObject() { - const allKeys = keysDeep(processor.inputObject); - $scope.fields = _.filter(allKeys, (key) => { return _.isString(_.get(processor.inputObject, key)); }); - refreshFieldData(); - } - - function refreshFieldData() { - $scope.fieldData = _.get(processor.inputObject, processor.sourceField); - } - - function processorUiChanged() { - pipeline.setDirty(); - } - - $scope.$watch('processor.inputObject', consumeNewInputObject); - - $scope.$watch('processor.sourceField', () => { - refreshFieldData(); - processorUiChanged(); - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view.html deleted file mode 100644 index 0b32379788f64..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view.html +++ /dev/null @@ -1,12 +0,0 @@ -
    - - -
    -
    - -
    {{ fieldData }}
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view_model.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view_model.js deleted file mode 100644 index ce831ab40028c..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/uppercase/view_model.js +++ /dev/null @@ -1,21 +0,0 @@ -import Processor from '../base/view_model'; - -export class Uppercase extends Processor { - constructor(processorId) { - super(processorId, 'uppercase', 'Uppercase'); - this.sourceField = ''; - } - - get description() { - const source = this.sourceField || '?'; - return `[${source}]`; - } - - get model() { - return { - processorId: this.processorId, - typeId: this.typeId, - sourceField: this.sourceField || '' - }; - } -}; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/view_models.js b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/view_models.js deleted file mode 100644 index b82d5c92d6108..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/processors/view_models.js +++ /dev/null @@ -1,14 +0,0 @@ -export { Append } from './append/view_model'; -export { Convert } from './convert/view_model'; -export { Date } from './date/view_model'; -export { GeoIp } from './geoip/view_model'; -export { Grok } from './grok/view_model'; -export { Gsub } from './gsub/view_model'; -export { Join } from './join/view_model'; -export { Lowercase } from './lowercase/view_model'; -export { Remove } from './remove/view_model'; -export { Rename } from './rename/view_model'; -export { Set } from './set/view_model'; -export { Split } from './split/view_model'; -export { Trim } from './trim/view_model'; -export { Uppercase } from './uppercase/view_model'; diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_output_preview.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_output_preview.less deleted file mode 100644 index f58c658b9ae9a..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_output_preview.less +++ /dev/null @@ -1,28 +0,0 @@ -@import (reference) "~ui/styles/variables"; -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/theme"; - -output-preview { - .visual { - border: none; - background-color: @settings-filebeat-wizard-panel-bg; - border-radius: 0; - overflow-x: auto; - } - - .visual.collapsed { - max-height: 125px; - overflow-y: auto; - } - - pre { - background-color: transparent; - border: none; - } - - .hide-unchanged { - .jsondiffpatch-unchanged { - display: none; - } - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_output.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_output.less deleted file mode 100644 index fa928d4d64fcd..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_output.less +++ /dev/null @@ -1,22 +0,0 @@ -@import (reference) "~ui/styles/variables"; -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/theme"; - -pipeline-output { - flex: 1 1 auto; - display: flex; - flex-direction: column; - - .header-line { - display: flex; - - label { - width: 100%; - } - } - - pre { - min-height: 450px; - flex: 1 1 1px; - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_setup.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_setup.less deleted file mode 100644 index 0bd1a3846ad44..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_pipeline_setup.less +++ /dev/null @@ -1,74 +0,0 @@ -@import (reference) "~ui/styles/variables"; -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/theme"; - -pipeline-setup { - .main-panels { - display: flex; - margin-bottom: 10px; - - .left-panel { - .flex-parent(1, 1, 1px); - width: 50%; - - &>label { - margin-bottom: 2px; - } - } - - .center-panel { - .flex-parent(0, 0, auto, column); - justify-content: center; - - .buttons { - .flex-parent(0, 0, auto, column); - } - } - - .right-panel { - .flex-parent(1, 1, 1px, column); - width: 50%; - } - - .pipeline { - min-height: 450px; - background-color: @settings-filebeat-wizard-panel-bg; - } - } - - label { - margin-bottom: 0px; - } - - ul.pipeline-container { - list-style-type: none; - padding: 0px; - margin-bottom: 0px; - - &>li { - padding: 1px; - } - } - - .add-processor { - padding:10px; - margin-bottom: 10px; - } - - .add-processor-dropdown { - display: flex; - justify-content: flex-start; - align-items: center; - - select.form-control { - background-color: @settings-filebeat-wizard-processor-select-bg; - border: none; - width: auto; - margin-right: 5px; - } - } - - textarea.form-control { - min-height: 150px; - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less deleted file mode 100644 index 4c64b650960c8..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container.less +++ /dev/null @@ -1,42 +0,0 @@ -@import (reference) "~ui/styles/variables"; -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/theme"; - -processor-ui-container { - display: block; - margin-bottom: 1px; - border-bottom: 2px solid; - border-color: white; - - .processor-ui-container-body { - display: block; - overflow: hidden; - position: relative; - - .overlay { - display: none; - position: absolute; - - top: -5000px; - left: -5000px; - width: 10000px; - height: 10000px; - background-color: @settings-filebeat-wizard-processor-container-overlay-bg; - } - - &.locked { - .overlay { - display: block; - } - } - } - - .processor-ui-container-body-content { - padding: 10px; - background-color: white; - } - - label { - font-weight: normal; - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container_header.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container_header.less deleted file mode 100644 index 246a4ab0792cd..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_processor_ui_container_header.less +++ /dev/null @@ -1,47 +0,0 @@ -@import (reference) "~ui/styles/variables"; -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/theme"; - -processor-ui-container-header { - .processor-ui-container-header { - display: flex; - align-items: center; - flex: 1 0 auto; - background-color: @settings-filebeat-wizard-panel-bg; - border: none; - padding: 10px; - - button { - width: 22px; - border-radius: 4px; - } - } - - .processor-ui-container-header-toggle { - flex: 0 0 auto; - margin-right: 5px; - } - - .processor-ui-container-header-title { - flex: 1 1 auto; - .ellipsis(); - font-weight: bold; - - .processor-title { - width: 100%; - } - - .processor-description { - font-weight: normal; - } - - .processor-description.danger { - font-weight: bold; - color: @brand-danger; - } - } - - .processor-ui-container-header-controls { - flex: 0 0 auto; - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_source_data.less b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_source_data.less deleted file mode 100644 index 99103305556ad..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/styles/_source_data.less +++ /dev/null @@ -1,17 +0,0 @@ -@import (reference) "~ui/styles/variables"; -@import (reference) "~ui/styles/mixins"; -@import (reference) "~ui/styles/theme"; - -source-data { - flex: 1 0 auto; - display: flex; - height: 22px; - - button { - flex: 0 0 auto; - width: 22px; - position: relative; - top: -5px; - margin-left: 5px; - } -} diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/output_preview.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/output_preview.html deleted file mode 100644 index 00622f43c4e72..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/output_preview.html +++ /dev/null @@ -1,24 +0,0 @@ -
    - - - collapse - expand -  /  - only show changes - show all -
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_output.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_output.html deleted file mode 100644 index 07da266198598..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_output.html +++ /dev/null @@ -1,18 +0,0 @@ -
    - - - -
    -
    {{ pipeline.output | json }}
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html deleted file mode 100644 index aafc8cb3c0d90..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/pipeline_setup.html +++ /dev/null @@ -1,74 +0,0 @@ -

    - Let's build a pipeline! Ingest pipelines are an easy way to modify documents before they're indexed in Elasticsearch. They're composed of processors which can change your data in many ways. Create a pipeline below while cycling through your samples to see its effect on your data. -

    -
    -
    - -
    -
      -
    • - -
    • -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    - - -
    -
    -
    - -
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html deleted file mode 100644 index fa9257053eed8..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container.html +++ /dev/null @@ -1,25 +0,0 @@ - - -
    -
    -
    - {{processor.error.message}} -
    -
    - - -
    -
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html deleted file mode 100644 index 34a25dad84b02..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/processor_ui_container_header.html +++ /dev/null @@ -1,61 +0,0 @@ -
    - - -
    - - {{processor.title}} - - - - - {{ processor.description }} - - - - - - Error - -
    - -
    - - - - - -
    -
    diff --git a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/source_data.html b/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/source_data.html deleted file mode 100644 index 3e0913a00fed1..0000000000000 --- a/src/core_plugins/kibana/public/management/sections/indices/add_data_steps/pipeline_setup/views/source_data.html +++ /dev/null @@ -1,20 +0,0 @@ - - diff --git a/src/core_plugins/kibana/server/lib/__tests__/converters/ingest_simulate_api_kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/__tests__/converters/ingest_simulate_api_kibana_to_es_converter.js deleted file mode 100644 index 66c1339c3b56d..0000000000000 --- a/src/core_plugins/kibana/server/lib/__tests__/converters/ingest_simulate_api_kibana_to_es_converter.js +++ /dev/null @@ -1,87 +0,0 @@ -import expect from 'expect.js'; -import _ from 'lodash'; -import ingestSimulateApiKibanaToEsConverter from '../../converters/ingest_simulate_api_kibana_to_es_converter'; - -describe('ingestSimulateApiKibanaToEsConverter', function () { - - it('populates the docs._source section and converts known processors', function () { - - function buildSamplePipeline(input) { - return { - processors: [ { processor_id: 'processor1', type_id: 'set', target_field: 'bar', value: 'foo' } ], - input: input - }; - } - - function buildExpected(input) { - return { - pipeline : { - processors: [{ - set: { - field: 'bar', - tag: 'processor1', - value: 'foo' - } - }] - }, - 'docs' : [ - { '_source': input } - ] - }; - } - - let expected; - let actual; - - expected = buildExpected(undefined); - actual = ingestSimulateApiKibanaToEsConverter(buildSamplePipeline(undefined)); - expect(actual).to.eql(expected); - - expected = buildExpected('foo'); - actual = ingestSimulateApiKibanaToEsConverter(buildSamplePipeline('foo')); - expect(actual).to.eql(expected); - - expected = buildExpected({ foo: 'bar' }); - actual = ingestSimulateApiKibanaToEsConverter(buildSamplePipeline({ foo: 'bar' })); - expect(actual).to.eql(expected); - }); - - it('handles multiple processors', function () { - const pipeline = { - processors: [ - { processor_id: 'processor1', type_id: 'set', target_field: 'bar', value: 'foo' }, - { processor_id: 'processor2', type_id: 'set', target_field: 'bar', value: 'foo' }, - ], - input: {} - }; - const expected = { - 'pipeline': { - 'processors': [ - { - set: { - field: 'bar', - tag: 'processor1', - value: 'foo' - } - }, - { - set: { - field: 'bar', - tag: 'processor2', - value: 'foo' - } - } - ] - }, - 'docs': [ - {'_source': {}} - ] - }; - - const actual = ingestSimulateApiKibanaToEsConverter(pipeline); - - expect(actual).to.eql(expected); - }); - - -}); diff --git a/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_processors_response.js b/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_processors_response.js deleted file mode 100644 index 888f6d352f9bd..0000000000000 --- a/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_processors_response.js +++ /dev/null @@ -1,90 +0,0 @@ -import processESIngestProcessorsResponse from '../process_es_ingest_processors_response'; -import expect from 'expect.js'; -import _ from 'lodash'; - -describe('processESIngestSimulateResponse', function () { - - it('should return a list of strings indicating the enabled processors', function () { - const response = { - nodes: { - node_foo: { - ingest: { - processors: [ - { type: 'proc_foo' }, - { type: 'proc_bar' } - ] - } - } - } - }; - - const expected = [ 'proc_foo', 'proc_bar' ]; - const actual = processESIngestProcessorsResponse(response); - - expect(_.isEqual(actual, expected)).to.be.ok(); - }); - - it('should return a unique list of processors', function () { - const response = { - nodes: { - node_foo: { - ingest: { - processors: [ - { type: 'proc_foo' }, - { type: 'proc_bar' } - ] - } - }, - node_bar: { - ingest: { - processors: [ - { type: 'proc_foo' }, - { type: 'proc_bar' } - ] - } - } - } - }; - - const expected = [ 'proc_foo', 'proc_bar' ]; - const actual = processESIngestProcessorsResponse(response); - - expect(_.isEqual(actual, expected)).to.be.ok(); - }); - - it('should combine the available processors from all nodes', function () { - const response = { - nodes: { - node_foo: { - ingest: { - processors: [ - { type: 'proc_foo' } - ] - } - }, - node_bar: { - ingest: { - processors: [ - { type: 'proc_bar' } - ] - } - } - } - }; - - const expected = [ 'proc_foo', 'proc_bar' ]; - const actual = processESIngestProcessorsResponse(response); - - expect(_.isEqual(actual, expected)).to.be.ok(); - }); - - it('should return an empty array for unexpected response', function () { - expect(_.isEqual(processESIngestProcessorsResponse({ nodes: {}}), [])).to.be.ok(); - expect(_.isEqual(processESIngestProcessorsResponse({}), [])).to.be.ok(); - expect(_.isEqual(processESIngestProcessorsResponse(undefined), [])).to.be.ok(); - expect(_.isEqual(processESIngestProcessorsResponse(null), [])).to.be.ok(); - expect(_.isEqual(processESIngestProcessorsResponse(''), [])).to.be.ok(); - expect(_.isEqual(processESIngestProcessorsResponse(1), [])).to.be.ok(); - }); - -}); diff --git a/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js b/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js deleted file mode 100644 index 9acae2371d5b0..0000000000000 --- a/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_error.js +++ /dev/null @@ -1,19 +0,0 @@ -import processESIngestSimulateError from '../process_es_ingest_simulate_error'; -import expect from 'expect.js'; -import _ from 'lodash'; - -describe('processESIngestSimulateError', function () { - - it('result will be returned for processor that threw the error', function () { - const error = _.set({}, 'body.error.root_cause[0].reason', 'foobar'); - _.set(error, 'body.error.root_cause[0].header.processor_tag', 'processor1'); - - const expected = [ - { processorId: 'processor1', error: { compile: true, message: 'foobar' } } - ]; - const actual = processESIngestSimulateError(error); - - expect(_.isEqual(actual, expected)).to.be.ok(); - }); - -}); diff --git a/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js b/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js deleted file mode 100644 index 0ff2fd0f7b191..0000000000000 --- a/src/core_plugins/kibana/server/lib/__tests__/process_es_ingest_simulate_response.js +++ /dev/null @@ -1,73 +0,0 @@ -import processESIngestSimulateResponse from '../process_es_ingest_simulate_response'; -import expect from 'expect.js'; -import _ from 'lodash'; - -describe('processESIngestSimulateResponse', function () { - - it('each processor that receives a result will contain response info', function () { - const response = { - docs: [ { processor_results: [ - { tag: 'processor1', doc: { _source: 'new_foo' }, error: undefined }, - { tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined }, - { tag: 'processor3', doc: { _source: 'new_baz' }, error: undefined } - ] } ] - }; - - const expected = [ - { processorId: 'processor1', output: 'new_foo', error: undefined }, - { processorId: 'processor2', output: 'new_bar', error: undefined }, - { processorId: 'processor3', output: 'new_baz', error: undefined } - ]; - const actual = processESIngestSimulateResponse(response); - - expect(actual).to.eql(expected); - }); - - describe('processors that return an error object', function () { - - it('will be the root_cause reason if one exists', function () { - const response = { - docs: [ { processor_results: [ - { tag: 'processor1', doc: { _source: 'new_foo' }, error: undefined }, - { - tag: 'processor2', - doc: 'dummy', - error: { root_cause: [ { reason: 'something bad happened', type: 'general exception' } ] } - } - ] } ] - }; - - const expected = [ - { processorId: 'processor1', output: 'new_foo', error: undefined }, - { processorId: 'processor2', output: undefined, error: { compile: false, message: 'something bad happened'} } - ]; - const actual = processESIngestSimulateResponse(response); - - expect(actual).to.eql(expected); - }); - - it('will be the root_cause type if reason does not exists', function () { - const response = { - docs: [ { processor_results: [ - { tag: 'processor2', doc: { _source: 'new_bar' }, error: undefined }, - { - tag: 'processor3', - doc: 'dummy', - error: { root_cause: [ { type: 'something bad happened' } ] } - } - ] } ] - }; - - const expected = [ - { processorId: 'processor2', output: 'new_bar', error: undefined }, - { processorId: 'processor3', output: undefined, error: { compile: false, message: 'something bad happened'} } - ]; - const actual = processESIngestSimulateResponse(response); - - expect(actual).to.eql(expected); - }); - - }); - - -}); diff --git a/src/core_plugins/kibana/server/lib/converters/ingest_pipeline_api_kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/converters/ingest_pipeline_api_kibana_to_es_converter.js deleted file mode 100644 index b26f14de1224a..0000000000000 --- a/src/core_plugins/kibana/server/lib/converters/ingest_pipeline_api_kibana_to_es_converter.js +++ /dev/null @@ -1,10 +0,0 @@ -import _ from 'lodash'; -import * as ingestProcessorApiKibanaToEsConverters from '../processors/converters'; - -export default function ingestPipelineApiKibanaToEsConverter(pipelineApiDocument) { - return { - processors: _.map(pipelineApiDocument, (processor) => { - return ingestProcessorApiKibanaToEsConverters[processor.type_id](processor); - }) - }; -} diff --git a/src/core_plugins/kibana/server/lib/converters/ingest_simulate_api_kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/converters/ingest_simulate_api_kibana_to_es_converter.js deleted file mode 100644 index 7859d21fc2e70..0000000000000 --- a/src/core_plugins/kibana/server/lib/converters/ingest_simulate_api_kibana_to_es_converter.js +++ /dev/null @@ -1,13 +0,0 @@ -import _ from 'lodash'; -import ingestPipelineApiKibanaToEsConverter from './ingest_pipeline_api_kibana_to_es_converter'; - -export default function ingestSimulateApiKibanaToEsConverter(simulateApiDocument) { - return { - pipeline: ingestPipelineApiKibanaToEsConverter(simulateApiDocument.processors), - docs: [ - { - _source: simulateApiDocument.input - } - ] - }; -} diff --git a/src/core_plugins/kibana/server/lib/process_es_ingest_processors_response.js b/src/core_plugins/kibana/server/lib/process_es_ingest_processors_response.js deleted file mode 100644 index 720ad47264193..0000000000000 --- a/src/core_plugins/kibana/server/lib/process_es_ingest_processors_response.js +++ /dev/null @@ -1,16 +0,0 @@ -const _ = require('lodash'); - -export default function processESIngestProcessorsResponse(response) { - const nodes = _.get(response, 'nodes'); - - const results = _.chain(nodes) - .map('ingest.processors') - .reduce((result, processors) => { - return result.concat(processors); - }) - .map('type') - .unique() - .value(); - - return results; -}; diff --git a/src/core_plugins/kibana/server/lib/process_es_ingest_simulate_error.js b/src/core_plugins/kibana/server/lib/process_es_ingest_simulate_error.js deleted file mode 100644 index 10bf2dc1b9720..0000000000000 --- a/src/core_plugins/kibana/server/lib/process_es_ingest_simulate_error.js +++ /dev/null @@ -1,23 +0,0 @@ -const _ = require('lodash'); - -function buildError(error) { - const errorMessage = _.get(error, 'body.error.root_cause[0].reason'); - return { - compile: true, - message: errorMessage - }; -} - -export default function processESIngestSimulateError(error) { - const processorId = _.get(error, 'body.error.root_cause[0].header.processor_tag'); - if (!processorId) throw error; - - const results = [ - { - processorId: processorId, - error: buildError(error) - } - ]; - - return results; -} diff --git a/src/core_plugins/kibana/server/lib/process_es_ingest_simulate_response.js b/src/core_plugins/kibana/server/lib/process_es_ingest_simulate_response.js deleted file mode 100644 index 7a7119432f7f5..0000000000000 --- a/src/core_plugins/kibana/server/lib/process_es_ingest_simulate_response.js +++ /dev/null @@ -1,24 +0,0 @@ -const _ = require('lodash'); - -function buildError(error) { - const errorMessage = _.get(error, 'root_cause[0].reason') || _.get(error, 'root_cause[0].type'); - if (!errorMessage) return; - - return { - compile: false, - message: errorMessage - }; -} - -export default function processESIngestSimulateResponse(resp) { - const processorResults = _.get(resp, 'docs[0].processor_results'); - const results = processorResults.map((processorResult) => { - return { - processorId: _.get(processorResult, 'tag'), - output: _.get(processorResult, 'doc._source'), - error: buildError(_.get(processorResult, 'error')) - }; - }); - - return results; -}; diff --git a/src/core_plugins/kibana/server/lib/processors/append/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/append/kibana_to_es_converter.js deleted file mode 100644 index 3dd87ac0e6f27..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/append/kibana_to_es_converter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function append(processorApiDocument) { - return { - append: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.target_field, - value: processorApiDocument.values - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/append/schema.js b/src/core_plugins/kibana/server/lib/processors/append/schema.js deleted file mode 100644 index 17f6582d548f5..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/append/schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const append = base.keys({ - type_id: Joi.string().only('append').required(), - target_field: Joi.string().allow(''), - values: Joi.array().items(Joi.string().allow('')) -}); diff --git a/src/core_plugins/kibana/server/lib/processors/base/schema.js b/src/core_plugins/kibana/server/lib/processors/base/schema.js deleted file mode 100644 index 66f699eed1a23..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/base/schema.js +++ /dev/null @@ -1,5 +0,0 @@ -import Joi from 'joi'; - -export const base = Joi.object({ - processor_id: Joi.string().required() -}); diff --git a/src/core_plugins/kibana/server/lib/processors/convert/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/convert/kibana_to_es_converter.js deleted file mode 100644 index 0fa89ae985bfc..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/convert/kibana_to_es_converter.js +++ /dev/null @@ -1,24 +0,0 @@ -import _ from 'lodash'; - -export default function convert(processorApiDocument) { - const types = { - //: , - auto: 'auto', - number: 'float', - string: 'string', - boolean: 'boolean' - }; - - const processor = { - convert: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - type: types[processorApiDocument.type] - } - }; - if (!_.isEmpty(processorApiDocument.target_field)) { - processor.convert.target_field = processorApiDocument.target_field; - } - - return processor; -} diff --git a/src/core_plugins/kibana/server/lib/processors/convert/schema.js b/src/core_plugins/kibana/server/lib/processors/convert/schema.js deleted file mode 100644 index 72d6938822afd..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/convert/schema.js +++ /dev/null @@ -1,9 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const convert = base.keys({ - type_id: Joi.string().only('convert').required(), - source_field: Joi.string().allow(''), - target_field: Joi.string().allow(''), - type: Joi.string() -}); diff --git a/src/core_plugins/kibana/server/lib/processors/converters.js b/src/core_plugins/kibana/server/lib/processors/converters.js deleted file mode 100644 index 19c4a17fc7ca8..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/converters.js +++ /dev/null @@ -1,14 +0,0 @@ -export append from '../processors/append/kibana_to_es_converter'; -export convert from '../processors/convert/kibana_to_es_converter'; -export date from '../processors/date/kibana_to_es_converter'; -export geoip from '../processors/geoip/kibana_to_es_converter'; -export grok from '../processors/grok/kibana_to_es_converter'; -export gsub from '../processors/gsub/kibana_to_es_converter'; -export join from '../processors/join/kibana_to_es_converter'; -export lowercase from '../processors/lowercase/kibana_to_es_converter'; -export remove from '../processors/remove/kibana_to_es_converter'; -export rename from '../processors/rename/kibana_to_es_converter'; -export set from '../processors/set/kibana_to_es_converter'; -export split from '../processors/split/kibana_to_es_converter'; -export trim from '../processors/trim/kibana_to_es_converter'; -export uppercase from '../processors/uppercase/kibana_to_es_converter'; diff --git a/src/core_plugins/kibana/server/lib/processors/date/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/date/kibana_to_es_converter.js deleted file mode 100644 index 3d8ee3f091508..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/date/kibana_to_es_converter.js +++ /dev/null @@ -1,23 +0,0 @@ -export default function date(processorApiDocument) { - const formats = []; - processorApiDocument.formats.forEach((format) => { - if (format.toUpperCase() === 'CUSTOM') { - if (processorApiDocument.custom_format) { - formats.push(processorApiDocument.custom_format); - } - } else { - formats.push(format); - } - }); - - return { - date: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - target_field: processorApiDocument.target_field, - formats: formats, - timezone: processorApiDocument.timezone, - locale: processorApiDocument.locale - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/date/schema.js b/src/core_plugins/kibana/server/lib/processors/date/schema.js deleted file mode 100644 index 5798712c016d1..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/date/schema.js +++ /dev/null @@ -1,12 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const date = base.keys({ - type_id: Joi.string().only('date').required(), - source_field: Joi.string().allow(''), - target_field: Joi.string().allow(''), - formats: Joi.array().items(Joi.string().allow('')), - timezone: Joi.string().allow(''), - locale: Joi.string().allow(''), - custom_format: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/geoip/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/geoip/kibana_to_es_converter.js deleted file mode 100644 index 0ffa984c4ea87..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/geoip/kibana_to_es_converter.js +++ /dev/null @@ -1,21 +0,0 @@ -import _ from 'lodash'; - -export default function geoip(processorApiDocument) { - const processor = { - geoip: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field - } - }; - if (!_.isEmpty(processorApiDocument.target_field)) { - processor.geoip.target_field = processorApiDocument.target_field; - } - if (!_.isEmpty(processorApiDocument.database_file)) { - processor.geoip.database_file = processorApiDocument.database_file; - } - if (!_.isEmpty(processorApiDocument.database_fields)) { - processor.geoip.properties = processorApiDocument.database_fields; - } - - return processor; -} diff --git a/src/core_plugins/kibana/server/lib/processors/geoip/schema.js b/src/core_plugins/kibana/server/lib/processors/geoip/schema.js deleted file mode 100644 index fd639e6434d89..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/geoip/schema.js +++ /dev/null @@ -1,10 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const geoip = base.keys({ - type_id: Joi.string().only('geoip').required(), - source_field: Joi.string().allow(''), - target_field: Joi.string().allow(''), - database_file: Joi.string().allow(''), - database_fields: Joi.array().items(Joi.string().allow('')), -}); diff --git a/src/core_plugins/kibana/server/lib/processors/grok/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/grok/kibana_to_es_converter.js deleted file mode 100644 index 3eff3fe031429..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/grok/kibana_to_es_converter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function grok(processorApiDocument) { - return { - grok: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - patterns: [ processorApiDocument.pattern ] - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/grok/schema.js b/src/core_plugins/kibana/server/lib/processors/grok/schema.js deleted file mode 100644 index 3bb3b7a47e769..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/grok/schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const grok = base.keys({ - type_id: Joi.string().only('grok').required(), - source_field: Joi.string().allow(''), - pattern: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/gsub/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/gsub/kibana_to_es_converter.js deleted file mode 100644 index 62da94dff89dc..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/gsub/kibana_to_es_converter.js +++ /dev/null @@ -1,10 +0,0 @@ -export default function gsub(processorApiDocument) { - return { - gsub: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - pattern: processorApiDocument.pattern, - replacement: processorApiDocument.replacement - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/gsub/schema.js b/src/core_plugins/kibana/server/lib/processors/gsub/schema.js deleted file mode 100644 index 9896f63ea8529..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/gsub/schema.js +++ /dev/null @@ -1,9 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const gsub = base.keys({ - type_id: Joi.string().only('gsub').required(), - source_field: Joi.string().allow(''), - pattern: Joi.string().allow(''), - replacement: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/join/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/join/kibana_to_es_converter.js deleted file mode 100644 index 58e0bea22824d..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/join/kibana_to_es_converter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function join(processorApiDocument) { - return { - join: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - separator: processorApiDocument.separator - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/join/schema.js b/src/core_plugins/kibana/server/lib/processors/join/schema.js deleted file mode 100644 index e82bb9c9f6578..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/join/schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const join = base.keys({ - type_id: Joi.string().only('join').required(), - source_field: Joi.string().allow(''), - separator: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/lowercase/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/lowercase/kibana_to_es_converter.js deleted file mode 100644 index b850bc066e873..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/lowercase/kibana_to_es_converter.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function lowercase(processorApiDocument) { - return { - lowercase: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/lowercase/schema.js b/src/core_plugins/kibana/server/lib/processors/lowercase/schema.js deleted file mode 100644 index 70c283b7b6327..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/lowercase/schema.js +++ /dev/null @@ -1,7 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const lowercase = base.keys({ - type_id: Joi.string().only('lowercase').required(), - source_field: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/remove/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/remove/kibana_to_es_converter.js deleted file mode 100644 index 840446ec39e3a..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/remove/kibana_to_es_converter.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function remove(processorApiDocument) { - return { - remove: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/remove/schema.js b/src/core_plugins/kibana/server/lib/processors/remove/schema.js deleted file mode 100644 index c2c2fc803be74..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/remove/schema.js +++ /dev/null @@ -1,7 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const remove = base.keys({ - type_id: Joi.string().only('remove').required(), - source_field: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/rename/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/rename/kibana_to_es_converter.js deleted file mode 100644 index 20ee7de0817b2..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/rename/kibana_to_es_converter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function rename(processorApiDocument) { - return { - rename: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - target_field: processorApiDocument.target_field - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/rename/schema.js b/src/core_plugins/kibana/server/lib/processors/rename/schema.js deleted file mode 100644 index b3118501d8623..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/rename/schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const rename = base.keys({ - type_id: Joi.string().only('rename').required(), - source_field: Joi.string().allow(''), - target_field: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/schemas.js b/src/core_plugins/kibana/server/lib/processors/schemas.js deleted file mode 100644 index bcc0dbe8bd99b..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/schemas.js +++ /dev/null @@ -1,14 +0,0 @@ -export { append } from './append/schema'; -export { convert } from './convert/schema'; -export { date } from './date/schema'; -export { geoip } from './geoip/schema'; -export { grok } from './grok/schema'; -export { gsub } from './gsub/schema'; -export { join } from './join/schema'; -export { lowercase } from './lowercase/schema'; -export { remove } from './remove/schema'; -export { rename } from './rename/schema'; -export { set } from './set/schema'; -export { split } from './split/schema'; -export { trim } from './trim/schema'; -export { uppercase } from './uppercase/schema'; diff --git a/src/core_plugins/kibana/server/lib/processors/set/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/set/kibana_to_es_converter.js deleted file mode 100644 index b0050b8ee1cfb..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/set/kibana_to_es_converter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function set(processorApiDocument) { - return { - set: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.target_field, - value: processorApiDocument.value - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/set/schema.js b/src/core_plugins/kibana/server/lib/processors/set/schema.js deleted file mode 100644 index a417bcad1bf96..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/set/schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const set = base.keys({ - type_id: Joi.string().only('set').required(), - target_field: Joi.string().allow(''), - value: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/split/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/split/kibana_to_es_converter.js deleted file mode 100644 index 76af3b077f5f0..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/split/kibana_to_es_converter.js +++ /dev/null @@ -1,9 +0,0 @@ -export default function split(processorApiDocument) { - return { - split: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field, - separator: processorApiDocument.separator - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/split/schema.js b/src/core_plugins/kibana/server/lib/processors/split/schema.js deleted file mode 100644 index 890a22b038701..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/split/schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const split = base.keys({ - type_id: Joi.string().only('split').required(), - source_field: Joi.string().allow(''), - separator: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/trim/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/trim/kibana_to_es_converter.js deleted file mode 100644 index 0c36010742e4a..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/trim/kibana_to_es_converter.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function trim(processorApiDocument) { - return { - trim: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/trim/schema.js b/src/core_plugins/kibana/server/lib/processors/trim/schema.js deleted file mode 100644 index c4d4da4195607..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/trim/schema.js +++ /dev/null @@ -1,7 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const trim = base.keys({ - type_id: Joi.string().only('trim').required(), - source_field: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/processors/uppercase/kibana_to_es_converter.js b/src/core_plugins/kibana/server/lib/processors/uppercase/kibana_to_es_converter.js deleted file mode 100644 index 9f7aab663f968..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/uppercase/kibana_to_es_converter.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function uppercase(processorApiDocument) { - return { - uppercase: { - tag: processorApiDocument.processor_id, - field: processorApiDocument.source_field - } - }; -} diff --git a/src/core_plugins/kibana/server/lib/processors/uppercase/schema.js b/src/core_plugins/kibana/server/lib/processors/uppercase/schema.js deleted file mode 100644 index d42a1bf22825d..0000000000000 --- a/src/core_plugins/kibana/server/lib/processors/uppercase/schema.js +++ /dev/null @@ -1,7 +0,0 @@ -import Joi from 'joi'; -import { base } from '../base/schema'; - -export const uppercase = base.keys({ - type_id: Joi.string().only('uppercase').required(), - source_field: Joi.string().allow('') -}); diff --git a/src/core_plugins/kibana/server/lib/schemas/resources/pipeline_schema.js b/src/core_plugins/kibana/server/lib/schemas/resources/pipeline_schema.js deleted file mode 100644 index e2396b1906db2..0000000000000 --- a/src/core_plugins/kibana/server/lib/schemas/resources/pipeline_schema.js +++ /dev/null @@ -1,5 +0,0 @@ -import _ from 'lodash'; -import Joi from 'joi'; -import * as ingestProcessorSchemas from '../../processors/schemas'; - -module.exports = Joi.array().items(_.values(ingestProcessorSchemas)); diff --git a/src/core_plugins/kibana/server/lib/schemas/simulate_request_schema.js b/src/core_plugins/kibana/server/lib/schemas/simulate_request_schema.js deleted file mode 100644 index ffea0c1c95511..0000000000000 --- a/src/core_plugins/kibana/server/lib/schemas/simulate_request_schema.js +++ /dev/null @@ -1,8 +0,0 @@ -import Joi from 'joi'; -import * as ingestProcessorSchemas from '../processors/schemas'; -import _ from 'lodash'; - -export default Joi.object({ - processors: Joi.array().items(_.values(ingestProcessorSchemas)).required().min(1), - input: Joi.object().required() -}); diff --git a/src/core_plugins/kibana/server/routes/api/ingest/index.js b/src/core_plugins/kibana/server/routes/api/ingest/index.js index c8371a4541bda..68e92a3567a9a 100644 --- a/src/core_plugins/kibana/server/routes/api/ingest/index.js +++ b/src/core_plugins/kibana/server/routes/api/ingest/index.js @@ -1,9 +1,5 @@ -import { registerProcessors } from './register_processors'; -import { registerSimulate } from './register_simulate'; import { registerFieldCapabilities } from './register_field_capabilities'; export default function (server) { - registerProcessors(server); - registerSimulate(server); registerFieldCapabilities(server); } diff --git a/src/core_plugins/kibana/server/routes/api/ingest/register_processors.js b/src/core_plugins/kibana/server/routes/api/ingest/register_processors.js deleted file mode 100644 index 399e33ea1a852..0000000000000 --- a/src/core_plugins/kibana/server/routes/api/ingest/register_processors.js +++ /dev/null @@ -1,24 +0,0 @@ -import _ from 'lodash'; -import handleESError from '../../../lib/handle_es_error'; -import handleResponse from '../../../lib/process_es_ingest_processors_response'; -import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../../common/lib/case_conversion'; - -export function registerProcessors(server) { - server.route({ - path: '/api/kibana/ingest/processors', - method: 'GET', - handler: function (request, reply) { - const boundCallWithRequest = _.partial(server.plugins.elasticsearch.callWithRequest, request); - - return boundCallWithRequest('transport.request', { - path: '/_nodes/ingest', - method: 'GET' - }) - .then(handleResponse) - .then(reply) - .catch((error) => { - reply(handleESError(error)); - }); - } - }); -}; diff --git a/src/core_plugins/kibana/server/routes/api/ingest/register_simulate.js b/src/core_plugins/kibana/server/routes/api/ingest/register_simulate.js deleted file mode 100644 index 5370534884666..0000000000000 --- a/src/core_plugins/kibana/server/routes/api/ingest/register_simulate.js +++ /dev/null @@ -1,37 +0,0 @@ -import _ from 'lodash'; -import handleESError from '../../../lib/handle_es_error'; -import handleResponse from '../../../lib/process_es_ingest_simulate_response'; -import handleError from '../../../lib/process_es_ingest_simulate_error'; -import simulateRequestSchema from '../../../lib/schemas/simulate_request_schema'; -import ingestSimulateApiKibanaToEsConverter from '../../../lib/converters/ingest_simulate_api_kibana_to_es_converter'; -import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from '../../../../common/lib/case_conversion'; - -export function registerSimulate(server) { - server.route({ - path: '/api/kibana/ingest/simulate', - method: 'POST', - config: { - validate: { - payload: simulateRequestSchema - } - }, - handler: function (request, reply) { - const boundCallWithRequest = _.partial(server.plugins.elasticsearch.callWithRequest, request); - const simulateApiDocument = request.payload; - const body = ingestSimulateApiKibanaToEsConverter(simulateApiDocument); - - return boundCallWithRequest('transport.request', { - path: '/_ingest/pipeline/_simulate', - query: { verbose: true }, - method: 'POST', - body: body - }) - .then(handleResponse, handleError) - .then((processors) => _.map(processors, keysToSnakeCaseShallow)) - .then(reply) - .catch((error) => { - reply(handleESError(error)); - }); - } - }); -}; diff --git a/test/unit/api/ingest/_processors.js b/test/unit/api/ingest/_processors.js deleted file mode 100644 index ecb0c43d7b688..0000000000000 --- a/test/unit/api/ingest/_processors.js +++ /dev/null @@ -1,19 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - return function (bdd, scenarioManager, request) { - bdd.describe('processors', () => { - - bdd.it('should return 200 for a successful run', function () { - return request.get('/kibana/ingest/processors') - .expect(200) - .then((response) => { - expect(_.isArray(response.body)).to.be(true); - }); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/_simulate.js b/test/unit/api/ingest/_simulate.js deleted file mode 100644 index 7b8daf3c0fe7c..0000000000000 --- a/test/unit/api/ingest/_simulate.js +++ /dev/null @@ -1,157 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var createTestData = require('intern/dojo/node!../../../unit/api/ingest/data'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'set', - target_field: 'foo', - value: 'bar' - }], - input: {} - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate', function simulatePipeline() { - - bdd.it('should return 400 for an invalid payload', function invalidPayload() { - - return Promise.all([ - request.post('/kibana/ingest/simulate').expect(400), - - request.post('/kibana/ingest/simulate') - .send({}) - .expect(400), - - // requires at least one processor - request.post('/kibana/ingest/simulate') - .send({input: {}, processors: []}) - .expect(400), - - // All processors must have a processorId property and a typeId property - request.post('/kibana/ingest/simulate') - .send({input: {}, processors: [{}]}) - .expect(400), - - request.post('/kibana/ingest/simulate') - .send({input: {}, processors: ['foo']}) - .expect(400), - - request.post('/kibana/ingest/simulate') - .send({input: {}, processors: 'foo'}) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a successful run', function () { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.describe('compilation errors', function simulatePipeline() { - const pipeline = { - input: { foo: '[message]' }, - processors: [ - { - processor_id: 'processor1', - type_id: 'set', - target_field: 'foo', - value: 'bar' - }, - { - processor_id: 'processor2', - type_id: 'gsub', - source_field: 'foo', - pattern: '[', - replacement: '<' - }, - { - processor_id: 'processor3', - type_id: 'set', - target_field: 'bar', - value: 'baz' - } - ] - }; - - bdd.it('should return a 200 for a compile error caused by a processor', function () { - request.post('/kibana/ingest/simulate') - .send(pipeline) - .expect(200) - .then((response) => { - expect(response.body[0].processor_id).to.be('processor2'); - expect(response.body[0].error.compile).to.be(true); - }); - }); - - bdd.it('should only return a result for the processor that threw the error', function () { - request.post('/kibana/ingest/simulate') - .send(pipeline) - .expect(200) - .then((response) => { - expect(response.body[0].processor_id).to.be('processor2'); - expect(response.body[0].error.compile).to.be(true); - expect(response.body.length).to.be(1); - }); - }); - }); - - bdd.describe('data errors', function simulatePipeline() { - const pipeline = { - input: { foo: '[message]' }, - processors: [ - { - processor_id: 'processor1', - type_id: 'set', - target_field: 'foo', - value: 'bar' - }, - { - processor_id: 'processor2', - type_id: 'gsub', - source_field: '', //invalid source field - pattern: '\\[', - replacement: '<' - }, - { - processor_id: 'processor3', - type_id: 'set', - target_field: 'bar', - value: 'baz' - } - ] - }; - - bdd.it('should return 200 with non-compile error object for a processor with an invalid source_field', () => { - return Promise.all([ - request.post('/kibana/ingest/simulate') - .send(pipeline) - .expect(200) - .then((response) => { - expect(response.body[0].error).to.be(undefined); - expect(response.body[1].error.compile).to.be(false); - expect(response.body[1].processor_id).to.be('processor2'); - }) - ]); - }); - - bdd.it('should return results up to and including the erroring processor', () => { - return Promise.all([ - request.post('/kibana/ingest/simulate') - .send(pipeline) - .expect(200) - .then((response) => { - expect(response.body.length).to.be(2); - }) - ]); - }); - - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/index.js b/test/unit/api/ingest/index.js index b5f09ecc01aa6..4d9efe55be550 100644 --- a/test/unit/api/ingest/index.js +++ b/test/unit/api/ingest/index.js @@ -6,9 +6,6 @@ define(function (require) { var url = require('intern/dojo/node!url'); var _ = require('intern/dojo/node!lodash'); var expect = require('intern/dojo/node!expect.js'); - var simulate = require('./_simulate'); - var processors = require('./_processors'); - var processorTypes = require('./processors/index'); var fieldCapabilities = require('./_field_capabilities'); bdd.describe('ingest API', function () { @@ -23,9 +20,6 @@ define(function (require) { return scenarioManager.unload('emptyKibana'); }); - simulate(bdd, scenarioManager, request); - processors(bdd, scenarioManager, request); - processorTypes(bdd, scenarioManager, request); fieldCapabilities(bdd, scenarioManager, request); }); }); diff --git a/test/unit/api/ingest/processors/_append.js b/test/unit/api/ingest/processors/_append.js deleted file mode 100644 index 238cac6b9ee24..0000000000000 --- a/test/unit/api/ingest/processors/_append.js +++ /dev/null @@ -1,67 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'append', - target_field: 'foo', - values: [ 'value1', 'value2' ] - }], - input: {} - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - append processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // Append processor requires targetField property - request.post('/kibana/ingest/simulate') - .send({ - input: {}, - processors: [{ - processor_id: 'processor1', - type_id: 'append', - values: [ 'value1', 'value2' ], - target_field: 42 - }] - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output.foo).to.be.eql([ 'value1', 'value2' ]); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'append', - targetField: 'foo', - value: [ 'value1', 'value2' ] - }], - input: {} - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_convert.js b/test/unit/api/ingest/processors/_convert.js deleted file mode 100644 index 38a07d3507766..0000000000000 --- a/test/unit/api/ingest/processors/_convert.js +++ /dev/null @@ -1,70 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'convert', - source_field: 'foo', - target_field: 'foo', - type: 'auto' - }], - input: { foo: '1234' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - convert processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // Convert processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - input: {}, - processors: [{ - processor_id: 'processor1', - type_id: 'convert', - value: 'auto', - source_field: 42, - target_field: 'foo' - }] - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', function () { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output.foo).to.be(1234); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'convert', - sourceField: 'foo', - targetField: 'foo', - type: 'string' - }], - input: {} - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_date.js b/test/unit/api/ingest/processors/_date.js deleted file mode 100644 index a3e018e4bd885..0000000000000 --- a/test/unit/api/ingest/processors/_date.js +++ /dev/null @@ -1,89 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - var moment = require('intern/dojo/node!moment'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'date', - source_field: 'dob', - target_field: 'dob', - formats: ['Custom'], - timezone: 'Etc/UTC', - locale: 'ENGLISH', - custom_format: 'MM/dd/yyyy' - }], - input: { dob: '07/05/1979' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - date processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // Date processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - input: { dob: '07/05/1979' }, - processors: [{ - processor_id: 'processor1', - type_id: 'date', - source_field: 42, - target_field: 'dob', - formats: 'Custom', - timezone: 'Etc/UTC', - locale: 'ENGLISH', - custom_format: 'MM/dd/yyyy' - }] - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', function () { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output.dob).to.be('1979-07-05T00:00:00.000Z'); - }); - }); - - bdd.it('should return a date in ISO 8601 format', function () { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(moment(response.body[0].output.dob, moment.ISO_8601).isValid()).to.be(true); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'date', - sourceField: 'dob', - targetField: 'dob', - formats: ['Custom'], - timezone: 'Etc/UTC', - locale: 'ENGLISH', - customFormat: 'MM/dd/yyyy' - }], - input: { dob: '07/05/1979' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_geoip.js b/test/unit/api/ingest/processors/_geoip.js deleted file mode 100644 index 452c3e5d8416c..0000000000000 --- a/test/unit/api/ingest/processors/_geoip.js +++ /dev/null @@ -1,71 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'geoip', - source_field: 'ip', - target_field: 'geoip' - }], - input: { ip: '74.125.21.103' } - }; - - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - geoip processor', () => { - //TODO: These tests can be re-added when we address - // installing plugins for integration tests - // https://github.com/elastic/kibana/issues/6852 - - // bdd.it('should return 400 for an invalid payload', () => { - // return Promise.all([ - // // Geo IP processor requires source_field property - // request.post('/kibana/ingest/simulate') - // .send({ - // input: { ip: '74.125.21.103' }, - // processors: [{ - // processor_id: 'processor1', - // type_id: 'geoip', - // source_field: 42, - // target_field: 'geoip' - // }] - // }) - // .expect(400) - // ]); - // }); - - // bdd.it('should return 200 for a valid simulate request', () => { - // return request.post('/kibana/ingest/simulate') - // .send(testPipeline) - // .expect(200); - // }); - - // bdd.it('should return a simulated output with the correct result for the given processor', () => { - // return request.post('/kibana/ingest/simulate') - // .send(testPipeline) - // .expect(200) - // .then(function (response) { - // expect(response.body[0].output.geoip.city_name).to.be('Mountain View'); - // }); - // }); - - // bdd.it('should enforce snake case', () => { - // return request.post('/kibana/ingest/simulate') - // .send({ - // processors: [{ - // processorId: 'processor1', - // typeId: 'geoip', - // sourceField: 'ip', - // targetField: 'geoip' - // }], - // input: { ip: '74.125.21.103' } - // }) - // .expect(400); - // }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_grok.js b/test/unit/api/ingest/processors/_grok.js deleted file mode 100644 index b1ee7d4e2de25..0000000000000 --- a/test/unit/api/ingest/processors/_grok.js +++ /dev/null @@ -1,72 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'grok', - source_field: 'foo', - pattern: '%{GREEDYDATA:bar} - %{GREEDYDATA:baz}' - }], - input: { foo: 'value1 - value2' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - grok processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // Grok processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - input: {}, - processors: [{ - processor_id: 'processor1', - type_id: 'grok', - source_field: 123, - pattern: '%{GREEDYDATA:bar} - %{GREEDYDATA:baz}' - }], - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', function () { - const expected = { - foo: 'value1 - value2', - bar: 'value1', - baz: 'value2' - }; - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output).to.eql(expected); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'grok', - sourceField: 'foo', - pattern: '%{GREEDYDATA:bar} - %{GREEDYDATA:baz}' - }], - input: { foo: 'value1 - value2' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_gsub.js b/test/unit/api/ingest/processors/_gsub.js deleted file mode 100644 index 2d97067a911f1..0000000000000 --- a/test/unit/api/ingest/processors/_gsub.js +++ /dev/null @@ -1,70 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'gsub', - source_field: 'foo', - pattern: 'bar', - replacement: 'baz' - }], - input: { foo: 'bar' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - gsub processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // GSub processor requires targetField property - request.post('/kibana/ingest/simulate') - .send({ - input: { foo: 'bar' }, - processors: [{ - processor_id: 'processor1', - type_id: 'gsub', - source_field: 42, - pattern: 'bar', - replacement: 'baz' - }] - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then((response) => { - expect(response.body[0].output.foo).to.be.equal('baz'); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'gsub', - sourceField: 'foo', - pattern: 'bar', - replacement: 'baz' - }], - input: { foo: 'bar' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_join.js b/test/unit/api/ingest/processors/_join.js deleted file mode 100644 index 7e98650fc47c1..0000000000000 --- a/test/unit/api/ingest/processors/_join.js +++ /dev/null @@ -1,67 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'join', - source_field: 'foo', - separator: ' ' - }], - input: { foo: ['value1', 'value2'] } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - join processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'join', - source_field: 1234, - separator: ' ' - }], - input: { foo: ['value1', 'value2'] } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', function () { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output.foo).to.be('value1 value2'); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'join', - sourceField: 'foo', - separator: ' ' - }], - input: { foo: ['value1', 'value2'] } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_lowercase.js b/test/unit/api/ingest/processors/_lowercase.js deleted file mode 100644 index 747dfb003b6c6..0000000000000 --- a/test/unit/api/ingest/processors/_lowercase.js +++ /dev/null @@ -1,64 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'lowercase', - source_field: 'foo' - }], - input: { foo: 'I am Mixed Case' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - lowercase processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'lowercase', - source_field: 1234 - }], - input: { foo: 'I am Mixed Case' } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output.foo).to.be('i am mixed case'); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'lowercase', - sourceField: 'foo' - }], - input: { foo: 'I am Mixed Case' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_remove.js b/test/unit/api/ingest/processors/_remove.js deleted file mode 100644 index f454b79ff936b..0000000000000 --- a/test/unit/api/ingest/processors/_remove.js +++ /dev/null @@ -1,64 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'remove', - source_field: 'foo' - }], - input: { foo: 'value1', bar: 'value2' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - remove processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'remove', - source_field: 1234 - }], - input: { foo: 'value1', bar: 'value2' } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output).to.eql({ bar: 'value2' }); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'remove', - sourceField: 'foo' - }], - input: { foo: 'value1', bar: 'value2' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_rename.js b/test/unit/api/ingest/processors/_rename.js deleted file mode 100644 index 3370ba6f48b45..0000000000000 --- a/test/unit/api/ingest/processors/_rename.js +++ /dev/null @@ -1,67 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'rename', - source_field: 'foo', - target_field: 'bar' - }], - input: { foo: 'value1' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - rename processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'rename', - source_field: 1234, - target_field: 'bar' - }], - input: { foo: 'value1' } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output).to.eql({ bar: 'value1' }); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'rename', - sourceField: 'foo', - targetField: 'bar' - }], - input: { foo: 'value1' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_set.js b/test/unit/api/ingest/processors/_set.js deleted file mode 100644 index 395a29a80d5b2..0000000000000 --- a/test/unit/api/ingest/processors/_set.js +++ /dev/null @@ -1,67 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'set', - target_field: 'foo', - value: 'bar' - }], - input: {} - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - set processor', function simulatePipeline() { - - bdd.it('should return 400 for an invalid payload', function invalidPayload() { - return Promise.all([ - // Set processor requires targetField property - request.post('/kibana/ingest/simulate') - .send({ - input: {}, - processors: [{ - processor_id: 'processor1', - type_id: 'set', - value: 'bar', - target_field: 42 - }] - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', function validSetSimulate() { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', function () { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output.foo).to.be.equal('bar'); - }); - }); - - bdd.it('should enforce snake case', function setSimulateSnakeCase() { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'set', - targetField: 'foo', - value: 'bar' - }], - input: {} - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_split.js b/test/unit/api/ingest/processors/_split.js deleted file mode 100644 index 9e6900b0f822e..0000000000000 --- a/test/unit/api/ingest/processors/_split.js +++ /dev/null @@ -1,67 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'split', - source_field: 'foo', - separator: ',' - }], - input: { foo: 'foo,bar,baz' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - split processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'split', - source_field: 1234, - separator: ',' - }], - input: { foo: 'foo,bar,baz' } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output).to.eql({ foo: ['foo', 'bar', 'baz'] }); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'split', - sourceField: 'foo', - separator: ',' - }], - input: { foo: 'foo,bar,baz' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_trim.js b/test/unit/api/ingest/processors/_trim.js deleted file mode 100644 index 1f08d5c1eb167..0000000000000 --- a/test/unit/api/ingest/processors/_trim.js +++ /dev/null @@ -1,64 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'trim', - source_field: 'foo' - }], - input: { foo: ' bar baz ' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - trim processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'trim', - source_field: 1234 - }], - input: { foo: ' bar baz ' } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output).to.eql({ foo: 'bar baz' }); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'trim', - sourceField: 'foo' - }], - input: { foo: ' bar baz ' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/_uppercase.js b/test/unit/api/ingest/processors/_uppercase.js deleted file mode 100644 index c2cd5cf1b7256..0000000000000 --- a/test/unit/api/ingest/processors/_uppercase.js +++ /dev/null @@ -1,64 +0,0 @@ -define(function (require) { - var Promise = require('bluebird'); - var _ = require('intern/dojo/node!lodash'); - var expect = require('intern/dojo/node!expect.js'); - - const testPipeline = { - processors: [{ - processor_id: 'processor1', - type_id: 'uppercase', - source_field: 'foo' - }], - input: { foo: 'bar baz' } - }; - - return function (bdd, scenarioManager, request) { - bdd.describe('simulate - uppercase processor', () => { - - bdd.it('should return 400 for an invalid payload', () => { - return Promise.all([ - // processor requires source_field property - request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processor_id: 'processor1', - type_id: 'uppercase', - source_field: 1234 - }], - input: { foo: 'bar baz' } - }) - .expect(400) - ]); - }); - - bdd.it('should return 200 for a valid simulate request', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200); - }); - - bdd.it('should return a simulated output with the correct result for the given processor', () => { - return request.post('/kibana/ingest/simulate') - .send(testPipeline) - .expect(200) - .then(function (response) { - expect(response.body[0].output).to.eql({ foo: 'BAR BAZ' }); - }); - }); - - bdd.it('should enforce snake case', () => { - return request.post('/kibana/ingest/simulate') - .send({ - processors: [{ - processorId: 'processor1', - typeId: 'uppercase', - sourceField: 'foo' - }], - input: { foo: 'bar baz' } - }) - .expect(400); - }); - - }); - }; -}); diff --git a/test/unit/api/ingest/processors/index.js b/test/unit/api/ingest/processors/index.js deleted file mode 100644 index 3420996767864..0000000000000 --- a/test/unit/api/ingest/processors/index.js +++ /dev/null @@ -1,34 +0,0 @@ -define(function (require) { - var append = require('./_append'); - var convert = require('./_convert'); - var date = require('./_date'); - var geoip = require('./_geoip'); - var grok = require('./_grok'); - var gsub = require('./_gsub'); - var join = require('./_join'); - var lowercase = require('./_lowercase'); - var remove = require('./_remove'); - var rename = require('./_rename'); - var set = require('./_set'); - var split = require('./_split'); - var trim = require('./_trim'); - var uppercase = require('./_uppercase'); - - return function processors(bdd, scenarioManager, request) { - append(bdd, scenarioManager, request); - convert(bdd, scenarioManager, request); - date(bdd, scenarioManager, request); - geoip(bdd, scenarioManager, request); - grok(bdd, scenarioManager, request); - gsub(bdd, scenarioManager, request); - join(bdd, scenarioManager, request); - lowercase(bdd, scenarioManager, request); - remove(bdd, scenarioManager, request); - rename(bdd, scenarioManager, request); - set(bdd, scenarioManager, request); - split(bdd, scenarioManager, request); - trim(bdd, scenarioManager, request); - uppercase(bdd, scenarioManager, request); - }; - -}); From b20f996601cbc83902310c27d5a87a6d93612ddf Mon Sep 17 00:00:00 2001 From: Lukas Olson Date: Fri, 9 Dec 2016 15:41:33 -0700 Subject: [PATCH 12/17] Add time navigation buttons (#9253) * Add time navigation buttons * changing vars to const * Address some review points * fixing typo in function name * Remove unused variables * [timepicker] Simplify zoom in/out and forward/back and remove getter that had side effects * [timepicker] Move time navigation calculations to own class and write test * [timepicker] Remove unused styling and classes * [timepicker] Change from i to span, add more explanatory comments * [timepicker] Remove unused variable * Remove zoom in/out buttons from timepicker nav * Change step forward/back timepicker nav button icons --- src/ui/public/styles/base.less | 17 -------- .../timepicker/__tests__/time_navigation.js | 38 ++++++++++++++++++ .../timepicker/kbn_global_timepicker.html | 14 +++++-- .../timepicker/kbn_global_timepicker.js | 18 ++++++--- src/ui/public/timepicker/time_navigation.js | 39 +++++++++++++++++++ 5 files changed, 101 insertions(+), 25 deletions(-) create mode 100644 src/ui/public/timepicker/__tests__/time_navigation.js create mode 100644 src/ui/public/timepicker/time_navigation.js diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less index 942f51838b037..f960e328f9f07 100644 --- a/src/ui/public/styles/base.less +++ b/src/ui/public/styles/base.less @@ -200,23 +200,6 @@ a { .button-variant(@navbar-default-color; @navbar-default-bg; @navbar-default-border); } -.navbar-timepicker { - > li > a { - padding-left: 7px !important; - padding-right: 7px !important; - } - - .fa { - font-size: 16px; - vertical-align: middle; - } - - button { - background-color: transparent; - border-radius: 0; - } -} - .navbar-timepicker-time-desc > .fa-clock-o { padding-right: 5px; } diff --git a/src/ui/public/timepicker/__tests__/time_navigation.js b/src/ui/public/timepicker/__tests__/time_navigation.js new file mode 100644 index 0000000000000..6cebf5e182580 --- /dev/null +++ b/src/ui/public/timepicker/__tests__/time_navigation.js @@ -0,0 +1,38 @@ +import expect from 'expect.js'; +import moment from 'moment'; +import timeNavigation from '../time_navigation'; + +describe('timeNavigation', () => { + let bounds; + + beforeEach(() => { + bounds = { + min: moment('2016-01-01T00:00:00.000Z'), + max: moment('2016-01-01T00:15:00.000Z') + }; + }); + + it('should step forward by the amount of the duration', () => { + const {from, to} = timeNavigation.stepForward(bounds); + expect(from).to.be('2016-01-01T00:15:00.000Z'); + expect(to).to.be('2016-01-01T00:30:00.000Z'); + }); + + it('should step backward by the amount of the duration', () => { + const {from, to} = timeNavigation.stepBackward(bounds); + expect(from).to.be('2015-12-31T23:45:00.000Z'); + expect(to).to.be('2016-01-01T00:00:00.000Z'); + }); + + it('should zoom out to be double the original duration', () => { + const {from, to} = timeNavigation.zoomOut(bounds); + expect(from).to.be('2015-12-31T23:52:30.000Z'); + expect(to).to.be('2016-01-01T00:22:30.000Z'); + }); + + it('should zoom in to be half the original duration', () => { + const {from, to} = timeNavigation.zoomIn(bounds); + expect(from).to.be('2016-01-01T00:03:45.000Z'); + expect(to).to.be('2016-01-01T00:11:15.000Z'); + }); +}); diff --git a/src/ui/public/timepicker/kbn_global_timepicker.html b/src/ui/public/timepicker/kbn_global_timepicker.html index 3e280b3b2c743..b7d8b684d9000 100644 --- a/src/ui/public/timepicker/kbn_global_timepicker.html +++ b/src/ui/public/timepicker/kbn_global_timepicker.html @@ -4,7 +4,7 @@ ng-click="toggleRefresh()" ng-show="timefilter.refreshInterval.value > 0" > - +
    - Auto-refresh + Auto-refresh {{timefilter.refreshInterval.display}}
    +
    + +
    + + +
    + +
    diff --git a/src/ui/public/timepicker/kbn_global_timepicker.js b/src/ui/public/timepicker/kbn_global_timepicker.js index 0656f3258e20d..7b6b62173095f 100644 --- a/src/ui/public/timepicker/kbn_global_timepicker.js +++ b/src/ui/public/timepicker/kbn_global_timepicker.js @@ -1,15 +1,15 @@ import moment from 'moment'; import UiModules from 'ui/modules'; -import chromeNavControlsRegistry from 'ui/registry/chrome_nav_controls'; -import { once, clone } from 'lodash'; +import { once, clone, assign } from 'lodash'; import toggleHtml from './kbn_global_timepicker.html'; +import timeNavigation from './time_navigation'; UiModules .get('kibana') -.directive('kbnGlobalTimepicker', (timefilter, globalState, $rootScope, config) => { +.directive('kbnGlobalTimepicker', (timefilter, globalState, $rootScope) => { const listenForUpdates = once($scope => { - $scope.$listen(timefilter, 'update', (newVal, oldVal) => { + $scope.$listen(timefilter, 'update', () => { globalState.time = clone(timefilter.time); globalState.refreshInterval = clone(timefilter.refreshInterval); globalState.save(); @@ -19,13 +19,21 @@ UiModules return { template: toggleHtml, replace: true, - link: ($scope, $el, attrs) => { + link: ($scope) => { listenForUpdates($rootScope); $rootScope.timefilter = timefilter; $rootScope.toggleRefresh = () => { timefilter.refreshInterval.pause = !timefilter.refreshInterval.pause; }; + + $scope.forward = function () { + assign(timefilter.time, timeNavigation.stepForward(timefilter.getBounds())); + }; + + $scope.back = function () { + assign(timefilter.time, timeNavigation.stepBackward(timefilter.getBounds())); + }; }, }; }); diff --git a/src/ui/public/timepicker/time_navigation.js b/src/ui/public/timepicker/time_navigation.js new file mode 100644 index 0000000000000..0e1b933989fc1 --- /dev/null +++ b/src/ui/public/timepicker/time_navigation.js @@ -0,0 +1,39 @@ +import moment from 'moment'; + +export default { + // travel forward in time based on the interval between from and to + stepForward({min, max}) { + const diff = max.diff(min); + return { + from: max.toISOString(), + to: moment(max).add(diff).toISOString() + }; + }, + + // travel backwards in time based on the interval between from and to + stepBackward({min, max}) { + const diff = max.diff(min); + return { + from: moment(min).subtract(diff).toISOString(), + to: min.toISOString() + }; + }, + + // zoom out, doubling the difference between start and end, keeping the same time range center + zoomOut({min, max}) { + const diff = max.diff(min); + return { + from: moment(min).subtract(diff / 2).toISOString(), + to: moment(max).add(diff / 2).toISOString() + }; + }, + + // zoom in, halving the difference between start and end, keeping the same time range center + zoomIn({min, max}) { + const diff = max.diff(min); + return { + from: moment(min).add(diff / 4).toISOString(), + to: moment(max).subtract(diff / 4).toISOString() + }; + } +}; From 10c21360108f7f152742369650631c193e45d551 Mon Sep 17 00:00:00 2001 From: Megan Walker Date: Sat, 10 Dec 2016 01:00:54 +0000 Subject: [PATCH 13/17] server onRequest handler no longer requires a socket in the request (#9332) Per #9302 A request sent through a HapiJS .inject() doesn't have a socket associated with the request, which causes a failure. --- src/server/http/setup_connection.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/server/http/setup_connection.js b/src/server/http/setup_connection.js index cd922c7ca9366..736d38678972d 100644 --- a/src/server/http/setup_connection.js +++ b/src/server/http/setup_connection.js @@ -48,7 +48,9 @@ export default function (kbnServer, server, config) { }); server.ext('onRequest', function (req, reply) { - if (req.raw.req.socket.encrypted) { + // A request sent through a HapiJS .inject() doesn't have a socket associated with the request + // which causes a failure. + if (!req.raw.req.socket || req.raw.req.socket.encrypted) { reply.continue(); } else { reply.redirect(formatUrl({ From 5864f2b0935bd7f5b86c647005858a8b406573e8 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Sat, 10 Dec 2016 10:00:27 +0100 Subject: [PATCH 14/17] pie chart unhandled error fix (#9420) --- src/ui/public/vislib/lib/dispatch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/public/vislib/lib/dispatch.js b/src/ui/public/vislib/lib/dispatch.js index 555899713358d..f426b0ed5f99c 100644 --- a/src/ui/public/vislib/lib/dispatch.js +++ b/src/ui/public/vislib/lib/dispatch.js @@ -40,7 +40,7 @@ export default function DispatchClass(Private, config) { const slices = isSlices ? data.slices : undefined; const handler = this.handler; const color = _.get(handler, 'data.color'); - const isPercentage = (handler && handler.visConfig.get('mode') === 'percentage'); + const isPercentage = (handler && handler.visConfig.get('mode', 'normal') === 'percentage'); const eventData = { value: d.y, From 0c736724b0ff7aff59d6029f90e73ae55791e8bf Mon Sep 17 00:00:00 2001 From: Spencer Date: Mon, 12 Dec 2016 13:44:18 -0700 Subject: [PATCH 15/17] Upgrade eslint (#9357) * upgrade eslint, all related deps, and config files * replace gruntify-eslint with basic eslint-cli wrapper * arrow-IIFEs must be invoked outside of the parens * move import statements before their use * reindent to satisfy new indentation check algorithm * place missing semicolon * ignore copy-pasted decode geohash code * [grunt/eslint] fix argument spacing * [gurnt/eslint] add comment about contents of report * [grunt/tasks] use `export default` --- .eslintignore | 10 ++- .eslintrc | 6 ++ Gruntfile.js | 12 ---- package.json | 9 ++- .../cluster/__tests__/_mock_cluster_fork.js | 4 +- src/cli/cluster/cluster_manager.js | 23 +++---- src/core_plugins/console/.eslintrc | 60 ++++++++-------- src/core_plugins/timelion/.eslintrc | 68 ------------------- src/fixtures/mock_ui_state.js | 2 +- src/ui/ui_bundle_collection.js | 10 +-- tasks/config/eslint.js | 57 ++++++++-------- tasks/eslint.js | 44 ++++++++++++ tasks/lint_staged_files.js | 26 +++++-- test/functional/apps/visualize/_data_table.js | 5 +- 14 files changed, 164 insertions(+), 172 deletions(-) delete mode 100644 src/core_plugins/timelion/.eslintrc create mode 100644 tasks/eslint.js diff --git a/.eslintignore b/.eslintignore index 2482278de75cc..94357790433f7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,9 @@ +/optimize +/src/fixtures/vislib/mock_data +/src/ui/public/angular-bootstrap +/test/fixtures/scenarios +/src/core_plugins/console/public/webpackShims +/src/core_plugins/console/public/tests/webpackShims /src/core_plugins/timelion/bower_components /src/core_plugins/timelion/vendor_components -test/fixtures/scenarios -optimize -test/fixtures/scenarios +/src/ui/public/utils/decode_geo_hash.js diff --git a/.eslintrc b/.eslintrc index 7a623df06a6ca..1904222b3cfa9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,2 +1,8 @@ --- extends: '@elastic/kibana' +rules: + no-unused-vars: off + no-var: off + prefer-const: off + no-extra-semi: off + quotes: off diff --git a/Gruntfile.js b/Gruntfile.js index 6c025cd1b94a0..bd843b0eff635 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -38,18 +38,6 @@ module.exports = function (grunt) { ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= package.author.company %>;' + ' Licensed <%= package.license %> */\n' }, - - lintThese: [ - 'Gruntfile.js', - '<%= root %>/tasks/**/*.js', - '<%= root %>/test/**/*.js', - '<%= src %>/**/*.js', - '!<%= src %>/ui/public/angular-bootstrap/**/*.js', - '!<%= src %>/core_plugins/timelion/bower_components/**/*.js', - '!<%= src %>/core_plugins/timelion/vendor_components/**/*.js', - '!<%= src %>/fixtures/**/*.js', - '!<%= root %>/test/fixtures/scenarios/**/*.js' - ] }; grunt.config.merge(config); diff --git a/package.json b/package.json index 01ac395810b7d..bcfb871d0edf2 100644 --- a/package.json +++ b/package.json @@ -165,17 +165,17 @@ "wreck": "6.2.0" }, "devDependencies": { - "@elastic/eslint-config-kibana": "0.0.3", + "@elastic/eslint-config-kibana": "0.2.1", "angular-mocks": "1.4.7", "auto-release-sinon": "1.0.3", - "babel-eslint": "4.1.8", + "babel-eslint": "6.1.2", "chai": "3.5.0", "cheerio": "0.22.0", "chokidar": "1.6.0", "chromedriver": "2.24.1", "elasticdump": "2.1.1", - "eslint": "1.10.3", - "eslint-plugin-mocha": "1.1.0", + "eslint": "3.11.1", + "eslint-plugin-mocha": "4.7.0", "event-stream": "3.3.2", "expect.js": "0.3.1", "faker": "1.1.0", @@ -189,7 +189,6 @@ "grunt-karma": "2.0.0", "grunt-run": "0.6.0", "grunt-simple-mocha": "0.4.0", - "gruntify-eslint": "1.0.1", "gulp-sourcemaps": "1.7.3", "handlebars": "4.0.5", "husky": "0.8.1", diff --git a/src/cli/cluster/__tests__/_mock_cluster_fork.js b/src/cli/cluster/__tests__/_mock_cluster_fork.js index 2671ca08bdb9a..0915d172a4cd6 100644 --- a/src/cli/cluster/__tests__/_mock_cluster_fork.js +++ b/src/cli/cluster/__tests__/_mock_cluster_fork.js @@ -24,7 +24,7 @@ export default class MockClusterFork extends EventEmitter { dead = true; this.emit('exit'); cluster.emit('exit', this, this.exitCode || 0); - }()); + })(); }), }, isDead: sinon.spy(() => dead), @@ -39,6 +39,6 @@ export default class MockClusterFork extends EventEmitter { await wait(); dead = false; this.emit('online'); - }()); + })(); } } diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js index 80b183cd663a6..1282d17442b02 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.js @@ -86,19 +86,16 @@ module.exports = class ClusterManager { const chokidar = require('chokidar'); const fromRoot = require('../../utils/from_root'); - const watchPaths = uniq( - [ - fromRoot('src/core_plugins'), - fromRoot('src/server'), - fromRoot('src/ui'), - fromRoot('src/utils'), - fromRoot('config'), - ...extraPaths - ] - .map(path => resolve(path)) - ); - - this.watcher = chokidar.watch(watchPaths, { + const watchPaths = [ + fromRoot('src/core_plugins'), + fromRoot('src/server'), + fromRoot('src/ui'), + fromRoot('src/utils'), + fromRoot('config'), + ...extraPaths + ].map(path => resolve(path)); + + this.watcher = chokidar.watch(uniq(watchPaths), { cwd: fromRoot('.'), ignored: /[\\\/](\..*|node_modules|bower_components|public|__tests__)[\\\/]/ }); diff --git a/src/core_plugins/console/.eslintrc b/src/core_plugins/console/.eslintrc index c5fcfc4ff6fda..f539f6cd5878f 100644 --- a/src/core_plugins/console/.eslintrc +++ b/src/core_plugins/console/.eslintrc @@ -1,34 +1,36 @@ --- root: true -extends: '@elastic/kibana' +extends: '../../../.eslintrc' rules: - block-scoped-var: [0] - camelcase: [0] - curly: [0] - dot-location: [0] - dot-notation: [0] - eqeqeq: [0] - guard-for-in: [0] - indent: [0] - max-len: [0] - new-cap: [0] - no-caller: [0] - no-empty: [0] - no-extend-native: [0] - no-loop-func: [0] - no-multi-str: [0] - no-nested-ternary: [0] - no-proto: [0] - no-sequences: [0] - no-undef: [0] - no-use-before-define: [0] - one-var: [0] - quotes: [0] - space-before-blocks: [0] - space-in-parens: [0] - space-infix-ops: [0] - semi: [0] - strict: [0] - wrap-iife: [0] + block-scoped-var: off + camelcase: off + curly: off + dot-location: off + dot-notation: off + eqeqeq: off + guard-for-in: off + indent: off + max-len: off + new-cap: off + no-caller: off + no-empty: off + no-extend-native: off + no-loop-func: off + no-multi-str: off + no-nested-ternary: off + no-proto: off + no-sequences: off + no-undef: off + no-use-before-define: off + one-var: off + quotes: off + space-before-blocks: off + space-in-parens: off + space-infix-ops: off + semi: off + strict: off + wrap-iife: off + no-var: off + prefer-const: off diff --git a/src/core_plugins/timelion/.eslintrc b/src/core_plugins/timelion/.eslintrc deleted file mode 100644 index 78ad50cf6a489..0000000000000 --- a/src/core_plugins/timelion/.eslintrc +++ /dev/null @@ -1,68 +0,0 @@ ---- -parser: babel-eslint - -env: - es6: true - amd: true - node: true - browser: true - jasmine: true - -rules: - block-scoped-var: 2 - camelcase: [ 2, { properties: never } ] - comma-dangle: 0 - comma-style: [ 2, last ] - consistent-return: 0 - curly: [ 2, multi-line ] - dot-location: [ 2, property ] - dot-notation: [ 2, { allowKeywords: true } ] - eqeqeq: [ 2, allow-null ] - guard-for-in: 2 - indent: [ 2, 2, { SwitchCase: 1 } ] - key-spacing: [ 0, { align: value } ] - max-len: [ 2, 140, 2, { ignoreComments: true, ignoreUrls: true } ] - new-cap: [ 2, { capIsNewExceptions: [ Private ] } ] - no-bitwise: 0 - no-caller: 2 - no-cond-assign: 0 - no-debugger: 2 - no-empty: 2 - no-eval: 2 - no-extend-native: 2 - no-extra-parens: 0 - no-irregular-whitespace: 2 - no-iterator: 2 - no-loop-func: 2 - no-multi-spaces: 0 - no-multi-str: 2 - no-nested-ternary: 2 - no-new: 0 - no-path-concat: 0 - no-proto: 2 - no-return-assign: 0 - no-script-url: 2 - no-sequences: 2 - no-shadow: 0 - no-trailing-spaces: 2 - no-undef: 2 - no-underscore-dangle: 0 - no-unused-expressions: 0 - no-unused-vars: 0 - no-use-before-define: [ 2, nofunc ] - no-with: 2 - one-var: [ 2, never ] - quotes: [ 2, single ] - semi-spacing: [ 2, { before: false, after: true } ] - semi: [ 2, always ] - space-after-keywords: [ 2, always ] - space-before-blocks: [ 2, always ] - space-before-function-paren: [ 2, { anonymous: always, named: never } ] - space-in-parens: [ 2, never ] - space-infix-ops: [ 2, { int32Hint: false } ] - space-return-throw-case: [ 2 ] - space-unary-ops: [ 2 ] - strict: [ 2, never ] - valid-typeof: 2 - wrap-iife: [ 2, outside ] - yoda: 0 diff --git a/src/fixtures/mock_ui_state.js b/src/fixtures/mock_ui_state.js index 50d913ae337e8..5f5b90e38eb3d 100644 --- a/src/fixtures/mock_ui_state.js +++ b/src/fixtures/mock_ui_state.js @@ -10,4 +10,4 @@ export default { }, on: _.noop, off: _.noop -} +}; diff --git a/src/ui/ui_bundle_collection.js b/src/ui/ui_bundle_collection.js index 76cd173678d97..463f217a9c46e 100644 --- a/src/ui/ui_bundle_collection.js +++ b/src/ui/ui_bundle_collection.js @@ -1,8 +1,3 @@ -const rimraf = promisify(require('rimraf')); -const mkdirp = promisify(require('mkdirp')); -const unlink = promisify(require('fs').unlink); -const readdir = promisify(require('fs').readdir); - import UiBundle from './ui_bundle'; import appEntryTemplate from './app_entry_template'; import { readFileSync as readSync } from 'fs'; @@ -10,6 +5,11 @@ import { pull, transform, pluck } from 'lodash'; import { promisify } from 'bluebird'; import { makeRe } from 'minimatch'; +const rimraf = promisify(require('rimraf')); +const mkdirp = promisify(require('mkdirp')); +const unlink = promisify(require('fs').unlink); +const readdir = promisify(require('fs').readdir); + class UiBundleCollection { constructor(bundlerEnv, filter) { this.each = []; diff --git a/tasks/config/eslint.js b/tasks/config/eslint.js index dbc1cea416969..ba5fb91a02080 100644 --- a/tasks/config/eslint.js +++ b/tasks/config/eslint.js @@ -1,30 +1,33 @@ -var resolve = require('path').resolve; +import { resolve } from 'path'; +export default grunt => ({ + options: { + paths: [ + 'Gruntfile.js', + 'bin', + 'config', + 'src', + 'tasks', + 'test', + 'utilities', + ], + }, -module.exports = function (grunt) { - return { - // just lint the source dir - source: { - options: { - cache: resolve(grunt.config.get('root'), '.eslint.fixSource.cache') - }, + source: { + options: { + cache: resolve(grunt.config.get('root'), '.eslint.fixSource.cache') + } + }, - files: { - src: '<%= lintThese %>' - } - }, + fixSource: { + options: { + cache: resolve(grunt.config.get('root'), '.eslint.fixSource.cache'), + fix: true + } + }, - // lint the source and fix any fixable errors - fixSource: { - options: { - cache: resolve(grunt.config.get('root'), '.eslint.fixSource.cache'), - fix: true - }, - - files: { - src: '<%= lintThese %>' - } - }, - - staged: {} - }; -}; + staged: { + options: { + paths: null // overridden by lintStagedFiles task + } + } +}); diff --git a/tasks/eslint.js b/tasks/eslint.js new file mode 100644 index 0000000000000..4ee55056c2c6d --- /dev/null +++ b/tasks/eslint.js @@ -0,0 +1,44 @@ +import { CLIEngine } from 'eslint'; + +const OPTION_DEFAULTS = { + paths: null, + cache: null, + fix: false +}; + +export default grunt => { + grunt.registerMultiTask('eslint', function () { + const options = this.options(OPTION_DEFAULTS); + + if (!options.paths) { + grunt.fatal(new Error('No eslint.options.paths specified')); + return; + } + + const cli = new CLIEngine({ + cache: options.cache, + fix: options.fix, + cwd: grunt.config.get('root'), + }); + + // report includes an entire list of files checked and the + // fixes, errors, and warning for each. + const report = cli.executeOnFiles(options.paths); + + // output fixes to disk + if (options.fix) { + CLIEngine.outputFixes(report); + } + + // log the formatted linting report + const formatter = cli.getFormatter(); + + const errTypes = []; + if (report.errorCount > 0) errTypes.push('errors'); + if (report.warningCount > 0) errTypes.push('warning'); + if (!errTypes.length) return; + + grunt.log.write(formatter(report.results)); + grunt.fatal(`eslint ${errTypes.join(' & ')}`); + }); +}; diff --git a/tasks/lint_staged_files.js b/tasks/lint_staged_files.js index e7c2f95752d13..d8117b62c2772 100644 --- a/tasks/lint_staged_files.js +++ b/tasks/lint_staged_files.js @@ -1,5 +1,7 @@ import { resolve } from 'path'; import { isStaged, getFilename } from './utils/files_to_commit'; +import { CLIEngine } from 'eslint'; +import minimatch from 'minimatch'; const root = resolve(__dirname, '..'); @@ -7,18 +9,32 @@ export default function (grunt) { grunt.registerTask('lintStagedFiles', function () { grunt.task.requires('collectFilesToCommit'); - // match these patterns - var patterns = grunt.config.get('eslint.source.files.src'); - if (!patterns) grunt.fail.warn('eslint file pattern is not defined'); + // convert eslint paths to globs + const cli = new CLIEngine(); + const eslintSourcePaths = grunt.config.get('eslint.options.paths'); + if (!eslintSourcePaths) grunt.fail.warn('eslint.options.paths is not defined'); + + const sourcePathRegexps = cli.resolveFileGlobPatterns(eslintSourcePaths) + .map(glob => minimatch.makeRe(glob)); const files = grunt.config .get('filesToCommit') .filter(isStaged) .map(getFilename) .map(file => resolve(root, file)) - .filter(file => grunt.file.isMatch(patterns, file)); + .filter(file => { + if (!sourcePathRegexps.some(re => file.match(re))) { + return false; + } + + if (cli.isPathIgnored(file)) { + return false; + } + + return true; + }); - grunt.config.set('eslint.staged.files.src', files); + grunt.config.set('eslint.staged.options.paths', files); grunt.task.run(['eslint:staged']); }); } diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index 1ec25f0006b2f..70f0eaa83f127 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -73,8 +73,9 @@ bdd.describe('visualize app', function describeIndexTests() { bdd.it('should show correct data, take screenshot', function () { var chartHeight = 0; - var expectedChartData = [ '0 2,088', '2,000 2,748', '4,000 2,707', '6,000 2,876', - '8,000 2,863', '10,000 147', '12,000 148', '14,000 129', '16,000 161', '18,000 137' + var expectedChartData = [ + '0 2,088', '2,000 2,748', '4,000 2,707', '6,000 2,876', + '8,000 2,863', '10,000 147', '12,000 148', '14,000 129', '16,000 161', '18,000 137' ]; return PageObjects.common.try(function () { From 8eed97b69fa71e096c04a01ae1b26efa641fec85 Mon Sep 17 00:00:00 2001 From: Matt Bargar Date: Mon, 12 Dec 2016 16:32:22 -0500 Subject: [PATCH 16/17] Limit scripted fields to painless and expression langs (#9172) We made a mistake in opening up Kibana scripted fields to any and all langs enabled in Elasticsearch. Kibana needs to do some magic to get certain scripted field features to work (https://github.com/elastic/kibana/pull/9171) and we can't commit to supporting anything beyond expression and painless scripts. On top of that, the other languages are being removed in ES 6.0 anyway. This commit makes that stance explicit by disallowing anything other than those two choices. --- docs/migration/migrate_6_0.asciidoc | 11 ++++++++++- src/ui/public/field_editor/field_editor.js | 12 ++++++------ src/ui/public/scripting_languages/index.js | 20 ++++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 src/ui/public/scripting_languages/index.js diff --git a/docs/migration/migrate_6_0.asciidoc b/docs/migration/migrate_6_0.asciidoc index cf481ba78d68d..27830c6f806fc 100644 --- a/docs/migration/migrate_6_0.asciidoc +++ b/docs/migration/migrate_6_0.asciidoc @@ -1,4 +1,13 @@ [[breaking-changes-6.0]] == Breaking changes in 6.0 -There are not yet any breaking changes in Kibana 6.0 +This section discusses the changes that you need to be aware of when migrating +your application to Kibana 6.0. + +[float] +=== Removed option to use unsupported scripting languages +*Details:* Kibana 5.x allowed users to create scripted fields using any scripting language enabled in Elasticsearch. +Kibana 6.0 will only support Painless and Lucene expression based scripts. + + +*Impact:* You will need to migrate your groovy, python, javascript, etc. scripted fields to Painless or Lucene expressions. \ No newline at end of file diff --git a/src/ui/public/field_editor/field_editor.js b/src/ui/public/field_editor/field_editor.js index ed66858deaf52..ffa5dfca5d16f 100644 --- a/src/ui/public/field_editor/field_editor.js +++ b/src/ui/public/field_editor/field_editor.js @@ -10,12 +10,14 @@ import chrome from 'ui/chrome'; import IndexPatternsCastMappingTypeProvider from 'ui/index_patterns/_cast_mapping_type'; import { scriptedFields as docLinks } from '../documentation_links/documentation_links'; import './field_editor.less'; +import { GetEnabledScriptingLanguagesProvider, getSupportedScriptingLanguages } from '../scripting_languages'; uiModules .get('kibana', ['colorpicker.module']) .directive('fieldEditor', function (Private, $sce) { let fieldFormats = Private(RegistryFieldFormatsProvider); let Field = Private(IndexPatternsFieldProvider); + let getEnabledScriptingLanguages = Private(GetEnabledScriptingLanguagesProvider); const fieldTypesByLang = { painless: ['number', 'string', 'date', 'boolean'], @@ -37,7 +39,7 @@ uiModules self.docLinks = docLinks; getScriptingLangs().then((langs) => { - self.scriptingLangs = langs; + self.scriptingLangs = _.intersection(langs, ['expression', 'painless']); if (!_.includes(self.scriptingLangs, self.field.lang)) { self.field.lang = undefined; } @@ -159,11 +161,9 @@ uiModules } function getScriptingLangs() { - return $http.get(chrome.addBasePath('/api/kibana/scripts/languages')) - .then((res) => res.data) - .catch(() => { - notify.error('Error getting available scripting languages from Elasticsearch'); - return []; + return getEnabledScriptingLanguages() + .then((enabledLanguages) => { + return _.intersection(enabledLanguages, getSupportedScriptingLanguages()); }); } diff --git a/src/ui/public/scripting_languages/index.js b/src/ui/public/scripting_languages/index.js new file mode 100644 index 0000000000000..9a26119cb3df7 --- /dev/null +++ b/src/ui/public/scripting_languages/index.js @@ -0,0 +1,20 @@ +import chrome from 'ui/chrome'; +import Notifier from 'ui/notify/notifier'; +import { intersection } from 'lodash'; + +const notify = new Notifier({ location: 'Scripting Language Service' }); + +export function getSupportedScriptingLanguages() { + return ['expression', 'painless']; +} + +export function GetEnabledScriptingLanguagesProvider($http) { + return () => { + return $http.get(chrome.addBasePath('/api/kibana/scripts/languages')) + .then((res) => res.data) + .catch(() => { + notify.error('Error getting available scripting languages from Elasticsearch'); + return []; + }); + }; +} From 10f7880bc89102a226ceffce32d55c3725f41d5e Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 12 Dec 2016 14:54:40 -0800 Subject: [PATCH 17/17] Add close button to localNavDropdowns. (#9437) * Add close button to localNavDropdowns. - Make Timelion docs dropdown scroll only the table. - Make Timelion tutorial a fluid height, instead of fixed. * Use chevron for localDropdownCloseButton. --- package.json | 2 +- src/core_plugins/timelion/public/app.less | 11 +--- .../public/partials/docs/tutorial.html | 66 ++++++++++--------- src/ui/public/kbn_top_nav/kbn_top_nav.html | 8 ++- src/ui/public/styles/base.less | 11 ++++ 5 files changed, 55 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index bcfb871d0edf2..0e80c63eee6ff 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@bigfunger/decompress-zip": "0.2.0-stripfix3", "@bigfunger/jsondiffpatch": "0.1.38-webpack", "@elastic/datemath": "2.3.0", - "@elastic/kibana-ui-framework": "0.0.11", + "@elastic/kibana-ui-framework": "0.0.13", "@spalger/filesaver": "1.1.2", "@spalger/leaflet-draw": "0.2.3", "@spalger/leaflet-heat": "0.1.3", diff --git a/src/core_plugins/timelion/public/app.less b/src/core_plugins/timelion/public/app.less index 4da20b3261d8a..0095eaa710480 100644 --- a/src/core_plugins/timelion/public/app.less +++ b/src/core_plugins/timelion/public/app.less @@ -121,7 +121,7 @@ timelion-interval { display: flex; } -.timelionFunctionsDropdown { +.timelionFunctionsDropdownContent { height: 310px; overflow-y: auto; } @@ -144,27 +144,22 @@ timelion-interval { } .doc-container-content { - padding: 0 20px 20px 20px; - height: 310px; - overflow-y: auto; - background-color: @body-bg; position: relative; } .doc-container-buttons { position: relative; text-align: center; - background-color: @body-bg; height: 40px; .btn-doc-prev { position: absolute; - left: 20px; + left: 0px; } .btn-doc-next { position: absolute; - right: 20px; + right: 0px; } } diff --git a/src/core_plugins/timelion/public/partials/docs/tutorial.html b/src/core_plugins/timelion/public/partials/docs/tutorial.html index e075aa5eb855f..23b4f22e7c74f 100644 --- a/src/core_plugins/timelion/public/partials/docs/tutorial.html +++ b/src/core_plugins/timelion/public/partials/docs/tutorial.html @@ -145,44 +145,46 @@

    Data: Transform insert beat boxing

    -
    +
    Function reference
    Click a function for details and arguments or return to the tutorial.
    - - - - - +
    +
    .{{function.name}}(){{function.help}}
    + + + + - - - + - -
    .{{function.name}}(){{function.help}}
    -
    - - - - - - - - - - - -
    Argument NameAccepted TypesInformation
    {{arg.name}}{{arg.types.join(', ')}}{{arg.help}}
    -
    - This function does not accept any arguments. Well that's simple, isn't it? + +
    +
    + + + + + + + + + + + +
    Argument NameAccepted TypesInformation
    {{arg.name}}{{arg.types.join(', ')}}{{arg.help}}
    +
    + This function does not accept any arguments. Well that's simple, isn't it? +
    - -
    + + + +
    diff --git a/src/ui/public/kbn_top_nav/kbn_top_nav.html b/src/ui/public/kbn_top_nav/kbn_top_nav.html index 5ec6577c825d6..dc96a4caec09d 100644 --- a/src/ui/public/kbn_top_nav/kbn_top_nav.html +++ b/src/ui/public/kbn_top_nav/kbn_top_nav.html @@ -39,10 +39,14 @@
    - + +
    + +
    diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less index f960e328f9f07..fcc9bb77cd419 100644 --- a/src/ui/public/styles/base.less +++ b/src/ui/public/styles/base.less @@ -1070,4 +1070,15 @@ fieldset { .makeKuiColumns(@n, (@i + 1)); } +/** + * 1. Override Bootstrap styles. + */ +.localDropdownCloseButton { + color: #2d2d2d !important; /* 1 */ + + .theme-dark & { + color: #cecece !important; /* 1 */ + } +} + @import "~dragula/dist/dragula.css";