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/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. 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/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. diff --git a/package.json b/package.json index 01ac395810b7d..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", @@ -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/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/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 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/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/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/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/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/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; } 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/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/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/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/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/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({ diff --git a/src/ui/public/courier/saved_object/saved_object.js b/src/ui/public/courier/saved_object/saved_object.js index 56e9f765dceae..d2a3c11d23ef8 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: 'text' + } } - } - }; + }; - // 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(); }); }; } 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/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/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/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 []; + }); + }; +} diff --git a/src/ui/public/styles/base.less b/src/ui/public/styles/base.less index 9c3ac595de46b..fcc9bb77cd419 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; } @@ -611,6 +594,8 @@ fieldset { } } +// TODO: Extract these styles into the UI Framework. + .page-row { padding: 0 10px; margin: 10px 0; @@ -621,12 +606,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 +1020,65 @@ 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)); +} + +/** + * 1. Override Bootstrap styles. + */ +.localDropdownCloseButton { + color: #2d2d2d !important; /* 1 */ + + .theme-dark & { + color: #cecece !important; /* 1 */ + } +} + @import "~dragula/dist/dragula.css"; 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() + }; + } +}; 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; } 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..f426b0ed5f99c 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', 'normal') === '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/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/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) { 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 { /* ... */ } ``` 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/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/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 () { 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 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); - }; - -});