From 439b6bb8480ed4700432719ee3a76137b9d31262 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Fri, 31 Mar 2017 15:43:21 +0200 Subject: [PATCH 01/40] converting Vis to es6 class --- src/ui/public/vis/vis.js | 290 ++++++++++++++++++++------------------- 1 file changed, 147 insertions(+), 143 deletions(-) diff --git a/src/ui/public/vis/vis.js b/src/ui/public/vis/vis.js index 76b9cabb1c05c3..9c91577e4aadac 100644 --- a/src/ui/public/vis/vis.js +++ b/src/ui/public/vis/vis.js @@ -23,169 +23,173 @@ export function VisProvider(Notifier, Private) { location: 'Vis' }); - function Vis(indexPattern, state, uiState) { - state = state || {}; + class Vis { + constructor(indexPattern, state, uiState) { + state = state || {}; - if (_.isString(state)) { - state = { - type: state + if (_.isString(state)) { + state = { + type: state + }; + } + + this.indexPattern = indexPattern; + + this.setState(state); + this.setUiState(uiState); + } + + convertOldState(type, oldState) { + if (!type || _.isString(type)) { + type = visTypes.byName[type || 'histogram']; + } + + const schemas = type.schemas; + + // This was put in place to do migrations at runtime. It's used to support people who had saved + // visualizations during the 4.0 betas. + const aggs = _.transform(oldState, function (newConfigs, oldConfigs, oldGroupName) { + const schema = schemas.all.byName[oldGroupName]; + + if (!schema) { + notify.log('unable to match old schema', oldGroupName, 'to a new schema'); + return; + } + + oldConfigs.forEach(function (oldConfig) { + const agg = { + schema: schema.name, + type: oldConfig.agg + }; + + const aggType = aggTypes.byName[agg.type]; + if (!aggType) { + notify.log('unable to find an agg type for old confg', oldConfig); + return; + } + + agg.params = _.pick(oldConfig, _.keys(aggType.params.byName)); + + newConfigs.push(agg); + }); + }, []); + + return { + type: type, + aggs: aggs }; } - this.indexPattern = indexPattern; + setState(state) { + this.title = state.title || ''; + const type = state.type || this.type; + if (_.isString(type)) { + this.type = visTypes.byName[type]; + if (!this.type) { + throw new Error(`Invalid type "${type}"`); + } + } else { + this.type = type; + } - this.setState(state); - this.setUiState(uiState); - } + this.listeners = _.assign({}, state.listeners, this.type.listeners); + this.params = _.defaults({}, + _.cloneDeep(state.params || {}), + _.cloneDeep(this.type.params.defaults || {}) + ); - Vis.convertOldState = function (type, oldState) { - if (!type || _.isString(type)) { - type = visTypes.byName[type || 'histogram']; + this.aggs = new AggConfigs(this, state.aggs); } - const schemas = type.schemas; + getStateInternal(includeDisabled) { + return { + title: this.title, + type: this.type.name, + params: this.params, + aggs: this.aggs + .filter(agg => includeDisabled || agg.enabled) + .map(agg => agg.toJSON()) + .filter(Boolean), + listeners: this.listeners + }; + } - // This was put in place to do migrations at runtime. It's used to support people who had saved - // visualizations during the 4.0 betas. - const aggs = _.transform(oldState, function (newConfigs, oldConfigs, oldGroupName) { - const schema = schemas.all.byName[oldGroupName]; + getEnabledState() { + return this.getStateInternal(false); + } - if (!schema) { - notify.log('unable to match old schema', oldGroupName, 'to a new schema'); - return; - } + getState() { + return this.getStateInternal(true); + } - oldConfigs.forEach(function (oldConfig) { - const agg = { - schema: schema.name, - type: oldConfig.agg - }; + createEditableVis() { + return this._editableVis || (this._editableVis = this.clone()); + } - const aggType = aggTypes.byName[agg.type]; - if (!aggType) { - notify.log('unable to find an agg type for old confg', oldConfig); - return; - } + getEditableVis() { + return this._editableVis || undefined; + } - agg.params = _.pick(oldConfig, _.keys(aggType.params.byName)); + clone() { + const uiJson = this.hasUiState() ? this.getUiState().toJSON() : {}; + return new Vis(this.indexPattern, this.getState(), uiJson); + } - newConfigs.push(agg); - }); - }, []); + requesting() { + // Invoke requesting() on each agg. Aggs is an instance of AggConfigs. + _.invoke(this.aggs.getRequestAggs(), 'requesting'); + } - return { - type: type, - aggs: aggs - }; - }; + isHierarchical() { + if (_.isFunction(this.type.hierarchicalData)) { + return !!this.type.hierarchicalData(this); + } else { + return !!this.type.hierarchicalData; + } + } - Vis.prototype.type = 'histogram'; + hasSchemaAgg(schemaName, aggTypeName) { + const aggs = this.aggs.bySchemaName[schemaName] || []; + return aggs.some(function (agg) { + if (!agg.type || !agg.type.name) return false; + return agg.type.name === aggTypeName; + }); + } - Vis.prototype.setState = function (state) { - this.title = state.title || ''; - const type = state.type || this.type; - if (_.isString(type)) { - this.type = visTypes.byName[type]; - if (!this.type) { - throw new Error(`Invalid type "${type}"`); + hasUiState() { + return !!this.__uiState; + } + + setUiState(uiState) { + if (uiState instanceof PersistedState) { + this.__uiState = uiState; } - } else { - this.type = type; - } - - this.listeners = _.assign({}, state.listeners, this.type.listeners); - this.params = _.defaults({}, - _.cloneDeep(state.params || {}), - _.cloneDeep(this.type.params.defaults || {}) - ); - - this.aggs = new AggConfigs(this, state.aggs); - }; - - Vis.prototype.getStateInternal = function (includeDisabled) { - return { - title: this.title, - type: this.type.name, - params: this.params, - aggs: this.aggs - .filter(agg => includeDisabled || agg.enabled) - .map(agg => agg.toJSON()) - .filter(Boolean), - listeners: this.listeners - }; - }; - - Vis.prototype.getEnabledState = function () { - return this.getStateInternal(false); - }; - - Vis.prototype.getState = function () { - return this.getStateInternal(true); - }; - - Vis.prototype.createEditableVis = function () { - return this._editableVis || (this._editableVis = this.clone()); - }; - - Vis.prototype.getEditableVis = function () { - return this._editableVis || undefined; - }; - - Vis.prototype.clone = function () { - const uiJson = this.hasUiState() ? this.getUiState().toJSON() : {}; - return new Vis(this.indexPattern, this.getState(), uiJson); - }; - - Vis.prototype.requesting = function () { - // Invoke requesting() on each agg. Aggs is an instance of AggConfigs. - _.invoke(this.aggs.getRequestAggs(), 'requesting'); - }; - - Vis.prototype.isHierarchical = function () { - if (_.isFunction(this.type.hierarchicalData)) { - return !!this.type.hierarchicalData(this); - } else { - return !!this.type.hierarchicalData; - } - }; - - Vis.prototype.hasSchemaAgg = function (schemaName, aggTypeName) { - const aggs = this.aggs.bySchemaName[schemaName] || []; - return aggs.some(function (agg) { - if (!agg.type || !agg.type.name) return false; - return agg.type.name === aggTypeName; - }); - }; - - Vis.prototype.hasUiState = function () { - return !!this.__uiState; - }; - Vis.prototype.setUiState = function (uiState) { - if (uiState instanceof PersistedState) { - this.__uiState = uiState; - } - }; - Vis.prototype.getUiState = function () { - return this.__uiState; - }; - - Vis.prototype.implementsRenderComplete = function () { - return this.type.implementsRenderComplete; - }; - - /** - * Currently this is only used to extract map-specific information - * (e.g. mapZoom, mapCenter). - */ - Vis.prototype.uiStateVal = function (key, val) { - if (this.hasUiState()) { - if (_.isUndefined(val)) { - return this.__uiState.get(key); + } + + getUiState() { + return this.__uiState; + } + + implementsRenderComplete() { + return this.type.implementsRenderComplete; + } + + /** + * Currently this is only used to extract map-specific information + * (e.g. mapZoom, mapCenter). + */ + uiStateVal(key, val) { + if (this.hasUiState()) { + if (_.isUndefined(val)) { + return this.__uiState.get(key); + } + return this.__uiState.set(key, val); } - return this.__uiState.set(key, val); + return val; } - return val; - }; + } + + Vis.prototype.type = 'histogram'; return Vis; } From 87257bfde063cb65a440de4fb1093700ac2c32d2 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:16:36 +0200 Subject: [PATCH 02/40] , and --- src/ui/public/visualize/visualization.html | 17 ++ src/ui/public/visualize/visualization.js | 143 +++++++++++ .../public/visualize/visualization_editor.js | 57 +++++ src/ui/public/visualize/visualize.html | 52 ++-- src/ui/public/visualize/visualize.js | 241 +++++------------- src/ui/public/visualize/visualize.less | 4 +- src/ui/public/visualize/visualize_legend.js | 13 +- 7 files changed, 304 insertions(+), 223 deletions(-) create mode 100644 src/ui/public/visualize/visualization.html create mode 100644 src/ui/public/visualize/visualization.js create mode 100644 src/ui/public/visualize/visualization_editor.js diff --git a/src/ui/public/visualize/visualization.html b/src/ui/public/visualize/visualization.html new file mode 100644 index 00000000000000..83c14406bacdc2 --- /dev/null +++ b/src/ui/public/visualize/visualization.html @@ -0,0 +1,17 @@ +
+
+
+ +

No results found

+
+
+
+
+
+ +
+ + diff --git a/src/ui/public/visualize/visualization.js b/src/ui/public/visualize/visualization.js new file mode 100644 index 00000000000000..abd5f356d876f1 --- /dev/null +++ b/src/ui/public/visualize/visualization.js @@ -0,0 +1,143 @@ +import 'ui/visualize/spy'; +import 'ui/visualize/visualize.less'; +import 'ui/visualize/visualize_legend'; +import _ from 'lodash'; +import { uiModules } from 'ui/modules'; +import { ResizeCheckerProvider } from 'ui/resize_checker'; +import visualizationTemplate from 'ui/visualize/visualization.html'; +import 'angular-sanitize'; + +uiModules +.get('kibana/directive', ['ngSanitize']) +.directive('visualization', function (Notifier, SavedVis, indexPatterns, Private, config, $timeout) { + const ResizeChecker = Private(ResizeCheckerProvider); + + return { + restrict: 'E', + require: '?renderCounter', + scope : { + vis: '=', + visData: '=', + uiState: '=?', + searchSource: '=' + }, + template: visualizationTemplate, + link: function ($scope, $el) { + const minVisChartHeight = 180; + const resizeChecker = new ResizeChecker($el); + + $scope.showSpyPanel = $scope.vis && $scope.vis.showSpyPanel || false; + + + //todo: lets make this a simple function call. + const getVisEl = jQueryGetter('.visualize-chart'); + const getVisContainer = jQueryGetter('.vis-container'); + const getSpyContainer = jQueryGetter('.visualize-spy-container'); + + // Show no results message when isZeroHits is true and it requires search + $scope.showNoResultsMessage = function () { + const requiresSearch = _.get($scope, 'vis.type.requiresSearch'); + const isZeroHits = _.get($scope,'visData.hits.total') === 0; + const shouldShowMessage = !_.get($scope, 'vis.params.handleNoResults'); + + return Boolean(requiresSearch && isZeroHits && shouldShowMessage); + }; + + const legendPositionToVisContainerClassMap = { + top: 'vis-container--legend-top', + bottom: 'vis-container--legend-bottom', + left: 'vis-container--legend-left', + right: 'vis-container--legend-right', + }; + + $scope.getVisContainerClasses = function () { + return legendPositionToVisContainerClassMap[$scope.vis.params.legendPosition]; + }; + + $scope.spy = {}; + $scope.spy.mode = ($scope.uiState) ? $scope.uiState.get('spy.mode', {}) : {}; + + const applyClassNames = function () { + const $visEl = getVisContainer(); + const $spyEl = getSpyContainer(); + if (!$spyEl) return; + + const fullSpy = ($scope.spy.mode && ($scope.spy.mode.fill || $scope.fullScreenSpy)); + + $visEl.toggleClass('spy-only', Boolean(fullSpy)); + $spyEl.toggleClass('only', Boolean(fullSpy)); + + $timeout(function () { + if (shouldHaveFullSpy()) { + $visEl.addClass('spy-only'); + $spyEl.addClass('only'); + } + }, 0); + }; + + const loadingDelay = config.get('visualization:loadingDelay'); + $scope.loadingStyle = { + '-webkit-transition-delay': loadingDelay, + 'transition-delay': loadingDelay + }; + + function shouldHaveFullSpy() { + const $visEl = getVisEl(); + if (!$visEl) return; + + return ($visEl.height() < minVisChartHeight) + && _.get($scope.spy, 'mode.fill') + && _.get($scope.spy, 'mode.name'); + } + + // spy watchers + $scope.$watch('fullScreenSpy', applyClassNames); + + $scope.$watchCollection('spy.mode', function () { + $scope.fullScreenSpy = shouldHaveFullSpy(); + applyClassNames(); + }); + + const Visualization = $scope.vis.type.visualization; + + //todo: make this not a jquery element + const visualization = new Visualization(getVisEl()[0], $scope.vis); + + const renderFunction = _.debounce(() => { + visualization.render($scope.visData) + .then(() => { + // renderComplete + $scope.$emit('renderComplete'); + }); + $scope.$apply(); + }, 200); + + $scope.$on('render', () => { + if (!$scope.vis || ($scope.vis.type.requiresSearch && !$scope.visData)) { + return; + } + renderFunction(); + }); + + $scope.$on('$destroy', () => { + visualization.destroy(); + }); + + resizeChecker.on('resize', () => { + visualization.resize(); + }); + + $scope.$watch('visData', () => { + $scope.$broadcast('render'); + }); + + + function jQueryGetter(selector) { + return function () { + const $sel = $el.find(selector); + if ($sel.size()) return $sel; + }; + } + } + }; +}); diff --git a/src/ui/public/visualize/visualization_editor.js b/src/ui/public/visualize/visualization_editor.js new file mode 100644 index 00000000000000..8340f7bfb264f6 --- /dev/null +++ b/src/ui/public/visualize/visualization_editor.js @@ -0,0 +1,57 @@ +import _ from 'lodash'; +import 'ui/visualize/spy'; +import 'ui/visualize/visualize.less'; +import 'ui/visualize/visualize_legend'; +import { uiModules } from 'ui/modules'; +import 'angular-sanitize'; +import { EditorTypesRegistryProvider } from 'ui/registry/editor_types'; +import { ResizeCheckerProvider } from 'ui/resize_checker'; + +uiModules +.get('kibana/directive', ['ngSanitize']) +.directive('visualizationEditor', function (Private) { + const editorTypes = Private(EditorTypesRegistryProvider); + const ResizeChecker = Private(ResizeCheckerProvider); + + return { + restrict: 'E', + require: '?renderCounter', + scope : { + vis: '=', + visData: '=', + uiState: '=?', + searchSource: '=' + }, + link: function ($scope, element) { + // Clone the _vis instance. + const vis = $scope.vis; + const resizeChecker = new ResizeChecker(element); + const Editor = typeof vis.type.editor === 'function' ? vis.type.editor : + editorTypes.find(editor => editor.key === vis.type.editor); + const editor = new Editor(element[0], vis); + + const renderFunction = _.debounce(() => { + editor.render($scope.visData, $scope.searchSource).then(() => { + $scope.$emit('renderComplete'); + $scope.$apply(); + }); + }, 200); + + $scope.$on('render', () => { + if (!$scope.vis) { + return; + } + renderFunction(); + }); + + resizeChecker.on('resize', () => { + editor.resize(); + }); + + $scope.$on('$destroy', () => { + editor.destroy(); + }); + + } + }; +}); diff --git a/src/ui/public/visualize/visualize.html b/src/ui/public/visualize/visualize.html index 8bdd44328dba50..be06f2b26523f4 100644 --- a/src/ui/public/visualize/visualize.html +++ b/src/ui/public/visualize/visualize.html @@ -1,35 +1,21 @@ -
-
-
- -

No results found

-
+ + -
-
- -
- -
- - - -
- - - diff --git a/src/ui/public/visualize/visualize.js b/src/ui/public/visualize/visualize.js index 8f7b37da08f216..cf1b0966ba73e1 100644 --- a/src/ui/public/visualize/visualize.js +++ b/src/ui/public/visualize/visualize.js @@ -1,10 +1,11 @@ -import 'ui/visualize/spy'; -import 'ui/visualize/visualize.less'; -import 'ui/visualize/visualize_legend'; -import _ from 'lodash'; import { uiModules } from 'ui/modules'; +import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; import visualizeTemplate from 'ui/visualize/visualize.html'; +import { RequestHandlersRegistryProvider } from 'ui/registry/request_handlers'; +import { ResponseHandlersRegistryProvider } from 'ui/registry/response_handlers'; import 'angular-sanitize'; +import './visualization'; +import './visualization_editor'; import { isTermSizeZeroError, @@ -12,173 +13,39 @@ import { uiModules .get('kibana/directive', ['ngSanitize']) -.directive('visualize', function (Notifier, SavedVis, indexPatterns, Private, config, $timeout) { - const notify = new Notifier({ - location: 'Visualize' - }); +.directive('visualize', function (Notifier, Private, timefilter) { + const notify = new Notifier({ location: 'Visualize' }); + const requestHandlers = Private(RequestHandlersRegistryProvider); + const responseHandlers = Private(ResponseHandlersRegistryProvider); + + function getHandler(from, name) { + if (typeof name === 'function') return name; + return from.find(handler => handler.name === name).handler; + } return { restrict: 'E', - require: '?renderCounter', scope : { showSpyPanel: '=?', - vis: '=', - uiState: '=?', - searchSource: '=?', - editableVis: '=?', - esResp: '=?', + editorMode: '=?', + savedObj: '=', + appState: '=', + uiState: '=?' }, template: visualizeTemplate, - link: function ($scope, $el, attr, renderCounter) { - const minVisChartHeight = 180; - - if (_.isUndefined($scope.showSpyPanel)) { - $scope.showSpyPanel = true; - } - - function getter(selector) { - return function () { - const $sel = $el.find(selector); - if ($sel.size()) return $sel; - }; - } - - const getVisEl = getter('[data-visualize-chart]'); - const getVisContainer = getter('[data-visualize-chart-container]'); - const getSpyContainer = getter('[data-spy-content-container]'); - - // Show no results message when isZeroHits is true and it requires search - $scope.showNoResultsMessage = function () { - const requiresSearch = _.get($scope, 'vis.type.requiresSearch'); - const isZeroHits = _.get($scope,'esResp.hits.total') === 0; - const shouldShowMessage = !_.get($scope, 'vis.params.handleNoResults'); - - return Boolean(requiresSearch && isZeroHits && shouldShowMessage); - }; - - const legendPositionToVisContainerClassMap = { - top: 'vis-container--legend-top', - bottom: 'vis-container--legend-bottom', - left: 'vis-container--legend-left', - right: 'vis-container--legend-right', - }; - - $scope.getVisContainerClasses = function () { - return legendPositionToVisContainerClassMap[$scope.vis.params.legendPosition]; - }; - - if (renderCounter && !$scope.vis.implementsRenderComplete()) { - renderCounter.disable(); - } - - $scope.spy = {}; - $scope.spy.mode = ($scope.uiState) ? $scope.uiState.get('spy.mode', {}) : {}; - - const updateSpy = function () { - const $visContainer = getVisContainer(); - const $spyEl = getSpyContainer(); - if (!$spyEl) return; - - const fullSpy = ($scope.spy.mode && ($scope.spy.mode.fill || $scope.fullScreenSpy)); - - $visContainer.toggleClass('spy-only', Boolean(fullSpy)); - $spyEl.toggleClass('only', Boolean(fullSpy)); - - $timeout(function () { - if (shouldHaveFullSpy()) { - $visContainer.addClass('spy-only'); - $spyEl.addClass('only'); - } - }, 0); - }; - - // we need to wait for some watchers to fire at least once - // before we are "ready", this manages that - const prereq = (function () { - const fns = []; - - return function register(fn) { - fns.push(fn); - - return function () { - fn.apply(this, arguments); - - if (fns.length) { - _.pull(fns, fn); - if (!fns.length) { - $scope.$root.$broadcast('ready:vis'); - } - } - }; - }; - }()); - - const loadingDelay = config.get('visualization:loadingDelay'); - $scope.loadingStyle = { - '-webkit-transition-delay': loadingDelay, - 'transition-delay': loadingDelay - }; - - function shouldHaveFullSpy() { - const $visEl = getVisEl(); - if (!$visEl) return; - - return ($visEl.height() < minVisChartHeight) - && _.get($scope.spy, 'mode.fill') - && _.get($scope.spy, 'mode.name'); - } - - // spy watchers - $scope.$watch('fullScreenSpy', updateSpy); - - $scope.$watchCollection('spy.mode', function () { - $scope.fullScreenSpy = shouldHaveFullSpy(); - updateSpy(); - }); - - function updateVisAggs() { - const enabledState = $scope.editableVis.getEnabledState(); - const shouldUpdate = enabledState.aggs.length !== $scope.vis.aggs.length; - - if (shouldUpdate) { - $scope.vis.setState(enabledState); - $scope.editableVis.dirty = false; - } - } - - $scope.$watch('vis', prereq(function (vis, oldVis) { - const $visEl = getVisEl(); - if (!$visEl) return; - - if (!attr.editableVis) { - $scope.editableVis = vis; - } - - if (oldVis) $scope.renderbot = null; - if (vis) { - $scope.renderbot = vis.type.createRenderbot(vis, $visEl, $scope.uiState); - } - })); - - $scope.$watchCollection('vis.params', prereq(function () { - updateVisAggs(); - if ($scope.renderbot) $scope.renderbot.updateParams(); - })); - - if (_.get($scope, 'vis.type.requiresSearch')) { - $scope.$watch('searchSource', prereq(function (searchSource) { - if (!searchSource || attr.esResp) return; - - // TODO: we need to have some way to clean up result requests - searchSource.onResults().then(function onResults(resp) { - if ($scope.searchSource !== searchSource) return; - - $scope.esResp = resp; - - return searchSource.onResults().then(onResults); - }).catch(notify.fatal); - - searchSource.onError(e => { + link: function ($scope, $el) { + $scope.vis = $scope.savedObj.vis; + $scope.editorMode = $scope.editorMode || false; + $scope.vis.showSpyPanel = $scope.showSpyPanel || false; + $scope.vis.editorMode = $scope.editorMode; + + const requestHandler = getHandler(requestHandlers, $scope.vis.type.requestHandler); + const responseHandler = getHandler(responseHandlers, $scope.vis.type.responseHandler); + + $scope.fetch = function () { + // searchSource is only there for courier request handler + requestHandler($scope.vis, $scope.appState, $scope.uiState, $scope.savedObj.searchSource) + .then(resp => responseHandler($scope.vis, resp), e => { $el.trigger('renderComplete'); if (isTermSizeZeroError(e)) { return notify.error( @@ -189,27 +56,39 @@ uiModules } notify.error(e); - }).catch(notify.fatal); - })); - } - - $scope.$watch('esResp', prereq(function (resp) { - if (!resp) return; - $scope.renderbot.render(resp); - })); + }) + .then(resp => { + $scope.visData = resp; + $scope.$apply(); + $scope.$broadcast('render'); + return resp; + }); + }; - $scope.$watch('renderbot', function (newRenderbot, oldRenderbot) { - if (oldRenderbot && newRenderbot !== oldRenderbot) { - oldRenderbot.destroy(); + $scope.vis.on('update', () => { + if ($scope.editorMode) { + const visState = $scope.vis.getState(); + $scope.appState.vis = visState; + $scope.appState.save(); + } else { + $scope.fetch(); } }); - $scope.$on('$destroy', function () { - if ($scope.renderbot) { - $el.off('renderComplete'); - $scope.renderbot.destroy(); - } - }); + const stateMonitor = stateMonitorFactory.create($scope.appState); + if ($scope.vis.type.requiresSearch) { + stateMonitor.onChange((status, type, keys) => { + if (['query', 'filters', 'vis'].includes(keys[0])) { + $scope.vis.setState($scope.appState.vis); + $scope.fetch(); + } + }); + + // visualize needs to know about timeFilter + $scope.$listen(timefilter, 'fetch', $scope.fetch); + } + + $scope.fetch(); } }; }); diff --git a/src/ui/public/visualize/visualize.less b/src/ui/public/visualize/visualize.less index a02cf866a85b4f..93f25c587244b4 100644 --- a/src/ui/public/visualize/visualize.less +++ b/src/ui/public/visualize/visualize.less @@ -1,9 +1,9 @@ @import (reference) "~ui/styles/variables"; -visualize { +visualization { display: flex; flex-direction: column; - height: 100%; + height: auto; width: 100%; overflow: auto; position: relative; diff --git a/src/ui/public/visualize/visualize_legend.js b/src/ui/public/visualize/visualize_legend.js index 428fe835114fc1..a084cc59ef7127 100644 --- a/src/ui/public/visualize/visualize_legend.js +++ b/src/ui/public/visualize/visualize_legend.js @@ -18,18 +18,18 @@ uiModules.get('kibana') const clickHandler = filterBarClickHandler($state); $scope.open = $scope.uiState.get('vis.legendOpen', true); - $scope.$watch('renderbot.chartData', function (data) { + $scope.$watch('visData', function (data) { if (!data) return; $scope.data = data; }); - $scope.$watch('renderbot.refreshLegend', () => { + $scope.$watch('vis.refreshLegend', () => { refresh(); }); $scope.highlight = function (event) { const el = event.currentTarget; - const handler = $scope.renderbot.vislibVis.handler; + const handler = $scope.vis.vislibVis.handler; //there is no guarantee that a Chart will set the highlight-function on its handler if (!handler || typeof handler.highlight !== 'function') { @@ -40,7 +40,7 @@ uiModules.get('kibana') $scope.unhighlight = function (event) { const el = event.currentTarget; - const handler = $scope.renderbot.vislibVis.handler; + const handler = $scope.vis.vislibVis.handler; //there is no guarantee that a Chart will set the unhighlight-function on its handler if (!handler || typeof handler.unHighlight !== 'function') { return; @@ -102,9 +102,8 @@ uiModules.get('kibana') ]; function refresh() { - if (!$scope.renderbot) return; - const vislibVis = $scope.renderbot.vislibVis; - if (!vislibVis.visConfig) { + const vislibVis = $scope.vis.vislibVis; + if (!vislibVis || !vislibVis.visConfig) { $scope.labels = [{ label: 'loading ...' }]; return; } // make sure vislib is defined at this point From faadd4caa3eaefcb6036b372fe44936da04ba230 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:17:23 +0200 Subject: [PATCH 03/40] editor, responseHandler and requestHandler registries --- src/ui/public/registry/editor_types.js | 7 +++++++ src/ui/public/registry/request_handlers.js | 6 ++++++ src/ui/public/registry/response_handlers.js | 6 ++++++ 3 files changed, 19 insertions(+) create mode 100644 src/ui/public/registry/editor_types.js create mode 100644 src/ui/public/registry/request_handlers.js create mode 100644 src/ui/public/registry/response_handlers.js diff --git a/src/ui/public/registry/editor_types.js b/src/ui/public/registry/editor_types.js new file mode 100644 index 00000000000000..29fc1a7e152c9e --- /dev/null +++ b/src/ui/public/registry/editor_types.js @@ -0,0 +1,7 @@ +import { uiRegistry } from 'ui/registry/_registry'; + +export const EditorTypesRegistryProvider = uiRegistry({ + name: 'editorTypes', + index: ['name'], + order: ['title'] +}); diff --git a/src/ui/public/registry/request_handlers.js b/src/ui/public/registry/request_handlers.js new file mode 100644 index 00000000000000..85e6a85ad07fbd --- /dev/null +++ b/src/ui/public/registry/request_handlers.js @@ -0,0 +1,6 @@ +import { uiRegistry } from 'ui/registry/_registry'; +export const RequestHandlersRegistryProvider = uiRegistry({ + name: 'requestHandlers', + index: ['name'], + order: ['title'] +}); diff --git a/src/ui/public/registry/response_handlers.js b/src/ui/public/registry/response_handlers.js new file mode 100644 index 00000000000000..6cc7203f1d3c71 --- /dev/null +++ b/src/ui/public/registry/response_handlers.js @@ -0,0 +1,6 @@ +import { uiRegistry } from 'ui/registry/_registry'; +export const ResponseHandlersRegistryProvider = uiRegistry({ + name: 'responseHandlers', + index: ['name'], + order: ['title'] +}); From 3789a7196a3fd62202a2949b7d87b4c86d554870 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:18:59 +0200 Subject: [PATCH 04/40] updating visualize editor app --- .../public/visualize/editor/__tests__/agg.js | 83 --------- .../visualize/editor/__tests__/agg_params.js | 107 ----------- .../public/visualize/editor/add_bucket_agg.js | 0 .../visualize/editor/advanced_toggle.html | 6 - .../kibana/public/visualize/editor/agg.html | 94 ---------- .../kibana/public/visualize/editor/agg.js | 89 --------- .../public/visualize/editor/agg_add.html | 45 ----- .../kibana/public/visualize/editor/agg_add.js | 30 --- .../public/visualize/editor/agg_group.html | 15 -- .../public/visualize/editor/agg_group.js | 59 ------ .../public/visualize/editor/agg_param.js | 30 --- .../public/visualize/editor/agg_params.html | 28 --- .../public/visualize/editor/agg_params.js | 155 ---------------- .../public/visualize/editor/agg_select.html | 12 -- .../public/visualize/editor/editor.html | 57 +----- .../kibana/public/visualize/editor/editor.js | 174 ++++-------------- .../visualize/editor/nesting_indicator.js | 29 --- .../public/visualize/editor/sidebar.html | 112 ----------- .../kibana/public/visualize/editor/sidebar.js | 26 --- .../visualize/editor/styles/_editor.less | 9 +- .../public/visualize/editor/vis_options.html | 9 - .../public/visualize/editor/vis_options.js | 35 ---- .../kibana/public/visualize/index.js | 27 ++- .../kibana/public/visualize/wizard/wizard.js | 20 +- 24 files changed, 75 insertions(+), 1176 deletions(-) delete mode 100644 src/core_plugins/kibana/public/visualize/editor/__tests__/agg.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/__tests__/agg_params.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/add_bucket_agg.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/advanced_toggle.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_add.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_add.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_group.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_group.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_param.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_params.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_params.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/agg_select.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/nesting_indicator.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/sidebar.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/sidebar.js delete mode 100644 src/core_plugins/kibana/public/visualize/editor/vis_options.html delete mode 100644 src/core_plugins/kibana/public/visualize/editor/vis_options.js diff --git a/src/core_plugins/kibana/public/visualize/editor/__tests__/agg.js b/src/core_plugins/kibana/public/visualize/editor/__tests__/agg.js deleted file mode 100644 index 9ac0b8878f2ed2..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/__tests__/agg.js +++ /dev/null @@ -1,83 +0,0 @@ - -import angular from 'angular'; -import _ from 'lodash'; -import expect from 'expect.js'; -import ngMock from 'ng_mock'; -import 'plugins/kibana/visualize/editor/agg'; - - -describe('Vis-Editor-Agg plugin directive', function () { - const $parentScope = {}; - let $elem; - - function makeConfig(which) { - const schemaMap = { - radius: { - title: 'Dot Size', - min: 0, - max: 1 - }, - metric: { - title: 'Y-Axis', - min: 1, - max: Infinity - } - }; - const typeOptions = ['count', 'avg', 'sum', 'min', 'max', 'cardinality']; - which = which || 'metric'; - - const schema = schemaMap[which]; - - return { - min: schema.min, - max: schema.max, - name: which, - title: schema.title, - group: 'metrics', - aggFilter: typeOptions, - // AggParams object - params: [] - }; - } - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function ($rootScope, $compile) { - $parentScope.agg = { - id: 1, - params: {}, - schema: makeConfig(), - getFieldOptions: () => null - }; - $parentScope.groupName = 'metrics'; - $parentScope.group = [{ - id: '1', - schema: makeConfig() - }, { - id: '2', - schema: makeConfig('radius') - }]; - - // share the scope - _.defaults($parentScope, $rootScope, Object.getPrototypeOf($rootScope)); - - // make the element - $elem = angular.element( - '' - ); - - // compile the html - $compile($elem)($parentScope); - - // Digest everything - $elem.scope().$digest(); - })); - - it('should only add the close button if there is more than the minimum', function () { - expect($parentScope.canRemove($parentScope.agg)).to.be(false); - $parentScope.group.push({ - id: '3', - schema: makeConfig() - }); - expect($parentScope.canRemove($parentScope.agg)).to.be(true); - }); -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/__tests__/agg_params.js b/src/core_plugins/kibana/public/visualize/editor/__tests__/agg_params.js deleted file mode 100644 index 8bd744ef16be99..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/__tests__/agg_params.js +++ /dev/null @@ -1,107 +0,0 @@ - -import angular from 'angular'; -import _ from 'lodash'; -import expect from 'expect.js'; -import ngMock from 'ng_mock'; -import 'plugins/kibana/visualize/editor/agg_params'; -import { VisProvider } from 'ui/vis'; -import { VisAggConfigProvider } from 'ui/vis/agg_config'; -import { VisSchemasProvider } from 'ui/vis/schemas'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - - -describe('Vis-Editor-Agg-Params plugin directive', function () { - let $parentScope = {}; - let Vis; - let vis; - let AggConfig; - let Schemas; - let $elem; - let compile; - let rootScope; - - const aggFilter = [ - '!top_hits', '!percentiles', '!median', '!std_dev', - '!derivative', '!cumulative_sum', '!moving_avg', '!serial_diff' - ]; - - let indexPattern; - let orderAggSchema; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private, $rootScope, $compile) { - rootScope = $rootScope; - compile = $compile; - - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - Schemas = Private(VisSchemasProvider); - AggConfig = Private(VisAggConfigProvider); - })); - - function init(config) { - $parentScope = {}; - _.defaults($parentScope, rootScope, Object.getPrototypeOf(rootScope)); - - orderAggSchema = (new Schemas([config])).all[0]; - $parentScope.groupName = 'metrics'; - - const state = { - schema: orderAggSchema, - type: 'count' - }; - - vis = new Vis(indexPattern, { - type: 'histogram', - aggs: [ - { - type: 'date_histogram', - schema: 'segment' - } - ] - }); - - $parentScope.agg = new AggConfig(vis, state); - - // make the element - $elem = angular.element( - `` - ); - - // compile the html - compile($elem)($parentScope); - - // Digest everything - $elem.scope().$digest(); - } - - afterEach(function () { - $parentScope.$destroy(); - $parentScope = null; - }); - - it('should show custom label parameter', function () { - init ({ - group: 'none', - name: 'orderAgg', - title: 'Order Agg', - aggFilter: aggFilter - }); - - const customLabelElement = $elem.find('label:contains("Custom Label")'); - expect(customLabelElement.length).to.be(1); - }); - - it('should hide custom label parameter', function () { - init ({ - group: 'none', - name: 'orderAgg', - title: 'Order Agg', - hideCustomLabel: true, - aggFilter: aggFilter - }); - - const customLabelElement = $elem.find('label:contains("Custom Label")'); - expect(customLabelElement.length).to.be(0); - }); -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/add_bucket_agg.js b/src/core_plugins/kibana/public/visualize/editor/add_bucket_agg.js deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/src/core_plugins/kibana/public/visualize/editor/advanced_toggle.html b/src/core_plugins/kibana/public/visualize/editor/advanced_toggle.html deleted file mode 100644 index 46fa98de80757e..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/advanced_toggle.html +++ /dev/null @@ -1,6 +0,0 @@ - \ No newline at end of file diff --git a/src/core_plugins/kibana/public/visualize/editor/agg.html b/src/core_plugins/kibana/public/visualize/editor/agg.html deleted file mode 100644 index c25191dccde8ca..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg.html +++ /dev/null @@ -1,94 +0,0 @@ - -
- - - - - - - {{ agg.schema.title }} - - - - - {{ describe() }} - - - - - {{ aggForm.describeErrors() }} - - - -
- - - - - - - - - - - - -
- -
- - - - - - diff --git a/src/core_plugins/kibana/public/visualize/editor/agg.js b/src/core_plugins/kibana/public/visualize/editor/agg.js deleted file mode 100644 index cd5edf85755569..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg.js +++ /dev/null @@ -1,89 +0,0 @@ -import 'plugins/kibana/visualize/editor/agg_params'; -import 'plugins/kibana/visualize/editor/agg_add'; -import _ from 'lodash'; -import { uiModules } from 'ui/modules'; -import aggTemplate from 'plugins/kibana/visualize/editor/agg.html'; -uiModules -.get('app/visualize') -.directive('visEditorAgg', function ($compile, $parse, $filter, Private, Notifier) { - const notify = new Notifier({ - location: 'visAggGroup' - }); - - return { - restrict: 'A', - template: aggTemplate, - require: 'form', - link: function ($scope, $el, attrs, kbnForm) { - $scope.editorOpen = !!$scope.agg.brandNew; - - $scope.$watch('editorOpen', function (open) { - // make sure that all of the form inputs are "touched" - // so that their errors propogate - if (!open) kbnForm.$setTouched(); - }); - - $scope.$watchMulti([ - '$index', - 'group.length' - ], function () { - $scope.aggIsTooLow = calcAggIsTooLow(); - }); - - /** - * Describe the aggregation, for display in the collapsed agg header - * @return {[type]} [description] - */ - $scope.describe = function () { - if (!$scope.agg.type.makeLabel) return ''; - const label = $scope.agg.type.makeLabel($scope.agg); - return label ? label : ''; - }; - - $scope.$on('drag-start', () => { - $scope.editorWasOpen = $scope.editorOpen; - $scope.editorOpen = false; - $scope.$emit('agg-drag-start', $scope.agg); - }); - - $scope.$on('drag-end', () => { - $scope.editorOpen = $scope.editorWasOpen; - $scope.$emit('agg-drag-end', $scope.agg); - }); - - $scope.remove = function (agg) { - const aggs = $scope.vis.aggs; - - const index = aggs.indexOf(agg); - if (index === -1) return notify.log('already removed'); - - aggs.splice(index, 1); - }; - - $scope.canRemove = function (aggregation) { - const metricCount = _.reduce($scope.group, function (count, agg) { - return (agg.schema.name === aggregation.schema.name) ? ++count : count; - }, 0); - - // make sure the the number of these aggs is above the min - return metricCount > aggregation.schema.min; - }; - - function calcAggIsTooLow() { - if (!$scope.agg.schema.mustBeFirst) { - return false; - } - - const firstDifferentSchema = _.findIndex($scope.group, function (agg) { - return agg.schema !== $scope.agg.schema; - }); - - if (firstDifferentSchema === -1) { - return false; - } - - return $scope.$index > firstDifferentSchema; - } - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_add.html b/src/core_plugins/kibana/public/visualize/editor/agg_add.html deleted file mode 100644 index 51e75740812246..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_add.html +++ /dev/null @@ -1,45 +0,0 @@ -
- -
    -
  • - - {{schema.title}} -
  • -
-
- -
- -
- - - -
- - -
diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_add.js b/src/core_plugins/kibana/public/visualize/editor/agg_add.js deleted file mode 100644 index ca04d4f213e2bd..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_add.js +++ /dev/null @@ -1,30 +0,0 @@ -import { VisAggConfigProvider } from 'ui/vis/agg_config'; -import { uiModules } from 'ui/modules'; -import aggAddTemplate from 'plugins/kibana/visualize/editor/agg_add.html'; - -uiModules -.get('kibana') -.directive('visEditorAggAdd', function (Private) { - const AggConfig = Private(VisAggConfigProvider); - - return { - restrict: 'E', - template: aggAddTemplate, - controllerAs: 'add', - controller: function ($scope) { - const self = this; - - self.form = false; - self.submit = function (schema) { - self.form = false; - - const aggConfig = new AggConfig($scope.vis, { - schema: schema - }); - aggConfig.brandNew = true; - - $scope.vis.aggs.push(aggConfig); - }; - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_group.html b/src/core_plugins/kibana/public/visualize/editor/agg_group.html deleted file mode 100644 index 4aae422618a0fb..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_group.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_group.js b/src/core_plugins/kibana/public/visualize/editor/agg_group.js deleted file mode 100644 index a845e181710ecf..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_group.js +++ /dev/null @@ -1,59 +0,0 @@ -import _ from 'lodash'; -import 'plugins/kibana/visualize/editor/agg'; -import 'plugins/kibana/visualize/editor/agg_add'; -import 'plugins/kibana/visualize/editor/nesting_indicator'; -import { uiModules } from 'ui/modules'; -import aggGroupTemplate from 'plugins/kibana/visualize/editor/agg_group.html'; - -uiModules -.get('app/visualize') -.directive('visEditorAggGroup', function () { - - return { - restrict: 'E', - template: aggGroupTemplate, - scope: true, - link: function ($scope, $el, attr) { - $scope.groupName = attr.groupName; - $scope.$bind('group', 'vis.aggs.bySchemaGroup["' + $scope.groupName + '"]'); - $scope.$bind('schemas', 'vis.type.schemas["' + $scope.groupName + '"]'); - - $scope.$watchMulti([ - 'schemas', - '[]group' - ], function () { - const stats = $scope.stats = { - min: 0, - max: 0, - count: $scope.group ? $scope.group.length : 0 - }; - - if (!$scope.schemas) return; - - $scope.schemas.forEach(function (schema) { - stats.min += schema.min; - stats.max += schema.max; - stats.deprecate = schema.deprecate; - }); - - $scope.availableSchema = $scope.schemas.filter(function (schema) { - const count = _.where($scope.group, { schema }).length; - if (count < schema.max) return true; - }); - }); - - $scope.$on('agg-drag-start', () => $scope.dragging = true); - $scope.$on('agg-drag-end', () => { - $scope.dragging = false; - - //the aggs have been reordered in [group] and we need - //to apply that ordering to [vis.aggs] - const indexOffset = $scope.vis.aggs.indexOf($scope.group[0]); - _.forEach($scope.group, (agg, index) => { - _.move($scope.vis.aggs, agg, indexOffset + index); - }); - }); - } - }; - -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_param.js b/src/core_plugins/kibana/public/visualize/editor/agg_param.js deleted file mode 100644 index 650bfb7774a0a0..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_param.js +++ /dev/null @@ -1,30 +0,0 @@ -import _ from 'lodash'; -import { uiModules } from 'ui/modules'; - -uiModules -.get('app/visualize') -.directive('visAggParamEditor', function (config) { - return { - restrict: 'E', - scope: true, - template: function ($el) { - return $el.html(); - }, - link: { - pre: function ($scope, $el, attr) { - $scope.$bind('aggParam', attr.aggParam); - }, - post: function ($scope) { - $scope.config = config; - - $scope.optionEnabled = function (option) { - if (option && _.isFunction(option.enabled)) { - return option.enabled($scope.agg); - } - - return true; - }; - } - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_params.html b/src/core_plugins/kibana/public/visualize/editor/agg_params.html deleted file mode 100644 index 4ab75f4c18f1a9..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_params.html +++ /dev/null @@ -1,28 +0,0 @@ -
-

- "{{ agg.schema.title }}" aggs must run before all other buckets! -

- -
- -
-

- {{agg.error}} -

-
- -
-

- {{ agg.schema.deprecateMessage }} -

-

- "{{ agg.schema.title }}" has been deprecated. -

-
- - diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_params.js b/src/core_plugins/kibana/public/visualize/editor/agg_params.js deleted file mode 100644 index 3b87ac96c4509e..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_params.js +++ /dev/null @@ -1,155 +0,0 @@ -import $ from 'jquery'; -import aggSelectHtml from 'plugins/kibana/visualize/editor/agg_select.html'; -import advancedToggleHtml from 'plugins/kibana/visualize/editor/advanced_toggle.html'; -import 'ui/filters/match_any'; -import 'plugins/kibana/visualize/editor/agg_param'; -import { AggTypesIndexProvider } from 'ui/agg_types/index'; -import { uiModules } from 'ui/modules'; -import aggParamsTemplate from 'plugins/kibana/visualize/editor/agg_params.html'; - -uiModules -.get('app/visualize') -.directive('visEditorAggParams', function ($compile, $parse, Private) { - const aggTypes = Private(AggTypesIndexProvider); - - return { - restrict: 'E', - template: aggParamsTemplate, - scope: true, - link: function ($scope, $el, attr) { - $scope.$bind('agg', attr.agg); - $scope.$bind('groupName', attr.groupName); - - $scope.aggTypeOptions = aggTypes.byType[$scope.groupName]; - $scope.advancedToggled = false; - - // We set up this watch prior to adding the controls below, because when the controls are added, - // there is a possibility that the agg type can be automatically selected (if there is only one) - $scope.$watch('agg.type', updateAggParamEditor); - - // this will contain the controls for the schema (rows or columns?), which are unrelated to - // controls for the agg, which is why they are first - addSchemaEditor(); - - // allow selection of an aggregation - addAggSelector(); - - function addSchemaEditor() { - const $schemaEditor = $('
').addClass('schemaEditors').appendTo($el); - - if ($scope.agg.schema.editor) { - $schemaEditor.append($scope.agg.schema.editor); - $compile($schemaEditor)($scope.$new()); - } - } - - function addAggSelector() { - const $aggSelect = $(aggSelectHtml).appendTo($el); - $compile($aggSelect)($scope); - } - - // params for the selected agg, these are rebuilt every time the agg in $aggSelect changes - let $aggParamEditors; // container for agg type param editors - let $aggParamEditorsScope; - - function updateAggParamEditor(newType, oldType) { - if ($aggParamEditors) { - $aggParamEditors.remove(); - $aggParamEditors = null; - } - - // if there's an old scope, destroy it - if ($aggParamEditorsScope) { - $aggParamEditorsScope.$destroy(); - $aggParamEditorsScope = null; - } - - // create child scope, used in the editors - $aggParamEditorsScope = $scope.$new(); - $aggParamEditorsScope.indexedFields = $scope.agg.getFieldOptions(); - - const agg = $scope.agg; - if (!agg) return; - - const type = $scope.agg.type; - - if (newType !== oldType) { - // don't reset on initial load, the - // saved params should persist - agg.resetParams(); - } - - if (!type) return; - - const aggParamHTML = { - basic: [], - advanced: [] - }; - - // build collection of agg params html - type.params.forEach(function (param, i) { - let aggParam; - let fields; - if (agg.schema.hideCustomLabel && param.name === 'customLabel') { - return; - } - // if field param exists, compute allowed fields - if (param.name === 'field') { - fields = $aggParamEditorsScope.indexedFields; - } else if (param.type === 'field') { - fields = $aggParamEditorsScope[`${param.name}Options`] = param.getFieldOptions($scope.agg); - } - - if (fields) { - const hasIndexedFields = fields.length > 0; - const isExtraParam = i > 0; - if (!hasIndexedFields && isExtraParam) { // don't draw the rest of the options if there are no indexed fields. - return; - } - } - - - let type = 'basic'; - if (param.advanced) type = 'advanced'; - - if (aggParam = getAggParamHTML(param, i)) { - aggParamHTML[type].push(aggParam); - } - - }); - - // compile the paramEditors html elements - let paramEditors = aggParamHTML.basic; - - if (aggParamHTML.advanced.length) { - paramEditors.push($(advancedToggleHtml).get(0)); - paramEditors = paramEditors.concat(aggParamHTML.advanced); - } - - $aggParamEditors = $(paramEditors).appendTo($el); - $compile($aggParamEditors)($aggParamEditorsScope); - } - - // build HTML editor given an aggParam and index - function getAggParamHTML(param, idx) { - // don't show params without an editor - if (!param.editor) { - return; - } - - const attrs = { - 'agg-param': 'agg.type.params[' + idx + ']' - }; - - if (param.advanced) { - attrs['ng-show'] = 'advancedToggled'; - } - - return $('') - .attr(attrs) - .append(param.editor) - .get(0); - } - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/agg_select.html b/src/core_plugins/kibana/public/visualize/editor/agg_select.html deleted file mode 100644 index 854996b9658229..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/agg_select.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - - -
diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.html b/src/core_plugins/kibana/public/visualize/editor/editor.html index 64030eec78d8b3..909b8d0991ba98 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/core_plugins/kibana/public/visualize/editor/editor.html @@ -85,53 +85,14 @@ index-patterns="[indexPattern]" > - -
- -
+ + - -
- -
- -
- - -
- - -
-
diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.js b/src/core_plugins/kibana/public/visualize/editor/editor.js index b47353601fea09..665034db1177f7 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/core_plugins/kibana/public/visualize/editor/editor.js @@ -1,6 +1,9 @@ import _ from 'lodash'; import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations'; -import 'plugins/kibana/visualize/editor/sidebar'; + +// import 'plugins/kibana/visualize/editor/sidebar'; +import 'ui/vis/editors/default/sidebar'; + import 'plugins/kibana/visualize/editor/agg_filter'; import 'ui/visualize'; import 'ui/collapsible_sidebar'; @@ -10,9 +13,7 @@ import angular from 'angular'; import { Notifier } from 'ui/notify/notifier'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { DocTitleProvider } from 'ui/doc_title'; -import { UtilsBrushEventProvider } from 'ui/utils/brush_event'; import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; -import { FilterBarClickHandlerProvider } from 'ui/filter_bar/filter_bar_click_handler'; import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; import uiRoutes from 'ui/routes'; import { uiModules } from 'ui/modules'; @@ -69,52 +70,34 @@ uiModules }; }); -function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kbnUrl, courier, Private, Promise, kbnBaseUrl) { +function VisEditor($scope, $route, timefilter, AppState, $window, kbnUrl, courier, Private, Promise, kbnBaseUrl) { const docTitle = Private(DocTitleProvider); - const brushEvent = Private(UtilsBrushEventProvider); const queryFilter = Private(FilterBarQueryFilterProvider); - const filterBarClickHandler = Private(FilterBarClickHandlerProvider); const notify = new Notifier({ location: 'Visualization Editor' }); - let stateMonitor; - // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; - - const $appStatus = this.appStatus = { - dirty: !savedVis.id - }; - - // Instance of src/ui/public/vis/vis.js. - const vis = savedVis.vis; - - // Clone the _vis instance. - const editableVis = vis.createEditableVis(); - - // We intend to keep editableVis and vis in sync with one another, so calling `requesting` on - // vis should call it on both. - vis.requesting = function () { - const requesting = editableVis.requesting; - // Invoking requesting() calls onRequest on each agg's type param. When a vis is marked as being - // requested, the bounds of that vis are updated and new data is fetched using the new bounds. - requesting.call(vis); - - // We need to keep editableVis in sync with vis. - requesting.call(editableVis); - }; - - // SearchSource is a promise-based stream of search results that can inherit from other search - // sources. - const searchSource = savedVis.searchSource; + // vis is instance of src/ui/public/vis/vis.js. + // SearchSource is a promise-based stream of search results that can inherit from other search sources. + const { vis, searchSource } = savedVis; + $scope.vis = vis; $scope.topNavMenu = [{ key: 'save', description: 'Save Visualization', template: require('plugins/kibana/visualize/editor/panels/save.html'), testId: 'visualizeSaveButton', + disableButton() { + return Boolean(vis.dirty); + }, + tooltip() { + if (vis.dirty) { + return 'Apply or Discard your changes before saving'; + } + } }, { key: 'share', description: 'Share Visualization', @@ -127,6 +110,12 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb testId: 'visualizeRefreshButton', }]; + let stateMonitor; + + const $appStatus = this.appStatus = { + dirty: !savedVis.id + }; + if (savedVis.id) { docTitle.change(savedVis.title); } @@ -137,13 +126,13 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb const stateDefaults = { uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {}, linked: !!savedVis.savedSearchId, - query: searchSource.getOwn('query') || { query_string: { query: '*' } }, + query: searchSource.getOwn('query') || { query_string: { analyze_wildcard: true, query: '*' } }, filters: searchSource.getOwn('filter') || [], vis: savedVisState }; // Instance of app_state.js. - const $state = $scope.$state = (function initState() { + const $state = (function initState() { // This is used to sync visualization state with the url when `appState.save()` is called. const appState = new AppState(stateDefaults); @@ -152,8 +141,7 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb // appState then they won't be equal. if (!angular.equals(appState.vis, savedVisState)) { Promise.try(function () { - editableVis.setState(appState.vis); - vis.setState(editableVis.getEnabledState()); + vis.setState(appState.vis); }) .catch(courier.redirectWhenMissing({ 'index-pattern-field': '/visualize' @@ -166,10 +154,8 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb function init() { // export some objects $scope.savedVis = savedVis; - $scope.searchSource = searchSource; - $scope.vis = vis; $scope.indexPattern = vis.indexPattern; - $scope.editableVis = editableVis; + $scope.searchSource = searchSource; $scope.state = $state; $scope.queryDocLinks = documentationLinks.query; $scope.dateDocLinks = documentationLinks.date; @@ -196,30 +182,6 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => { $appStatus.dirty = status.dirty || !savedVis.id; }); - $scope.$on('$destroy', () => stateMonitor.destroy()); - - editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state); - editableVis.listeners.brush = vis.listeners.brush = brushEvent($state); - - // track state of editable vis vs. "actual" vis - $scope.stageEditableVis = transferVisState(editableVis, vis, true); - $scope.resetEditableVis = transferVisState(vis, editableVis); - $scope.$watch(function () { - return editableVis.getEnabledState(); - }, function (newState) { - editableVis.dirty = !angular.equals(newState, vis.getEnabledState()); - - $scope.responseValueAggs = null; - try { - $scope.responseValueAggs = editableVis.aggs.getResponseAggs().filter(function (agg) { - return _.get(agg, 'schema.group') === 'metrics'; - }); - } - // this can fail when the agg.type is changed but the - // params have not been set yet. watcher will trigger again - // when the params update - catch (e) {} // eslint-disable-line no-empty - }, true); $state.replace(); @@ -229,56 +191,20 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb $scope.$watchMulti([ 'searchSource.get("index").timeFieldName', - 'vis.type.requiresTimePicker', + 'vis.type.options.showTimePicker', ], function ([timeField, requiresTimePicker]) { timefilter.enabled = Boolean(timeField || requiresTimePicker); }); // update the searchSource when filters update $scope.$listen(queryFilter, 'update', function () { - searchSource.set('filter', queryFilter.getFilters()); $state.save(); }); - // fetch data when filters fire fetch event - $scope.$listen(queryFilter, 'fetch', $scope.fetch); - - - $scope.$listen($state, 'fetch_with_changes', function (keys) { - if (_.contains(keys, 'linked') && $state.linked === true) { - // abort and reload route - $route.reload(); - return; - } - - if (_.contains(keys, 'vis')) { - $state.vis.listeners = _.defaults($state.vis.listeners || {}, vis.listeners); - - // only update when we need to, otherwise colors change and we - // risk loosing an in-progress result - vis.setState($state.vis); - editableVis.setState($state.vis); - } - - // we use state to track query, must write before we fetch - if ($state.query && !$state.linked) { - searchSource.set('query', $state.query); - } else { - searchSource.set('query', null); - } - - if (_.isEqual(keys, ['filters'])) { - // updates will happen in filter watcher if needed - return; - } - - $scope.fetch(); - }); - - // Without this manual emission, we'd miss filters and queries that were on the $state initially - $state.emit('fetch_with_changes'); - - $scope.$listen(timefilter, 'fetch', _.bindKey($scope, 'fetch')); + // update the searchSource when query updates + $scope.fetch = function () { + $state.save(); + }; $scope.$on('ready:vis', function () { $scope.$emit('application.load'); @@ -286,20 +212,10 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb $scope.$on('$destroy', function () { savedVis.destroy(); + stateMonitor.destroy(); }); } - $scope.fetch = function () { - // This is used by some plugins to trigger a fetch (Timelion and Time Series Visual Builder) - $rootScope.$broadcast('fetch'); - $state.save(); - searchSource.set('filter', queryFilter.getFilters()); - if (!$state.linked) searchSource.set('query', $state.query); - if ($scope.vis.type.requiresSearch) { - courier.fetch(); - } - }; - /** * Called when the user clicks "Save" button. */ @@ -368,29 +284,5 @@ function VisEditor($rootScope, $scope, $route, timefilter, AppState, $window, kb searchSource.inherits(parentsParent); }; - function transferVisState(fromVis, toVis, stage) { - return function () { - - //verify this before we copy the "new" state - const isAggregationsChanged = !fromVis.aggs.jsonDataEquals(toVis.aggs); - - const view = fromVis.getEnabledState(); - const full = fromVis.getState(); - toVis.setState(view); - editableVis.dirty = false; - $state.vis = full; - - /** - * Only fetch (full ES round trip), if the play-button has been pressed (ie. 'stage' variable) and if there - * has been changes in the Data-tab. - */ - if (stage && isAggregationsChanged) { - $scope.fetch(); - } else { - $state.save(); - } - }; - } - init(); } diff --git a/src/core_plugins/kibana/public/visualize/editor/nesting_indicator.js b/src/core_plugins/kibana/public/visualize/editor/nesting_indicator.js deleted file mode 100644 index f9a6420f4cef53..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/nesting_indicator.js +++ /dev/null @@ -1,29 +0,0 @@ -import $ from 'jquery'; -import { createColorPalette } from 'ui/vis/components/color/color_palette'; -import { uiModules } from 'ui/modules'; - -uiModules -.get('kibana') -.directive('nestingIndicator', function () { - return { - restrict: 'E', - scope: { - item: '=', - list: '=' - }, - link: function ($scope, $el) { - $scope.$watchCollection('list', function () { - if (!$scope.list || !$scope.item) return; - - const index = $scope.list.indexOf($scope.item); - const bars = $scope.list.slice(0, index + 1); - const colors = createColorPalette(bars.length); - - $el.html(bars.map(function (bar, i) { - return $(document.createElement('span')) - .css('background-color', colors[i]); - })); - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/sidebar.html b/src/core_plugins/kibana/public/visualize/editor/sidebar.html deleted file mode 100644 index 28fad28ef7f0fb..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/sidebar.html +++ /dev/null @@ -1,112 +0,0 @@ - diff --git a/src/core_plugins/kibana/public/visualize/editor/sidebar.js b/src/core_plugins/kibana/public/visualize/editor/sidebar.js deleted file mode 100644 index 73f98224d6c92e..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/sidebar.js +++ /dev/null @@ -1,26 +0,0 @@ -import 'plugins/kibana/visualize/editor/agg_group'; -import 'plugins/kibana/visualize/editor/vis_options'; -import { uiModules } from 'ui/modules'; -import sidebarTemplate from 'plugins/kibana/visualize/editor/sidebar.html'; -uiModules -.get('app/visualize') -.directive('visEditorSidebar', function () { - - - return { - restrict: 'E', - template: sidebarTemplate, - scope: true, - controllerAs: 'sidebar', - controller: function ($scope) { - $scope.$bind('vis', 'editableVis'); - - $scope.$watch('vis.type', (visType) => { - if (visType) { - this.showData = visType.schemas.buckets || visType.schemas.metrics; - this.section = this.section || (this.showData ? 'data' : 'options'); - } - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less b/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less index 8f92c042c4bd56..14c6f640c332eb 100644 --- a/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less +++ b/src/core_plugins/kibana/public/visualize/editor/styles/_editor.less @@ -47,7 +47,11 @@ .flex-parent(0, 1, auto); } - + > visualize { + height: 100%; + flex: 1 1 auto; + display: flex; + } } @@ -301,9 +305,8 @@ } .vis-editor-canvas { - flex: 1 0 (@screen-md-min - @vis-editor-sidebar-basis); display: flex; - flex-direction: column; + flex-direction: row; overflow: auto; padding-left: @collapser-width; diff --git a/src/core_plugins/kibana/public/visualize/editor/vis_options.html b/src/core_plugins/kibana/public/visualize/editor/vis_options.html deleted file mode 100644 index 126b2ea99ab602..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/vis_options.html +++ /dev/null @@ -1,9 +0,0 @@ -
- -
-
diff --git a/src/core_plugins/kibana/public/visualize/editor/vis_options.js b/src/core_plugins/kibana/public/visualize/editor/vis_options.js deleted file mode 100644 index efb09c4628d84d..00000000000000 --- a/src/core_plugins/kibana/public/visualize/editor/vis_options.js +++ /dev/null @@ -1,35 +0,0 @@ -import { uiModules } from 'ui/modules'; -import visOptionsTemplate from 'plugins/kibana/visualize/editor/vis_options.html'; - -/** - * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. - * This lets you specify a full-screen UI for editing a vis type, instead of using the regular - * sidebar. - */ - -uiModules -.get('app/visualize') -.directive('visEditorVisOptions', function (Private, $timeout, $compile) { - return { - restrict: 'E', - template: visOptionsTemplate, - scope: { - vis: '=', - savedVis: '=', - uiState: '=', - editor: '=', - stageEditableVis: '=' - }, - link: function ($scope, $el) { - const $optionContainer = $el.find('[data-visualization-options]'); - - // Bind the `editor` template with the scope. - const $editor = $compile($scope.editor)($scope); - $optionContainer.append($editor); - - $scope.$watch('vis.type.schemas.all.length', function (len) { - $scope.alwaysShowOptions = len === 0; - }); - } - }; -}); diff --git a/src/core_plugins/kibana/public/visualize/index.js b/src/core_plugins/kibana/public/visualize/index.js index cb5ef507f4fb3d..76bf680a314c0a 100644 --- a/src/core_plugins/kibana/public/visualize/index.js +++ b/src/core_plugins/kibana/public/visualize/index.js @@ -1,16 +1,7 @@ import 'plugins/kibana/visualize/styles/main.less'; import 'plugins/kibana/visualize/editor/editor'; import 'plugins/kibana/visualize/wizard/wizard'; -import 'plugins/kibana/visualize/editor/add_bucket_agg'; -import 'plugins/kibana/visualize/editor/agg'; -import 'plugins/kibana/visualize/editor/agg_add'; import 'plugins/kibana/visualize/editor/agg_filter'; -import 'plugins/kibana/visualize/editor/agg_group'; -import 'plugins/kibana/visualize/editor/agg_param'; -import 'plugins/kibana/visualize/editor/agg_params'; -import 'plugins/kibana/visualize/editor/nesting_indicator'; -import 'plugins/kibana/visualize/editor/sidebar'; -import 'plugins/kibana/visualize/editor/vis_options'; import 'ui/draggable/draggable_container'; import 'ui/draggable/draggable_item'; import 'ui/draggable/draggable_handle'; @@ -24,6 +15,17 @@ import { VisualizeListingController } from './listing/visualize_listing'; import { VisualizeConstants } from './visualize_constants'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { savedVisualizationProvider } from 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register'; +import { noneRequestHandlerProvider } from 'ui/vis/request_handlers/none'; +import { CourierRequestHandlerProvider } from 'ui/vis/request_handlers/courier'; +import { noneResponseHandler } from 'ui/vis/response_handlers/none'; +import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic'; + +import { defaultEditor } from 'ui/vis/editors/default/default'; + + +import { RequestHandlersRegistryProvider } from 'ui/registry/request_handlers'; +import { ResponseHandlersRegistryProvider } from 'ui/registry/response_handlers'; +import { EditorTypesRegistryProvider } from 'ui/registry/editor_types'; uiRoutes .defaults(/visualize/, { @@ -36,5 +38,10 @@ uiRoutes }); // preloading - SavedObjectRegistryProvider.register(savedVisualizationProvider); +RequestHandlersRegistryProvider.register(CourierRequestHandlerProvider); +RequestHandlersRegistryProvider.register(noneRequestHandlerProvider); +ResponseHandlersRegistryProvider.register(noneResponseHandler); +ResponseHandlersRegistryProvider.register(BasicResponseHandlerProvider); +EditorTypesRegistryProvider.register(defaultEditor); + diff --git a/src/core_plugins/kibana/public/visualize/wizard/wizard.js b/src/core_plugins/kibana/public/visualize/wizard/wizard.js index 776b1174b53ca0..f59a2fc5e41b56 100644 --- a/src/core_plugins/kibana/public/visualize/wizard/wizard.js +++ b/src/core_plugins/kibana/public/visualize/wizard/wizard.js @@ -5,7 +5,7 @@ import 'plugins/kibana/discover/saved_searches/saved_searches'; import './wizard.less'; import _ from 'lodash'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; +import { CATEGORY } from 'ui/vis/vis_category'; import { DashboardConstants } from 'plugins/kibana/dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; import routes from 'ui/routes'; @@ -33,15 +33,13 @@ routes.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, timefilter, Private) { timefilter.enabled = false; - const VisType = Private(VisVisTypeProvider); - const visTypeCategoryToHumanReadableMap = { - [VisType.CATEGORY.BASIC]: 'Basic Charts', - [VisType.CATEGORY.DATA]: 'Data', - [VisType.CATEGORY.GRAPHIC]: 'Graphic', - [VisType.CATEGORY.MAP]: 'Maps', - [VisType.CATEGORY.OTHER]: 'Other', - [VisType.CATEGORY.TIME]: 'Time Series', + [CATEGORY.BASIC]: 'Basic Charts', + [CATEGORY.DATA]: 'Data', + [CATEGORY.GRAPHIC]: 'Graphic', + [CATEGORY.MAP]: 'Maps', + [CATEGORY.OTHER]: 'Other', + [CATEGORY.TIME]: 'Time Series' }; const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; @@ -54,6 +52,8 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time visTypes.forEach(visType => { const categoryName = visType.category; + if (categoryName === CATEGORY.HIDDEN) return; + // Create category object if it doesn't exist yet. if (!categoryToVisTypesMap[categoryName]) { categoryToVisTypesMap[categoryName] = { @@ -74,7 +74,7 @@ module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, time // Sort the categories alphabetically. const sortedVisTypeCategories = Object.values(categoryToVisTypesMap).sort((a, b) => { - const other = VisType.CATEGORY.OTHER.toLowerCase(); + const other = CATEGORY.OTHER.toLowerCase(); // Put "other" category at the end of the list. const labelA = a.label.toLowerCase(); From edc4cfe512c634f309cf62575cc4cdda3e6facf0 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:19:31 +0200 Subject: [PATCH 05/40] vis, default editor, request handlers and vis types --- src/ui/public/vis/__tests__/_agg_configs.js | 2 +- .../response_handlers/_build_chart_data.js | 133 +++++++++++++++ .../vis/editors/default/__tests__/agg.js | 83 ++++++++++ .../editors/default/__tests__/agg_params.js | 107 ++++++++++++ .../vis/editors/default/advanced_toggle.html | 6 + src/ui/public/vis/editors/default/agg.html | 94 +++++++++++ src/ui/public/vis/editors/default/agg.js | 89 ++++++++++ .../public/vis/editors/default/agg_add.html | 45 +++++ src/ui/public/vis/editors/default/agg_add.js | 30 ++++ .../public/vis/editors/default/agg_group.html | 15 ++ .../public/vis/editors/default/agg_group.js | 60 +++++++ .../public/vis/editors/default/agg_param.js | 30 ++++ .../vis/editors/default/agg_params.html | 28 ++++ .../public/vis/editors/default/agg_params.js | 155 ++++++++++++++++++ .../vis/editors/default/agg_select.html | 12 ++ .../public/vis/editors/default/default.html | 7 + src/ui/public/vis/editors/default/default.js | 94 +++++++++++ .../vis/editors/default/nesting_indicator.js | 29 ++++ .../vis/{ => editors/default}/schemas.js | 0 .../public/vis/editors/default/sidebar.html | 115 +++++++++++++ src/ui/public/vis/editors/default/sidebar.js | 26 +++ .../vis/editors/default/vis_options.html | 7 + .../public/vis/editors/default/vis_options.js | 58 +++++++ src/ui/public/vis/renderbot.js | 50 ------ src/ui/public/vis/request_handlers/courier.js | 29 ++++ src/ui/public/vis/request_handlers/none.js | 12 ++ src/ui/public/vis/response_handlers/basic.js | 75 +++++++++ src/ui/public/vis/response_handlers/none.js | 17 ++ src/ui/public/vis/response_handlers/tabify.js | 22 +++ src/ui/public/vis/vis.js | 137 ++++++++-------- src/ui/public/vis/vis_category.js | 8 + src/ui/public/vis/vis_factory.js | 23 +++ src/ui/public/vis/vis_type.js | 47 ------ .../public/vis/vis_types/angular_vis_type.js | 63 +++++++ src/ui/public/vis/vis_types/base_vis_type.js | 54 ++++++ src/ui/public/vis/vis_types/index.js | 6 + src/ui/public/vis/vis_types/react_vis_type.js | 47 ++++++ .../public/vis/vis_types/vislib_vis_type.js | 67 ++++++++ 38 files changed, 1711 insertions(+), 171 deletions(-) create mode 100644 src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js create mode 100644 src/ui/public/vis/editors/default/__tests__/agg.js create mode 100644 src/ui/public/vis/editors/default/__tests__/agg_params.js create mode 100644 src/ui/public/vis/editors/default/advanced_toggle.html create mode 100644 src/ui/public/vis/editors/default/agg.html create mode 100644 src/ui/public/vis/editors/default/agg.js create mode 100644 src/ui/public/vis/editors/default/agg_add.html create mode 100644 src/ui/public/vis/editors/default/agg_add.js create mode 100644 src/ui/public/vis/editors/default/agg_group.html create mode 100644 src/ui/public/vis/editors/default/agg_group.js create mode 100644 src/ui/public/vis/editors/default/agg_param.js create mode 100644 src/ui/public/vis/editors/default/agg_params.html create mode 100644 src/ui/public/vis/editors/default/agg_params.js create mode 100644 src/ui/public/vis/editors/default/agg_select.html create mode 100644 src/ui/public/vis/editors/default/default.html create mode 100644 src/ui/public/vis/editors/default/default.js create mode 100644 src/ui/public/vis/editors/default/nesting_indicator.js rename src/ui/public/vis/{ => editors/default}/schemas.js (100%) create mode 100644 src/ui/public/vis/editors/default/sidebar.html create mode 100644 src/ui/public/vis/editors/default/sidebar.js create mode 100644 src/ui/public/vis/editors/default/vis_options.html create mode 100644 src/ui/public/vis/editors/default/vis_options.js delete mode 100644 src/ui/public/vis/renderbot.js create mode 100644 src/ui/public/vis/request_handlers/courier.js create mode 100644 src/ui/public/vis/request_handlers/none.js create mode 100644 src/ui/public/vis/response_handlers/basic.js create mode 100644 src/ui/public/vis/response_handlers/none.js create mode 100644 src/ui/public/vis/response_handlers/tabify.js create mode 100644 src/ui/public/vis/vis_category.js create mode 100644 src/ui/public/vis/vis_factory.js delete mode 100644 src/ui/public/vis/vis_type.js create mode 100644 src/ui/public/vis/vis_types/angular_vis_type.js create mode 100644 src/ui/public/vis/vis_types/base_vis_type.js create mode 100644 src/ui/public/vis/vis_types/index.js create mode 100644 src/ui/public/vis/vis_types/react_vis_type.js create mode 100644 src/ui/public/vis/vis_types/vislib_vis_type.js diff --git a/src/ui/public/vis/__tests__/_agg_configs.js b/src/ui/public/vis/__tests__/_agg_configs.js index 60898d713d3b3d..9a47cb87e8d98d 100644 --- a/src/ui/public/vis/__tests__/_agg_configs.js +++ b/src/ui/public/vis/__tests__/_agg_configs.js @@ -6,7 +6,7 @@ import { VisAggConfigProvider } from 'ui/vis/agg_config'; import { VisProvider } from 'ui/vis'; import { VisAggConfigsProvider } from 'ui/vis/agg_configs'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import { IndexedArray } from 'ui/indexed_array'; describe('AggConfigs', function () { diff --git a/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js b/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js new file mode 100644 index 00000000000000..79804f649d6181 --- /dev/null +++ b/src/ui/public/vis/__tests__/response_handlers/_build_chart_data.js @@ -0,0 +1,133 @@ +import _ from 'lodash'; +import ngMock from 'ng_mock'; +import expect from 'expect.js'; +import sinon from 'auto-release-sinon'; +import { AggResponseTabifyTableProvider } from 'ui/agg_response/tabify/_table'; +import { AggResponseIndexProvider } from 'ui/agg_response/index'; +import { VislibVisTypeBuildChartDataProvider } from 'ui/vislib_vis_type/build_chart_data'; + +describe('renderbot#buildChartData', function () { + let buildChartData; + let aggResponse; + let Table; + + beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.inject(function (Private) { + Table = Private(AggResponseTabifyTableProvider); + aggResponse = Private(AggResponseIndexProvider); + buildChartData = Private(VislibVisTypeBuildChartDataProvider); + })); + + describe('for hierarchical vis', function () { + it('defers to hierarchical aggResponse converter', function () { + const football = {}; + const renderbot = { + vis: { + isHierarchical: _.constant(true) + } + }; + + const stub = sinon.stub(aggResponse, 'hierarchical').returns(football); + expect(buildChartData.call(renderbot, football)).to.be(football); + expect(stub).to.have.property('callCount', 1); + expect(stub.firstCall.args[0]).to.be(renderbot.vis); + expect(stub.firstCall.args[1]).to.be(football); + }); + }); + + describe('for point plot', function () { + it('calls tabify to simplify the data into a table', function () { + const renderbot = { + vis: { + isHierarchical: _.constant(false) + } + }; + const football = { tables: [], hits: { total: 1 } }; + + const stub = sinon.stub(aggResponse, 'tabify').returns(football); + expect(buildChartData.call(renderbot, football)).to.eql({ rows: [], hits: 1 }); + expect(stub).to.have.property('callCount', 1); + expect(stub.firstCall.args[0]).to.be(renderbot.vis); + expect(stub.firstCall.args[1]).to.be(football); + }); + + it('returns a single chart if the tabify response contains only a single table', function () { + const chart = { hits: 1, rows: [], columns: [] }; + const renderbot = { + vis: { + isHierarchical: _.constant(false), + type: { + responseConverter: _.constant(chart) + } + } + }; + const esResp = { hits: { total: 1 } }; + const tabbed = { tables: [ new Table() ] }; + + sinon.stub(aggResponse, 'tabify').returns(tabbed); + expect(buildChartData.call(renderbot, esResp)).to.eql(chart); + }); + + it('converts table groups into rows/columns wrappers for charts', function () { + const converter = sinon.stub().returns('chart'); + const esResp = { hits: { total: 1 } }; + const tables = [new Table(), new Table(), new Table(), new Table()]; + + const renderbot = { + vis: { + isHierarchical: _.constant(false), + type: { + responseConverter: converter + } + } + }; + + sinon.stub(aggResponse, 'tabify').returns({ + tables: [ + { + aggConfig: { params: { row: true } }, + tables: [ + { + aggConfig: { params: { row: false } }, + tables: [ tables[0] ] + }, + { + aggConfig: { params: { row: false } }, + tables: [ tables[1] ] + } + ] + }, + { + aggConfig: { params: { row: true } }, + tables: [ + { + aggConfig: { params: { row: false } }, + tables: [ tables[2] ] + }, + { + aggConfig: { params: { row: false } }, + tables: [ tables[3] ] + } + ] + } + ] + }); + + const chartData = buildChartData.call(renderbot, esResp); + + // verify tables were converted + expect(converter).to.have.property('callCount', 4); + expect(converter.args[0][1]).to.be(tables[0]); + expect(converter.args[1][1]).to.be(tables[1]); + expect(converter.args[2][1]).to.be(tables[2]); + expect(converter.args[3][1]).to.be(tables[3]); + + expect(chartData).to.have.property('rows'); + expect(chartData.rows).to.have.length(2); + chartData.rows.forEach(function (row) { + expect(row).to.have.property('columns'); + expect(row.columns).to.eql([ 'chart', 'chart' ]); + }); + }); + }); +}); diff --git a/src/ui/public/vis/editors/default/__tests__/agg.js b/src/ui/public/vis/editors/default/__tests__/agg.js new file mode 100644 index 00000000000000..3309376423964f --- /dev/null +++ b/src/ui/public/vis/editors/default/__tests__/agg.js @@ -0,0 +1,83 @@ + +import angular from 'angular'; +import _ from 'lodash'; +import expect from 'expect.js'; +import ngMock from 'ng_mock'; +import '../agg'; + + +describe('Vis-Editor-Agg plugin directive', function () { + const $parentScope = {}; + let $elem; + + function makeConfig(which) { + const schemaMap = { + radius: { + title: 'Dot Size', + min: 0, + max: 1 + }, + metric: { + title: 'Y-Axis', + min: 1, + max: Infinity + } + }; + const typeOptions = ['count', 'avg', 'sum', 'min', 'max', 'cardinality']; + which = which || 'metric'; + + const schema = schemaMap[which]; + + return { + min: schema.min, + max: schema.max, + name: which, + title: schema.title, + group: 'metrics', + aggFilter: typeOptions, + // AggParams object + params: [] + }; + } + + beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.inject(function ($rootScope, $compile) { + $parentScope.agg = { + id: 1, + params: {}, + schema: makeConfig(), + getFieldOptions: () => null + }; + $parentScope.groupName = 'metrics'; + $parentScope.group = [{ + id: '1', + schema: makeConfig() + }, { + id: '2', + schema: makeConfig('radius') + }]; + + // share the scope + _.defaults($parentScope, $rootScope, Object.getPrototypeOf($rootScope)); + + // make the element + $elem = angular.element( + '' + ); + + // compile the html + $compile($elem)($parentScope); + + // Digest everything + $elem.scope().$digest(); + })); + + it('should only add the close button if there is more than the minimum', function () { + expect($parentScope.canRemove($parentScope.agg)).to.be(false); + $parentScope.group.push({ + id: '3', + schema: makeConfig() + }); + expect($parentScope.canRemove($parentScope.agg)).to.be(true); + }); +}); diff --git a/src/ui/public/vis/editors/default/__tests__/agg_params.js b/src/ui/public/vis/editors/default/__tests__/agg_params.js new file mode 100644 index 00000000000000..55385f2b57fa55 --- /dev/null +++ b/src/ui/public/vis/editors/default/__tests__/agg_params.js @@ -0,0 +1,107 @@ + +import angular from 'angular'; +import _ from 'lodash'; +import expect from 'expect.js'; +import ngMock from 'ng_mock'; +import '../agg_params'; +import { VisProvider } from 'ui/vis'; +import { VisAggConfigProvider } from 'ui/vis/agg_config'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; + + +describe('Vis-Editor-Agg-Params plugin directive', function () { + let $parentScope = {}; + let Vis; + let vis; + let AggConfig; + let Schemas; + let $elem; + let compile; + let rootScope; + + const aggFilter = [ + '!top_hits', '!percentiles', '!median', '!std_dev', + '!derivative', '!cumulative_sum', '!moving_avg', '!serial_diff' + ]; + + let indexPattern; + let orderAggSchema; + + beforeEach(ngMock.module('kibana')); + beforeEach(ngMock.inject(function (Private, $rootScope, $compile) { + rootScope = $rootScope; + compile = $compile; + + Vis = Private(VisProvider); + indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + Schemas = Private(VisSchemasProvider); + AggConfig = Private(VisAggConfigProvider); + })); + + function init(config) { + $parentScope = {}; + _.defaults($parentScope, rootScope, Object.getPrototypeOf(rootScope)); + + orderAggSchema = (new Schemas([config])).all[0]; + $parentScope.groupName = 'metrics'; + + const state = { + schema: orderAggSchema, + type: 'count' + }; + + vis = new Vis(indexPattern, { + type: 'histogram', + aggs: [ + { + type: 'date_histogram', + schema: 'segment' + } + ] + }); + + $parentScope.agg = new AggConfig(vis, state); + + // make the element + $elem = angular.element( + `` + ); + + // compile the html + compile($elem)($parentScope); + + // Digest everything + $elem.scope().$digest(); + } + + afterEach(function () { + $parentScope.$destroy(); + $parentScope = null; + }); + + it('should show custom label parameter', function () { + init ({ + group: 'none', + name: 'orderAgg', + title: 'Order Agg', + aggFilter: aggFilter + }); + + const customLabelElement = $elem.find('label:contains("Custom Label")'); + expect(customLabelElement.length).to.be(1); + }); + + it('should hide custom label parameter', function () { + init ({ + group: 'none', + name: 'orderAgg', + title: 'Order Agg', + hideCustomLabel: true, + aggFilter: aggFilter + }); + + const customLabelElement = $elem.find('label:contains("Custom Label")'); + expect(customLabelElement.length).to.be(0); + }); +}); diff --git a/src/ui/public/vis/editors/default/advanced_toggle.html b/src/ui/public/vis/editors/default/advanced_toggle.html new file mode 100644 index 00000000000000..46fa98de80757e --- /dev/null +++ b/src/ui/public/vis/editors/default/advanced_toggle.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/ui/public/vis/editors/default/agg.html b/src/ui/public/vis/editors/default/agg.html new file mode 100644 index 00000000000000..140f15e08685b7 --- /dev/null +++ b/src/ui/public/vis/editors/default/agg.html @@ -0,0 +1,94 @@ + +
+ + + + + + + {{ agg.schema.title }} + + + + + {{ describe() }} + + + + + {{ aggForm.describeErrors() }} + + + +
+ + + + + + + + + + + + +
+ +
+ + + + + + diff --git a/src/ui/public/vis/editors/default/agg.js b/src/ui/public/vis/editors/default/agg.js new file mode 100644 index 00000000000000..9c64547dff3efd --- /dev/null +++ b/src/ui/public/vis/editors/default/agg.js @@ -0,0 +1,89 @@ +import './agg_params'; +import './agg_add'; +import _ from 'lodash'; +import { uiModules } from 'ui/modules'; +import aggTemplate from './agg.html'; +uiModules +.get('app/visualize') +.directive('visEditorAgg', function ($compile, $parse, $filter, Private, Notifier) { + const notify = new Notifier({ + location: 'visAggGroup' + }); + + return { + restrict: 'A', + template: aggTemplate, + require: 'form', + link: function ($scope, $el, attrs, kbnForm) { + $scope.editorOpen = !!$scope.agg.brandNew; + + $scope.$watch('editorOpen', function (open) { + // make sure that all of the form inputs are "touched" + // so that their errors propogate + if (!open) kbnForm.$setTouched(); + }); + + $scope.$watchMulti([ + '$index', + 'group.length' + ], function () { + $scope.aggIsTooLow = calcAggIsTooLow(); + }); + + /** + * Describe the aggregation, for display in the collapsed agg header + * @return {[type]} [description] + */ + $scope.describe = function () { + if (!$scope.agg.type.makeLabel) return ''; + const label = $scope.agg.type.makeLabel($scope.agg); + return label ? label : ''; + }; + + $scope.$on('drag-start', () => { + $scope.editorWasOpen = $scope.editorOpen; + $scope.editorOpen = false; + $scope.$emit('agg-drag-start', $scope.agg); + }); + + $scope.$on('drag-end', () => { + $scope.editorOpen = $scope.editorWasOpen; + $scope.$emit('agg-drag-end', $scope.agg); + }); + + $scope.remove = function (agg) { + const aggs = $scope.vis.aggs; + + const index = aggs.indexOf(agg); + if (index === -1) return notify.log('already removed'); + + aggs.splice(index, 1); + }; + + $scope.canRemove = function (aggregation) { + const metricCount = _.reduce($scope.group, function (count, agg) { + return (agg.schema.name === aggregation.schema.name) ? ++count : count; + }, 0); + + // make sure the the number of these aggs is above the min + return metricCount > aggregation.schema.min; + }; + + function calcAggIsTooLow() { + if (!$scope.agg.schema.mustBeFirst) { + return false; + } + + const firstDifferentSchema = _.findIndex($scope.group, function (agg) { + return agg.schema !== $scope.agg.schema; + }); + + if (firstDifferentSchema === -1) { + return false; + } + + return $scope.$index > firstDifferentSchema; + } + } + }; +}); diff --git a/src/ui/public/vis/editors/default/agg_add.html b/src/ui/public/vis/editors/default/agg_add.html new file mode 100644 index 00000000000000..0c0b037a17062d --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_add.html @@ -0,0 +1,45 @@ +
+ +
    +
  • + + {{schema.title}} +
  • +
+
+ +
+ +
+ + + +
+ + +
diff --git a/src/ui/public/vis/editors/default/agg_add.js b/src/ui/public/vis/editors/default/agg_add.js new file mode 100644 index 00000000000000..57cd4032c8714f --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_add.js @@ -0,0 +1,30 @@ +import { VisAggConfigProvider } from 'ui/vis/agg_config'; +import { uiModules } from 'ui/modules'; +import aggAddTemplate from './agg_add.html'; + +uiModules +.get('kibana') +.directive('visEditorAggAdd', function (Private) { + const AggConfig = Private(VisAggConfigProvider); + + return { + restrict: 'E', + template: aggAddTemplate, + controllerAs: 'add', + controller: function ($scope) { + const self = this; + + self.form = false; + self.submit = function (schema) { + self.form = false; + + const aggConfig = new AggConfig($scope.vis, { + schema: schema + }); + aggConfig.brandNew = true; + + $scope.vis.aggs.push(aggConfig); + }; + } + }; +}); diff --git a/src/ui/public/vis/editors/default/agg_group.html b/src/ui/public/vis/editors/default/agg_group.html new file mode 100644 index 00000000000000..32b124a8255bcb --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_group.html @@ -0,0 +1,15 @@ + diff --git a/src/ui/public/vis/editors/default/agg_group.js b/src/ui/public/vis/editors/default/agg_group.js new file mode 100644 index 00000000000000..723bd211ce1a10 --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_group.js @@ -0,0 +1,60 @@ +import _ from 'lodash'; +import './agg'; +import './agg_add'; +import './nesting_indicator'; + +import { uiModules } from 'ui/modules'; +import aggGroupTemplate from './agg_group.html'; + +uiModules +.get('app/visualize') +.directive('visEditorAggGroup', function () { + + return { + restrict: 'E', + template: aggGroupTemplate, + scope: true, + link: function ($scope, $el, attr) { + $scope.groupName = attr.groupName; + $scope.$bind('group', 'vis.aggs.bySchemaGroup["' + $scope.groupName + '"]'); + $scope.$bind('schemas', 'vis.type.schemas["' + $scope.groupName + '"]'); + + $scope.$watchMulti([ + 'schemas', + '[]group' + ], function () { + const stats = $scope.stats = { + min: 0, + max: 0, + count: $scope.group ? $scope.group.length : 0 + }; + + if (!$scope.schemas) return; + + $scope.schemas.forEach(function (schema) { + stats.min += schema.min; + stats.max += schema.max; + stats.deprecate = schema.deprecate; + }); + + $scope.availableSchema = $scope.schemas.filter(function (schema) { + const count = _.where($scope.group, { schema }).length; + if (count < schema.max) return true; + }); + }); + + $scope.$on('agg-drag-start', () => $scope.dragging = true); + $scope.$on('agg-drag-end', () => { + $scope.dragging = false; + + //the aggs have been reordered in [group] and we need + //to apply that ordering to [vis.aggs] + const indexOffset = $scope.vis.aggs.indexOf($scope.group[0]); + _.forEach($scope.group, (agg, index) => { + _.move($scope.vis.aggs, agg, indexOffset + index); + }); + }); + } + }; + +}); diff --git a/src/ui/public/vis/editors/default/agg_param.js b/src/ui/public/vis/editors/default/agg_param.js new file mode 100644 index 00000000000000..650bfb7774a0a0 --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_param.js @@ -0,0 +1,30 @@ +import _ from 'lodash'; +import { uiModules } from 'ui/modules'; + +uiModules +.get('app/visualize') +.directive('visAggParamEditor', function (config) { + return { + restrict: 'E', + scope: true, + template: function ($el) { + return $el.html(); + }, + link: { + pre: function ($scope, $el, attr) { + $scope.$bind('aggParam', attr.aggParam); + }, + post: function ($scope) { + $scope.config = config; + + $scope.optionEnabled = function (option) { + if (option && _.isFunction(option.enabled)) { + return option.enabled($scope.agg); + } + + return true; + }; + } + } + }; +}); diff --git a/src/ui/public/vis/editors/default/agg_params.html b/src/ui/public/vis/editors/default/agg_params.html new file mode 100644 index 00000000000000..4ab75f4c18f1a9 --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_params.html @@ -0,0 +1,28 @@ +
+

+ "{{ agg.schema.title }}" aggs must run before all other buckets! +

+ +
+ +
+

+ {{agg.error}} +

+
+ +
+

+ {{ agg.schema.deprecateMessage }} +

+

+ "{{ agg.schema.title }}" has been deprecated. +

+
+ + diff --git a/src/ui/public/vis/editors/default/agg_params.js b/src/ui/public/vis/editors/default/agg_params.js new file mode 100644 index 00000000000000..e2905168fe5030 --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_params.js @@ -0,0 +1,155 @@ +import $ from 'jquery'; +import aggSelectHtml from './agg_select.html'; +import advancedToggleHtml from './advanced_toggle.html'; +import 'ui/filters/match_any'; +import './agg_param'; +import { AggTypesIndexProvider } from 'ui/agg_types/index'; +import { uiModules } from 'ui/modules'; +import aggParamsTemplate from './agg_params.html'; + +uiModules +.get('app/visualize') +.directive('visEditorAggParams', function ($compile, $parse, Private) { + const aggTypes = Private(AggTypesIndexProvider); + + return { + restrict: 'E', + template: aggParamsTemplate, + scope: true, + link: function ($scope, $el, attr) { + $scope.$bind('agg', attr.agg); + $scope.$bind('groupName', attr.groupName); + + $scope.aggTypeOptions = aggTypes.byType[$scope.groupName]; + $scope.advancedToggled = false; + + // We set up this watch prior to adding the controls below, because when the controls are added, + // there is a possibility that the agg type can be automatically selected (if there is only one) + $scope.$watch('agg.type', updateAggParamEditor); + + // this will contain the controls for the schema (rows or columns?), which are unrelated to + // controls for the agg, which is why they are first + addSchemaEditor(); + + // allow selection of an aggregation + addAggSelector(); + + function addSchemaEditor() { + const $schemaEditor = $('
').addClass('schemaEditors').appendTo($el); + + if ($scope.agg.schema.editor) { + $schemaEditor.append($scope.agg.schema.editor); + $compile($schemaEditor)($scope.$new()); + } + } + + function addAggSelector() { + const $aggSelect = $(aggSelectHtml).appendTo($el); + $compile($aggSelect)($scope); + } + + // params for the selected agg, these are rebuilt every time the agg in $aggSelect changes + let $aggParamEditors; // container for agg type param editors + let $aggParamEditorsScope; + + function updateAggParamEditor(newType, oldType) { + if ($aggParamEditors) { + $aggParamEditors.remove(); + $aggParamEditors = null; + } + + // if there's an old scope, destroy it + if ($aggParamEditorsScope) { + $aggParamEditorsScope.$destroy(); + $aggParamEditorsScope = null; + } + + // create child scope, used in the editors + $aggParamEditorsScope = $scope.$new(); + $aggParamEditorsScope.indexedFields = $scope.agg.getFieldOptions(); + + const agg = $scope.agg; + if (!agg) return; + + const type = $scope.agg.type; + + if (newType !== oldType) { + // don't reset on initial load, the + // saved params should persist + agg.resetParams(); + } + + if (!type) return; + + const aggParamHTML = { + basic: [], + advanced: [] + }; + + // build collection of agg params html + type.params.forEach(function (param, i) { + let aggParam; + let fields; + if (agg.schema.hideCustomLabel && param.name === 'customLabel') { + return; + } + // if field param exists, compute allowed fields + if (param.name === 'field') { + fields = $aggParamEditorsScope.indexedFields; + } else if (param.type === 'field') { + fields = $aggParamEditorsScope[`${param.name}Options`] = param.getFieldOptions($scope.agg); + } + + if (fields) { + const hasIndexedFields = fields.length > 0; + const isExtraParam = i > 0; + if (!hasIndexedFields && isExtraParam) { // don't draw the rest of the options if there are no indexed fields. + return; + } + } + + + let type = 'basic'; + if (param.advanced) type = 'advanced'; + + if (aggParam = getAggParamHTML(param, i)) { + aggParamHTML[type].push(aggParam); + } + + }); + + // compile the paramEditors html elements + let paramEditors = aggParamHTML.basic; + + if (aggParamHTML.advanced.length) { + paramEditors.push($(advancedToggleHtml).get(0)); + paramEditors = paramEditors.concat(aggParamHTML.advanced); + } + + $aggParamEditors = $(paramEditors).appendTo($el); + $compile($aggParamEditors)($aggParamEditorsScope); + } + + // build HTML editor given an aggParam and index + function getAggParamHTML(param, idx) { + // don't show params without an editor + if (!param.editor) { + return; + } + + const attrs = { + 'agg-param': 'agg.type.params[' + idx + ']' + }; + + if (param.advanced) { + attrs['ng-show'] = 'advancedToggled'; + } + + return $('') + .attr(attrs) + .append(param.editor) + .get(0); + } + } + }; +}); diff --git a/src/ui/public/vis/editors/default/agg_select.html b/src/ui/public/vis/editors/default/agg_select.html new file mode 100644 index 00000000000000..854996b9658229 --- /dev/null +++ b/src/ui/public/vis/editors/default/agg_select.html @@ -0,0 +1,12 @@ +
+ + + +
diff --git a/src/ui/public/vis/editors/default/default.html b/src/ui/public/vis/editors/default/default.html new file mode 100644 index 00000000000000..39671645110a67 --- /dev/null +++ b/src/ui/public/vis/editors/default/default.html @@ -0,0 +1,7 @@ +
+ +
+ +
+ +
diff --git a/src/ui/public/vis/editors/default/default.js b/src/ui/public/vis/editors/default/default.js new file mode 100644 index 00000000000000..518a16378f51f8 --- /dev/null +++ b/src/ui/public/vis/editors/default/default.js @@ -0,0 +1,94 @@ +import './sidebar'; +import './vis_options'; +import $ from 'jquery'; + + +import _ from 'lodash'; +import angular from 'angular'; +import defaultEditorTemplate from './default.html'; + +const defaultEditor = function ($rootScope, $compile) { + return class DefaultEditor { + static key = 'default'; + + constructor(el, vis) { + this.el = $(el); + this.vis = vis; + + if (!this.vis.type.editorConfig.optionTabs && this.vis.type.editorConfig.optionsTemplate) { + this.vis.type.editorConfig.optionTabs = [ + { name: 'options', title: 'Options', editor: this.vis.type.editorConfig.optionsTemplate } + ]; + } + } + + render(visData, searchSource) { + let $scope; + + const updateScope = () => { + $scope.vis = this.vis; + $scope.visData = visData; + $scope.uiState = this.vis.getUiState(); + $scope.searchSource = searchSource; + }; + + return new Promise(resolve => { + if (!this.$scope) { + this.$scope = $scope = $rootScope.$new(); + + updateScope(); + + // track state of editable vis vs. "actual" vis + $scope.stageEditableVis = () => { + $scope.vis.updateState(); + $scope.vis.dirty = false; + }; + $scope.resetEditableVis = () => { + $scope.vis.resetState(); + $scope.vis.dirty = false; + }; + + $scope.$watch(function () { + return $scope.vis.getCurrentState(false); + }, function (newState) { + $scope.vis.dirty = !angular.equals(newState, $scope.vis.getEnabledState()); + + $scope.responseValueAggs = null; + try { + $scope.responseValueAggs = $scope.vis.aggs.getResponseAggs().filter(function (agg) { + return _.get(agg, 'schema.group') === 'metrics'; + }); + } + // this can fail when the agg.type is changed but the + // params have not been set yet. watcher will trigger again + // when the params update + catch (e) {} // eslint-disable-line no-empty + }, true); + + this.el.html($compile(defaultEditorTemplate)($scope)); + } else { + $scope = this.$scope; + updateScope(); + } + + $scope.$broadcast('render'); + + resolve(true); + }); + } + + resize() { + + } + + destroy() { + if (this.$scope) { + this.$scope.$destroy(); + this.$scope = null; + } + } + resize() {} + }; +}; + +export { defaultEditor }; diff --git a/src/ui/public/vis/editors/default/nesting_indicator.js b/src/ui/public/vis/editors/default/nesting_indicator.js new file mode 100644 index 00000000000000..f9a6420f4cef53 --- /dev/null +++ b/src/ui/public/vis/editors/default/nesting_indicator.js @@ -0,0 +1,29 @@ +import $ from 'jquery'; +import { createColorPalette } from 'ui/vis/components/color/color_palette'; +import { uiModules } from 'ui/modules'; + +uiModules +.get('kibana') +.directive('nestingIndicator', function () { + return { + restrict: 'E', + scope: { + item: '=', + list: '=' + }, + link: function ($scope, $el) { + $scope.$watchCollection('list', function () { + if (!$scope.list || !$scope.item) return; + + const index = $scope.list.indexOf($scope.item); + const bars = $scope.list.slice(0, index + 1); + const colors = createColorPalette(bars.length); + + $el.html(bars.map(function (bar, i) { + return $(document.createElement('span')) + .css('background-color', colors[i]); + })); + }); + } + }; +}); diff --git a/src/ui/public/vis/schemas.js b/src/ui/public/vis/editors/default/schemas.js similarity index 100% rename from src/ui/public/vis/schemas.js rename to src/ui/public/vis/editors/default/schemas.js diff --git a/src/ui/public/vis/editors/default/sidebar.html b/src/ui/public/vis/editors/default/sidebar.html new file mode 100644 index 00000000000000..6b2b609486e564 --- /dev/null +++ b/src/ui/public/vis/editors/default/sidebar.html @@ -0,0 +1,115 @@ + diff --git a/src/ui/public/vis/editors/default/sidebar.js b/src/ui/public/vis/editors/default/sidebar.js new file mode 100644 index 00000000000000..864528742e6e20 --- /dev/null +++ b/src/ui/public/vis/editors/default/sidebar.js @@ -0,0 +1,26 @@ +import './agg_group'; +import './vis_options'; +import { uiModules } from 'ui/modules'; +import sidebarTemplate from './sidebar.html'; + +uiModules +.get('app/visualize') +.directive('visEditorSidebar', function () { + + + return { + restrict: 'E', + template: sidebarTemplate, + scope: true, + controllerAs: 'sidebar', + controller: function ($scope) { + + $scope.$watch('vis.type', (visType) => { + if (visType) { + this.showData = visType.schemas.buckets || visType.schemas.metrics; + this.section = this.section || (this.showData ? 'data' : 'options'); + } + }); + } + }; +}); diff --git a/src/ui/public/vis/editors/default/vis_options.html b/src/ui/public/vis/editors/default/vis_options.html new file mode 100644 index 00000000000000..caf4df0b424d9a --- /dev/null +++ b/src/ui/public/vis/editors/default/vis_options.html @@ -0,0 +1,7 @@ + diff --git a/src/ui/public/vis/editors/default/vis_options.js b/src/ui/public/vis/editors/default/vis_options.js new file mode 100644 index 00000000000000..7096e51afeaa81 --- /dev/null +++ b/src/ui/public/vis/editors/default/vis_options.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { uiModules } from 'ui/modules'; +import visOptionsTemplate from './vis_options.html'; + +/** + * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. + * This lets you specify a full-screen UI for editing a vis type, instead of using the regular + * sidebar. + */ + +uiModules +.get('app/visualize') +.directive('visEditorVisOptions', function (Private, $timeout, $compile) { + return { + restrict: 'E', + template: visOptionsTemplate, + scope: { + vis: '=', + visData: '=', + uiState: '=', + editor: '=', + visualizeEditor: '=' + }, + link: function ($scope, $el) { + const $optionContainer = $el.find('[data-visualization-options]'); + + const reactOptionsComponent = typeof $scope.editor !== 'string'; + const renderReactComponent = () => { + const Component = $scope.editor; + render(, $el[0]); + }; + // Bind the `editor` template with the scope. + if (reactOptionsComponent) { + renderReactComponent(); + } else { + const $editor = $compile($scope.editor)($scope); + $optionContainer.append($editor); + } + + $scope.$watchGroup(['visData', 'visualizeEditor'], () => { + if (reactOptionsComponent) { + renderReactComponent(); + } + }); + + $scope.$watch('vis.type.schemas.all.length', function (len) { + $scope.alwaysShowOptions = len === 0; + }); + + $el.on('$destroy', () => { + if (reactOptionsComponent) { + unmountComponentAtNode($el[0]); + } + }); + } + }; +}); diff --git a/src/ui/public/vis/renderbot.js b/src/ui/public/vis/renderbot.js deleted file mode 100644 index bdf25579771e24..00000000000000 --- a/src/ui/public/vis/renderbot.js +++ /dev/null @@ -1,50 +0,0 @@ -import _ from 'lodash'; - -export function VisRenderbotProvider() { - - /** - * "Abstract" renderbot class which just defines the expected API - * - * @param {Vis} vis - the vis object that contains all configuration data required to render the vis - * @param {jQuery} $el - a jQuery wrapped element to render into - */ - function Renderbot(vis, $el, uiState) { - this.vis = vis; - this.$el = $el; - this.uiState = uiState; - } - - /** - * Each renderbot should implement a #render() method which - * should accept an elasticsearch response and update the underlying visualization - * - * @override - * @param {object} esResp - The raw elasticsearch response - * @return {undefined} - */ - Renderbot.prototype.render = function () { - throw new Error('not implemented'); - }; - - /** - * Each renderbot should implement the #destroy() method which - * should tear down the owned element, remove event listeners, etc. - * - * @override - * @return {undefined} - */ - Renderbot.prototype.destroy = function () { - throw new Error('not implemented'); - }; - - /** - * Each renderbot can optionally implement the #updateParams() method which - * is used to pass in new vis params. It should not re-render the vis - * - * @override - * @return {undefined} - */ - Renderbot.prototype.updateParams = _.noop; - - return Renderbot; -} diff --git a/src/ui/public/vis/request_handlers/courier.js b/src/ui/public/vis/request_handlers/courier.js new file mode 100644 index 00000000000000..03df31a972d3b3 --- /dev/null +++ b/src/ui/public/vis/request_handlers/courier.js @@ -0,0 +1,29 @@ +// request handler: +// handler function: a function that returns a promise +// promise returns response data when resolved +//import courier from 'ui/courier/fetch/fetch'; + +const CourierRequestHandlerProvider = function (Private, courier) { + return { + name: 'courier', + handler: function (vis, appState, uiState, searchSource) { + searchSource.set('filter', appState.filters); + if (!appState.linked) searchSource.set('query', appState.query); + + return new Promise((resolve, reject) => { + searchSource.onResults().then(resp => { + searchSource.rawResponse = resp; + resolve(resp); + }).catch(e => reject(e)); + + searchSource.onError(e => { + reject(e); + }).catch(e => reject(e)); + + courier.fetch(); + }); + } + }; +}; + +export { CourierRequestHandlerProvider }; diff --git a/src/ui/public/vis/request_handlers/none.js b/src/ui/public/vis/request_handlers/none.js new file mode 100644 index 00000000000000..5bb0444bddb774 --- /dev/null +++ b/src/ui/public/vis/request_handlers/none.js @@ -0,0 +1,12 @@ +const noneRequestHandlerProvider = function () { + return { + name: 'none', + handler: function () { + return new Promise((resolve) => { + resolve(); + }); + } + }; +}; + +export { noneRequestHandlerProvider }; diff --git a/src/ui/public/vis/response_handlers/basic.js b/src/ui/public/vis/response_handlers/basic.js new file mode 100644 index 00000000000000..886ebabe336ab3 --- /dev/null +++ b/src/ui/public/vis/response_handlers/basic.js @@ -0,0 +1,75 @@ +import { AggResponseIndexProvider } from 'ui/agg_response/index'; +import { AggResponseTabifyTableProvider } from 'ui/agg_response/tabify/_table'; + + +const BasicResponseHandlerProvider = function (Private) { + const aggResponse = Private(AggResponseIndexProvider); + const Table = Private(AggResponseTabifyTableProvider); + + function convertTableGroup(vis, tableGroup) { + const tables = tableGroup.tables; + const firstChild = tables[0]; + if (firstChild instanceof Table) { + + const chart = convertTable(vis, firstChild); + // if chart is within a split, assign group title to its label + if (tableGroup.$parent) { + chart.label = tableGroup.title; + } + return chart; + } + + if (!tables.length) return; + const out = {}; + let outList; + + tables.forEach(function (table) { + if (!outList) { + const aggConfig = table.aggConfig; + const direction = aggConfig.params.row ? 'rows' : 'columns'; + outList = out[direction] = []; + } + + let output; + if (output = convertTableGroup(vis, table)) { + outList.push(output); + } + }); + + return out; + } + + function convertTable(vis, table) { + return vis.type.responseConverter ? vis.type.responseConverter(vis, table) : table; + } + + return { + name: 'basic', + handler: function (vis, response) { + return new Promise((resolve) => { + if (vis.isHierarchical()) { + // the hierarchical converter is very self-contained (woot!) + resolve(aggResponse.hierarchical(vis, response)); + } + + const tableGroup = aggResponse.tabify(vis, response, { + canSplit: true, + asAggConfigResults: true + }); + + let converted = convertTableGroup(vis, tableGroup); + if (!converted) { + // mimic a row of tables that doesn't have any tables + // https://github.com/elastic/kibana/blob/7bfb68cd24ed42b1b257682f93c50cd8d73e2520/src/kibana/components/vislib/components/zero_injection/inject_zeros.js#L32 + converted = { rows: [] }; + } + + converted.hits = response.hits.total; + + resolve(converted); + }); + } + }; +}; + +export { BasicResponseHandlerProvider }; diff --git a/src/ui/public/vis/response_handlers/none.js b/src/ui/public/vis/response_handlers/none.js new file mode 100644 index 00000000000000..ce9874a378f16f --- /dev/null +++ b/src/ui/public/vis/response_handlers/none.js @@ -0,0 +1,17 @@ +// response handler: +// receives response data and vis configuration +// returns a promise +// promise returns response data when resolved + +const noneResponseHandler = function () { + return { + name: 'none', + handler: function (vis, response) { + return new Promise((resolve) => { + resolve(response); + }); + } + }; +}; + +export { noneResponseHandler }; diff --git a/src/ui/public/vis/response_handlers/tabify.js b/src/ui/public/vis/response_handlers/tabify.js new file mode 100644 index 00000000000000..843ee4684616d3 --- /dev/null +++ b/src/ui/public/vis/response_handlers/tabify.js @@ -0,0 +1,22 @@ +import { AggResponseIndexProvider } from 'ui/agg_response/index'; + +const TabifyResponseHandlerProvider = function (Private) { + const aggResponse = Private(AggResponseIndexProvider); + + return { + name: 'tabify', + handler: function (vis, response) { + return new Promise((resolve) => { + + const tableGroup = aggResponse.tabify(vis, response, { + canSplit: true, + asAggConfigResults: true + }); + + resolve(tableGroup); + }); + } + }; +}; + +export { TabifyResponseHandlerProvider }; diff --git a/src/ui/public/vis/vis.js b/src/ui/public/vis/vis.js index 9c91577e4aadac..2e0f2d4b69c9b0 100644 --- a/src/ui/public/vis/vis.js +++ b/src/ui/public/vis/vis.js @@ -8,79 +8,60 @@ * Not to be confused with vislib/vis.js. */ +import { EventEmitter } from 'events'; import _ from 'lodash'; -import { AggTypesIndexProvider } from 'ui/agg_types/index'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { VisAggConfigsProvider } from 'ui/vis/agg_configs'; import { PersistedState } from 'ui/persisted_state'; +import { UtilsBrushEventProvider } from 'ui/utils/brush_event'; +import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; +import { FilterBarClickHandlerProvider } from 'ui/filter_bar/filter_bar_click_handler'; -export function VisProvider(Notifier, Private) { - const aggTypes = Private(AggTypesIndexProvider); +export function VisProvider(Private, indexPatterns, timefilter, getAppState) { const visTypes = Private(VisTypesRegistryProvider); const AggConfigs = Private(VisAggConfigsProvider); - - const notify = new Notifier({ - location: 'Vis' - }); - - class Vis { - constructor(indexPattern, state, uiState) { - state = state || {}; - - if (_.isString(state)) { - state = { - type: state + const brushEvent = Private(UtilsBrushEventProvider); + const queryFilter = Private(FilterBarQueryFilterProvider); + const filterBarClickHandler = Private(FilterBarClickHandlerProvider); + + class Vis extends EventEmitter { + constructor(indexPattern, visState, uiState) { + super(); + visState = visState || {}; + + if (_.isString(visState)) { + visState = { + type: visState }; } - this.indexPattern = indexPattern; - this.setState(state); - this.setUiState(uiState); - } - - convertOldState(type, oldState) { - if (!type || _.isString(type)) { - type = visTypes.byName[type || 'histogram']; + if (!uiState) { + uiState = new PersistedState(); } - const schemas = type.schemas; - - // This was put in place to do migrations at runtime. It's used to support people who had saved - // visualizations during the 4.0 betas. - const aggs = _.transform(oldState, function (newConfigs, oldConfigs, oldGroupName) { - const schema = schemas.all.byName[oldGroupName]; + this.setCurrentState(visState); + this.setState(this.getCurrentState(), false); + this.setUiState(uiState); - if (!schema) { - notify.log('unable to match old schema', oldGroupName, 'to a new schema'); - return; + this.API = { + indexPatterns: indexPatterns, + timeFilter: timefilter, + queryFilter: queryFilter, + events: { + filter: (event) => { + const appState = getAppState(); + filterBarClickHandler(appState)(event); + }, brush: brushEvent(visState), } - - oldConfigs.forEach(function (oldConfig) { - const agg = { - schema: schema.name, - type: oldConfig.agg - }; - - const aggType = aggTypes.byName[agg.type]; - if (!aggType) { - notify.log('unable to find an agg type for old confg', oldConfig); - return; - } - - agg.params = _.pick(oldConfig, _.keys(aggType.params.byName)); - - newConfigs.push(agg); - }); - }, []); - - return { - type: type, - aggs: aggs }; } - setState(state) { + isEditorMode() { + return this.editorMode || false; + } + + setCurrentState(state) { this.title = state.title || ''; const type = state.type || this.type; if (_.isString(type)) { @@ -92,25 +73,47 @@ export function VisProvider(Notifier, Private) { this.type = type; } - this.listeners = _.assign({}, state.listeners, this.type.listeners); this.params = _.defaults({}, _.cloneDeep(state.params || {}), - _.cloneDeep(this.type.params.defaults || {}) + _.cloneDeep(this.type.visConfig.defaults || {}) ); this.aggs = new AggConfigs(this, state.aggs); } - getStateInternal(includeDisabled) { + setState(state, updateCurrentState = true) { + this._state = _.cloneDeep(state); + if (updateCurrentState) this.resetState(); + } + + updateState() { + this.setState(this.getCurrentState()); + this.emit('update'); + } + + resetState() { + this.setCurrentState(this._state); + } + + getCurrentState(includeDisabled) { return { title: this.title, type: this.type.name, params: this.params, aggs: this.aggs - .filter(agg => includeDisabled || agg.enabled) .map(agg => agg.toJSON()) - .filter(Boolean), - listeners: this.listeners + .filter(agg => includeDisabled || agg.enabled) + .filter(Boolean) + }; + } + + getStateInternal(includeDisabled) { + return { + title: this._state.title, + type: this._state.type, + params: this._state.params, + aggs: this._state.aggs + .filter(agg => includeDisabled || agg.enabled) }; } @@ -122,14 +125,6 @@ export function VisProvider(Notifier, Private) { return this.getStateInternal(true); } - createEditableVis() { - return this._editableVis || (this._editableVis = this.clone()); - } - - getEditableVis() { - return this._editableVis || undefined; - } - clone() { const uiJson = this.hasUiState() ? this.getUiState().toJSON() : {}; return new Vis(this.indexPattern, this.getState(), uiJson); @@ -170,10 +165,6 @@ export function VisProvider(Notifier, Private) { return this.__uiState; } - implementsRenderComplete() { - return this.type.implementsRenderComplete; - } - /** * Currently this is only used to extract map-specific information * (e.g. mapZoom, mapCenter). diff --git a/src/ui/public/vis/vis_category.js b/src/ui/public/vis/vis_category.js new file mode 100644 index 00000000000000..919cf36936f8b6 --- /dev/null +++ b/src/ui/public/vis/vis_category.js @@ -0,0 +1,8 @@ +export const CATEGORY = { + BASIC: 'basic', + DATA: 'data', + MAP: 'map', + OTHER: 'other', + TIME: 'time', + HIDDEN: 'hidden' +}; diff --git a/src/ui/public/vis/vis_factory.js b/src/ui/public/vis/vis_factory.js new file mode 100644 index 00000000000000..398e0da89b1ad4 --- /dev/null +++ b/src/ui/public/vis/vis_factory.js @@ -0,0 +1,23 @@ +import { VisTypeProvider, AngularVisTypeProvider, ReactVisTypeProvider, VislibVisTypeProvider } from './vis_types'; + +export const VisFactoryProvider = (Private) => { + const VisType = Private(VisTypeProvider); + const AngularVisType = Private(AngularVisTypeProvider); + const ReactVisType = Private(ReactVisTypeProvider); + const VislibVisType = Private(VislibVisTypeProvider); + + return { + createBaseVisualization: (config) => { + return new VisType(config); + }, + createAngularVisualization: (config) => { + return new AngularVisType(config); + }, + createReactVisualization: (config) => { + return new ReactVisType(config); + }, + createVislibVisualization: (config) => { + return new VislibVisType(config); + } + }; +}; diff --git a/src/ui/public/vis/vis_type.js b/src/ui/public/vis/vis_type.js deleted file mode 100644 index 102edf340def9a..00000000000000 --- a/src/ui/public/vis/vis_type.js +++ /dev/null @@ -1,47 +0,0 @@ -import { VisSchemasProvider } from './schemas'; - -export function VisVisTypeProvider(Private) { - const VisTypeSchemas = Private(VisSchemasProvider); - - class VisType { - constructor(opts) { - opts = opts || {}; - - this.name = opts.name; - this.title = opts.title; - this.responseConverter = opts.responseConverter; - this.hierarchicalData = opts.hierarchicalData || false; - this.icon = opts.icon; - this.image = opts.image; - this.description = opts.description; - this.category = opts.category || VisType.CATEGORY.OTHER; - this.isExperimental = opts.isExperimental; - this.schemas = opts.schemas || new VisTypeSchemas(); - this.params = opts.params || {}; - this.requiresSearch = opts.requiresSearch == null ? true : opts.requiresSearch; // Default to true unless otherwise specified - this.requiresTimePicker = !!opts.requiresTimePicker; - this.fullEditor = opts.fullEditor == null ? false : opts.fullEditor; - this.implementsRenderComplete = opts.implementsRenderComplete || false; - - if (!this.params.optionTabs) { - this.params.optionTabs = [ - { name: 'options', title: 'Options', editor: this.params.editor } - ]; - } - } - - createRenderbot() { - throw new Error('not implemented'); - } - } - - VisType.CATEGORY = { - BASIC: 'basic', - DATA: 'data', - MAP: 'map', - OTHER: 'other', - TIME: 'time', - }; - - return VisType; -} diff --git a/src/ui/public/vis/vis_types/angular_vis_type.js b/src/ui/public/vis/vis_types/angular_vis_type.js new file mode 100644 index 00000000000000..92a63398a057c8 --- /dev/null +++ b/src/ui/public/vis/vis_types/angular_vis_type.js @@ -0,0 +1,63 @@ +import { VisTypeProvider } from 'ui/vis/vis_types'; +import $ from 'jquery'; + + +export function AngularVisTypeProvider(Private, $compile, $rootScope) { + const VisType = Private(VisTypeProvider); + + class AngularVisController { + constructor(domeElement, vis) { + this.el = $(domeElement); + this.vis = vis; + } + + render(esResponse) { + + return new Promise((resolve, reject) => { + const updateScope = () => { + this.$scope.vis = this.vis.clone(); + this.$scope.esResponse = esResponse; + this.$scope.renderComplete = resolve; + this.$scope.renderFailed = reject; + }; + + if (!this.$scope) { + this.$scope = $rootScope.$new(); + updateScope(); + this.$scope.uiState = this.vis.getUiState(); + this.el.html($compile(this.vis.type.visConfig.template)(this.$scope)); + } else { + updateScope(); + } + }); + } + + resize() { + if (this.$scope) { + this.$scope.$emit('resize'); + } + } + + destroy() { + if (this.$scope) { + this.$scope.$destroy(); + this.$scope = null; + } + } + } + + class AngularVisType extends VisType { + constructor(opts) { + opts.visualization = AngularVisController; + + super(opts); + + this.visConfig.template = opts.visConfig ? opts.visConfig.template : opts.template; + if (!this.visConfig.template) { + throw new Error('Missing template for AngularVisType'); + } + } + } + + return AngularVisType; +} diff --git a/src/ui/public/vis/vis_types/base_vis_type.js b/src/ui/public/vis/vis_types/base_vis_type.js new file mode 100644 index 00000000000000..09d39ddb28f320 --- /dev/null +++ b/src/ui/public/vis/vis_types/base_vis_type.js @@ -0,0 +1,54 @@ +import { CATEGORY } from '../vis_category'; +import _ from 'lodash'; + +export function VisTypeProvider() { + class VisType { + constructor(opts) { + opts = opts || {}; + + if (!opts.name) throw('vis_type must define its name'); + if (!opts.title) throw('vis_type must define its title'); + if (!opts.description) throw('vis_type must define its description'); + if (!opts.icon && !opts.image) throw('vis_type must define its icon or image'); + if (!opts.visualization) throw('vis_type must define visualization controller'); + + const _defaults = { + // name, title, description, icon, image + category: CATEGORY.OTHER, + visualization: null, // must be a class with render/resize/destroy methods + visConfig: { + defaults: {}, // default configuration + }, + requestHandler: 'courier', // select one from registry or pass a function + responseHandler: 'tabify', + editor: 'default', + editorConfig: { + collections: {}, // collections used for configuration (list of positions, ...) + }, + options: { // controls the visualize editor + showTimePicker: true, + showQueryBar: true, + showFilterBar: true, + hierarchicalData: false // we should get rid of this i guess ? + }, + isExperimental: false + }; + + _.defaultsDeep(this, opts, _defaults); + + this.requiresSearch = !(this.requestHandler === 'none'); + } + } + + Object.defineProperty(VisType.prototype, 'schemas', { + get() { + if (this.editorConfig && this.editorConfig.schemas) { + return this.editorConfig.schemas; + } + + return []; //throw `Can't get schemas from a visualization without using the default editor`; + } + }); + + return VisType; +} diff --git a/src/ui/public/vis/vis_types/index.js b/src/ui/public/vis/vis_types/index.js new file mode 100644 index 00000000000000..6755b55428d41b --- /dev/null +++ b/src/ui/public/vis/vis_types/index.js @@ -0,0 +1,6 @@ +import { VisTypeProvider } from './base_vis_type'; +import { AngularVisTypeProvider } from './angular_vis_type'; +import { VislibVisTypeProvider } from './vislib_vis_type'; +import { ReactVisTypeProvider } from './react_vis_type'; + +export { VisTypeProvider, AngularVisTypeProvider, VislibVisTypeProvider, ReactVisTypeProvider }; diff --git a/src/ui/public/vis/vis_types/react_vis_type.js b/src/ui/public/vis/vis_types/react_vis_type.js new file mode 100644 index 00000000000000..b9833e03f4905a --- /dev/null +++ b/src/ui/public/vis/vis_types/react_vis_type.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { VisTypeProvider } from 'ui/vis/vis_types'; + +export function ReactVisTypeProvider(Private) { + const VisType = Private(VisTypeProvider); + + class ReactVisController { + constructor(el, vis) { + this.el = el; + this.vis = vis; + } + + render(visData) { + this.visData = visData; + + return new Promise((resolve) => { + const Component = this.vis.type.visConfig.component; + render(, this.el); + }); + } + + resize() { + if (this.visData) { + this.render(this.vis, this.visData); + } + } + + destroy() { + unmountComponentAtNode(this.el); + } + } + + class ReactVisType extends VisType { + constructor(opts) { + opts.visualization = ReactVisController; + + super(opts); + + if (!this.visConfig.component) { + throw new Error('Missing component for ReactVisType'); + } + } + } + + return ReactVisType; +} diff --git a/src/ui/public/vis/vis_types/vislib_vis_type.js b/src/ui/public/vis/vis_types/vislib_vis_type.js new file mode 100644 index 00000000000000..069ef366702c48 --- /dev/null +++ b/src/ui/public/vis/vis_types/vislib_vis_type.js @@ -0,0 +1,67 @@ +import 'ui/vislib'; +import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; +import 'plugins/kbn_vislib_vis_types/controls/point_series_options'; +import 'plugins/kbn_vislib_vis_types/controls/line_interpolation_option'; +import 'plugins/kbn_vislib_vis_types/controls/heatmap_options'; +import 'plugins/kbn_vislib_vis_types/controls/point_series'; +import { VisTypeProvider } from './base_vis_type'; +import { AggResponsePointSeriesProvider } from 'ui/agg_response/point_series/point_series'; +import VislibProvider from 'ui/vislib'; + +export function VislibVisTypeProvider(Private) { + const VisType = Private(VisTypeProvider); + const pointSeries = Private(AggResponsePointSeriesProvider); + const vislib = Private(VislibProvider); + + class VislibVisController { + constructor(el, vis) { + this.el = el; + this.vis = vis; + } + + render(esResponse) { + if (this.vis.vislibVis) { + this.destroy(); + } else { + this.vis.refreshLegend = 0; + } + + return new Promise(resolve => { + this.vis.vislibVis = new vislib.Vis(this.el, this.vis.params); + this.vis.vislibVis.on('brush', this.vis.API.events.brush); + this.vis.vislibVis.on('click', this.vis.API.events.filter); + this.vis.vislibVis.on('renderComplete', resolve); + this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + this.vis.refreshLegend++; + }); + } + + resize() { + return; + } + + destroy() { + if (this.vislibVis) { + this.vis.vislibVis.off('brush', this.vis.API.events.brush); + this.vis.vislibVis.off('click', this.vis.API.events.filter); + this.vis.vislibVis.destroy(); + } + } + } + + class VislibVisType extends VisType { + constructor(opts) { + if (!opts.responseHandler) { + opts.responseHandler = 'basic'; + } + if (!opts.responseConverter) { + opts.responseConverter = pointSeries; + } + opts.visualization = VislibVisController; + super(opts); + this.refreshLegend = 0; + } + } + + return VislibVisType; +} From d6cf0f88aecc1aee885cd5ff56c41970cd34c6c1 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:20:02 +0200 Subject: [PATCH 06/40] updating vis types --- .../template_vis_type/template_renderbot.js | 27 - .../template_vis_type/template_vis_type.js | 25 - .../vis_maps/__tests__/geohash_layer.js | 508 ------- .../vis_maps/__tests__/geohash_sample_data.js | 1341 ----------------- .../public/vis_maps/__tests__/kibana_map.js | 139 -- src/ui/public/vis_maps/geohash_layer.js | 96 -- src/ui/public/vis_maps/kibana_map.js | 565 ------- src/ui/public/vis_maps/kibana_map_layer.js | 38 - src/ui/public/vis_maps/maps_renderbot.js | 262 ---- src/ui/public/vis_maps/maps_vis_type.js | 22 - .../public/vis_maps/markers/geohash_grid.js | 17 - src/ui/public/vis_maps/markers/heatmap.js | 189 --- .../public/vis_maps/markers/scaled_circles.js | 231 --- .../public/vis_maps/markers/shaded_circles.js | 46 - src/ui/public/vis_maps/styles/_tilemap.less | 185 --- .../__tests__/_build_chart_data.js | 133 -- .../__tests__/_vislib_renderbot.js | 173 --- .../public/vislib_vis_type/__tests__/index.js | 4 - .../vislib_vis_type/build_chart_data.js | 69 - .../vislib_vis_type/vislib_renderbot.js | 79 - .../public/vislib_vis_type/vislib_vis_type.js | 85 -- 21 files changed, 4234 deletions(-) delete mode 100644 src/ui/public/template_vis_type/template_renderbot.js delete mode 100644 src/ui/public/template_vis_type/template_vis_type.js delete mode 100644 src/ui/public/vis_maps/__tests__/geohash_layer.js delete mode 100644 src/ui/public/vis_maps/__tests__/geohash_sample_data.js delete mode 100644 src/ui/public/vis_maps/__tests__/kibana_map.js delete mode 100644 src/ui/public/vis_maps/geohash_layer.js delete mode 100644 src/ui/public/vis_maps/kibana_map.js delete mode 100644 src/ui/public/vis_maps/kibana_map_layer.js delete mode 100644 src/ui/public/vis_maps/maps_renderbot.js delete mode 100644 src/ui/public/vis_maps/maps_vis_type.js delete mode 100644 src/ui/public/vis_maps/markers/geohash_grid.js delete mode 100644 src/ui/public/vis_maps/markers/heatmap.js delete mode 100644 src/ui/public/vis_maps/markers/scaled_circles.js delete mode 100644 src/ui/public/vis_maps/markers/shaded_circles.js delete mode 100644 src/ui/public/vis_maps/styles/_tilemap.less delete mode 100644 src/ui/public/vislib_vis_type/__tests__/_build_chart_data.js delete mode 100644 src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js delete mode 100644 src/ui/public/vislib_vis_type/__tests__/index.js delete mode 100644 src/ui/public/vislib_vis_type/build_chart_data.js delete mode 100644 src/ui/public/vislib_vis_type/vislib_renderbot.js delete mode 100644 src/ui/public/vislib_vis_type/vislib_vis_type.js diff --git a/src/ui/public/template_vis_type/template_renderbot.js b/src/ui/public/template_vis_type/template_renderbot.js deleted file mode 100644 index 66da925d52f2ae..00000000000000 --- a/src/ui/public/template_vis_type/template_renderbot.js +++ /dev/null @@ -1,27 +0,0 @@ -import _ from 'lodash'; -import { VisRenderbotProvider } from 'ui/vis/renderbot'; - -export function TemplateRenderbotProvider(Private, $compile, $rootScope) { - const Renderbot = Private(VisRenderbotProvider); - - _.class(TemplateRenderbot).inherits(Renderbot); - function TemplateRenderbot(vis, $el, uiState) { - TemplateRenderbot.Super.call(this, vis, $el, uiState); - - this.$scope = $rootScope.$new(); - this.$scope.vis = vis; - this.$scope.uiState = uiState; - - $el.html($compile(this.vis.type.template)(this.$scope)); - } - - TemplateRenderbot.prototype.render = function (esResponse) { - this.$scope.esResponse = esResponse; - }; - - TemplateRenderbot.prototype.destroy = function () { - this.$scope.$destroy(); - }; - - return TemplateRenderbot; -} diff --git a/src/ui/public/template_vis_type/template_vis_type.js b/src/ui/public/template_vis_type/template_vis_type.js deleted file mode 100644 index de89d4e213b36b..00000000000000 --- a/src/ui/public/template_vis_type/template_vis_type.js +++ /dev/null @@ -1,25 +0,0 @@ -import _ from 'lodash'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { TemplateRenderbotProvider } from 'ui/template_vis_type/template_renderbot'; - -export function TemplateVisTypeProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const TemplateRenderbot = Private(TemplateRenderbotProvider); - - _.class(TemplateVisType).inherits(VisType); - function TemplateVisType(opts = {}) { - TemplateVisType.Super.call(this, opts); - - this.template = opts.template; - if (!this.template) { - throw new Error('Missing template for TemplateVisType'); - } - } - - TemplateVisType.prototype.createRenderbot = function (vis, $el, uiState) { - return new TemplateRenderbot(vis, $el, uiState); - }; - - return TemplateVisType; -} - diff --git a/src/ui/public/vis_maps/__tests__/geohash_layer.js b/src/ui/public/vis_maps/__tests__/geohash_layer.js deleted file mode 100644 index c61ed5d991bf8e..00000000000000 --- a/src/ui/public/vis_maps/__tests__/geohash_layer.js +++ /dev/null @@ -1,508 +0,0 @@ -import expect from 'expect.js'; -import { KibanaMap } from 'ui/vis_maps/kibana_map'; -import { GeohashLayer } from 'ui/vis_maps/geohash_layer'; -import { GeoHashSampleData } from './geohash_sample_data'; - -describe('kibana_map tests', function () { - - let domNode; - let kibanaMap; - - - function setupDOM() { - domNode = document.createElement('div'); - domNode.style.top = '0'; - domNode.style.left = '0'; - domNode.style.width = '512px'; - domNode.style.height = '512px'; - domNode.style.position = 'fixed'; - domNode.style['pointer-events'] = 'none'; - document.body.appendChild(domNode); - } - - function teardownDOM() { - domNode.innerHTML = ''; - document.body.removeChild(domNode); - } - - - describe('GeohashGridLayer', function () { - - beforeEach(async function () { - setupDOM(); - kibanaMap = new KibanaMap(domNode, { - minZoom: 1, - maxZoom: 10 - }); - kibanaMap.setZoomLevel(3); - kibanaMap.setCenter({ - lon: -100, - lat: 40 - }); - }); - - afterEach(function () { - kibanaMap.destroy(); - teardownDOM(); - }); - - [ - { - options: { 'mapType': 'Scaled Circle Markers' }, - expected: `[ - { - "fill": "#bd0026", - "d": "M343,263.8A19.2,19.2,0,1,1,342.9,263.8 z" - }, - { - "fill": "#bd0026", - "d": "M343,225.03843394373595A18.961566056264047,18.961566056264047,0,1,1,342.9,225.03843394373595 z" - }, - { - "fill": "#bd0026", - "d": "M283,264.19815701843777A17.80184298156226,17.80184298156226,0,1,1,282.9,264.19815701843777 z" - }, - { - "fill": "#f03b20", - "d": "M405,224.2748797495895A16.72512025041049,16.72512025041049,0,1,1,404.9,224.2748797495895 z" - }, - { - "fill": "#f03b20", - "d": "M285,223.50180417608374A16.498195823916255,16.498195823916255,0,1,1,284.9,223.50180417608374 z" - }, - { - "fill": "#f03b20", - "d": "M343,299.1036928470748A15.896307152925205,15.896307152925205,0,1,1,342.9,299.1036928470748 z" - }, - { - "fill": "#f03b20", - "d": "M283,300.2846189453604A15.71538105463958,15.71538105463958,0,1,1,282.9,300.2846189453604 z" - }, - { - "fill": "#fd8d3c", - "d": "M148,267.0272116156895A13.972788384310489,13.972788384310489,0,1,1,147.9,267.0272116156895 z" - }, - { - "fill": "#feb24c", - "d": "M219,270.4178825645856A11.582117435414355,11.582117435414355,0,1,1,218.9,270.4178825645856 z" - }, - { - "fill": "#feb24c", - "d": "M146,189.63311915018554A11.366880849814459,11.366880849814459,0,1,1,145.9,189.63311915018554 z" - }, - { - "fill": "#feb24c", - "d": "M281,191.96973262756177A11.030267372438226,11.030267372438226,0,1,1,280.9,191.96973262756177 z" - }, - { - "fill": "#feb24c", - "d": "M220,231.85362974571228A10.146370254287714,10.146370254287714,0,1,1,219.9,231.85362974571228 z" - }, - { - "fill": "#feb24c", - "d": "M144,231.1923722152369A9.807627784763092,9.807627784763092,0,1,1,143.9,231.1923722152369 z" - }, - { - "fill": "#feb24c", - "d": "M387,268.27221854599287A9.72778145400714,9.72778145400714,0,1,1,386.9,268.27221854599287 z" - }, - { - "fill": "#feb24c", - "d": "M217,191.09542834646925A8.90457165353074,8.90457165353074,0,1,1,216.9,191.09542834646925 z" - }, - { - "fill": "#fed976", - "d": "M218,300.40744573968243A8.592554260317598,8.592554260317598,0,1,1,217.9,300.40744573968243 z" - }, - { - "fill": "#fed976", - "d": "M363,339.5411821762003A7.458817823799684,7.458817823799684,0,1,1,362.9,339.5411821762003 z" - }, - { - "fill": "#fed976", - "d": "M331,205.43072931381437A6.569270686185644,6.569270686185644,0,1,1,330.9,205.43072931381437 z" - }, - { - "fill": "#fed976", - "d": "M163,299.9012571034098A5.098742896590189,5.098742896590189,0,1,1,162.9,299.9012571034098 z" - }, - { - "fill": "#fed976", - "d": "M34,77.6735731867532A4.326426813246795,4.326426813246795,0,1,1,33.9,77.6735731867532 z" - }, - { - "fill": "#fed976", - "d": "M268,341.7954688958982A4.204531104101819,4.204531104101819,0,1,1,267.9,341.7954688958982 z" - }, - { - "fill": "#fed976", - "d": "M71,118.82649906983305A4.173500930166947,4.173500930166947,0,1,1,70.9,118.82649906983305 z" - }, - { - "fill": "#fed976", - "d": "M119,235.1169130974434A3.8830869025566206,3.8830869025566206,0,1,1,118.9,235.1169130974434 z" - }, - { - "fill": "#fed976", - "d": "M451,396.15053353027315A3.849466469726874,3.849466469726874,0,1,1,450.9,396.15053353027315 z" - }, - { - "fill": "#fed976", - "d": "M64,104.18445019554242A3.815549804457569,3.815549804457569,0,1,1,63.9,104.18445019554242 z" - }, - { - "fill": "#fed976", - "d": "M7,15.430879972386867A3.5691200276131325,3.5691200276131325,0,1,1,6.9,15.430879972386867 z" - }, - { - "fill": "#fed976", - "d": "M434,206.8985557756997A3.1014442243003013,3.1014442243003013,0,1,1,433.9,206.8985557756997 z" - }, - { - "fill": "#fed976", - "d": "M119,201.2073035006183A2.792696499381677,2.792696499381677,0,1,1,118.9,201.2073035006183 z" - }, - { - "fill": "#fed976", - "d": "M-1,420.89773444794906A2.1022655520509095,2.1022655520509095,0,1,1,-1.1,420.89773444794906 z" - }, - { - "fill": "#fed976", - "d": "M443,217.859886428343A1.1401135716569843,1.1401135716569843,0,1,1,442.9,217.859886428343 z" - }, - { - "fill": "#fed976", - "d": "M121,260.85988642834303A1.1401135716569843,1.1401135716569843,0,1,1,120.9,260.85988642834303 z" - }, - { - "fill": "#fed976", - "d": "M-4,399.27892886445886A0.7210711355411324,0.7210711355411324,0,1,1,-4.1,399.27892886445886 z" - } -]` - }, - { - options: { 'mapType': 'Shaded Circle Markers' }, - expected: `[ - { - "fill": "#bd0026", - "d": "M343,267A16,16,0,1,1,342.9,267 z" - }, - { - "fill": "#bd0026", - "d": "M343,226A18,18,0,1,1,342.9,226 z" - }, - { - "fill": "#bd0026", - "d": "M283,266A16,16,0,1,1,282.9,266 z" - }, - { - "fill": "#f03b20", - "d": "M405,223A18,18,0,1,1,404.9,223 z" - }, - { - "fill": "#f03b20", - "d": "M285,222A18,18,0,1,1,284.9,222 z" - }, - { - "fill": "#f03b20", - "d": "M343,300A15,15,0,1,1,342.9,300 z" - }, - { - "fill": "#f03b20", - "d": "M283,301A15,15,0,1,1,282.9,301 z" - }, - { - "fill": "#fd8d3c", - "d": "M148,265A16,16,0,1,1,147.9,265 z" - }, - { - "fill": "#feb24c", - "d": "M219,266A16,16,0,1,1,218.9,266 z" - }, - { - "fill": "#feb24c", - "d": "M146,183A18,18,0,1,1,145.9,183 z" - }, - { - "fill": "#feb24c", - "d": "M281,184A19,19,0,1,1,280.9,184 z" - }, - { - "fill": "#feb24c", - "d": "M220,225A17,17,0,1,1,219.9,225 z" - }, - { - "fill": "#feb24c", - "d": "M144,224A17,17,0,1,1,143.9,224 z" - }, - { - "fill": "#feb24c", - "d": "M387,262A16,16,0,1,1,386.9,262 z" - }, - { - "fill": "#feb24c", - "d": "M217,181A19,19,0,1,1,216.9,181 z" - }, - { - "fill": "#fed976", - "d": "M218,293A16,16,0,1,1,217.9,293 z" - }, - { - "fill": "#fed976", - "d": "M363,333A14,14,0,1,1,362.9,333 z" - }, - { - "fill": "#fed976", - "d": "M331,194A18,18,0,1,1,330.9,194 z" - }, - { - "fill": "#fed976", - "d": "M163,290A15,15,0,1,1,162.9,290 z" - }, - { - "fill": "#fed976", - "d": "M34,56A26,26,0,1,1,33.9,56 z" - }, - { - "fill": "#fed976", - "d": "M268,332A14,14,0,1,1,267.9,332 z" - }, - { - "fill": "#fed976", - "d": "M71,100A23,23,0,1,1,70.9,100 z" - }, - { - "fill": "#fed976", - "d": "M119,222A17,17,0,1,1,118.9,222 z" - }, - { - "fill": "#fed976", - "d": "M451,387A13,13,0,1,1,450.9,387 z" - }, - { - "fill": "#fed976", - "d": "M64,84A24,24,0,1,1,63.9,84 z" - }, - { - "fill": "#fed976", - "d": "M7,-7A26,26,0,1,1,6.9,-7 z" - }, - { - "fill": "#fed976", - "d": "M434,192A18,18,0,1,1,433.9,192 z" - }, - { - "fill": "#fed976", - "d": "M119,185A19,19,0,1,1,118.9,185 z" - }, - { - "fill": "#fed976", - "d": "M-1,410A13,13,0,1,1,-1.1,410 z" - }, - { - "fill": "#fed976", - "d": "M443,201A18,18,0,1,1,442.9,201 z" - }, - { - "fill": "#fed976", - "d": "M121,245A17,17,0,1,1,120.9,245 z" - }, - { - "fill": "#fed976", - "d": "M-4,386A14,14,0,1,1,-4.1,386 z" - } -]` - }, - { - options: { 'mapType': 'Shaded Geohash Grid' }, - expected: `[ - { - "fill": "#bd0026", - "d": "M313 301L313 261L377 261L377 301z" - }, - { - "fill": "#bd0026", - "d": "M313 261L313 218L377 218L377 261z" - }, - { - "fill": "#bd0026", - "d": "M249 301L249 261L313 261L313 301z" - }, - { - "fill": "#f03b20", - "d": "M377 261L377 218L441 218L441 261z" - }, - { - "fill": "#f03b20", - "d": "M249 261L249 218L313 218L313 261z" - }, - { - "fill": "#f03b20", - "d": "M313 338L313 301L377 301L377 338z" - }, - { - "fill": "#f03b20", - "d": "M249 338L249 301L313 301L313 338z" - }, - { - "fill": "#fd8d3c", - "d": "M121 301L121 261L185 261L185 301z" - }, - { - "fill": "#feb24c", - "d": "M185 301L185 261L249 261L249 301z" - }, - { - "fill": "#feb24c", - "d": "M121 218L121 170L185 170L185 218z" - }, - { - "fill": "#feb24c", - "d": "M249 218L249 170L313 170L313 218z" - }, - { - "fill": "#feb24c", - "d": "M185 261L185 218L249 218L249 261z" - }, - { - "fill": "#feb24c", - "d": "M121 261L121 218L185 218L185 261z" - }, - { - "fill": "#feb24c", - "d": "M377 301L377 261L441 261L441 301z" - }, - { - "fill": "#feb24c", - "d": "M185 218L185 170L249 170L249 218z" - }, - { - "fill": "#fed976", - "d": "M185 338L185 301L249 301L249 338z" - }, - { - "fill": "#fed976", - "d": "M313 374L313 338L377 338L377 374z" - }, - { - "fill": "#fed976", - "d": "M313 218L313 170L377 170L377 218z" - }, - { - "fill": "#fed976", - "d": "M121 338L121 301L185 301L185 338z" - }, - { - "fill": "#fed976", - "d": "M-7 116L-7 54L57 54L57 116z" - }, - { - "fill": "#fed976", - "d": "M249 374L249 338L313 338L313 374z" - }, - { - "fill": "#fed976", - "d": "M57 170L57 116L121 116L121 170z" - }, - { - "fill": "#fed976", - "d": "M57 261L57 218L121 218L121 261z" - }, - { - "fill": "#fed976", - "d": "M441 408L441 374L505 374L505 408z" - }, - { - "fill": "#fed976", - "d": "M57 116L57 54L121 54L121 116z" - }, - { - "fill": "#fed976", - "d": "M-7 54L-7 -21L57 -21L57 54z" - }, - { - "fill": "#fed976", - "d": "M377 218L377 170L441 170L441 218z" - }, - { - "fill": "#fed976", - "d": "M57 218L57 170L121 170L121 218z" - }, - { - "fill": "#fed976", - "d": "M-7 441L-7 408L57 408L57 441z" - }, - { - "fill": "#fed976", - "d": "M441 261L441 218L505 218L505 261z" - }, - { - "fill": "#fed976", - "d": "M57 301L57 261L121 261L121 301z" - }, - { - "fill": "#fed976", - "d": "M-7 408L-7 374L57 374L57 408z" - } -]` - } - ].forEach(function (test) { - - it(test.options.mapType, function () { - - const geohashGridOptions = test.options; - const geohashLayer = new GeohashLayer(GeoHashSampleData, geohashGridOptions, kibanaMap.getZoomLevel(), kibanaMap); - kibanaMap.addLayer(geohashLayer); - const markersNodeList = domNode.querySelectorAll('path.leaflet-clickable'); - const markerArray = []; - for (let i = 0; i < markersNodeList.length; i++) { - markerArray.push(markersNodeList[i]); - } - - const expectedGeohashGridMarkers = test.expected; - const expectedMarkers = JSON.parse(expectedGeohashGridMarkers).map(path => { - return { - fill: path.fill, - coords: path.d.match(/[0-9\.]+/g).map(parseFloat) - }; - }); - const actualMarkers = markerArray.map(a => { - return { - fill: a.getAttribute('fill'), - coords: a.getAttribute('d').match(/[0-9\.]+/g).map(parseFloat) - }; - }); - expect(actualMarkers.length).to.equal(expectedMarkers.length); - for (let i = 0; i < expectedMarkers.length; i++) { - expect(actualMarkers[i].fill).to.equal(expectedMarkers[i].fill); - actualMarkers[i].coords.forEach((coord, c) => { - closeTo(actualMarkers[i].coords[c], expectedMarkers[i].coords[c]); - }); - } - }); - }); - - - it('should not throw when fitting on empty-data layer', function () { - - const geohashLayer = new GeohashLayer({ - type: 'FeatureCollection', - features: [] - }, { 'mapType': 'Scaled Circle Markers' }, kibanaMap.getZoomLevel(), kibanaMap); - kibanaMap.addLayer(geohashLayer); - - expect(() => { - kibanaMap.fitToData(); - }).to.not.throwException(); - }); - - - }); - -}); - - -function closeTo(actual, expected) { - const epsilon = 1;//allow 2px slack - expect(actual - epsilon < expected && expected < actual + epsilon).to.equal(true); -} diff --git a/src/ui/public/vis_maps/__tests__/geohash_sample_data.js b/src/ui/public/vis_maps/__tests__/geohash_sample_data.js deleted file mode 100644 index 86b4289c88dffe..00000000000000 --- a/src/ui/public/vis_maps/__tests__/geohash_sample_data.js +++ /dev/null @@ -1,1341 +0,0 @@ -const sampleData = `{ - "type": "FeatureCollection", - "features": [{ - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-84.81215765699744, 36.289477944374084]}, - "properties": { - "geohash": "dn", - "value": 1418, - "aggConfigResult": { - "key": 1418, - "value": 1418, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dn", - "value": "dn", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 4, - "type": "bucket" - }, - "$order": 5, - "type": "metric" - }, - "center": [36.5625, -84.375], - "rectangle": [[33.75, -90], [33.75, -78.75], [39.375, -78.75], [39.375, -90]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-84.8004243336618, 41.63311270996928]}, - "properties": { - "geohash": "dp", - "value": 1383, - "aggConfigResult": { - "key": 1383, - "value": 1383, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dp", - "value": "dp", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 7, - "type": "bucket" - }, - "$order": 8, - "type": "metric" - }, - "center": [42.1875, -84.375], - "rectangle": [[39.375, -90], [39.375, -78.75], [45, -78.75], [45, -90]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-95.20564651116729, 36.4947619009763]}, - "properties": { - "geohash": "9y", - "value": 1219, - "aggConfigResult": { - "key": 1219, - "value": 1219, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9y", - "value": "9y", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 10, - "type": "bucket" - }, - "$order": 11, - "type": "metric" - }, - "center": [36.5625, -95.625], - "rectangle": [[33.75, -101.25], [33.75, -90], [39.375, -90], [39.375, -101.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-73.8917376101017, 42.086046701297164]}, - "properties": { - "geohash": "dr", - "value": 1076, - "aggConfigResult": { - "key": 1076, - "value": 1076, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dr", - "value": "dr", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 13, - "type": "bucket" - }, - "$order": 14, - "type": "metric" - }, - "center": [42.1875, -73.125], - "rectangle": [[39.375, -78.75], [39.375, -67.5], [45, -67.5], [45, -78.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-94.9999794177711, 42.19089978374541]}, - "properties": { - "geohash": "9z", - "value": 1047, - "aggConfigResult": { - "key": 1047, - "value": 1047, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9z", - "value": "9z", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 16, - "type": "bucket" - }, - "$order": 17, - "type": "metric" - }, - "center": [42.1875, -95.625], - "rectangle": [[39.375, -101.25], [39.375, -90], [45, -90], [45, -101.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-84.72070790827274, 31.68308235704899]}, - "properties": { - "geohash": "dj", - "value": 972, - "aggConfigResult": { - "key": 972, - "value": 972, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dj", - "value": "dj", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 19, - "type": "bucket" - }, - "$order": 20, - "type": "metric" - }, - "center": [30.9375, -84.375], - "rectangle": [[28.125, -90], [28.125, -78.75], [33.75, -78.75], [33.75, -90]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-95.22422080859542, 31.44715240225196]}, - "properties": { - "geohash": "9v", - "value": 950, - "aggConfigResult": { - "key": 950, - "value": 950, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9v", - "value": "9v", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 22, - "type": "bucket" - }, - "$order": 23, - "type": "metric" - }, - "center": [30.9375, -95.625], - "rectangle": [[28.125, -101.25], [28.125, -90], [33.75, -90], [33.75, -101.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-119.02438038960099, 36.617594081908464]}, - "properties": { - "geohash": "9q", - "value": 751, - "aggConfigResult": { - "key": 751, - "value": 751, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9q", - "value": "9q", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 25, - "type": "bucket" - }, - "$order": 26, - "type": "metric" - }, - "center": [36.5625, -118.125], - "rectangle": [[33.75, -123.75], [33.75, -112.5], [39.375, -112.5], [39.375, -123.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-106.54198246076703, 36.47509602829814]}, - "properties": { - "geohash": "9w", - "value": 516, - "aggConfigResult": { - "key": 516, - "value": 516, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9w", - "value": "9w", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 28, - "type": "bucket" - }, - "$order": 29, - "type": "metric" - }, - "center": [36.5625, -106.875], - "rectangle": [[33.75, -112.5], [33.75, -101.25], [39.375, -101.25], [39.375, -112.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-119.28373273462057, 47.07595920190215]}, - "properties": { - "geohash": "c2", - "value": 497, - "aggConfigResult": { - "key": 497, - "value": 497, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "c2", - "value": "c2", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 31, - "type": "bucket" - }, - "$order": 32, - "type": "metric" - }, - "center": [47.8125, -118.125], - "rectangle": [[45, -123.75], [45, -112.5], [50.625, -112.5], [50.625, -123.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-95.67718841135502, 46.75232579000294]}, - "properties": { - "geohash": "cb", - "value": 468, - "aggConfigResult": { - "key": 468, - "value": 468, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "cb", - "value": "cb", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 34, - "type": "bucket" - }, - "$order": 35, - "type": "metric" - }, - "center": [47.8125, -95.625], - "rectangle": [[45, -101.25], [45, -90], [50.625, -90], [50.625, -101.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-106.2923239544034, 41.907251570373774]}, - "properties": { - "geohash": "9x", - "value": 396, - "aggConfigResult": { - "key": 396, - "value": 396, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9x", - "value": "9x", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 37, - "type": "bucket" - }, - "$order": 38, - "type": "metric" - }, - "center": [42.1875, -106.875], - "rectangle": [[39.375, -112.5], [39.375, -101.25], [45, -101.25], [45, -112.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-119.63544443249702, 42.04197423532605]}, - "properties": { - "geohash": "9r", - "value": 370, - "aggConfigResult": { - "key": 370, - "value": 370, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9r", - "value": "9r", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 40, - "type": "bucket" - }, - "$order": 41, - "type": "metric" - }, - "center": [42.1875, -118.125], - "rectangle": [[39.375, -123.75], [39.375, -112.5], [45, -112.5], [45, -123.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-76.97201896458864, 37.06826982088387]}, - "properties": { - "geohash": "dq", - "value": 364, - "aggConfigResult": { - "key": 364, - "value": 364, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dq", - "value": "dq", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 43, - "type": "bucket" - }, - "$order": 44, - "type": "metric" - }, - "center": [36.5625, -73.125], - "rectangle": [[33.75, -78.75], [33.75, -67.5], [39.375, -67.5], [39.375, -78.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-106.92424703389406, 47.192871160805225]}, - "properties": { - "geohash": "c8", - "value": 305, - "aggConfigResult": { - "key": 305, - "value": 305, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "c8", - "value": "c8", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 46, - "type": "bucket" - }, - "$order": 47, - "type": "metric" - }, - "center": [47.8125, -106.875], - "rectangle": [[45, -112.5], [45, -101.25], [50.625, -101.25], [50.625, -112.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-106.78505832329392, 32.50790253281593]}, - "properties": { - "geohash": "9t", - "value": 284, - "aggConfigResult": { - "key": 284, - "value": 284, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9t", - "value": "9t", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 49, - "type": "bucket" - }, - "$order": 50, - "type": "metric" - }, - "center": [30.9375, -106.875], - "rectangle": [[28.125, -112.5], [28.125, -101.25], [33.75, -101.25], [33.75, -112.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-152.9292524792254, 59.777277521789074]}, - "properties": { - "geohash": "bd", - "value": 217, - "aggConfigResult": { - "key": 217, - "value": 217, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "bd", - "value": "bd", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 52, - "type": "bucket" - }, - "$order": 53, - "type": "metric" - }, - "center": [59.0625, -151.875], - "rectangle": [[56.25, -157.5], [56.25, -146.25], [61.875, -146.25], [61.875, -157.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-81.13159038126469, 26.815882762894034]}, - "properties": { - "geohash": "dh", - "value": 214, - "aggConfigResult": { - "key": 214, - "value": 214, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dh", - "value": "dh", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 55, - "type": "bucket" - }, - "$order": 56, - "type": "metric" - }, - "center": [25.3125, -84.375], - "rectangle": [[22.5, -90], [22.5, -78.75], [28.125, -78.75], [28.125, -90]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-162.1049272455275, 64.38826035708189]}, - "properties": { - "geohash": "b7", - "value": 194, - "aggConfigResult": { - "key": 194, - "value": 194, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "b7", - "value": "b7", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 58, - "type": "bucket" - }, - "$order": 59, - "type": "metric" - }, - "center": [64.6875, -163.125], - "rectangle": [[61.875, -168.75], [61.875, -157.5], [67.5, -157.5], [67.5, -168.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-161.59194018691778, 60.06503529846668]}, - "properties": { - "geohash": "b6", - "value": 168, - "aggConfigResult": { - "key": 168, - "value": 168, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "b6", - "value": "b6", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 61, - "type": "bucket" - }, - "$order": 62, - "type": "metric" - }, - "center": [59.0625, -163.125], - "rectangle": [[56.25, -168.75], [56.25, -157.5], [61.875, -157.5], [61.875, -168.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-86.82362716645002, 45.665992330759764]}, - "properties": { - "geohash": "f0", - "value": 166, - "aggConfigResult": { - "key": 166, - "value": 166, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "f0", - "value": "f0", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 64, - "type": "bucket" - }, - "$order": 65, - "type": "metric" - }, - "center": [47.8125, -84.375], - "rectangle": [[45, -90], [45, -78.75], [50.625, -78.75], [50.625, -90]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-152.04110082238913, 65.17680524848402]}, - "properties": { - "geohash": "be", - "value": 158, - "aggConfigResult": { - "key": 158, - "value": 158, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "be", - "value": "be", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 67, - "type": "bucket" - }, - "$order": 68, - "type": "metric" - }, - "center": [64.6875, -151.875], - "rectangle": [[61.875, -157.5], [61.875, -146.25], [67.5, -146.25], [67.5, -157.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-116.37748382985592, 33.16976627334952]}, - "properties": { - "geohash": "9m", - "value": 100, - "aggConfigResult": { - "key": 100, - "value": 100, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9m", - "value": "9m", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 70, - "type": "bucket" - }, - "$order": 71, - "type": "metric" - }, - "center": [30.9375, -118.125], - "rectangle": [[28.125, -123.75], [28.125, -112.5], [33.75, -112.5], [33.75, -123.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-139.12713261321187, 59.41271326504648]}, - "properties": { - "geohash": "bf", - "value": 72, - "aggConfigResult": { - "key": 72, - "value": 72, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "bf", - "value": "bf", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 73, - "type": "bucket" - }, - "$order": 74, - "type": "metric" - }, - "center": [59.0625, -140.625], - "rectangle": [[56.25, -146.25], [56.25, -135], [61.875, -135], [61.875, -146.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-97.89513738825917, 26.928304536268115]}, - "properties": { - "geohash": "9u", - "value": 68, - "aggConfigResult": { - "key": 68, - "value": 68, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9u", - "value": "9u", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 76, - "type": "bucket" - }, - "$order": 77, - "type": "metric" - }, - "center": [25.3125, -95.625], - "rectangle": [[22.5, -101.25], [22.5, -90], [28.125, -90], [28.125, -101.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-132.52599561586976, 55.60743710026145]}, - "properties": { - "geohash": "c1", - "value": 67, - "aggConfigResult": { - "key": 67, - "value": 67, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "c1", - "value": "c1", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 79, - "type": "bucket" - }, - "$order": 80, - "type": "metric" - }, - "center": [53.4375, -129.375], - "rectangle": [[50.625, -135], [50.625, -123.75], [56.25, -123.75], [56.25, -135]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-124.13590382784605, 42.24034773185849]}, - "properties": { - "geohash": "9p", - "value": 58, - "aggConfigResult": { - "key": 58, - "value": 58, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9p", - "value": "9p", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 82, - "type": "bucket" - }, - "$order": 83, - "type": "metric" - }, - "center": [42.1875, -129.375], - "rectangle": [[39.375, -135], [39.375, -123.75], [45, -123.75], [45, -135]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-65.72741221636534, 18.170374436303973]}, - "properties": { - "geohash": "de", - "value": 57, - "aggConfigResult": { - "key": 57, - "value": 57, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "de", - "value": "de", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 85, - "type": "bucket" - }, - "$order": 86, - "type": "metric" - }, - "center": [19.6875, -61.875], - "rectangle": [[16.875, -67.5], [16.875, -56.25], [22.5, -56.25], [22.5, -67.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-133.79055473953485, 57.08371731452644]}, - "properties": { - "geohash": "c4", - "value": 56, - "aggConfigResult": { - "key": 56, - "value": 56, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "c4", - "value": "c4", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 88, - "type": "bucket" - }, - "$order": 89, - "type": "metric" - }, - "center": [59.0625, -129.375], - "rectangle": [[56.25, -135], [56.25, -123.75], [61.875, -123.75], [61.875, -135]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-152.2658603824675, 69.64116730727255]}, - "properties": { - "geohash": "bs", - "value": 51, - "aggConfigResult": { - "key": 51, - "value": 51, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "bs", - "value": "bs", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 91, - "type": "bucket" - }, - "$order": 92, - "type": "metric" - }, - "center": [70.3125, -151.875], - "rectangle": [[67.5, -157.5], [67.5, -146.25], [73.125, -146.25], [73.125, -157.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-143.8043469004333, 64.64996575377882]}, - "properties": { - "geohash": "bg", - "value": 49, - "aggConfigResult": { - "key": 49, - "value": 49, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "bg", - "value": "bg", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 94, - "type": "bucket" - }, - "$order": 95, - "type": "metric" - }, - "center": [64.6875, -140.625], - "rectangle": [[61.875, -146.25], [61.875, -135], [67.5, -135], [67.5, -146.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-162.65227859839797, 54.967785738408566]}, - "properties": { - "geohash": "b3", - "value": 43, - "aggConfigResult": { - "key": 43, - "value": 43, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "b3", - "value": "b3", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 97, - "type": "bucket" - }, - "$order": 98, - "type": "metric" - }, - "center": [53.4375, -163.125], - "rectangle": [[50.625, -168.75], [50.625, -157.5], [56.25, -157.5], [56.25, -168.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-156.20294423773885, 20.63592097721994]}, - "properties": { - "geohash": "8e", - "value": 40, - "aggConfigResult": { - "key": 40, - "value": 40, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "8e", - "value": "8e", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 100, - "type": "bucket" - }, - "$order": 101, - "type": "metric" - }, - "center": [19.6875, -151.875], - "rectangle": [[16.875, -157.5], [16.875, -146.25], [22.5, -146.25], [22.5, -157.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-68.71966263279319, 45.89407338760793]}, - "properties": { - "geohash": "f2", - "value": 37, - "aggConfigResult": { - "key": 37, - "value": 37, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "f2", - "value": "f2", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 103, - "type": "bucket" - }, - "$order": 104, - "type": "metric" - }, - "center": [47.8125, -73.125], - "rectangle": [[45, -78.75], [45, -67.5], [50.625, -67.5], [50.625, -78.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-159.04649432748556, 21.810192000120878]}, - "properties": { - "geohash": "87", - "value": 31, - "aggConfigResult": { - "key": 31, - "value": 31, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "87", - "value": "87", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 106, - "type": "bucket" - }, - "$order": 107, - "type": "metric" - }, - "center": [19.6875, -163.125], - "rectangle": [[16.875, -168.75], [16.875, -157.5], [22.5, -157.5], [22.5, -168.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-124.07574724406004, 46.70505428686738]}, - "properties": { - "geohash": "c0", - "value": 30, - "aggConfigResult": { - "key": 30, - "value": 30, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "c0", - "value": "c0", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 109, - "type": "bucket" - }, - "$order": 110, - "type": "metric" - }, - "center": [47.8125, -129.375], - "rectangle": [[45, -135], [45, -123.75], [50.625, -123.75], [50.625, -135]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-170.66843625158072, 64.42178352735937]}, - "properties": { - "geohash": "b5", - "value": 18, - "aggConfigResult": { - "key": 18, - "value": 18, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "b5", - "value": "b5", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 112, - "type": "bucket" - }, - "$order": 113, - "type": "metric" - }, - "center": [64.6875, -174.375], - "rectangle": [[61.875, -180], [61.875, -168.75], [67.5, -168.75], [67.5, -180]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-164.1237143240869, 68.94954898394644]}, - "properties": { - "geohash": "bk", - "value": 17, - "aggConfigResult": { - "key": 17, - "value": 17, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "bk", - "value": "bk", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 115, - "type": "bucket" - }, - "$order": 116, - "type": "metric" - }, - "center": [70.3125, -163.125], - "rectangle": [[67.5, -168.75], [67.5, -157.5], [73.125, -157.5], [73.125, -168.75]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-145.23947272449732, 14.257271960377693]}, - "properties": { - "geohash": "8f", - "value": 17, - "aggConfigResult": { - "key": 17, - "value": 17, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "8f", - "value": "8f", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 118, - "type": "bucket" - }, - "$order": 119, - "type": "metric" - }, - "center": [14.0625, -140.625], - "rectangle": [[11.25, -146.25], [11.25, -135], [16.875, -135], [16.875, -146.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-169.90729674696922, 56.83546897955239]}, - "properties": { - "geohash": "b4", - "value": 16, - "aggConfigResult": { - "key": 16, - "value": 16, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "b4", - "value": "b4", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 121, - "type": "bucket" - }, - "$order": 122, - "type": "metric" - }, - "center": [59.0625, -174.375], - "rectangle": [[56.25, -180], [56.25, -168.75], [61.875, -168.75], [61.875, -180]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-170.12874579057097, 14.265542635694146]}, - "properties": { - "geohash": "84", - "value": 12, - "aggConfigResult": { - "key": 12, - "value": 12, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "84", - "value": "84", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 124, - "type": "bucket" - }, - "$order": 125, - "type": "metric" - }, - "center": [14.0625, -174.375], - "rectangle": [[11.25, -180], [11.25, -168.75], [16.875, -168.75], [16.875, -180]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-144.66744488105178, 69.03327229432762]}, - "properties": { - "geohash": "bu", - "value": 11, - "aggConfigResult": { - "key": 11, - "value": 11, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "bu", - "value": "bu", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 127, - "type": "bucket" - }, - "$order": 128, - "type": "metric" - }, - "center": [70.3125, -140.625], - "rectangle": [[67.5, -146.25], [67.5, -135], [73.125, -135], [73.125, -146.25]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-67.10587805137038, 44.86871098168194]}, - "properties": { - "geohash": "dx", - "value": 5, - "aggConfigResult": { - "key": 5, - "value": 5, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "dx", - "value": "dx", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 130, - "type": "bucket" - }, - "$order": 131, - "type": "metric" - }, - "center": [42.1875, -61.875], - "rectangle": [[39.375, -67.5], [39.375, -56.25], [45, -56.25], [45, -67.5]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-174.69428664073348, 52.15187128633261]}, - "properties": { - "geohash": "b1", - "value": 5, - "aggConfigResult": { - "key": 5, - "value": 5, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "b1", - "value": "b1", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 133, - "type": "bucket" - }, - "$order": 134, - "type": "metric" - }, - "center": [53.4375, -174.375], - "rectangle": [[50.625, -180], [50.625, -168.75], [56.25, -168.75], [56.25, -180]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-123.75373480841517, 39.26203776150942]}, - "properties": { - "geohash": "9n", - "value": 5, - "aggConfigResult": { - "key": 5, - "value": 5, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "9n", - "value": "9n", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 136, - "type": "bucket" - }, - "$order": 137, - "type": "metric" - }, - "center": [36.5625, -129.375], - "rectangle": [[33.75, -135], [33.75, -123.75], [39.375, -123.75], [39.375, -135]] - } - }, { - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [-145.7686112076044, 18.124444372951984]}, - "properties": { - "geohash": "8g", - "value": 2, - "aggConfigResult": { - "key": 2, - "value": 2, - "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, - "$parent": { - "key": "8g", - "value": "8g", - "aggConfig": { - "id": "2", - "enabled": true, - "type": "geohash_grid", - "schema": "segment", - "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} - }, - "$order": 139, - "type": "bucket" - }, - "$order": 140, - "type": "metric" - }, - "center": [19.6875, -140.625], - "rectangle": [[16.875, -146.25], [16.875, -135], [22.5, -135], [22.5, -146.25]] - } - }], - "properties": {"min": 2, "max": 1418, "zoom": 3, "center": [39.57182223734374, -109.51171875]} -}`; - -export const GeoHashSampleData = JSON.parse(sampleData); diff --git a/src/ui/public/vis_maps/__tests__/kibana_map.js b/src/ui/public/vis_maps/__tests__/kibana_map.js deleted file mode 100644 index cad4d79f3e62c3..00000000000000 --- a/src/ui/public/vis_maps/__tests__/kibana_map.js +++ /dev/null @@ -1,139 +0,0 @@ -import expect from 'expect.js'; -import { KibanaMap } from 'ui/vis_maps/kibana_map'; - -describe('kibana_map tests', function () { - - let domNode; - let kibanaMap; - - function setupDOM() { - domNode = document.createElement('div'); - domNode.style.top = '0'; - domNode.style.left = '0'; - domNode.style.width = '512px'; - domNode.style.height = '512px'; - domNode.style.position = 'fixed'; - domNode.style['pointer-events'] = 'none'; - document.body.appendChild(domNode); - } - - function teardownDOM() { - domNode.innerHTML = ''; - document.body.removeChild(domNode); - } - - - describe('KibanaMap - basics', function () { - - beforeEach(async function () { - setupDOM(); - kibanaMap = new KibanaMap(domNode, { - minZoom: 1, - maxZoom: 10, - center: [0,0], - zoom: 0 - }); - }); - - afterEach(function () { - kibanaMap.destroy(); - teardownDOM(); - }); - - it('should instantiate at zoom level 2', function () { - const bounds = kibanaMap.getBounds(); - expect(bounds.bottom_right.lon).to.equal(90); - expect(bounds.top_left.lon).to.equal(-90); - expect(kibanaMap.getCenter().lon).to.equal(0); - expect(kibanaMap.getCenter().lat).to.equal(0); - expect(kibanaMap.getZoomLevel()).to.equal(2); - }); - - it('should resize to fit container', function () { - - kibanaMap.setZoomLevel(2); - expect(kibanaMap.getCenter().lon).to.equal(0); - expect(kibanaMap.getCenter().lat).to.equal(0); - - domNode.style.width = '1024px'; - domNode.style.height = '1024px'; - kibanaMap.resize(); - - expect(kibanaMap.getCenter().lon).to.equal(0); - expect(kibanaMap.getCenter().lat).to.equal(0); - const bounds = kibanaMap.getBounds(); - expect(bounds.bottom_right.lon).to.equal(180); - expect(bounds.top_left.lon).to.equal(-180); - - }); - - }); - - - describe('KibanaMap - baseLayer', function () { - - beforeEach(async function () { - setupDOM(); - kibanaMap = new KibanaMap(domNode, { - minZoom: 1, - maxZoom: 10, - center: [0,0], - zoom: 0 - }); - }); - - afterEach(function () { - kibanaMap.destroy(); - teardownDOM(); - }); - - - it('TMS', async function () { - - const options = { - 'url': 'https://tiles-stage.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana', - 'minZoom': 0, - 'maxZoom': 12, - 'attribution': '© [Elastic Maps Service](https://www.elastic.co/elastic-maps-service)' - }; - - - return new Promise(function (resolve) { - kibanaMap.on('baseLayer:loaded', () => { - resolve(); - }); - kibanaMap.setBaseLayer({ - baseLayerType: 'tms', - options: options - }); - }); - }); - - it('WMS - should handle empty settings', async function () { - - const invalidOptions = { - url: undefined, - version: undefined, - layers: undefined, - format: 'image/png', - transparent: true, - attribution: undefined, - styles: '', - minZoom: 1, - maxZoom: 18 - }; - - kibanaMap.setBaseLayer({ - baseLayerType: 'wms', - options: invalidOptions - }); - - expect(kibanaMap.getLeafletBaseLayer()).to.eql(null); - - - }); - - }); - - -}); diff --git a/src/ui/public/vis_maps/geohash_layer.js b/src/ui/public/vis_maps/geohash_layer.js deleted file mode 100644 index 00dafadf0be6cf..00000000000000 --- a/src/ui/public/vis_maps/geohash_layer.js +++ /dev/null @@ -1,96 +0,0 @@ -import L from 'leaflet'; -import _ from 'lodash'; - -import { KibanaMapLayer } from './kibana_map_layer'; -import { HeatmapMarkers } from './markers/heatmap'; -import { ScaledCirclesMarkers } from './markers/scaled_circles'; -import { ShadedCirclesMarkers } from './markers/shaded_circles'; -import { GeohashGridMarkers } from './markers/geohash_grid'; - -export class GeohashLayer extends KibanaMapLayer { - - constructor(featureCollection, options, zoom, kibanaMap) { - - super(); - - this._geohashGeoJson = featureCollection; - this._geohashOptions = options; - this._zoom = zoom; - this._kibanaMap = kibanaMap; - const geojson = L.geoJson(this._geohashGeoJson); - this._bounds = geojson.getBounds(); - this._createGeohashMarkers(); - } - - _createGeohashMarkers() { - const markerOptions = { - valueFormatter: this._geohashOptions.valueFormatter, - tooltipFormatter: this._geohashOptions.tooltipFormatter - }; - switch (this._geohashOptions.mapType) { - case 'Scaled Circle Markers': - this._geohashMarkers = new ScaledCirclesMarkers(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap); - break; - case 'Shaded Circle Markers': - this._geohashMarkers = new ShadedCirclesMarkers(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap); - break; - case 'Shaded Geohash Grid': - this._geohashMarkers = new GeohashGridMarkers(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap); - break; - case 'Heatmap': - this._geohashMarkers = new HeatmapMarkers(this._geohashGeoJson, { - radius: parseFloat(this._geohashOptions.heatmap.heatRadius), - blur: parseFloat(this._geohashOptions.heatmap.heatBlur), - maxZoom: parseFloat(this._geohashOptions.heatmap.heatMaxZoom), - minOpacity: parseFloat(this._geohashOptions.heatmap.heatMinOpacity), - tooltipFormatter: this._geohashOptions.tooltipFormatter - }, this._zoom, this._kibanaMap); - break; - default: - throw new Error(`${this._geohashOptions.mapType} mapType not recognized`); - - } - - this._geohashMarkers.on('showTooltip', (event) => this.emit('showTooltip', event)); - this._geohashMarkers.on('hideTooltip', (event) => this.emit('hideTooltip', event)); - this._leafletLayer = this._geohashMarkers.getLeafletLayer(); - } - - appendLegendContents(jqueryDiv) { - return this._geohashMarkers.appendLegendContents(jqueryDiv); - } - - movePointer(...args) { - this._geohashMarkers.movePointer(...args); - } - - getBounds() { - return this._bounds; - } - - updateExtent() { - //this removal is required to trigger the bounds filter again - this._kibanaMap.removeLayer(this); - this._createGeohashMarkers(); - this._kibanaMap.addLayer(this); - } - - - isReusable(options) { - - if (_.isEqual(this._geohashOptions, options)) { - return true; - } - - if (this._geohashOptions.mapType !== options.mapType) { - return false; - } else if (this._geohashOptions.mapType === 'Heatmap' && !_.isEqual(this._geohashOptions.heatmap, options)) { - return false; - } else { - return true; - } - } -} - - - diff --git a/src/ui/public/vis_maps/kibana_map.js b/src/ui/public/vis_maps/kibana_map.js deleted file mode 100644 index e44f95462cd5fe..00000000000000 --- a/src/ui/public/vis_maps/kibana_map.js +++ /dev/null @@ -1,565 +0,0 @@ -import { EventEmitter } from 'events'; -import L from 'leaflet'; -import $ from 'jquery'; -import _ from 'lodash'; -import { zoomToPrecision } from 'ui/utils/zoom_to_precision'; - -function makeFitControl(fitContainer, kibanaMap) { - - const FitControl = L.Control.extend({ - options: { - position: 'topleft' - }, - initialize: function (fitContainer, kibanaMap) { - this._fitContainer = fitContainer; - this._kibanaMap = kibanaMap; - this._leafletMap = null; - }, - onAdd: function (leafletMap) { - this._leafletMap = leafletMap; - $(this._fitContainer).html('') - .on('click', e => { - e.preventDefault(); - this._kibanaMap.fitToData(); - }); - - return this._fitContainer; - }, - onRemove: function () { - $(this._fitContainer).off('click'); - } - }); - - return new FitControl(fitContainer, kibanaMap); -} - -function makeLegedControl(container, kibanaMap, position) { - - const LegendControl = L.Control.extend({ - - options: { - position: 'topright' - }, - - initialize: function (container, kibanaMap, position) { - this._legendContainer = container; - this._kibanaMap = kibanaMap; - this.options.position = position; - - }, - - updateContents() { - this._legendContainer.empty(); - const $div = $('
').addClass('tilemap-legend'); - this._legendContainer.append($div); - const layers = this._kibanaMap.getLayers(); - layers.forEach((layer) =>layer.appendLegendContents($div)); - }, - - - onAdd: function () { - this._layerUpdateHandle = () => this.updateContents(); - this._kibanaMap.on('layers:update', this._layerUpdateHandle); - this.updateContents(); - return this._legendContainer.get(0); - }, - onRemove: function () { - this._kibanaMap.removeListener('layers:update', this._layerUpdateHandle); - this._legendContainer.empty(); - } - - }); - - return new LegendControl(container, kibanaMap, position); -} - -/** - * Collects map functionality required for Kibana. - * Serves as simple abstraction for leaflet as well. - */ -export class KibanaMap extends EventEmitter { - - constructor(containerNode, options) { - - super(); - this._containerNode = containerNode; - this._leafletBaseLayer = null; - this._baseLayerSettings = null; - this._baseLayerIsDesaturated = true; - - this._leafletDrawControl = null; - this._leafletFitControl = null; - this._leafletLegendControl = null; - this._legendPosition = 'topright'; - - this._layers = []; - this._listeners = []; - this._showTooltip = false; - - - const leafletOptions = { - minZoom: options.minZoom, - maxZoom: options.maxZoom, - center: options.center ? options.center : [0, 0], - zoom: options.zoom ? options.zoom : 2 - }; - - this._leafletMap = L.map(containerNode, leafletOptions); - this._leafletMap.scrollWheelZoom.disable(); - const worldBounds = L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)); - this._leafletMap.setMaxBounds(worldBounds); - - let previousZoom = this._leafletMap.getZoom(); - this._leafletMap.on('zoomend', () => { - if (previousZoom !== this._leafletMap.getZoom()) { - previousZoom = this._leafletMap.getZoom(); - this.emit('zoomchange'); - } - }); - this._leafletMap.on('zoomend', () => this.emit('zoomend')); - this._leafletMap.on('dragend', () => this.emit('dragend')); - this._leafletMap.on('dragend', e => this._layers.forEach(layer => layer.updateExtent('dragend', e))); - this._leafletMap.on('mousemove', e => this._layers.forEach(layer => layer.movePointer('mousemove', e))); - this._leafletMap.on('mouseout', e => this._layers.forEach(layer => layer.movePointer('mouseout', e))); - this._leafletMap.on('mousedown', e => this._layers.forEach(layer => layer.movePointer('mousedown', e))); - this._leafletMap.on('mouseup', e => this._layers.forEach(layer => layer.movePointer('mouseup', e))); - this._leafletMap.on('draw:created', event => { - const drawType = event.layerType; - if (drawType === 'rectangle') { - const bounds = event.layer.getBounds(); - - const southEast = bounds.getSouthEast(); - const northWest = bounds.getNorthWest(); - let southEastLng = southEast.lng; - if (southEastLng > 180) { - southEastLng -= 360; - } - let northWestLng = northWest.lng; - if (northWestLng < -180) { - northWestLng += 360; - } - - const southEastLat = southEast.lat; - const northWestLat = northWest.lat; - - //Bounds cannot be created unless they form a box with larger than 0 dimensions - //Invalid areas are rejected by ES. - if (southEastLat === northWestLat || southEastLng === northWestLng) { - return; - } - - this.emit('drawCreated:rectangle', { - bounds: { - bottom_right: { - lat: southEastLat, - lon: southEastLng - }, - top_left: { - lat: northWestLat, - lon: northWestLng - } - } - }); - } else if (drawType === 'polygon') { - const latLongs = event.layer.getLatLngs(); - this.emit('drawCreated:polygon', { - points: latLongs.map(leafletLatLng => { - return { - lat: leafletLatLng.lat, - lon: leafletLatLng.lng - }; - }) - }); - } - }); - - this.resize(); - - - } - - setShowTooltip(showTooltip) { - this._showTooltip = showTooltip; - } - - getLayers() { - return this._layers.slice(); - } - - - addLayer(kibanaLayer) { - - - this.emit('layers:invalidate'); - - const onshowTooltip = (event) => { - - if (!this._showTooltip) { - return; - } - - if (!this._popup) { - this._popup = L.popup({ autoPan: false }); - this._popup.setLatLng(event.position); - this._popup.setContent(event.content); - this._popup.openOn(this._leafletMap); - } else { - if (!this._popup.getLatLng().equals(event.position)) { - this._popup.setLatLng(event.position); - } - if (this._popup.getContent() !== event.content) { - this._popup.setContent(event.content); - } - } - - }; - - kibanaLayer.on('showTooltip', onshowTooltip); - this._listeners.push({ name: 'showTooltip', handle: onshowTooltip, layer: kibanaLayer }); - - const onHideTooltip = () => { - this._leafletMap.closePopup(); - this._popup = null; - }; - kibanaLayer.on('hideTooltip', onHideTooltip); - this._listeners.push({ name: 'hideTooltip', handle: onHideTooltip, layer: kibanaLayer }); - - - const onStyleChanged = () => { - if (this._leafletLegendControl) { - this._leafletLegendControl.updateContents(); - } - }; - kibanaLayer.on('styleChanged', onStyleChanged); - this._listeners.push({ name: 'styleChanged', handle: onStyleChanged, layer: kibanaLayer }); - - this._layers.push(kibanaLayer); - kibanaLayer.addToLeafletMap(this._leafletMap); - this.emit('layers:update'); - } - - removeLayer(layer) { - const index = this._layers.indexOf(layer); - if (index >= 0) { - this._layers.splice(index, 1); - layer.removeFromLeafletMap(this._leafletMap); - } - this._listeners.forEach(listener => { - if (listener.layer === layer) { - listener.layer.removeListener(listener.name, listener.handle); - } - }); - } - - destroy() { - if (this._leafletFitControl) { - this._leafletMap.removeControl(this._leafletFitControl); - } - if (this._leafletDrawControl) { - this._leafletMap.removeControl(this._leafletDrawControl); - } - if (this._leafletLegendControl) { - this._leafletMap.removeControl(this._leafletLegendControl); - } - this.setBaseLayer(null); - let layer; - while (this._layers.length) { - layer = this._layers.pop(); - layer.removeFromLeafletMap(this._leafletMap); - } - this._leafletMap.remove(); - this._containerNode.innerHTML = ''; - this._listeners.forEach(listener => listener.layer.removeListener(listener.name, listener.handle)); - } - - getCenter() { - const center = this._leafletMap.getCenter(); - return { lon: center.lng, lat: center.lat }; - } - - setCenter(latitude, longitude) { - const latLong = L.latLng(latitude, longitude); - if (latLong.equals && !latLong.equals(this._leafletMap.getCenter())) { - this._leafletMap.setView(latLong); - } - } - - setZoomLevel(zoomLevel) { - if (this._leafletMap.getZoom() !== zoomLevel) { - this._leafletMap.setZoom(zoomLevel); - } - } - - getZoomLevel() { - return this._leafletMap.getZoom(); - } - - getMaxZoomLevel() { - return this._leafletMap.getMaxZoom(); - } - - getAutoPrecision() { - return zoomToPrecision(this._leafletMap.getZoom(), 12, this._leafletMap.getMaxZoom()); - } - - getBounds() { - - const bounds = this._leafletMap.getBounds(); - if (!bounds) { - return null; - } - - const southEast = bounds.getSouthEast(); - const northWest = bounds.getNorthWest(); - let southEastLng = southEast.lng; - if (southEastLng > 180) { - southEastLng -= 360; - } - let northWestLng = northWest.lng; - if (northWestLng < -180) { - northWestLng += 360; - } - - const southEastLat = southEast.lat; - const northWestLat = northWest.lat; - - //Bounds cannot be created unless they form a box with larger than 0 dimensions - //Invalid areas are rejected by ES. - if (southEastLat === northWestLat || southEastLng === northWestLng) { - return; - } - - return { - bottom_right: { - lat: southEastLat, - lon: southEastLng - }, - top_left: { - lat: northWestLat, - lon: northWestLng - } - }; - } - - - setDesaturateBaseLayer(isDesaturated) { - if (isDesaturated === this._baseLayerIsDesaturated) { - return; - } - this._baseLayerIsDesaturated = isDesaturated; - this._updateDesaturation(); - this._leafletBaseLayer.redraw(); - } - - addDrawControl() { - const shapeOptions = { - shapeOptions: { - stroke: false, - color: '#000' - } - }; - const drawOptions = { - draw: { - polyline: false, - marker: false, - circle: false, - rectangle: shapeOptions, - polygon: shapeOptions - } - }; - this._leafletDrawControl = new L.Control.Draw(drawOptions); - this._leafletMap.addControl(this._leafletDrawControl); - } - - addFitControl() { - - if (this._leafletFitControl || !this._leafletMap) { - return; - } - - const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit'); - this._leafletFitControl = makeFitControl(fitContainer, this); - this._leafletMap.addControl(this._leafletFitControl); - } - - addLegendControl() { - if (this._leafletLegendControl || !this._leafletMap) { - return; - } - this._updateLegend(); - } - - setLegendPosition(position) { - if (this._legendPosition === position) { - if (!this._leafletLegendControl) { - this._updateLegend(); - } - } else { - this._legendPosition = position; - this._updateLegend(); - } - - - } - - _updateLegend() { - if (this._leafletLegendControl) { - this._leafletMap.removeControl(this._leafletLegendControl); - } - const $wrapper = $('
').addClass('tilemap-legend-wrapper'); - this._leafletLegendControl = makeLegedControl($wrapper, this, this._legendPosition); - this._leafletMap.addControl(this._leafletLegendControl); - } - - resize() { - this._leafletMap.invalidateSize(); - this._updateExtent(); - } - - - getLeafletBaseLayer() { - return this._leafletBaseLayer; - } - - setBaseLayer(settings) { - - if (_.isEqual(settings, this._baseLayerSettings)) { - return; - } - - this._baseLayerSettings = settings; - if (settings === null) { - if (this._leafletBaseLayer && this._leafletMap) { - this._leafletMap.removeLayer(this._leafletBaseLayer); - this._leafletBaseLayer = null; - } - return; - } - - if (this._leafletBaseLayer) { - this._leafletMap.removeLayer(this._leafletBaseLayer); - this._leafletBaseLayer = null; - } - - let baseLayer; - if (settings.baseLayerType === 'wms') { - baseLayer = this._getWMSBaseLayer(settings.options); - } else if (settings.baseLayerType === 'tms') { - baseLayer = this._getTMSBaseLayer((settings.options)); - } - - if (baseLayer) { - baseLayer.on('tileload', () => this._updateDesaturation()); - baseLayer.on('load', () => { - this.emit('baseLayer:loaded'); - }); - baseLayer.on('loading', () => { - this.emit('baseLayer:loading'); - }); - - this._leafletBaseLayer = baseLayer; - this._leafletBaseLayer.addTo(this._leafletMap); - this._leafletBaseLayer.bringToBack(); - if (settings.options.minZoom > this._leafletMap.getZoom()) { - this._leafletMap.setZoom(settings.options.minZoom); - } - this.resize(); - } - - } - - isInside(bucketRectBounds) { - const mapBounds = this._leafletMap.getBounds(); - return mapBounds.intersects(bucketRectBounds); - } - - fitToData() { - - if (!this._leafletMap) { - return; - } - - let bounds = null; - this._layers.forEach(layer => { - const b = layer.getBounds(); - if (bounds) { - bounds.extend(b); - } else { - bounds = b; - } - }); - - if (bounds && bounds.isValid()) { - this._leafletMap.fitBounds(bounds); - } - } - - _getTMSBaseLayer(options) { - return L.tileLayer(options.url, { - minZoom: options.minZoom, - maxZoom: options.maxZoom, - subdomains: options.subdomains || [], - attribution: options.attribution - }); - } - - _getWMSBaseLayer(options) { - const wmsOptions = { - attribution: options.attribution || '', - format: options.format || '', - layers: options.layers || '', - minZoom: options.minZoom, - maxZoom: options.maxZoom, - styles: options.styles || '', - transparent: options.transparent, - version: options.version || '1.3.0' - }; - - return (typeof options.url === 'string' && options.url.length) ? L.tileLayer.wms(options.url, wmsOptions) : null; - } - - _updateExtent() { - this._layers.forEach(layer => layer.updateExtent()); - } - - _updateDesaturation() { - const tiles = $('img.leaflet-tile-loaded'); - if (this._baseLayerIsDesaturated) { - tiles.removeClass('filters-off'); - } else if (!this._baseLayerIsDesaturated) { - tiles.addClass('filters-off'); - } - } - - persistUiStateForVisualization(visualization) { - function persistMapStateInUiState() { - const uiState = visualization.getUiState(); - const centerFromUIState = uiState.get('mapCenter'); - const zoomFromUiState = parseInt(uiState.get('mapZoom')); - if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) { - uiState.set('mapZoom', this.getZoomLevel()); - } - const centerFromMap = this.getCenter(); - if (!centerFromUIState || centerFromMap.lon !== centerFromUIState[1] || centerFromMap.lat !== centerFromUIState[0]) { - uiState.set('mapCenter', [centerFromMap.lat, centerFromMap.lon]); - } - } - - this.on('dragend', persistMapStateInUiState); - this.on('zoomend', persistMapStateInUiState); - } - - useUiStateFromVisualization(visualization) { - const uiState = visualization.getUiState(); - const zoomFromUiState = parseInt(uiState.get('mapZoom')); - const centerFromUIState = uiState.get('mapCenter'); - if (!isNaN(zoomFromUiState)) { - this.setZoomLevel(zoomFromUiState); - } - if (centerFromUIState) { - this.setCenter(centerFromUIState[0], centerFromUIState[1]); - } - } -} - - diff --git a/src/ui/public/vis_maps/kibana_map_layer.js b/src/ui/public/vis_maps/kibana_map_layer.js deleted file mode 100644 index 19a957cda49604..00000000000000 --- a/src/ui/public/vis_maps/kibana_map_layer.js +++ /dev/null @@ -1,38 +0,0 @@ -import { EventEmitter } from 'events'; - - -export class KibanaMapLayer extends EventEmitter { - constructor() { - super(); - this._leafletLayer = null; - } - - getBounds() { - return this._leafletLayer.getBounds(); - } - - addToLeafletMap(leafletMap) { - this._leafletLayer.addTo(leafletMap); - } - - removeFromLeafletMap(leafletMap) { - leafletMap.removeLayer(this._leafletLayer); - } - - appendLegendContents() { - } - - updateExtent() { - } - - movePointer() { - } -} - - - - - - - - diff --git a/src/ui/public/vis_maps/maps_renderbot.js b/src/ui/public/vis_maps/maps_renderbot.js deleted file mode 100644 index 172d3f973fecaa..00000000000000 --- a/src/ui/public/vis_maps/maps_renderbot.js +++ /dev/null @@ -1,262 +0,0 @@ -import $ from 'jquery'; -import _ from 'lodash'; -import { VisRenderbotProvider } from 'ui/vis/renderbot'; -import { VislibVisTypeBuildChartDataProvider } from 'ui/vislib_vis_type/build_chart_data'; -import { FilterBarPushFilterProvider } from 'ui/filter_bar/push_filter'; -import { KibanaMap } from './kibana_map'; -import { GeohashLayer } from './geohash_layer'; -import './lib/service_settings'; -import './styles/_tilemap.less'; -import { ResizeCheckerProvider } from 'ui/resize_checker'; - -// eslint-disable-next-line kibana-custom/no-default-export -export default function MapsRenderbotFactory(Private, $injector, serviceSettings, Notifier, courier, getAppState) { - - const ResizeChecker = Private(ResizeCheckerProvider); - const Renderbot = Private(VisRenderbotProvider); - const buildChartData = Private(VislibVisTypeBuildChartDataProvider); - const notify = new Notifier({ location: 'Coordinate Map' }); - - class MapsRenderbot extends Renderbot { - - constructor(vis, $el, uiState) { - super(vis, $el, uiState); - this._buildChartData = buildChartData.bind(this); - this._geohashLayer = null; - this._kibanaMap = null; - this._$container = $el; - this._kibanaMapReady = this._makeKibanaMap($el); - this._baseLayerDirty = true; - this._dataDirty = true; - this._paramsDirty = true; - this._resizeChecker = new ResizeChecker($el); - this._resizeChecker.on('resize', () => { - if (this._kibanaMap) { - this._kibanaMap.resize(); - } - }); - } - - async _makeKibanaMap() { - - try { - this._tmsService = await serviceSettings.getTMSService(); - this._tmsError = null; - } catch (e) { - this._tmsService = null; - this._tmsError = e; - notify.warning(e.message); - } - - if (this._kibanaMap) { - this._kibanaMap.destroy(); - } - - const containerElement = $(this._$container)[0]; - const options = _.clone(this._getMinMaxZoom()); - const uiState = this.vis.getUiState(); - const zoomFromUiState = parseInt(uiState.get('mapZoom')); - const centerFromUIState = uiState.get('mapCenter'); - options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.type.params.defaults.mapZoom; - options.center = centerFromUIState ? centerFromUIState : this.vis.type.params.defaults.mapCenter; - - this._kibanaMap = new KibanaMap(containerElement, options); - this._kibanaMap.addDrawControl(); - this._kibanaMap.addFitControl(); - this._kibanaMap.addLegendControl(); - this._kibanaMap.persistUiStateForVisualization(this.vis); - - let previousPrecision = this._kibanaMap.getAutoPrecision(); - let precisionChange = false; - this._kibanaMap.on('zoomchange', () => { - precisionChange = (previousPrecision !== this._kibanaMap.getAutoPrecision()); - previousPrecision = this._kibanaMap.getAutoPrecision(); - }); - this._kibanaMap.on('zoomend', () => { - - const isAutoPrecision = _.get(this._chartData, 'geohashGridAgg.params.autoPrecision', true); - if (!isAutoPrecision) { - return; - } - - this._dataDirty = true; - if (precisionChange) { - courier.fetch(); - } else { - this._recreateGeohashLayer(); - this._dataDirty = false; - this._doRenderComplete(); - } - }); - - - this._kibanaMap.on('drawCreated:rectangle', event => { - addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds); - }); - this._kibanaMap.on('drawCreated:polygon', event => { - addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_polygon', { points: event.points }); - }); - this._kibanaMap.on('baseLayer:loaded', () => { - this._baseLayerDirty = false; - this._doRenderComplete(); - }); - this._kibanaMap.on('baseLayer:loading', () => { - this._baseLayerDirty = true; - }); - } - - _getMinMaxZoom() { - const mapParams = this._getMapsParams(); - if (this._tmsError) { - return serviceSettings.getFallbackZoomSettings(mapParams.wms.enabled); - } else { - return this._tmsService.getMinMaxZoom(mapParams.wms.enabled); - } - } - - _recreateGeohashLayer() { - if (this._geohashLayer) { - this._kibanaMap.removeLayer(this._geohashLayer); - } - if (!this._geohashGeoJson) { - return; - } - const geohashOptions = this._getGeohashOptions(); - this._geohashLayer = new GeohashLayer(this._chartData.geoJson, geohashOptions, this._kibanaMap.getZoomLevel(), this._kibanaMap); - this._kibanaMap.addLayer(this._geohashLayer); - } - - - /** - * called on data change - * @param esResponse - */ - render(esResponse) { - this._dataDirty = true; - this._kibanaMapReady.then(() => { - this._chartData = this._buildChartData(esResponse); - this._geohashGeoJson = this._chartData.geoJson; - this._recreateGeohashLayer(); - this._kibanaMap.useUiStateFromVisualization(this.vis); - this._kibanaMap.resize(); - this._dataDirty = false; - this._doRenderComplete(); - }); - } - - destroy() { - if (this._kibanaMap) { - this._kibanaMap.destroy(); - } - } - - /** - * called on options change (vis.params change) - */ - updateParams() { - - this._paramsDirty = true; - this._kibanaMapReady.then(async() => { - const mapParams = this._getMapsParams(); - const { minZoom, maxZoom } = this._getMinMaxZoom(); - - if (mapParams.wms.enabled) { - - if (maxZoom > this._kibanaMap.getMaxZoomLevel()) { - this._geohashLayer = null; - this._kibanaMapReady = this._makeKibanaMap(); - } - - this._kibanaMap.setBaseLayer({ - baseLayerType: 'wms', - options: { - minZoom: minZoom, - maxZoom: maxZoom, - url: mapParams.wms.url, - ...mapParams.wms.options - } - }); - } else { - - if (maxZoom < this._kibanaMap.getMaxZoomLevel()) { - this._geohashLayer = null; - this._kibanaMapReady = this._makeKibanaMap(); - this._kibanaMap.setZoomLevel(maxZoom); - } - - if (!this._tmsError) { - const url = this._tmsService.getUrl(); - const options = this._tmsService.getTMSOptions(); - this._kibanaMap.setBaseLayer({ - baseLayerType: 'tms', - options: { url, ...options } - }); - } - } - const geohashOptions = this._getGeohashOptions(); - if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) { - this._recreateGeohashLayer(); - } - - this._kibanaMap.setDesaturateBaseLayer(mapParams.isDesaturated); - this._kibanaMap.setShowTooltip(mapParams.addTooltip); - this._kibanaMap.setLegendPosition(mapParams.legendPosition); - - this._kibanaMap.useUiStateFromVisualization(this.vis); - this._kibanaMap.resize(); - this._paramsDirty = false; - this._doRenderComplete(); - }); - } - - _getMapsParams() { - return _.assign( - {}, - this.vis.type.params.defaults, - { type: this.vis.type.name }, - this.vis.params - ); - } - - _getGeohashOptions() { - const newParams = this._getMapsParams(); - return { - valueFormatter: this._chartData ? this._chartData.valueFormatter : null, - tooltipFormatter: this._chartData ? this._chartData.tooltipFormatter : null, - mapType: newParams.mapType, - heatmap: { - heatBlur: newParams.heatBlur, - heatMaxZoom: newParams.heatMaxZoom, - heatMinOpacity: newParams.heatMinOpacity, - heatRadius: newParams.heatRadius - } - }; - } - - _doRenderComplete() { - if (this._paramsDirty || this._dataDirty || this._baseLayerDirty) { - return; - } - this.$el.trigger('renderComplete'); - } - - } - - function addSpatialFilter(agg, filterName, filterData) { - if (!agg) { - return; - } - - const indexPatternName = agg.vis.indexPattern.id; - const field = agg.fieldName(); - const filter = {}; - filter[filterName] = { ignore_unmapped: true }; - filter[filterName][field] = filterData; - - const putFilter = Private(FilterBarPushFilterProvider)(getAppState()); - return putFilter(filter, false, indexPatternName); - } - - - return MapsRenderbot; -} diff --git a/src/ui/public/vis_maps/maps_vis_type.js b/src/ui/public/vis_maps/maps_vis_type.js deleted file mode 100644 index a63e2386ac8f78..00000000000000 --- a/src/ui/public/vis_maps/maps_vis_type.js +++ /dev/null @@ -1,22 +0,0 @@ -import _ from 'lodash'; -import 'ui/vislib'; -import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import MapsVisTypeMapsRenderbotProvider from 'ui/vis_maps/maps_renderbot'; - -export function MapsVisTypeProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const MapsRenderbot = Private(MapsVisTypeMapsRenderbotProvider); - - _.class(MapsVisType).inherits(VisType); - function MapsVisType(opts = {}) { - MapsVisType.Super.call(this, opts); - this.listeners = opts.listeners || {}; - } - - MapsVisType.prototype.createRenderbot = function (vis, $el, uiState) { - return new MapsRenderbot(vis, $el, uiState); - }; - - return MapsVisType; -} diff --git a/src/ui/public/vis_maps/markers/geohash_grid.js b/src/ui/public/vis_maps/markers/geohash_grid.js deleted file mode 100644 index 6666ad842f6f27..00000000000000 --- a/src/ui/public/vis_maps/markers/geohash_grid.js +++ /dev/null @@ -1,17 +0,0 @@ -import L from 'leaflet'; -import { ScaledCirclesMarkers } from './scaled_circles'; - -export class GeohashGridMarkers extends ScaledCirclesMarkers { - getMarkerFunction() { - return function (feature) { - const geohashRect = feature.properties.rectangle; - // get bounds from northEast[3] and southWest[1] - // corners in geohash rectangle - const corners = [ - [geohashRect[3][0], geohashRect[3][1]], - [geohashRect[1][0], geohashRect[1][1]] - ]; - return L.rectangle(corners); - }; - } -} diff --git a/src/ui/public/vis_maps/markers/heatmap.js b/src/ui/public/vis_maps/markers/heatmap.js deleted file mode 100644 index ced8983550931b..00000000000000 --- a/src/ui/public/vis_maps/markers/heatmap.js +++ /dev/null @@ -1,189 +0,0 @@ -import L from 'leaflet'; -import _ from 'lodash'; -import d3 from 'd3'; -import { EventEmitter } from 'events'; - -/** - * Map overlay: canvas layer with leaflet.heat plugin - * - * @param map {Leaflet Object} - * @param geoJson {geoJson Object} - * @param params {Object} - */ -export class HeatmapMarkers extends EventEmitter { - - constructor(featureCollection, options, zoom) { - - super(); - this._geojsonFeatureCollection = featureCollection; - const points = dataToHeatArray(featureCollection); - this._leafletLayer = L.heatLayer(points, options); - this._tooltipFormatter = options.tooltipFormatter; - this._zoom = zoom; - this._disableTooltips = false; - this._getLatLng = _.memoize(function (feature) { - return L.latLng( - feature.geometry.coordinates[1], - feature.geometry.coordinates[0] - ); - }, function (feature) { - // turn coords into a string for the memoize cache - return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(','); - }); - - this._currentFeature = null; - this._addTooltips(); - } - - getBounds() { - return this._leafletLayer.getBounds(); - } - - getLeafletLayer() { - return this._leafletLayer; - } - - appendLegendContents() { - } - - - movePointer(type, event) { - if (type === 'mousemove') { - this._deboundsMoveMoveLocation(event); - } else if (type === 'mouseout') { - this.emit('hideTooltip'); - } else if (type === 'mousedown') { - this._disableTooltips = true; - this.emit('hideTooltip'); - } else if (type === 'mouseup') { - this._disableTooltips = false; - } - } - - - _addTooltips() { - - const mouseMoveLocation = (e) => { - - - - if (!this._geojsonFeatureCollection.features.length || this._disableTooltips) { - this.emit('hideTooltip'); - return; - } - - const feature = this._nearestFeature(e.latlng); - if (this._tooltipProximity(e.latlng, feature)) { - const content = this._tooltipFormatter(feature); - if (!content) { - return; - } - this.emit('showTooltip', { - content: content, - position: e.latlng - }); - } else { this.emit('hideTooltip'); - } - }; - - this._deboundsMoveMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, { - 'leading': true, - 'trailing': false - }); - } - - /** - * Finds nearest feature in mapData to event latlng - * - * @method _nearestFeature - * @param latLng {Leaflet latLng} - * @return nearestPoint {Leaflet latLng} - */ - _nearestFeature(latLng) { - const self = this; - let nearest; - - if (latLng.lng < -180 || latLng.lng > 180) { - return; - } - - _.reduce(this._geojsonFeatureCollection.features, function (distance, feature) { - const featureLatLng = self._getLatLng(feature); - const dist = latLng.distanceTo(featureLatLng); - - if (dist < distance) { - nearest = feature; - return dist; - } - - return distance; - }, Infinity); - - return nearest; - } - - /** - * display tooltip if feature is close enough to event latlng - * - * @method _tooltipProximity - * @param latlng {Leaflet latLng Object} - * @param feature {geoJson Object} - * @return {Boolean} - */ - _tooltipProximity(latlng, feature) { - if (!feature) return; - - let showTip = false; - const featureLatLng = this._getLatLng(feature); - - // zoomScale takes map zoom and returns proximity value for tooltip display - // domain (input values) is map zoom (min 1 and max 18) - // range (output values) is distance in meters - // used to compare proximity of event latlng to feature latlng - const zoomScale = d3.scale.linear() - .domain([1, 4, 7, 10, 13, 16, 18]) - .range([1000000, 300000, 100000, 15000, 2000, 150, 50]); - - const proximity = zoomScale(this._zoom); - const distance = latlng.distanceTo(featureLatLng); - - // maxLngDif is max difference in longitudes - // to prevent feature tooltip from appearing 360° - // away from event latlng - const maxLngDif = 40; - const lngDif = Math.abs(latlng.lng - featureLatLng.lng); - - if (distance < proximity && lngDif < maxLngDif) { - showTip = true; - } - - d3.scale.pow().exponent(0.2) - .domain([1, 18]) - .range([1500000, 50]); - return showTip; - } - -} - - - -/** - * returns normalized data for heat map intensity - * - * @method dataToHeatArray - * @param featureCollection {Array} - * @return {Array} - */ -function dataToHeatArray(featureCollection) { - const max = _.get(featureCollection, 'properties.max'); - - return featureCollection.features.map((feature) => { - const lat = feature.geometry.coordinates[1]; - const lng = feature.geometry.coordinates[0]; - // show bucket value normalized to max value - const heatIntensity = feature.properties.value / max; - - return [lat, lng, heatIntensity]; - }); -} - diff --git a/src/ui/public/vis_maps/markers/scaled_circles.js b/src/ui/public/vis_maps/markers/scaled_circles.js deleted file mode 100644 index 577977769b7c10..00000000000000 --- a/src/ui/public/vis_maps/markers/scaled_circles.js +++ /dev/null @@ -1,231 +0,0 @@ -import L from 'leaflet'; -import _ from 'lodash'; -import d3 from 'd3'; -import $ from 'jquery'; -import { EventEmitter } from 'events'; - -export class ScaledCirclesMarkers extends EventEmitter { - - constructor(featureCollection, options, targetZoom, kibanaMap) { - super(); - this._geohashGeoJson = featureCollection; - this._zoom = targetZoom; - - this._valueFormatter = options.valueFormatter; - this._tooltipFormatter = options.tooltipFormatter; - this._map = options.map; - - this._legendColors = null; - this._legendQuantizer = null; - - this._popups = []; - this._leafletLayer = L.geoJson(null, { - pointToLayer: this.getMarkerFunction(), - style: this.getStyleFunction(), - onEachFeature: (feature, layer) => { - this._bindPopup(feature, layer); - }, - filter: (feature) => { - const bucketRectBounds = _.get(feature, 'properties.rectangle'); - return kibanaMap.isInside(bucketRectBounds); - } - }); - this._leafletLayer.addData(this._geohashGeoJson); - } - - getLeafletLayer() { - return this._leafletLayer; - } - - - getStyleFunction() { - const min = _.get(this._geohashGeoJson, 'properties.min', 0); - const max = _.get(this._geohashGeoJson, 'properties.max', 1); - - const quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain(); - this._legendColors = makeCircleMarkerLegendColors(min, max); - this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors); - - return makeStyleFunction(min, max, this._legendColors, quantizeDomain); - } - - - movePointer() { - } - - getLabel() { - if (this._popups.length) { - return this._popups[0].feature.properties.aggConfigResult.aggConfig.makeLabel(); - } - return ''; - } - - - appendLegendContents(jqueryDiv) { - - if (!this._legendColors || !this._legendQuantizer) { - return; - } - - const titleText = this.getLabel(); - const $title = $('
').addClass('tilemap-legend-title').text(titleText); - jqueryDiv.append($title); - - this._legendColors.forEach((color) => { - const labelText = this._legendQuantizer - .invertExtent(color) - .map(this._valueFormatter) - .join(' – '); - - const label = $('
'); - const icon = $('').css({ - background: color, - 'border-color': makeColorDarker(color) - }); - - const text = $('').text(labelText); - label.append(icon); - label.append(text); - - jqueryDiv.append(label); - }); - - } - - - /** - * Binds popup and events to each feature on map - * - * @method bindPopup - * @param feature {Object} - * @param layer {Object} - * return {undefined} - */ - _bindPopup(feature, layer) { - const popup = layer.on({ - mouseover: (e) => { - const layer = e.target; - // bring layer to front if not older browser - if (!L.Browser.ie && !L.Browser.opera) { - layer.bringToFront(); - } - this._showTooltip(feature); - }, - mouseout: () => { - this.emit('hideTooltip'); - } - }); - - this._popups.push(popup); - } - - /** - * Checks if event latlng is within bounds of mapData - * features and shows tooltip for that feature - * - * @method _showTooltip - * @param feature {LeafletFeature} - * @param latLng? {Leaflet latLng} - * @return undefined - */ - _showTooltip(feature, latLng) { - - const lat = _.get(feature, 'geometry.coordinates.1'); - const lng = _.get(feature, 'geometry.coordinates.0'); - latLng = latLng || L.latLng(lat, lng); - - const content = this._tooltipFormatter(feature); - if (!content) { - return; - } - - this.emit('showTooltip', { - content: content, - position: latLng - }); - } - - getMarkerFunction() { - const scaleFactor = 0.6; - return (feature, latlng) => { - const value = feature.properties.value; - const scaledRadius = this._radiusScale(value) * scaleFactor; - return L.circleMarker(latlng).setRadius(scaledRadius); - }; - } - - /** - * radiusScale returns a number for scaled circle markers - * for relative sizing of markers - * - * @method _radiusScale - * @param value {Number} - * @return {Number} - */ - _radiusScale(value) { - - //magic numbers - const precisionBiasBase = 5; - const precisionBiasNumerator = 200; - - const precision = _.max(this._geohashGeoJson.features.map((feature) => { - return String(feature.properties.geohash).length; - })); - - const pct = Math.abs(value) / Math.abs(this._geohashGeoJson.properties.max); - const zoomRadius = 0.5 * Math.pow(2, this._zoom); - const precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision); - - // square root value percentage - return Math.pow(pct, 0.5) * zoomRadius * precisionScale; - } - - getBounds() { - return this._leafletLayer.getBounds(); - } - -} - - -/** - * d3 quantize scale returns a hex color, used for marker fill color - * - * @method quantizeLegendColors - * return {undefined} - */ -function makeCircleMarkerLegendColors(min, max) { - const reds1 = ['#ff6128']; - const reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c']; - const reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; - const bottomCutoff = 2; - const middleCutoff = 24; - let legendColors; - if (max - min <= bottomCutoff) { - legendColors = reds1; - } else if (max - min <= middleCutoff) { - legendColors = reds3; - } else { - legendColors = reds5; - } - return legendColors; -} - -function makeColorDarker(color) { - const amount = 1.3;//magic number, carry over from earlier - return d3.hcl(color).darker(amount).toString(); -} - -function makeStyleFunction(min, max, legendColors, quantizeDomain) { - const legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(legendColors); - return (feature) => { - const value = _.get(feature, 'properties.value'); - const color = legendQuantizer(value); - return { - fillColor: color, - color: makeColorDarker(color), - weight: 1.5, - opacity: 1, - fillOpacity: 0.75 - }; - }; -} diff --git a/src/ui/public/vis_maps/markers/shaded_circles.js b/src/ui/public/vis_maps/markers/shaded_circles.js deleted file mode 100644 index a28d86d7578d4b..00000000000000 --- a/src/ui/public/vis_maps/markers/shaded_circles.js +++ /dev/null @@ -1,46 +0,0 @@ -import L from 'leaflet'; -import _ from 'lodash'; -import { ScaledCirclesMarkers } from './scaled_circles'; - -export class ShadedCirclesMarkers extends ScaledCirclesMarkers { - getMarkerFunction() { - // multiplier to reduce size of all circles - const scaleFactor = 0.8; - return (feature, latlng) => { - const radius = this._geohashMinDistance(feature) * scaleFactor; - return L.circle(latlng, radius); - }; - } - - - /** - * _geohashMinDistance returns a min distance in meters for sizing - * circle markers to fit within geohash grid rectangle - * - * @method _geohashMinDistance - * @param feature {Object} - * @return {Number} - */ - _geohashMinDistance(feature) { - const centerPoint = _.get(feature, 'properties.center'); - const geohashRect = _.get(feature, 'properties.rectangle'); - - // centerPoint is an array of [lat, lng] - // geohashRect is the 4 corners of the geoHash rectangle - // an array that starts at the southwest corner and proceeds - // clockwise, each value being an array of [lat, lng] - - // center lat and southeast lng - const east = L.latLng([centerPoint[0], geohashRect[2][1]]); - // southwest lat and center lng - const north = L.latLng([geohashRect[3][0], centerPoint[1]]); - - // get latLng of geohash center point - const center = L.latLng([centerPoint[0], centerPoint[1]]); - - // get smallest radius at center of geohash grid rectangle - const eastRadius = Math.floor(center.distanceTo(east)); - const northRadius = Math.floor(center.distanceTo(north)); - return _.min([eastRadius, northRadius]); - } -} diff --git a/src/ui/public/vis_maps/styles/_tilemap.less b/src/ui/public/vis_maps/styles/_tilemap.less deleted file mode 100644 index ba6e21d6185290..00000000000000 --- a/src/ui/public/vis_maps/styles/_tilemap.less +++ /dev/null @@ -1,185 +0,0 @@ -@import (reference) "~ui/styles/variables"; - -/* _tilemap */ - -.tilemap { - margin-bottom: 6px; - border: 1px solid; - border-color: @tilemap-border; - position: relative; -} - -/* leaflet Dom Util div for map label */ - -.tilemap-legend { - padding: 5px 7px 5px 7px; - margin: 0; - font: 11px/13px Arial, Helvetica, sans-serif; - background: @tilemap-legend-base-bg; - background: @tilemap-legend-bg; - box-shadow: 0 0 12px rgba(0,0,0,0.3); - border-radius: 5px; - text-align: left; - line-height: 15px; - color: @tilemap-color; -} - -.tilemap-legend-title { - font-weight: bold; -} - -.tilemap-legend i { - width: 10px; - height: 10px; - float: left; - margin: 2px 4px 0 0; - opacity: 1; - border-radius: 50%; - border-width: 1px; - border-style: solid; - border-color: @tilemap-legend-i-border; - background: @tilemap-legend-i-bg; -} - -/* top left needs some more styles */ -.leaflet-top.leaflet-left .tilemap-legend-wrapper { - position: absolute; - left: 50px; - white-space: nowrap; -} - -.leaflet-top.leaflet-left .tilemap-legend-wrapper span { - padding-right: 20px; -} - -/* leaflet Dom Util div for map legend */ - -.tilemap-info { - padding: 3px 7px 3px 7px; - margin: 0; - font-size: 12px; - background: @tilemap-info-base-bg; - background: fade(@tilemap-info-bg, 92%); - box-shadow: 0 0 12px rgba(0,0,0,0.3); - border-radius: 5px; -} - -.tilemap-info h2 { - font-size: 12px !important; - margin: 0 !important; - padding: 0; - color: @tilemap-info-header-color; -} - -.leaflet-control-fit { - text-align: center; - background: @tilemap-leaflet-control-bg; - width: 26px; - height: 26px; - outline: 1px @tilemap-leaflet-control-outline; -} - -/* over-rides leaflet popup styles to look like kibana tooltip */ - -.leaflet-container { - background: @tilemap-leaflet-container-bg !important; - outline: 0 !important; -} - -.leaflet-popup-content-wrapper { - margin: 0; - padding: 0; -} - -.leaflet-popup { - margin-bottom: 16px !important; - pointer-events: none; -} - -.leaflet-popup-content-wrapper { - background: @tooltip-bg !important; - color: @tooltip-color !important; - border-radius: 4px !important; - padding: 0 !important; -} - -.leaflet-popup-content { - margin: 0 !important; - line-height: 24px !important; - font-size: @font-size-base; - font-weight: normal; - word-wrap: break-word; - overflow: hidden; - pointer-events: none; - - > :last-child { - margin-bottom: @tooltip-space; - } - - > * { - margin: @tooltip-space @tooltip-space 0; - } - - table { - td,th { - padding: @tooltip-space-tight; - - &.row-bucket { - word-break: break-all; - } - } - } -} - -.leaflet-popup-tip-container, .leaflet-popup-close-button { - display: none !important; -} - -.leaflet-control-layers-expanded { - padding: 0; - margin: 0; - font: 12px/13px Arial, Helvetica, sans-serif; - line-height: 14px !important; -} - -.leaflet-control-layers-expanded label { - font-weight: normal !important; - margin: 0 !important; - padding: 0 !important; -} - -.leaflet-draw-tooltip { - display: none; -} - -.leaflet-control-attribution { - background-color: @tilemap-leaflet-footer-bg !important; - color: @tilemap-leaflet-footer-color !important; - - p { - display: inline; - } -} - -.leaflet-left { - .leaflet-control { - a, - a:hover { - color: @tilemap-leaflet-control-color; - } - } - - .leaflet-draw-actions a { - background-color: @tilemap-leaflet-control-draw-action-bg; - } -} - -/* filter to desaturate mapquest tiles */ - -img.leaflet-tile { - filter: @tilemap-filter; -} - -img.leaflet-tile.filters-off { - filter: none; -} diff --git a/src/ui/public/vislib_vis_type/__tests__/_build_chart_data.js b/src/ui/public/vislib_vis_type/__tests__/_build_chart_data.js deleted file mode 100644 index 4eb5413fa5fa14..00000000000000 --- a/src/ui/public/vislib_vis_type/__tests__/_build_chart_data.js +++ /dev/null @@ -1,133 +0,0 @@ -import _ from 'lodash'; -import ngMock from 'ng_mock'; -import expect from 'expect.js'; -import sinon from 'sinon'; -import { AggResponseTabifyTableProvider } from 'ui/agg_response/tabify/_table'; -import { AggResponseIndexProvider } from 'ui/agg_response/index'; -import { VislibVisTypeBuildChartDataProvider } from 'ui/vislib_vis_type/build_chart_data'; - -describe('renderbot#buildChartData', function () { - let buildChartData; - let aggResponse; - let Table; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Table = Private(AggResponseTabifyTableProvider); - aggResponse = Private(AggResponseIndexProvider); - buildChartData = Private(VislibVisTypeBuildChartDataProvider); - })); - - describe('for hierarchical vis', function () { - it('defers to hierarchical aggResponse converter', function () { - const football = {}; - const renderbot = { - vis: { - isHierarchical: _.constant(true) - } - }; - - const stub = sinon.stub(aggResponse, 'hierarchical').returns(football); - expect(buildChartData.call(renderbot, football)).to.be(football); - expect(stub).to.have.property('callCount', 1); - expect(stub.firstCall.args[0]).to.be(renderbot.vis); - expect(stub.firstCall.args[1]).to.be(football); - }); - }); - - describe('for point plot', function () { - it('calls tabify to simplify the data into a table', function () { - const renderbot = { - vis: { - isHierarchical: _.constant(false) - } - }; - const football = { tables: [], hits: { total: 1 } }; - - const stub = sinon.stub(aggResponse, 'tabify').returns(football); - expect(buildChartData.call(renderbot, football)).to.eql({ rows: [], hits: 1 }); - expect(stub).to.have.property('callCount', 1); - expect(stub.firstCall.args[0]).to.be(renderbot.vis); - expect(stub.firstCall.args[1]).to.be(football); - }); - - it('returns a single chart if the tabify response contains only a single table', function () { - const chart = { hits: 1, rows: [], columns: [] }; - const renderbot = { - vis: { - isHierarchical: _.constant(false), - type: { - responseConverter: _.constant(chart) - } - } - }; - const esResp = { hits: { total: 1 } }; - const tabbed = { tables: [ new Table() ] }; - - sinon.stub(aggResponse, 'tabify').returns(tabbed); - expect(buildChartData.call(renderbot, esResp)).to.eql(chart); - }); - - it('converts table groups into rows/columns wrappers for charts', function () { - const converter = sinon.stub().returns('chart'); - const esResp = { hits: { total: 1 } }; - const tables = [new Table(), new Table(), new Table(), new Table()]; - - const renderbot = { - vis: { - isHierarchical: _.constant(false), - type: { - responseConverter: converter - } - } - }; - - sinon.stub(aggResponse, 'tabify').returns({ - tables: [ - { - aggConfig: { params: { row: true } }, - tables: [ - { - aggConfig: { params: { row: false } }, - tables: [ tables[0] ] - }, - { - aggConfig: { params: { row: false } }, - tables: [ tables[1] ] - } - ] - }, - { - aggConfig: { params: { row: true } }, - tables: [ - { - aggConfig: { params: { row: false } }, - tables: [ tables[2] ] - }, - { - aggConfig: { params: { row: false } }, - tables: [ tables[3] ] - } - ] - } - ] - }); - - const chartData = buildChartData.call(renderbot, esResp); - - // verify tables were converted - expect(converter).to.have.property('callCount', 4); - expect(converter.args[0][1]).to.be(tables[0]); - expect(converter.args[1][1]).to.be(tables[1]); - expect(converter.args[2][1]).to.be(tables[2]); - expect(converter.args[3][1]).to.be(tables[3]); - - expect(chartData).to.have.property('rows'); - expect(chartData.rows).to.have.length(2); - chartData.rows.forEach(function (row) { - expect(row).to.have.property('columns'); - expect(row.columns).to.eql([ 'chart', 'chart' ]); - }); - }); - }); -}); diff --git a/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js b/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js deleted file mode 100644 index ca5b76ffab56c1..00000000000000 --- a/src/ui/public/vislib_vis_type/__tests__/_vislib_renderbot.js +++ /dev/null @@ -1,173 +0,0 @@ -import _ from 'lodash'; -import $ from 'jquery'; -import ngMock from 'ng_mock'; -import expect from 'expect.js'; -import sinon from 'sinon'; -import VislibProvider from 'ui/vislib'; -import { VislibVisProvider } from 'ui/vislib/vis'; -import { VisRenderbotProvider } from 'ui/vis/renderbot'; -import VislibVisTypeVislibRenderbotProvider from 'ui/vislib_vis_type/vislib_renderbot'; -import 'ui/persisted_state'; -import noDigestPromises from 'test_utils/no_digest_promises'; - -describe('renderbot', function exportWrapper() { - let vislib; - let Vis; - let Renderbot; - let VislibRenderbot; - let persistedState; - const mockVisType = { - name: 'test' - }; - - function init() { - ngMock.module('kibana'); - - ngMock.inject(function ($injector, Private) { - vislib = Private(VislibProvider); - Vis = Private(VislibVisProvider); - Renderbot = Private(VisRenderbotProvider); - VislibRenderbot = Private(VislibVisTypeVislibRenderbotProvider); - persistedState = new ($injector.get('PersistedState'))(); - }); - } - - beforeEach(init); - - describe('creation', function () { - const vis = { type: mockVisType }; - const $el = 'element'; - let createVisStub; - let renderbot; - - beforeEach(function () { - createVisStub = sinon.stub(VislibRenderbot.prototype, '_createVis', _.noop); - renderbot = new VislibRenderbot(vis, $el, persistedState); - }); - - it('should be a Renderbot', function () { - expect(renderbot).to.be.a(Renderbot); - }); - - it('should create a new Vis object', function () { - expect(createVisStub.callCount).to.be(1); - }); - }); - - describe('_createVis', function () { - const vis = { - type: mockVisType, - listeners: { - 'test': _.noop, - 'test2': _.noop, - 'test3': _.noop - } - }; - const $el = $('
testing
'); - let listenerSpy; - let renderbot; - - beforeEach(function () { - sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _.constant({})); - listenerSpy = sinon.spy(vislib.Vis.prototype, 'on'); - renderbot = new VislibRenderbot(vis, $el, persistedState); - }); - - it('should attach listeners and set vislibVis', function () { - expect(listenerSpy.callCount).to.be(3); - expect(listenerSpy.calledWith('test', _.noop)).to.be(true); - expect(renderbot.vislibVis).to.be.a(Vis); - }); - }); - - describe('param update', function () { - const $el = $('
testing
'); - const params = { el: $el[0], one: 'fish', two: 'fish' }; - const vis = { - type: _.defaults({ - params: { - defaults: params - } - }, mockVisType) - }; - let createVisSpy; - let renderbot; - - beforeEach(function () { - createVisSpy = sinon.spy(VislibRenderbot.prototype, '_createVis'); - renderbot = new VislibRenderbot(vis, $el, persistedState); - }); - - it('should create a new Vis object when params change', function () { - // called on init - expect(createVisSpy.callCount).to.be(1); - renderbot.updateParams(_.clone(params)); - // not called again, same params - expect(createVisSpy.callCount).to.be(1); - renderbot.vis.params = { one: 'fishy', two: 'fishy' }; - renderbot.updateParams(); - // called again, new params - expect(createVisSpy.callCount).to.be(2); - renderbot.updateParams(); - // same params again, no new call - expect(createVisSpy.callCount).to.be(2); - }); - }); - - describe('render', function () { - noDigestPromises.activateForSuite(); - - const vis = { type: mockVisType, isHierarchical: _.constant(false) }; - const $el = $('
testing
'); - - beforeEach(function () { - sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _.constant({})); - }); - - it('should use #buildChartData', function () { - const renderbot = new VislibRenderbot(vis, $el, persistedState); - - const football = {}; - const buildStub = sinon.stub(renderbot, 'buildChartData', _.constant(football)); - const renderStub = sinon.stub(renderbot.vislibVis, 'render'); - - return renderbot.render('flat data', persistedState).then(() => { - expect(renderStub.callCount).to.be(1); - expect(buildStub.callCount).to.be(1); - expect(renderStub.firstCall.args[0]).to.be(football); - }); - }); - }); - - describe('destroy', function () { - const vis = { - type: mockVisType, - listeners: { - 'test': _.noop, - 'test2': _.noop, - 'test3': _.noop - } - }; - const $el = $('
testing
'); - let listenerSpy; - let renderbot; - - beforeEach(function () { - sinon.stub(VislibRenderbot.prototype, '_getVislibParams', _.constant({})); - listenerSpy = sinon.spy(vislib.Vis.prototype, 'off'); - renderbot = new VislibRenderbot(vis, $el, persistedState); - }); - - it('should detatch listeners', function () { - renderbot.destroy(); - expect(listenerSpy.callCount).to.be(3); - expect(listenerSpy.calledWith('test', _.noop)).to.be(true); - }); - - it('should destroy the vis', function () { - const spy = sinon.spy(renderbot.vislibVis, 'destroy'); - renderbot.destroy(); - expect(spy.callCount).to.be(1); - }); - }); -}); diff --git a/src/ui/public/vislib_vis_type/__tests__/index.js b/src/ui/public/vislib_vis_type/__tests__/index.js deleted file mode 100644 index 6934881ba83559..00000000000000 --- a/src/ui/public/vislib_vis_type/__tests__/index.js +++ /dev/null @@ -1,4 +0,0 @@ -import './_vislib_renderbot'; -import './_build_chart_data'; -describe('Vis Type', function () { -}); diff --git a/src/ui/public/vislib_vis_type/build_chart_data.js b/src/ui/public/vislib_vis_type/build_chart_data.js deleted file mode 100644 index 8ef78984cd3dde..00000000000000 --- a/src/ui/public/vislib_vis_type/build_chart_data.js +++ /dev/null @@ -1,69 +0,0 @@ -import { AggResponseIndexProvider } from 'ui/agg_response/index'; -import { AggResponseTabifyTableProvider } from 'ui/agg_response/tabify/_table'; - -export function VislibVisTypeBuildChartDataProvider(Private) { - const aggResponse = Private(AggResponseIndexProvider); - const Table = Private(AggResponseTabifyTableProvider); - - return function (esResponse) { - const vis = this.vis; - - if (vis.isHierarchical()) { - // the hierarchical converter is very self-contained (woot!) - return aggResponse.hierarchical(vis, esResponse); - } - - const tableGroup = aggResponse.tabify(vis, esResponse, { - canSplit: true, - asAggConfigResults: true - }); - - let converted = convertTableGroup(vis, tableGroup); - if (!converted) { - // mimic a row of tables that doesn't have any tables - // https://github.com/elastic/kibana/blob/7bfb68cd24ed42b1b257682f93c50cd8d73e2520/src/kibana/components/vislib/components/zero_injection/inject_zeros.js#L32 - converted = { rows: [] }; - } - - converted.hits = esResponse.hits.total; - - return converted; - }; - - function convertTableGroup(vis, tableGroup) { - const tables = tableGroup.tables; - const firstChild = tables[0]; - if (firstChild instanceof Table) { - - const chart = convertTable(vis, firstChild); - // if chart is within a split, assign group title to its label - if (tableGroup.$parent) { - chart.label = tableGroup.title; - } - return chart; - } - - if (!tables.length) return; - const out = {}; - let outList; - - tables.forEach(function (table) { - if (!outList) { - const aggConfig = table.aggConfig; - const direction = aggConfig.params.row ? 'rows' : 'columns'; - outList = out[direction] = []; - } - - let output; - if (output = convertTableGroup(vis, table)) { - outList.push(output); - } - }); - - return out; - } - - function convertTable(vis, table) { - return vis.type.responseConverter(vis, table); - } -} diff --git a/src/ui/public/vislib_vis_type/vislib_renderbot.js b/src/ui/public/vislib_vis_type/vislib_renderbot.js deleted file mode 100644 index 58a970e9d803bd..00000000000000 --- a/src/ui/public/vislib_vis_type/vislib_renderbot.js +++ /dev/null @@ -1,79 +0,0 @@ -import _ from 'lodash'; -import VislibProvider from 'ui/vislib'; -import { VisRenderbotProvider } from 'ui/vis/renderbot'; -import { VislibVisTypeBuildChartDataProvider } from 'ui/vislib_vis_type/build_chart_data'; - -// eslint-disable-next-line kibana-custom/no-default-export -export default function VislibRenderbotFactory(Private, $injector) { - const AngularPromise = $injector.get('Promise'); - const vislib = Private(VislibProvider); - const Renderbot = Private(VisRenderbotProvider); - const buildChartData = Private(VislibVisTypeBuildChartDataProvider); - - _.class(VislibRenderbot).inherits(Renderbot); - function VislibRenderbot(vis, $el, uiState) { - VislibRenderbot.Super.call(this, vis, $el, uiState); - this.refreshLegend = 0; - this._createVis(); - } - - VislibRenderbot.prototype._createVis = function () { - if (this.vislibVis) this.destroy(); - - this.vislibParams = this._getVislibParams(); - this.vislibVis = new vislib.Vis(this.$el[0], this.vislibParams); - - _.each(this.vis.listeners, (listener, event) => { - this.vislibVis.on(event, listener); - }); - - if (this.chartData) { - this.vislibVis.render(this.chartData, this.uiState); - this.refreshLegend++; - } - }; - - VislibRenderbot.prototype._getVislibParams = function () { - const self = this; - - return _.assign( - {}, - self.vis.type.params.defaults, - { type: self.vis.type.name }, - self.vis.params - ); - }; - - VislibRenderbot.prototype.buildChartData = buildChartData; - VislibRenderbot.prototype.render = function (esResponse) { - this.chartData = this.buildChartData(esResponse); - return AngularPromise.delay(1).then(() => { - this.vislibVis.render(this.chartData, this.uiState); - this.refreshLegend++; - }); - }; - - VislibRenderbot.prototype.destroy = function () { - const self = this; - - const vislibVis = self.vislibVis; - - _.forOwn(self.vis.listeners, function (listener, event) { - vislibVis.off(event, listener); - }); - - vislibVis.destroy(); - }; - - VislibRenderbot.prototype.updateParams = function () { - const self = this; - - // get full vislib params object - const newParams = self._getVislibParams(); - - // if there's been a change, replace the vis - if (!_.isEqual(newParams, self.vislibParams)) self._createVis(); - }; - - return VislibRenderbot; -} diff --git a/src/ui/public/vislib_vis_type/vislib_vis_type.js b/src/ui/public/vislib_vis_type/vislib_vis_type.js deleted file mode 100644 index 81813b0285cb4a..00000000000000 --- a/src/ui/public/vislib_vis_type/vislib_vis_type.js +++ /dev/null @@ -1,85 +0,0 @@ -import _ from 'lodash'; -import 'ui/vislib'; -import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; -import 'plugins/kbn_vislib_vis_types/controls/point_series_options'; -import 'plugins/kbn_vislib_vis_types/controls/line_interpolation_option'; -import 'plugins/kbn_vislib_vis_types/controls/heatmap_options'; -import 'plugins/kbn_vislib_vis_types/controls/point_series'; -import 'plugins/kbn_vislib_vis_types/controls/gauge_options'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { AggResponsePointSeriesProvider } from 'ui/agg_response/point_series/point_series'; -import VislibVisTypeVislibRenderbotProvider from 'ui/vislib_vis_type/vislib_renderbot'; - -export function VislibVisTypeVislibVisTypeProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const pointSeries = Private(AggResponsePointSeriesProvider); - const VislibRenderbot = Private(VislibVisTypeVislibRenderbotProvider); - - // converts old config format (pre 5.2) to the new one - const updateParams = function (params) { - - const updateIfSet = (from, to, prop, func) => { - if (from[prop]) { - to[prop] = func ? func(from[prop]) : from[prop]; - delete from[prop]; - } - }; - - if (params.gauge) { - updateIfSet(params, params.gauge.style, 'fontSize'); - } - - if (params.seriesParams) { - updateIfSet(params, params.seriesParams[0], 'drawLinesBetweenPoints'); - updateIfSet(params, params.seriesParams[0], 'showCircles'); - updateIfSet(params, params.seriesParams[0], 'radiusRatio'); - updateIfSet(params, params.seriesParams[0], 'interpolate'); - updateIfSet(params, params.seriesParams[0], 'type'); - - if (params.mode) { - const stacked = ['stacked', 'percentage', 'wiggle', 'silhouette'].includes(params.mode); - params.seriesParams[0].mode = stacked ? 'stacked' : 'normal'; - const axisMode = ['stacked', 'overlap'].includes(params.mode) ? 'normal' : params.mode; - params.valueAxes[0].scale.mode = axisMode; - delete params.mode; - } - - if (params.smoothLines) { - params.seriesParams[0].interpolate = 'cardinal'; - delete params.smoothLines; - } - } - - if (params.valueAxes) { - updateIfSet(params, params.valueAxes[0].scale, 'setYExtents'); - updateIfSet(params, params.valueAxes[0].scale, 'defaultYExtents'); - - if (params.scale) { - params.valueAxes[0].scale.type = params.scale; - delete params.scale; - } - } - - if (params.categoryAxes && params.categoryAxes.length) { - updateIfSet(params, params.categoryAxes[0], 'expandLastBucket'); - } - }; - - _.class(VislibVisType).inherits(VisType); - function VislibVisType(opts = {}) { - VislibVisType.Super.call(this, opts); - - if (this.responseConverter == null) { - this.responseConverter = pointSeries; - } - - this.listeners = opts.listeners || {}; - } - - VislibVisType.prototype.createRenderbot = function (vis, $el, uiState) { - if (vis.type.name !== 'pie') updateParams(vis.params); - return new VislibRenderbot(vis, $el, uiState); - }; - - return VislibVisType; -} From 814eccc36b69c050302c62690470e7d4422692f6 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:20:56 +0200 Subject: [PATCH 07/40] updating kibana to work with new visualize --- src/core_plugins/kibana/public/dashboard/dashboard.html | 2 ++ src/core_plugins/kibana/public/dashboard/dashboard.js | 1 + src/core_plugins/kibana/public/dashboard/grid.js | 2 ++ .../kibana/public/dashboard/panel/panel.html | 6 ++---- src/core_plugins/kibana/public/dashboard/panel/panel.js | 3 ++- .../kibana/public/discover/controllers/discover.js | 6 +++++- src/core_plugins/kibana/public/discover/index.html | 9 +++++---- src/core_plugins/spy_modes/public/table_spy_mode.js | 6 +++--- src/ui/public/agg_types/buckets/terms.js | 2 +- .../agg_types/metrics/lib/parent_pipeline_agg_helper.js | 2 +- .../agg_types/metrics/lib/sibling_pipeline_agg_helper.js | 2 +- src/ui/public/timefilter/timefilter.js | 4 ++++ src/ui/public/vislib/lib/handler.js | 2 +- src/ui/public/vislib/lib/types/point_series.js | 2 +- 14 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/core_plugins/kibana/public/dashboard/dashboard.html b/src/core_plugins/kibana/public/dashboard/dashboard.html index e79c141ec85d4f..4ff39e75b79284 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard.html +++ b/src/core_plugins/kibana/public/dashboard/dashboard.html @@ -117,6 +117,7 @@

get-vis-click-handler="getFilterBarClickHandler" get-vis-brush-handler="getBrushEvent" save-state="saveState" + app-state="appState" toggle-expand="toggleExpandPanel" create-child-ui-state="createChildUiState" toggle-expand="toggleExpandPanel" @@ -133,6 +134,7 @@

get-vis-click-handler="getFilterBarClickHandler" get-vis-brush-handler="getBrushEvent" save-state="saveState" + app-state="appState" register-panel-index-pattern="registerPanelIndexPattern" create-child-ui-state="createChildUiState" toggle-expand="toggleExpandPanel(expandedPanel.panelIndex)" diff --git a/src/core_plugins/kibana/public/dashboard/dashboard.js b/src/core_plugins/kibana/public/dashboard/dashboard.js index f76af430dbf429..0ac9fca9058864 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard.js @@ -147,6 +147,7 @@ app.directive('dashboardApp', function ($injector) { $scope.timefilter = timefilter; $scope.expandedPanel = null; $scope.dashboardViewMode = dashboardState.getViewMode(); + $scope.appState = dashboardState.getAppState(); $scope.landingPageUrl = () => `#${DashboardConstants.LANDING_PAGE_PATH}`; $scope.getBrushEvent = () => brushEvent(dashboardState.getAppState()); diff --git a/src/core_plugins/kibana/public/dashboard/grid.js b/src/core_plugins/kibana/public/dashboard/grid.js index f03339519fbc02..cb2b47a2d058cf 100644 --- a/src/core_plugins/kibana/public/dashboard/grid.js +++ b/src/core_plugins/kibana/public/dashboard/grid.js @@ -53,6 +53,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { * @type {function} */ saveState: '=', + appState: '=', /** * Expand or collapse a panel, so it either takes up the whole screen or goes back to its * natural size. @@ -219,6 +220,7 @@ app.directive('dashboardGrid', function ($compile, Notifier) { get-vis-click-handler="getVisClickHandler" get-vis-brush-handler="getVisBrushHandler" save-state="saveState" + app-state="appState" register-panel-index-pattern="registerPanelIndexPattern" toggle-expand="toggleExpand(${panel.panelIndex})" create-child-ui-state="createChildUiState"> diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel.html b/src/core_plugins/kibana/public/dashboard/panel/panel.html index 8727a99b470743..6db60243465269 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel.html +++ b/src/core_plugins/kibana/public/dashboard/panel/panel.html @@ -79,14 +79,12 @@ diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel.js b/src/core_plugins/kibana/public/dashboard/panel/panel.js index bbc2ace9b29db1..e11a67f280e5aa 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel.js +++ b/src/core_plugins/kibana/public/dashboard/panel/panel.js @@ -85,7 +85,8 @@ uiModules * Call when changes should be propagated to the url and thus saved in state. * @type {function} */ - saveState: '=' + saveState: '=', + appState: '=', }, link: function ($scope, element) { if (!$scope.panel.id || !$scope.panel.type) return; diff --git a/src/core_plugins/kibana/public/discover/controllers/discover.js b/src/core_plugins/kibana/public/discover/controllers/discover.js index 9a710e48d9d451..4f6f2f1fed57f4 100644 --- a/src/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/core_plugins/kibana/public/discover/controllers/discover.js @@ -16,6 +16,7 @@ import 'ui/state_management/app_state'; import 'ui/timefilter'; import 'ui/share'; import { VisProvider } from 'ui/vis'; +import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic'; import { DocTitleProvider } from 'ui/doc_title'; import { UtilsBrushEventProvider } from 'ui/utils/brush_event'; import PluginsKibanaDiscoverHitSortFnProvider from 'plugins/kibana/discover/_hit_sort_fn'; @@ -98,7 +99,7 @@ function discoverController($scope, config, courier, $route, $window, Notifier, const HitSortFn = Private(PluginsKibanaDiscoverHitSortFnProvider); const queryFilter = Private(FilterBarQueryFilterProvider); const filterManager = Private(FilterManagerProvider); - + const responseHandler = Private(BasicResponseHandlerProvider).handler; const notify = new Notifier({ location: 'Discover' }); @@ -487,6 +488,9 @@ function discoverController($scope, config, courier, $route, $window, Notifier, segmented.on('mergedSegment', function (merged) { $scope.mergedEsResp = merged; + responseHandler($scope.vis, merged).then(resp => { + $scope.visData = resp; + }); $scope.hits = merged.hits.total; const indexPattern = $scope.searchSource.get('index'); diff --git a/src/core_plugins/kibana/public/discover/index.html b/src/core_plugins/kibana/public/discover/index.html index 5ea49c5bd05ec8..618045cfd5dee0 100644 --- a/src/core_plugins/kibana/public/discover/index.html +++ b/src/core_plugins/kibana/public/discover/index.html @@ -130,13 +130,14 @@

Searching

- - + vis-data="visData" + style="height: 200px" + > +
diff --git a/src/core_plugins/spy_modes/public/table_spy_mode.js b/src/core_plugins/spy_modes/public/table_spy_mode.js index 8232c73a3741c5..3251e3a1138961 100644 --- a/src/core_plugins/spy_modes/public/table_spy_mode.js +++ b/src/core_plugins/spy_modes/public/table_spy_mode.js @@ -15,16 +15,16 @@ function VisSpyTableProvider(Notifier, $filter, $rootScope, config, Private) { link: function tableLinkFn($scope) { $rootScope.$watchMulti.call($scope, [ 'vis', - 'esResp' + 'visData' ], function () { - if (!$scope.vis || !$scope.esResp) { + if (!$scope.vis || !$scope.visData) { $scope.table = null; } else { if (!$scope.spy.params.spyPerPage) { $scope.spy.params.spyPerPage = PER_PAGE_DEFAULT; } - $scope.table = tabifyAggResponse($scope.vis, $scope.esResp, { + $scope.table = tabifyAggResponse($scope.vis, $scope.searchSource.rawResponse, { canSplit: false, asAggConfigResults: true, partialRows: true diff --git a/src/ui/public/agg_types/buckets/terms.js b/src/ui/public/agg_types/buckets/terms.js index 04a4db324e5a2d..fe997c65b29cd0 100644 --- a/src/ui/public/agg_types/buckets/terms.js +++ b/src/ui/public/agg_types/buckets/terms.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import { AggTypesBucketsBucketAggTypeProvider } from 'ui/agg_types/buckets/_bucket_agg_type'; import { VisAggConfigProvider } from 'ui/vis/agg_config'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import { AggTypesBucketsCreateFilterTermsProvider } from 'ui/agg_types/buckets/create_filter/terms'; import orderAggTemplate from 'ui/agg_types/controls/order_agg.html'; import orderAndSizeTemplate from 'ui/agg_types/controls/order_and_size.html'; diff --git a/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js b/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js index 1c063d8e1530af..80ea23f18d0689 100644 --- a/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js +++ b/src/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.js @@ -1,7 +1,7 @@ import metricAggTemplate from 'ui/agg_types/controls/sub_agg.html'; import _ from 'lodash'; import { VisAggConfigProvider } from 'ui/vis/agg_config'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import { parentPipelineAggController } from './parent_pipeline_agg_controller'; import { parentPipelineAggWritter } from './parent_pipeline_agg_writter'; diff --git a/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js index 9e79b5d105da03..e92726ab13cde8 100644 --- a/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js +++ b/src/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import { VisAggConfigProvider } from 'ui/vis/agg_config'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import { siblingPipelineAggController } from './sibling_pipeline_agg_controller'; import { siblingPipelineAggWritter } from './sibling_pipeline_agg_writter'; diff --git a/src/ui/public/timefilter/timefilter.js b/src/ui/public/timefilter/timefilter.js index 97cbc2bb0a8391..9afd1e201facc0 100644 --- a/src/ui/public/timefilter/timefilter.js +++ b/src/ui/public/timefilter/timefilter.js @@ -73,6 +73,10 @@ uiModules ], diffInterval); } + Timefilter.prototype.update = function () { + $rootScope.$apply(); + }; + Timefilter.prototype.get = function (indexPattern) { let filter; const timefield = indexPattern.timeFieldName && _.find(indexPattern.fields, { name: indexPattern.timeFieldName }); diff --git a/src/ui/public/vislib/lib/handler.js b/src/ui/public/vislib/lib/handler.js index d274357b004317..cf19d8ebfc44d2 100644 --- a/src/ui/public/vislib/lib/handler.js +++ b/src/ui/public/vislib/lib/handler.js @@ -145,7 +145,7 @@ export function VisHandlerProvider(Private) { loadedCount++; if (loadedCount === chartSelection.length) { // events from all charts are propagated to vis, we only need to fire renderComplete once they all finish - $(self.el).trigger('renderComplete'); + self.vis.emit('renderComplete'); } }); diff --git a/src/ui/public/vislib/lib/types/point_series.js b/src/ui/public/vislib/lib/types/point_series.js index ae473d194c60ab..5d474b888d91b9 100644 --- a/src/ui/public/vislib/lib/types/point_series.js +++ b/src/ui/public/vislib/lib/types/point_series.js @@ -180,7 +180,7 @@ export function VislibTypesPointSeries() { const defaults = create()(cfg, data); const seriesLimit = 25; const hasCharts = defaults.charts.length; - const tooManySeries = defaults.charts[0].series.length > seriesLimit; + const tooManySeries = defaults.charts.length && defaults.charts[0].series.length > seriesLimit; if (hasCharts && tooManySeries) { defaults.error = 'There are too many series defined.'; } From 071f0c0b84acfaac5dd7c4ecdcdf45a17796ca55 Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 09:21:24 +0200 Subject: [PATCH 08/40] migrating existing visualizations --- .../kbn_vislib_vis_types/public/area.js | 165 +- .../public/controls/heatmap_options.html | 2 +- .../controls/point_series/category_axis.html | 2 +- .../public/controls/point_series/series.html | 6 +- .../public/controls/point_series/series.js | 4 +- .../controls/point_series/value_axes.html | 6 +- .../public/controls/vislib_basic_options.html | 2 +- .../kbn_vislib_vis_types/public/gauge.js | 73 +- .../kbn_vislib_vis_types/public/goal.js | 72 +- .../kbn_vislib_vis_types/public/heatmap.js | 125 +- .../kbn_vislib_vis_types/public/histogram.js | 166 +- .../public/horizontal_bar.js | 159 +- .../public/kbn_vislib_vis_types.js | 2 - .../kbn_vislib_vis_types/public/line.js | 165 +- .../kbn_vislib_vis_types/public/metric.js | 72 +- .../kbn_vislib_vis_types/public/pie.js | 115 +- .../kbn_vislib_vis_types/public/tile_map.js | 80 - .../markdown_vis/public/markdown_vis.js | 21 +- .../public/markdown_vis_controller.js | 4 +- .../metrics/public/components/vis_editor.js | 21 +- .../metrics/public/directives/vis_editor.js | 37 - .../public/directives/visualization.js | 46 - .../metrics/public/kbn_vis_types/editor.html | 5 - .../public/kbn_vis_types/editor_controller.js | 139 +- .../metrics/public/kbn_vis_types/index.js | 65 +- .../public/kbn_vis_types/request_handler.js | 69 + .../metrics/public/kbn_vis_types/vis.html | 4 - .../public/kbn_vis_types/vis_controller.js | 49 - .../metrics/public/less/editor.less | 3 + .../metrics/public/lib/add_scope.js | 33 - .../public/lib/create_brush_handler.js | 13 +- src/core_plugins/metrics/public/lib/fetch.js | 42 - .../metrics/public/lib/fetch_fields.js | 44 +- .../region_map/public/choropleth_layer.js | 2 +- .../public/region_map_controller.js | 2 +- .../region_map/public/region_map_vis.js | 97 +- .../table_vis/public/table_vis.js | 68 +- .../table_vis/public/table_vis_controller.js | 2 +- .../tagcloud/public/tag_cloud_controller.js | 2 +- .../tagcloud/public/tag_cloud_vis.js | 71 +- .../tagcloud/public/tag_cloud_vis_params.html | 4 +- .../tagcloud/public/tag_cloud_vis_params.js | 3 + src/core_plugins/tile_map/index.js | 8 + src/core_plugins/tile_map/package.json | 4 + .../public/__tests__/geohash_layer.js | 508 +++++++ .../public/__tests__/geohash_sample_data.js | 1341 +++++++++++++++++ .../tile_map/public/__tests__/kibana_map.js | 139 ++ .../public/__tests__/tilemap_settings.js | 61 + .../__tests__/tilemap_settings_mocked.js | 140 ++ .../public/editors/tile_map.html | 0 .../tile_map/public/geohash_layer.js | 96 ++ .../tile_map/public/images/icon-tilemap.svg | 16 + .../tile_map/public/kibana_map.js | 564 +++++++ .../tile_map/public/kibana_map_layer.js | 38 + .../tile_map/public/lib/tilemap_settings.js | 208 +++ .../tile_map/public/maps_renderbot.js | 258 ++++ .../tile_map/public/maps_visualization.js | 33 + .../tile_map/public/markers/geohash_grid.js | 17 + .../tile_map/public/markers/heatmap.js | 189 +++ .../tile_map/public/markers/scaled_circles.js | 231 +++ .../tile_map/public/markers/shaded_circles.js | 46 + .../tile_map/public/styles/_tilemap.less | 185 +++ .../tile_map/public/tile_map_vis.js | 93 ++ src/core_plugins/timelion/public/vis/index.js | 73 +- .../public/vis/timelion_request_handler.js | 48 + .../timelion/public/vis/timelion_vis.html | 2 +- .../public/vis/timelion_vis_controller.js | 73 +- .../public/vis/timelion_vis_params.html | 2 +- .../vis/timelion_vis_params_controller.js | 12 - 69 files changed, 5194 insertions(+), 1253 deletions(-) delete mode 100644 src/core_plugins/kbn_vislib_vis_types/public/tile_map.js delete mode 100644 src/core_plugins/metrics/public/directives/vis_editor.js delete mode 100644 src/core_plugins/metrics/public/directives/visualization.js delete mode 100644 src/core_plugins/metrics/public/kbn_vis_types/editor.html create mode 100644 src/core_plugins/metrics/public/kbn_vis_types/request_handler.js delete mode 100644 src/core_plugins/metrics/public/kbn_vis_types/vis.html delete mode 100644 src/core_plugins/metrics/public/kbn_vis_types/vis_controller.js delete mode 100644 src/core_plugins/metrics/public/lib/add_scope.js delete mode 100644 src/core_plugins/metrics/public/lib/fetch.js create mode 100644 src/core_plugins/tile_map/index.js create mode 100644 src/core_plugins/tile_map/package.json create mode 100644 src/core_plugins/tile_map/public/__tests__/geohash_layer.js create mode 100644 src/core_plugins/tile_map/public/__tests__/geohash_sample_data.js create mode 100644 src/core_plugins/tile_map/public/__tests__/kibana_map.js create mode 100644 src/core_plugins/tile_map/public/__tests__/tilemap_settings.js create mode 100644 src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js rename src/core_plugins/{kbn_vislib_vis_types => tile_map}/public/editors/tile_map.html (100%) create mode 100644 src/core_plugins/tile_map/public/geohash_layer.js create mode 100644 src/core_plugins/tile_map/public/images/icon-tilemap.svg create mode 100644 src/core_plugins/tile_map/public/kibana_map.js create mode 100644 src/core_plugins/tile_map/public/kibana_map_layer.js create mode 100644 src/core_plugins/tile_map/public/lib/tilemap_settings.js create mode 100644 src/core_plugins/tile_map/public/maps_renderbot.js create mode 100644 src/core_plugins/tile_map/public/maps_visualization.js create mode 100644 src/core_plugins/tile_map/public/markers/geohash_grid.js create mode 100644 src/core_plugins/tile_map/public/markers/heatmap.js create mode 100644 src/core_plugins/tile_map/public/markers/scaled_circles.js create mode 100644 src/core_plugins/tile_map/public/markers/shaded_circles.js create mode 100644 src/core_plugins/tile_map/public/styles/_tilemap.less create mode 100644 src/core_plugins/tile_map/public/tile_map_vis.js create mode 100644 src/core_plugins/timelion/public/vis/timelion_request_handler.js delete mode 100644 src/core_plugins/timelion/public/vis/timelion_vis_params_controller.js diff --git a/src/core_plugins/kbn_vislib_vis_types/public/area.js b/src/core_plugins/kbn_vislib_vis_types/public/area.js index 62bdf1cf0fab0d..a143e057a997e5 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/area.js @@ -1,22 +1,22 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html'; import image from './images/icon-area.svg'; export default function PointSeriesVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'area', title: 'Area', image, description: 'Emphasize the quantity beneath a line chart', - category: VisType.CATEGORY.BASIC, - params: { + category: CATEGORY.BASIC, + visConfig: { defaults: { + type: 'area', grid: { categoryLines: false, style: { @@ -29,8 +29,7 @@ export default function PointSeriesVisType(Private) { type: 'category', position: 'bottom', show: true, - style: { - }, + style: {}, scale: { type: 'linear' }, @@ -48,8 +47,7 @@ export default function PointSeriesVisType(Private) { type: 'value', position: 'left', show: true, - style: { - }, + style: {}, scale: { type: 'linear', mode: 'normal' @@ -84,31 +82,34 @@ export default function PointSeriesVisType(Private) { times: [], addTimeMarker: false, }, - positions: ['top', 'left', 'right', 'bottom'], - chartTypes: [{ - value: 'line', - text: 'line' - }, { - value: 'area', - text: 'area' - }, { - value: 'histogram', - text: 'bar' - }], - axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], - scaleTypes: ['linear', 'log', 'square root'], - chartModes: ['normal', 'stacked'], - interpolationModes: [{ - value: 'linear', - text: 'straight', - }, { - value: 'cardinal', - text: 'smoothed', - }, { - value: 'step-after', - text: 'stepped', - }], - editor: pointSeriesTemplate, + }, + editorConfig: { + collections: { + positions: ['top', 'left', 'right', 'bottom'], + chartTypes: [{ + value: 'line', + text: 'line' + }, { + value: 'area', + text: 'area' + }, { + value: 'histogram', + text: 'bar' + }], + axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], + scaleTypes: ['linear', 'log', 'square root'], + chartModes: ['normal', 'stacked'], + interpolationModes: [{ + value: 'linear', + text: 'straight', + }, { + value: 'cardinal', + text: 'smoothed', + }, { + value: 'step-after', + text: 'stepped', + }], + }, optionTabs: [ { name: 'advanced', @@ -118,50 +119,50 @@ export default function PointSeriesVisType(Private) { }, { name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate }, ], - }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Y-Axis', - aggFilter: ['!geo_centroid'], - min: 1, - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: 'metrics', - name: 'radius', - title: 'Dot Size', - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] - }, - { - group: 'buckets', - name: 'segment', - title: 'X-Axis', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'group', - title: 'Split Series', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'split', - title: 'Split Chart', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Y-Axis', + aggFilter: ['!geo_centroid'], + min: 1, + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'metrics', + name: 'radius', + title: 'Dot Size', + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] + }, + { + group: 'buckets', + name: 'segment', + title: 'X-Axis', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'group', + title: 'Split Series', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'split', + title: 'Split Chart', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html b/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html index 24b1ff32289686..efc6857772e675 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html @@ -8,7 +8,7 @@ id="colorSchema" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.colorSchema" - ng-options="mode for mode in vis.type.params.colorSchemas" + ng-options="mode for mode in vis.editorConfig.collections.colorSchemas" >
diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.html b/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.html index 5e418675970f4c..72cbf410a94b56 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.html @@ -40,7 +40,7 @@ id="{{ 'seriesType' + $index }}" class="kuiSelect kuiSideBarSelect" ng-model="chart.type" - ng-options="mode.value as mode.text for mode in vis.type.params.chartTypes" + ng-options="mode.value as mode.text for mode in vis.type.editorConfig.collections.chartTypes" >
@@ -54,7 +54,7 @@ id="{{ 'seriesMode' + $index }}" class="kuiSelect kuiSideBarSelect" ng-model="chart.mode" - ng-options="mode for mode in vis.type.params.chartModes" + ng-options="mode for mode in vis.type.editorConfig.collections.chartModes" >
@@ -86,7 +86,7 @@ id="{{ 'lineMode' + $index }}" class="kuiSelect kuiSideBarSelect" ng-model="chart.interpolate" - ng-options="mode.value as mode.text for mode in vis.type.params.interpolationModes" + ng-options="mode.value as mode.text for mode in vis.type.editorConfig.collections.interpolationModes" > diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.js b/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.js index b0512ff20cebc6..aee69b840abb0d 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/series.js @@ -61,7 +61,9 @@ module.directive('vislibSeries', function () { return $scope.vis.params.seriesParams.map(series => series.type).join(); }, () => { const types = _.uniq(_.map($scope.vis.params.seriesParams, 'type')); - $scope.savedVis.type = types.length === 1 ? types[0] : 'histogram'; + const oldType = $scope.vis.type.type; + $scope.vis.type.type = types.length === 1 ? types[0] : 'histogram'; + if (oldType !== $scope.vis.type.type) $scope.vis.updateState(); }); $scope.$watch('vis.params.valueAxes.length', () => { diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/value_axes.html b/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/value_axes.html index 5d101490b2b268..f2f0b910af7abf 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/value_axes.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/point_series/value_axes.html @@ -84,7 +84,7 @@ class="kuiSelect kuiSideBarSelect" ng-change="updateAxisName(axis)" ng-model="axis.position" - ng-options="mode disable when isPositionDisabled(mode) for mode in vis.type.params.positions" + ng-options="mode disable when isPositionDisabled(mode) for mode in vis.type.editorConfig.collections.positions" > @@ -98,7 +98,7 @@ id="{{ 'valueAxisMode' + $index }}" class="kuiSelect kuiSideBarSelect" ng-model="axis.scale.mode" - ng-options="mode for mode in vis.type.params.axisModes" + ng-options="mode for mode in vis.type.editorConfig.collections.axisModes" > @@ -112,7 +112,7 @@ id="{{ 'valueAxisScaleType' + $index }}" class="kuiSelect kuiSideBarSelect" ng-model="axis.scale.type" - ng-options="type for type in vis.type.params.scaleTypes" + ng-options="type for type in vis.type.editorConfig.collections.scaleTypes" > diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html b/src/core_plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html index 9104462e414f16..50b4d37a44a409 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/vislib_basic_options.html @@ -6,7 +6,7 @@ diff --git a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js index b878d2b4c7be15..72441d4b25fff3 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js @@ -1,23 +1,22 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; import image from './images/icon-gauge.svg'; export default function GaugeVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'gauge', title: 'Gauge', image, description: `Gauges indicate the status of a metric. Use it to show how a metric's value relates to reference threshold values.`, - category: VisType.CATEGORY.DATA, - params: { + category: CATEGORY.DATA, + visConfig: { defaults: { addTooltip: true, addLegend: true, @@ -61,35 +60,37 @@ export default function GaugeVisType(Private) { } } }, - gaugeTypes: ['Arc', 'Circle', 'Metric'], - gaugeColorMode: ['None', 'Labels', 'Background'], - gaugeStyles: ['Full', 'Bars', 'Lines'], - scales: ['linear', 'log', 'square root'], - colorSchemas: Object.keys(vislibColorMaps), - editor: gaugeTemplate }, - implementsRenderComplete: true, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Metric', - min: 1, - aggFilter: [ - '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', - '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'], - defaults: [ - { schema: 'metric', type: 'count' } - ] + editorConfig: { + collections: { + gaugeTypes: ['Arc', 'Circle', 'Metric'], + gaugeColorMode: ['None', 'Labels', 'Background'], + scales: ['linear', 'log', 'square root'], + colorSchemas: Object.keys(vislibColorMaps), }, - { - group: 'buckets', - name: 'group', - title: 'Split Group', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + optionsTemplate: gaugeTemplate, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Metric', + min: 1, + aggFilter: [ + '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', + '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'group', + title: 'Split Group', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/core_plugins/kbn_vislib_vis_types/public/goal.js index 170907d4e7489d..bd0ed5ead91643 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/goal.js @@ -1,22 +1,21 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; import image from './images/icon-goal.svg'; export default function GoalVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'goal', title: 'Goal', image, description: 'A goal chart indicates how close you are to your final goal.', - category: VisType.CATEGORY.DATA, - params: { + category: CATEGORY.DATA, + visConfig: { defaults: { addTooltip: true, addLegend: false, @@ -56,34 +55,37 @@ export default function GoalVisType(Private) { } } }, - gaugeTypes: ['Arc', 'Circle', 'Metric'], - gaugeColorMode: ['None', 'Labels', 'Background'], - scales: ['linear', 'log', 'square root'], - colorSchemas: Object.keys(vislibColorMaps), - editor: gaugeTemplate }, - implementsRenderComplete: true, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Metric', - min: 1, - aggFilter: [ - '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', - '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'], - defaults: [ - { schema: 'metric', type: 'count' } - ] + editorConfig: { + collections: { + gaugeTypes: ['Arc', 'Circle', 'Metric'], + gaugeColorMode: ['None', 'Labels', 'Background'], + scales: ['linear', 'log', 'square root'], + colorSchemas: Object.keys(vislibColorMaps), }, - { - group: 'buckets', - name: 'group', - title: 'Split Group', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + optionsTemplate: gaugeTemplate, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Metric', + min: 1, + aggFilter: [ + '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', + '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'group', + title: 'Split Group', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js b/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js index e1e0bf34b944ac..2b68b597c07fbd 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js @@ -1,23 +1,23 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import heatmapTemplate from 'plugins/kbn_vislib_vis_types/editors/heatmap.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; import image from './images/icon-heatmap.svg'; export default function HeatmapVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'heatmap', title: 'Heat Map', image, description: 'Shade cells within a matrix', - category: VisType.CATEGORY.BASIC, - params: { + category: CATEGORY.BASIC, + visConfig: { defaults: { + type: 'heatmap', addTooltip: true, addLegend: true, enableHover: false, @@ -44,59 +44,64 @@ export default function HeatmapVisType(Private) { } }] }, - legendPositions: [{ - value: 'left', - text: 'left', - }, { - value: 'right', - text: 'right', - }, { - value: 'top', - text: 'top', - }, { - value: 'bottom', - text: 'bottom', - }], - scales: ['linear', 'log', 'square root'], - colorSchemas: Object.keys(vislibColorMaps), - editor: heatmapTemplate }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: 1, - aggFilter: ['count', 'avg', 'median', 'sum', 'min', 'max', 'cardinality', 'std_dev', 'top_hits'], - defaults: [ - { schema: 'metric', type: 'count' } - ] + editorConfig: { + collectons: { + legendPositions: [{ + value: 'left', + text: 'left', + }, { + value: 'right', + text: 'right', + }, { + value: 'top', + text: 'top', + }, { + value: 'bottom', + text: 'bottom', + }], + scales: ['linear', 'log', 'square root'], + colorSchemas: Object.keys(vislibColorMaps), }, - { - group: 'buckets', - name: 'segment', - title: 'X-Axis', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'group', - title: 'Y-Axis', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'split', - title: 'Split Chart', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + optionsTemplate: heatmapTemplate, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Value', + min: 1, + max: 1, + aggFilter: ['count', 'avg', 'median', 'sum', 'min', 'max', 'cardinality', 'std_dev', 'top_hits'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'segment', + title: 'X-Axis', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'group', + title: 'Y-Axis', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'split', + title: 'Split Chart', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } + }); } 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 c65bce00be3815..ea8a267424b208 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js @@ -1,22 +1,22 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html'; import image from './images/icon-vertical.svg'; export default function PointSeriesVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'histogram', title: 'Vertical Bar', image, description: 'Assign a continuous variable to each axis', - category: VisType.CATEGORY.BASIC, - params: { + category: CATEGORY.BASIC, + visConfig: { defaults: { + type: 'histogram', grid: { categoryLines: false, style: { @@ -29,8 +29,7 @@ export default function PointSeriesVisType(Private) { type: 'category', position: 'bottom', show: true, - style: { - }, + style: {}, scale: { type: 'linear' }, @@ -48,8 +47,7 @@ export default function PointSeriesVisType(Private) { type: 'value', position: 'left', show: true, - style: { - }, + style: {}, scale: { type: 'linear', mode: 'normal' @@ -85,31 +83,34 @@ export default function PointSeriesVisType(Private) { times: [], addTimeMarker: false, }, - positions: ['top', 'left', 'right', 'bottom'], - chartTypes: [{ - value: 'line', - text: 'line' - }, { - value: 'area', - text: 'area' - }, { - value: 'histogram', - text: 'bar' - }], - axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], - scaleTypes: ['linear', 'log', 'square root'], - chartModes: ['normal', 'stacked'], - interpolationModes: [{ - value: 'linear', - text: 'straight', - }, { - value: 'cardinal', - text: 'smoothed', - }, { - value: 'step-after', - text: 'stepped', - }], - editor: pointSeriesTemplate, + }, + editorConfig: { + collections: { + positions: ['top', 'left', 'right', 'bottom'], + chartTypes: [{ + value: 'line', + text: 'line' + }, { + value: 'area', + text: 'area' + }, { + value: 'histogram', + text: 'bar' + }], + axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], + scaleTypes: ['linear', 'log', 'square root'], + chartModes: ['normal', 'stacked'], + interpolationModes: [{ + value: 'linear', + text: 'straight', + }, { + value: 'cardinal', + text: 'smoothed', + }, { + value: 'step-after', + text: 'stepped', + }], + }, optionTabs: [ { name: 'advanced', @@ -119,50 +120,51 @@ export default function PointSeriesVisType(Private) { }, { name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate }, ], - }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Y-Axis', - min: 1, - aggFilter: ['!geo_centroid'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: 'metrics', - name: 'radius', - title: 'Dot Size', - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] - }, - { - group: 'buckets', - name: 'segment', - title: 'X-Axis', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'group', - title: 'Split Series', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'split', - title: 'Split Chart', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Y-Axis', + min: 1, + aggFilter: ['!geo_centroid'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'metrics', + name: 'radius', + title: 'Dot Size', + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] + }, + { + group: 'buckets', + name: 'segment', + title: 'X-Axis', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'group', + title: 'Split Series', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'split', + title: 'Split Chart', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } + }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js index 2f92b023ec8cb9..a23e90503f47c8 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js @@ -1,22 +1,22 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html'; import image from './images/icon-horizontal.svg'; export default function PointSeriesVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'horizontal_bar', title: 'Horizontal Bar', image, description: 'Assign a continuous variable to each axis', - category: VisType.CATEGORY.BASIC, - params: { + category: CATEGORY.BASIC, + visConfig: { defaults: { + type: 'histogram', grid: { categoryLines: false, style: { @@ -85,31 +85,34 @@ export default function PointSeriesVisType(Private) { times: [], addTimeMarker: false, }, - positions: ['top', 'left', 'right', 'bottom'], - chartTypes: [{ - value: 'line', - text: 'line' - }, { - value: 'area', - text: 'area' - }, { - value: 'histogram', - text: 'bar' - }], - axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], - scaleTypes: ['linear', 'log', 'square root'], - chartModes: ['normal', 'stacked'], - interpolationModes: [{ - value: 'linear', - text: 'straight', - }, { - value: 'cardinal', - text: 'smoothed', - }, { - value: 'step-after', - text: 'stepped', - }], - editor: pointSeriesTemplate, + }, + editorConfig: { + collections: { + positions: ['top', 'left', 'right', 'bottom'], + chartTypes: [{ + value: 'line', + text: 'line' + }, { + value: 'area', + text: 'area' + }, { + value: 'histogram', + text: 'bar' + }], + axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], + scaleTypes: ['linear', 'log', 'square root'], + chartModes: ['normal', 'stacked'], + interpolationModes: [{ + value: 'linear', + text: 'straight', + }, { + value: 'cardinal', + text: 'smoothed', + }, { + value: 'step-after', + text: 'stepped', + }], + }, optionTabs: [ { name: 'advanced', @@ -119,50 +122,50 @@ export default function PointSeriesVisType(Private) { }, { name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate }, ], - }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Y-Axis', - min: 1, - aggFilter: ['!geo_centroid'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: 'metrics', - name: 'radius', - title: 'Dot Size', - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] - }, - { - group: 'buckets', - name: 'segment', - title: 'X-Axis', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'group', - title: 'Split Series', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'split', - title: 'Split Chart', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Y-Axis', + min: 1, + aggFilter: ['!geo_centroid'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'metrics', + name: 'radius', + title: 'Dot Size', + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] + }, + { + group: 'buckets', + name: 'segment', + title: 'X-Axis', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'group', + title: 'Split Series', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'split', + title: 'Split Chart', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js b/src/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js index 671806d4afadcd..a47adf7eb8c7fc 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js @@ -4,7 +4,6 @@ import histogramVisTypeProvider from 'plugins/kbn_vislib_vis_types/histogram'; import lineVisTypeProvider from 'plugins/kbn_vislib_vis_types/line'; import pieVisTypeProvider from 'plugins/kbn_vislib_vis_types/pie'; import areaVisTypeProvider from 'plugins/kbn_vislib_vis_types/area'; -import tileMapVisTypeProvider from 'plugins/kbn_vislib_vis_types/tile_map'; import heatmapVisTypeProvider from 'plugins/kbn_vislib_vis_types/heatmap'; import horizontalBarVisTypeProvider from 'plugins/kbn_vislib_vis_types/horizontal_bar'; import gaugeVisTypeProvider from 'plugins/kbn_vislib_vis_types/gauge'; @@ -15,7 +14,6 @@ VisTypesRegistryProvider.register(histogramVisTypeProvider); VisTypesRegistryProvider.register(lineVisTypeProvider); VisTypesRegistryProvider.register(pieVisTypeProvider); VisTypesRegistryProvider.register(areaVisTypeProvider); -VisTypesRegistryProvider.register(tileMapVisTypeProvider); VisTypesRegistryProvider.register(heatmapVisTypeProvider); VisTypesRegistryProvider.register(horizontalBarVisTypeProvider); VisTypesRegistryProvider.register(gaugeVisTypeProvider); 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 77237faa0c996c..8f43d9bfeeb9a2 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/line.js @@ -1,22 +1,22 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from 'plugins/kbn_vislib_vis_types/editors/point_series.html'; import image from './images/icon-line.svg'; export default function PointSeriesVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'line', title: 'Line', image, description: 'Emphasize trends', - category: VisType.CATEGORY.BASIC, - params: { + category: CATEGORY.BASIC, + visConfig: { defaults: { + type: 'line', grid: { categoryLines: false, style: { @@ -29,8 +29,7 @@ export default function PointSeriesVisType(Private) { type: 'category', position: 'bottom', show: true, - style: { - }, + style: {}, scale: { type: 'linear' }, @@ -48,8 +47,7 @@ export default function PointSeriesVisType(Private) { type: 'value', position: 'left', show: true, - style: { - }, + style: {}, scale: { type: 'linear', mode: 'normal' @@ -85,31 +83,34 @@ export default function PointSeriesVisType(Private) { times: [], addTimeMarker: false, }, - positions: ['top', 'left', 'right', 'bottom'], - chartTypes: [{ - value: 'line', - text: 'line' - }, { - value: 'area', - text: 'area' - }, { - value: 'histogram', - text: 'bar' - }], - axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], - scaleTypes: ['linear', 'log', 'square root'], - chartModes: ['normal', 'stacked'], - interpolationModes: [{ - value: 'linear', - text: 'straight', - }, { - value: 'cardinal', - text: 'smoothed', - }, { - value: 'step-after', - text: 'stepped', - }], - editor: pointSeriesTemplate, + }, + editorConfig: { + collections: { + positions: ['top', 'left', 'right', 'bottom'], + chartTypes: [{ + value: 'line', + text: 'line' + }, { + value: 'area', + text: 'area' + }, { + value: 'histogram', + text: 'bar' + }], + axisModes: ['normal', 'percentage', 'wiggle', 'silhouette'], + scaleTypes: ['linear', 'log', 'square root'], + chartModes: ['normal', 'stacked'], + interpolationModes: [{ + value: 'linear', + text: 'straight', + }, { + value: 'cardinal', + text: 'smoothed', + }, { + value: 'step-after', + text: 'stepped', + }], + }, optionTabs: [ { name: 'advanced', @@ -119,50 +120,50 @@ export default function PointSeriesVisType(Private) { }, { name: 'options', title: 'Panel Settings', editor: pointSeriesTemplate }, ], - }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Y-Axis', - min: 1, - aggFilter: ['!geo_centroid'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: 'metrics', - name: 'radius', - title: 'Dot Size', - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'] - }, - { - group: 'buckets', - name: 'segment', - title: 'X-Axis', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'group', - title: 'Split Series', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - }, - { - group: 'buckets', - name: 'split', - title: 'Split Chart', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Y-Axis', + min: 1, + aggFilter: ['!geo_centroid'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'metrics', + name: 'radius', + title: 'Dot Size', + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'] + }, + { + group: 'buckets', + name: 'segment', + title: 'X-Axis', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'group', + title: 'Split Series', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'split', + title: 'Split Chart', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/metric.js b/src/core_plugins/kbn_vislib_vis_types/public/metric.js index b9087d45848e38..c42b34ccbffbfd 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/metric.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/metric.js @@ -1,22 +1,21 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import gaugeTemplate from 'plugins/kbn_vislib_vis_types/editors/gauge.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; import image from './images/icon-number.svg'; export default function MetricVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'metric', title: 'Metric', image, description: 'Display a calculation as a single number', - category: VisType.CATEGORY.DATA, - params: { + category: CATEGORY.DATA, + visConfig: { defaults: { addTooltip: true, addLegend: false, @@ -56,34 +55,37 @@ export default function MetricVisType(Private) { } } }, - gaugeTypes: ['Arc', 'Circle', 'Metric'], - gaugeColorMode: ['None', 'Labels', 'Background'], - scales: ['linear', 'log', 'square root'], - colorSchemas: Object.keys(vislibColorMaps), - editor: gaugeTemplate }, - implementsRenderComplete: true, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Metric', - min: 1, - aggFilter: [ - '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', - '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'], - defaults: [ - { schema: 'metric', type: 'count' } - ] + editorConfig: { + collections: { + gaugeTypes: ['Arc', 'Circle', 'Metric'], + gaugeColorMode: ['None', 'Labels', 'Background'], + scales: ['linear', 'log', 'square root'], + colorSchemas: Object.keys(vislibColorMaps), }, - { - group: 'buckets', - name: 'group', - title: 'Split Group', - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + optionsTemplate: gaugeTemplate, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Metric', + min: 1, + aggFilter: [ + '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', + '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'group', + title: 'Split Group', + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + } }); } 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 07d325252f1aa6..dbfce62a0f63fc 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/pie.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/pie.js @@ -1,76 +1,79 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { VislibVisTypeVislibVisTypeProvider } from 'ui/vislib_vis_type/vislib_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { CATEGORY } from 'ui/vis/vis_category'; import pieTemplate from 'plugins/kbn_vislib_vis_types/editors/pie.html'; import image from './images/icon-pie.svg'; export default function HistogramVisType(Private) { - const VisType = Private(VisVisTypeProvider); - const VislibVisType = Private(VislibVisTypeVislibVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new VislibVisType({ + return VisFactory.createVislibVisualization({ name: 'pie', title: 'Pie', image, description: 'Compare parts of a whole', - category: VisType.CATEGORY.BASIC, - params: { + category: CATEGORY.BASIC, + visConfig: { defaults: { + type: 'pie', addTooltip: true, addLegend: true, legendPosition: 'right', isDonut: false }, - legendPositions: [{ - value: 'left', - text: 'left', - }, { - value: 'right', - text: 'right', - }, { - value: 'top', - text: 'top', - }, { - value: 'bottom', - text: 'bottom', - }], - editor: pieTemplate }, - responseConverter: false, - hierarchicalData: true, - implementsRenderComplete: true, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Slice Size', - min: 1, - max: 1, - aggFilter: ['sum', 'count', 'cardinality', 'top_hits'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: 'buckets', - name: 'segment', - icon: 'fa fa-scissors', - title: 'Split Slices', - min: 0, - max: Infinity, - aggFilter: '!geohash_grid' + editorConfig: { + collections: { + legendPositions: [{ + value: 'left', + text: 'left', + }, { + value: 'right', + text: 'right', + }, { + value: 'top', + text: 'top', + }, { + value: 'bottom', + text: 'bottom', + }], }, - { - group: 'buckets', - name: 'split', - icon: 'fa fa-th', - title: 'Split Chart', - mustBeFirst: true, - min: 0, - max: 1, - aggFilter: '!geohash_grid' - } - ]) + optionsTemplate: pieTemplate, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Slice Size', + min: 1, + max: 1, + aggFilter: ['sum', 'count', 'cardinality', 'top_hits'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'segment', + icon: 'fa fa-scissors', + title: 'Split Slices', + min: 0, + max: Infinity, + aggFilter: '!geohash_grid' + }, + { + group: 'buckets', + name: 'split', + icon: 'fa fa-th', + title: 'Split Chart', + mustBeFirst: true, + min: 0, + max: 1, + aggFilter: '!geohash_grid' + } + ]) + }, + hierarchicalData: true, + implementsRenderComplete: true }); } diff --git a/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js b/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js deleted file mode 100644 index f1b7275ca68317..00000000000000 --- a/src/core_plugins/kbn_vislib_vis_types/public/tile_map.js +++ /dev/null @@ -1,80 +0,0 @@ -import { supports } from 'ui/utils/supports'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { MapsVisTypeProvider } from 'ui/vis_maps/maps_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; -import { AggResponseGeoJsonProvider } from 'ui/agg_response/geo_json/geo_json'; -import tileMapTemplate from 'plugins/kbn_vislib_vis_types/editors/tile_map.html'; -import image from './images/icon-tilemap.svg'; - -export default function TileMapVisType(Private, getAppState, courier, config) { - const VisType = Private(VisVisTypeProvider); - const MapsVisType = Private(MapsVisTypeProvider); - const Schemas = Private(VisSchemasProvider); - const geoJsonConverter = Private(AggResponseGeoJsonProvider); - - return new MapsVisType({ - name: 'tile_map', - title: 'Coordinate Map', - image, - description: 'Plot latitude and longitude coordinates on a map', - category: VisType.CATEGORY.MAP, - params: { - defaults: { - mapType: 'Scaled Circle Markers', - isDesaturated: true, - addTooltip: true, - heatMaxZoom: 0, - heatMinOpacity: 0.1, - heatRadius: 25, - heatBlur: 15, - legendPosition: 'bottomright', - mapZoom: 2, - mapCenter: [0, 0], - wms: config.get('visualization:tileMap:WMSdefaults') - }, - legendPositions: [{ - value: 'bottomleft', - text: 'bottom left', - }, { - value: 'bottomright', - text: 'bottom right', - }, { - value: 'topleft', - text: 'top left', - }, { - value: 'topright', - text: 'top right', - }], - mapTypes: ['Scaled Circle Markers', - 'Shaded Circle Markers', - 'Shaded Geohash Grid', - 'Heatmap' - ], - canDesaturate: !!supports.cssFilters, - editor: tileMapTemplate - }, - responseConverter: geoJsonConverter, - implementsRenderComplete: true, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: 'buckets', - name: 'segment', - title: 'Geo Coordinates', - aggFilter: 'geohash_grid', - min: 1, - max: 1 - } - ]) - }); -} diff --git a/src/core_plugins/markdown_vis/public/markdown_vis.js b/src/core_plugins/markdown_vis/public/markdown_vis.js index faa01d44ab791e..7ea21c6a403a4b 100644 --- a/src/core_plugins/markdown_vis/public/markdown_vis.js +++ b/src/core_plugins/markdown_vis/public/markdown_vis.js @@ -1,7 +1,7 @@ import 'plugins/markdown_vis/markdown_vis.less'; import 'plugins/markdown_vis/markdown_vis_controller'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { CATEGORY } from 'ui/vis/vis_category'; import markdownVisTemplate from 'plugins/markdown_vis/markdown_vis.html'; import markdownVisParamsTemplate from 'plugins/markdown_vis/markdown_vis_params.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; @@ -14,22 +14,23 @@ import image from './images/icon-markdown.svg'; VisTypesRegistryProvider.register(MarkdownVisProvider); function MarkdownVisProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const TemplateVisType = Private(TemplateVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); // return the visType object, which kibana will use to display and configure new // Vis object of this type. - return new TemplateVisType({ + return VisFactory.createAngularVisualization({ name: 'markdown', title: 'Markdown', image, description: 'Create a document using markdown syntax', - category: VisType.CATEGORY.OTHER, - template: markdownVisTemplate, - params: { - editor: markdownVisParamsTemplate + category: CATEGORY.OTHER, + visConfig: { + template: markdownVisTemplate, }, - requiresSearch: false, + editorConfig: { + optionsTemplate: markdownVisParamsTemplate + }, + requestHandler: 'none', implementsRenderComplete: true, }); } diff --git a/src/core_plugins/markdown_vis/public/markdown_vis_controller.js b/src/core_plugins/markdown_vis/public/markdown_vis_controller.js index 0a37e4c30d5f5b..e34dde63add147 100644 --- a/src/core_plugins/markdown_vis/public/markdown_vis_controller.js +++ b/src/core_plugins/markdown_vis/public/markdown_vis_controller.js @@ -9,11 +9,11 @@ marked.setOptions({ const module = uiModules.get('kibana/markdown_vis', ['kibana', 'ngSanitize']); -module.controller('KbnMarkdownVisController', function ($scope, $element) { +module.controller('KbnMarkdownVisController', function ($scope) { $scope.$watch('vis.params.markdown', function (html) { if (html) { $scope.html = marked(html); } - $element.trigger('renderComplete'); + $scope.renderComplete(); }); }); diff --git a/src/core_plugins/metrics/public/components/vis_editor.js b/src/core_plugins/metrics/public/components/vis_editor.js index 67ec263f7da7da..a6144b468f125f 100644 --- a/src/core_plugins/metrics/public/components/vis_editor.js +++ b/src/core_plugins/metrics/public/components/vis_editor.js @@ -3,28 +3,33 @@ import VisEditorVisualization from './vis_editor_visualization'; import Visualization from './visualization'; import VisPicker from './vis_picker'; import PanelConfig from './panel_config'; +import brushHandler from '../lib/create_brush_handler'; class VisEditor extends Component { constructor(props) { super(props); - this.state = { model: props.model }; + this.state = { model: props.vis.params }; + this.onBrush = brushHandler(props.vis.API.timeFilter); } render() { const handleChange = (part) => { const nextModel = { ...this.state.model, ...part }; this.setState({ model: nextModel }); - if (this.props.onChange) { - this.props.onChange(nextModel); + if (this.props.onChange || true) { + console.log(nextModel); + this.props.vis.params = nextModel; + this.props.vis.updateState(); + //this.props.onChange(nextModel); } }; - if (this.props.embedded) { + if (!this.props.vis.isEditorMode()) { return ( ); } @@ -43,12 +48,12 @@ class VisEditor extends Component { autoApply={this.props.autoApply} model={model} visData={this.props.visData} - onBrush={this.props.onBrush} + onBrush={this.onBrush} onCommit={this.props.onCommit} onToggleAutoApply={this.props.onToggleAutoApply} onChange={handleChange} /> diff --git a/src/core_plugins/metrics/public/directives/vis_editor.js b/src/core_plugins/metrics/public/directives/vis_editor.js deleted file mode 100644 index f1ec761a4dbfa6..00000000000000 --- a/src/core_plugins/metrics/public/directives/vis_editor.js +++ /dev/null @@ -1,37 +0,0 @@ -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { uiModules } from 'ui/modules'; -import VisEditor from '../components/vis_editor'; -import addScope from '../lib/add_scope'; -import angular from 'angular'; -import createBrushHandler from '../lib/create_brush_handler'; -const app = uiModules.get('apps/metrics/directives'); -app.directive('metricsVisEditor', (timefilter) => { - return { - restrict: 'E', - link: ($scope, $el) => { - const addToState = ['autoApply', 'dirty', 'embedded', 'fields', 'visData']; - const Component = addScope(VisEditor, $scope, addToState); - const handleBrush = createBrushHandler($scope, timefilter); - const handleChange = part => { - $scope.$evalAsync(() => angular.copy(part, $scope.model)); - }; - const handleCommit = () => { - $scope.$evalAsync(() => $scope.commit()); - }; - const handleToggleAutoApply = () => { - $scope.$evalAsync(() => $scope.toggleAutoApply()); - }; - render(, $el[0]); - $scope.$on('$destroy', () => { - unmountComponentAtNode($el[0]); - }); - } - }; -}); - diff --git a/src/core_plugins/metrics/public/directives/visualization.js b/src/core_plugins/metrics/public/directives/visualization.js deleted file mode 100644 index a0e7630f710ba2..00000000000000 --- a/src/core_plugins/metrics/public/directives/visualization.js +++ /dev/null @@ -1,46 +0,0 @@ -import _ from 'lodash'; -import $ from 'jquery'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import Visualization from '../components/visualization'; -import addScope from '../lib/add_scope'; -import { uiModules } from 'ui/modules'; -import createBrushHandler from '../lib/create_brush_handler'; - -const app = uiModules.get('apps/metrics/directives'); -app.directive('metricsVisualization', (timefilter, $timeout) => { - return { - restrict: 'E', - link: ($scope, $el) => { - const addToState = ['model', 'visData', 'reversed']; - const Component = addScope(Visualization, $scope, addToState); - const handleBrush = createBrushHandler($scope, timefilter); - render(, $el[0]); - $scope.$on('$destroy', () => unmountComponentAtNode($el[0])); - - // For Metrics, Gauges and markdown visualizations we want to hide the - // panel title because it just doens't make sense to show it. - // This is wrapped in a timeout so it happens after the directive is mouted. - // otherwise the .panel might not be available. - $timeout(() => { - const panel = $($el[0]).parents('.panel'); - if (panel.length) { - const panelHeading = panel.find('.panel-heading'); - const panelTitle = panel.find('.panel-title'); - const matchingTypes = ['metric', 'gauge', 'markdown']; - if (panelHeading.length && panelTitle.length && _.contains(matchingTypes, $scope.model.type)) { - panel.css({ position: 'relative' }); - panelHeading.css({ - position: 'absolute', - top: 0, - right: 0, - zIndex: 100 - }); - panelTitle.css({ display: 'none' }); - } - } - }, 1); - } - }; -}); - diff --git a/src/core_plugins/metrics/public/kbn_vis_types/editor.html b/src/core_plugins/metrics/public/kbn_vis_types/editor.html deleted file mode 100644 index afdd392f5f9d85..00000000000000 --- a/src/core_plugins/metrics/public/kbn_vis_types/editor.html +++ /dev/null @@ -1,5 +0,0 @@ -
- -
diff --git a/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js b/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js index a0cbdcd447ca2d..d78b9fd9fc36c2 100644 --- a/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js +++ b/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js @@ -1,122 +1,47 @@ -import { uiModules } from 'ui/modules'; -import '../services/executor'; -import createNewPanel from '../lib/create_new_panel'; -import '../directives/vis_editor'; -import _ from 'lodash'; -import angular from 'angular'; -import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { FetchFieldsProvider } from '../lib/fetch_fields'; const AUTO_APPLY_KEY = 'metrics_autoApply'; -const app = uiModules.get('kibana/metrics_vis', ['kibana']); -app.controller('MetricsEditorController', ( - $location, - $element, - $scope, - Private, - timefilter, - localStorage, - metricsExecutor -) => { +function ReactEditorControllerProvider(Private, localStorage) { + const fetchFields = Private(FetchFieldsProvider); - const autoApply = localStorage.get(AUTO_APPLY_KEY); - $scope.autoApply = autoApply != null ? autoApply : true; - $scope.embedded = $location.search().embed === 'true'; - const queryFilter = Private(FilterBarQueryFilterProvider); - const createFetch = Private(require('../lib/fetch')); - const fetch = () => { - const fn = createFetch($scope); - return fn().then((resp) => { - $element.trigger('renderComplete'); - return resp; - }); - }; - const fetchFields = Private(require('../lib/fetch_fields')); - - const debouncedFetch = _.debounce(() => fetch(), 1000, { - leading: false, - trailing: true - }); - - const debouncedFetchFields = _.debounce(fetchFields($scope), 1000, { - leading: false, - trailing: true - }); + class ReactEditorController { + constructor(el, vis) { + this.el = el; + this.vis = vis; - // If the model doesn't exist we need to either intialize it with a copy from - // the $scope.vis._editableVis.params or create a new panel all together. - if (!$scope.model) { - if ($scope.vis._editableVis.params.type) { - $scope.model = _.assign({}, $scope.vis._editableVis.params); - } else { - $scope.model = createNewPanel(); - angular.copy($scope.model, $scope.vis._editableVis.params); + const autoApply = localStorage.get(AUTO_APPLY_KEY); + vis.autoApply = autoApply != null ? autoApply : true; } - fetch(); - } - - $scope.commit = () => { - fetch(); - $scope.dirty = false; - }; - - $scope.toggleAutoApply = () => { - $scope.autoApply = !$scope.autoApply; - localStorage.set(AUTO_APPLY_KEY, $scope.autoApply); - }; - $scope.$watchCollection('model', (newValue, oldValue) => { - angular.copy(newValue, $scope.vis._editableVis.params); - $scope.stageEditableVis(); - $scope.dirty = !_.isEqual(newValue, oldValue); - - if ($scope.dirty && $scope.autoApply) { - debouncedFetch(); - $scope.dirty = false; - } + render(visData) { + this.visData = visData; - const patternsToFetch = []; - // Fetch any missing index patterns - if (!$scope.fields[newValue.index_pattern]) { - patternsToFetch.push(newValue.index_pattern); + return new Promise((resolve) => { + fetchFields(this.vis.params.index_pattern).then(fields => { + this.vis.fields = fields; + const Component = this.vis.type.editorConfig.component; + render(, this.el); + }); + }); } - newValue.series.forEach(series => { - if (series.override_index_pattern && - !$scope.fields[series.series_index_pattern]) { - patternsToFetch.push(series.series_index_pattern); + resize() { + if (this.visData) { + this.render(this.visData); } - }); - - if (newValue.annotations) { - newValue.annotations.forEach(item => { - if (item.index_pattern && - !$scope.fields[item.index_pattern]) { - patternsToFetch.push(item.index_pattern); - } - }); } - if(patternsToFetch.length) { - debouncedFetchFields(_.unique(patternsToFetch)); + destroy() { + unmountComponentAtNode(this.el); } - }); - - $scope.visData = {}; - $scope.fields = {}; - // All those need to be consolidated - $scope.$listen(queryFilter, 'fetch', fetch); - $scope.$on('fetch', fetch); - - fetchFields($scope)($scope.model.index_pattern); - - // Register fetch - metricsExecutor.register({ execute: fetch }); - - // Start the executor - metricsExecutor.start(); - - // Destory the executor - $scope.$on('$destroy', metricsExecutor.destroy); + } -}); + return { + name: 'react_editor', + handler: ReactEditorController + }; +} +export { ReactEditorControllerProvider }; diff --git a/src/core_plugins/metrics/public/kbn_vis_types/index.js b/src/core_plugins/metrics/public/kbn_vis_types/index.js index a35d016b7e5f1f..6de6fed5f46c28 100644 --- a/src/core_plugins/metrics/public/kbn_vis_types/index.js +++ b/src/core_plugins/metrics/public/kbn_vis_types/index.js @@ -1,36 +1,65 @@ -import './vis_controller'; -import './editor_controller'; import '../visualizations/less/main.less'; import 'react-select/dist/react-select.css'; import '../less/main.less'; import image from '../images/icon-visualbuilder.svg'; -import { TemplateVisTypeProvider } from 'ui/template_vis_type'; +import { MetricsRequestHandlerProvider } from './request_handler'; +import { ReactEditorControllerProvider } from './editor_controller'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { CATEGORY } from 'ui/vis/vis_category'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; // register the provider with the visTypes registry so that other know it exists import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; VisTypesRegistryProvider.register(MetricsVisProvider); export default function MetricsVisProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const TemplateVisType = Private(TemplateVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); + const ReactEditorController = Private(ReactEditorControllerProvider).handler; + const metricsRequestHandler = Private(MetricsRequestHandlerProvider).handler; - // return the visType object, which kibana will use to display and configure new - // Vis object of this type. - return new TemplateVisType({ + return VisFactory.createReactVisualization({ name: 'metrics', title: 'Visual Builder', - image, description: 'Build time-series using a visual pipeline interface', - category: VisType.CATEGORY.TIME, + category: CATEGORY.TIME, + image, isExperimental: true, - template: require('./vis.html'), - fullEditor: true, - params: { - editor: require('./editor.html') + visConfig: { + defaults: { + id: '61ca57f0-469d-11e7-af02-69e470af7417', + type: 'timeseries', + series: [ + { + id: '61ca57f1-469d-11e7-af02-69e470af7417', + color: '#68BC00', + split_mode: 'everything', + metrics: [ + { + id: '61ca57f2-469d-11e7-af02-69e470af7417', + type: 'count' + }], + seperate_axis: 0, + axis_position: 'right', + formatter: 'number', + chart_type: 'line', + line_width: 1, + point_size: 1, + fill: 0.5, + stacked: 'none' + }], + time_field: '@timestamp', + index_pattern: '*', + interval: 'auto', + axis_position: 'left', + axis_formatter: 'number', + show_legend:1 + }, + component: require('../components/vis_editor') + }, + editor: ReactEditorController, + editorConfig: { + component: require('../components/vis_editor') }, - requiresSearch: false, - requiresTimePicker: true, - implementsRenderComplete: true, + requestHandler: metricsRequestHandler, + responseHandler: 'none' }); } diff --git a/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js b/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js new file mode 100644 index 00000000000000..2282bdb02b1315 --- /dev/null +++ b/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js @@ -0,0 +1,69 @@ +import { validateInterval } from '../lib/validate_interval'; +import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context'; + +const MetricsRequestHandlerProvider = function (Private, Notifier, config, timefilter, $http) { + const dashboardContext = Private(dashboardContextProvider); + const notify = new Notifier({ location: 'Metrics' }); + + return { + name: 'metrics', + handler: function (vis /*, appState, uiState*/) { + + return new Promise((resolve) => { + const panel = vis.params; + if (panel && panel.id) { + const params = { + timerange: timefilter.getBounds(), + filters: [dashboardContext()], + panels: [panel] + }; + + try { + const maxBuckets = config.get('metrics:max_buckets'); + validateInterval(timefilter, panel, maxBuckets); + return $http.post('../api/metrics/vis/data', params) + .success(resp => { + + const patternsToFetch = []; + // Fetch any missing index patterns + if (!vis.fields) vis.fields = {}; + + if (!vis.fields[vis.params.index_pattern]) { + patternsToFetch.push(vis.params.index_pattern); + } + + vis.params.series.forEach(series => { + if (series.override_index_pattern && + !vis.fields[series.series_index_pattern]) { + patternsToFetch.push(series.series_index_pattern); + } + }); + + if (vis.params.annotations) { + vis.params.annotations.forEach(item => { + if (item.index_pattern && + !vis.fields[item.index_pattern]) { + patternsToFetch.push(item.index_pattern); + } + }); + } + + resolve(resp); + }) + .error(resp => { + resolve({}); + const err = new Error(resp.message); + err.stack = resp.stack; + notify.error(err); + }); + } catch (e) { + notify.error(e); + return resolve(); + } + } + }); + } + }; +}; + +export { MetricsRequestHandlerProvider }; diff --git a/src/core_plugins/metrics/public/kbn_vis_types/vis.html b/src/core_plugins/metrics/public/kbn_vis_types/vis.html deleted file mode 100644 index 59db84f9bc896d..00000000000000 --- a/src/core_plugins/metrics/public/kbn_vis_types/vis.html +++ /dev/null @@ -1,4 +0,0 @@ -
- -
diff --git a/src/core_plugins/metrics/public/kbn_vis_types/vis_controller.js b/src/core_plugins/metrics/public/kbn_vis_types/vis_controller.js deleted file mode 100644 index dff2b669c6daf4..00000000000000 --- a/src/core_plugins/metrics/public/kbn_vis_types/vis_controller.js +++ /dev/null @@ -1,49 +0,0 @@ -import { uiModules } from 'ui/modules'; -import 'ui/state_management/app_state'; -import '../directives/visualization'; -import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; - -const app = uiModules.get('kibana/metrics_vis'); - -app.controller('MetricsVisController', ( - $scope, - $element, - Private, - timefilter, - getAppState, - $location -) => { - - // If we are in the visualize editor context (and not embedded) we should not - // render the visualizations. This is handled by the editor itself. - const embedded = $location.search().embed === 'true'; - if (!embedded && $scope.vis._editableVis) { - return; - } - // We need to watch the app state for changes to the dark theme attribute. - $scope.state = getAppState(); - $scope.$watch('state.options.darkTheme', newValue => { - $scope.reversed = Boolean(newValue); - }); - - const queryFilter = Private(FilterBarQueryFilterProvider); - const createFetch = Private(require('../lib/fetch')); - const fetch = () => { - const fn = createFetch($scope); - return fn().then((resp) => { - $element.trigger('renderComplete'); - return resp; - }); - }; - - - $scope.model = $scope.vis.params; - $scope.$watch('vis.params', fetch); - - // All those need to be consolidated - $scope.$listen(timefilter, 'fetch', fetch); - $scope.$listen(queryFilter, 'fetch', fetch); - - $scope.$on('courier:searchRefresh', fetch); - $scope.$on('fetch', fetch); -}); diff --git a/src/core_plugins/metrics/public/less/editor.less b/src/core_plugins/metrics/public/less/editor.less index 9fcd6d5fabfdbf..fb85295e97f276 100644 --- a/src/core_plugins/metrics/public/less/editor.less +++ b/src/core_plugins/metrics/public/less/editor.less @@ -1,5 +1,8 @@ @borderRadius: 4px; +.vis_editor { + flex: 1; +} .vis_editor_container { background: @pageColor; } diff --git a/src/core_plugins/metrics/public/lib/add_scope.js b/src/core_plugins/metrics/public/lib/add_scope.js deleted file mode 100644 index 047501f8fba914..00000000000000 --- a/src/core_plugins/metrics/public/lib/add_scope.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -export default function addScope(WrappedComponent, $scope, addToState = []) { - return React.createClass({ - - getInitialState() { - const state = {}; - addToState.forEach(key => { - state[key] = $scope[key]; - }); - return state; - }, - - componentWillMount() { - this.unsubs = addToState.map(key => { - return $scope.$watchCollection(key, newValue => { - const newState = {}; - newState[key] = newValue; - this.setState(newState); - }); - }); - }, - - componentWillUnmount() { - this.unsubs.forEach(fn => fn()); - }, - - render() { - return ( - - ); - } - }); -} diff --git a/src/core_plugins/metrics/public/lib/create_brush_handler.js b/src/core_plugins/metrics/public/lib/create_brush_handler.js index 8b6b5bf3152316..b4eb7ca25e1c57 100644 --- a/src/core_plugins/metrics/public/lib/create_brush_handler.js +++ b/src/core_plugins/metrics/public/lib/create_brush_handler.js @@ -1,8 +1,9 @@ import moment from 'moment'; -export default ($scope, timefilter) => ranges => { - $scope.$evalAsync(() => { - timefilter.time.from = moment(ranges.xaxis.from).toISOString(); - timefilter.time.to = moment(ranges.xaxis.to).toISOString(); - timefilter.time.mode = 'absolute'; - }); +export default (timefilter) => ranges => { + //$scope.$evalAsync(() => { + timefilter.time.from = moment(ranges.xaxis.from).toISOString(); + timefilter.time.to = moment(ranges.xaxis.to).toISOString(); + timefilter.time.mode = 'absolute'; + timefilter.update(); + //}); }; diff --git a/src/core_plugins/metrics/public/lib/fetch.js b/src/core_plugins/metrics/public/lib/fetch.js deleted file mode 100644 index a2419b79700ccb..00000000000000 --- a/src/core_plugins/metrics/public/lib/fetch.js +++ /dev/null @@ -1,42 +0,0 @@ -import { validateInterval } from './validate_interval'; -import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context'; -export default ( - timefilter, - Private, - Notifier, - $http, - config, -) => { - const dashboardContext = Private(dashboardContextProvider); - const notify = new Notifier({ location: 'Metrics' }); - return $scope => () => { - const panel = $scope.model; - if (panel && panel.id) { - const params = { - timerange: timefilter.getBounds(), - filters: [dashboardContext()], - panels: [panel] - }; - - try { - const maxBuckets = config.get('metrics:max_buckets'); - validateInterval(timefilter, panel, maxBuckets); - return $http.post('../api/metrics/vis/data', params) - .success(resp => { - $scope.visData = resp; - }) - .error(resp => { - $scope.visData = {}; - const err = new Error(resp.message); - err.stack = resp.stack; - notify.error(err); - }); - } catch (e) { - notify.error(e); - return Promise.resolve(); - } - } - return Promise.resolve(); - }; -}; - diff --git a/src/core_plugins/metrics/public/lib/fetch_fields.js b/src/core_plugins/metrics/public/lib/fetch_fields.js index 8f0949ed34a7fa..4c5180141a480c 100644 --- a/src/core_plugins/metrics/public/lib/fetch_fields.js +++ b/src/core_plugins/metrics/public/lib/fetch_fields.js @@ -1,25 +1,29 @@ -export default ( - Notifier, - $http -) => { +const FetchFieldsProvider = (Notifier, $http) => { const notify = new Notifier({ location: 'Metrics' }); - return $scope => (indexPatterns = ['*']) => { + return (indexPatterns = ['*']) => { if (!Array.isArray(indexPatterns)) indexPatterns = [indexPatterns]; - return Promise.all(indexPatterns.map(pattern => { - return $http.get(`../api/metrics/fields?index=${pattern}`) - .success(resp => { - if (!$scope.fields) $scope.fields = {}; - if (resp.length && pattern) { - $scope.fields[pattern] = resp; - } - }) - .error(resp => { - $scope.visData = {}; - const err = new Error(resp.message); - err.stack = resp.stack; - notify.error(err); - }); - })); + return new Promise((resolve, reject) => { + const fields = {}; + + Promise.all(indexPatterns.map(pattern => { + return $http.get(`../api/metrics/fields?index=${pattern}`) + .success(resp => { + if (resp.length && pattern) { + fields[pattern] = resp; + } + }) + .error(resp => { + const err = new Error(resp.message); + err.stack = resp.stack; + notify.error(err); + reject(err); + }); + })).then(() => { + resolve(fields); + }); + }); }; }; +export { FetchFieldsProvider }; + diff --git a/src/core_plugins/region_map/public/choropleth_layer.js b/src/core_plugins/region_map/public/choropleth_layer.js index bd9b42c065018d..639d69f4850908 100644 --- a/src/core_plugins/region_map/public/choropleth_layer.js +++ b/src/core_plugins/region_map/public/choropleth_layer.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import L from 'leaflet'; import _ from 'lodash'; import d3 from 'd3'; -import { KibanaMapLayer } from 'ui/vis_maps/kibana_map_layer'; +import { KibanaMapLayer } from '../../tile_map/public/kibana_map_layer'; import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; export default class ChoroplethLayer extends KibanaMapLayer { diff --git a/src/core_plugins/region_map/public/region_map_controller.js b/src/core_plugins/region_map/public/region_map_controller.js index 0245f51f551ec7..9507529f4e8256 100644 --- a/src/core_plugins/region_map/public/region_map_controller.js +++ b/src/core_plugins/region_map/public/region_map_controller.js @@ -2,7 +2,7 @@ import { uiModules } from 'ui/modules'; import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; import _ from 'lodash'; import AggConfigResult from 'ui/vis/agg_config_result'; -import { KibanaMap } from 'ui/vis_maps/kibana_map'; +import { KibanaMap } from '../../tile_map/public/kibana_map'; import ChoroplethLayer from './choropleth_layer'; import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; import AggResponsePointSeriesTooltipFormatterProvider from './tooltip_formatter'; diff --git a/src/core_plugins/region_map/public/region_map_vis.js b/src/core_plugins/region_map/public/region_map_vis.js index e2ab2437baf347..f631adb867be13 100644 --- a/src/core_plugins/region_map/public/region_map_vis.js +++ b/src/core_plugins/region_map/public/region_map_vis.js @@ -3,32 +3,29 @@ import './region_map_controller'; import './region_map_vis_params'; import regionTemplate from './region_map_controller.html'; import image from './images/icon-vector-map.svg'; -import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { CATEGORY } from 'ui/vis/vis_category'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmapsConfig) { - - const VisType = Private(VisVisTypeProvider); - const TemplateVisType = Private(TemplateVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); const vectorLayers = regionmapsConfig.layers; const selectedLayer = vectorLayers[0]; const selectedJoinField = selectedLayer ? vectorLayers[0].fields[0] : null; - return new TemplateVisType({ + return VisFactory.createAngularVisualization({ name: 'region_map', title: 'Region Map', implementsRenderComplete: true, description: 'Show metrics on a thematic map. Use one of the provide base maps, or add your own. ' + 'Darker colors represent higher values.', - category: VisType.CATEGORY.MAP, + category: CATEGORY.MAP, image, - template: regionTemplate, - params: { + visConfig: { defaults: { legendPosition: 'bottomright', addTooltip: true, @@ -36,45 +33,51 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps selectedLayer: selectedLayer, selectedJoinField: selectedJoinField }, - legendPositions: [{ - value: 'bottomleft', - text: 'bottom left', - }, { - value: 'bottomright', - text: 'bottom right', - }, { - value: 'topleft', - text: 'top left', - }, { - value: 'topright', - text: 'top right', - }], - colorSchemas: Object.keys(truncatedColorMaps), - vectorLayers: vectorLayers, - editor: '' + template: regionTemplate, }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits', 'sum_bucket', 'min_bucket', 'max_bucket', 'avg_bucket'], - defaults: [ - { schema: 'metric', type: 'count' } - ] + editorConfig: { + optionsTemplate: '', + collections: { + legendPositions: [{ + value: 'bottomleft', + text: 'bottom left', + }, { + value: 'bottomright', + text: 'bottom right', + }, { + value: 'topleft', + text: 'top left', + }, { + value: 'topright', + text: 'top right', + }], + colorSchemas: Object.keys(truncatedColorMaps), + vectorLayers: vectorLayers, }, - { - group: 'buckets', - name: 'segment', - icon: 'fa fa-globe', - title: 'shape field', - min: 1, - max: 1, - aggFilter: ['terms'] - } - ]) + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Value', + min: 1, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits', + 'sum_bucket', 'min_bucket', 'max_bucket', 'avg_bucket'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'segment', + icon: 'fa fa-globe', + title: 'shape field', + min: 1, + max: 1, + aggFilter: ['terms'] + } + ]) + } }); }); diff --git a/src/core_plugins/table_vis/public/table_vis.js b/src/core_plugins/table_vis/public/table_vis.js index c0039ccbd72a97..51921067b8cd8b 100644 --- a/src/core_plugins/table_vis/public/table_vis.js +++ b/src/core_plugins/table_vis/public/table_vis.js @@ -3,9 +3,9 @@ import 'plugins/table_vis/table_vis_controller'; import 'plugins/table_vis/table_vis_params'; import 'ui/agg_table'; import 'ui/agg_table/agg_table_group'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { CATEGORY } from 'ui/vis/vis_category'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import tableVisTemplate from 'plugins/table_vis/table_vis.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import image from './images/icon-table.svg'; @@ -22,8 +22,7 @@ VisTypesRegistryProvider.register(TableVisTypeProvider); // define the TableVisType function TableVisTypeProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const TemplateVisType = Private(TemplateVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); // define the TableVisController which is used in the template @@ -31,14 +30,14 @@ function TableVisTypeProvider(Private) { // return the visType object, which kibana will use to display and configure new // Vis object of this type. - return new TemplateVisType({ + return VisFactory.createAngularVisualization({ + type: 'table', name: 'table', title: 'Data Table', image, description: 'Display values in a table', - category: VisType.CATEGORY.DATA, - template: tableVisTemplate, - params: { + category: CATEGORY.DATA, + visConfig: { defaults: { perPage: 10, showPartialRows: false, @@ -50,34 +49,37 @@ function TableVisTypeProvider(Private) { showTotal: false, totalFunc: 'sum' }, - editor: '' + template: tableVisTemplate, + }, + editorConfig: { + optionsTemplate: '', + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Metric', + aggFilter: '!geo_centroid', + min: 1, + defaults: [ + { type: 'count', schema: 'metric' } + ] + }, + { + group: 'buckets', + name: 'bucket', + title: 'Split Rows' + }, + { + group: 'buckets', + name: 'split', + title: 'Split Table' + } + ]) }, implementsRenderComplete: true, hierarchicalData: function (vis) { return Boolean(vis.params.showPartialRows || vis.params.showMeticsAtAllLevels); - }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Metric', - aggFilter: '!geo_centroid', - min: 1, - defaults: [ - { type: 'count', schema: 'metric' } - ] - }, - { - group: 'buckets', - name: 'bucket', - title: 'Split Rows' - }, - { - group: 'buckets', - name: 'split', - title: 'Split Table' - } - ]) + } }); } diff --git a/src/core_plugins/table_vis/public/table_vis_controller.js b/src/core_plugins/table_vis/public/table_vis_controller.js index 8620a3712e4346..d320647a887929 100644 --- a/src/core_plugins/table_vis/public/table_vis_controller.js +++ b/src/core_plugins/table_vis/public/table_vis_controller.js @@ -44,7 +44,7 @@ module.controller('KbnTableVisController', function ($scope, $element, Private) return table.rows.length > 0; }); - $element.trigger('renderComplete'); + $scope.renderComplete(); } $scope.hasSomeRows = hasSomeRows; diff --git a/src/core_plugins/tagcloud/public/tag_cloud_controller.js b/src/core_plugins/tagcloud/public/tag_cloud_controller.js index fe4de66c3b7d8d..0680183696e245 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_controller.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_controller.js @@ -47,7 +47,7 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private, } - $element.trigger('renderComplete'); + $scope.renderComplete(); }); $scope.$watch('esResponse', async function (response) { diff --git a/src/core_plugins/tagcloud/public/tag_cloud_vis.js b/src/core_plugins/tagcloud/public/tag_cloud_vis.js index 5c65c13a6c587d..6141ca51fe146b 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_vis.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_vis.js @@ -1,59 +1,62 @@ import 'plugins/tagcloud/tag_cloud.less'; import 'plugins/tagcloud/tag_cloud_controller'; import 'plugins/tagcloud/tag_cloud_vis_params'; -import { VisVisTypeProvider } from 'ui/vis/vis_type'; -import { TemplateVisTypeProvider } from 'ui/template_vis_type/template_vis_type'; -import { VisSchemasProvider } from 'ui/vis/schemas'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { CATEGORY } from 'ui/vis/vis_category'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; import tagCloudTemplate from 'plugins/tagcloud/tag_cloud_controller.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import image from './images/icon-tagcloud.svg'; VisTypesRegistryProvider.register(function TagCloudProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const TemplateVisType = Private(TemplateVisTypeProvider); + const VisFactory = Private(VisFactoryProvider); const Schemas = Private(VisSchemasProvider); - return new TemplateVisType({ + return VisFactory.createAngularVisualization({ name: 'tagcloud', title: 'Tag Cloud', image, - implementsRenderComplete: true, description: 'A group of words, sized according to their importance', - category: VisType.CATEGORY.OTHER, - template: tagCloudTemplate, - params: { + category: CATEGORY.OTHER, + visConfig: { defaults: { scale: 'linear', orientation: 'single', minFontSize: 18, maxFontSize: 72 }, - scales: ['linear', 'log', 'square root'], - orientations: ['single', 'right angled', 'multiple'], - editor: '' + template: tagCloudTemplate, }, - schemas: new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Tag Size', - min: 1, - max: 1, - aggFilter: ['!std_dev', '!percentiles', '!percentile_ranks', '!derivative'], - defaults: [ - { schema: 'metric', type: 'count' } - ] + responseHandler: 'none', + editorConfig: { + collections: { + scales: ['linear', 'log', 'square root'], + orientations: ['single', 'right angled', 'multiple'], }, - { - group: 'buckets', - name: 'segment', - icon: 'fa fa-cloud', - title: 'Tags', - min: 1, - max: 1, - aggFilter: ['terms'] - } - ]) + optionsTemplate: '', + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Tag Size', + min: 1, + max: 1, + aggFilter: ['!std_dev', '!percentiles', '!percentile_ranks', '!derivative'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'segment', + icon: 'fa fa-cloud', + title: 'Tags', + min: 1, + max: 1, + aggFilter: ['terms'] + } + ]) + } }); }); diff --git a/src/core_plugins/tagcloud/public/tag_cloud_vis_params.html b/src/core_plugins/tagcloud/public/tag_cloud_vis_params.html index 7c466480b22a5f..491d1734b73662 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_vis_params.html +++ b/src/core_plugins/tagcloud/public/tag_cloud_vis_params.html @@ -1,11 +1,11 @@
- +
- +
diff --git a/src/core_plugins/tagcloud/public/tag_cloud_vis_params.js b/src/core_plugins/tagcloud/public/tag_cloud_vis_params.js index f10c0b6100c466..f3928db45832c3 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_vis_params.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_vis_params.js @@ -13,6 +13,7 @@ uiModules.get('kibana/table_vis') link: function ($scope, $element) { const sliderContainer = $element[0]; const slider = sliderContainer.querySelector('.tag-cloud-fontsize-slider'); + $scope.config = $scope.vis.type.editorConfig; noUiSlider.create(slider, { start: [$scope.vis.params.minFontSize, $scope.vis.params.maxFontSize], connect: true, @@ -25,7 +26,9 @@ uiModules.get('kibana/table_vis') const fontSize = slider.noUiSlider.get(); $scope.vis.params.minFontSize = parseInt(fontSize[0], 10); $scope.vis.params.maxFontSize = parseInt(fontSize[1], 10); + $scope.vis.updateState(); $scope.$apply(); + }); } }; diff --git a/src/core_plugins/tile_map/index.js b/src/core_plugins/tile_map/index.js new file mode 100644 index 00000000000000..28117dcedf1a2b --- /dev/null +++ b/src/core_plugins/tile_map/index.js @@ -0,0 +1,8 @@ +export default function (kibana) { + + return new kibana.Plugin({ + uiExports: { + visTypes: ['plugins/tile_map/tile_map_vis'] + } + }); +} diff --git a/src/core_plugins/tile_map/package.json b/src/core_plugins/tile_map/package.json new file mode 100644 index 00000000000000..7635eb01023998 --- /dev/null +++ b/src/core_plugins/tile_map/package.json @@ -0,0 +1,4 @@ +{ + "name": "tile_map", + "version": "kibana" +} diff --git a/src/core_plugins/tile_map/public/__tests__/geohash_layer.js b/src/core_plugins/tile_map/public/__tests__/geohash_layer.js new file mode 100644 index 00000000000000..c61ed5d991bf8e --- /dev/null +++ b/src/core_plugins/tile_map/public/__tests__/geohash_layer.js @@ -0,0 +1,508 @@ +import expect from 'expect.js'; +import { KibanaMap } from 'ui/vis_maps/kibana_map'; +import { GeohashLayer } from 'ui/vis_maps/geohash_layer'; +import { GeoHashSampleData } from './geohash_sample_data'; + +describe('kibana_map tests', function () { + + let domNode; + let kibanaMap; + + + function setupDOM() { + domNode = document.createElement('div'); + domNode.style.top = '0'; + domNode.style.left = '0'; + domNode.style.width = '512px'; + domNode.style.height = '512px'; + domNode.style.position = 'fixed'; + domNode.style['pointer-events'] = 'none'; + document.body.appendChild(domNode); + } + + function teardownDOM() { + domNode.innerHTML = ''; + document.body.removeChild(domNode); + } + + + describe('GeohashGridLayer', function () { + + beforeEach(async function () { + setupDOM(); + kibanaMap = new KibanaMap(domNode, { + minZoom: 1, + maxZoom: 10 + }); + kibanaMap.setZoomLevel(3); + kibanaMap.setCenter({ + lon: -100, + lat: 40 + }); + }); + + afterEach(function () { + kibanaMap.destroy(); + teardownDOM(); + }); + + [ + { + options: { 'mapType': 'Scaled Circle Markers' }, + expected: `[ + { + "fill": "#bd0026", + "d": "M343,263.8A19.2,19.2,0,1,1,342.9,263.8 z" + }, + { + "fill": "#bd0026", + "d": "M343,225.03843394373595A18.961566056264047,18.961566056264047,0,1,1,342.9,225.03843394373595 z" + }, + { + "fill": "#bd0026", + "d": "M283,264.19815701843777A17.80184298156226,17.80184298156226,0,1,1,282.9,264.19815701843777 z" + }, + { + "fill": "#f03b20", + "d": "M405,224.2748797495895A16.72512025041049,16.72512025041049,0,1,1,404.9,224.2748797495895 z" + }, + { + "fill": "#f03b20", + "d": "M285,223.50180417608374A16.498195823916255,16.498195823916255,0,1,1,284.9,223.50180417608374 z" + }, + { + "fill": "#f03b20", + "d": "M343,299.1036928470748A15.896307152925205,15.896307152925205,0,1,1,342.9,299.1036928470748 z" + }, + { + "fill": "#f03b20", + "d": "M283,300.2846189453604A15.71538105463958,15.71538105463958,0,1,1,282.9,300.2846189453604 z" + }, + { + "fill": "#fd8d3c", + "d": "M148,267.0272116156895A13.972788384310489,13.972788384310489,0,1,1,147.9,267.0272116156895 z" + }, + { + "fill": "#feb24c", + "d": "M219,270.4178825645856A11.582117435414355,11.582117435414355,0,1,1,218.9,270.4178825645856 z" + }, + { + "fill": "#feb24c", + "d": "M146,189.63311915018554A11.366880849814459,11.366880849814459,0,1,1,145.9,189.63311915018554 z" + }, + { + "fill": "#feb24c", + "d": "M281,191.96973262756177A11.030267372438226,11.030267372438226,0,1,1,280.9,191.96973262756177 z" + }, + { + "fill": "#feb24c", + "d": "M220,231.85362974571228A10.146370254287714,10.146370254287714,0,1,1,219.9,231.85362974571228 z" + }, + { + "fill": "#feb24c", + "d": "M144,231.1923722152369A9.807627784763092,9.807627784763092,0,1,1,143.9,231.1923722152369 z" + }, + { + "fill": "#feb24c", + "d": "M387,268.27221854599287A9.72778145400714,9.72778145400714,0,1,1,386.9,268.27221854599287 z" + }, + { + "fill": "#feb24c", + "d": "M217,191.09542834646925A8.90457165353074,8.90457165353074,0,1,1,216.9,191.09542834646925 z" + }, + { + "fill": "#fed976", + "d": "M218,300.40744573968243A8.592554260317598,8.592554260317598,0,1,1,217.9,300.40744573968243 z" + }, + { + "fill": "#fed976", + "d": "M363,339.5411821762003A7.458817823799684,7.458817823799684,0,1,1,362.9,339.5411821762003 z" + }, + { + "fill": "#fed976", + "d": "M331,205.43072931381437A6.569270686185644,6.569270686185644,0,1,1,330.9,205.43072931381437 z" + }, + { + "fill": "#fed976", + "d": "M163,299.9012571034098A5.098742896590189,5.098742896590189,0,1,1,162.9,299.9012571034098 z" + }, + { + "fill": "#fed976", + "d": "M34,77.6735731867532A4.326426813246795,4.326426813246795,0,1,1,33.9,77.6735731867532 z" + }, + { + "fill": "#fed976", + "d": "M268,341.7954688958982A4.204531104101819,4.204531104101819,0,1,1,267.9,341.7954688958982 z" + }, + { + "fill": "#fed976", + "d": "M71,118.82649906983305A4.173500930166947,4.173500930166947,0,1,1,70.9,118.82649906983305 z" + }, + { + "fill": "#fed976", + "d": "M119,235.1169130974434A3.8830869025566206,3.8830869025566206,0,1,1,118.9,235.1169130974434 z" + }, + { + "fill": "#fed976", + "d": "M451,396.15053353027315A3.849466469726874,3.849466469726874,0,1,1,450.9,396.15053353027315 z" + }, + { + "fill": "#fed976", + "d": "M64,104.18445019554242A3.815549804457569,3.815549804457569,0,1,1,63.9,104.18445019554242 z" + }, + { + "fill": "#fed976", + "d": "M7,15.430879972386867A3.5691200276131325,3.5691200276131325,0,1,1,6.9,15.430879972386867 z" + }, + { + "fill": "#fed976", + "d": "M434,206.8985557756997A3.1014442243003013,3.1014442243003013,0,1,1,433.9,206.8985557756997 z" + }, + { + "fill": "#fed976", + "d": "M119,201.2073035006183A2.792696499381677,2.792696499381677,0,1,1,118.9,201.2073035006183 z" + }, + { + "fill": "#fed976", + "d": "M-1,420.89773444794906A2.1022655520509095,2.1022655520509095,0,1,1,-1.1,420.89773444794906 z" + }, + { + "fill": "#fed976", + "d": "M443,217.859886428343A1.1401135716569843,1.1401135716569843,0,1,1,442.9,217.859886428343 z" + }, + { + "fill": "#fed976", + "d": "M121,260.85988642834303A1.1401135716569843,1.1401135716569843,0,1,1,120.9,260.85988642834303 z" + }, + { + "fill": "#fed976", + "d": "M-4,399.27892886445886A0.7210711355411324,0.7210711355411324,0,1,1,-4.1,399.27892886445886 z" + } +]` + }, + { + options: { 'mapType': 'Shaded Circle Markers' }, + expected: `[ + { + "fill": "#bd0026", + "d": "M343,267A16,16,0,1,1,342.9,267 z" + }, + { + "fill": "#bd0026", + "d": "M343,226A18,18,0,1,1,342.9,226 z" + }, + { + "fill": "#bd0026", + "d": "M283,266A16,16,0,1,1,282.9,266 z" + }, + { + "fill": "#f03b20", + "d": "M405,223A18,18,0,1,1,404.9,223 z" + }, + { + "fill": "#f03b20", + "d": "M285,222A18,18,0,1,1,284.9,222 z" + }, + { + "fill": "#f03b20", + "d": "M343,300A15,15,0,1,1,342.9,300 z" + }, + { + "fill": "#f03b20", + "d": "M283,301A15,15,0,1,1,282.9,301 z" + }, + { + "fill": "#fd8d3c", + "d": "M148,265A16,16,0,1,1,147.9,265 z" + }, + { + "fill": "#feb24c", + "d": "M219,266A16,16,0,1,1,218.9,266 z" + }, + { + "fill": "#feb24c", + "d": "M146,183A18,18,0,1,1,145.9,183 z" + }, + { + "fill": "#feb24c", + "d": "M281,184A19,19,0,1,1,280.9,184 z" + }, + { + "fill": "#feb24c", + "d": "M220,225A17,17,0,1,1,219.9,225 z" + }, + { + "fill": "#feb24c", + "d": "M144,224A17,17,0,1,1,143.9,224 z" + }, + { + "fill": "#feb24c", + "d": "M387,262A16,16,0,1,1,386.9,262 z" + }, + { + "fill": "#feb24c", + "d": "M217,181A19,19,0,1,1,216.9,181 z" + }, + { + "fill": "#fed976", + "d": "M218,293A16,16,0,1,1,217.9,293 z" + }, + { + "fill": "#fed976", + "d": "M363,333A14,14,0,1,1,362.9,333 z" + }, + { + "fill": "#fed976", + "d": "M331,194A18,18,0,1,1,330.9,194 z" + }, + { + "fill": "#fed976", + "d": "M163,290A15,15,0,1,1,162.9,290 z" + }, + { + "fill": "#fed976", + "d": "M34,56A26,26,0,1,1,33.9,56 z" + }, + { + "fill": "#fed976", + "d": "M268,332A14,14,0,1,1,267.9,332 z" + }, + { + "fill": "#fed976", + "d": "M71,100A23,23,0,1,1,70.9,100 z" + }, + { + "fill": "#fed976", + "d": "M119,222A17,17,0,1,1,118.9,222 z" + }, + { + "fill": "#fed976", + "d": "M451,387A13,13,0,1,1,450.9,387 z" + }, + { + "fill": "#fed976", + "d": "M64,84A24,24,0,1,1,63.9,84 z" + }, + { + "fill": "#fed976", + "d": "M7,-7A26,26,0,1,1,6.9,-7 z" + }, + { + "fill": "#fed976", + "d": "M434,192A18,18,0,1,1,433.9,192 z" + }, + { + "fill": "#fed976", + "d": "M119,185A19,19,0,1,1,118.9,185 z" + }, + { + "fill": "#fed976", + "d": "M-1,410A13,13,0,1,1,-1.1,410 z" + }, + { + "fill": "#fed976", + "d": "M443,201A18,18,0,1,1,442.9,201 z" + }, + { + "fill": "#fed976", + "d": "M121,245A17,17,0,1,1,120.9,245 z" + }, + { + "fill": "#fed976", + "d": "M-4,386A14,14,0,1,1,-4.1,386 z" + } +]` + }, + { + options: { 'mapType': 'Shaded Geohash Grid' }, + expected: `[ + { + "fill": "#bd0026", + "d": "M313 301L313 261L377 261L377 301z" + }, + { + "fill": "#bd0026", + "d": "M313 261L313 218L377 218L377 261z" + }, + { + "fill": "#bd0026", + "d": "M249 301L249 261L313 261L313 301z" + }, + { + "fill": "#f03b20", + "d": "M377 261L377 218L441 218L441 261z" + }, + { + "fill": "#f03b20", + "d": "M249 261L249 218L313 218L313 261z" + }, + { + "fill": "#f03b20", + "d": "M313 338L313 301L377 301L377 338z" + }, + { + "fill": "#f03b20", + "d": "M249 338L249 301L313 301L313 338z" + }, + { + "fill": "#fd8d3c", + "d": "M121 301L121 261L185 261L185 301z" + }, + { + "fill": "#feb24c", + "d": "M185 301L185 261L249 261L249 301z" + }, + { + "fill": "#feb24c", + "d": "M121 218L121 170L185 170L185 218z" + }, + { + "fill": "#feb24c", + "d": "M249 218L249 170L313 170L313 218z" + }, + { + "fill": "#feb24c", + "d": "M185 261L185 218L249 218L249 261z" + }, + { + "fill": "#feb24c", + "d": "M121 261L121 218L185 218L185 261z" + }, + { + "fill": "#feb24c", + "d": "M377 301L377 261L441 261L441 301z" + }, + { + "fill": "#feb24c", + "d": "M185 218L185 170L249 170L249 218z" + }, + { + "fill": "#fed976", + "d": "M185 338L185 301L249 301L249 338z" + }, + { + "fill": "#fed976", + "d": "M313 374L313 338L377 338L377 374z" + }, + { + "fill": "#fed976", + "d": "M313 218L313 170L377 170L377 218z" + }, + { + "fill": "#fed976", + "d": "M121 338L121 301L185 301L185 338z" + }, + { + "fill": "#fed976", + "d": "M-7 116L-7 54L57 54L57 116z" + }, + { + "fill": "#fed976", + "d": "M249 374L249 338L313 338L313 374z" + }, + { + "fill": "#fed976", + "d": "M57 170L57 116L121 116L121 170z" + }, + { + "fill": "#fed976", + "d": "M57 261L57 218L121 218L121 261z" + }, + { + "fill": "#fed976", + "d": "M441 408L441 374L505 374L505 408z" + }, + { + "fill": "#fed976", + "d": "M57 116L57 54L121 54L121 116z" + }, + { + "fill": "#fed976", + "d": "M-7 54L-7 -21L57 -21L57 54z" + }, + { + "fill": "#fed976", + "d": "M377 218L377 170L441 170L441 218z" + }, + { + "fill": "#fed976", + "d": "M57 218L57 170L121 170L121 218z" + }, + { + "fill": "#fed976", + "d": "M-7 441L-7 408L57 408L57 441z" + }, + { + "fill": "#fed976", + "d": "M441 261L441 218L505 218L505 261z" + }, + { + "fill": "#fed976", + "d": "M57 301L57 261L121 261L121 301z" + }, + { + "fill": "#fed976", + "d": "M-7 408L-7 374L57 374L57 408z" + } +]` + } + ].forEach(function (test) { + + it(test.options.mapType, function () { + + const geohashGridOptions = test.options; + const geohashLayer = new GeohashLayer(GeoHashSampleData, geohashGridOptions, kibanaMap.getZoomLevel(), kibanaMap); + kibanaMap.addLayer(geohashLayer); + const markersNodeList = domNode.querySelectorAll('path.leaflet-clickable'); + const markerArray = []; + for (let i = 0; i < markersNodeList.length; i++) { + markerArray.push(markersNodeList[i]); + } + + const expectedGeohashGridMarkers = test.expected; + const expectedMarkers = JSON.parse(expectedGeohashGridMarkers).map(path => { + return { + fill: path.fill, + coords: path.d.match(/[0-9\.]+/g).map(parseFloat) + }; + }); + const actualMarkers = markerArray.map(a => { + return { + fill: a.getAttribute('fill'), + coords: a.getAttribute('d').match(/[0-9\.]+/g).map(parseFloat) + }; + }); + expect(actualMarkers.length).to.equal(expectedMarkers.length); + for (let i = 0; i < expectedMarkers.length; i++) { + expect(actualMarkers[i].fill).to.equal(expectedMarkers[i].fill); + actualMarkers[i].coords.forEach((coord, c) => { + closeTo(actualMarkers[i].coords[c], expectedMarkers[i].coords[c]); + }); + } + }); + }); + + + it('should not throw when fitting on empty-data layer', function () { + + const geohashLayer = new GeohashLayer({ + type: 'FeatureCollection', + features: [] + }, { 'mapType': 'Scaled Circle Markers' }, kibanaMap.getZoomLevel(), kibanaMap); + kibanaMap.addLayer(geohashLayer); + + expect(() => { + kibanaMap.fitToData(); + }).to.not.throwException(); + }); + + + }); + +}); + + +function closeTo(actual, expected) { + const epsilon = 1;//allow 2px slack + expect(actual - epsilon < expected && expected < actual + epsilon).to.equal(true); +} diff --git a/src/core_plugins/tile_map/public/__tests__/geohash_sample_data.js b/src/core_plugins/tile_map/public/__tests__/geohash_sample_data.js new file mode 100644 index 00000000000000..86b4289c88dffe --- /dev/null +++ b/src/core_plugins/tile_map/public/__tests__/geohash_sample_data.js @@ -0,0 +1,1341 @@ +const sampleData = `{ + "type": "FeatureCollection", + "features": [{ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-84.81215765699744, 36.289477944374084]}, + "properties": { + "geohash": "dn", + "value": 1418, + "aggConfigResult": { + "key": 1418, + "value": 1418, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dn", + "value": "dn", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 4, + "type": "bucket" + }, + "$order": 5, + "type": "metric" + }, + "center": [36.5625, -84.375], + "rectangle": [[33.75, -90], [33.75, -78.75], [39.375, -78.75], [39.375, -90]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-84.8004243336618, 41.63311270996928]}, + "properties": { + "geohash": "dp", + "value": 1383, + "aggConfigResult": { + "key": 1383, + "value": 1383, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dp", + "value": "dp", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 7, + "type": "bucket" + }, + "$order": 8, + "type": "metric" + }, + "center": [42.1875, -84.375], + "rectangle": [[39.375, -90], [39.375, -78.75], [45, -78.75], [45, -90]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-95.20564651116729, 36.4947619009763]}, + "properties": { + "geohash": "9y", + "value": 1219, + "aggConfigResult": { + "key": 1219, + "value": 1219, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9y", + "value": "9y", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 10, + "type": "bucket" + }, + "$order": 11, + "type": "metric" + }, + "center": [36.5625, -95.625], + "rectangle": [[33.75, -101.25], [33.75, -90], [39.375, -90], [39.375, -101.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-73.8917376101017, 42.086046701297164]}, + "properties": { + "geohash": "dr", + "value": 1076, + "aggConfigResult": { + "key": 1076, + "value": 1076, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dr", + "value": "dr", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 13, + "type": "bucket" + }, + "$order": 14, + "type": "metric" + }, + "center": [42.1875, -73.125], + "rectangle": [[39.375, -78.75], [39.375, -67.5], [45, -67.5], [45, -78.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-94.9999794177711, 42.19089978374541]}, + "properties": { + "geohash": "9z", + "value": 1047, + "aggConfigResult": { + "key": 1047, + "value": 1047, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9z", + "value": "9z", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 16, + "type": "bucket" + }, + "$order": 17, + "type": "metric" + }, + "center": [42.1875, -95.625], + "rectangle": [[39.375, -101.25], [39.375, -90], [45, -90], [45, -101.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-84.72070790827274, 31.68308235704899]}, + "properties": { + "geohash": "dj", + "value": 972, + "aggConfigResult": { + "key": 972, + "value": 972, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dj", + "value": "dj", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 19, + "type": "bucket" + }, + "$order": 20, + "type": "metric" + }, + "center": [30.9375, -84.375], + "rectangle": [[28.125, -90], [28.125, -78.75], [33.75, -78.75], [33.75, -90]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-95.22422080859542, 31.44715240225196]}, + "properties": { + "geohash": "9v", + "value": 950, + "aggConfigResult": { + "key": 950, + "value": 950, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9v", + "value": "9v", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 22, + "type": "bucket" + }, + "$order": 23, + "type": "metric" + }, + "center": [30.9375, -95.625], + "rectangle": [[28.125, -101.25], [28.125, -90], [33.75, -90], [33.75, -101.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-119.02438038960099, 36.617594081908464]}, + "properties": { + "geohash": "9q", + "value": 751, + "aggConfigResult": { + "key": 751, + "value": 751, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9q", + "value": "9q", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 25, + "type": "bucket" + }, + "$order": 26, + "type": "metric" + }, + "center": [36.5625, -118.125], + "rectangle": [[33.75, -123.75], [33.75, -112.5], [39.375, -112.5], [39.375, -123.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-106.54198246076703, 36.47509602829814]}, + "properties": { + "geohash": "9w", + "value": 516, + "aggConfigResult": { + "key": 516, + "value": 516, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9w", + "value": "9w", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 28, + "type": "bucket" + }, + "$order": 29, + "type": "metric" + }, + "center": [36.5625, -106.875], + "rectangle": [[33.75, -112.5], [33.75, -101.25], [39.375, -101.25], [39.375, -112.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-119.28373273462057, 47.07595920190215]}, + "properties": { + "geohash": "c2", + "value": 497, + "aggConfigResult": { + "key": 497, + "value": 497, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "c2", + "value": "c2", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 31, + "type": "bucket" + }, + "$order": 32, + "type": "metric" + }, + "center": [47.8125, -118.125], + "rectangle": [[45, -123.75], [45, -112.5], [50.625, -112.5], [50.625, -123.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-95.67718841135502, 46.75232579000294]}, + "properties": { + "geohash": "cb", + "value": 468, + "aggConfigResult": { + "key": 468, + "value": 468, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "cb", + "value": "cb", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 34, + "type": "bucket" + }, + "$order": 35, + "type": "metric" + }, + "center": [47.8125, -95.625], + "rectangle": [[45, -101.25], [45, -90], [50.625, -90], [50.625, -101.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-106.2923239544034, 41.907251570373774]}, + "properties": { + "geohash": "9x", + "value": 396, + "aggConfigResult": { + "key": 396, + "value": 396, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9x", + "value": "9x", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 37, + "type": "bucket" + }, + "$order": 38, + "type": "metric" + }, + "center": [42.1875, -106.875], + "rectangle": [[39.375, -112.5], [39.375, -101.25], [45, -101.25], [45, -112.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-119.63544443249702, 42.04197423532605]}, + "properties": { + "geohash": "9r", + "value": 370, + "aggConfigResult": { + "key": 370, + "value": 370, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9r", + "value": "9r", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 40, + "type": "bucket" + }, + "$order": 41, + "type": "metric" + }, + "center": [42.1875, -118.125], + "rectangle": [[39.375, -123.75], [39.375, -112.5], [45, -112.5], [45, -123.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-76.97201896458864, 37.06826982088387]}, + "properties": { + "geohash": "dq", + "value": 364, + "aggConfigResult": { + "key": 364, + "value": 364, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dq", + "value": "dq", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 43, + "type": "bucket" + }, + "$order": 44, + "type": "metric" + }, + "center": [36.5625, -73.125], + "rectangle": [[33.75, -78.75], [33.75, -67.5], [39.375, -67.5], [39.375, -78.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-106.92424703389406, 47.192871160805225]}, + "properties": { + "geohash": "c8", + "value": 305, + "aggConfigResult": { + "key": 305, + "value": 305, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "c8", + "value": "c8", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 46, + "type": "bucket" + }, + "$order": 47, + "type": "metric" + }, + "center": [47.8125, -106.875], + "rectangle": [[45, -112.5], [45, -101.25], [50.625, -101.25], [50.625, -112.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-106.78505832329392, 32.50790253281593]}, + "properties": { + "geohash": "9t", + "value": 284, + "aggConfigResult": { + "key": 284, + "value": 284, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9t", + "value": "9t", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 49, + "type": "bucket" + }, + "$order": 50, + "type": "metric" + }, + "center": [30.9375, -106.875], + "rectangle": [[28.125, -112.5], [28.125, -101.25], [33.75, -101.25], [33.75, -112.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-152.9292524792254, 59.777277521789074]}, + "properties": { + "geohash": "bd", + "value": 217, + "aggConfigResult": { + "key": 217, + "value": 217, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "bd", + "value": "bd", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 52, + "type": "bucket" + }, + "$order": 53, + "type": "metric" + }, + "center": [59.0625, -151.875], + "rectangle": [[56.25, -157.5], [56.25, -146.25], [61.875, -146.25], [61.875, -157.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-81.13159038126469, 26.815882762894034]}, + "properties": { + "geohash": "dh", + "value": 214, + "aggConfigResult": { + "key": 214, + "value": 214, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dh", + "value": "dh", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 55, + "type": "bucket" + }, + "$order": 56, + "type": "metric" + }, + "center": [25.3125, -84.375], + "rectangle": [[22.5, -90], [22.5, -78.75], [28.125, -78.75], [28.125, -90]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-162.1049272455275, 64.38826035708189]}, + "properties": { + "geohash": "b7", + "value": 194, + "aggConfigResult": { + "key": 194, + "value": 194, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "b7", + "value": "b7", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 58, + "type": "bucket" + }, + "$order": 59, + "type": "metric" + }, + "center": [64.6875, -163.125], + "rectangle": [[61.875, -168.75], [61.875, -157.5], [67.5, -157.5], [67.5, -168.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-161.59194018691778, 60.06503529846668]}, + "properties": { + "geohash": "b6", + "value": 168, + "aggConfigResult": { + "key": 168, + "value": 168, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "b6", + "value": "b6", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 61, + "type": "bucket" + }, + "$order": 62, + "type": "metric" + }, + "center": [59.0625, -163.125], + "rectangle": [[56.25, -168.75], [56.25, -157.5], [61.875, -157.5], [61.875, -168.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-86.82362716645002, 45.665992330759764]}, + "properties": { + "geohash": "f0", + "value": 166, + "aggConfigResult": { + "key": 166, + "value": 166, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "f0", + "value": "f0", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 64, + "type": "bucket" + }, + "$order": 65, + "type": "metric" + }, + "center": [47.8125, -84.375], + "rectangle": [[45, -90], [45, -78.75], [50.625, -78.75], [50.625, -90]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-152.04110082238913, 65.17680524848402]}, + "properties": { + "geohash": "be", + "value": 158, + "aggConfigResult": { + "key": 158, + "value": 158, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "be", + "value": "be", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 67, + "type": "bucket" + }, + "$order": 68, + "type": "metric" + }, + "center": [64.6875, -151.875], + "rectangle": [[61.875, -157.5], [61.875, -146.25], [67.5, -146.25], [67.5, -157.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-116.37748382985592, 33.16976627334952]}, + "properties": { + "geohash": "9m", + "value": 100, + "aggConfigResult": { + "key": 100, + "value": 100, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9m", + "value": "9m", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 70, + "type": "bucket" + }, + "$order": 71, + "type": "metric" + }, + "center": [30.9375, -118.125], + "rectangle": [[28.125, -123.75], [28.125, -112.5], [33.75, -112.5], [33.75, -123.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-139.12713261321187, 59.41271326504648]}, + "properties": { + "geohash": "bf", + "value": 72, + "aggConfigResult": { + "key": 72, + "value": 72, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "bf", + "value": "bf", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 73, + "type": "bucket" + }, + "$order": 74, + "type": "metric" + }, + "center": [59.0625, -140.625], + "rectangle": [[56.25, -146.25], [56.25, -135], [61.875, -135], [61.875, -146.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-97.89513738825917, 26.928304536268115]}, + "properties": { + "geohash": "9u", + "value": 68, + "aggConfigResult": { + "key": 68, + "value": 68, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9u", + "value": "9u", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 76, + "type": "bucket" + }, + "$order": 77, + "type": "metric" + }, + "center": [25.3125, -95.625], + "rectangle": [[22.5, -101.25], [22.5, -90], [28.125, -90], [28.125, -101.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-132.52599561586976, 55.60743710026145]}, + "properties": { + "geohash": "c1", + "value": 67, + "aggConfigResult": { + "key": 67, + "value": 67, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "c1", + "value": "c1", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 79, + "type": "bucket" + }, + "$order": 80, + "type": "metric" + }, + "center": [53.4375, -129.375], + "rectangle": [[50.625, -135], [50.625, -123.75], [56.25, -123.75], [56.25, -135]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-124.13590382784605, 42.24034773185849]}, + "properties": { + "geohash": "9p", + "value": 58, + "aggConfigResult": { + "key": 58, + "value": 58, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9p", + "value": "9p", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 82, + "type": "bucket" + }, + "$order": 83, + "type": "metric" + }, + "center": [42.1875, -129.375], + "rectangle": [[39.375, -135], [39.375, -123.75], [45, -123.75], [45, -135]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-65.72741221636534, 18.170374436303973]}, + "properties": { + "geohash": "de", + "value": 57, + "aggConfigResult": { + "key": 57, + "value": 57, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "de", + "value": "de", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 85, + "type": "bucket" + }, + "$order": 86, + "type": "metric" + }, + "center": [19.6875, -61.875], + "rectangle": [[16.875, -67.5], [16.875, -56.25], [22.5, -56.25], [22.5, -67.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-133.79055473953485, 57.08371731452644]}, + "properties": { + "geohash": "c4", + "value": 56, + "aggConfigResult": { + "key": 56, + "value": 56, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "c4", + "value": "c4", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 88, + "type": "bucket" + }, + "$order": 89, + "type": "metric" + }, + "center": [59.0625, -129.375], + "rectangle": [[56.25, -135], [56.25, -123.75], [61.875, -123.75], [61.875, -135]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-152.2658603824675, 69.64116730727255]}, + "properties": { + "geohash": "bs", + "value": 51, + "aggConfigResult": { + "key": 51, + "value": 51, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "bs", + "value": "bs", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 91, + "type": "bucket" + }, + "$order": 92, + "type": "metric" + }, + "center": [70.3125, -151.875], + "rectangle": [[67.5, -157.5], [67.5, -146.25], [73.125, -146.25], [73.125, -157.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-143.8043469004333, 64.64996575377882]}, + "properties": { + "geohash": "bg", + "value": 49, + "aggConfigResult": { + "key": 49, + "value": 49, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "bg", + "value": "bg", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 94, + "type": "bucket" + }, + "$order": 95, + "type": "metric" + }, + "center": [64.6875, -140.625], + "rectangle": [[61.875, -146.25], [61.875, -135], [67.5, -135], [67.5, -146.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-162.65227859839797, 54.967785738408566]}, + "properties": { + "geohash": "b3", + "value": 43, + "aggConfigResult": { + "key": 43, + "value": 43, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "b3", + "value": "b3", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 97, + "type": "bucket" + }, + "$order": 98, + "type": "metric" + }, + "center": [53.4375, -163.125], + "rectangle": [[50.625, -168.75], [50.625, -157.5], [56.25, -157.5], [56.25, -168.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-156.20294423773885, 20.63592097721994]}, + "properties": { + "geohash": "8e", + "value": 40, + "aggConfigResult": { + "key": 40, + "value": 40, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "8e", + "value": "8e", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 100, + "type": "bucket" + }, + "$order": 101, + "type": "metric" + }, + "center": [19.6875, -151.875], + "rectangle": [[16.875, -157.5], [16.875, -146.25], [22.5, -146.25], [22.5, -157.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-68.71966263279319, 45.89407338760793]}, + "properties": { + "geohash": "f2", + "value": 37, + "aggConfigResult": { + "key": 37, + "value": 37, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "f2", + "value": "f2", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 103, + "type": "bucket" + }, + "$order": 104, + "type": "metric" + }, + "center": [47.8125, -73.125], + "rectangle": [[45, -78.75], [45, -67.5], [50.625, -67.5], [50.625, -78.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-159.04649432748556, 21.810192000120878]}, + "properties": { + "geohash": "87", + "value": 31, + "aggConfigResult": { + "key": 31, + "value": 31, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "87", + "value": "87", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 106, + "type": "bucket" + }, + "$order": 107, + "type": "metric" + }, + "center": [19.6875, -163.125], + "rectangle": [[16.875, -168.75], [16.875, -157.5], [22.5, -157.5], [22.5, -168.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-124.07574724406004, 46.70505428686738]}, + "properties": { + "geohash": "c0", + "value": 30, + "aggConfigResult": { + "key": 30, + "value": 30, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "c0", + "value": "c0", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 109, + "type": "bucket" + }, + "$order": 110, + "type": "metric" + }, + "center": [47.8125, -129.375], + "rectangle": [[45, -135], [45, -123.75], [50.625, -123.75], [50.625, -135]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-170.66843625158072, 64.42178352735937]}, + "properties": { + "geohash": "b5", + "value": 18, + "aggConfigResult": { + "key": 18, + "value": 18, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "b5", + "value": "b5", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 112, + "type": "bucket" + }, + "$order": 113, + "type": "metric" + }, + "center": [64.6875, -174.375], + "rectangle": [[61.875, -180], [61.875, -168.75], [67.5, -168.75], [67.5, -180]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-164.1237143240869, 68.94954898394644]}, + "properties": { + "geohash": "bk", + "value": 17, + "aggConfigResult": { + "key": 17, + "value": 17, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "bk", + "value": "bk", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 115, + "type": "bucket" + }, + "$order": 116, + "type": "metric" + }, + "center": [70.3125, -163.125], + "rectangle": [[67.5, -168.75], [67.5, -157.5], [73.125, -157.5], [73.125, -168.75]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-145.23947272449732, 14.257271960377693]}, + "properties": { + "geohash": "8f", + "value": 17, + "aggConfigResult": { + "key": 17, + "value": 17, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "8f", + "value": "8f", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 118, + "type": "bucket" + }, + "$order": 119, + "type": "metric" + }, + "center": [14.0625, -140.625], + "rectangle": [[11.25, -146.25], [11.25, -135], [16.875, -135], [16.875, -146.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-169.90729674696922, 56.83546897955239]}, + "properties": { + "geohash": "b4", + "value": 16, + "aggConfigResult": { + "key": 16, + "value": 16, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "b4", + "value": "b4", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 121, + "type": "bucket" + }, + "$order": 122, + "type": "metric" + }, + "center": [59.0625, -174.375], + "rectangle": [[56.25, -180], [56.25, -168.75], [61.875, -168.75], [61.875, -180]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-170.12874579057097, 14.265542635694146]}, + "properties": { + "geohash": "84", + "value": 12, + "aggConfigResult": { + "key": 12, + "value": 12, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "84", + "value": "84", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 124, + "type": "bucket" + }, + "$order": 125, + "type": "metric" + }, + "center": [14.0625, -174.375], + "rectangle": [[11.25, -180], [11.25, -168.75], [16.875, -168.75], [16.875, -180]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-144.66744488105178, 69.03327229432762]}, + "properties": { + "geohash": "bu", + "value": 11, + "aggConfigResult": { + "key": 11, + "value": 11, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "bu", + "value": "bu", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 127, + "type": "bucket" + }, + "$order": 128, + "type": "metric" + }, + "center": [70.3125, -140.625], + "rectangle": [[67.5, -146.25], [67.5, -135], [73.125, -135], [73.125, -146.25]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-67.10587805137038, 44.86871098168194]}, + "properties": { + "geohash": "dx", + "value": 5, + "aggConfigResult": { + "key": 5, + "value": 5, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "dx", + "value": "dx", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 130, + "type": "bucket" + }, + "$order": 131, + "type": "metric" + }, + "center": [42.1875, -61.875], + "rectangle": [[39.375, -67.5], [39.375, -56.25], [45, -56.25], [45, -67.5]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-174.69428664073348, 52.15187128633261]}, + "properties": { + "geohash": "b1", + "value": 5, + "aggConfigResult": { + "key": 5, + "value": 5, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "b1", + "value": "b1", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 133, + "type": "bucket" + }, + "$order": 134, + "type": "metric" + }, + "center": [53.4375, -174.375], + "rectangle": [[50.625, -180], [50.625, -168.75], [56.25, -168.75], [56.25, -180]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-123.75373480841517, 39.26203776150942]}, + "properties": { + "geohash": "9n", + "value": 5, + "aggConfigResult": { + "key": 5, + "value": 5, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "9n", + "value": "9n", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 136, + "type": "bucket" + }, + "$order": 137, + "type": "metric" + }, + "center": [36.5625, -129.375], + "rectangle": [[33.75, -135], [33.75, -123.75], [39.375, -123.75], [39.375, -135]] + } + }, { + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [-145.7686112076044, 18.124444372951984]}, + "properties": { + "geohash": "8g", + "value": 2, + "aggConfigResult": { + "key": 2, + "value": 2, + "aggConfig": {"id": "1", "enabled": true, "type": "count", "schema": "metric", "params": {}}, + "$parent": { + "key": "8g", + "value": "8g", + "aggConfig": { + "id": "2", + "enabled": true, + "type": "geohash_grid", + "schema": "segment", + "params": {"field": "geo.coordinates", "autoPrecision": true, "useGeocentroid": true, "precision": 2} + }, + "$order": 139, + "type": "bucket" + }, + "$order": 140, + "type": "metric" + }, + "center": [19.6875, -140.625], + "rectangle": [[16.875, -146.25], [16.875, -135], [22.5, -135], [22.5, -146.25]] + } + }], + "properties": {"min": 2, "max": 1418, "zoom": 3, "center": [39.57182223734374, -109.51171875]} +}`; + +export const GeoHashSampleData = JSON.parse(sampleData); diff --git a/src/core_plugins/tile_map/public/__tests__/kibana_map.js b/src/core_plugins/tile_map/public/__tests__/kibana_map.js new file mode 100644 index 00000000000000..a3752e42c2d644 --- /dev/null +++ b/src/core_plugins/tile_map/public/__tests__/kibana_map.js @@ -0,0 +1,139 @@ +import expect from 'expect.js'; +import { KibanaMap } from 'ui/vis_maps/kibana_map'; + +describe('kibana_map tests', function () { + + let domNode; + let kibanaMap; + + function setupDOM() { + domNode = document.createElement('div'); + domNode.style.top = '0'; + domNode.style.left = '0'; + domNode.style.width = '512px'; + domNode.style.height = '512px'; + domNode.style.position = 'fixed'; + domNode.style['pointer-events'] = 'none'; + document.body.appendChild(domNode); + } + + function teardownDOM() { + domNode.innerHTML = ''; + document.body.removeChild(domNode); + } + + + describe('KibanaMap - basics', function () { + + beforeEach(async function () { + setupDOM(); + kibanaMap = new KibanaMap(domNode, { + minZoom: 1, + maxZoom: 10, + center: [0,0], + zoom: 0 + }); + }); + + afterEach(function () { + kibanaMap.destroy(); + teardownDOM(); + }); + + it('should instantiate with world in view', function () { + const bounds = kibanaMap.getBounds(); + expect(bounds.bottom_right.lon).to.equal(180); + expect(bounds.top_left.lon).to.equal(-180); + expect(kibanaMap.getCenter().lon).to.equal(0); + expect(kibanaMap.getCenter().lat).to.equal(0); + expect(kibanaMap.getZoomLevel()).to.equal(1); + }); + + it('should resize to fit container', function () { + + kibanaMap.setZoomLevel(2); + expect(kibanaMap.getCenter().lon).to.equal(0); + expect(kibanaMap.getCenter().lat).to.equal(0); + + domNode.style.width = '1024px'; + domNode.style.height = '1024px'; + kibanaMap.resize(); + + expect(kibanaMap.getCenter().lon).to.equal(0); + expect(kibanaMap.getCenter().lat).to.equal(0); + const bounds = kibanaMap.getBounds(); + expect(bounds.bottom_right.lon).to.equal(180); + expect(bounds.top_left.lon).to.equal(-180); + + }); + + }); + + + describe('KibanaMap - baseLayer', function () { + + beforeEach(async function () { + setupDOM(); + kibanaMap = new KibanaMap(domNode, { + minZoom: 1, + maxZoom: 10, + center: [0,0], + zoom: 0 + }); + }); + + afterEach(function () { + kibanaMap.destroy(); + teardownDOM(); + }); + + + it('TMS', async function () { + + const options = { + 'url': 'https://tiles-stage.elastic.co/v2/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana', + 'minZoom': 0, + 'maxZoom': 12, + 'attribution': '© [Elastic Tile Service](https://www.elastic.co/elastic-tile-service)' + }; + + + return new Promise(function (resolve) { + kibanaMap.on('baseLayer:loaded', () => { + resolve(); + }); + kibanaMap.setBaseLayer({ + baseLayerType: 'tms', + options: options + }); + }); + }); + + it('WMS - should handle empty settings', async function () { + + const invalidOptions = { + url: undefined, + version: undefined, + layers: undefined, + format: 'image/png', + transparent: true, + attribution: undefined, + styles: '', + minZoom: 1, + maxZoom: 18 + }; + + kibanaMap.setBaseLayer({ + baseLayerType: 'wms', + options: invalidOptions + }); + + expect(kibanaMap.getLeafletBaseLayer()).to.eql(null); + + + }); + + }); + + +}); diff --git a/src/core_plugins/tile_map/public/__tests__/tilemap_settings.js b/src/core_plugins/tile_map/public/__tests__/tilemap_settings.js new file mode 100644 index 00000000000000..e5d8982aac2e50 --- /dev/null +++ b/src/core_plugins/tile_map/public/__tests__/tilemap_settings.js @@ -0,0 +1,61 @@ +import expect from 'expect.js'; +import ngMock from 'ng_mock'; +import url from 'url'; + +describe('tilemaptest - TileMapSettingsTests-deprecated', function () { + let tilemapSettings; + let loadSettings; + + beforeEach(ngMock.module('kibana', ($provide) => { + $provide.decorator('tilemapsConfig', () => ({ + manifestServiceUrl: 'https://proxy-tiles.elastic.co/v1/manifest', + deprecated: { + isOverridden: true, + config: { + url: 'https://tiles.elastic.co/v1/default/{z}/{x}/{y}.png?my_app_name=kibana_tests', + options: { + minZoom: 1, + maxZoom: 10, + attribution: '© [Elastic Tile Service](https://www.elastic.co/elastic_tile_service)' + } + }, + } + })); + })); + + beforeEach(ngMock.inject(function ($injector, $rootScope) { + tilemapSettings = $injector.get('tilemapSettings'); + + loadSettings = () => { + tilemapSettings.loadSettings(); + $rootScope.$digest(); + }; + })); + + describe('getting settings', function () { + beforeEach(function () { + loadSettings(); + }); + + it('should get url', function () { + + const mapUrl = tilemapSettings.getUrl(); + expect(mapUrl).to.contain('{x}'); + expect(mapUrl).to.contain('{y}'); + expect(mapUrl).to.contain('{z}'); + + const urlObject = url.parse(mapUrl, true); + expect(urlObject.hostname).to.be('tiles.elastic.co'); + expect(urlObject.query).to.have.property('my_app_name', 'kibana_tests'); + + }); + + it('should get options', function () { + const options = tilemapSettings.getTMSOptions(); + expect(options).to.have.property('minZoom'); + expect(options).to.have.property('maxZoom'); + expect(options).to.have.property('attribution'); + }); + + }); +}); diff --git a/src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js b/src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js new file mode 100644 index 00000000000000..3f34c40e9c1edf --- /dev/null +++ b/src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js @@ -0,0 +1,140 @@ +import expect from 'expect.js'; +import ngMock from 'ng_mock'; +import url from 'url'; + +describe('tilemaptest - TileMapSettingsTests-mocked', function () { + let tilemapSettings; + let tilemapsConfig; + let loadSettings; + + beforeEach(ngMock.module('kibana', ($provide) => { + $provide.decorator('tilemapsConfig', () => ({ + manifestServiceUrl: 'http://foo.bar/manifest', + deprecated: { + isOverridden: false, + config: { + url: '', + options: { + minZoom: 1, + maxZoom: 10, + attribution: '© [Elastic Tile Service](https://www.elastic.co/elastic_tile_service)' + } + }, + } + })); + })); + + beforeEach(ngMock.inject(($injector, $httpBackend) => { + tilemapSettings = $injector.get('tilemapSettings'); + tilemapsConfig = $injector.get('tilemapsConfig'); + + loadSettings = (expectedUrl) => { + // body and headers copied from https://proxy-tiles.elastic.co/v1/manifest + const MANIFEST_BODY = `{ + "services":[ + { + "id":"road_map", + "url":"https://proxy-tiles.elastic.co/v1/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana", + "minZoom":0, + "maxZoom":12, + "attribution":"© [Elastic Tile Service](https://www.elastic.co/elastic-tile-service)" + } + ] + }`; + + const MANIFEST_HEADERS = { + 'access-control-allow-methods': 'GET, OPTIONS', + 'access-control-allow-origin': '*', + 'content-length': `${MANIFEST_BODY.length}`, + 'content-type': 'application/json; charset=utf-8', + date: (new Date()).toUTCString(), + server: 'tileprox/20170102101655-a02e54d', + status: '200', + }; + + $httpBackend + .expect('GET', expectedUrl ? expectedUrl : () => true) + .respond(MANIFEST_BODY, MANIFEST_HEADERS); + + tilemapSettings.loadSettings(); + + $httpBackend.flush(); + }; + })); + + afterEach(ngMock.inject($httpBackend => { + $httpBackend.verifyNoOutstandingRequest(); + $httpBackend.verifyNoOutstandingExpectation(); + })); + + describe('getting settings', function () { + beforeEach(() => { + loadSettings(); + }); + + + it('should get url', async function () { + + const mapUrl = tilemapSettings.getUrl(); + expect(mapUrl).to.contain('{x}'); + expect(mapUrl).to.contain('{y}'); + expect(mapUrl).to.contain('{z}'); + + const urlObject = url.parse(mapUrl, true); + expect(urlObject).to.have.property('hostname', 'proxy-tiles.elastic.co'); + expect(urlObject.query).to.have.property('my_app_name', 'kibana'); + expect(urlObject.query).to.have.property('elastic_tile_service_tos', 'agree'); + expect(urlObject.query).to.have.property('my_app_version'); + + }); + + it('should get options', async function () { + const options = tilemapSettings.getTMSOptions(); + expect(options).to.have.property('minZoom', 0); + expect(options).to.have.property('maxZoom', 12); + expect(options).to.have.property('attribution').contain('©'); // html entity for ©, ensures that attribution is escaped + }); + + }); + + describe('modify', function () { + function assertQuery(expected) { + const mapUrl = tilemapSettings.getUrl(); + const urlObject = url.parse(mapUrl, true); + Object.keys(expected).forEach(key => { + expect(urlObject.query).to.have.property(key, expected[key]); + }); + } + + it('accepts an object', () => { + tilemapSettings.addQueryParams({ foo: 'bar' }); + loadSettings(); + assertQuery({ foo: 'bar' }); + }); + + it('merged additions with previous values', () => { + // ensure that changes are always additive + tilemapSettings.addQueryParams({ foo: 'bar' }); + tilemapSettings.addQueryParams({ bar: 'stool' }); + loadSettings(); + assertQuery({ foo: 'bar', bar: 'stool' }); + }); + + it('overwrites conflicting previous values', () => { + // ensure that conflicts are overwritten + tilemapSettings.addQueryParams({ foo: 'bar' }); + tilemapSettings.addQueryParams({ bar: 'stool' }); + tilemapSettings.addQueryParams({ foo: 'tstool' }); + loadSettings(); + assertQuery({ foo: 'tstool', bar: 'stool' }); + }); + + it('merges query params into manifest request', () => { + tilemapSettings.addQueryParams({ foo: 'bar' }); + tilemapsConfig.manifestServiceUrl = 'http://test.com/manifest?v=1'; + loadSettings('http://test.com/manifest?v=1&my_app_version=1.2.3&foo=bar'); + }); + + }); + +}); diff --git a/src/core_plugins/kbn_vislib_vis_types/public/editors/tile_map.html b/src/core_plugins/tile_map/public/editors/tile_map.html similarity index 100% rename from src/core_plugins/kbn_vislib_vis_types/public/editors/tile_map.html rename to src/core_plugins/tile_map/public/editors/tile_map.html diff --git a/src/core_plugins/tile_map/public/geohash_layer.js b/src/core_plugins/tile_map/public/geohash_layer.js new file mode 100644 index 00000000000000..00dafadf0be6cf --- /dev/null +++ b/src/core_plugins/tile_map/public/geohash_layer.js @@ -0,0 +1,96 @@ +import L from 'leaflet'; +import _ from 'lodash'; + +import { KibanaMapLayer } from './kibana_map_layer'; +import { HeatmapMarkers } from './markers/heatmap'; +import { ScaledCirclesMarkers } from './markers/scaled_circles'; +import { ShadedCirclesMarkers } from './markers/shaded_circles'; +import { GeohashGridMarkers } from './markers/geohash_grid'; + +export class GeohashLayer extends KibanaMapLayer { + + constructor(featureCollection, options, zoom, kibanaMap) { + + super(); + + this._geohashGeoJson = featureCollection; + this._geohashOptions = options; + this._zoom = zoom; + this._kibanaMap = kibanaMap; + const geojson = L.geoJson(this._geohashGeoJson); + this._bounds = geojson.getBounds(); + this._createGeohashMarkers(); + } + + _createGeohashMarkers() { + const markerOptions = { + valueFormatter: this._geohashOptions.valueFormatter, + tooltipFormatter: this._geohashOptions.tooltipFormatter + }; + switch (this._geohashOptions.mapType) { + case 'Scaled Circle Markers': + this._geohashMarkers = new ScaledCirclesMarkers(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap); + break; + case 'Shaded Circle Markers': + this._geohashMarkers = new ShadedCirclesMarkers(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap); + break; + case 'Shaded Geohash Grid': + this._geohashMarkers = new GeohashGridMarkers(this._geohashGeoJson, markerOptions, this._zoom, this._kibanaMap); + break; + case 'Heatmap': + this._geohashMarkers = new HeatmapMarkers(this._geohashGeoJson, { + radius: parseFloat(this._geohashOptions.heatmap.heatRadius), + blur: parseFloat(this._geohashOptions.heatmap.heatBlur), + maxZoom: parseFloat(this._geohashOptions.heatmap.heatMaxZoom), + minOpacity: parseFloat(this._geohashOptions.heatmap.heatMinOpacity), + tooltipFormatter: this._geohashOptions.tooltipFormatter + }, this._zoom, this._kibanaMap); + break; + default: + throw new Error(`${this._geohashOptions.mapType} mapType not recognized`); + + } + + this._geohashMarkers.on('showTooltip', (event) => this.emit('showTooltip', event)); + this._geohashMarkers.on('hideTooltip', (event) => this.emit('hideTooltip', event)); + this._leafletLayer = this._geohashMarkers.getLeafletLayer(); + } + + appendLegendContents(jqueryDiv) { + return this._geohashMarkers.appendLegendContents(jqueryDiv); + } + + movePointer(...args) { + this._geohashMarkers.movePointer(...args); + } + + getBounds() { + return this._bounds; + } + + updateExtent() { + //this removal is required to trigger the bounds filter again + this._kibanaMap.removeLayer(this); + this._createGeohashMarkers(); + this._kibanaMap.addLayer(this); + } + + + isReusable(options) { + + if (_.isEqual(this._geohashOptions, options)) { + return true; + } + + if (this._geohashOptions.mapType !== options.mapType) { + return false; + } else if (this._geohashOptions.mapType === 'Heatmap' && !_.isEqual(this._geohashOptions.heatmap, options)) { + return false; + } else { + return true; + } + } +} + + + diff --git a/src/core_plugins/tile_map/public/images/icon-tilemap.svg b/src/core_plugins/tile_map/public/images/icon-tilemap.svg new file mode 100644 index 00000000000000..0888ce38f05a25 --- /dev/null +++ b/src/core_plugins/tile_map/public/images/icon-tilemap.svg @@ -0,0 +1,16 @@ + + + + icon-tilemap + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/src/core_plugins/tile_map/public/kibana_map.js b/src/core_plugins/tile_map/public/kibana_map.js new file mode 100644 index 00000000000000..da48554c5220ef --- /dev/null +++ b/src/core_plugins/tile_map/public/kibana_map.js @@ -0,0 +1,564 @@ +import { EventEmitter } from 'events'; +import L from 'leaflet'; +import $ from 'jquery'; +import _ from 'lodash'; +import { zoomToPrecision } from 'ui/utils/zoom_to_precision'; + +function makeFitControl(fitContainer, kibanaMap) { + + const FitControl = L.Control.extend({ + options: { + position: 'topleft' + }, + initialize: function (fitContainer, kibanaMap) { + this._fitContainer = fitContainer; + this._kibanaMap = kibanaMap; + this._leafletMap = null; + }, + onAdd: function (leafletMap) { + this._leafletMap = leafletMap; + $(this._fitContainer).html('') + .on('click', e => { + e.preventDefault(); + this._kibanaMap.fitToData(); + }); + + return this._fitContainer; + }, + onRemove: function () { + $(this._fitContainer).off('click'); + } + }); + + return new FitControl(fitContainer, kibanaMap); +} + +function makeLegedControl(container, kibanaMap, position) { + + const LegendControl = L.Control.extend({ + + options: { + position: 'topright' + }, + + initialize: function (container, kibanaMap, position) { + this._legendContainer = container; + this._kibanaMap = kibanaMap; + this.options.position = position; + + }, + + updateContents() { + this._legendContainer.empty(); + const $div = $('
').addClass('tilemap-legend'); + this._legendContainer.append($div); + const layers = this._kibanaMap.getLayers(); + layers.forEach((layer) =>layer.appendLegendContents($div)); + }, + + + onAdd: function () { + this._layerUpdateHandle = () => this.updateContents(); + this._kibanaMap.on('layers:update', this._layerUpdateHandle); + this.updateContents(); + return this._legendContainer.get(0); + }, + onRemove: function () { + this._kibanaMap.removeListener('layers:update', this._layerUpdateHandle); + this._legendContainer.empty(); + } + + }); + + return new LegendControl(container, kibanaMap, position); +} + +/** + * Collects map functionality required for Kibana. + * Serves as simple abstraction for leaflet as well. + */ +export class KibanaMap extends EventEmitter { + + constructor(containerNode, options) { + + super(); + this._containerNode = containerNode; + this._leafletBaseLayer = null; + this._baseLayerSettings = null; + this._baseLayerIsDesaturated = true; + + this._leafletDrawControl = null; + this._leafletFitControl = null; + this._leafletLegendControl = null; + this._legendPosition = 'topright'; + + this._layers = []; + this._listeners = []; + this._showTooltip = false; + + + const leafletOptions = { + minZoom: options.minZoom, + maxZoom: options.maxZoom, + center: options.center ? options.center : [0, 0], + zoom: options.zoom ? options.zoom : 0 + }; + + this._leafletMap = L.map(containerNode, leafletOptions); + const worldBounds = L.latLngBounds(L.latLng(-90, -180), L.latLng(90, 180)); + this._leafletMap.setMaxBounds(worldBounds); + + let previousZoom = this._leafletMap.getZoom(); + this._leafletMap.on('zoomend', () => { + if (previousZoom !== this._leafletMap.getZoom()) { + previousZoom = this._leafletMap.getZoom(); + this.emit('zoomchange'); + } + }); + this._leafletMap.on('zoomend', () => this.emit('zoomend')); + this._leafletMap.on('dragend', () => this.emit('dragend')); + this._leafletMap.on('dragend', e => this._layers.forEach(layer => layer.updateExtent('dragend', e))); + this._leafletMap.on('mousemove', e => this._layers.forEach(layer => layer.movePointer('mousemove', e))); + this._leafletMap.on('mouseout', e => this._layers.forEach(layer => layer.movePointer('mouseout', e))); + this._leafletMap.on('mousedown', e => this._layers.forEach(layer => layer.movePointer('mousedown', e))); + this._leafletMap.on('mouseup', e => this._layers.forEach(layer => layer.movePointer('mouseup', e))); + this._leafletMap.on('draw:created', event => { + const drawType = event.layerType; + if (drawType === 'rectangle') { + const bounds = event.layer.getBounds(); + + const southEast = bounds.getSouthEast(); + const northWest = bounds.getNorthWest(); + let southEastLng = southEast.lng; + if (southEastLng > 180) { + southEastLng -= 360; + } + let northWestLng = northWest.lng; + if (northWestLng < -180) { + northWestLng += 360; + } + + const southEastLat = southEast.lat; + const northWestLat = northWest.lat; + + //Bounds cannot be created unless they form a box with larger than 0 dimensions + //Invalid areas are rejected by ES. + if (southEastLat === northWestLat || southEastLng === northWestLng) { + return; + } + + this.emit('drawCreated:rectangle', { + bounds: { + bottom_right: { + lat: southEastLat, + lon: southEastLng + }, + top_left: { + lat: northWestLat, + lon: northWestLng + } + } + }); + } else if (drawType === 'polygon') { + const latLongs = event.layer.getLatLngs(); + this.emit('drawCreated:polygon', { + points: latLongs.map(leafletLatLng => { + return { + lat: leafletLatLng.lat, + lon: leafletLatLng.lng + }; + }) + }); + } + }); + + this.resize(); + + + } + + setShowTooltip(showTooltip) { + this._showTooltip = showTooltip; + } + + getLayers() { + return this._layers.slice(); + } + + + addLayer(kibanaLayer) { + + + this.emit('layers:invalidate'); + + const onshowTooltip = (event) => { + + if (!this._showTooltip) { + return; + } + + if (!this._popup) { + this._popup = L.popup({ autoPan: false }); + this._popup.setLatLng(event.position); + this._popup.setContent(event.content); + this._popup.openOn(this._leafletMap); + } else { + if (!this._popup.getLatLng().equals(event.position)) { + this._popup.setLatLng(event.position); + } + if (this._popup.getContent() !== event.content) { + this._popup.setContent(event.content); + } + } + + }; + + kibanaLayer.on('showTooltip', onshowTooltip); + this._listeners.push({ name: 'showTooltip', handle: onshowTooltip, layer: kibanaLayer }); + + const onHideTooltip = () => { + this._leafletMap.closePopup(); + this._popup = null; + }; + kibanaLayer.on('hideTooltip', onHideTooltip); + this._listeners.push({ name: 'hideTooltip', handle: onHideTooltip, layer: kibanaLayer }); + + + const onStyleChanged = () => { + if (this._leafletLegendControl) { + this._leafletLegendControl.updateContents(); + } + }; + kibanaLayer.on('styleChanged', onStyleChanged); + this._listeners.push({ name: 'styleChanged', handle: onStyleChanged, layer: kibanaLayer }); + + this._layers.push(kibanaLayer); + kibanaLayer.addToLeafletMap(this._leafletMap); + this.emit('layers:update'); + } + + removeLayer(layer) { + const index = this._layers.indexOf(layer); + if (index >= 0) { + this._layers.splice(index, 1); + layer.removeFromLeafletMap(this._leafletMap); + } + this._listeners.forEach(listener => { + if (listener.layer === layer) { + listener.layer.removeListener(listener.name, listener.handle); + } + }); + } + + destroy() { + if (this._leafletFitControl) { + this._leafletMap.removeControl(this._leafletFitControl); + } + if (this._leafletDrawControl) { + this._leafletMap.removeControl(this._leafletDrawControl); + } + if (this._leafletLegendControl) { + this._leafletMap.removeControl(this._leafletLegendControl); + } + this.setBaseLayer(null); + let layer; + while (this._layers.length) { + layer = this._layers.pop(); + layer.removeFromLeafletMap(this._leafletMap); + } + this._leafletMap.remove(); + this._containerNode.innerHTML = ''; + this._listeners.forEach(listener => listener.layer.removeListener(listener.name, listener.handle)); + } + + getCenter() { + const center = this._leafletMap.getCenter(); + return { lon: center.lng, lat: center.lat }; + } + + setCenter(latitude, longitude) { + const latLong = L.latLng(latitude, longitude); + if (latLong.equals && !latLong.equals(this._leafletMap.getCenter())) { + this._leafletMap.setView(latLong); + } + } + + setZoomLevel(zoomLevel) { + if (this._leafletMap.getZoom() !== zoomLevel) { + this._leafletMap.setZoom(zoomLevel); + } + } + + getZoomLevel() { + return this._leafletMap.getZoom(); + } + + getMaxZoomLevel() { + return this._leafletMap.getMaxZoom(); + } + + getAutoPrecision() { + return zoomToPrecision(this._leafletMap.getZoom(), 12, this._leafletMap.getMaxZoom()); + } + + getBounds() { + + const bounds = this._leafletMap.getBounds(); + if (!bounds) { + return null; + } + + const southEast = bounds.getSouthEast(); + const northWest = bounds.getNorthWest(); + let southEastLng = southEast.lng; + if (southEastLng > 180) { + southEastLng -= 360; + } + let northWestLng = northWest.lng; + if (northWestLng < -180) { + northWestLng += 360; + } + + const southEastLat = southEast.lat; + const northWestLat = northWest.lat; + + //Bounds cannot be created unless they form a box with larger than 0 dimensions + //Invalid areas are rejected by ES. + if (southEastLat === northWestLat || southEastLng === northWestLng) { + return; + } + + return { + bottom_right: { + lat: southEastLat, + lon: southEastLng + }, + top_left: { + lat: northWestLat, + lon: northWestLng + } + }; + } + + + setDesaturateBaseLayer(isDesaturated) { + if (isDesaturated === this._baseLayerIsDesaturated) { + return; + } + this._baseLayerIsDesaturated = isDesaturated; + this._updateDesaturation(); + this._leafletBaseLayer.redraw(); + } + + addDrawControl() { + const shapeOptions = { + shapeOptions: { + stroke: false, + color: '#000' + } + }; + const drawOptions = { + draw: { + polyline: false, + marker: false, + circle: false, + rectangle: shapeOptions, + polygon: shapeOptions + } + }; + this._leafletDrawControl = new L.Control.Draw(drawOptions); + this._leafletMap.addControl(this._leafletDrawControl); + } + + addFitControl() { + + if (this._leafletFitControl || !this._leafletMap) { + return; + } + + const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit'); + this._leafletFitControl = makeFitControl(fitContainer, this); + this._leafletMap.addControl(this._leafletFitControl); + } + + addLegendControl() { + if (this._leafletLegendControl || !this._leafletMap) { + return; + } + this._updateLegend(); + } + + setLegendPosition(position) { + if (this._legendPosition === position) { + if (!this._leafletLegendControl) { + this._updateLegend(); + } + } else { + this._legendPosition = position; + this._updateLegend(); + } + + + } + + _updateLegend() { + if (this._leafletLegendControl) { + this._leafletMap.removeControl(this._leafletLegendControl); + } + const $wrapper = $('
').addClass('tilemap-legend-wrapper'); + this._leafletLegendControl = makeLegedControl($wrapper, this, this._legendPosition); + this._leafletMap.addControl(this._leafletLegendControl); + } + + resize() { + this._leafletMap.invalidateSize(); + this._updateExtent(); + } + + + getLeafletBaseLayer() { + return this._leafletBaseLayer; + } + + setBaseLayer(settings) { + + if (_.isEqual(settings, this._baseLayerSettings)) { + return; + } + + this._baseLayerSettings = settings; + if (settings === null) { + if (this._leafletBaseLayer && this._leafletMap) { + this._leafletMap.removeLayer(this._leafletBaseLayer); + this._leafletBaseLayer = null; + } + return; + } + + if (this._leafletBaseLayer) { + this._leafletMap.removeLayer(this._leafletBaseLayer); + this._leafletBaseLayer = null; + } + + let baseLayer; + if (settings.baseLayerType === 'wms') { + baseLayer = this._getWMSBaseLayer(settings.options); + } else if (settings.baseLayerType === 'tms') { + baseLayer = this._getTMSBaseLayer((settings.options)); + } + + if (baseLayer) { + baseLayer.on('tileload', () => this._updateDesaturation()); + baseLayer.on('load', () => { + this.emit('baseLayer:loaded'); + }); + baseLayer.on('loading', () => { + this.emit('baseLayer:loading'); + }); + + this._leafletBaseLayer = baseLayer; + this._leafletBaseLayer.addTo(this._leafletMap); + this._leafletBaseLayer.bringToBack(); + if (settings.options.minZoom > this._leafletMap.getZoom()) { + this._leafletMap.setZoom(settings.options.minZoom); + } + this.resize(); + } + + } + + isInside(bucketRectBounds) { + const mapBounds = this._leafletMap.getBounds(); + return mapBounds.intersects(bucketRectBounds); + } + + fitToData() { + + if (!this._leafletMap) { + return; + } + + let bounds = null; + this._layers.forEach(layer => { + const b = layer.getBounds(); + if (bounds) { + bounds.extend(b); + } else { + bounds = b; + } + }); + + if (bounds && bounds.isValid()) { + this._leafletMap.fitBounds(bounds); + } + } + + _getTMSBaseLayer(options) { + return L.tileLayer(options.url, { + minZoom: options.minZoom, + maxZoom: options.maxZoom, + subdomains: options.subdomains || [], + attribution: options.attribution + }); + } + + _getWMSBaseLayer(options) { + const wmsOptions = { + attribution: options.attribution || '', + format: options.format || '', + layers: options.layers || '', + minZoom: options.minZoom, + maxZoom: options.maxZoom, + styles: options.styles || '', + transparent: options.transparent, + version: options.version || '1.3.0' + }; + + return (typeof options.url === 'string' && options.url.length) ? L.tileLayer.wms(options.url, wmsOptions) : null; + } + + _updateExtent() { + this._layers.forEach(layer => layer.updateExtent()); + } + + _updateDesaturation() { + const tiles = $('img.leaflet-tile-loaded'); + if (this._baseLayerIsDesaturated) { + tiles.removeClass('filters-off'); + } else if (!this._baseLayerIsDesaturated) { + tiles.addClass('filters-off'); + } + } + + persistUiStateForVisualization(visualization) { + function persistMapStateInUiState() { + const uiState = visualization.getUiState(); + const centerFromUIState = uiState.get('mapCenter'); + const zoomFromUiState = parseInt(uiState.get('mapZoom')); + if (isNaN(zoomFromUiState) || this.getZoomLevel() !== zoomFromUiState) { + uiState.set('mapZoom', this.getZoomLevel()); + } + const centerFromMap = this.getCenter(); + if (!centerFromUIState || centerFromMap.lon !== centerFromUIState[1] || centerFromMap.lat !== centerFromUIState[0]) { + uiState.set('mapCenter', [centerFromMap.lat, centerFromMap.lon]); + } + } + + this.on('dragend', persistMapStateInUiState); + this.on('zoomend', persistMapStateInUiState); + } + + useUiStateFromVisualization(visualization) { + const uiState = visualization.getUiState(); + const zoomFromUiState = parseInt(uiState.get('mapZoom')); + const centerFromUIState = uiState.get('mapCenter'); + if (!isNaN(zoomFromUiState)) { + this.setZoomLevel(zoomFromUiState); + } + if (centerFromUIState) { + this.setCenter(centerFromUIState[0], centerFromUIState[1]); + } + } +} + + diff --git a/src/core_plugins/tile_map/public/kibana_map_layer.js b/src/core_plugins/tile_map/public/kibana_map_layer.js new file mode 100644 index 00000000000000..19a957cda49604 --- /dev/null +++ b/src/core_plugins/tile_map/public/kibana_map_layer.js @@ -0,0 +1,38 @@ +import { EventEmitter } from 'events'; + + +export class KibanaMapLayer extends EventEmitter { + constructor() { + super(); + this._leafletLayer = null; + } + + getBounds() { + return this._leafletLayer.getBounds(); + } + + addToLeafletMap(leafletMap) { + this._leafletLayer.addTo(leafletMap); + } + + removeFromLeafletMap(leafletMap) { + leafletMap.removeLayer(this._leafletLayer); + } + + appendLegendContents() { + } + + updateExtent() { + } + + movePointer() { + } +} + + + + + + + + diff --git a/src/core_plugins/tile_map/public/lib/tilemap_settings.js b/src/core_plugins/tile_map/public/lib/tilemap_settings.js new file mode 100644 index 00000000000000..54571ecc0d3a5f --- /dev/null +++ b/src/core_plugins/tile_map/public/lib/tilemap_settings.js @@ -0,0 +1,208 @@ +import { uiModules } from 'ui/modules'; +import _ from 'lodash'; +import marked from 'marked'; +import { modifyUrl } from 'ui/url'; + +marked.setOptions({ + gfm: true, // Github-flavored markdown + sanitize: true // Sanitize HTML tags +}); + +uiModules.get('kibana') + .service('tilemapSettings', function ($http, tilemapsConfig, $sanitize, kbnVersion) { + const attributionFromConfig = $sanitize(marked(tilemapsConfig.deprecated.config.options.attribution || '')); + const optionsFromConfig = _.assign({}, tilemapsConfig.deprecated.config.options, { attribution: attributionFromConfig }); + const extendUrl = (url, props) => ( + modifyUrl(url, parsed => _.merge(parsed, props)) + ); + + /** + * Unescape a url template that was escaped by encodeURI() so leaflet + * will be able to correctly locate the varables in the template + * @param {String} url + * @return {String} + */ + const unescapeTemplateVars = url => { + const ENCODED_TEMPLATE_VARS_RE = /%7B(\w+?)%7D/g; + return url.replace(ENCODED_TEMPLATE_VARS_RE, (total, varName) => `{${varName}}`); + }; + + class TilemapSettings { + + constructor() { + + this._queryParams = { + my_app_version: kbnVersion + }; + this._error = null; + + //initialize settings with the default of the configuration + this._url = tilemapsConfig.deprecated.config.url; + this._tmsOptions = optionsFromConfig; + + this._invalidateSettings(); + + } + + + _invalidateSettings() { + + this._settingsInitialized = false; + this._loadSettings = _.once(async() => { + + if (tilemapsConfig.deprecated.isOverridden) {//if settings are overridden, we will use those. + this._settingsInitialized = true; + } + + if (this._settingsInitialized) { + return true; + } + + return this._getTileServiceManifest(tilemapsConfig.manifestServiceUrl, this._queryParams) + .then(response => { + const service = _.get(response, 'data.services[0]'); + if (!service) { + throw new Error('Manifest response does not include sufficient service data.'); + } + + this._error = null; + this._tmsOptions = { + attribution: $sanitize(marked(service.attribution)), + minZoom: service.minZoom, + maxZoom: service.maxZoom, + subdomains: service.subdomains || [] + }; + + this._url = unescapeTemplateVars(extendUrl(service.url, { + query: this._queryParams + })); + + this._settingsInitialized = true; + }) + .catch(e => { + this._settingsInitialized = true; + + if (!e) { + e = new Error('Unkown error'); + } + + if (!(e instanceof Error)) { + e = new Error(e.data || `status ${e.statusText || e.status}`); + } + + this._error = new Error(`Could not retrieve manifest from the tile service: ${e.message}`); + }) + .then(() => { + return true; + }); + }); + } + + /** + * Must be called before getUrl/getTMSOptions/getMapOptions can be called. + */ + loadSettings() { + return this._loadSettings(); + } + + /** + * Add optional query-parameters for the request. + * These are only applied when requesting dfrom the manifest. + * + * @param additionalQueryParams + */ + addQueryParams(additionalQueryParams) { + + //check if there are any changes in the settings. + let changes = false; + for (const key in additionalQueryParams) { + if (additionalQueryParams.hasOwnProperty(key)) { + if (additionalQueryParams[key] !== this._queryParams[key]) { + changes = true; + break; + } + } + } + + if (changes) { + this._queryParams = _.assign({}, this._queryParams, additionalQueryParams); + this._invalidateSettings(); + } + + } + + /** + * Get the url of the default TMS + * @return {string} + */ + getUrl() { + if (!this._settingsInitialized) { + throw new Error('Cannot retrieve url before calling .loadSettings first'); + } + return this._url; + } + + /** + * Get the options of the default TMS + * @return {{}} + */ + getTMSOptions() { + if (!this._settingsInitialized) { + throw new Error('Cannot retrieve options before calling .loadSettings first'); + } + return this._tmsOptions; + } + + + /** + * @return {{maxZoom: (*|number), minZoom: (*|number)}} + */ + getMinMaxZoom(isWMSEnabled) { + if (isWMSEnabled) { + return { + minZoom: 0, + maxZoom: 18 + }; + } + + //Otherwise, we use the settings from the yml. + //note that it is no longer possible to only override the zoom-settings, since all options are read from the manifest + //by default. + //For a custom configuration, users will need to override tilemap.url as well. + return { + minZoom: this._tmsOptions.minZoom, + maxZoom: this._tmsOptions.maxZoom + }; + + } + + isInitialized() { + return this._settingsInitialized; + } + + + /** + * Checks if there was an error during initialization of the parameters + */ + hasError() { + return this._error !== null; + } + + getError() { + return this._error; + } + + /** + * Make this a method to allow for overrides by test code + */ + _getTileServiceManifest(manifestUrl) { + return $http({ + url: extendUrl(manifestUrl, { query: this._queryParams }), + method: 'GET' + }); + } + + } + + return new TilemapSettings(); + }); diff --git a/src/core_plugins/tile_map/public/maps_renderbot.js b/src/core_plugins/tile_map/public/maps_renderbot.js new file mode 100644 index 00000000000000..e8dd48a9aaec93 --- /dev/null +++ b/src/core_plugins/tile_map/public/maps_renderbot.js @@ -0,0 +1,258 @@ +import $ from 'jquery'; +import _ from 'lodash'; +import { FilterBarPushFilterProvider } from 'ui/filter_bar/push_filter'; +import { KibanaMap } from './kibana_map'; +import { GeohashLayer } from './geohash_layer'; +import './lib/tilemap_settings'; +import './styles/_tilemap.less'; + + +module.exports = function MapsRenderbotFactory(Private, $injector, tilemapSettings, Notifier, courier, getAppState) { + + const notify = new Notifier({ location: 'Tilemap' }); + + class MapsRenderbot { + + constructor(vis, $el, uiState) { + this.vis = vis; + this.$el = $el; + this.uiState = uiState; + this._geohashLayer = null; + this._kibanaMap = null; + this._$container = $el; + this._kibanaMapReady = this._makeKibanaMap($el); + + this._baseLayerDirty = true; + this._dataDirty = true; + this._paramsDirty = true; + } + + resize() { + if (this._kibanaMap) { + this._kibanaMap.resize(); + } + } + + async _makeKibanaMap() { + + if (!tilemapSettings.isInitialized()) { + await tilemapSettings.loadSettings(); + } + + if (tilemapSettings.getError()) { + //Still allow the visualization to be built, but show a toast that there was a problem retrieving map settings + //Even though the basemap will not display, the user will at least still see the overlay data + notify.warning(tilemapSettings.getError().message); + } + + if (this._kibanaMap) { + this._kibanaMap.destroy(); + } + const containerElement = $(this._$container)[0]; + const options = _.clone(this._getMinMaxZoom()); + const uiState = this.vis.getUiState(); + const zoomFromUiState = parseInt(uiState.get('mapZoom')); + const centerFromUIState = uiState.get('mapCenter'); + options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.type.visConfig.defaults.mapZoom; + options.center = centerFromUIState ? centerFromUIState : this.vis.type.visConfig.defaults.mapCenter; + + this._kibanaMap = new KibanaMap(containerElement, options); + this._kibanaMap.addDrawControl(); + this._kibanaMap.addFitControl(); + this._kibanaMap.addLegendControl(); + this._kibanaMap.persistUiStateForVisualization(this.vis); + + let previousPrecision = this._kibanaMap.getAutoPrecision(); + let precisionChange = false; + this._kibanaMap.on('zoomchange', () => { + precisionChange = (previousPrecision !== this._kibanaMap.getAutoPrecision()); + previousPrecision = this._kibanaMap.getAutoPrecision(); + }); + this._kibanaMap.on('zoomend', () => { + + const isAutoPrecision = _.get(this._chartData, 'geohashGridAgg.params.autoPrecision', true); + if (!isAutoPrecision) { + return; + } + + this._dataDirty = true; + if (precisionChange) { + courier.fetch(); + } else { + this._recreateGeohashLayer(); + this._dataDirty = false; + this._doRenderComplete(); + } + }); + + + this._kibanaMap.on('drawCreated:rectangle', event => { + addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds); + }); + this._kibanaMap.on('drawCreated:polygon', event => { + addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_polygon', { points: event.points }); + }); + this._kibanaMap.on('baseLayer:loaded', () => { + this._baseLayerDirty = false; + this._doRenderComplete(); + }); + this._kibanaMap.on('baseLayer:loading', () => { + this._baseLayerDirty = true; + }); + } + + _getMinMaxZoom() { + const mapParams = this._getMapsParams(); + return tilemapSettings.getMinMaxZoom(mapParams.wms.enabled); + } + + _recreateGeohashLayer() { + if (this._geohashLayer) { + this._kibanaMap.removeLayer(this._geohashLayer); + } + if (!this._geohashGeoJson) { + return; + } + const geohashOptions = this._getGeohashOptions(); + this._geohashLayer = new GeohashLayer(this._chartData.geoJson, geohashOptions, this._kibanaMap.getZoomLevel(), this._kibanaMap); + this._kibanaMap.addLayer(this._geohashLayer); + } + + + /** + * called on data change + * @param esResponse + */ + async render(esResponse) { + this._dataDirty = true; + this._kibanaMapReady.then(() => { + this._chartData = esResponse; + this._geohashGeoJson = this._chartData.geoJson; + this._recreateGeohashLayer(); + this._kibanaMap.useUiStateFromVisualization(this.vis); + this._kibanaMap.resize(); + this._dataDirty = false; + this._doRenderComplete(); + }); + } + + destroy() { + if (this._kibanaMap) { + this._kibanaMap.destroy(); + } + } + + /** + * called on options change (vis.params change) + */ + updateParams() { + + this._paramsDirty = true; + this._kibanaMapReady.then(async() => { + const mapParams = this._getMapsParams(); + const { minZoom, maxZoom } = this._getMinMaxZoom(); + + if (mapParams.wms.enabled) { + + if (maxZoom > this._kibanaMap.getMaxZoomLevel()) { + this._geohashLayer = null; + this._kibanaMapReady = this._makeKibanaMap(); + } + + this._kibanaMap.setBaseLayer({ + baseLayerType: 'wms', + options: { + minZoom: minZoom, + maxZoom: maxZoom, + url: mapParams.wms.url, + ...mapParams.wms.options + } + }); + } else { + + if (maxZoom < this._kibanaMap.getMaxZoomLevel()) { + this._geohashLayer = null; + this._kibanaMapReady = this._makeKibanaMap(); + this._kibanaMap.setZoomLevel(maxZoom); + } + + if (!tilemapSettings.hasError()) { + const url = tilemapSettings.getUrl(); + const options = tilemapSettings.getTMSOptions(); + this._kibanaMap.setBaseLayer({ + baseLayerType: 'tms', + options: { url, ...options } + }); + } + } + const geohashOptions = this._getGeohashOptions(); + if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) { + this._recreateGeohashLayer(); + } + + this._kibanaMap.setDesaturateBaseLayer(mapParams.isDesaturated); + this._kibanaMap.setShowTooltip(mapParams.addTooltip); + this._kibanaMap.setLegendPosition(mapParams.legendPosition); + + this._kibanaMap.useUiStateFromVisualization(this.vis); + this._kibanaMap.resize(); + this._paramsDirty = false; + this._doRenderComplete(); + }); + } + + _getMapsParams() { + return _.assign( + {}, + this.vis.type.visConfig.defaults, + { + type: this.vis.type.name, + hasTimeField: this.vis.indexPattern && this.vis.indexPattern.hasTimeField()// Add attribute which determines whether an index is time based or not. + }, + this.vis.params + ); + } + + _getGeohashOptions() { + const newParams = this._getMapsParams(); + return { + valueFormatter: this._chartData ? this._chartData.valueFormatter : null, + tooltipFormatter: this._chartData ? this._chartData.tooltipFormatter : null, + mapType: newParams.mapType, + heatmap: { + heatBlur: newParams.heatBlur, + heatMaxZoom: newParams.heatMaxZoom, + heatMinOpacity: newParams.heatMinOpacity, + heatRadius: newParams.heatRadius + } + }; + } + + _doRenderComplete() { + if (this._paramsDirty || this._dataDirty || this._baseLayerDirty) { + return false; + } + this.$el.trigger('renderComplete'); + return true; + } + + } + + function addSpatialFilter(agg, filterName, filterData) { + if (!agg) { + return; + } + + const indexPatternName = agg.vis.indexPattern.id; + const field = agg.fieldName(); + const filter = {}; + filter[filterName] = { ignore_unmapped: true }; + filter[filterName][field] = filterData; + + const putFilter = Private(FilterBarPushFilterProvider)(getAppState()); + return putFilter(filter, false, indexPatternName); + } + + + return MapsRenderbot; +}; diff --git a/src/core_plugins/tile_map/public/maps_visualization.js b/src/core_plugins/tile_map/public/maps_visualization.js new file mode 100644 index 00000000000000..0e2f52e6f2c32b --- /dev/null +++ b/src/core_plugins/tile_map/public/maps_visualization.js @@ -0,0 +1,33 @@ +import 'ui/vislib'; +import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; +import MapsVisTypeMapsRenderbotProvider from './maps_renderbot'; +import $ from 'jquery'; + +export function MapsVisualizationProvider(Private) { + + const MapsRenderbot = Private(MapsVisTypeMapsRenderbotProvider); + + class MapsVisController { + constructor(el, vis) { + this.el = $(el); + this._vis = vis; + this.renderbot = new MapsRenderbot(this._vis, this.el, vis.getUiState()); + } + + async render(esResponse) { + //todo: should notify of render-completeness, which it isn't doing correctly now + this.renderbot.render(esResponse); + } + + resize() { + this.renderbot.resize(); + } + + destroy() { + this.renderbot.destroy(); + } + } + + return MapsVisController; +} + diff --git a/src/core_plugins/tile_map/public/markers/geohash_grid.js b/src/core_plugins/tile_map/public/markers/geohash_grid.js new file mode 100644 index 00000000000000..6666ad842f6f27 --- /dev/null +++ b/src/core_plugins/tile_map/public/markers/geohash_grid.js @@ -0,0 +1,17 @@ +import L from 'leaflet'; +import { ScaledCirclesMarkers } from './scaled_circles'; + +export class GeohashGridMarkers extends ScaledCirclesMarkers { + getMarkerFunction() { + return function (feature) { + const geohashRect = feature.properties.rectangle; + // get bounds from northEast[3] and southWest[1] + // corners in geohash rectangle + const corners = [ + [geohashRect[3][0], geohashRect[3][1]], + [geohashRect[1][0], geohashRect[1][1]] + ]; + return L.rectangle(corners); + }; + } +} diff --git a/src/core_plugins/tile_map/public/markers/heatmap.js b/src/core_plugins/tile_map/public/markers/heatmap.js new file mode 100644 index 00000000000000..ced8983550931b --- /dev/null +++ b/src/core_plugins/tile_map/public/markers/heatmap.js @@ -0,0 +1,189 @@ +import L from 'leaflet'; +import _ from 'lodash'; +import d3 from 'd3'; +import { EventEmitter } from 'events'; + +/** + * Map overlay: canvas layer with leaflet.heat plugin + * + * @param map {Leaflet Object} + * @param geoJson {geoJson Object} + * @param params {Object} + */ +export class HeatmapMarkers extends EventEmitter { + + constructor(featureCollection, options, zoom) { + + super(); + this._geojsonFeatureCollection = featureCollection; + const points = dataToHeatArray(featureCollection); + this._leafletLayer = L.heatLayer(points, options); + this._tooltipFormatter = options.tooltipFormatter; + this._zoom = zoom; + this._disableTooltips = false; + this._getLatLng = _.memoize(function (feature) { + return L.latLng( + feature.geometry.coordinates[1], + feature.geometry.coordinates[0] + ); + }, function (feature) { + // turn coords into a string for the memoize cache + return [feature.geometry.coordinates[1], feature.geometry.coordinates[0]].join(','); + }); + + this._currentFeature = null; + this._addTooltips(); + } + + getBounds() { + return this._leafletLayer.getBounds(); + } + + getLeafletLayer() { + return this._leafletLayer; + } + + appendLegendContents() { + } + + + movePointer(type, event) { + if (type === 'mousemove') { + this._deboundsMoveMoveLocation(event); + } else if (type === 'mouseout') { + this.emit('hideTooltip'); + } else if (type === 'mousedown') { + this._disableTooltips = true; + this.emit('hideTooltip'); + } else if (type === 'mouseup') { + this._disableTooltips = false; + } + } + + + _addTooltips() { + + const mouseMoveLocation = (e) => { + + + + if (!this._geojsonFeatureCollection.features.length || this._disableTooltips) { + this.emit('hideTooltip'); + return; + } + + const feature = this._nearestFeature(e.latlng); + if (this._tooltipProximity(e.latlng, feature)) { + const content = this._tooltipFormatter(feature); + if (!content) { + return; + } + this.emit('showTooltip', { + content: content, + position: e.latlng + }); + } else { this.emit('hideTooltip'); + } + }; + + this._deboundsMoveMoveLocation = _.debounce(mouseMoveLocation.bind(this), 15, { + 'leading': true, + 'trailing': false + }); + } + + /** + * Finds nearest feature in mapData to event latlng + * + * @method _nearestFeature + * @param latLng {Leaflet latLng} + * @return nearestPoint {Leaflet latLng} + */ + _nearestFeature(latLng) { + const self = this; + let nearest; + + if (latLng.lng < -180 || latLng.lng > 180) { + return; + } + + _.reduce(this._geojsonFeatureCollection.features, function (distance, feature) { + const featureLatLng = self._getLatLng(feature); + const dist = latLng.distanceTo(featureLatLng); + + if (dist < distance) { + nearest = feature; + return dist; + } + + return distance; + }, Infinity); + + return nearest; + } + + /** + * display tooltip if feature is close enough to event latlng + * + * @method _tooltipProximity + * @param latlng {Leaflet latLng Object} + * @param feature {geoJson Object} + * @return {Boolean} + */ + _tooltipProximity(latlng, feature) { + if (!feature) return; + + let showTip = false; + const featureLatLng = this._getLatLng(feature); + + // zoomScale takes map zoom and returns proximity value for tooltip display + // domain (input values) is map zoom (min 1 and max 18) + // range (output values) is distance in meters + // used to compare proximity of event latlng to feature latlng + const zoomScale = d3.scale.linear() + .domain([1, 4, 7, 10, 13, 16, 18]) + .range([1000000, 300000, 100000, 15000, 2000, 150, 50]); + + const proximity = zoomScale(this._zoom); + const distance = latlng.distanceTo(featureLatLng); + + // maxLngDif is max difference in longitudes + // to prevent feature tooltip from appearing 360° + // away from event latlng + const maxLngDif = 40; + const lngDif = Math.abs(latlng.lng - featureLatLng.lng); + + if (distance < proximity && lngDif < maxLngDif) { + showTip = true; + } + + d3.scale.pow().exponent(0.2) + .domain([1, 18]) + .range([1500000, 50]); + return showTip; + } + +} + + + +/** + * returns normalized data for heat map intensity + * + * @method dataToHeatArray + * @param featureCollection {Array} + * @return {Array} + */ +function dataToHeatArray(featureCollection) { + const max = _.get(featureCollection, 'properties.max'); + + return featureCollection.features.map((feature) => { + const lat = feature.geometry.coordinates[1]; + const lng = feature.geometry.coordinates[0]; + // show bucket value normalized to max value + const heatIntensity = feature.properties.value / max; + + return [lat, lng, heatIntensity]; + }); +} + diff --git a/src/core_plugins/tile_map/public/markers/scaled_circles.js b/src/core_plugins/tile_map/public/markers/scaled_circles.js new file mode 100644 index 00000000000000..577977769b7c10 --- /dev/null +++ b/src/core_plugins/tile_map/public/markers/scaled_circles.js @@ -0,0 +1,231 @@ +import L from 'leaflet'; +import _ from 'lodash'; +import d3 from 'd3'; +import $ from 'jquery'; +import { EventEmitter } from 'events'; + +export class ScaledCirclesMarkers extends EventEmitter { + + constructor(featureCollection, options, targetZoom, kibanaMap) { + super(); + this._geohashGeoJson = featureCollection; + this._zoom = targetZoom; + + this._valueFormatter = options.valueFormatter; + this._tooltipFormatter = options.tooltipFormatter; + this._map = options.map; + + this._legendColors = null; + this._legendQuantizer = null; + + this._popups = []; + this._leafletLayer = L.geoJson(null, { + pointToLayer: this.getMarkerFunction(), + style: this.getStyleFunction(), + onEachFeature: (feature, layer) => { + this._bindPopup(feature, layer); + }, + filter: (feature) => { + const bucketRectBounds = _.get(feature, 'properties.rectangle'); + return kibanaMap.isInside(bucketRectBounds); + } + }); + this._leafletLayer.addData(this._geohashGeoJson); + } + + getLeafletLayer() { + return this._leafletLayer; + } + + + getStyleFunction() { + const min = _.get(this._geohashGeoJson, 'properties.min', 0); + const max = _.get(this._geohashGeoJson, 'properties.max', 1); + + const quantizeDomain = (min !== max) ? [min, max] : d3.scale.quantize().domain(); + this._legendColors = makeCircleMarkerLegendColors(min, max); + this._legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(this._legendColors); + + return makeStyleFunction(min, max, this._legendColors, quantizeDomain); + } + + + movePointer() { + } + + getLabel() { + if (this._popups.length) { + return this._popups[0].feature.properties.aggConfigResult.aggConfig.makeLabel(); + } + return ''; + } + + + appendLegendContents(jqueryDiv) { + + if (!this._legendColors || !this._legendQuantizer) { + return; + } + + const titleText = this.getLabel(); + const $title = $('
').addClass('tilemap-legend-title').text(titleText); + jqueryDiv.append($title); + + this._legendColors.forEach((color) => { + const labelText = this._legendQuantizer + .invertExtent(color) + .map(this._valueFormatter) + .join(' – '); + + const label = $('
'); + const icon = $('').css({ + background: color, + 'border-color': makeColorDarker(color) + }); + + const text = $('').text(labelText); + label.append(icon); + label.append(text); + + jqueryDiv.append(label); + }); + + } + + + /** + * Binds popup and events to each feature on map + * + * @method bindPopup + * @param feature {Object} + * @param layer {Object} + * return {undefined} + */ + _bindPopup(feature, layer) { + const popup = layer.on({ + mouseover: (e) => { + const layer = e.target; + // bring layer to front if not older browser + if (!L.Browser.ie && !L.Browser.opera) { + layer.bringToFront(); + } + this._showTooltip(feature); + }, + mouseout: () => { + this.emit('hideTooltip'); + } + }); + + this._popups.push(popup); + } + + /** + * Checks if event latlng is within bounds of mapData + * features and shows tooltip for that feature + * + * @method _showTooltip + * @param feature {LeafletFeature} + * @param latLng? {Leaflet latLng} + * @return undefined + */ + _showTooltip(feature, latLng) { + + const lat = _.get(feature, 'geometry.coordinates.1'); + const lng = _.get(feature, 'geometry.coordinates.0'); + latLng = latLng || L.latLng(lat, lng); + + const content = this._tooltipFormatter(feature); + if (!content) { + return; + } + + this.emit('showTooltip', { + content: content, + position: latLng + }); + } + + getMarkerFunction() { + const scaleFactor = 0.6; + return (feature, latlng) => { + const value = feature.properties.value; + const scaledRadius = this._radiusScale(value) * scaleFactor; + return L.circleMarker(latlng).setRadius(scaledRadius); + }; + } + + /** + * radiusScale returns a number for scaled circle markers + * for relative sizing of markers + * + * @method _radiusScale + * @param value {Number} + * @return {Number} + */ + _radiusScale(value) { + + //magic numbers + const precisionBiasBase = 5; + const precisionBiasNumerator = 200; + + const precision = _.max(this._geohashGeoJson.features.map((feature) => { + return String(feature.properties.geohash).length; + })); + + const pct = Math.abs(value) / Math.abs(this._geohashGeoJson.properties.max); + const zoomRadius = 0.5 * Math.pow(2, this._zoom); + const precisionScale = precisionBiasNumerator / Math.pow(precisionBiasBase, precision); + + // square root value percentage + return Math.pow(pct, 0.5) * zoomRadius * precisionScale; + } + + getBounds() { + return this._leafletLayer.getBounds(); + } + +} + + +/** + * d3 quantize scale returns a hex color, used for marker fill color + * + * @method quantizeLegendColors + * return {undefined} + */ +function makeCircleMarkerLegendColors(min, max) { + const reds1 = ['#ff6128']; + const reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c']; + const reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026']; + const bottomCutoff = 2; + const middleCutoff = 24; + let legendColors; + if (max - min <= bottomCutoff) { + legendColors = reds1; + } else if (max - min <= middleCutoff) { + legendColors = reds3; + } else { + legendColors = reds5; + } + return legendColors; +} + +function makeColorDarker(color) { + const amount = 1.3;//magic number, carry over from earlier + return d3.hcl(color).darker(amount).toString(); +} + +function makeStyleFunction(min, max, legendColors, quantizeDomain) { + const legendQuantizer = d3.scale.quantize().domain(quantizeDomain).range(legendColors); + return (feature) => { + const value = _.get(feature, 'properties.value'); + const color = legendQuantizer(value); + return { + fillColor: color, + color: makeColorDarker(color), + weight: 1.5, + opacity: 1, + fillOpacity: 0.75 + }; + }; +} diff --git a/src/core_plugins/tile_map/public/markers/shaded_circles.js b/src/core_plugins/tile_map/public/markers/shaded_circles.js new file mode 100644 index 00000000000000..a28d86d7578d4b --- /dev/null +++ b/src/core_plugins/tile_map/public/markers/shaded_circles.js @@ -0,0 +1,46 @@ +import L from 'leaflet'; +import _ from 'lodash'; +import { ScaledCirclesMarkers } from './scaled_circles'; + +export class ShadedCirclesMarkers extends ScaledCirclesMarkers { + getMarkerFunction() { + // multiplier to reduce size of all circles + const scaleFactor = 0.8; + return (feature, latlng) => { + const radius = this._geohashMinDistance(feature) * scaleFactor; + return L.circle(latlng, radius); + }; + } + + + /** + * _geohashMinDistance returns a min distance in meters for sizing + * circle markers to fit within geohash grid rectangle + * + * @method _geohashMinDistance + * @param feature {Object} + * @return {Number} + */ + _geohashMinDistance(feature) { + const centerPoint = _.get(feature, 'properties.center'); + const geohashRect = _.get(feature, 'properties.rectangle'); + + // centerPoint is an array of [lat, lng] + // geohashRect is the 4 corners of the geoHash rectangle + // an array that starts at the southwest corner and proceeds + // clockwise, each value being an array of [lat, lng] + + // center lat and southeast lng + const east = L.latLng([centerPoint[0], geohashRect[2][1]]); + // southwest lat and center lng + const north = L.latLng([geohashRect[3][0], centerPoint[1]]); + + // get latLng of geohash center point + const center = L.latLng([centerPoint[0], centerPoint[1]]); + + // get smallest radius at center of geohash grid rectangle + const eastRadius = Math.floor(center.distanceTo(east)); + const northRadius = Math.floor(center.distanceTo(north)); + return _.min([eastRadius, northRadius]); + } +} diff --git a/src/core_plugins/tile_map/public/styles/_tilemap.less b/src/core_plugins/tile_map/public/styles/_tilemap.less new file mode 100644 index 00000000000000..ba6e21d6185290 --- /dev/null +++ b/src/core_plugins/tile_map/public/styles/_tilemap.less @@ -0,0 +1,185 @@ +@import (reference) "~ui/styles/variables"; + +/* _tilemap */ + +.tilemap { + margin-bottom: 6px; + border: 1px solid; + border-color: @tilemap-border; + position: relative; +} + +/* leaflet Dom Util div for map label */ + +.tilemap-legend { + padding: 5px 7px 5px 7px; + margin: 0; + font: 11px/13px Arial, Helvetica, sans-serif; + background: @tilemap-legend-base-bg; + background: @tilemap-legend-bg; + box-shadow: 0 0 12px rgba(0,0,0,0.3); + border-radius: 5px; + text-align: left; + line-height: 15px; + color: @tilemap-color; +} + +.tilemap-legend-title { + font-weight: bold; +} + +.tilemap-legend i { + width: 10px; + height: 10px; + float: left; + margin: 2px 4px 0 0; + opacity: 1; + border-radius: 50%; + border-width: 1px; + border-style: solid; + border-color: @tilemap-legend-i-border; + background: @tilemap-legend-i-bg; +} + +/* top left needs some more styles */ +.leaflet-top.leaflet-left .tilemap-legend-wrapper { + position: absolute; + left: 50px; + white-space: nowrap; +} + +.leaflet-top.leaflet-left .tilemap-legend-wrapper span { + padding-right: 20px; +} + +/* leaflet Dom Util div for map legend */ + +.tilemap-info { + padding: 3px 7px 3px 7px; + margin: 0; + font-size: 12px; + background: @tilemap-info-base-bg; + background: fade(@tilemap-info-bg, 92%); + box-shadow: 0 0 12px rgba(0,0,0,0.3); + border-radius: 5px; +} + +.tilemap-info h2 { + font-size: 12px !important; + margin: 0 !important; + padding: 0; + color: @tilemap-info-header-color; +} + +.leaflet-control-fit { + text-align: center; + background: @tilemap-leaflet-control-bg; + width: 26px; + height: 26px; + outline: 1px @tilemap-leaflet-control-outline; +} + +/* over-rides leaflet popup styles to look like kibana tooltip */ + +.leaflet-container { + background: @tilemap-leaflet-container-bg !important; + outline: 0 !important; +} + +.leaflet-popup-content-wrapper { + margin: 0; + padding: 0; +} + +.leaflet-popup { + margin-bottom: 16px !important; + pointer-events: none; +} + +.leaflet-popup-content-wrapper { + background: @tooltip-bg !important; + color: @tooltip-color !important; + border-radius: 4px !important; + padding: 0 !important; +} + +.leaflet-popup-content { + margin: 0 !important; + line-height: 24px !important; + font-size: @font-size-base; + font-weight: normal; + word-wrap: break-word; + overflow: hidden; + pointer-events: none; + + > :last-child { + margin-bottom: @tooltip-space; + } + + > * { + margin: @tooltip-space @tooltip-space 0; + } + + table { + td,th { + padding: @tooltip-space-tight; + + &.row-bucket { + word-break: break-all; + } + } + } +} + +.leaflet-popup-tip-container, .leaflet-popup-close-button { + display: none !important; +} + +.leaflet-control-layers-expanded { + padding: 0; + margin: 0; + font: 12px/13px Arial, Helvetica, sans-serif; + line-height: 14px !important; +} + +.leaflet-control-layers-expanded label { + font-weight: normal !important; + margin: 0 !important; + padding: 0 !important; +} + +.leaflet-draw-tooltip { + display: none; +} + +.leaflet-control-attribution { + background-color: @tilemap-leaflet-footer-bg !important; + color: @tilemap-leaflet-footer-color !important; + + p { + display: inline; + } +} + +.leaflet-left { + .leaflet-control { + a, + a:hover { + color: @tilemap-leaflet-control-color; + } + } + + .leaflet-draw-actions a { + background-color: @tilemap-leaflet-control-draw-action-bg; + } +} + +/* filter to desaturate mapquest tiles */ + +img.leaflet-tile { + filter: @tilemap-filter; +} + +img.leaflet-tile.filters-off { + filter: none; +} diff --git a/src/core_plugins/tile_map/public/tile_map_vis.js b/src/core_plugins/tile_map/public/tile_map_vis.js new file mode 100644 index 00000000000000..595882b5596ac5 --- /dev/null +++ b/src/core_plugins/tile_map/public/tile_map_vis.js @@ -0,0 +1,93 @@ +import { supports } from 'ui/utils/supports'; +import { CATEGORY } from 'ui/vis/vis_category'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { MapsVisualizationProvider } from './maps_visualization'; +import { VisSchemasProvider } from 'ui/vis/editors/default/schemas'; +import { AggResponseGeoJsonProvider } from 'ui/agg_response/geo_json/geo_json'; +import tileMapTemplate from './editors/tile_map.html'; +import image from './images/icon-tilemap.svg'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; + + +VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, courier, config) { + + const Schemas = Private(VisSchemasProvider); + const geoJsonConverter = Private(AggResponseGeoJsonProvider); + const VisFactory = Private(VisFactoryProvider); + const MapsVisualization = Private(MapsVisualizationProvider); + + + return VisFactory.createBaseVisualization({ + name: 'tile_map', + title: 'Coordinate Map', + image, + description: 'Plot latitude and longitude coordinates on a map', + category: CATEGORY.MAP, + visConfig: { + defaults: { + mapType: 'Scaled Circle Markers', + isDesaturated: true, + addTooltip: true, + heatMaxZoom: 0, + heatMinOpacity: 0.1, + heatRadius: 25, + heatBlur: 15, + legendPosition: 'bottomright', + mapZoom: 2, + mapCenter: [0, 0], + wms: config.get('visualization:tileMap:WMSdefaults') + } + }, + responseConverter: geoJsonConverter, + responseHandler: 'basic', + implementsRenderComplete: true, + visualization: MapsVisualization, + editorConfig: { + collections: { + legendPositions: [{ + value: 'bottomleft', + text: 'bottom left', + }, { + value: 'bottomright', + text: 'bottom right', + }, { + value: 'topleft', + text: 'top left', + }, { + value: 'topright', + text: 'top right', + }], + mapTypes: [ + 'Scaled Circle Markers', + 'Shaded Circle Markers', + 'Shaded Geohash Grid', + 'Heatmap' + ], + canDesaturate: !!supports.cssFilters + }, + optionsTemplate: tileMapTemplate, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: 'Value', + min: 1, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: 'buckets', + name: 'segment', + title: 'Geo Coordinates', + aggFilter: 'geohash_grid', + min: 1, + max: 1 + } + ]) + } + }); + +}); diff --git a/src/core_plugins/timelion/public/vis/index.js b/src/core_plugins/timelion/public/vis/index.js index a1da755059a852..f45b7a518ed8ff 100644 --- a/src/core_plugins/timelion/public/vis/index.js +++ b/src/core_plugins/timelion/public/vis/index.js @@ -1,35 +1,48 @@ -import { VisVisTypeProvider } from 'ui/vis/vis_type'; +import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { CATEGORY } from 'ui/vis/vis_category'; import image from '../images/icon-timelion.svg'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { TemplateVisTypeProvider } from 'ui/template_vis_type'; -import template from 'plugins/timelion/vis/timelion_vis.html'; -import editorTemplate from 'plugins/timelion/vis/timelion_vis_params.html'; -import 'plugins/timelion/vis/timelion_vis_controller'; -import 'plugins/timelion/vis/timelion_vis_params_controller'; -import 'plugins/timelion/vis/timelion_vis.less'; +import { RequestHandlersRegistryProvider } from 'ui/registry/request_handlers'; +import { TimelionRequestHandlerProvider } from './timelion_request_handler'; -// export the provider so that the visType can be required with Private() -export default function TimelionVisProvider(Private) { - const VisType = Private(VisVisTypeProvider); - const TemplateVisType = Private(TemplateVisTypeProvider); +define(function (require) { + // we also need to load the controller and used by the template + require('plugins/timelion/vis/timelion_vis_controller'); + require('plugins/timelion/directives/timelion_expression_input'); - // return the visType object, which kibana will use to display and configure new - // Vis object of this type. - return new TemplateVisType({ - name: 'timelion', - title: 'Timelion', - image, - description: 'Build time-series using functional expressions', - category: VisType.CATEGORY.TIME, - template, - params: { - editor: editorTemplate, - }, - requiresSearch: false, - requiresTimePicker: true, - implementsRenderComplete: true, - }); -} + // Stylin + require('plugins/timelion/vis/timelion_vis.less'); -// register the provider with the visTypes registry so that other know it exists -VisTypesRegistryProvider.register(TimelionVisProvider); + // register the provider with the visTypes registry so that other know it exists + VisTypesRegistryProvider.register(TimelionVisProvider); + RequestHandlersRegistryProvider.register(TimelionRequestHandlerProvider); + + function TimelionVisProvider(Private) { + const VisFactory = Private(VisFactoryProvider); + + // return the visType object, which kibana will use to display and configure new + // Vis object of this type. + return VisFactory.createAngularVisualization({ + name: 'timelion', + title: 'Timelion', + image, + description: 'Build time-series using functional expressions', + category: CATEGORY.TIME, + visConfig: { + defaults: { + expression: '.es(*)', + interval: '1m' + }, + template: require('plugins/timelion/vis/timelion_vis.html'), + }, + editorConfig: { + optionsTemplate: require('plugins/timelion/vis/timelion_vis_params.html') + }, + requestHandler: 'timelion', + responseHandler: 'none', + }); + } + + // export the provider so that the visType can be required with Private() + return TimelionVisProvider; +}); diff --git a/src/core_plugins/timelion/public/vis/timelion_request_handler.js b/src/core_plugins/timelion/public/vis/timelion_request_handler.js new file mode 100644 index 00000000000000..312de599a3bd07 --- /dev/null +++ b/src/core_plugins/timelion/public/vis/timelion_request_handler.js @@ -0,0 +1,48 @@ +import _ from 'lodash'; +import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context'; + +const TimelionRequestHandlerProvider = function (Private, Notifier, $http, $rootScope, timefilter) { + const timezone = Private(require('plugins/timelion/services/timezone'))(); + const dashboardContext = Private(dashboardContextProvider); + + const notify = new Notifier({ + location: 'Timelion' + }); + + return { + name: 'timelion', + handler: function (vis /*, appState, uiState */) { + + return new Promise((resolve, reject) => { + console.log('[timelion] get'); + + const expression = vis.params.expression; + if (!expression) return; + + $http.post('../api/timelion/run', { + sheet: [expression], + extended: { + es: { + filter: dashboardContext() + } + }, + time: _.extend(timefilter.time, { + interval: vis.params.interval, + timezone: timezone + }), + }) + .success(function (resp) { + resolve(resp); + }) + .error(function (resp) { + const err = new Error(resp.message); + err.stack = resp.stack; + notify.error(err); + reject(err); + }); + }); + } + }; +}; + +export { TimelionRequestHandlerProvider }; diff --git a/src/core_plugins/timelion/public/vis/timelion_vis.html b/src/core_plugins/timelion/public/vis/timelion_vis.html index 665d5c292e5bcb..b9afdae6169a9d 100644 --- a/src/core_plugins/timelion/public/vis/timelion_vis.html +++ b/src/core_plugins/timelion/public/vis/timelion_vis.html @@ -1,3 +1,3 @@
-
+
diff --git a/src/core_plugins/timelion/public/vis/timelion_vis_controller.js b/src/core_plugins/timelion/public/vis/timelion_vis_controller.js index 1167697d5fff06..72f1404c9141dd 100644 --- a/src/core_plugins/timelion/public/vis/timelion_vis_controller.js +++ b/src/core_plugins/timelion/public/vis/timelion_vis_controller.js @@ -1,69 +1,14 @@ -import _ from 'lodash'; -import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; -import { uiModules } from 'ui/modules'; -import timezoneProvider from 'plugins/timelion/services/timezone'; -import { dashboardContextProvider } from 'plugins/kibana/dashboard/dashboard_context'; -import 'plugins/timelion/directives/chart/chart'; -import 'plugins/timelion/directives/timelion_interval/timelion_interval'; -import 'ui/state_management/app_state'; +define(function (require) { + require('plugins/timelion/directives/chart/chart'); + require('plugins/timelion/directives/timelion_interval/timelion_interval'); + require('ui/state_management/app_state'); -const module = uiModules.get('kibana/timelion_vis', ['kibana']); + const module = require('ui/modules').get('kibana/timelion_vis', ['kibana']); + module.controller('TimelionVisController', function ($scope) { -module.controller('TimelionVisController', function ($scope, $element, Private, Notifier, $http, $rootScope, timefilter) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const timezone = Private(timezoneProvider)(); - const dashboardContext = Private(dashboardContextProvider); - - const notify = new Notifier({ - location: 'Timelion' - }); - - $scope.search = function run() { - const expression = $scope.vis.params.expression; - if (!expression) return; - - $http.post('../api/timelion/run', { - sheet: [expression], - extended: { - es: { - filter: dashboardContext() - } - }, - time: _.extend(timefilter.time, { - interval: $scope.vis.params.interval, - timezone: timezone - }), - }) - // data, status, headers, config - .success(function (resp) { - $scope.sheet = resp.sheet; - }) - .error(function (resp) { - $scope.sheet = []; - const err = new Error(resp.message); - err.stack = resp.stack; - notify.error(err); + $scope.$on('renderComplete', event => { + event.stopPropagation(); + $scope.renderComplete(); }); - }; - - // This is bad, there should be a single event that triggers a refresh of data. - - // When the expression updates - $scope.$watchMulti(['vis.params.expression', 'vis.params.interval'], $scope.search); - - // When the time filter changes - $scope.$listen(timefilter, 'fetch', $scope.search); - - // When a filter is added to the filter bar? - $scope.$listen(queryFilter, 'fetch', $scope.search); - - // When auto refresh happens - $scope.$on('courier:searchRefresh', $scope.search); - - $scope.$on('fetch', $scope.search); - - $scope.$on('renderComplete', event => { - event.stopPropagation(); - $element.trigger('renderComplete'); }); }); diff --git a/src/core_plugins/timelion/public/vis/timelion_vis_params.html b/src/core_plugins/timelion/public/vis/timelion_vis_params.html index 67f612384ec3b3..3b9e3f94b08aeb 100644 --- a/src/core_plugins/timelion/public/vis/timelion_vis_params.html +++ b/src/core_plugins/timelion/public/vis/timelion_vis_params.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/core_plugins/timelion/public/vis/timelion_vis_params_controller.js b/src/core_plugins/timelion/public/vis/timelion_vis_params_controller.js deleted file mode 100644 index 35584da4c6f29d..00000000000000 --- a/src/core_plugins/timelion/public/vis/timelion_vis_params_controller.js +++ /dev/null @@ -1,12 +0,0 @@ -import { uiModules } from 'ui/modules'; -import 'plugins/timelion/directives/timelion_expression_input'; - -const module = uiModules.get('kibana/timelion_vis', ['kibana']); -module.controller('TimelionVisParamsController', function ($scope, $rootScope) { - $scope.vis.params.expression = $scope.vis.params.expression || '.es(*)'; - $scope.vis.params.interval = $scope.vis.params.interval || '1m'; - - $scope.search = function () { - $rootScope.$broadcast('courier:searchRefresh'); - }; -}); From d44c398a9bde009fdf3929907b23ea94c3490ebe Mon Sep 17 00:00:00 2001 From: ppisljar Date: Wed, 7 Jun 2017 10:52:09 +0200 Subject: [PATCH 09/40] fixing bugs with rebase fixing tests fixing tsvb auto apply button fix tsvb resizing issue update scope.$apply for react editor components re-render react component when vis.params are updated (#12347) fixing heatmap remove obsolete maps_renderbot indirection remove cruft/ remove custom resizing and hook into global event fixing brush event fixing resize event for vislib_vis_type fixing legend changing based on nathans review --- .../public/controls/gauge_options.html | 6 +- .../public/controls/gauge_options.js | 2 +- .../public/controls/heatmap_options.html | 8 +- .../public/controls/heatmap_options.js | 3 + .../public/editors/__tests__/point_series.js | 2 +- .../public/editors/heatmap.html | 2 +- .../kbn_vislib_vis_types/public/gauge.js | 1 + .../kbn_vislib_vis_types/public/heatmap.js | 2 +- .../kibana/public/dashboard/panel/panel.html | 2 + .../public/visualize/editor/editor.html | 2 + .../kibana/public/visualize/editor/editor.js | 3 - .../kibana/public/visualize/index.js | 16 +- .../__tests__/markdown_vis_controller.js | 1 + .../metrics/public/components/vis_editor.js | 34 ++- .../metrics/public/lib/__tests__/add_scope.js | 58 ---- .../lib/__tests__/create_brush_handler.js | 14 +- .../public/region_map_controller.js | 13 +- .../public/region_map_vis_params.html | 6 +- .../public/region_map_vis_params.js | 10 +- .../table_vis/public/__tests__/_table_vis.js | 34 ++- .../public/__tests__/_table_vis_controller.js | 1 + .../tagcloud/public/tag_cloud_controller.js | 14 +- .../public/__tests__/geohash_layer.js | 4 +- .../tile_map/public/__tests__/kibana_map.js | 10 +- .../public}/__tests__/service_settings.js | 25 +- .../public/__tests__/tilemap_settings.js | 61 ---- .../__tests__/tilemap_settings_mocked.js | 140 --------- .../tile_map/public/editors/tile_map.html | 4 +- .../tile_map/public/kibana_map.js | 2 +- .../tile_map/public}/lib/service_settings.js | 0 .../tile_map/public/lib/tilemap_settings.js | 208 ------------- .../tile_map/public/maps_renderbot.js | 258 ---------------- .../tile_map/public/maps_visualization.js | 276 +++++++++++++++++- src/core_plugins/timelion/public/vis/index.js | 6 +- .../agg_types/__tests__/agg_param_writer.js | 25 +- src/ui/public/registry/response_handlers.js | 6 - .../{editor_types.js => vis_editor_types.js} | 4 +- ...st_handlers.js => vis_request_handlers.js} | 4 +- .../public/registry/vis_response_handlers.js | 6 + src/ui/public/vis/__tests__/_vis.js | 10 - .../public/vis/editors/default/agg_add.html | 6 +- .../public/vis/editors/default/sidebar.html | 9 +- .../public/vis/editors/default/vis_options.js | 9 +- src/ui/public/vis/request_handlers/courier.js | 6 +- src/ui/public/vis/vis.js | 8 +- .../public/vis/vis_types/angular_vis_type.js | 1 + src/ui/public/vis/vis_types/base_vis_type.js | 2 +- src/ui/public/vis/vis_types/react_vis_type.js | 2 +- .../public/vis/vis_types/vislib_vis_type.js | 7 +- src/ui/public/vislib/vis.js | 36 +-- .../public/visualize/__tests__/visualize.js | 20 +- .../public/visualize/visualization_editor.js | 4 +- src/ui/public/visualize/visualize.html | 4 - src/ui/public/visualize/visualize.js | 32 +- src/ui/public/visualize/visualize_legend.js | 7 +- .../functional/page_objects/visualize_page.js | 4 +- 56 files changed, 490 insertions(+), 950 deletions(-) delete mode 100644 src/core_plugins/metrics/public/lib/__tests__/add_scope.js rename src/{ui/public/vis_maps => core_plugins/tile_map/public}/__tests__/service_settings.js (95%) delete mode 100644 src/core_plugins/tile_map/public/__tests__/tilemap_settings.js delete mode 100644 src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js rename src/{ui/public/vis_maps => core_plugins/tile_map/public}/lib/service_settings.js (100%) delete mode 100644 src/core_plugins/tile_map/public/lib/tilemap_settings.js delete mode 100644 src/core_plugins/tile_map/public/maps_renderbot.js delete mode 100644 src/ui/public/registry/response_handlers.js rename src/ui/public/registry/{editor_types.js => vis_editor_types.js} (52%) rename src/ui/public/registry/{request_handlers.js => vis_request_handlers.js} (50%) create mode 100644 src/ui/public/registry/vis_response_handlers.js diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.html b/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.html index 4c8afa31f64e98..fee2c53a65b5c0 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.html @@ -8,7 +8,7 @@ id="gaugeType" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.gauge.gaugeType" - ng-options="mode for mode in vis.type.params.gaugeTypes" + ng-options="mode for mode in collections.gaugeTypes" >
@@ -183,7 +183,7 @@ id="colorSchema" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.gauge.colorSchema" - ng-options="mode for mode in vis.type.params.colorSchemas" + ng-options="mode for mode in collections.colorSchemas" >
reset colors
@@ -255,7 +255,7 @@ id="gaugeColorMode" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.gauge.gaugeColorMode" - ng-options="mode for mode in vis.type.params.gaugeColorMode" + ng-options="mode for mode in collections.gaugeColorMode" >
diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.js b/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.js index 6523c57d644338..e2849ac43797b6 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/gauge_options.js @@ -9,7 +9,7 @@ module.directive('gaugeOptions', function () { template: gaugeOptionsTemplate, replace: true, link: function ($scope) { - + $scope.collections = $scope.vis.type.editorConfig.collections; $scope.showColorRange = true; $scope.$watch('vis.params.gauge.gaugeType', type => { diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html b/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html index efc6857772e675..ed2affb0e34c0d 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.html @@ -8,7 +8,7 @@ id="colorSchema" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.colorSchema" - ng-options="mode for mode in vis.editorConfig.collections.colorSchemas" + ng-options="mode for mode in collections.colorSchemas" >
@@ -141,7 +141,7 @@ ng-click="removeRange($index)" class="kuiButton kuiButton--danger kuiButton--small" > - + @@ -149,7 +149,7 @@

- + Required: You must specify at least one range.

diff --git a/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.js b/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.js index bfdaad8071068c..2810cc72ce9064 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/controls/heatmap_options.js @@ -9,6 +9,9 @@ module.directive('heatmapOptions', function () { template: heatmapOptionsTemplate, replace: true, link: function ($scope) { + + $scope.collections = $scope.vis.type.editorConfig.collections; + const verticalRotation = 270; $scope.showColorRange = false; $scope.showLabels = false; diff --git a/src/core_plugins/kbn_vislib_vis_types/public/editors/__tests__/point_series.js b/src/core_plugins/kbn_vislib_vis_types/public/editors/__tests__/point_series.js index 8b46f02d555b49..b0bf4191a6b202 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/editors/__tests__/point_series.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/editors/__tests__/point_series.js @@ -20,7 +20,7 @@ describe('point series editor', function () { function makeConfig() { return { type: 'line', - params: lineVisType.params.defaults, + params: lineVisType.visConfig.defaults, aggs: [ { type: 'count', schema: 'metric', params: { field: 'bytes' } }, { type: 'terms', schema: 'segment', params: { field: 'machine.os' } }, diff --git a/src/core_plugins/kbn_vislib_vis_types/public/editors/heatmap.html b/src/core_plugins/kbn_vislib_vis_types/public/editors/heatmap.html index fa86afbd9d3c7f..1d1d5e418a5911 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/editors/heatmap.html +++ b/src/core_plugins/kbn_vislib_vis_types/public/editors/heatmap.html @@ -32,7 +32,7 @@ id="legendPosition" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.legendPosition" - ng-options="position.value as position.text for position in vis.type.params.legendPositions" + ng-options="position.value as position.text for position in vis.type.editorConfig.collections.legendPositions" >
diff --git a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js index 72441d4b25fff3..a35390116902ca 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js @@ -18,6 +18,7 @@ export default function GaugeVisType(Private) { category: CATEGORY.DATA, visConfig: { defaults: { + type:'gauge', addTooltip: true, addLegend: true, diff --git a/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js b/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js index 2b68b597c07fbd..656c94c71697df 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js @@ -46,7 +46,7 @@ export default function HeatmapVisType(Private) { }, }, editorConfig: { - collectons: { + collections: { legendPositions: [{ value: 'left', text: 'left', diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel.html b/src/core_plugins/kibana/public/dashboard/panel/panel.html index 6db60243465269..2f94871efead38 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel.html +++ b/src/core_plugins/kibana/public/dashboard/panel/panel.html @@ -83,8 +83,10 @@ saved-obj="savedObj" app-state="appState" ui-state="uiState" + data-shared-item data-title="{{savedObj.title}}" data-description="{{savedObj.description}}" + render-counter class="panel-content"> diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.html b/src/core_plugins/kibana/public/visualize/editor/editor.html index 909b8d0991ba98..e50ff1eee2265e 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/core_plugins/kibana/public/visualize/editor/editor.html @@ -90,6 +90,8 @@ ui-state="uiState" app-state="state" editor-mode="true" + data-shared-item + render-counter data-title="{{savedVis.lastSavedTitle}}" data-description="{{savedVis.description}}" show-spy-panel="chrome.getVisible()"> diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.js b/src/core_plugins/kibana/public/visualize/editor/editor.js index 665034db1177f7..8d7fc1df4cfe0c 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/core_plugins/kibana/public/visualize/editor/editor.js @@ -1,9 +1,6 @@ import _ from 'lodash'; import 'plugins/kibana/visualize/saved_visualizations/saved_visualizations'; - -// import 'plugins/kibana/visualize/editor/sidebar'; import 'ui/vis/editors/default/sidebar'; - import 'plugins/kibana/visualize/editor/agg_filter'; import 'ui/visualize'; import 'ui/collapsible_sidebar'; diff --git a/src/core_plugins/kibana/public/visualize/index.js b/src/core_plugins/kibana/public/visualize/index.js index 76bf680a314c0a..c0bf9caab21458 100644 --- a/src/core_plugins/kibana/public/visualize/index.js +++ b/src/core_plugins/kibana/public/visualize/index.js @@ -23,9 +23,9 @@ import { BasicResponseHandlerProvider } from 'ui/vis/response_handlers/basic'; import { defaultEditor } from 'ui/vis/editors/default/default'; -import { RequestHandlersRegistryProvider } from 'ui/registry/request_handlers'; -import { ResponseHandlersRegistryProvider } from 'ui/registry/response_handlers'; -import { EditorTypesRegistryProvider } from 'ui/registry/editor_types'; +import { VisRequestHandlersRegistryProvider } from 'ui/registry/vis_request_handlers'; +import { VisResponseHandlersRegistryProvider } from 'ui/registry/vis_response_handlers'; +import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; uiRoutes .defaults(/visualize/, { @@ -39,9 +39,9 @@ uiRoutes // preloading SavedObjectRegistryProvider.register(savedVisualizationProvider); -RequestHandlersRegistryProvider.register(CourierRequestHandlerProvider); -RequestHandlersRegistryProvider.register(noneRequestHandlerProvider); -ResponseHandlersRegistryProvider.register(noneResponseHandler); -ResponseHandlersRegistryProvider.register(BasicResponseHandlerProvider); -EditorTypesRegistryProvider.register(defaultEditor); +VisRequestHandlersRegistryProvider.register(CourierRequestHandlerProvider); +VisRequestHandlersRegistryProvider.register(noneRequestHandlerProvider); +VisResponseHandlersRegistryProvider.register(noneResponseHandler); +VisResponseHandlersRegistryProvider.register(BasicResponseHandlerProvider); +VisEditorTypesRegistryProvider.register(defaultEditor); diff --git a/src/core_plugins/markdown_vis/public/__tests__/markdown_vis_controller.js b/src/core_plugins/markdown_vis/public/__tests__/markdown_vis_controller.js index f9dc317de5f2bb..5898bf6899f20f 100644 --- a/src/core_plugins/markdown_vis/public/__tests__/markdown_vis_controller.js +++ b/src/core_plugins/markdown_vis/public/__tests__/markdown_vis_controller.js @@ -8,6 +8,7 @@ describe('markdown vis controller', function () { beforeEach(ngMock.module('kibana/markdown_vis')); beforeEach(ngMock.inject(function ($rootScope, $controller) { $scope = $rootScope.$new(); + $scope.renderComplete = () => {}; const $element = $('
'); $controller('KbnMarkdownVisController', { $scope, $element }); $scope.$digest(); diff --git a/src/core_plugins/metrics/public/components/vis_editor.js b/src/core_plugins/metrics/public/components/vis_editor.js index a6144b468f125f..b677a707cf5f8e 100644 --- a/src/core_plugins/metrics/public/components/vis_editor.js +++ b/src/core_plugins/metrics/public/components/vis_editor.js @@ -9,20 +9,29 @@ class VisEditor extends Component { constructor(props) { super(props); - this.state = { model: props.vis.params }; + this.state = { model: props.vis.params, dirty: false, autoApply: true }; this.onBrush = brushHandler(props.vis.API.timeFilter); } render() { const handleChange = (part) => { const nextModel = { ...this.state.model, ...part }; - this.setState({ model: nextModel }); - if (this.props.onChange || true) { - console.log(nextModel); - this.props.vis.params = nextModel; + + this.props.vis.params = nextModel; + if (this.state.autoApply) { this.props.vis.updateState(); - //this.props.onChange(nextModel); } + + this.setState({ model: nextModel, dirty: !this.state.autoApply }); + }; + + const handleAutoApplyToggle = (part) => { + this.setState({ autoApply: part.target.checked }); + }; + + const handleCommit = () => { + this.props.vis.updateState(); + this.setState({ dirty: false }); }; if (!this.props.vis.isEditorMode()) { @@ -44,13 +53,13 @@ class VisEditor extends Component { model={model} onChange={handleChange} /> ); } + return null; } + componentDidMount() { + this.props.renderComplete(); + } + } VisEditor.propTypes = { diff --git a/src/core_plugins/metrics/public/lib/__tests__/add_scope.js b/src/core_plugins/metrics/public/lib/__tests__/add_scope.js deleted file mode 100644 index c2728ce598c0a7..00000000000000 --- a/src/core_plugins/metrics/public/lib/__tests__/add_scope.js +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react'; -import { expect } from 'chai'; -import { shallow } from 'enzyme'; -import sinon from 'sinon'; -import addScope from '../add_scope'; - -const Component = React.createClass({ - render() { - return (
); - } -}); - -describe('addScope()', () => { - - let unsubStub; - let watchCollectionStub; - let $scope; - - beforeEach(() => { - unsubStub = sinon.stub(); - watchCollectionStub = sinon.stub().returns(unsubStub); - $scope = { - $watchCollection: watchCollectionStub, - testOne: 1, - testTwo: 2 - }; - }); - - it('adds $scope variables as props to wrapped component', () => { - const WrappedComponent = addScope(Component, $scope, ['testOne', 'testTwo']); - const wrapper = shallow(); - expect(wrapper.state('testOne')).to.equal(1); - expect(wrapper.state('testTwo')).to.equal(2); - }); - - it('calls $scope.$watchCollection on each scoped item', () => { - const WrappedComponent = addScope(Component, $scope, ['testOne', 'testTwo']); - shallow(); - expect(watchCollectionStub.calledTwice).to.equal(true); - expect(watchCollectionStub.firstCall.args[0]).to.equal('testOne'); - expect(watchCollectionStub.secondCall.args[0]).to.equal('testTwo'); - }); - - it('unsubscribes from watches', () => { - const WrappedComponent = addScope(Component, $scope, ['testOne', 'testTwo']); - const wrapper = shallow(); - wrapper.unmount(); - expect(unsubStub.calledTwice).to.equal(true); - }); - - it('updates state when watch is called', () => { - const WrappedComponent = addScope(Component, $scope, ['testOne']); - const wrapper = shallow(); - watchCollectionStub.firstCall.args[1].call(null, 3); - expect(wrapper.state('testOne')).to.equal(3); - }); - -}); diff --git a/src/core_plugins/metrics/public/lib/__tests__/create_brush_handler.js b/src/core_plugins/metrics/public/lib/__tests__/create_brush_handler.js index d57f05e4bbf7fe..9c1b865215ccbd 100644 --- a/src/core_plugins/metrics/public/lib/__tests__/create_brush_handler.js +++ b/src/core_plugins/metrics/public/lib/__tests__/create_brush_handler.js @@ -1,29 +1,19 @@ import createBrushHandler from '../create_brush_handler'; -import sinon from 'sinon'; import moment from 'moment'; import { expect } from 'chai'; describe('createBrushHandler', () => { - - let evalAsyncStub; - let $scope; let timefilter; let fn; let range; beforeEach(() => { - evalAsyncStub = sinon.stub().yields(); - $scope = { $evalAsync: evalAsyncStub }; - timefilter = { time: {} }; - fn = createBrushHandler($scope, timefilter); + timefilter = { time: {}, update: () => {} }; + fn = createBrushHandler(timefilter); range = { xaxis: { from: '2017-01-01T00:00:00Z', to: '2017-01-01T00:10:00Z' } }; fn(range); }); - it('returns brushHandler() that calls $scope.$evalAsync()', () => { - expect(evalAsyncStub.calledOnce).to.equal(true); - }); - it('returns brushHandler() that updates timefilter', () => { expect(timefilter.time.from).to.equal(moment(range.xaxis.from).toISOString()); expect(timefilter.time.to).to.equal(moment(range.xaxis.to).toISOString()); diff --git a/src/core_plugins/region_map/public/region_map_controller.js b/src/core_plugins/region_map/public/region_map_controller.js index 9507529f4e8256..905dc3c9b69107 100644 --- a/src/core_plugins/region_map/public/region_map_controller.js +++ b/src/core_plugins/region_map/public/region_map_controller.js @@ -6,8 +6,7 @@ import { KibanaMap } from '../../tile_map/public/kibana_map'; import ChoroplethLayer from './choropleth_layer'; import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; import AggResponsePointSeriesTooltipFormatterProvider from './tooltip_formatter'; -import { ResizeCheckerProvider } from 'ui/resize_checker'; -import 'ui/vis_maps/lib/service_settings'; +import '../../tile_map/public/lib/service_settings'; const module = uiModules.get('kibana/region_map', ['kibana']); @@ -15,18 +14,18 @@ module.controller('KbnRegionMapController', function ($scope, $element, Private, serviceSettings, config) { const tooltipFormatter = Private(AggResponsePointSeriesTooltipFormatterProvider); - const ResizeChecker = Private(ResizeCheckerProvider); const notify = new Notifier({ location: 'Region map' }); - const resizeChecker = new ResizeChecker($element); + let kibanaMap = null; - resizeChecker.on('resize', () => { + let choroplethLayer = null; + const kibanaMapReady = makeKibanaMap(); + + $scope.$watch('resize', () => { if (kibanaMap) { kibanaMap.resize(); } }); - let choroplethLayer = null; - const kibanaMapReady = makeKibanaMap(); $scope.$watch('esResponse', async function (response) { kibanaMapReady.then(() => { diff --git a/src/core_plugins/region_map/public/region_map_vis_params.html b/src/core_plugins/region_map/public/region_map_vis_params.html index 45a218267a3b69..9aba92115e880d 100644 --- a/src/core_plugins/region_map/public/region_map_vis_params.html +++ b/src/core_plugins/region_map/public/region_map_vis_params.html @@ -15,9 +15,9 @@ id="regionMap" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.selectedLayer" - ng-options="layer.name for layer in vis.type.params.vectorLayers" + ng-options="layer.name for layer in collections.vectorLayers" ng-change="onLayerChange()" - ng-init="vis.params.selectedLayer=vis.type.params.vectorLayers[0]" + ng-init="vis.params.selectedLayer=collections.vectorLayers[0]" >
@@ -51,7 +51,7 @@ id="colorSchema" class="kuiSelect kuiSideBarSelect" ng-model="vis.params.colorSchema" - ng-options="mode for mode in vis.type.params.colorSchemas" + ng-options="mode for mode in collections.colorSchemas" >
diff --git a/src/core_plugins/region_map/public/region_map_vis_params.js b/src/core_plugins/region_map/public/region_map_vis_params.js index 88189c43307e56..5b286f2d8c06c9 100644 --- a/src/core_plugins/region_map/public/region_map_vis_params.js +++ b/src/core_plugins/region_map/public/region_map_vis_params.js @@ -13,11 +13,13 @@ uiModules.get('kibana/region_map') template: regionMapVisParamsTemplate, link: function ($scope) { + $scope.collections = $scope.vis.type.editorConfig.collections; + $scope.onLayerChange = onLayerChange; serviceSettings.getFileLayers() .then(function (layersFromService) { - const newVectorLayers = $scope.vis.type.params.vectorLayers.slice(); + const newVectorLayers = $scope.collections.vectorLayers.slice(); for (let i = 0; i < layersFromService.length; i += 1) { const layerFromService = layersFromService[i]; const alreadyAdded = newVectorLayers.some((layer) =>_.eq(layerFromService, layer)); @@ -26,10 +28,10 @@ uiModules.get('kibana/region_map') } } - $scope.vis.type.params.vectorLayers = newVectorLayers; + $scope.collections.vectorLayers = newVectorLayers; - if ($scope.vis.type.params.vectorLayers[0] && !$scope.vis.params.selectedLayer) { - $scope.vis.params.selectedLayer = $scope.vis.type.params.vectorLayers[0]; + if ($scope.collections.vectorLayers[0] && !$scope.vis.params.selectedLayer) { + $scope.vis.params.selectedLayer = $scope.collections.vectorLayers[0]; onLayerChange(); } diff --git a/src/core_plugins/table_vis/public/__tests__/_table_vis.js b/src/core_plugins/table_vis/public/__tests__/_table_vis.js index 5b1b44776b607d..6b4414d4cefccb 100644 --- a/src/core_plugins/table_vis/public/__tests__/_table_vis.js +++ b/src/core_plugins/table_vis/public/__tests__/_table_vis.js @@ -29,9 +29,10 @@ describe('Integration', function () { $rootScope.vis = vis; $rootScope.esResponse = esResponse; $rootScope.uiState = require('fixtures/mock_ui_state'); - $el = $(''); + $el = $(''); $compile($el)($rootScope); $rootScope.$apply(); + } function OneRangeVis(params) { @@ -89,20 +90,25 @@ describe('Integration', function () { it('passes the table groups to the kbnAggTableGroup directive', function () { init(new OneRangeVis(), fixtures.oneRangeBucket); - const $atg = $el.find('kbn-agg-table-group').first(); - expect($atg.size()).to.be(1); - expect($atg.attr('group')).to.be('tableGroups'); - expect($atg.isolateScope().group).to.be($atg.scope().tableGroups); + $rootScope.$on('renderComplete', () => { + const $atg = $el.find('kbn-agg-table-group').first(); + expect($atg.size()).to.be(1); + expect($atg.attr('group')).to.be('tableGroups'); + expect($atg.isolateScope().group).to.be($atg.scope().tableGroups); + }); + }); it('displays an error if the search had no hits', function () { init(new OneRangeVis(), { hits: { total: 0, hits: [] } }); - expect($el.find('kbn-agg-table-group').size()).to.be(0); + $rootScope.$on('renderComplete', () => { + expect($el.find('kbn-agg-table-group').size()).to.be(0); - const $err = $el.find('.table-vis-error'); - expect($err.size()).to.be(1); - expect($err.text().trim()).to.be('No results found'); + const $err = $el.find('.table-vis-error'); + expect($err.size()).to.be(1); + expect($err.text().trim()).to.be('No results found'); + }); }); it('displays an error if the search hits, but didn\'t create any rows', function () { @@ -121,10 +127,12 @@ describe('Integration', function () { init(new ThreeTermVis(visParams), resp); - expect($el.find('kbn-agg-table-group').size()).to.be(0); + $rootScope.$on('renderComplete', () => { + expect($el.find('kbn-agg-table-group').size()).to.be(0); - const $err = $el.find('.table-vis-error'); - expect($err.size()).to.be(1); - expect($err.text().trim()).to.be('No results found'); + const $err = $el.find('.table-vis-error'); + expect($err.size()).to.be(1); + expect($err.text().trim()).to.be('No results found'); + }); }); }); diff --git a/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js b/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js index bf5dd7c5870978..6f458fa3aa4132 100644 --- a/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js +++ b/src/core_plugins/table_vis/public/__tests__/_table_vis_controller.js @@ -58,6 +58,7 @@ describe('Controller', function () { $rootScope.vis = vis; $rootScope.uiState = new AppState({ uiState: {} }).makeStateful('uiState'); + $rootScope.renderComplete = () => {}; $rootScope.newScope = function (scope) { $scope = scope; }; $el = $('
') diff --git a/src/core_plugins/tagcloud/public/tag_cloud_controller.js b/src/core_plugins/tagcloud/public/tag_cloud_controller.js index 0680183696e245..3beea10eb69f51 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_controller.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_controller.js @@ -20,6 +20,7 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private, const aggConfigResult = new AggConfigResult(aggs[0], false, event, event); clickHandler({ point: { aggConfigResult: aggConfigResult } }); }); + tagCloud.on('renderComplete', () => { const truncatedMessage = containerNode.querySelector('.tagcloud-truncated-message'); @@ -33,13 +34,9 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private, const bucketName = containerNode.querySelector('.tagcloud-custom-label'); bucketName.innerHTML = `${$scope.vis.aggs[0].makeLabel()} - ${$scope.vis.aggs[1].makeLabel()}`; - - truncatedMessage.style.display = truncated ? 'block' : 'none'; - const status = tagCloud.getStatus(); - if (TagCloud.STATUS.COMPLETE === status) { incompleteMessage.style.display = 'none'; } else if (TagCloud.STATUS.INCOMPLETE === status) { @@ -87,14 +84,9 @@ module.controller('KbnTagCloudController', function ($scope, $element, Private, $scope.$watch('vis.params', (options) => tagCloud.setOptions(options)); - $scope.$watch(getContainerSize, _.debounce(() => { + $scope.$watch('resize', () => { tagCloud.resize(); - }, 1000, { trailing: true }), true); - - - function getContainerSize() { - return { width: $element.width(), height: $element.height() }; - } + }); function getValue(metricsAgg, bucket) { let size = metricsAgg.getValue(bucket); diff --git a/src/core_plugins/tile_map/public/__tests__/geohash_layer.js b/src/core_plugins/tile_map/public/__tests__/geohash_layer.js index c61ed5d991bf8e..147f5fee0769dd 100644 --- a/src/core_plugins/tile_map/public/__tests__/geohash_layer.js +++ b/src/core_plugins/tile_map/public/__tests__/geohash_layer.js @@ -1,6 +1,6 @@ import expect from 'expect.js'; -import { KibanaMap } from 'ui/vis_maps/kibana_map'; -import { GeohashLayer } from 'ui/vis_maps/geohash_layer'; +import { KibanaMap } from '../kibana_map'; +import { GeohashLayer } from '../geohash_layer'; import { GeoHashSampleData } from './geohash_sample_data'; describe('kibana_map tests', function () { diff --git a/src/core_plugins/tile_map/public/__tests__/kibana_map.js b/src/core_plugins/tile_map/public/__tests__/kibana_map.js index a3752e42c2d644..a1f6195528755b 100644 --- a/src/core_plugins/tile_map/public/__tests__/kibana_map.js +++ b/src/core_plugins/tile_map/public/__tests__/kibana_map.js @@ -1,5 +1,5 @@ import expect from 'expect.js'; -import { KibanaMap } from 'ui/vis_maps/kibana_map'; +import { KibanaMap } from '../kibana_map'; describe('kibana_map tests', function () { @@ -40,13 +40,13 @@ describe('kibana_map tests', function () { teardownDOM(); }); - it('should instantiate with world in view', function () { + it('should instantiate at zoom level 2', function () { const bounds = kibanaMap.getBounds(); - expect(bounds.bottom_right.lon).to.equal(180); - expect(bounds.top_left.lon).to.equal(-180); + expect(bounds.bottom_right.lon).to.equal(90); + expect(bounds.top_left.lon).to.equal(-90); expect(kibanaMap.getCenter().lon).to.equal(0); expect(kibanaMap.getCenter().lat).to.equal(0); - expect(kibanaMap.getZoomLevel()).to.equal(1); + expect(kibanaMap.getZoomLevel()).to.equal(2); }); it('should resize to fit container', function () { diff --git a/src/ui/public/vis_maps/__tests__/service_settings.js b/src/core_plugins/tile_map/public/__tests__/service_settings.js similarity index 95% rename from src/ui/public/vis_maps/__tests__/service_settings.js rename to src/core_plugins/tile_map/public/__tests__/service_settings.js index 5bcbf214d2f679..87ff9272b6fa67 100644 --- a/src/ui/public/vis_maps/__tests__/service_settings.js +++ b/src/core_plugins/tile_map/public/__tests__/service_settings.js @@ -15,19 +15,18 @@ describe('service_settings (FKA tilemaptest)', function () { const manifestUrl2 = 'https://foobar/v1/manifest'; const manifest = { - 'services': [ - { - 'id': 'tiles_v2', - 'name': 'Elastic Tile Service', - 'manifest': tmsManifestUrl, - 'type': 'tms' - }, - { - 'id': 'geo_layers', - 'name': 'Elastic Layer Service', - 'manifest': vectorManifestUrl, - 'type': 'file' - } + 'services': [{ + 'id': 'tiles_v2', + 'name': 'Elastic Tile Service', + 'manifest': tmsManifestUrl, + 'type': 'tms' + }, + { + 'id': 'geo_layers', + 'name': 'Elastic Layer Service', + 'manifest': vectorManifestUrl, + 'type': 'file' + } ] }; diff --git a/src/core_plugins/tile_map/public/__tests__/tilemap_settings.js b/src/core_plugins/tile_map/public/__tests__/tilemap_settings.js deleted file mode 100644 index e5d8982aac2e50..00000000000000 --- a/src/core_plugins/tile_map/public/__tests__/tilemap_settings.js +++ /dev/null @@ -1,61 +0,0 @@ -import expect from 'expect.js'; -import ngMock from 'ng_mock'; -import url from 'url'; - -describe('tilemaptest - TileMapSettingsTests-deprecated', function () { - let tilemapSettings; - let loadSettings; - - beforeEach(ngMock.module('kibana', ($provide) => { - $provide.decorator('tilemapsConfig', () => ({ - manifestServiceUrl: 'https://proxy-tiles.elastic.co/v1/manifest', - deprecated: { - isOverridden: true, - config: { - url: 'https://tiles.elastic.co/v1/default/{z}/{x}/{y}.png?my_app_name=kibana_tests', - options: { - minZoom: 1, - maxZoom: 10, - attribution: '© [Elastic Tile Service](https://www.elastic.co/elastic_tile_service)' - } - }, - } - })); - })); - - beforeEach(ngMock.inject(function ($injector, $rootScope) { - tilemapSettings = $injector.get('tilemapSettings'); - - loadSettings = () => { - tilemapSettings.loadSettings(); - $rootScope.$digest(); - }; - })); - - describe('getting settings', function () { - beforeEach(function () { - loadSettings(); - }); - - it('should get url', function () { - - const mapUrl = tilemapSettings.getUrl(); - expect(mapUrl).to.contain('{x}'); - expect(mapUrl).to.contain('{y}'); - expect(mapUrl).to.contain('{z}'); - - const urlObject = url.parse(mapUrl, true); - expect(urlObject.hostname).to.be('tiles.elastic.co'); - expect(urlObject.query).to.have.property('my_app_name', 'kibana_tests'); - - }); - - it('should get options', function () { - const options = tilemapSettings.getTMSOptions(); - expect(options).to.have.property('minZoom'); - expect(options).to.have.property('maxZoom'); - expect(options).to.have.property('attribution'); - }); - - }); -}); diff --git a/src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js b/src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js deleted file mode 100644 index 3f34c40e9c1edf..00000000000000 --- a/src/core_plugins/tile_map/public/__tests__/tilemap_settings_mocked.js +++ /dev/null @@ -1,140 +0,0 @@ -import expect from 'expect.js'; -import ngMock from 'ng_mock'; -import url from 'url'; - -describe('tilemaptest - TileMapSettingsTests-mocked', function () { - let tilemapSettings; - let tilemapsConfig; - let loadSettings; - - beforeEach(ngMock.module('kibana', ($provide) => { - $provide.decorator('tilemapsConfig', () => ({ - manifestServiceUrl: 'http://foo.bar/manifest', - deprecated: { - isOverridden: false, - config: { - url: '', - options: { - minZoom: 1, - maxZoom: 10, - attribution: '© [Elastic Tile Service](https://www.elastic.co/elastic_tile_service)' - } - }, - } - })); - })); - - beforeEach(ngMock.inject(($injector, $httpBackend) => { - tilemapSettings = $injector.get('tilemapSettings'); - tilemapsConfig = $injector.get('tilemapsConfig'); - - loadSettings = (expectedUrl) => { - // body and headers copied from https://proxy-tiles.elastic.co/v1/manifest - const MANIFEST_BODY = `{ - "services":[ - { - "id":"road_map", - "url":"https://proxy-tiles.elastic.co/v1/default/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana", - "minZoom":0, - "maxZoom":12, - "attribution":"© [Elastic Tile Service](https://www.elastic.co/elastic-tile-service)" - } - ] - }`; - - const MANIFEST_HEADERS = { - 'access-control-allow-methods': 'GET, OPTIONS', - 'access-control-allow-origin': '*', - 'content-length': `${MANIFEST_BODY.length}`, - 'content-type': 'application/json; charset=utf-8', - date: (new Date()).toUTCString(), - server: 'tileprox/20170102101655-a02e54d', - status: '200', - }; - - $httpBackend - .expect('GET', expectedUrl ? expectedUrl : () => true) - .respond(MANIFEST_BODY, MANIFEST_HEADERS); - - tilemapSettings.loadSettings(); - - $httpBackend.flush(); - }; - })); - - afterEach(ngMock.inject($httpBackend => { - $httpBackend.verifyNoOutstandingRequest(); - $httpBackend.verifyNoOutstandingExpectation(); - })); - - describe('getting settings', function () { - beforeEach(() => { - loadSettings(); - }); - - - it('should get url', async function () { - - const mapUrl = tilemapSettings.getUrl(); - expect(mapUrl).to.contain('{x}'); - expect(mapUrl).to.contain('{y}'); - expect(mapUrl).to.contain('{z}'); - - const urlObject = url.parse(mapUrl, true); - expect(urlObject).to.have.property('hostname', 'proxy-tiles.elastic.co'); - expect(urlObject.query).to.have.property('my_app_name', 'kibana'); - expect(urlObject.query).to.have.property('elastic_tile_service_tos', 'agree'); - expect(urlObject.query).to.have.property('my_app_version'); - - }); - - it('should get options', async function () { - const options = tilemapSettings.getTMSOptions(); - expect(options).to.have.property('minZoom', 0); - expect(options).to.have.property('maxZoom', 12); - expect(options).to.have.property('attribution').contain('©'); // html entity for ©, ensures that attribution is escaped - }); - - }); - - describe('modify', function () { - function assertQuery(expected) { - const mapUrl = tilemapSettings.getUrl(); - const urlObject = url.parse(mapUrl, true); - Object.keys(expected).forEach(key => { - expect(urlObject.query).to.have.property(key, expected[key]); - }); - } - - it('accepts an object', () => { - tilemapSettings.addQueryParams({ foo: 'bar' }); - loadSettings(); - assertQuery({ foo: 'bar' }); - }); - - it('merged additions with previous values', () => { - // ensure that changes are always additive - tilemapSettings.addQueryParams({ foo: 'bar' }); - tilemapSettings.addQueryParams({ bar: 'stool' }); - loadSettings(); - assertQuery({ foo: 'bar', bar: 'stool' }); - }); - - it('overwrites conflicting previous values', () => { - // ensure that conflicts are overwritten - tilemapSettings.addQueryParams({ foo: 'bar' }); - tilemapSettings.addQueryParams({ bar: 'stool' }); - tilemapSettings.addQueryParams({ foo: 'tstool' }); - loadSettings(); - assertQuery({ foo: 'tstool', bar: 'stool' }); - }); - - it('merges query params into manifest request', () => { - tilemapSettings.addQueryParams({ foo: 'bar' }); - tilemapsConfig.manifestServiceUrl = 'http://test.com/manifest?v=1'; - loadSettings('http://test.com/manifest?v=1&my_app_version=1.2.3&foo=bar'); - }); - - }); - -}); diff --git a/src/core_plugins/tile_map/public/editors/tile_map.html b/src/core_plugins/tile_map/public/editors/tile_map.html index b215b68bb0e188..e4201184dde1e8 100644 --- a/src/core_plugins/tile_map/public/editors/tile_map.html +++ b/src/core_plugins/tile_map/public/editors/tile_map.html @@ -4,8 +4,8 @@
diff --git a/src/core_plugins/tile_map/public/kibana_map.js b/src/core_plugins/tile_map/public/kibana_map.js index da48554c5220ef..e657deff532eb9 100644 --- a/src/core_plugins/tile_map/public/kibana_map.js +++ b/src/core_plugins/tile_map/public/kibana_map.js @@ -101,7 +101,7 @@ export class KibanaMap extends EventEmitter { minZoom: options.minZoom, maxZoom: options.maxZoom, center: options.center ? options.center : [0, 0], - zoom: options.zoom ? options.zoom : 0 + zoom: options.zoom ? options.zoom : 2 }; this._leafletMap = L.map(containerNode, leafletOptions); diff --git a/src/ui/public/vis_maps/lib/service_settings.js b/src/core_plugins/tile_map/public/lib/service_settings.js similarity index 100% rename from src/ui/public/vis_maps/lib/service_settings.js rename to src/core_plugins/tile_map/public/lib/service_settings.js diff --git a/src/core_plugins/tile_map/public/lib/tilemap_settings.js b/src/core_plugins/tile_map/public/lib/tilemap_settings.js deleted file mode 100644 index 54571ecc0d3a5f..00000000000000 --- a/src/core_plugins/tile_map/public/lib/tilemap_settings.js +++ /dev/null @@ -1,208 +0,0 @@ -import { uiModules } from 'ui/modules'; -import _ from 'lodash'; -import marked from 'marked'; -import { modifyUrl } from 'ui/url'; - -marked.setOptions({ - gfm: true, // Github-flavored markdown - sanitize: true // Sanitize HTML tags -}); - -uiModules.get('kibana') - .service('tilemapSettings', function ($http, tilemapsConfig, $sanitize, kbnVersion) { - const attributionFromConfig = $sanitize(marked(tilemapsConfig.deprecated.config.options.attribution || '')); - const optionsFromConfig = _.assign({}, tilemapsConfig.deprecated.config.options, { attribution: attributionFromConfig }); - const extendUrl = (url, props) => ( - modifyUrl(url, parsed => _.merge(parsed, props)) - ); - - /** - * Unescape a url template that was escaped by encodeURI() so leaflet - * will be able to correctly locate the varables in the template - * @param {String} url - * @return {String} - */ - const unescapeTemplateVars = url => { - const ENCODED_TEMPLATE_VARS_RE = /%7B(\w+?)%7D/g; - return url.replace(ENCODED_TEMPLATE_VARS_RE, (total, varName) => `{${varName}}`); - }; - - class TilemapSettings { - - constructor() { - - this._queryParams = { - my_app_version: kbnVersion - }; - this._error = null; - - //initialize settings with the default of the configuration - this._url = tilemapsConfig.deprecated.config.url; - this._tmsOptions = optionsFromConfig; - - this._invalidateSettings(); - - } - - - _invalidateSettings() { - - this._settingsInitialized = false; - this._loadSettings = _.once(async() => { - - if (tilemapsConfig.deprecated.isOverridden) {//if settings are overridden, we will use those. - this._settingsInitialized = true; - } - - if (this._settingsInitialized) { - return true; - } - - return this._getTileServiceManifest(tilemapsConfig.manifestServiceUrl, this._queryParams) - .then(response => { - const service = _.get(response, 'data.services[0]'); - if (!service) { - throw new Error('Manifest response does not include sufficient service data.'); - } - - this._error = null; - this._tmsOptions = { - attribution: $sanitize(marked(service.attribution)), - minZoom: service.minZoom, - maxZoom: service.maxZoom, - subdomains: service.subdomains || [] - }; - - this._url = unescapeTemplateVars(extendUrl(service.url, { - query: this._queryParams - })); - - this._settingsInitialized = true; - }) - .catch(e => { - this._settingsInitialized = true; - - if (!e) { - e = new Error('Unkown error'); - } - - if (!(e instanceof Error)) { - e = new Error(e.data || `status ${e.statusText || e.status}`); - } - - this._error = new Error(`Could not retrieve manifest from the tile service: ${e.message}`); - }) - .then(() => { - return true; - }); - }); - } - - /** - * Must be called before getUrl/getTMSOptions/getMapOptions can be called. - */ - loadSettings() { - return this._loadSettings(); - } - - /** - * Add optional query-parameters for the request. - * These are only applied when requesting dfrom the manifest. - * - * @param additionalQueryParams - */ - addQueryParams(additionalQueryParams) { - - //check if there are any changes in the settings. - let changes = false; - for (const key in additionalQueryParams) { - if (additionalQueryParams.hasOwnProperty(key)) { - if (additionalQueryParams[key] !== this._queryParams[key]) { - changes = true; - break; - } - } - } - - if (changes) { - this._queryParams = _.assign({}, this._queryParams, additionalQueryParams); - this._invalidateSettings(); - } - - } - - /** - * Get the url of the default TMS - * @return {string} - */ - getUrl() { - if (!this._settingsInitialized) { - throw new Error('Cannot retrieve url before calling .loadSettings first'); - } - return this._url; - } - - /** - * Get the options of the default TMS - * @return {{}} - */ - getTMSOptions() { - if (!this._settingsInitialized) { - throw new Error('Cannot retrieve options before calling .loadSettings first'); - } - return this._tmsOptions; - } - - - /** - * @return {{maxZoom: (*|number), minZoom: (*|number)}} - */ - getMinMaxZoom(isWMSEnabled) { - if (isWMSEnabled) { - return { - minZoom: 0, - maxZoom: 18 - }; - } - - //Otherwise, we use the settings from the yml. - //note that it is no longer possible to only override the zoom-settings, since all options are read from the manifest - //by default. - //For a custom configuration, users will need to override tilemap.url as well. - return { - minZoom: this._tmsOptions.minZoom, - maxZoom: this._tmsOptions.maxZoom - }; - - } - - isInitialized() { - return this._settingsInitialized; - } - - - /** - * Checks if there was an error during initialization of the parameters - */ - hasError() { - return this._error !== null; - } - - getError() { - return this._error; - } - - /** - * Make this a method to allow for overrides by test code - */ - _getTileServiceManifest(manifestUrl) { - return $http({ - url: extendUrl(manifestUrl, { query: this._queryParams }), - method: 'GET' - }); - } - - } - - return new TilemapSettings(); - }); diff --git a/src/core_plugins/tile_map/public/maps_renderbot.js b/src/core_plugins/tile_map/public/maps_renderbot.js deleted file mode 100644 index e8dd48a9aaec93..00000000000000 --- a/src/core_plugins/tile_map/public/maps_renderbot.js +++ /dev/null @@ -1,258 +0,0 @@ -import $ from 'jquery'; -import _ from 'lodash'; -import { FilterBarPushFilterProvider } from 'ui/filter_bar/push_filter'; -import { KibanaMap } from './kibana_map'; -import { GeohashLayer } from './geohash_layer'; -import './lib/tilemap_settings'; -import './styles/_tilemap.less'; - - -module.exports = function MapsRenderbotFactory(Private, $injector, tilemapSettings, Notifier, courier, getAppState) { - - const notify = new Notifier({ location: 'Tilemap' }); - - class MapsRenderbot { - - constructor(vis, $el, uiState) { - this.vis = vis; - this.$el = $el; - this.uiState = uiState; - this._geohashLayer = null; - this._kibanaMap = null; - this._$container = $el; - this._kibanaMapReady = this._makeKibanaMap($el); - - this._baseLayerDirty = true; - this._dataDirty = true; - this._paramsDirty = true; - } - - resize() { - if (this._kibanaMap) { - this._kibanaMap.resize(); - } - } - - async _makeKibanaMap() { - - if (!tilemapSettings.isInitialized()) { - await tilemapSettings.loadSettings(); - } - - if (tilemapSettings.getError()) { - //Still allow the visualization to be built, but show a toast that there was a problem retrieving map settings - //Even though the basemap will not display, the user will at least still see the overlay data - notify.warning(tilemapSettings.getError().message); - } - - if (this._kibanaMap) { - this._kibanaMap.destroy(); - } - const containerElement = $(this._$container)[0]; - const options = _.clone(this._getMinMaxZoom()); - const uiState = this.vis.getUiState(); - const zoomFromUiState = parseInt(uiState.get('mapZoom')); - const centerFromUIState = uiState.get('mapCenter'); - options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.type.visConfig.defaults.mapZoom; - options.center = centerFromUIState ? centerFromUIState : this.vis.type.visConfig.defaults.mapCenter; - - this._kibanaMap = new KibanaMap(containerElement, options); - this._kibanaMap.addDrawControl(); - this._kibanaMap.addFitControl(); - this._kibanaMap.addLegendControl(); - this._kibanaMap.persistUiStateForVisualization(this.vis); - - let previousPrecision = this._kibanaMap.getAutoPrecision(); - let precisionChange = false; - this._kibanaMap.on('zoomchange', () => { - precisionChange = (previousPrecision !== this._kibanaMap.getAutoPrecision()); - previousPrecision = this._kibanaMap.getAutoPrecision(); - }); - this._kibanaMap.on('zoomend', () => { - - const isAutoPrecision = _.get(this._chartData, 'geohashGridAgg.params.autoPrecision', true); - if (!isAutoPrecision) { - return; - } - - this._dataDirty = true; - if (precisionChange) { - courier.fetch(); - } else { - this._recreateGeohashLayer(); - this._dataDirty = false; - this._doRenderComplete(); - } - }); - - - this._kibanaMap.on('drawCreated:rectangle', event => { - addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds); - }); - this._kibanaMap.on('drawCreated:polygon', event => { - addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_polygon', { points: event.points }); - }); - this._kibanaMap.on('baseLayer:loaded', () => { - this._baseLayerDirty = false; - this._doRenderComplete(); - }); - this._kibanaMap.on('baseLayer:loading', () => { - this._baseLayerDirty = true; - }); - } - - _getMinMaxZoom() { - const mapParams = this._getMapsParams(); - return tilemapSettings.getMinMaxZoom(mapParams.wms.enabled); - } - - _recreateGeohashLayer() { - if (this._geohashLayer) { - this._kibanaMap.removeLayer(this._geohashLayer); - } - if (!this._geohashGeoJson) { - return; - } - const geohashOptions = this._getGeohashOptions(); - this._geohashLayer = new GeohashLayer(this._chartData.geoJson, geohashOptions, this._kibanaMap.getZoomLevel(), this._kibanaMap); - this._kibanaMap.addLayer(this._geohashLayer); - } - - - /** - * called on data change - * @param esResponse - */ - async render(esResponse) { - this._dataDirty = true; - this._kibanaMapReady.then(() => { - this._chartData = esResponse; - this._geohashGeoJson = this._chartData.geoJson; - this._recreateGeohashLayer(); - this._kibanaMap.useUiStateFromVisualization(this.vis); - this._kibanaMap.resize(); - this._dataDirty = false; - this._doRenderComplete(); - }); - } - - destroy() { - if (this._kibanaMap) { - this._kibanaMap.destroy(); - } - } - - /** - * called on options change (vis.params change) - */ - updateParams() { - - this._paramsDirty = true; - this._kibanaMapReady.then(async() => { - const mapParams = this._getMapsParams(); - const { minZoom, maxZoom } = this._getMinMaxZoom(); - - if (mapParams.wms.enabled) { - - if (maxZoom > this._kibanaMap.getMaxZoomLevel()) { - this._geohashLayer = null; - this._kibanaMapReady = this._makeKibanaMap(); - } - - this._kibanaMap.setBaseLayer({ - baseLayerType: 'wms', - options: { - minZoom: minZoom, - maxZoom: maxZoom, - url: mapParams.wms.url, - ...mapParams.wms.options - } - }); - } else { - - if (maxZoom < this._kibanaMap.getMaxZoomLevel()) { - this._geohashLayer = null; - this._kibanaMapReady = this._makeKibanaMap(); - this._kibanaMap.setZoomLevel(maxZoom); - } - - if (!tilemapSettings.hasError()) { - const url = tilemapSettings.getUrl(); - const options = tilemapSettings.getTMSOptions(); - this._kibanaMap.setBaseLayer({ - baseLayerType: 'tms', - options: { url, ...options } - }); - } - } - const geohashOptions = this._getGeohashOptions(); - if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) { - this._recreateGeohashLayer(); - } - - this._kibanaMap.setDesaturateBaseLayer(mapParams.isDesaturated); - this._kibanaMap.setShowTooltip(mapParams.addTooltip); - this._kibanaMap.setLegendPosition(mapParams.legendPosition); - - this._kibanaMap.useUiStateFromVisualization(this.vis); - this._kibanaMap.resize(); - this._paramsDirty = false; - this._doRenderComplete(); - }); - } - - _getMapsParams() { - return _.assign( - {}, - this.vis.type.visConfig.defaults, - { - type: this.vis.type.name, - hasTimeField: this.vis.indexPattern && this.vis.indexPattern.hasTimeField()// Add attribute which determines whether an index is time based or not. - }, - this.vis.params - ); - } - - _getGeohashOptions() { - const newParams = this._getMapsParams(); - return { - valueFormatter: this._chartData ? this._chartData.valueFormatter : null, - tooltipFormatter: this._chartData ? this._chartData.tooltipFormatter : null, - mapType: newParams.mapType, - heatmap: { - heatBlur: newParams.heatBlur, - heatMaxZoom: newParams.heatMaxZoom, - heatMinOpacity: newParams.heatMinOpacity, - heatRadius: newParams.heatRadius - } - }; - } - - _doRenderComplete() { - if (this._paramsDirty || this._dataDirty || this._baseLayerDirty) { - return false; - } - this.$el.trigger('renderComplete'); - return true; - } - - } - - function addSpatialFilter(agg, filterName, filterData) { - if (!agg) { - return; - } - - const indexPatternName = agg.vis.indexPattern.id; - const field = agg.fieldName(); - const filter = {}; - filter[filterName] = { ignore_unmapped: true }; - filter[filterName][field] = filterData; - - const putFilter = Private(FilterBarPushFilterProvider)(getAppState()); - return putFilter(filter, false, indexPatternName); - } - - - return MapsRenderbot; -}; diff --git a/src/core_plugins/tile_map/public/maps_visualization.js b/src/core_plugins/tile_map/public/maps_visualization.js index 0e2f52e6f2c32b..227de739b4fdaf 100644 --- a/src/core_plugins/tile_map/public/maps_visualization.js +++ b/src/core_plugins/tile_map/public/maps_visualization.js @@ -1,33 +1,281 @@ import 'ui/vislib'; import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; -import MapsVisTypeMapsRenderbotProvider from './maps_renderbot'; import $ from 'jquery'; +import _ from 'lodash'; +import { FilterBarPushFilterProvider } from 'ui/filter_bar/push_filter'; +import { KibanaMap } from './kibana_map'; +import { GeohashLayer } from './geohash_layer'; +import './lib/service_settings'; +import './styles/_tilemap.less'; -export function MapsVisualizationProvider(Private) { - const MapsRenderbot = Private(MapsVisTypeMapsRenderbotProvider); +export function MapsVisualizationProvider(Private, $injector, serviceSettings, Notifier, courier, getAppState) { - class MapsVisController { - constructor(el, vis) { - this.el = $(el); - this._vis = vis; - this.renderbot = new MapsRenderbot(this._vis, this.el, vis.getUiState()); + const notify = new Notifier({ location: 'Coordinate Map' }); + + class MapsVisualization { + + constructor(element, vis) { + + this.vis = vis; + this.$el = $(element); + this._$container = this.$el; + + + this._geohashLayer = null; + this._kibanaMap = null; + this._kibanaMapReady = this._makeKibanaMap(); + this._baseLayerDirty = true; + this._dataDirty = true; + this._paramsDirty = true; + this._currentParams = null; + } + + destroy() { + if (this._kibanaMap) { + this._kibanaMap.destroy(); + } } async render(esResponse) { - //todo: should notify of render-completeness, which it isn't doing correctly now - this.renderbot.render(esResponse); + + if (esResponse && typeof esResponse.geohashGridAgg === 'undefined') { + this.updateParams(); + return; + } + + //todo: do render complete! + this._dataDirty = true; + this._kibanaMapReady.then(() => { + this._chartData = esResponse; + this._geohashGeoJson = this._chartData.geoJson; + this._recreateGeohashLayer(); + this._kibanaMap.useUiStateFromVisualization(this.vis); + this._kibanaMap.resize(); + this._dataDirty = false; + this._doRenderComplete(); + }); } resize() { - this.renderbot.resize(); + if (this._kibanaMap) { + this._kibanaMap.resize(); + } } - destroy() { - this.renderbot.destroy(); + //********************************************************************************************************** + + async _makeKibanaMap() { + + try { + this._tmsService = await serviceSettings.getTMSService(); + this._tmsError = null; + } catch (e) { + this._tmsService = null; + this._tmsError = e; + notify.warning(e.message); + } + + if (this._kibanaMap) { + this._kibanaMap.destroy(); + } + const containerElement = $(this._$container)[0]; + const options = _.clone(this._getMinMaxZoom()); + const uiState = this.vis.getUiState(); + const zoomFromUiState = parseInt(uiState.get('mapZoom')); + const centerFromUIState = uiState.get('mapCenter'); + options.zoom = !isNaN(zoomFromUiState) ? zoomFromUiState : this.vis.type.visConfig.defaults.mapZoom; + options.center = centerFromUIState ? centerFromUIState : this.vis.type.visConfig.defaults.mapCenter; + + this._kibanaMap = new KibanaMap(containerElement, options); + this._kibanaMap.addDrawControl(); + this._kibanaMap.addFitControl(); + this._kibanaMap.addLegendControl(); + this._kibanaMap.persistUiStateForVisualization(this.vis); + + let previousPrecision = this._kibanaMap.getAutoPrecision(); + let precisionChange = false; + this._kibanaMap.on('zoomchange', () => { + precisionChange = (previousPrecision !== this._kibanaMap.getAutoPrecision()); + previousPrecision = this._kibanaMap.getAutoPrecision(); + }); + this._kibanaMap.on('zoomend', () => { + + const isAutoPrecision = _.get(this._chartData, 'geohashGridAgg.params.autoPrecision', true); + if (!isAutoPrecision) { + return; + } + + this._dataDirty = true; + if (precisionChange) { + courier.fetch(); + } else { + this._recreateGeohashLayer(); + this._dataDirty = false; + this._doRenderComplete(); + } + }); + + + this._kibanaMap.on('drawCreated:rectangle', event => { + addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_bounding_box', event.bounds); + }); + this._kibanaMap.on('drawCreated:polygon', event => { + addSpatialFilter(_.get(this._chartData, 'geohashGridAgg'), 'geo_polygon', { points: event.points }); + }); + this._kibanaMap.on('baseLayer:loaded', () => { + this._baseLayerDirty = false; + this._doRenderComplete(); + }); + this._kibanaMap.on('baseLayer:loading', () => { + this._baseLayerDirty = true; + }); + } + + _getMinMaxZoom() { + const mapParams = this._getMapsParams(); + if (this._tmsError) { + return serviceSettings.getFallbackZoomSettings(mapParams.wms.enabled); + } else { + return this._tmsService.getMinMaxZoom(mapParams.wms.enabled); + } + } + + _recreateGeohashLayer() { + if (this._geohashLayer) { + this._kibanaMap.removeLayer(this._geohashLayer); + } + if (!this._geohashGeoJson) { + return; + } + const geohashOptions = this._getGeohashOptions(); + this._geohashLayer = new GeohashLayer(this._chartData.geoJson, geohashOptions, this._kibanaMap.getZoomLevel(), this._kibanaMap); + this._kibanaMap.addLayer(this._geohashLayer); + } + + + /** + * called on options change (vis.params change) + */ + async updateParams() { + + + this._paramsDirty = true; + await this._kibanaMapReady; + + const mapParams = this._getMapsParams(); + if (_.eq(this._currentParams, mapParams)) { + this._paramsDirty = false; + return; + } + + this._currentParams = mapParams; + const { minZoom, maxZoom } = this._getMinMaxZoom(); + + if (mapParams.wms.enabled) { + + if (maxZoom > this._kibanaMap.getMaxZoomLevel()) { + this._geohashLayer = null; + this._kibanaMapReady = this._makeKibanaMap(); + } + + this._kibanaMap.setBaseLayer({ + baseLayerType: 'wms', + options: { + minZoom: minZoom, + maxZoom: maxZoom, + url: mapParams.wms.url, + ...mapParams.wms.options + } + }); + } else { + + if (maxZoom < this._kibanaMap.getMaxZoomLevel()) { + this._geohashLayer = null; + this._kibanaMapReady = this._makeKibanaMap(); + this._kibanaMap.setZoomLevel(maxZoom); + } + + if (!this._tmsError) { + const url = this._tmsService.getUrl(); + const options = this._tmsService.getTMSOptions(); + this._kibanaMap.setBaseLayer({ + baseLayerType: 'tms', + options: { url, ...options } + }); + } + } + const geohashOptions = this._getGeohashOptions(); + if (!this._geohashLayer || !this._geohashLayer.isReusable(geohashOptions)) { + this._recreateGeohashLayer(); + } + + } + + _getMapsParams() { + return _.assign( + {}, + this.vis.type.visConfig.defaults, + { type: this.vis.type.name }, + this.vis.params + ); + } + + _getGeohashOptions() { + const newParams = this._getMapsParams(); + return { + valueFormatter: this._chartData ? this._chartData.valueFormatter : null, + tooltipFormatter: this._chartData ? this._chartData.tooltipFormatter : null, + mapType: newParams.mapType, + heatmap: { + heatBlur: newParams.heatBlur, + heatMaxZoom: newParams.heatMaxZoom, + heatMinOpacity: newParams.heatMinOpacity, + heatRadius: newParams.heatRadius + } + }; + } + + _doRenderComplete() { + + return; + + if (this._paramsDirty || this._dataDirty || this._baseLayerDirty) { + const mapParams = this._getMapsParams(); + this._kibanaMap.setDesaturateBaseLayer(mapParams.isDesaturated); + this._kibanaMap.setShowTooltip(mapParams.addTooltip); + this._kibanaMap.setLegendPosition(mapParams.legendPosition); + + this._kibanaMap.useUiStateFromVisualization(this.vis); + this._kibanaMap.resize(); + this._paramsDirty = false; + + + // this._doRenderComplete(); + + return; + } + this.$el.trigger('renderComplete'); } + } - return MapsVisController; + function addSpatialFilter(agg, filterName, filterData) { + if (!agg) { + return; + } + + const indexPatternName = agg.vis.indexPattern.id; + const field = agg.fieldName(); + const filter = {}; + filter[filterName] = { ignore_unmapped: true }; + filter[filterName][field] = filterData; + + const putFilter = Private(FilterBarPushFilterProvider)(getAppState()); + return putFilter(filter, false, indexPatternName); + } + + + return MapsVisualization; } diff --git a/src/core_plugins/timelion/public/vis/index.js b/src/core_plugins/timelion/public/vis/index.js index f45b7a518ed8ff..6c32a909506c6e 100644 --- a/src/core_plugins/timelion/public/vis/index.js +++ b/src/core_plugins/timelion/public/vis/index.js @@ -2,11 +2,11 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { CATEGORY } from 'ui/vis/vis_category'; import image from '../images/icon-timelion.svg'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { RequestHandlersRegistryProvider } from 'ui/registry/request_handlers'; +import { VisRequestHandlersRegistryProvider } from 'ui/registry/vis_request_handlers'; import { TimelionRequestHandlerProvider } from './timelion_request_handler'; define(function (require) { - // we also need to load the controller and used by the template + // we also need to load the controller and directive used by the template require('plugins/timelion/vis/timelion_vis_controller'); require('plugins/timelion/directives/timelion_expression_input'); @@ -15,7 +15,7 @@ define(function (require) { // register the provider with the visTypes registry so that other know it exists VisTypesRegistryProvider.register(TimelionVisProvider); - RequestHandlersRegistryProvider.register(TimelionRequestHandlerProvider); + VisRequestHandlersRegistryProvider.register(TimelionRequestHandlerProvider); function TimelionVisProvider(Private) { const VisFactory = Private(VisFactoryProvider); diff --git a/src/ui/public/agg_types/__tests__/agg_param_writer.js b/src/ui/public/agg_types/__tests__/agg_param_writer.js index ea46c558fd3ebb..ffdd9c475f37a8 100644 --- a/src/ui/public/agg_types/__tests__/agg_param_writer.js +++ b/src/ui/public/agg_types/__tests__/agg_param_writer.js @@ -3,6 +3,7 @@ import { VisProvider } from 'ui/vis'; import { AggTypesIndexProvider } from 'ui/agg_types/index'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import { VisAggConfigProvider } from 'ui/vis/agg_config'; // eslint-disable-next-line kibana-custom/no-default-export export default function AggParamWriterHelper(Private) { @@ -10,6 +11,7 @@ export default function AggParamWriterHelper(Private) { const aggTypes = Private(AggTypesIndexProvider); const visTypes = Private(VisTypesRegistryProvider); const stubbedLogstashIndexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); + const AggConfig = Private(VisAggConfigProvider); /** * Helper object for writing aggParams. Specify an aggType and it will find a vis & schema, and @@ -66,7 +68,7 @@ export default function AggParamWriterHelper(Private) { } self.vis = new Vis(self.indexPattern, { - type: self.visType + type: self.visType.name }); } @@ -79,17 +81,26 @@ export default function AggParamWriterHelper(Private) { if (self.aggType.type === 'metrics') { paramValues.field = _.sample(self.indexPattern.fields.byType.number); } else { - paramValues.field = _.sample(self.indexPattern.fields.byType.string); + const type = self.aggType.params.byName.field.filterFieldTypes || 'string'; + let field; + do { + field = _.sample(self.indexPattern.fields.byType[type]); + } while (!field.aggregatable); + paramValues.field = field.name; } } + + const agg = new AggConfig(self.vis, { + id: 1, + schema: self.visAggSchema.name, + type: self.aggType.name, + params: paramValues + }); + self.vis.setState({ type: self.vis.type.name, - aggs: [{ - type: self.aggType, - schema: self.visAggSchema, - params: paramValues - }] + aggs: [agg.toJSON()] }); const aggConfig = _.find(self.vis.aggs, function (aggConfig) { diff --git a/src/ui/public/registry/response_handlers.js b/src/ui/public/registry/response_handlers.js deleted file mode 100644 index 6cc7203f1d3c71..00000000000000 --- a/src/ui/public/registry/response_handlers.js +++ /dev/null @@ -1,6 +0,0 @@ -import { uiRegistry } from 'ui/registry/_registry'; -export const ResponseHandlersRegistryProvider = uiRegistry({ - name: 'responseHandlers', - index: ['name'], - order: ['title'] -}); diff --git a/src/ui/public/registry/editor_types.js b/src/ui/public/registry/vis_editor_types.js similarity index 52% rename from src/ui/public/registry/editor_types.js rename to src/ui/public/registry/vis_editor_types.js index 29fc1a7e152c9e..646c63dc621724 100644 --- a/src/ui/public/registry/editor_types.js +++ b/src/ui/public/registry/vis_editor_types.js @@ -1,7 +1,7 @@ import { uiRegistry } from 'ui/registry/_registry'; -export const EditorTypesRegistryProvider = uiRegistry({ - name: 'editorTypes', +export const VisEditorTypesRegistryProvider = uiRegistry({ + name: 'visEditorTypes', index: ['name'], order: ['title'] }); diff --git a/src/ui/public/registry/request_handlers.js b/src/ui/public/registry/vis_request_handlers.js similarity index 50% rename from src/ui/public/registry/request_handlers.js rename to src/ui/public/registry/vis_request_handlers.js index 85e6a85ad07fbd..bdcd55fb0cc8d6 100644 --- a/src/ui/public/registry/request_handlers.js +++ b/src/ui/public/registry/vis_request_handlers.js @@ -1,6 +1,6 @@ import { uiRegistry } from 'ui/registry/_registry'; -export const RequestHandlersRegistryProvider = uiRegistry({ - name: 'requestHandlers', +export const VisRequestHandlersRegistryProvider = uiRegistry({ + name: 'visRequestHandlers', index: ['name'], order: ['title'] }); diff --git a/src/ui/public/registry/vis_response_handlers.js b/src/ui/public/registry/vis_response_handlers.js new file mode 100644 index 00000000000000..dd1bdc3bcf627a --- /dev/null +++ b/src/ui/public/registry/vis_response_handlers.js @@ -0,0 +1,6 @@ +import { uiRegistry } from 'ui/registry/_registry'; +export const VisResponseHandlersRegistryProvider = uiRegistry({ + name: 'visResponseHandlers', + index: ['name'], + order: ['title'] +}); diff --git a/src/ui/public/vis/__tests__/_vis.js b/src/ui/public/vis/__tests__/_vis.js index 2a3c3bc4800848..cc42d661fca88d 100644 --- a/src/ui/public/vis/__tests__/_vis.js +++ b/src/ui/public/vis/__tests__/_vis.js @@ -40,10 +40,6 @@ describe('Vis Class', function () { expect(vis).to.have.property('type'); expect(vis.type).to.eql(visTypes.byName.pie); - expect(vis).to.have.property('listeners'); - expect(vis.listeners).to.have.property('click'); - expect(vis.listeners.click).to.eql(_.noop); - expect(vis).to.have.property('params'); expect(vis.params).to.have.property('isDonut', true); expect(vis).to.have.property('indexPattern', indexPattern); @@ -63,10 +59,6 @@ describe('Vis Class', function () { expect(state).to.have.property('params'); expect(state.params).to.have.property('isDonut', true); - expect(state).to.have.property('listeners'); - expect(state.listeners).to.have.property('click'); - expect(state.listeners.click).to.eql(_.noop); - expect(state).to.have.property('aggs'); expect(state.aggs).to.have.length(3); }); @@ -86,8 +78,6 @@ describe('Vis Class', function () { expect(vis.type).to.eql(visTypes.byName.histogram); expect(vis).to.have.property('aggs'); expect(vis.aggs).to.have.length(1); - expect(vis).to.have.property('listeners'); - expect(vis.listeners).to.eql({}); expect(vis).to.have.property('params'); expect(vis.params).to.have.property('addLegend', true); expect(vis.params).to.have.property('addTooltip', true); diff --git a/src/ui/public/vis/editors/default/agg_add.html b/src/ui/public/vis/editors/default/agg_add.html index 0c0b037a17062d..51e75740812246 100644 --- a/src/ui/public/vis/editors/default/agg_add.html +++ b/src/ui/public/vis/editors/default/agg_add.html @@ -21,7 +21,7 @@