From f78af5fbce467452663d29a289c3d28c9fc2370a Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 29 Nov 2019 15:24:02 +0300 Subject: [PATCH 01/59] Deangularize timelion vis --- .../timelion/public/kibana_services.ts | 38 ++ .../core_plugins/timelion/public/plugin.ts | 2 + .../timelion/public/vis/get_inner_angular.ts | 102 ++++++ .../core_plugins/timelion/public/vis/index.ts | 3 +- .../timelion/public/vis/legacy_imports.ts | 35 ++ .../public/vis/timelion_controller.ts | 106 ++++++ .../public/vis/timelion_vis_legacy_module.ts | 49 +++ .../accessibility/kbn_accessible_click.js | 78 ++-- src/legacy/ui/public/directives/paginate.js | 337 +++++++++--------- .../directives/watch_multi/watch_multi.js | 148 ++++---- 10 files changed, 619 insertions(+), 279 deletions(-) create mode 100644 src/legacy/core_plugins/timelion/public/kibana_services.ts create mode 100644 src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts create mode 100644 src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts create mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts create mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts diff --git a/src/legacy/core_plugins/timelion/public/kibana_services.ts b/src/legacy/core_plugins/timelion/public/kibana_services.ts new file mode 100644 index 0000000000000..4be7029a5f5d5 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/kibana_services.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Panel } from './panels/panel'; + +export interface TimelionKibanaServices { + timelionPanels: Map; +} +let services: TimelionKibanaServices | null = null; + +export function setServices(newServices: TimelionKibanaServices) { + services = newServices; +} + +export function getServices() { + if (!services) { + throw new Error( + 'Kibana services not set - are you trying to import this module from outside of the visualize app?' + ); + } + return services; +} diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index ba8c25c20abea..25fe2439455b5 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -32,6 +32,7 @@ import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; +import { setServices } from './kibana_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { @@ -65,6 +66,7 @@ export class TimelionPlugin implements Plugin, void> { ) { const timelionPanels: Map = new Map(); + setServices({ timelionPanels }); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, http: core.http, diff --git a/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts b/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts new file mode 100644 index 0000000000000..1a04225a04c9f --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// inner angular imports +// these are necessary to bootstrap the local angular. +// They can stay even after NP cutover +import angular from 'angular'; +import 'ui/angular-bootstrap'; +import 'angular-sortable-view'; +import 'brace/mode/hjson'; +import 'brace/ext/searchbox'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +import { CoreStart, LegacyCoreStart } from 'kibana/public'; +import { + PrivateProvider, + PaginateDirectiveProvider, + PaginateControlsDirectiveProvider, + watchMultiDecorator, + KbnAccessibleClickProvider, + StateManagementConfigProvider, + configureAppAngularModule, +} from './legacy_imports'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap']; + +export function getAngularModule(name: string, core: CoreStart) { + const uiModule = getInnerAngular(name, core); + configureAppAngularModule(uiModule, core as LegacyCoreStart, true); + return uiModule; +} + +let initialized = false; + +export function getInnerAngular(name = 'kibana/timelion_vis', core: CoreStart) { + if (!initialized) { + createLocalPrivateModule(); + createLocalI18nModule(); + createLocalConfigModule(core.uiSettings); + createLocalPaginateModule(); + initialized = true; + } + return angular + .module(name, [ + ...thirdPartyAngularDependencies, + 'timelionVisPaginate', + 'timelionVisConfig', + 'timelionVisPrivate', + 'timelionVisI18n', + ]) + .config(watchMultiDecorator) + .directive('kbnAccessibleClick', KbnAccessibleClickProvider); +} + +function createLocalPrivateModule() { + angular.module('timelionVisPrivate', []).provider('Private', PrivateProvider); +} + +function createLocalConfigModule(uiSettings: any) { + angular + .module('timelionVisConfig', ['timelionVisPrivate']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', function() { + return { + $get: () => ({ + get: (value: string) => { + return uiSettings ? uiSettings.get(value) : undefined; + }, + }), + }; + }); +} + +function createLocalI18nModule() { + angular + .module('timelionVisI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createLocalPaginateModule() { + angular + .module('timelionVisPaginate', []) + .directive('paginate', PaginateDirectiveProvider) + .directive('paginateControls', PaginateControlsDirectiveProvider); +} diff --git a/src/legacy/core_plugins/timelion/public/vis/index.ts b/src/legacy/core_plugins/timelion/public/vis/index.ts index 7b82553a24e5b..b93cceeb5c4ac 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.ts +++ b/src/legacy/core_plugins/timelion/public/vis/index.ts @@ -26,6 +26,7 @@ import editorConfigTemplate from './timelion_vis_params.html'; import { TimelionVisualizationDependencies } from '../plugin'; // @ts-ignore import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; +import { TimelionVisController } from './timelion_controller'; export const TIMELION_VIS_NAME = 'timelion'; @@ -41,7 +42,7 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe description: i18n.translate('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - visualization: AngularVisController, + visualization: TimelionVisController, visConfig: { defaults: { expression: '.es(*)', diff --git a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts new file mode 100644 index 0000000000000..0e528811560de --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { npSetup, npStart } from 'ui/new_platform'; +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; + +// @ts-ignore +export { PaginateDirectiveProvider } from 'ui/directives/paginate'; +// @ts-ignore +export { PaginateControlsDirectiveProvider } from 'ui/directives/paginate'; +// @ts-ignore +export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; + +// @ts-ignore +export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { configureAppAngularModule } from 'ui/legacy_compat'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts new file mode 100644 index 0000000000000..9e27a4307fe15 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; +import $ from 'jquery'; + +import { Vis, VisParams } from '../../../visualizations/public'; +import { npStart } from './legacy_imports'; +import { getServices } from '../kibana_services'; +import { getAngularModule } from './get_inner_angular'; +import { initTimelionLegacyModule } from './timelion_vis_legacy_module'; + +const innerAngularName = 'kibana/timelion_vis'; + +export class TimelionVisController { + private timelionVisModule: IModule | undefined; + private injector: auto.IInjectorService | undefined; + el: JQuery; + vis: Vis; + $rootScope: IRootScopeService | null = null; + $scope: (IScope & { [key: string]: any }) | undefined; + $compile: ICompileService | undefined; + + constructor(domeElement: Element, vis: Vis) { + this.el = $(domeElement); + this.vis = vis; + } + + getInjector() { + if (!this.injector) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); + this.injector = angular.bootstrap(mountpoint, [innerAngularName]); + this.el.append(mountpoint); + } + + return this.injector; + } + + initLocalAngular() { + if (!this.timelionVisModule) { + this.timelionVisModule = getAngularModule(innerAngularName, npStart.core); + initTimelionLegacyModule(this.timelionVisModule, getServices().timelionPanels); + } + } + + async render(esResponse: object, visParams: VisParams, status: { [key: string]: boolean }) { + this.initLocalAngular(); + + return new Promise(async (resolve, reject) => { + if (!this.$rootScope) { + const $injector = this.getInjector(); + this.$rootScope = $injector.get('$rootScope'); + this.$compile = $injector.get('$compile'); + } + const updateScope = () => { + if (!this.$scope) { + return; + } + + this.$scope.vis = this.vis; + this.$scope.visState = this.vis.getState(); + this.$scope.esResponse = esResponse; + this.$scope.visParams = visParams; + this.$scope.renderComplete = resolve; + this.$scope.renderFailed = reject; + this.$scope.resize = Date.now(); + this.$scope.updateStatus = status; + this.$scope.$apply(); + }; + + if (!this.$scope && this.$compile) { + this.$scope = this.$rootScope.$new(); + this.$scope.uiState = this.vis.getUiState(); + updateScope(); + this.el.html(this.$compile(this.vis.type.visConfig.template)(this.$scope)); + this.$scope.$apply(); + } else { + updateScope(); + } + }); + } + + destroy() { + if (this.$rootScope) { + this.$rootScope.$destroy(); + this.$rootScope = null; + } + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts new file mode 100644 index 0000000000000..9f48204caa9aa --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IModule } from 'angular'; + +import { Panel } from '../panels/panel'; +// @ts-ignore +import { Chart } from '../directives/chart/chart'; +// @ts-ignore +import { TimelionInterval } from '../directives/timelion_interval/timelion_interval'; +// @ts-ignore +import { TimelionExpInput } from '../directives/timelion_expression_input'; +// @ts-ignore +import { TimelionExpressionSuggestions } from '../directives/timelion_expression_suggestions/timelion_expression_suggestions'; + +/** @internal */ +export const initTimelionLegacyModule = ( + angularIns: IModule, + timelionPanels: Map +): void => { + angularIns + .controller('TimelionVisController', function($scope: any) { + $scope.$on('timelionChartRendered', (event: any) => { + event.stopPropagation(); + $scope.renderComplete(); + }); + }) + .constant('timelionPanels', timelionPanels) + .directive('chart', Chart) + .directive('timelionInterval', TimelionInterval) + .directive('timelionExpressionSuggestions', TimelionExpressionSuggestions) + .directive('timelionExpressionInput', TimelionExpInput); +}; diff --git a/src/legacy/ui/public/accessibility/kbn_accessible_click.js b/src/legacy/ui/public/accessibility/kbn_accessible_click.js index 1abf322daa9a8..0b5016d882c41 100644 --- a/src/legacy/ui/public/accessibility/kbn_accessible_click.js +++ b/src/legacy/ui/public/accessibility/kbn_accessible_click.js @@ -43,52 +43,54 @@ import { } from '@elastic/eui'; import { uiModules } from '../modules'; -uiModules.get('kibana') - .directive('kbnAccessibleClick', function () { - return { - restrict: 'A', - controller: $element => { - $element.on('keydown', e => { +export function KbnAccessibleClickProvider() { + return { + restrict: 'A', + controller: $element => { + $element.on('keydown', e => { // Prevent a scroll from occurring if the user has hit space. - if (e.keyCode === keyCodes.SPACE) { - e.preventDefault(); - } - }); - }, - link: (scope, element, attrs) => { + if (e.keyCode === keyCodes.SPACE) { + e.preventDefault(); + } + }); + }, + link: (scope, element, attrs) => { // The whole point of this directive is to hack in functionality that native buttons provide // by default. - const elementType = element.prop('tagName'); + const elementType = element.prop('tagName'); - if (elementType === 'BUTTON') { - throw new Error(`kbnAccessibleClick doesn't need to be used on a button.`); - } + if (elementType === 'BUTTON') { + throw new Error(`kbnAccessibleClick doesn't need to be used on a button.`); + } - if (elementType === 'A' && attrs.href !== undefined) { - throw new Error(`kbnAccessibleClick doesn't need to be used on a link if it has a href attribute.`); - } + if (elementType === 'A' && attrs.href !== undefined) { + throw new Error(`kbnAccessibleClick doesn't need to be used on a link if it has a href attribute.`); + } - // We're emulating a click action, so we should already have a regular click handler defined. - if (!attrs.ngClick) { - throw new Error('kbnAccessibleClick requires ng-click to be defined on its element.'); - } + // We're emulating a click action, so we should already have a regular click handler defined. + if (!attrs.ngClick) { + throw new Error('kbnAccessibleClick requires ng-click to be defined on its element.'); + } - // If the developer hasn't already specified attributes required for accessibility, add them. - if (attrs.tabindex === undefined) { - element.attr('tabindex', '0'); - } + // If the developer hasn't already specified attributes required for accessibility, add them. + if (attrs.tabindex === undefined) { + element.attr('tabindex', '0'); + } - if (attrs.role === undefined) { - element.attr('role', 'button'); - } + if (attrs.role === undefined) { + element.attr('role', 'button'); + } - element.on('keyup', e => { + element.on('keyup', e => { // Support keyboard accessibility by emulating mouse click on ENTER or SPACE keypress. - if (accessibleClickKeys[e.keyCode]) { + if (accessibleClickKeys[e.keyCode]) { // Delegate to the click handler on the element (assumed to be ng-click). - element.click(); - } - }); - }, - }; - }); + element.click(); + } + }); + }, + }; +} + +uiModules.get('kibana') + .directive('kbnAccessibleClick', KbnAccessibleClickProvider); diff --git a/src/legacy/ui/public/directives/paginate.js b/src/legacy/ui/public/directives/paginate.js index 7ecd5fefe6710..a7ec768ceb3d4 100644 --- a/src/legacy/ui/public/directives/paginate.js +++ b/src/legacy/ui/public/directives/paginate.js @@ -22,209 +22,214 @@ import { i18n } from '@kbn/i18n'; import { uiModules } from '../modules'; import paginateControlsTemplate from './partials/paginate_controls.html'; -uiModules.get('kibana') - .directive('paginate', function ($parse, $compile) { - return { - restrict: 'E', - scope: true, - link: { - pre: function ($scope, $el, attrs) { - if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; - if ($el.find('paginate-controls.paginate-bottom').length === 0 && attrs.bottomControls) { - $el.append($compile('')($scope)); - } - }, - post: function ($scope, $el, attrs) { - if (_.isUndefined(attrs.topControls)) attrs.topControls = false; - if ($el.find('paginate-controls.paginate-top').length === 0 && attrs.topControls) { - $el.prepend($compile('')($scope)); - } - - const paginate = $scope.paginate; - - // add some getters to the controller powered by attributes - paginate.getList = $parse(attrs.list); - paginate.perPageProp = attrs.perPageProp; - - if (attrs.perPage) { - paginate.perPage = attrs.perPage; - $scope.showSelector = false; - } else { - $scope.showSelector = true; - } - - paginate.otherWidthGetter = $parse(attrs.otherWidth); - - paginate.init(); +export function PaginateDirectiveProvider($parse, $compile) { + return { + restrict: 'E', + scope: true, + link: { + pre: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true; + if ($el.find('paginate-controls.paginate-bottom').length === 0 && attrs.bottomControls) { + $el.append($compile('')($scope)); } }, - controllerAs: 'paginate', - controller: function ($scope, $document) { - const self = this; - const ALL = 0; - const allSizeTitle = i18n.translate('common.ui.directives.paginate.size.allDropDownOptionLabel', { - defaultMessage: 'All', - }); + post: function ($scope, $el, attrs) { + if (_.isUndefined(attrs.topControls)) attrs.topControls = false; + if ($el.find('paginate-controls.paginate-top').length === 0 && attrs.topControls) { + $el.prepend($compile('')($scope)); + } - self.sizeOptions = [ - { title: '10', value: 10 }, - { title: '25', value: 25 }, - { title: '100', value: 100 }, - { title: allSizeTitle, value: ALL } - ]; + const paginate = $scope.paginate; - // setup the watchers, called in the post-link function - self.init = function () { + // add some getters to the controller powered by attributes + paginate.getList = $parse(attrs.list); + paginate.perPageProp = attrs.perPageProp; - self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + if (attrs.perPage) { + paginate.perPage = attrs.perPage; + $scope.showSelector = false; + } else { + $scope.showSelector = true; + } - $scope.$watchMulti([ - 'paginate.perPage', - self.perPageProp, - self.otherWidthGetter - ], function (vals, oldVals) { - const intChanges = vals[0] !== oldVals[0]; + paginate.otherWidthGetter = $parse(attrs.otherWidth); - if (intChanges) { - if (!setPerPage(self.perPage)) { + paginate.init(); + } + }, + controllerAs: 'paginate', + controller: function ($scope, $document) { + const self = this; + const ALL = 0; + const allSizeTitle = i18n.translate('common.ui.directives.paginate.size.allDropDownOptionLabel', { + defaultMessage: 'All', + }); + + self.sizeOptions = [ + { title: '10', value: 10 }, + { title: '25', value: 25 }, + { title: '100', value: 100 }, + { title: allSizeTitle, value: ALL } + ]; + + // setup the watchers, called in the post-link function + self.init = function () { + + self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + + $scope.$watchMulti([ + 'paginate.perPage', + self.perPageProp, + self.otherWidthGetter + ], function (vals, oldVals) { + const intChanges = vals[0] !== oldVals[0]; + + if (intChanges) { + if (!setPerPage(self.perPage)) { // if we are not able to set the external value, // render now, otherwise wait for the external value // to trigger the watcher again - self.renderList(); - } - return; + self.renderList(); } + return; + } - self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; - if (self.perPage == null) { - self.perPage = ALL; - return; - } + self.perPage = _.parseInt(self.perPage) || $scope[self.perPageProp]; + if (self.perPage == null) { + self.perPage = ALL; + return; + } - self.renderList(); - }); + self.renderList(); + }); - $scope.$watch('page', self.changePage); - $scope.$watchCollection(self.getList, function (list) { - $scope.list = list; - self.renderList(); - }); - }; + $scope.$watch('page', self.changePage); + $scope.$watchCollection(self.getList, function (list) { + $scope.list = list; + self.renderList(); + }); + }; - self.goToPage = function (number) { - if (number) { - if (number.hasOwnProperty('number')) number = number.number; - $scope.page = $scope.pages[number - 1] || $scope.pages[0]; - } - }; + self.goToPage = function (number) { + if (number) { + if (number.hasOwnProperty('number')) number = number.number; + $scope.page = $scope.pages[number - 1] || $scope.pages[0]; + } + }; - self.goToTop = function goToTop() { - $document.scrollTop(0); - }; + self.goToTop = function goToTop() { + $document.scrollTop(0); + }; - self.renderList = function () { - $scope.pages = []; - if (!$scope.list) return; + self.renderList = function () { + $scope.pages = []; + if (!$scope.list) return; - const perPage = _.parseInt(self.perPage); - const count = perPage ? Math.ceil($scope.list.length / perPage) : 1; + const perPage = _.parseInt(self.perPage); + const count = perPage ? Math.ceil($scope.list.length / perPage) : 1; - _.times(count, function (i) { - let page; + _.times(count, function (i) { + let page; - if (perPage) { - const start = perPage * i; - page = $scope.list.slice(start, start + perPage); - } else { - page = $scope.list.slice(0); - } + if (perPage) { + const start = perPage * i; + page = $scope.list.slice(start, start + perPage); + } else { + page = $scope.list.slice(0); + } - page.number = i + 1; - page.i = i; + page.number = i + 1; + page.i = i; - page.count = count; - page.first = page.number === 1; - page.last = page.number === count; - page.firstItem = (page.number - 1) * perPage + 1; - page.lastItem = Math.min(page.number * perPage, $scope.list.length); + page.count = count; + page.first = page.number === 1; + page.last = page.number === count; + page.firstItem = (page.number - 1) * perPage + 1; + page.lastItem = Math.min(page.number * perPage, $scope.list.length); - page.prev = $scope.pages[i - 1]; - if (page.prev) page.prev.next = page; + page.prev = $scope.pages[i - 1]; + if (page.prev) page.prev.next = page; - $scope.pages.push(page); - }); + $scope.pages.push(page); + }); - // set the new page, or restore the previous page number - if ($scope.page && $scope.page.i < $scope.pages.length) { - $scope.page = $scope.pages[$scope.page.i]; - } else { - $scope.page = $scope.pages[0]; - } + // set the new page, or restore the previous page number + if ($scope.page && $scope.page.i < $scope.pages.length) { + $scope.page = $scope.pages[$scope.page.i]; + } else { + $scope.page = $scope.pages[0]; + } - if ($scope.page && $scope.onPageChanged) { - $scope.onPageChanged($scope.page); - } - }; + if ($scope.page && $scope.onPageChanged) { + $scope.onPageChanged($scope.page); + } + }; - self.changePage = function (page) { - if (!page) { - $scope.otherPages = null; - return; - } + self.changePage = function (page) { + if (!page) { + $scope.otherPages = null; + return; + } - // setup the list of the other pages to link to - $scope.otherPages = []; - const width = +self.otherWidthGetter($scope) || 5; - let left = page.i - Math.round((width - 1) / 2); - let right = left + width - 1; + // setup the list of the other pages to link to + $scope.otherPages = []; + const width = +self.otherWidthGetter($scope) || 5; + let left = page.i - Math.round((width - 1) / 2); + let right = left + width - 1; - // shift neg count from left to right - if (left < 0) { - right += 0 - left; - left = 0; - } + // shift neg count from left to right + if (left < 0) { + right += 0 - left; + left = 0; + } - // shift extra right nums to left - const lastI = page.count - 1; - if (right > lastI) { - right = lastI; - left = right - width + 1; - } + // shift extra right nums to left + const lastI = page.count - 1; + if (right > lastI) { + right = lastI; + left = right - width + 1; + } - for (let i = left; i <= right; i++) { - const other = $scope.pages[i]; + for (let i = left; i <= right; i++) { + const other = $scope.pages[i]; - if (!other) continue; + if (!other) continue; - $scope.otherPages.push(other); - if (other.last) $scope.otherPages.containsLast = true; - if (other.first) $scope.otherPages.containsFirst = true; - } + $scope.otherPages.push(other); + if (other.last) $scope.otherPages.containsLast = true; + if (other.first) $scope.otherPages.containsFirst = true; + } - if ($scope.onPageChanged) { - $scope.onPageChanged($scope.page); - } - }; + if ($scope.onPageChanged) { + $scope.onPageChanged($scope.page); + } + }; - function setPerPage(val) { - let $ppParent = $scope; + function setPerPage(val) { + let $ppParent = $scope; - while ($ppParent && !_.has($ppParent, self.perPageProp)) { - $ppParent = $ppParent.$parent; - } + while ($ppParent && !_.has($ppParent, self.perPageProp)) { + $ppParent = $ppParent.$parent; + } - if ($ppParent) { - $ppParent[self.perPageProp] = val; - return true; - } + if ($ppParent) { + $ppParent[self.perPageProp] = val; + return true; } } - }; - }) - .directive('paginateControls', function () { + } + }; +} + +export function PaginateControlsDirectiveProvider() { // this directive is automatically added by paginate if not found within it's $el - return { - restrict: 'E', - template: paginateControlsTemplate - }; - }); + return { + restrict: 'E', + template: paginateControlsTemplate + }; +} + +uiModules + .get('kibana') + .directive('paginate', PaginateDirectiveProvider) + .directive('paginateControls', PaginateControlsDirectiveProvider); diff --git a/src/legacy/ui/public/directives/watch_multi/watch_multi.js b/src/legacy/ui/public/directives/watch_multi/watch_multi.js index add95e8146f26..03bccd598f351 100644 --- a/src/legacy/ui/public/directives/watch_multi/watch_multi.js +++ b/src/legacy/ui/public/directives/watch_multi/watch_multi.js @@ -21,10 +21,8 @@ import _ from 'lodash'; import { uiModules } from '../../modules'; import { callEach } from '../../utils/function'; -uiModules.get('kibana') - .config(function ($provide) { - - $provide.decorator('$rootScope', function ($delegate) { +export function watchMultiDecorator($provide) { + $provide.decorator('$rootScope', function ($delegate) { /** * Watch multiple expressions with a single callback. Along * with making code simpler it also merges all of the watcher @@ -53,84 +51,86 @@ uiModules.get('kibana') * @param {Function} fn - the callback function * @return {Function} - an unwatch function, just like the return value of $watch */ - $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { - if (!Array.isArray(expressions)) throw new TypeError('expected an array of expressions to watch'); - if (!_.isFunction(fn)) throw new TypeError('expected a function that is triggered on each watch'); - - const $scope = this; - const vals = new Array(expressions.length); - const prev = new Array(expressions.length); - let fire = false; - let init = 0; - const neededInits = expressions.length; - - // first, register all of the multi-watchers - const unwatchers = expressions.map(function (expr, i) { - expr = normalizeExpression($scope, expr); - if (!expr) return; - - return expr.fn.call($scope, expr.get, function (newVal, oldVal) { - if (newVal === oldVal) { - init += 1; - } - - vals[i] = newVal; - prev[i] = oldVal; - fire = true; - }, expr.deep); - }); - - // then, the watcher that checks to see if any of - // the other watchers triggered this cycle - let flip = false; - unwatchers.push($scope.$watch(function () { - if (init < neededInits) return init; + $delegate.constructor.prototype.$watchMulti = function (expressions, fn) { + if (!Array.isArray(expressions)) throw new TypeError('expected an array of expressions to watch'); + if (!_.isFunction(fn)) throw new TypeError('expected a function that is triggered on each watch'); + + const $scope = this; + const vals = new Array(expressions.length); + const prev = new Array(expressions.length); + let fire = false; + let init = 0; + const neededInits = expressions.length; + + // first, register all of the multi-watchers + const unwatchers = expressions.map(function (expr, i) { + expr = normalizeExpression($scope, expr); + if (!expr) return; - if (fire) { - fire = false; - flip = !flip; + return expr.fn.call($scope, expr.get, function (newVal, oldVal) { + if (newVal === oldVal) { + init += 1; } - return flip; - }, function () { - if (init < neededInits) return false; - fn(vals.slice(0), prev.slice(0)); - vals.forEach(function (v, i) { - prev[i] = v; - }); - })); + vals[i] = newVal; + prev[i] = oldVal; + fire = true; + }, expr.deep); + }); + + // then, the watcher that checks to see if any of + // the other watchers triggered this cycle + let flip = false; + unwatchers.push($scope.$watch(function () { + if (init < neededInits) return init; + + if (fire) { + fire = false; + flip = !flip; + } + return flip; + }, function () { + if (init < neededInits) return false; + + fn(vals.slice(0), prev.slice(0)); + vals.forEach(function (v, i) { + prev[i] = v; + }); + })); + + return _.partial(callEach, unwatchers); + }; - return _.partial(callEach, unwatchers); + function normalizeExpression($scope, expr) { + if (!expr) return; + const norm = { + fn: $scope.$watch, + deep: false }; - function normalizeExpression($scope, expr) { - if (!expr) return; - const norm = { - fn: $scope.$watch, - deep: false - }; - - if (_.isFunction(expr)) return _.assign(norm, { get: expr }); - if (_.isObject(expr)) return _.assign(norm, expr); - if (!_.isString(expr)) return; - - if (expr.substr(0, 2) === '[]') { - return _.assign(norm, { - fn: $scope.$watchCollection, - get: expr.substr(2) - }); - } + if (_.isFunction(expr)) return _.assign(norm, { get: expr }); + if (_.isObject(expr)) return _.assign(norm, expr); + if (!_.isString(expr)) return; - if (expr.charAt(0) === '=') { - return _.assign(norm, { - deep: true, - get: expr.substr(1) - }); - } + if (expr.substr(0, 2) === '[]') { + return _.assign(norm, { + fn: $scope.$watchCollection, + get: expr.substr(2) + }); + } - return _.assign(norm, { get: expr }); + if (expr.charAt(0) === '=') { + return _.assign(norm, { + deep: true, + get: expr.substr(1) + }); } - return $delegate; - }); + return _.assign(norm, { get: expr }); + } + + return $delegate; }); +} + +uiModules.get('kibana').config(watchMultiDecorator); From e88932439843cdf10903abad6ef8084634153464 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 17:22:24 +0300 Subject: [PATCH 02/59] Refactoring --- .../timelion/public/kibana_services.ts | 2 +- .../core_plugins/timelion/public/vis/index.ts | 4 +- .../timelion/public/vis/legacy_imports.ts | 6 ++ .../public/vis/timelion_controller.ts | 4 +- .../public/vis/timelion_request_handler.ts | 5 +- .../public/vis/timelion_vis_legacy_module.ts | 2 +- .../public/vis/vis_types/angular_vis_type.js | 68 ------------------- 7 files changed, 12 insertions(+), 79 deletions(-) delete mode 100644 src/legacy/ui/public/vis/vis_types/angular_vis_type.js diff --git a/src/legacy/core_plugins/timelion/public/kibana_services.ts b/src/legacy/core_plugins/timelion/public/kibana_services.ts index 4be7029a5f5d5..93d9ad7c39c5a 100644 --- a/src/legacy/core_plugins/timelion/public/kibana_services.ts +++ b/src/legacy/core_plugins/timelion/public/kibana_services.ts @@ -31,7 +31,7 @@ export function setServices(newServices: TimelionKibanaServices) { export function getServices() { if (!services) { throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the visualize app?' + 'Kibana services not set - are you trying to import this module from outside of the timelion app?' ); } return services; diff --git a/src/legacy/core_plugins/timelion/public/vis/index.ts b/src/legacy/core_plugins/timelion/public/vis/index.ts index b93cceeb5c4ac..cbab37548068d 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.ts +++ b/src/legacy/core_plugins/timelion/public/vis/index.ts @@ -19,13 +19,11 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore -import { DefaultEditorSize } from 'ui/vis/editor_size'; +import { DefaultEditorSize } from './legacy_imports'; import { getTimelionRequestHandler } from './timelion_request_handler'; import visConfigTemplate from './timelion_vis.html'; import editorConfigTemplate from './timelion_vis_params.html'; import { TimelionVisualizationDependencies } from '../plugin'; -// @ts-ignore -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; import { TimelionVisController } from './timelion_controller'; export const TIMELION_VIS_NAME = 'timelion'; diff --git a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts index 0e528811560de..803cb20b39443 100644 --- a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts +++ b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts @@ -33,3 +33,9 @@ export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_clic // @ts-ignore export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; export { configureAppAngularModule } from 'ui/legacy_compat'; +// @ts-ignore +export { DefaultEditorSize } from 'ui/vis/editor_size'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +export { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; +export { VisParams } from 'ui/vis'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts index 9e27a4307fe15..f9e3ece1783ae 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts @@ -24,7 +24,7 @@ import { Vis, VisParams } from '../../../visualizations/public'; import { npStart } from './legacy_imports'; import { getServices } from '../kibana_services'; import { getAngularModule } from './get_inner_angular'; -import { initTimelionLegacyModule } from './timelion_vis_legacy_module'; +import { initTimelionVisLegacyModule } from './timelion_vis_legacy_module'; const innerAngularName = 'kibana/timelion_vis'; @@ -56,7 +56,7 @@ export class TimelionVisController { initLocalAngular() { if (!this.timelionVisModule) { this.timelionVisModule = getAngularModule(innerAngularName, npStart.core); - initTimelionLegacyModule(this.timelionVisModule, getServices().timelionPanels); + initTimelionVisLegacyModule(this.timelionVisModule, getServices().timelionPanels); } } diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 14cd3d0083e6a..55a54260efaa4 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -17,13 +17,10 @@ * under the License. */ -// @ts-ignore -import { timezoneProvider } from 'ui/vis/lib/timezone'; -import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; +import { timezoneProvider, KIBANA_CONTEXT_NAME, VisParams } from './legacy_imports'; interface Stats { cacheCount: number; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts index 9f48204caa9aa..37a198611a07a 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts @@ -30,7 +30,7 @@ import { TimelionExpInput } from '../directives/timelion_expression_input'; import { TimelionExpressionSuggestions } from '../directives/timelion_expression_suggestions/timelion_expression_suggestions'; /** @internal */ -export const initTimelionLegacyModule = ( +export const initTimelionVisLegacyModule = ( angularIns: IModule, timelionPanels: Map ): void => { diff --git a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js deleted file mode 100644 index 1d1176ac48834..0000000000000 --- a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import chrome from 'ui/chrome'; - -export class AngularVisController { - constructor(domeElement, vis) { - this.el = $(domeElement); - this.vis = vis; - } - - render(esResponse, visParams, status) { - - return new Promise(async (resolve, reject) => { - if (!this.$rootScope) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - this.$rootScope = $injector.get('$rootScope'); - this.$compile = $injector.get('$compile'); - } - const updateScope = () => { - this.$scope.vis = this.vis; - this.$scope.visState = this.vis.getState(); - this.$scope.esResponse = esResponse; - this.$scope.visParams = visParams; - this.$scope.renderComplete = resolve; - this.$scope.renderFailed = reject; - this.$scope.resize = Date.now(); - this.$scope.updateStatus = status; - this.$scope.$apply(); - }; - - if (!this.$scope) { - this.$scope = this.$rootScope.$new(); - this.$scope.uiState = this.vis.getUiState(); - updateScope(); - this.el.html(this.$compile(this.vis.type.visConfig.template)(this.$scope)); - this.$scope.$apply(); - } else { - updateScope(); - } - }); - } - - destroy() { - if (this.$scope) { - this.$scope.$destroy(); - this.$scope = null; - } - } -} - From 8c434352581c6a8a38beb7d1c718e55d09bb93dd Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 17:30:07 +0300 Subject: [PATCH 03/59] Fix path --- src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts | 1 - .../timelion/public/vis/timelion_request_handler.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts index 803cb20b39443..1ca5745763e4f 100644 --- a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts +++ b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts @@ -37,5 +37,4 @@ export { configureAppAngularModule } from 'ui/legacy_compat'; export { DefaultEditorSize } from 'ui/vis/editor_size'; // @ts-ignore export { timezoneProvider } from 'ui/vis/lib/timezone'; -export { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; export { VisParams } from 'ui/vis'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 55a54260efaa4..67f571ef99225 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -17,10 +17,11 @@ * under the License. */ +import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; import { i18n } from '@kbn/i18n'; import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; -import { timezoneProvider, KIBANA_CONTEXT_NAME, VisParams } from './legacy_imports'; +import { timezoneProvider, VisParams } from './legacy_imports'; interface Stats { cacheCount: number; From fd07c8ca960cdbf5be7341d5b177c1cf42f4c8de Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 21:53:28 +0300 Subject: [PATCH 04/59] Update timelion_controller.ts --- .../core_plugins/timelion/public/vis/timelion_controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts index f9e3ece1783ae..5c4cff1a058f4 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts @@ -45,7 +45,7 @@ export class TimelionVisController { getInjector() { if (!this.injector) { const mountpoint = document.createElement('div'); - mountpoint.setAttribute('style', 'height: 100%; width: 100%;'); + mountpoint.setAttribute('class', 'visChart'); this.injector = angular.bootstrap(mountpoint, [innerAngularName]); this.el.append(mountpoint); } @@ -89,7 +89,7 @@ export class TimelionVisController { this.$scope = this.$rootScope.$new(); this.$scope.uiState = this.vis.getUiState(); updateScope(); - this.el.html(this.$compile(this.vis.type.visConfig.template)(this.$scope)); + this.el.find('div').append(this.$compile(this.vis.type.visConfig.template)(this.$scope)); this.$scope.$apply(); } else { updateScope(); From 4ca27df9ca9197d03880decbb76918376a8e4546 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 22:20:09 +0300 Subject: [PATCH 05/59] Remove unused deps --- .../timelion/public/vis/get_inner_angular.ts | 45 +------------------ .../timelion/public/vis/legacy_imports.ts | 10 +---- 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts b/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts index 1a04225a04c9f..8773370c3b05b 100644 --- a/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts +++ b/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts @@ -21,19 +21,11 @@ // these are necessary to bootstrap the local angular. // They can stay even after NP cutover import angular from 'angular'; -import 'ui/angular-bootstrap'; -import 'angular-sortable-view'; -import 'brace/mode/hjson'; -import 'brace/ext/searchbox'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { CoreStart, LegacyCoreStart } from 'kibana/public'; import { - PrivateProvider, - PaginateDirectiveProvider, - PaginateControlsDirectiveProvider, watchMultiDecorator, KbnAccessibleClickProvider, - StateManagementConfigProvider, configureAppAngularModule, } from './legacy_imports'; @@ -49,43 +41,15 @@ let initialized = false; export function getInnerAngular(name = 'kibana/timelion_vis', core: CoreStart) { if (!initialized) { - createLocalPrivateModule(); createLocalI18nModule(); - createLocalConfigModule(core.uiSettings); - createLocalPaginateModule(); initialized = true; } return angular - .module(name, [ - ...thirdPartyAngularDependencies, - 'timelionVisPaginate', - 'timelionVisConfig', - 'timelionVisPrivate', - 'timelionVisI18n', - ]) + .module(name, [...thirdPartyAngularDependencies, 'timelionVisI18n']) .config(watchMultiDecorator) .directive('kbnAccessibleClick', KbnAccessibleClickProvider); } -function createLocalPrivateModule() { - angular.module('timelionVisPrivate', []).provider('Private', PrivateProvider); -} - -function createLocalConfigModule(uiSettings: any) { - angular - .module('timelionVisConfig', ['timelionVisPrivate']) - .provider('stateManagementConfig', StateManagementConfigProvider) - .provider('config', function() { - return { - $get: () => ({ - get: (value: string) => { - return uiSettings ? uiSettings.get(value) : undefined; - }, - }), - }; - }); -} - function createLocalI18nModule() { angular .module('timelionVisI18n', []) @@ -93,10 +57,3 @@ function createLocalI18nModule() { .filter('i18n', i18nFilter) .directive('i18nId', i18nDirective); } - -function createLocalPaginateModule() { - angular - .module('timelionVisPaginate', []) - .directive('paginate', PaginateDirectiveProvider) - .directive('paginateControls', PaginateControlsDirectiveProvider); -} diff --git a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts index 1ca5745763e4f..8ec629f4982bf 100644 --- a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts +++ b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts @@ -17,21 +17,13 @@ * under the License. */ -export { npSetup, npStart } from 'ui/new_platform'; -// @ts-ignore -export { PrivateProvider } from 'ui/private/private'; +export { npStart } from 'ui/new_platform'; -// @ts-ignore -export { PaginateDirectiveProvider } from 'ui/directives/paginate'; -// @ts-ignore -export { PaginateControlsDirectiveProvider } from 'ui/directives/paginate'; // @ts-ignore export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; // @ts-ignore export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; -// @ts-ignore -export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; export { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore export { DefaultEditorSize } from 'ui/vis/editor_size'; From 9d140894b32b926e66fb32abc01f1b717041eac4 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 10 Dec 2019 18:11:42 +0300 Subject: [PATCH 06/59] Create vis_type_timelion --- .../core_plugins/timelion/public/plugin.ts | 2 +- .../common/lib/calculate_interval.js | 66 + .../vis_type_timelion/common/lib/index.js | 21 + .../core_plugins/vis_type_timelion/index.ts | 43 + .../vis_type_timelion/package.json | 4 + .../public/components/chart.tsx | 26 + .../public/components/timelion_vis.tsx | 37 + .../helpers/timelion_request_handler.ts | 109 + .../vis_type_timelion/public/index.ts | 25 + .../public/kibana_services.ts | 38 + .../vis_type_timelion/public/legacy.ts | 36 + .../public/legacy_imports.ts | 32 + .../public/lib/_tests_/calculate_interval.js | 63 + .../public/lib/observe_resize.js | 47 + .../vis_type_timelion/public/panels/panel.ts | 45 + .../public/panels/timechart/flot.js | 26 + .../public/panels/timechart/schema.ts | 399 +++ .../public/panels/timechart/tick_generator.ts | 61 + .../public/panels/timechart/timechart.ts | 28 + .../panels/timechart/xaxis_formatter.ts | 54 + .../vis_type_timelion/public/plugin.ts | 94 + .../public/services/_saved_sheet.js | 83 + .../public/services/saved_sheet_register.js | 24 + .../public/services/saved_sheets.js | 49 + .../public/services/tick_formatters.ts | 90 + .../public/timelion_vis_fn.ts | 93 + .../public/timelion_vis_params.html | 27 + .../public/timelion_vis_type.ts | 64 + .../public/vis_controller.ts | 92 + .../webpackShims/jquery.flot.axislabels.js | 462 +++ .../webpackShims/jquery.flot.crosshair.js | 176 + .../public/webpackShims/jquery.flot.js | 3168 +++++++++++++++++ .../webpackShims/jquery.flot.selection.js | 360 ++ .../public/webpackShims/jquery.flot.stack.js | 188 + .../public/webpackShims/jquery.flot.symbol.js | 71 + .../public/webpackShims/jquery.flot.time.js | 432 +++ .../server/fit_functions/__tests__/average.js | 93 + .../server/fit_functions/__tests__/carry.js | 91 + .../server/fit_functions/average.js | 94 + .../server/fit_functions/carry.js | 51 + .../server/fit_functions/nearest.js | 46 + .../server/fit_functions/none.js | 26 + .../server/fit_functions/scale.js | 92 + .../server/handlers/__tests__/parse_sheet.js | 53 + .../server/handlers/chain_runner.js | 228 ++ .../server/handlers/lib/arg_type.js | 37 + .../server/handlers/lib/index_arguments.js | 60 + .../server/handlers/lib/parse_sheet.js | 49 + .../server/handlers/lib/preprocess_chain.js | 67 + .../handlers/lib/reposition_arguments.js | 84 + .../server/handlers/lib/tl_config.js | 42 + .../server/handlers/lib/validate_arg.js | 53 + .../server/handlers/lib/validate_time.js | 44 + .../vis_type_timelion/server/index.ts | 25 + .../server/lib/__tests__/load_functions.js | 42 + .../vis_type_timelion/server/lib/alter.js | 49 + .../vis_type_timelion/server/lib/asSorted.js | 26 + .../server/lib/build_target.js | 39 + .../server/lib/classes/chainable.js | 28 + .../server/lib/classes/datasource.js | 101 + .../server/lib/classes/timelion_function.d.ts | 53 + .../server/lib/classes/timelion_function.js | 64 + .../server/lib/functions_md.js | 93 + .../server/lib/get_namespaced_settings.js | 44 + .../server/lib/load_functions.d.ts | 28 + .../server/lib/load_functions.js | 55 + .../server/lib/offset_time.js | 70 + .../server/lib/offset_time.test.js | 56 + .../server/lib/process_function_definition.js | 34 + .../vis_type_timelion/server/lib/reduce.js | 146 + .../server/lib/split_interval.js | 30 + .../server/lib/to_milliseconds.js | 54 + .../server/lib/unzipPairs.js | 29 + .../vis_type_timelion/server/plugin.ts | 62 + .../server/routes/functions.js | 35 + .../vis_type_timelion/server/routes/index.ts | 32 + .../vis_type_timelion/server/routes/run.js | 73 + .../server/routes/validate_es.js | 74 + .../server/series_functions/__tests__/abs.js | 44 + .../series_functions/__tests__/aggregate.js | 76 + .../server/series_functions/__tests__/bars.js | 60 + .../series_functions/__tests__/color.js | 80 + .../series_functions/__tests__/condition.js | 113 + .../series_functions/__tests__/cusum.js | 39 + .../series_functions/__tests__/derivative.js | 39 + .../series_functions/__tests__/divide.js | 39 + .../server/series_functions/__tests__/es.js | 498 +++ .../series_functions/__tests__/first.js | 32 + .../server/series_functions/__tests__/fit.js | 159 + .../__tests__/fixtures/bucketList.js | 27 + .../__tests__/fixtures/es_response.js | 231 ++ .../__tests__/fixtures/seriesList.js | 32 + .../__tests__/fixtures/tlConfig.js | 74 + .../series_functions/__tests__/graphite.js | 64 + .../__tests__/helpers/get_series.js | 29 + .../__tests__/helpers/get_series_list.js | 27 + .../helpers/get_single_series_list.js | 26 + .../__tests__/helpers/invoke_series_fn.js | 42 + .../server/series_functions/__tests__/hide.js | 45 + .../series_functions/__tests__/label.js | 45 + .../series_functions/__tests__/legend.js | 59 + .../series_functions/__tests__/lines.js | 53 + .../server/series_functions/__tests__/log.js | 39 + .../server/series_functions/__tests__/max.js | 39 + .../server/series_functions/__tests__/min.js | 39 + .../__tests__/movingaverage.js | 77 + .../series_functions/__tests__/movingstd.js | 62 + .../series_functions/__tests__/multiply.js | 39 + .../series_functions/__tests__/points.js | 83 + .../series_functions/__tests__/precision.js | 45 + .../series_functions/__tests__/quandl.js | 100 + .../series_functions/__tests__/range.js | 39 + .../__tests__/scale_interval.js | 38 + .../series_functions/__tests__/static.js | 44 + .../series_functions/__tests__/subtract.js | 79 + .../server/series_functions/__tests__/sum.js | 39 + .../series_functions/__tests__/title.js | 39 + .../server/series_functions/__tests__/trim.js | 63 + .../series_functions/__tests__/yaxis.js | 111 + .../server/series_functions/abs.js | 44 + .../server/series_functions/aggregate/avg.js | 24 + .../series_functions/aggregate/cardinality.js | 24 + .../series_functions/aggregate/first.js | 24 + .../series_functions/aggregate/index.js | 71 + .../server/series_functions/aggregate/last.js | 24 + .../server/series_functions/aggregate/max.js | 24 + .../server/series_functions/aggregate/min.js | 24 + .../server/series_functions/aggregate/sum.js | 24 + .../server/series_functions/bars.js | 57 + .../server/series_functions/color.js | 74 + .../server/series_functions/condition.js | 158 + .../server/series_functions/cusum.js | 54 + .../server/series_functions/derivative.js | 46 + .../server/series_functions/divide.js | 48 + .../server/series_functions/es/index.js | 154 + .../series_functions/es/lib/agg_body.js | 39 + .../es/lib/agg_response_to_series_list.js | 81 + .../series_functions/es/lib/build_request.js | 94 + .../es/lib/create_date_agg.js | 67 + .../server/series_functions/first.js | 39 + .../server/series_functions/fit.js | 64 + .../server/series_functions/graphite.js | 90 + .../server/series_functions/hide.js | 47 + .../server/series_functions/holt/index.js | 144 + .../server/series_functions/holt/lib/des.js | 64 + .../server/series_functions/holt/lib/ses.js | 53 + .../server/series_functions/holt/lib/tes.js | 108 + .../server/series_functions/label.js | 62 + .../server/series_functions/legend.js | 133 + .../server/series_functions/lines.js | 85 + .../server/series_functions/log.js | 53 + .../server/series_functions/max.js | 50 + .../server/series_functions/min.js | 50 + .../server/series_functions/movingaverage.js | 134 + .../server/series_functions/movingstd.js | 116 + .../server/series_functions/multiply.js | 48 + .../server/series_functions/points.js | 128 + .../server/series_functions/precision.js | 53 + .../server/series_functions/props.js | 86 + .../server/series_functions/quandl.js | 121 + .../server/series_functions/range.js | 65 + .../server/series_functions/scale_interval.js | 57 + .../server/series_functions/static.js | 78 + .../server/series_functions/subtract.js | 48 + .../server/series_functions/sum.js | 49 + .../server/series_functions/title.js | 48 + .../server/series_functions/trend/index.js | 96 + .../series_functions/trend/lib/regress.js | 79 + .../server/series_functions/trim.js | 68 + .../server/series_functions/worldbank.js | 115 + .../series_functions/worldbank_indicators.js | 86 + .../server/series_functions/yaxis.js | 177 + .../vis_type_timelion/server/types.ts | 26 + visualize_app.ts.~LOCAL | 30 + 174 files changed, 15999 insertions(+), 1 deletion(-) create mode 100644 src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/common/lib/index.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/index.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/package.json create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/index.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/legacy.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/flot.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/tick_generator.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/xaxis_formatter.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/plugin.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_params.html create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/index.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/plugin.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/run.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/types.ts create mode 100644 visualize_app.ts.~LOCAL diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 25fe2439455b5..97f9b614f475f 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -78,7 +78,7 @@ export class TimelionPlugin implements Plugin, void> { this.registerPanels(dependencies); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); + // visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js new file mode 100644 index 0000000000000..185b1df402c16 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import toMS from '../../server/lib/to_milliseconds.js'; + +// Totally cribbed this from Kibana 3. +// I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. +function roundInterval(interval) { + switch (true) { + case (interval <= 500): // <= 0.5s + return '100ms'; + case (interval <= 5000): // <= 5s + return '1s'; + case (interval <= 7500): // <= 7.5s + return '5s'; + case (interval <= 15000): // <= 15s + return '10s'; + case (interval <= 45000): // <= 45s + return '30s'; + case (interval <= 180000): // <= 3m + return '1m'; + case (interval <= 450000): // <= 9m + return '5m'; + case (interval <= 1200000): // <= 20m + return '10m'; + case (interval <= 2700000): // <= 45m + return '30m'; + case (interval <= 7200000): // <= 2h + return '1h'; + case (interval <= 21600000): // <= 6h + return '3h'; + case (interval <= 86400000): // <= 24h + return '12h'; + case (interval <= 604800000): // <= 1w + return '24h'; + case (interval <= 1814400000): // <= 3w + return '1w'; + case (interval < 3628800000): // < 2y + return '30d'; + default: + return '1y'; + } +} + +export function calculateInterval(from, to, size, interval, min) { + if (interval !== 'auto') return interval; + const dateMathInterval = roundInterval((to - from) / size); + if (toMS(dateMathInterval) < toMS(min)) return min; + return dateMathInterval; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/index.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.js new file mode 100644 index 0000000000000..927331043f0b3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { calculateInterval } from './calculate_interval'; +export const DEFAULT_TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss.SSS'; diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts new file mode 100644 index 0000000000000..4c0ce26e2cf86 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; + +const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'timelion_vis', + require: ['kibana', 'elasticsearch'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + // styleSheetPaths: resolve(__dirname, 'public/index.scss'), + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + }); + +// eslint-disable-next-line import/no-default-export +export default timelionVisPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_timelion/package.json b/src/legacy/core_plugins/vis_type_timelion/package.json new file mode 100644 index 0000000000000..9b09f98ce6caf --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/package.json @@ -0,0 +1,4 @@ +{ + "name": "timelion_vis", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx new file mode 100644 index 0000000000000..9ab3295a971a0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +function ChartComponent(props: any) { + return
Charts
; +} + +export { ChartComponent }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx new file mode 100644 index 0000000000000..6546297527903 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { ChartComponent } from './chart'; + +function TimelionVisComponent(props: any) { + return ( +
+ +
+ ) +} + +export { TimelionVisComponent }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts new file mode 100644 index 0000000000000..269923d37d8d8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -0,0 +1,109 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; +import { i18n } from '@kbn/i18n'; +import { TimelionVisualizationDependencies } from '../plugin'; +import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; +import { timezoneProvider, VisParams } from '../legacy_imports'; + +interface Stats { + cacheCount: number; + invokeTime: number; + queryCount: number; + queryTime: number; + sheetTime: number; +} + +interface Sheet { + list: Array>; + render: Record; + type: string; +} + +export interface TimelionSuccessResponse { + sheet: Sheet[]; + stats: Stats; + visType: string; + type: KIBANA_CONTEXT_NAME; +} + +export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { + const { uiSettings, http, timefilter } = dependencies; + const timezone = timezoneProvider(uiSettings)(); + + return async function({ + timeRange, + filters, + query, + visParams, + }: { + timeRange: TimeRange; + filters: esFilters.Filter[]; + query: Query; + visParams: VisParams; + forceFetch?: boolean; + }): Promise { + const expression = visParams.expression; + + if (!expression) { + throw new Error( + i18n.translate('timelion.emptyExpressionErrorMessage', { + defaultMessage: 'Timelion error: No expression provided', + }) + ); + } + + const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); + + // parse the time range client side to make sure it behaves like other charts + const timeRangeBounds = timefilter.calculateBounds(timeRange); + + try { + return await http.post('../api/timelion/run', { + body: JSON.stringify({ + sheet: [expression], + extended: { + es: { + filter: esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs), + }, + }, + time: { + from: timeRangeBounds.min, + to: timeRangeBounds.max, + interval: visParams.interval, + timezone, + }, + }), + }); + } catch (e) { + if (e && e.body) { + const err = new Error( + `${i18n.translate('timelion.requestHandlerErrorTitle', { + defaultMessage: 'Timelion request error', + })}: ${e.body.title} ${e.body.message}` + ); + err.stack = e.stack; + throw err; + } else { + throw e; + } + } + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/index.ts new file mode 100644 index 0000000000000..98cc35877094e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../../core/public'; +import { TimelionVisPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts new file mode 100644 index 0000000000000..93d9ad7c39c5a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Panel } from './panels/panel'; + +export interface TimelionKibanaServices { + timelionPanels: Map; +} +let services: TimelionKibanaServices | null = null; + +export function setServices(newServices: TimelionKibanaServices) { + services = newServices; +} + +export function getServices() { + if (!services) { + throw new Error( + 'Kibana services not set - are you trying to import this module from outside of the timelion app?' + ); + } + return services; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts new file mode 100644 index 0000000000000..7d171ecccf2f7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +import { TimelionPluginSetupDependencies } from './plugin'; +import { plugin } from '.'; + +const plugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, plugins); +export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts new file mode 100644 index 0000000000000..8ec629f4982bf --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { npStart } from 'ui/new_platform'; + +// @ts-ignore +export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; + +// @ts-ignore +export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; +export { configureAppAngularModule } from 'ui/legacy_compat'; +// @ts-ignore +export { DefaultEditorSize } from 'ui/vis/editor_size'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +export { VisParams } from 'ui/vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js new file mode 100644 index 0000000000000..929e6b44602a8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const filename = require('path').basename(__filename); +const fn = require(`../calculate_interval`); +const moment = require('moment'); +const expect = require('chai').expect; + + +const from = (count, unit) => moment().subtract(count, unit).valueOf(); +const to = moment().valueOf(); +const size = 200; +const min = '1ms'; + +describe(filename, () => { + it('Exports a function', () => { + expect(fn).to.be.a('function'); + }); + + it('Only calculates when interval = auto', () => { + const partialFn = (interval) => fn(from(1, 'y'), to, size, interval, min); + expect(partialFn('1ms')).to.equal('1ms'); + expect(partialFn('bag_of_beans')).to.equal('bag_of_beans'); + expect(partialFn('auto')).to.not.equal('auto'); + }); + + it('Calculates nice round intervals', () => { + const partialFn = (count, unit) => fn(from(count, unit), to, size, 'auto', min); + expect(partialFn(15, 'm')).to.equal('1s'); + expect(partialFn(1, 'h')).to.equal('30s'); + expect(partialFn(3, 'd')).to.equal('30m'); + expect(partialFn(1, 'w')).to.equal('1h'); + expect(partialFn(1, 'y')).to.equal('24h'); + expect(partialFn(100, 'y')).to.equal('1y'); + }); + + it('Does not calculate an interval lower than the minimum', () => { + const partialFn = (count, unit) => fn(from(count, unit), to, size, 'auto', '1m'); + expect(partialFn(5, 's')).to.equal('1m'); + expect(partialFn(15, 'm')).to.equal('1m'); + expect(partialFn(1, 'h')).to.equal('1m'); + expect(partialFn(3, 'd')).to.equal('30m'); + expect(partialFn(1, 'w')).to.equal('1h'); + expect(partialFn(1, 'y')).to.equal('24h'); + expect(partialFn(100, 'y')).to.equal('1y'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js b/src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js new file mode 100644 index 0000000000000..d19ffe69c2110 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function ($elem, fn, frequency) { + + frequency = frequency || 500; + let currentHeight = $elem.height(); + let currentWidth = $elem.width(); + + let timeout; + + function checkLoop() { + timeout = setTimeout(function () { + if (currentHeight !== $elem.height() || currentWidth !== $elem.width()) { + currentHeight = $elem.height(); + currentWidth = $elem.width(); + + if (currentWidth > 0 && currentWidth > 0) fn(); + } + checkLoop(); + }, frequency); + } + + checkLoop(); + + return function () { + clearTimeout(timeout); + }; + + +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts new file mode 100644 index 0000000000000..3512cd96a9596 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +interface PanelConfig { + help?: string; + render?: Function; +} + +export class Panel { + name: string; + help: string; + render: Function | undefined; + + constructor(name: string, config: PanelConfig) { + this.name = name; + this.help = config.help || ''; + this.render = config.render; + + if (!config.render) { + throw new Error( + i18n.translate('timelion.panels.noRenderFunctionErrorMessage', { + defaultMessage: 'Panel must have a rendering function', + }) + ); + } + } +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/flot.js b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/flot.js new file mode 100644 index 0000000000000..d6ca6d96c34ef --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/flot.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('jquery.flot'); +require('jquery.flot.time'); +require('jquery.flot.symbol'); +require('jquery.flot.crosshair'); +require('jquery.flot.selection'); +require('jquery.flot.stack'); +require('jquery.flot.axislabels'); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts new file mode 100644 index 0000000000000..3c663d664c519 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts @@ -0,0 +1,399 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './flot'; +import _ from 'lodash'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { timefilter } from 'ui/timefilter'; +// @ts-ignore +import observeResize from '../../lib/observe_resize'; +// @ts-ignore +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; +import { TimelionVisualizationDependencies } from '../../plugin'; +import { tickFormatters } from '../../services/tick_formatters'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { generateTicksProvider } from './tick_generator'; + +const DEBOUNCE_DELAY = 50; + +export function timechartFn(dependencies: TimelionVisualizationDependencies) { + // @ts-ignore + const { $rootScope, $compile, uiSettings } = dependencies; + return function() { + return { + help: 'Draw a timeseries chart', + render($scope: any, $elem: any) { + const template = '
'; + const formatters = tickFormatters() as any; + const getxAxisFormatter = xaxisFormatterProvider(uiSettings); + const generateTicks = generateTicksProvider(); + + // TODO: I wonder if we should supply our own moment that sets this every time? + // could just use angular's injection to provide a moment service? + moment.tz.setDefault(uiSettings.get('dateFormat:tz')); + + const render = $scope.seriesList.render || {}; + + $scope.chart = $scope.seriesList.list; + $scope.interval = $scope.interval; + $scope.search = $scope.search || _.noop; + + let legendValueNumbers: any; + let legendCaption: any; + const debouncedSetLegendNumbers = _.debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }); + // ensure legend is the same height with or without a caption so legend items do not move around + const emptyCaption = '
'; + + const defaultOptions = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + grid: { + show: render.grid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: any, series: any) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.setAttribute('kbn-accessible-click', ''); + wrapperSpan.setAttribute('ng-click', `toggleSeries(${series._id})`); + wrapperSpan.setAttribute('ng-focus', `focusSeries(${series._id})`); + wrapperSpan.setAttribute('ng-mouseover', `highlightSeries(${series._id})`); + + labelSpan.setAttribute('ng-non-bindable', ''); + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + colors: [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', + ], + }; + + const originalColorMap = new Map(); + $scope.chart.forEach((series: any, seriesIndex: any) => { + if (!series.color) { + const colorIndex = seriesIndex % defaultOptions.colors.length; + series.color = defaultOptions.colors[colorIndex]; + } + originalColorMap.set(series, series.color); + }); + + let highlightedSeries: any; + let focusedSeries: any; + function unhighlightSeries() { + if (highlightedSeries === null) { + return; + } + + highlightedSeries = null; + focusedSeries = null; + $scope.chart.forEach((series: any) => { + series.color = originalColorMap.get(series); // reset the colors + }); + drawPlot($scope.chart); + } + $scope.highlightSeries = _.debounce(function(id: any) { + if (highlightedSeries === id) { + return; + } + + highlightedSeries = id; + $scope.chart.forEach((series: any, seriesIndex: any) => { + if (seriesIndex !== id) { + series.color = 'rgba(128,128,128,0.1)'; // mark as grey + } else { + series.color = originalColorMap.get(series); // color it like it was + } + }); + drawPlot($scope.chart); + }, DEBOUNCE_DELAY); + $scope.focusSeries = function(id: any) { + focusedSeries = id; + $scope.highlightSeries(id); + }; + + $scope.toggleSeries = function(id: any) { + const series = $scope.chart[id]; + series._hide = !series._hide; + drawPlot($scope.chart); + }; + + const cancelResize = observeResize($elem, function() { + drawPlot($scope.chart); + }); + + $scope.$on('$destroy', function() { + cancelResize(); + $elem.off('plothover'); + $elem.off('plotselected'); + $elem.off('mouseleave'); + }); + + $elem.on('plothover', function(event: any, pos: any, item: any) { + $rootScope.$broadcast('timelionPlotHover', event, pos, item); + }); + + $elem.on('plotselected', function(event: any, ranges: any) { + timefilter.setTime({ + from: moment(ranges.xaxis.from), + to: moment(ranges.xaxis.to), + }); + }); + + $elem.on('mouseleave', function() { + $rootScope.$broadcast('timelionPlotLeave'); + }); + + $scope.$on('timelionPlotHover', function(angularEvent: any, flotEvent: any, pos: any) { + if (!$scope.plot) return; + $scope.plot.setCrosshair(pos); + debouncedSetLegendNumbers(pos); + }); + + $scope.$on('timelionPlotLeave', function() { + if (!$scope.plot) return; + $scope.plot.clearCrosshair(); + clearLegendNumbers(); + }); + + // Shamelessly borrowed from the flotCrosshairs example + function setLegendNumbers(pos: any) { + unhighlightSeries(); + + const plot = $scope.plot; + + const axes = plot.getAxes(); + if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { + return; + } + + let i; + const dataset = plot.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format( + _.get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT) + ) + ); + } + for (i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines.show && !series.lines.steps; + const precision = _.get(series, '_meta.precision', 2); + + if (series._hide) continue; + + const currentPoint = series.data.find((point: any, index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (y != null) { + let label = y.toFixed(precision); + if (series.yaxis.tickFormatter) { + label = series.yaxis.tickFormatter(label, series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + } + + function clearLegendNumbers() { + if (legendCaption) { + legendCaption.html(emptyCaption); + } + _.each(legendValueNumbers, function(num) { + $(num).empty(); + }); + } + + let legendScope = $scope.$new(); + function drawPlot(plotConfig: any) { + if (!$('.chart-canvas', $elem).length) $elem.html(template); + const canvasElem = $('.chart-canvas', $elem); + + // we can't use `$.plot` to draw the chart when the height or width is 0 + // so, we'll need another event to trigger drawPlot to actually draw it + if (canvasElem.height() === 0 || canvasElem.width() === 0) { + return; + } + + const title = _(plotConfig) + .map('_title') + .compact() + .last() as any; + $('.chart-top-title', $elem).text(title == null ? '' : title); + + const options = _.cloneDeep(defaultOptions) as any; + + // Get the X-axis tick format + const time = timefilter.getBounds() as any; + const interval = calculateInterval( + time.min.valueOf(), + time.max.valueOf(), + uiSettings.get('timelion:target_buckets') || 200, + $scope.interval, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = getxAxisFormatter(interval); + + // Use moment to format ticks so we get timezone correction + options.xaxis.tickFormatter = function(val: any) { + return moment(val).format(format); + }; + + // Calculate how many ticks can fit on the axis + const tickLetterWidth = 7; + const tickPadding = 45; + options.xaxis.ticks = Math.floor( + $elem.width() / (format.length * tickLetterWidth + tickPadding) + ); + + const series = _.map(plotConfig, function(serie: any, index) { + serie = _.cloneDeep( + _.defaults(serie, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + serie._id = index; + + if (serie.color) { + const span = document.createElement('span'); + span.style.color = serie.color; + serie.color = span.style.color; + } + + if (serie._hide) { + serie.data = []; + serie.stack = false; + // serie.color = "#ddd"; + serie.label = '(hidden) ' + serie.label; + } + + if (serie._global) { + _.merge(options, serie._global, function(objVal, srcVal) { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) return srcVal; + if (srcVal == null) return objVal; + }); + } + + return serie; + }); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: any) => { + if (yaxis && yaxis.units) { + yaxis.tickFormatter = formatters[yaxis.units.type]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicks; + } + } + }); + } + + // @ts-ignore + $scope.plot = $.plot(canvasElem, _.compact(series), options); + + if ($scope.plot) { + $scope.$emit('timelionChartRendered'); + } + + legendScope.$destroy(); + legendScope = $scope.$new(); + // Used to toggle the series, and for displaying values on hover + legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); + _.each(canvasElem.find('.ngLegendValue'), function(elem) { + $compile(elem)(legendScope); + }); + + if (_.get($scope.plot.getData(), '[0]._global.legend.showTime', true)) { + legendCaption = $(''); + legendCaption.html(emptyCaption); + canvasElem.find('div.legend table').append(legendCaption); + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + const $legendLabels = canvasElem.find('div.legend table .legendLabel>span'); + $legendLabels.get(focusedSeries).focus(); + } + } + } + $scope.$watch('chart', drawPlot); + }, + }; + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/tick_generator.ts new file mode 100644 index 0000000000000..f7d696a0316db --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/tick_generator.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function generateTicksProvider() { + function floorInBase(n: any, base: any) { + return base * Math.floor(n / base); + } + + function generateTicks(axis: any) { + const returnTicks = []; + let tickSize = 2; + let delta = axis.delta; + let steps = 0; + let tickVal; + let tickCount = 0; + + // Count the steps + while (Math.abs(delta) >= 1024) { + steps++; + delta /= 1024; + } + + // Set the tick size relative to the remaining delta + while (tickSize <= 1024) { + if (delta <= tickSize) { + break; + } + tickSize *= 2; + } + axis.tickSize = tickSize * Math.pow(1024, steps); + + // Calculate the new ticks + const tickMin = floorInBase(axis.min, axis.tickSize); + do { + tickVal = tickMin + tickCount++ * axis.tickSize; + returnTicks.push(tickVal); + } while (tickVal < axis.max); + + return returnTicks; + } + + return function(axis: any) { + return generateTicks(axis); + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts new file mode 100644 index 0000000000000..4173bfeb331e2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { timechartFn } from './schema'; +import { Panel } from '../panel'; +import { TimelionVisualizationDependencies } from '../../plugin'; + +export function getTimeChart(dependencies: TimelionVisualizationDependencies) { + // Schema is broken out so that it may be extended for use in other plugins + // Its also easier to test. + return new Panel('timechart', timechartFn(dependencies)()); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/xaxis_formatter.ts new file mode 100644 index 0000000000000..db3408dae33db --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/xaxis_formatter.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; + +import { i18n } from '@kbn/i18n'; + +export function xaxisFormatterProvider(config: any) { + function getFormat(esInterval: any) { + const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/); + + if (parts == null || parts[1] == null || parts[2] == null) { + throw new Error( + i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { + defaultMessage: 'Unknown interval', + }) + ); + } + + const interval = moment.duration(Number(parts[1]), parts[2]); + + // Cribbed from Kibana's TimeBuckets class + const rules = config.get('dateFormat:scaled'); + + for (let i = rules.length - 1; i >= 0; i--) { + const rule = rules[i]; + if (!rule[0] || interval >= moment.duration(rule[0])) { + return rule[1]; + } + } + + return config.get('dateFormat'); + } + + return function(esInterval: any) { + return getFormat(esInterval); + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts new file mode 100644 index 0000000000000..1d5b965bf5d66 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + IUiSettingsClient, + HttpSetup, +} from 'kibana/public'; +import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; +import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; +import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; +import { getTimeChart } from './panels/timechart/timechart'; +import { Panel } from './panels/panel'; +import { setServices } from './kibana_services'; + +import { getTimelionVisualizationConfig } from './timelion_vis_fn'; +import { getTimelionVisDefinition } from './timelion_vis_type'; + +/** @internal */ +export interface TimelionVisualizationDependencies { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timelionPanels: Map; + timefilter: TimefilterContract; +} + +/** @internal */ +export interface TimelionPluginSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export class TimelionVisPlugin implements Plugin { + initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public async setup( + core: CoreSetup, + { expressions, visualizations, data }: TimelionPluginSetupDependencies + ) { + const timelionPanels: Map = new Map(); + + setServices({ timelionPanels }); + const dependencies: TimelionVisualizationDependencies = { + uiSettings: core.uiSettings, + http: core.http, + timelionPanels, + timefilter: data.query.timefilter.timefilter, + }; + + this.registerPanels(dependencies); + + expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); + visualizations.types.createBaseVisualization(getTimelionVisDefinition(dependencies)); + } + + private registerPanels(dependencies: TimelionVisualizationDependencies) { + const timeChartPanel: Panel = getTimeChart(dependencies); + + dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); + } + + public start(core: CoreStart) { + const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); + + if (timelionUiEnabled === false) { + core.chrome.navLinks.update('timelion', { hidden: true }); + } + } +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js b/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js new file mode 100644 index 0000000000000..6e01351c15030 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { uiModules } from 'ui/modules'; +import { createLegacyClass } from 'ui/utils/legacy_class'; +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +const module = uiModules.get('app/timelion'); + +// Used only by the savedSheets service, usually no reason to change this +module.factory('SavedSheet', function (Private, config) { + + // SavedSheet constructor. Usually you'd interact with an instance of this. + // ID is option, without it one will be generated on save. + const SavedObject = Private(SavedObjectProvider); + createLegacyClass(SavedSheet).inherits(SavedObject); + function SavedSheet(id) { + // Gives our SavedSheet the properties of a SavedObject + SavedObject.call(this, { + type: SavedSheet.type, + mapping: SavedSheet.mapping, + + // if this is null/undefined then the SavedObject will be assigned the defaults + id: id, + + // default values that will get assigned if the doc is new + defaults: { + title: 'New TimeLion Sheet', + hits: 0, + description: '', + timelion_sheet: ['.es(*)'], + timelion_interval: 'auto', + timelion_chart_height: 275, + timelion_columns: config.get('timelion:default_columns') || 2, + timelion_rows: config.get('timelion:default_rows') || 2, + version: 1, + } + }); + + this.showInRecentlyAccessed = true; + } + + // save these objects with the 'sheet' type + SavedSheet.type = 'timelion-sheet'; + + // if type:sheet has no mapping, we push this mapping into ES + SavedSheet.mapping = { + title: 'text', + hits: 'integer', + description: 'text', + timelion_sheet: 'text', + timelion_interval: 'keyword', + timelion_other_interval: 'keyword', + timelion_chart_height: 'integer', + timelion_columns: 'integer', + timelion_rows: 'integer', + version: 'integer' + }; + + // Order these fields to the top, the rest are alphabetical + SavedSheet.fieldOrder = ['title', 'description']; + + SavedSheet.prototype.getFullPath = function () { + return `/app/timelion#/${this.id}`; + }; + + return SavedSheet; +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js new file mode 100644 index 0000000000000..0353a4d742dd3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './saved_sheets'; + +export default function savedSearchObjectFn(savedSheets) { + return savedSheets; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js new file mode 100644 index 0000000000000..d303069e74dea --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; +import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_object_registry'; +import { uiModules } from 'ui/modules'; +import './_saved_sheet.js'; + +const module = uiModules.get('app/sheet'); + +// Register this service with the saved object registry so it can be +// edited by the object editor. +savedObjectManagementRegistry.register({ + service: 'savedSheets', + title: 'sheets' +}); + +// This is the only thing that gets injected into controllers +module.service('savedSheets', function (Private, SavedSheet, kbnUrl, chrome) { + const savedObjectClient = Private(SavedObjectsClientProvider); + const savedSheetLoader = new SavedObjectLoader(SavedSheet, kbnUrl, chrome, savedObjectClient); + savedSheetLoader.urlFor = function (id) { + return kbnUrl.eval('#/{{id}}', { id: id }); + }; + + // Customize loader properties since adding an 's' on type doesn't work for type 'timelion-sheet'. + savedSheetLoader.loaderProperties = { + name: 'timelion-sheet', + noun: 'Saved Sheets', + nouns: 'saved sheets' + }; + return savedSheetLoader; +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts new file mode 100644 index 0000000000000..2c78d2423cc06 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +function baseTickFormatter(value: any, axis: any) { + const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; + const formatted = '' + Math.round(value * factor) / factor; + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (axis.tickDecimals != null) { + const decimal = formatted.indexOf('.'); + const precision = decimal === -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.tickDecimals) { + return ( + (precision ? formatted : formatted + '.') + + ('' + factor).substr(1, axis.tickDecimals - precision) + ); + } + } + + return formatted; +} + +function unitFormatter(divisor: any, units: any) { + return (val: any) => { + let index = 0; + const isNegative = val < 0; + val = Math.abs(val); + while (val >= divisor && index < units.length) { + val /= divisor; + index++; + } + const value = (Math.round(val * 100) / 100) * (isNegative ? -1 : 1); + return `${value}${units[index]}`; + }; +} + +export function tickFormatters() { + const formatters = { + bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), + 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), + bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), + 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), + currency(val: any, axis: any) { + return val.toLocaleString('en', { + style: 'currency', + currency: axis.options.units.prefix || 'USD', + }); + }, + percent(val: any, axis: any) { + let precision = + _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); + // toFixed only accepts values between 0 and 20 + if (precision < 0) { + precision = 0; + } else if (precision > 20) { + precision = 20; + } + + return (val * 100).toFixed(precision) + '%'; + }, + custom(val: any, axis: any) { + const formattedVal = baseTickFormatter(val, axis); + const prefix = axis.options.units.prefix; + const suffix = axis.options.units.suffix; + return prefix + formattedVal + suffix; + }, + }; + + return formatters; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts new file mode 100644 index 0000000000000..f8186d4965f2a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TimelionVisualizationDependencies } from './plugin'; +import { TIMELION_VIS_NAME } from './timelion_vis_type'; + +const name = 'timelion_vis'; + +interface Arguments { + expression: string; + interval: any; +} + +interface RenderValue { + visData: Context; + visType: 'timelion'; + visParams: VisParams; +} + +type Context = KibanaContext | null; +type VisParams = Arguments; +type Return = Promise>; + +export const getTimelionVisualizationConfig = ( + dependencies: TimelionVisualizationDependencies +): ExpressionFunction => ({ + name, + type: 'render', + context: { + types: ['kibana_context', 'null'], + }, + help: i18n.translate('timelion.function.help', { + defaultMessage: 'Timelion visualization', + }), + args: { + expression: { + types: ['string'], + aliases: ['_'], + default: '".es(*)"', + help: '', + }, + interval: { + types: ['string', 'null'], + default: 'auto', + help: '', + }, + }, + async fn(context, args) { + const timelionRequestHandler = getTimelionRequestHandler(dependencies); + + const visParams = { expression: args.expression, interval: args.interval }; + + const response = await timelionRequestHandler({ + timeRange: get(context, 'timeRange'), + query: get(context, 'query'), + filters: get(context, 'filters'), + visParams, + forceFetch: true, + }); + + response.visType = TIMELION_VIS_NAME; + + return { + type: 'render', + as: 'visualization', + value: { + visParams, + visType: TIMELION_VIS_NAME, + visData: response, + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_params.html b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_params.html new file mode 100644 index 0000000000000..9f2d2094fb1f7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_params.html @@ -0,0 +1,27 @@ +
+
+ +
+ +
+
+ +
+
+ +
+ + +
+ +
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts new file mode 100644 index 0000000000000..42a08cb9724f0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +// @ts-ignore +import { DefaultEditorSize } from './legacy_imports'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TimelionVisComponent } from './components/timelion_vis'; +import editorConfigTemplate from './timelion_vis_params.html'; +import { TimelionVisualizationDependencies } from './plugin'; +import { VisController } from './vis_controller'; + +export const TIMELION_VIS_NAME = 'timelion'; + +export function getTimelionVisDefinition(dependencies: TimelionVisualizationDependencies) { + const timelionRequestHandler = getTimelionRequestHandler(dependencies); + + // return the visType object, which kibana will use to display and configure new + // Vis object of this type. + return { + name: TIMELION_VIS_NAME, + title: 'Timelion', + icon: 'visTimelion', + description: i18n.translate('timelion.timelionDescription', { + defaultMessage: 'Build time-series using functional expressions', + }), + visualization: VisController, + visConfig: { + defaults: { + expression: '.es(*)', + interval: 'auto', + }, + component: TimelionVisComponent, + }, + editorConfig: { + optionsTemplate: editorConfigTemplate, + defaultSize: DefaultEditorSize.MEDIUM, + }, + requestHandler: timelionRequestHandler, + responseHandler: 'none', + options: { + showIndexSelection: false, + showQueryBar: false, + showFilterBar: false, + }, + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts new file mode 100644 index 0000000000000..5c427fa09ac81 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts @@ -0,0 +1,92 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { Fragment } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nContext } from 'ui/i18n'; + + class VisController { + el: any; + vis: any; + + constructor(el: any, vis: any) { + this.el = el; + this.vis = vis; + } + + async render(visData, visParams, status) { + if (status.params || (visParams.useTimeFilter && status.time)) { + this.visParams = visParams; + this.drawVis(); + } + } + + destroy() { + unmountComponentAtNode(this.el); + } + + drawVis = () => { + const text = 'Vis controller'; + render(null, this.el); + }; + + async initControls() { + // const controlParamsList = this.visParams.controls.filter((controlParams) => { + // // ignore controls that do not have indexPattern or field + // return controlParams.indexPattern && controlParams.fieldName; + // }); + + // const controlFactoryPromises = controlParamsList.map((controlParams) => { + // const factory = controlFactory(controlParams); + // return factory(controlParams, this.visParams.useTimeFilter, SearchSource); + // }); + // const controls = await Promise.all(controlFactoryPromises); + + // const getControl = (id) => { + // return controls.find(control => { + // return id === control.id; + // }); + // }; + + // const controlInitPromises = []; + // getLineageMap(controlParamsList).forEach((lineage, controlId) => { + // // first lineage item is the control. remove it + // lineage.shift(); + // const ancestors = []; + // lineage.forEach(ancestorId => { + // ancestors.push(getControl(ancestorId)); + // }); + // const control = getControl(controlId); + // control.setAncestors(ancestors); + // controlInitPromises.push(control.fetch()); + // }); + + // await Promise.all(controlInitPromises); + // return controls; + } + + stageFilter = async (controlIndex, newValue) => { + if (this.visParams.updateFiltersOnChange) { + } else { + this.drawVis(); + } + } +} + +export { VisController }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js new file mode 100644 index 0000000000000..cda8038953c76 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js @@ -0,0 +1,462 @@ +/* +Axis Labels Plugin for flot. +http://github.com/markrcote/flot-axislabels +Original code is Copyright (c) 2010 Xuan Luo. +Original code was released under the GPLv3 license by Xuan Luo, September 2010. +Original code was rereleased under the MIT license by Xuan Luo, April 2012. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +(function ($) { + var options = { + axisLabels: { + show: true + } + }; + + function canvasSupported() { + return !!document.createElement('canvas').getContext; + } + + function canvasTextSupported() { + if (!canvasSupported()) { + return false; + } + var dummy_canvas = document.createElement('canvas'); + var context = dummy_canvas.getContext('2d'); + return typeof context.fillText == 'function'; + } + + function css3TransitionSupported() { + var div = document.createElement('div'); + return typeof div.style.MozTransition != 'undefined' // Gecko + || typeof div.style.OTransition != 'undefined' // Opera + || typeof div.style.webkitTransition != 'undefined' // WebKit + || typeof div.style.transition != 'undefined'; + } + + + function AxisLabel(axisName, position, padding, plot, opts) { + this.axisName = axisName; + this.position = position; + this.padding = padding; + this.plot = plot; + this.opts = opts; + this.width = 0; + this.height = 0; + } + + AxisLabel.prototype.cleanup = function() { + }; + + + CanvasAxisLabel.prototype = new AxisLabel(); + CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; + function CanvasAxisLabel(axisName, position, padding, plot, opts) { + AxisLabel.prototype.constructor.call(this, axisName, position, padding, + plot, opts); + } + + CanvasAxisLabel.prototype.calculateSize = function() { + if (!this.opts.axisLabelFontSizePixels) + this.opts.axisLabelFontSizePixels = 14; + if (!this.opts.axisLabelFontFamily) + this.opts.axisLabelFontFamily = 'sans-serif'; + + var textWidth = this.opts.axisLabelFontSizePixels + this.padding; + var textHeight = this.opts.axisLabelFontSizePixels + this.padding; + if (this.position == 'left' || this.position == 'right') { + this.width = this.opts.axisLabelFontSizePixels + this.padding; + this.height = 0; + } else { + this.width = 0; + this.height = this.opts.axisLabelFontSizePixels + this.padding; + } + }; + + CanvasAxisLabel.prototype.draw = function(box) { + if (!this.opts.axisLabelColour) + this.opts.axisLabelColour = 'black'; + var ctx = this.plot.getCanvas().getContext('2d'); + ctx.save(); + ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + + this.opts.axisLabelFontFamily; + ctx.fillStyle = this.opts.axisLabelColour; + var width = ctx.measureText(this.opts.axisLabel).width; + var height = this.opts.axisLabelFontSizePixels; + var x, y, angle = 0; + if (this.position == 'top') { + x = box.left + box.width/2 - width/2; + y = box.top + height*0.72; + } else if (this.position == 'bottom') { + x = box.left + box.width/2 - width/2; + y = box.top + box.height - height*0.72; + } else if (this.position == 'left') { + x = box.left + height*0.72; + y = box.height/2 + box.top + width/2; + angle = -Math.PI/2; + } else if (this.position == 'right') { + x = box.left + box.width - height*0.72; + y = box.height/2 + box.top - width/2; + angle = Math.PI/2; + } + ctx.translate(x, y); + ctx.rotate(angle); + ctx.fillText(this.opts.axisLabel, 0, 0); + ctx.restore(); + }; + + + HtmlAxisLabel.prototype = new AxisLabel(); + HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; + function HtmlAxisLabel(axisName, position, padding, plot, opts) { + AxisLabel.prototype.constructor.call(this, axisName, position, + padding, plot, opts); + this.elem = null; + } + + HtmlAxisLabel.prototype.calculateSize = function() { + var elem = $('
' + + this.opts.axisLabel + '
'); + this.plot.getPlaceholder().append(elem); + // store height and width of label itself, for use in draw() + this.labelWidth = elem.outerWidth(true); + this.labelHeight = elem.outerHeight(true); + elem.remove(); + + this.width = this.height = 0; + if (this.position == 'left' || this.position == 'right') { + this.width = this.labelWidth + this.padding; + } else { + this.height = this.labelHeight + this.padding; + } + }; + + HtmlAxisLabel.prototype.cleanup = function() { + if (this.elem) { + this.elem.remove(); + } + }; + + HtmlAxisLabel.prototype.draw = function(box) { + this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); + this.elem = $('
' + + this.opts.axisLabel + '
'); + this.plot.getPlaceholder().append(this.elem); + if (this.position == 'top') { + this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + + 'px'); + this.elem.css('top', box.top + 'px'); + } else if (this.position == 'bottom') { + this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + + 'px'); + this.elem.css('top', box.top + box.height - this.labelHeight + + 'px'); + } else if (this.position == 'left') { + this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + + 'px'); + this.elem.css('left', box.left + 'px'); + } else if (this.position == 'right') { + this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + + 'px'); + this.elem.css('left', box.left + box.width - this.labelWidth + + 'px'); + } + }; + + + CssTransformAxisLabel.prototype = new HtmlAxisLabel(); + CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; + function CssTransformAxisLabel(axisName, position, padding, plot, opts) { + HtmlAxisLabel.prototype.constructor.call(this, axisName, position, + padding, plot, opts); + } + + CssTransformAxisLabel.prototype.calculateSize = function() { + HtmlAxisLabel.prototype.calculateSize.call(this); + this.width = this.height = 0; + if (this.position == 'left' || this.position == 'right') { + this.width = this.labelHeight + this.padding; + } else { + this.height = this.labelHeight + this.padding; + } + }; + + CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { + var stransforms = { + '-moz-transform': '', + '-webkit-transform': '', + '-o-transform': '', + '-ms-transform': '' + }; + if (x != 0 || y != 0) { + var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; + stransforms['-moz-transform'] += stdTranslate; + stransforms['-webkit-transform'] += stdTranslate; + stransforms['-o-transform'] += stdTranslate; + stransforms['-ms-transform'] += stdTranslate; + } + if (degrees != 0) { + var rotation = degrees / 90; + var stdRotate = ' rotate(' + degrees + 'deg)'; + stransforms['-moz-transform'] += stdRotate; + stransforms['-webkit-transform'] += stdRotate; + stransforms['-o-transform'] += stdRotate; + stransforms['-ms-transform'] += stdRotate; + } + var s = 'top: 0; left: 0; '; + for (var prop in stransforms) { + if (stransforms[prop]) { + s += prop + ':' + stransforms[prop] + ';'; + } + } + s += ';'; + return s; + }; + + CssTransformAxisLabel.prototype.calculateOffsets = function(box) { + var offsets = { x: 0, y: 0, degrees: 0 }; + if (this.position == 'bottom') { + offsets.x = box.left + box.width/2 - this.labelWidth/2; + offsets.y = box.top + box.height - this.labelHeight; + } else if (this.position == 'top') { + offsets.x = box.left + box.width/2 - this.labelWidth/2; + offsets.y = box.top; + } else if (this.position == 'left') { + offsets.degrees = -90; + offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; + offsets.y = box.height/2 + box.top; + } else if (this.position == 'right') { + offsets.degrees = 90; + offsets.x = box.left + box.width - this.labelWidth/2 + - this.labelHeight/2; + offsets.y = box.height/2 + box.top; + } + offsets.x = Math.round(offsets.x); + offsets.y = Math.round(offsets.y); + + return offsets; + }; + + CssTransformAxisLabel.prototype.draw = function(box) { + this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); + var offsets = this.calculateOffsets(box); + this.elem = $('
' + this.opts.axisLabel + '
'); + this.plot.getPlaceholder().append(this.elem); + }; + + + IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); + IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; + function IeTransformAxisLabel(axisName, position, padding, plot, opts) { + CssTransformAxisLabel.prototype.constructor.call(this, axisName, + position, padding, + plot, opts); + this.requiresResize = false; + } + + IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { + // I didn't feel like learning the crazy Matrix stuff, so this uses + // a combination of the rotation transform and CSS positioning. + var s = ''; + if (degrees != 0) { + var rotation = degrees/90; + while (rotation < 0) { + rotation += 4; + } + s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; + // see below + this.requiresResize = (this.position == 'right'); + } + if (x != 0) { + s += 'left: ' + x + 'px; '; + } + if (y != 0) { + s += 'top: ' + y + 'px; '; + } + return s; + }; + + IeTransformAxisLabel.prototype.calculateOffsets = function(box) { + var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( + this, box); + // adjust some values to take into account differences between + // CSS and IE rotations. + if (this.position == 'top') { + // FIXME: not sure why, but placing this exactly at the top causes + // the top axis label to flip to the bottom... + offsets.y = box.top + 1; + } else if (this.position == 'left') { + offsets.x = box.left; + offsets.y = box.height/2 + box.top - this.labelWidth/2; + } else if (this.position == 'right') { + offsets.x = box.left + box.width - this.labelHeight; + offsets.y = box.height/2 + box.top - this.labelWidth/2; + } + return offsets; + }; + + IeTransformAxisLabel.prototype.draw = function(box) { + CssTransformAxisLabel.prototype.draw.call(this, box); + if (this.requiresResize) { + this.elem = this.plot.getPlaceholder().find("." + this.axisName + + "Label"); + // Since we used CSS positioning instead of transforms for + // translating the element, and since the positioning is done + // before any rotations, we have to reset the width and height + // in case the browser wrapped the text (specifically for the + // y2axis). + this.elem.css('width', this.labelWidth); + this.elem.css('height', this.labelHeight); + } + }; + + + function init(plot) { + plot.hooks.processOptions.push(function (plot, options) { + + if (!options.axisLabels.show) + return; + + // This is kind of a hack. There are no hooks in Flot between + // the creation and measuring of the ticks (setTicks, measureTickLabels + // in setupGrid() ) and the drawing of the ticks and plot box + // (insertAxisLabels in setupGrid() ). + // + // Therefore, we use a trick where we run the draw routine twice: + // the first time to get the tick measurements, so that we can change + // them, and then have it draw it again. + var secondPass = false; + + var axisLabels = {}; + var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; + + var defaultPadding = 2; // padding between axis and tick labels + plot.hooks.draw.push(function (plot, ctx) { + var hasAxisLabels = false; + if (!secondPass) { + // MEASURE AND SET OPTIONS + $.each(plot.getAxes(), function(axisName, axis) { + var opts = axis.options // Flot 0.7 + || plot.getOptions()[axisName]; // Flot 0.6 + + // Handle redraws initiated outside of this plug-in. + if (axisName in axisLabels) { + axis.labelHeight = axis.labelHeight - + axisLabels[axisName].height; + axis.labelWidth = axis.labelWidth - + axisLabels[axisName].width; + opts.labelHeight = axis.labelHeight; + opts.labelWidth = axis.labelWidth; + axisLabels[axisName].cleanup(); + delete axisLabels[axisName]; + } + + if (!opts || !opts.axisLabel || !axis.show) + return; + + hasAxisLabels = true; + var renderer = null; + + if (!opts.axisLabelUseHtml && + navigator.appName == 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) { + rv = parseFloat(RegExp.$1); + } + if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { + renderer = CssTransformAxisLabel; + } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { + renderer = IeTransformAxisLabel; + } else if (opts.axisLabelUseCanvas) { + renderer = CanvasAxisLabel; + } else { + renderer = HtmlAxisLabel; + } + } else { + if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { + renderer = HtmlAxisLabel; + } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { + renderer = CanvasAxisLabel; + } else { + renderer = CssTransformAxisLabel; + } + } + + var padding = opts.axisLabelPadding === undefined ? + defaultPadding : opts.axisLabelPadding; + + axisLabels[axisName] = new renderer(axisName, + axis.position, padding, + plot, opts); + + // flot interprets axis.labelHeight and .labelWidth as + // the height and width of the tick labels. We increase + // these values to make room for the axis label and + // padding. + + axisLabels[axisName].calculateSize(); + + // AxisLabel.height and .width are the size of the + // axis label and padding. + // Just set opts here because axis will be sorted out on + // the redraw. + + opts.labelHeight = axis.labelHeight + + axisLabels[axisName].height; + opts.labelWidth = axis.labelWidth + + axisLabels[axisName].width; + }); + + // If there are axis labels, re-draw with new label widths and + // heights. + + if (hasAxisLabels) { + secondPass = true; + plot.setupGrid(); + plot.draw(); + } + } else { + secondPass = false; + // DRAW + $.each(plot.getAxes(), function(axisName, axis) { + var opts = axis.options // Flot 0.7 + || plot.getOptions()[axisName]; // Flot 0.6 + if (!opts || !opts.axisLabel || !axis.show) + return; + + axisLabels[axisName].draw(axis.box); + }); + } + }); + }); + } + + + $.plot.plugins.push({ + init: init, + options: options, + name: 'axisLabels', + version: '2.0' + }); +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js new file mode 100644 index 0000000000000..5111695e3d12c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js @@ -0,0 +1,176 @@ +/* Flot plugin for showing crosshairs when the mouse hovers over the plot. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +The plugin supports these options: + + crosshair: { + mode: null or "x" or "y" or "xy" + color: color + lineWidth: number + } + +Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical +crosshair that lets you trace the values on the x axis, "y" enables a +horizontal crosshair and "xy" enables them both. "color" is the color of the +crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of +the drawn lines (default is 1). + +The plugin also adds four public methods: + + - setCrosshair( pos ) + + Set the position of the crosshair. Note that this is cleared if the user + moves the mouse. "pos" is in coordinates of the plot and should be on the + form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple + axes), which is coincidentally the same format as what you get from a + "plothover" event. If "pos" is null, the crosshair is cleared. + + - clearCrosshair() + + Clear the crosshair. + + - lockCrosshair(pos) + + Cause the crosshair to lock to the current location, no longer updating if + the user moves the mouse. Optionally supply a position (passed on to + setCrosshair()) to move it to. + + Example usage: + + var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; + $("#graph").bind( "plothover", function ( evt, position, item ) { + if ( item ) { + // Lock the crosshair to the data point being hovered + myFlot.lockCrosshair({ + x: item.datapoint[ 0 ], + y: item.datapoint[ 1 ] + }); + } else { + // Return normal crosshair operation + myFlot.unlockCrosshair(); + } + }); + + - unlockCrosshair() + + Free the crosshair to move again after locking it. +*/ + +(function ($) { + var options = { + crosshair: { + mode: null, // one of null, "x", "y" or "xy", + color: "rgba(170, 0, 0, 0.80)", + lineWidth: 1 + } + }; + + function init(plot) { + // position of crosshair in pixels + var crosshair = { x: -1, y: -1, locked: false }; + + plot.setCrosshair = function setCrosshair(pos) { + if (!pos) + crosshair.x = -1; + else { + var o = plot.p2c(pos); + crosshair.x = Math.max(0, Math.min(o.left, plot.width())); + crosshair.y = Math.max(0, Math.min(o.top, plot.height())); + } + + plot.triggerRedrawOverlay(); + }; + + plot.clearCrosshair = plot.setCrosshair; // passes null for pos + + plot.lockCrosshair = function lockCrosshair(pos) { + if (pos) + plot.setCrosshair(pos); + crosshair.locked = true; + }; + + plot.unlockCrosshair = function unlockCrosshair() { + crosshair.locked = false; + }; + + function onMouseOut(e) { + if (crosshair.locked) + return; + + if (crosshair.x != -1) { + crosshair.x = -1; + plot.triggerRedrawOverlay(); + } + } + + function onMouseMove(e) { + if (crosshair.locked) + return; + + if (plot.getSelection && plot.getSelection()) { + crosshair.x = -1; // hide the crosshair while selecting + return; + } + + var offset = plot.offset(); + crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); + crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); + plot.triggerRedrawOverlay(); + } + + plot.hooks.bindEvents.push(function (plot, eventHolder) { + if (!plot.getOptions().crosshair.mode) + return; + + eventHolder.mouseout(onMouseOut); + eventHolder.mousemove(onMouseMove); + }); + + plot.hooks.drawOverlay.push(function (plot, ctx) { + var c = plot.getOptions().crosshair; + if (!c.mode) + return; + + var plotOffset = plot.getPlotOffset(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + if (crosshair.x != -1) { + var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; + + ctx.strokeStyle = c.color; + ctx.lineWidth = c.lineWidth; + ctx.lineJoin = "round"; + + ctx.beginPath(); + if (c.mode.indexOf("x") != -1) { + var drawX = Math.floor(crosshair.x) + adj; + ctx.moveTo(drawX, 0); + ctx.lineTo(drawX, plot.height()); + } + if (c.mode.indexOf("y") != -1) { + var drawY = Math.floor(crosshair.y) + adj; + ctx.moveTo(0, drawY); + ctx.lineTo(plot.width(), drawY); + } + ctx.stroke(); + } + ctx.restore(); + }); + + plot.hooks.shutdown.push(function (plot, eventHolder) { + eventHolder.unbind("mouseout", onMouseOut); + eventHolder.unbind("mousemove", onMouseMove); + }); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'crosshair', + version: '1.0' + }); +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js new file mode 100644 index 0000000000000..5d613037cf234 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js @@ -0,0 +1,3168 @@ +/* JavaScript plotting library for jQuery, version 0.8.3. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +*/ + +// first an inline dependency, jquery.colorhelpers.js, we inline it here +// for convenience + +/* Plugin for jQuery for working with colors. + * + * Version 1.1. + * + * Inspiration from jQuery color animation plugin by John Resig. + * + * Released under the MIT license by Ole Laursen, October 2009. + * + * Examples: + * + * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() + * var c = $.color.extract($("#mydiv"), 'background-color'); + * console.log(c.r, c.g, c.b, c.a); + * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" + * + * Note that .scale() and .add() return the same modified object + * instead of making a new one. + * + * V. 1.1: Fix error handling so e.g. parsing an empty string does + * produce a color rather than just crashing. + */ +(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); + +// the actual Flot code +(function($) { + + // Cache the prototype hasOwnProperty for faster access + + var hasOwnProperty = Object.prototype.hasOwnProperty; + + // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM + // operation produces the same effect as detach, i.e. removing the element + // without touching its jQuery data. + + // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+. + + if (!$.fn.detach) { + $.fn.detach = function() { + return this.each(function() { + if (this.parentNode) { + this.parentNode.removeChild( this ); + } + }); + }; + } + + /////////////////////////////////////////////////////////////////////////// + // The Canvas object is a wrapper around an HTML5 tag. + // + // @constructor + // @param {string} cls List of classes to apply to the canvas. + // @param {element} container Element onto which to append the canvas. + // + // Requiring a container is a little iffy, but unfortunately canvas + // operations don't work unless the canvas is attached to the DOM. + + function Canvas(cls, container) { + + var element = container.children("." + cls)[0]; + + if (element == null) { + + element = document.createElement("canvas"); + element.className = cls; + + $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) + .appendTo(container); + + // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas + + if (!element.getContext) { + if (window.G_vmlCanvasManager) { + element = window.G_vmlCanvasManager.initElement(element); + } else { + throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); + } + } + } + + this.element = element; + + var context = this.context = element.getContext("2d"); + + // Determine the screen's ratio of physical to device-independent + // pixels. This is the ratio between the canvas width that the browser + // advertises and the number of pixels actually present in that space. + + // The iPhone 4, for example, has a device-independent width of 320px, + // but its screen is actually 640px wide. It therefore has a pixel + // ratio of 2, while most normal devices have a ratio of 1. + + var devicePixelRatio = window.devicePixelRatio || 1, + backingStoreRatio = + context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || 1; + + this.pixelRatio = devicePixelRatio / backingStoreRatio; + + // Size the canvas to match the internal dimensions of its container + + this.resize(container.width(), container.height()); + + // Collection of HTML div layers for text overlaid onto the canvas + + this.textContainer = null; + this.text = {}; + + // Cache of text fragments and metrics, so we can avoid expensively + // re-calculating them when the plot is re-rendered in a loop. + + this._textCache = {}; + } + + // Resizes the canvas to the given dimensions. + // + // @param {number} width New width of the canvas, in pixels. + // @param {number} width New height of the canvas, in pixels. + + Canvas.prototype.resize = function(width, height) { + + if (width <= 0 || height <= 0) { + throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); + } + + var element = this.element, + context = this.context, + pixelRatio = this.pixelRatio; + + // Resize the canvas, increasing its density based on the display's + // pixel ratio; basically giving it more pixels without increasing the + // size of its element, to take advantage of the fact that retina + // displays have that many more pixels in the same advertised space. + + // Resizing should reset the state (excanvas seems to be buggy though) + + if (this.width != width) { + element.width = width * pixelRatio; + element.style.width = width + "px"; + this.width = width; + } + + if (this.height != height) { + element.height = height * pixelRatio; + element.style.height = height + "px"; + this.height = height; + } + + // Save the context, so we can reset in case we get replotted. The + // restore ensure that we're really back at the initial state, and + // should be safe even if we haven't saved the initial state yet. + + context.restore(); + context.save(); + + // Scale the coordinate space to match the display density; so even though we + // may have twice as many pixels, we still want lines and other drawing to + // appear at the same size; the extra pixels will just make them crisper. + + context.scale(pixelRatio, pixelRatio); + }; + + // Clears the entire canvas area, not including any overlaid HTML text + + Canvas.prototype.clear = function() { + this.context.clearRect(0, 0, this.width, this.height); + }; + + // Finishes rendering the canvas, including managing the text overlay. + + Canvas.prototype.render = function() { + + var cache = this._textCache; + + // For each text layer, add elements marked as active that haven't + // already been rendered, and remove those that are no longer active. + + for (var layerKey in cache) { + if (hasOwnProperty.call(cache, layerKey)) { + + var layer = this.getTextLayer(layerKey), + layerCache = cache[layerKey]; + + layer.hide(); + + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey]; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + + var positions = styleCache[key].positions; + + for (var i = 0, position; position = positions[i]; i++) { + if (position.active) { + if (!position.rendered) { + layer.append(position.element); + position.rendered = true; + } + } else { + positions.splice(i--, 1); + if (position.rendered) { + position.element.detach(); + } + } + } + + if (positions.length == 0) { + delete styleCache[key]; + } + } + } + } + } + + layer.show(); + } + } + }; + + // Creates (if necessary) and returns the text overlay container. + // + // @param {string} classes String of space-separated CSS classes used to + // uniquely identify the text layer. + // @return {object} The jQuery-wrapped text-layer div. + + Canvas.prototype.getTextLayer = function(classes) { + + var layer = this.text[classes]; + + // Create the text layer if it doesn't exist + + if (layer == null) { + + // Create the text layer container, if it doesn't exist + + if (this.textContainer == null) { + this.textContainer = $("
") + .css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0, + 'font-size': "smaller", + color: "#545454" + }) + .insertAfter(this.element); + } + + layer = this.text[classes] = $("
") + .addClass(classes) + .css({ + position: "absolute", + top: 0, + left: 0, + bottom: 0, + right: 0 + }) + .appendTo(this.textContainer); + } + + return layer; + }; + + // Creates (if necessary) and returns a text info object. + // + // The object looks like this: + // + // { + // width: Width of the text's wrapper div. + // height: Height of the text's wrapper div. + // element: The jQuery-wrapped HTML div containing the text. + // positions: Array of positions at which this text is drawn. + // } + // + // The positions array contains objects that look like this: + // + // { + // active: Flag indicating whether the text should be visible. + // rendered: Flag indicating whether the text is currently visible. + // element: The jQuery-wrapped HTML div containing the text. + // x: X coordinate at which to draw the text. + // y: Y coordinate at which to draw the text. + // } + // + // Each position after the first receives a clone of the original element. + // + // The idea is that that the width, height, and general 'identity' of the + // text is constant no matter where it is placed; the placements are a + // secondary property. + // + // Canvas maintains a cache of recently-used text info objects; getTextInfo + // either returns the cached element or creates a new entry. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {string} text Text string to retrieve info for. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @param {number=} width Maximum width of the text before it wraps. + // @return {object} a text info object. + + Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { + + var textStyle, layerCache, styleCache, info; + + // Cast the value to a string, in case we were given a number or such + + text = "" + text; + + // If the font is a font-spec object, generate a CSS font definition + + if (typeof font === "object") { + textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; + } else { + textStyle = font; + } + + // Retrieve (or create) the cache for the text's layer and styles + + layerCache = this._textCache[layer]; + + if (layerCache == null) { + layerCache = this._textCache[layer] = {}; + } + + styleCache = layerCache[textStyle]; + + if (styleCache == null) { + styleCache = layerCache[textStyle] = {}; + } + + info = styleCache[text]; + + // If we can't find a matching element in our cache, create a new one + + if (info == null) { + + var element = $("
").html(text) + .css({ + position: "absolute", + 'max-width': width, + top: -9999 + }) + .appendTo(this.getTextLayer(layer)); + + if (typeof font === "object") { + element.css({ + font: textStyle, + color: font.color + }); + } else if (typeof font === "string") { + element.addClass(font); + } + + info = styleCache[text] = { + width: element.outerWidth(true), + height: element.outerHeight(true), + element: element, + positions: [] + }; + + element.detach(); + } + + return info; + }; + + // Adds a text string to the canvas text overlay. + // + // The text isn't drawn immediately; it is marked as rendering, which will + // result in its addition to the canvas on the next render pass. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {number} x X coordinate at which to draw the text. + // @param {number} y Y coordinate at which to draw the text. + // @param {string} text Text string to draw. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which to rotate the text, in degrees. + // Angle is currently unused, it will be implemented in the future. + // @param {number=} width Maximum width of the text before it wraps. + // @param {string=} halign Horizontal alignment of the text; either "left", + // "center" or "right". + // @param {string=} valign Vertical alignment of the text; either "top", + // "middle" or "bottom". + + Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { + + var info = this.getTextInfo(layer, text, font, angle, width), + positions = info.positions; + + // Tweak the div's position to match the text's alignment + + if (halign == "center") { + x -= info.width / 2; + } else if (halign == "right") { + x -= info.width; + } + + if (valign == "middle") { + y -= info.height / 2; + } else if (valign == "bottom") { + y -= info.height; + } + + // Determine whether this text already exists at this position. + // If so, mark it for inclusion in the next render pass. + + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = true; + return; + } + } + + // If the text doesn't exist at this position, create a new entry + + // For the very first position we'll re-use the original element, + // while for subsequent ones we'll clone it. + + position = { + active: true, + rendered: false, + element: positions.length ? info.element.clone() : info.element, + x: x, + y: y + }; + + positions.push(position); + + // Move the element to its final position within the container + + position.element.css({ + top: Math.round(y), + left: Math.round(x), + 'text-align': halign // In case the text wraps + }); + }; + + // Removes one or more text strings from the canvas text overlay. + // + // If no parameters are given, all text within the layer is removed. + // + // Note that the text is not immediately removed; it is simply marked as + // inactive, which will result in its removal on the next render pass. + // This avoids the performance penalty for 'clear and redraw' behavior, + // where we potentially get rid of all text on a layer, but will likely + // add back most or all of it later, as when redrawing axes, for example. + // + // @param {string} layer A string of space-separated CSS classes uniquely + // identifying the layer containing this text. + // @param {number=} x X coordinate of the text. + // @param {number=} y Y coordinate of the text. + // @param {string=} text Text string to remove. + // @param {(string|object)=} font Either a string of space-separated CSS + // classes or a font-spec object, defining the text's font and style. + // @param {number=} angle Angle at which the text is rotated, in degrees. + // Angle is currently unused, it will be implemented in the future. + + Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { + if (text == null) { + var layerCache = this._textCache[layer]; + if (layerCache != null) { + for (var styleKey in layerCache) { + if (hasOwnProperty.call(layerCache, styleKey)) { + var styleCache = layerCache[styleKey]; + for (var key in styleCache) { + if (hasOwnProperty.call(styleCache, key)) { + var positions = styleCache[key].positions; + for (var i = 0, position; position = positions[i]; i++) { + position.active = false; + } + } + } + } + } + } + } else { + var positions = this.getTextInfo(layer, text, font, angle).positions; + for (var i = 0, position; position = positions[i]; i++) { + if (position.x == x && position.y == y) { + position.active = false; + } + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + // The top-level container for the entire plot. + + function Plot(placeholder, data_, options_, plugins) { + // data is on the form: + // [ series1, series2 ... ] + // where series is either just the data as [ [x1, y1], [x2, y2], ... ] + // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } + + var series = [], + options = { + // the color theme used for graphs + colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], + legend: { + show: true, + noColumns: 1, // number of columns in legend table + labelFormatter: null, // fn: string -> string + labelBoxBorderColor: "#ccc", // border color for the little label boxes + container: null, // container (as jQuery object) to put legend in, null means default on top of graph + position: "ne", // position of default legend container within plot + margin: 5, // distance from grid edge to default legend container within plot + backgroundColor: null, // null means auto-detect + backgroundOpacity: 0.85, // set to 0 to avoid background + sorted: null // default to no legend sorting + }, + xaxis: { + show: null, // null = auto-detect, true = always, false = never + position: "bottom", // or "top" + mode: null, // null or "time" + font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } + color: null, // base color, labels, ticks + tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" + transform: null, // null or f: number -> number to transform axis + inverseTransform: null, // if transform is set, this should be the inverse function + min: null, // min. value to show, null means set automatically + max: null, // max. value to show, null means set automatically + autoscaleMargin: null, // margin in % to add if auto-setting min/max + ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks + tickFormatter: null, // fn: number -> string + labelWidth: null, // size of tick labels in pixels + labelHeight: null, + reserveSpace: null, // whether to reserve space even if axis isn't shown + tickLength: null, // size in pixels of ticks, or "full" for whole line + alignTicksWithAxis: null, // axis number or null for no sync + tickDecimals: null, // no. of decimals, null means auto + tickSize: null, // number or [number, "unit"] + minTickSize: null // number or [number, "unit"] + }, + yaxis: { + autoscaleMargin: 0.02, + position: "left" // or "right" + }, + xaxes: [], + yaxes: [], + series: { + points: { + show: false, + radius: 3, + lineWidth: 2, // in pixels + fill: true, + fillColor: "#ffffff", + symbol: "circle" // or callback + }, + lines: { + // we don't put in show: false so we can see + // whether lines were actively disabled + lineWidth: 2, // in pixels + fill: false, + fillColor: null, + steps: false + // Omit 'zero', so we can later default its value to + // match that of the 'fill' option. + }, + bars: { + show: false, + lineWidth: 2, // in pixels + barWidth: 1, // in units of the x axis + fill: true, + fillColor: null, + align: "left", // "left", "right", or "center" + horizontal: false, + zero: true + }, + shadowSize: 3, + highlightColor: null + }, + grid: { + show: true, + aboveData: false, + color: "#545454", // primary color used for outline and labels + backgroundColor: null, // null for transparent, else color + borderColor: null, // set if different from the grid color + tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" + margin: 0, // distance from the canvas edge to the grid + labelMargin: 5, // in pixels + axisMargin: 8, // in pixels + borderWidth: 2, // in pixels + minBorderMargin: null, // in pixels, null means taken from points radius + markings: null, // array of ranges or fn: axes -> array of ranges + markingsColor: "#f4f4f4", + markingsLineWidth: 2, + // interactive stuff + clickable: false, + hoverable: false, + autoHighlight: true, // highlight in case mouse is near + mouseActiveRadius: 10 // how far the mouse can be away to activate an item + }, + interaction: { + redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow + }, + hooks: {} + }, + surface = null, // the canvas for the plot itself + overlay = null, // canvas for interactive stuff on top of plot + eventHolder = null, // jQuery object that events should be bound to + ctx = null, octx = null, + xaxes = [], yaxes = [], + plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, + plotWidth = 0, plotHeight = 0, + hooks = { + processOptions: [], + processRawData: [], + processDatapoints: [], + processOffset: [], + drawBackground: [], + drawSeries: [], + draw: [], + bindEvents: [], + drawOverlay: [], + shutdown: [] + }, + plot = this; + + // public functions + plot.setData = setData; + plot.setupGrid = setupGrid; + plot.draw = draw; + plot.getPlaceholder = function() { return placeholder; }; + plot.getCanvas = function() { return surface.element; }; + plot.getPlotOffset = function() { return plotOffset; }; + plot.width = function () { return plotWidth; }; + plot.height = function () { return plotHeight; }; + plot.offset = function () { + var o = eventHolder.offset(); + o.left += plotOffset.left; + o.top += plotOffset.top; + return o; + }; + plot.getData = function () { return series; }; + plot.getAxes = function () { + var res = {}, i; + $.each(xaxes.concat(yaxes), function (_, axis) { + if (axis) + res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; + }); + return res; + }; + plot.getXAxes = function () { return xaxes; }; + plot.getYAxes = function () { return yaxes; }; + plot.c2p = canvasToAxisCoords; + plot.p2c = axisToCanvasCoords; + plot.getOptions = function () { return options; }; + plot.highlight = highlight; + plot.unhighlight = unhighlight; + plot.triggerRedrawOverlay = triggerRedrawOverlay; + plot.pointOffset = function(point) { + return { + left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), + top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) + }; + }; + plot.shutdown = shutdown; + plot.destroy = function () { + shutdown(); + placeholder.removeData("plot").empty(); + + series = []; + options = null; + surface = null; + overlay = null; + eventHolder = null; + ctx = null; + octx = null; + xaxes = []; + yaxes = []; + hooks = null; + highlights = []; + plot = null; + }; + plot.resize = function () { + var width = placeholder.width(), + height = placeholder.height(); + surface.resize(width, height); + overlay.resize(width, height); + }; + + // public attributes + plot.hooks = hooks; + + // initialize + initPlugins(plot); + parseOptions(options_); + setupCanvases(); + setData(data_); + setupGrid(); + draw(); + bindEvents(); + + + function executeHooks(hook, args) { + args = [plot].concat(args); + for (var i = 0; i < hook.length; ++i) + hook[i].apply(this, args); + } + + function initPlugins() { + + // References to key classes, allowing plugins to modify them + + var classes = { + Canvas: Canvas + }; + + for (var i = 0; i < plugins.length; ++i) { + var p = plugins[i]; + p.init(plot, classes); + if (p.options) + $.extend(true, options, p.options); + } + } + + function parseOptions(opts) { + + $.extend(true, options, opts); + + // $.extend merges arrays, rather than replacing them. When less + // colors are provided than the size of the default palette, we + // end up with those colors plus the remaining defaults, which is + // not expected behavior; avoid it by replacing them here. + + if (opts && opts.colors) { + options.colors = opts.colors; + } + + if (options.xaxis.color == null) + options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + if (options.yaxis.color == null) + options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility + options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; + if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility + options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; + + if (options.grid.borderColor == null) + options.grid.borderColor = options.grid.color; + if (options.grid.tickColor == null) + options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); + + // Fill in defaults for axis options, including any unspecified + // font-spec fields, if a font-spec was provided. + + // If no x/y axis options were provided, create one of each anyway, + // since the rest of the code assumes that they exist. + + var i, axisOptions, axisCount, + fontSize = placeholder.css("font-size"), + fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, + fontDefaults = { + style: placeholder.css("font-style"), + size: Math.round(0.8 * fontSizeDefault), + variant: placeholder.css("font-variant"), + weight: placeholder.css("font-weight"), + family: placeholder.css("font-family") + }; + + axisCount = options.xaxes.length || 1; + for (i = 0; i < axisCount; ++i) { + + axisOptions = options.xaxes[i]; + if (axisOptions && !axisOptions.tickColor) { + axisOptions.tickColor = axisOptions.color; + } + + axisOptions = $.extend(true, {}, options.xaxis, axisOptions); + options.xaxes[i] = axisOptions; + + if (axisOptions.font) { + axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); + if (!axisOptions.font.color) { + axisOptions.font.color = axisOptions.color; + } + if (!axisOptions.font.lineHeight) { + axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); + } + } + } + + axisCount = options.yaxes.length || 1; + for (i = 0; i < axisCount; ++i) { + + axisOptions = options.yaxes[i]; + if (axisOptions && !axisOptions.tickColor) { + axisOptions.tickColor = axisOptions.color; + } + + axisOptions = $.extend(true, {}, options.yaxis, axisOptions); + options.yaxes[i] = axisOptions; + + if (axisOptions.font) { + axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); + if (!axisOptions.font.color) { + axisOptions.font.color = axisOptions.color; + } + if (!axisOptions.font.lineHeight) { + axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); + } + } + } + + // backwards compatibility, to be removed in future + if (options.xaxis.noTicks && options.xaxis.ticks == null) + options.xaxis.ticks = options.xaxis.noTicks; + if (options.yaxis.noTicks && options.yaxis.ticks == null) + options.yaxis.ticks = options.yaxis.noTicks; + if (options.x2axis) { + options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); + options.xaxes[1].position = "top"; + // Override the inherit to allow the axis to auto-scale + if (options.x2axis.min == null) { + options.xaxes[1].min = null; + } + if (options.x2axis.max == null) { + options.xaxes[1].max = null; + } + } + if (options.y2axis) { + options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); + options.yaxes[1].position = "right"; + // Override the inherit to allow the axis to auto-scale + if (options.y2axis.min == null) { + options.yaxes[1].min = null; + } + if (options.y2axis.max == null) { + options.yaxes[1].max = null; + } + } + if (options.grid.coloredAreas) + options.grid.markings = options.grid.coloredAreas; + if (options.grid.coloredAreasColor) + options.grid.markingsColor = options.grid.coloredAreasColor; + if (options.lines) + $.extend(true, options.series.lines, options.lines); + if (options.points) + $.extend(true, options.series.points, options.points); + if (options.bars) + $.extend(true, options.series.bars, options.bars); + if (options.shadowSize != null) + options.series.shadowSize = options.shadowSize; + if (options.highlightColor != null) + options.series.highlightColor = options.highlightColor; + + // save options on axes for future reference + for (i = 0; i < options.xaxes.length; ++i) + getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; + for (i = 0; i < options.yaxes.length; ++i) + getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; + + // add hooks from options + for (var n in hooks) + if (options.hooks[n] && options.hooks[n].length) + hooks[n] = hooks[n].concat(options.hooks[n]); + + executeHooks(hooks.processOptions, [options]); + } + + function setData(d) { + series = parseData(d); + fillInSeriesOptions(); + processData(); + } + + function parseData(d) { + var res = []; + for (var i = 0; i < d.length; ++i) { + var s = $.extend(true, {}, options.series); + + if (d[i].data != null) { + s.data = d[i].data; // move the data instead of deep-copy + delete d[i].data; + + $.extend(true, s, d[i]); + + d[i].data = s.data; + } + else + s.data = d[i]; + res.push(s); + } + + return res; + } + + function axisNumber(obj, coord) { + var a = obj[coord + "axis"]; + if (typeof a == "object") // if we got a real axis, extract number + a = a.n; + if (typeof a != "number") + a = 1; // default to first axis + return a; + } + + function allAxes() { + // return flat array without annoying null entries + return $.grep(xaxes.concat(yaxes), function (a) { return a; }); + } + + function canvasToAxisCoords(pos) { + // return an object with x/y corresponding to all used axes + var res = {}, i, axis; + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) + res["x" + axis.n] = axis.c2p(pos.left); + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) + res["y" + axis.n] = axis.c2p(pos.top); + } + + if (res.x1 !== undefined) + res.x = res.x1; + if (res.y1 !== undefined) + res.y = res.y1; + + return res; + } + + function axisToCanvasCoords(pos) { + // get canvas coords from the first pair of x/y found in pos + var res = {}, i, axis, key; + + for (i = 0; i < xaxes.length; ++i) { + axis = xaxes[i]; + if (axis && axis.used) { + key = "x" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "x"; + + if (pos[key] != null) { + res.left = axis.p2c(pos[key]); + break; + } + } + } + + for (i = 0; i < yaxes.length; ++i) { + axis = yaxes[i]; + if (axis && axis.used) { + key = "y" + axis.n; + if (pos[key] == null && axis.n == 1) + key = "y"; + + if (pos[key] != null) { + res.top = axis.p2c(pos[key]); + break; + } + } + } + + return res; + } + + function getOrCreateAxis(axes, number) { + if (!axes[number - 1]) + axes[number - 1] = { + n: number, // save the number for future reference + direction: axes == xaxes ? "x" : "y", + options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) + }; + + return axes[number - 1]; + } + + function fillInSeriesOptions() { + + var neededColors = series.length, maxIndex = -1, i; + + // Subtract the number of series that already have fixed colors or + // color indexes from the number that we still need to generate. + + for (i = 0; i < series.length; ++i) { + var sc = series[i].color; + if (sc != null) { + neededColors--; + if (typeof sc == "number" && sc > maxIndex) { + maxIndex = sc; + } + } + } + + // If any of the series have fixed color indexes, then we need to + // generate at least as many colors as the highest index. + + if (neededColors <= maxIndex) { + neededColors = maxIndex + 1; + } + + // Generate all the colors, using first the option colors and then + // variations on those colors once they're exhausted. + + var c, colors = [], colorPool = options.colors, + colorPoolSize = colorPool.length, variation = 0; + + for (i = 0; i < neededColors; i++) { + + c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); + + // Each time we exhaust the colors in the pool we adjust + // a scaling factor used to produce more variations on + // those colors. The factor alternates negative/positive + // to produce lighter/darker colors. + + // Reset the variation after every few cycles, or else + // it will end up producing only white or black colors. + + if (i % colorPoolSize == 0 && i) { + if (variation >= 0) { + if (variation < 0.5) { + variation = -variation - 0.2; + } else variation = 0; + } else variation = -variation; + } + + colors[i] = c.scale('rgb', 1 + variation); + } + + // Finalize the series options, filling in their colors + + var colori = 0, s; + for (i = 0; i < series.length; ++i) { + s = series[i]; + + // assign colors + if (s.color == null) { + s.color = colors[colori].toString(); + ++colori; + } + else if (typeof s.color == "number") + s.color = colors[s.color].toString(); + + // turn on lines automatically in case nothing is set + if (s.lines.show == null) { + var v, show = true; + for (v in s) + if (s[v] && s[v].show) { + show = false; + break; + } + if (show) + s.lines.show = true; + } + + // If nothing was provided for lines.zero, default it to match + // lines.fill, since areas by default should extend to zero. + + if (s.lines.zero == null) { + s.lines.zero = !!s.lines.fill; + } + + // setup axes + s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); + s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); + } + } + + function processData() { + var topSentry = Number.POSITIVE_INFINITY, + bottomSentry = Number.NEGATIVE_INFINITY, + fakeInfinity = Number.MAX_VALUE, + i, j, k, m, length, + s, points, ps, x, y, axis, val, f, p, + data, format; + + function updateAxis(axis, min, max) { + if (min < axis.datamin && min != -fakeInfinity) + axis.datamin = min; + if (max > axis.datamax && max != fakeInfinity) + axis.datamax = max; + } + + $.each(allAxes(), function (_, axis) { + // init axis + axis.datamin = topSentry; + axis.datamax = bottomSentry; + axis.used = false; + }); + + for (i = 0; i < series.length; ++i) { + s = series[i]; + s.datapoints = { points: [] }; + + executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); + } + + // first pass: clean and copy data + for (i = 0; i < series.length; ++i) { + s = series[i]; + + data = s.data; + format = s.datapoints.format; + + if (!format) { + format = []; + // find out how to copy + format.push({ x: true, number: true, required: true }); + format.push({ y: true, number: true, required: true }); + + if (s.bars.show || (s.lines.show && s.lines.fill)) { + var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); + format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); + if (s.bars.horizontal) { + delete format[format.length - 1].y; + format[format.length - 1].x = true; + } + } + + s.datapoints.format = format; + } + + if (s.datapoints.pointsize != null) + continue; // already filled in + + s.datapoints.pointsize = format.length; + + ps = s.datapoints.pointsize; + points = s.datapoints.points; + + var insertSteps = s.lines.show && s.lines.steps; + s.xaxis.used = s.yaxis.used = true; + + for (j = k = 0; j < data.length; ++j, k += ps) { + p = data[j]; + + var nullify = p == null; + if (!nullify) { + for (m = 0; m < ps; ++m) { + val = p[m]; + f = format[m]; + + if (f) { + if (f.number && val != null) { + val = +val; // convert to number + if (isNaN(val)) + val = null; + else if (val == Infinity) + val = fakeInfinity; + else if (val == -Infinity) + val = -fakeInfinity; + } + + if (val == null) { + if (f.required) + nullify = true; + + if (f.defaultValue != null) + val = f.defaultValue; + } + } + + points[k + m] = val; + } + } + + if (nullify) { + for (m = 0; m < ps; ++m) { + val = points[k + m]; + if (val != null) { + f = format[m]; + // extract min/max info + if (f.autoscale !== false) { + if (f.x) { + updateAxis(s.xaxis, val, val); + } + if (f.y) { + updateAxis(s.yaxis, val, val); + } + } + } + points[k + m] = null; + } + } + else { + // a little bit of line specific stuff that + // perhaps shouldn't be here, but lacking + // better means... + if (insertSteps && k > 0 + && points[k - ps] != null + && points[k - ps] != points[k] + && points[k - ps + 1] != points[k + 1]) { + // copy the point to make room for a middle point + for (m = 0; m < ps; ++m) + points[k + ps + m] = points[k + m]; + + // middle point has same y + points[k + 1] = points[k - ps + 1]; + + // we've added a point, better reflect that + k += ps; + } + } + } + } + + // give the hooks a chance to run + for (i = 0; i < series.length; ++i) { + s = series[i]; + + executeHooks(hooks.processDatapoints, [ s, s.datapoints]); + } + + // second pass: find datamax/datamin for auto-scaling + for (i = 0; i < series.length; ++i) { + s = series[i]; + points = s.datapoints.points; + ps = s.datapoints.pointsize; + format = s.datapoints.format; + + var xmin = topSentry, ymin = topSentry, + xmax = bottomSentry, ymax = bottomSentry; + + for (j = 0; j < points.length; j += ps) { + if (points[j] == null) + continue; + + for (m = 0; m < ps; ++m) { + val = points[j + m]; + f = format[m]; + if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) + continue; + + if (f.x) { + if (val < xmin) + xmin = val; + if (val > xmax) + xmax = val; + } + if (f.y) { + if (val < ymin) + ymin = val; + if (val > ymax) + ymax = val; + } + } + } + + if (s.bars.show) { + // make sure we got room for the bar on the dancing floor + var delta; + + switch (s.bars.align) { + case "left": + delta = 0; + break; + case "right": + delta = -s.bars.barWidth; + break; + default: + delta = -s.bars.barWidth / 2; + } + + if (s.bars.horizontal) { + ymin += delta; + ymax += delta + s.bars.barWidth; + } + else { + xmin += delta; + xmax += delta + s.bars.barWidth; + } + } + + updateAxis(s.xaxis, xmin, xmax); + updateAxis(s.yaxis, ymin, ymax); + } + + $.each(allAxes(), function (_, axis) { + if (axis.datamin == topSentry) + axis.datamin = null; + if (axis.datamax == bottomSentry) + axis.datamax = null; + }); + } + + function setupCanvases() { + + // Make sure the placeholder is clear of everything except canvases + // from a previous plot in this container that we'll try to re-use. + + placeholder.css("padding", 0) // padding messes up the positioning + .children().filter(function(){ + return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); + }).remove(); + + if (placeholder.css("position") == 'static') + placeholder.css("position", "relative"); // for positioning labels and overlay + + surface = new Canvas("flot-base", placeholder); + overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features + + ctx = surface.context; + octx = overlay.context; + + // define which element we're listening for events on + eventHolder = $(overlay.element).unbind(); + + // If we're re-using a plot object, shut down the old one + + var existing = placeholder.data("plot"); + + if (existing) { + existing.shutdown(); + overlay.clear(); + } + + // save in case we get replotted + placeholder.data("plot", plot); + } + + function bindEvents() { + // bind events + if (options.grid.hoverable) { + eventHolder.mousemove(onMouseMove); + + // Use bind, rather than .mouseleave, because we officially + // still support jQuery 1.2.6, which doesn't define a shortcut + // for mouseenter or mouseleave. This was a bug/oversight that + // was fixed somewhere around 1.3.x. We can return to using + // .mouseleave when we drop support for 1.2.6. + + eventHolder.bind("mouseleave", onMouseLeave); + } + + if (options.grid.clickable) + eventHolder.click(onClick); + + executeHooks(hooks.bindEvents, [eventHolder]); + } + + function shutdown() { + if (redrawTimeout) + clearTimeout(redrawTimeout); + + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mouseleave", onMouseLeave); + eventHolder.unbind("click", onClick); + + executeHooks(hooks.shutdown, [eventHolder]); + } + + function setTransformationHelpers(axis) { + // set helper functions on the axis, assumes plot area + // has been computed already + + function identity(x) { return x; } + + var s, m, t = axis.options.transform || identity, + it = axis.options.inverseTransform; + + // precompute how much the axis is scaling a point + // in canvas space + if (axis.direction == "x") { + s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); + m = Math.min(t(axis.max), t(axis.min)); + } + else { + s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); + s = -s; + m = Math.max(t(axis.max), t(axis.min)); + } + + // data point to canvas coordinate + if (t == identity) // slight optimization + axis.p2c = function (p) { return (p - m) * s; }; + else + axis.p2c = function (p) { return (t(p) - m) * s; }; + // canvas coordinate to data point + if (!it) + axis.c2p = function (c) { return m + c / s; }; + else + axis.c2p = function (c) { return it(m + c / s); }; + } + + function measureTickLabels(axis) { + + var opts = axis.options, + ticks = axis.ticks || [], + labelWidth = opts.labelWidth || 0, + labelHeight = opts.labelHeight || 0, + maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), + legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + font = opts.font || "flot-tick-label tickLabel"; + + for (var i = 0; i < ticks.length; ++i) { + + var t = ticks[i]; + + if (!t.label) + continue; + + var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); + + labelWidth = Math.max(labelWidth, info.width); + labelHeight = Math.max(labelHeight, info.height); + } + + axis.labelWidth = opts.labelWidth || labelWidth; + axis.labelHeight = opts.labelHeight || labelHeight; + } + + function allocateAxisBoxFirstPhase(axis) { + // find the bounding box of the axis by looking at label + // widths/heights and ticks, make room by diminishing the + // plotOffset; this first phase only looks at one + // dimension per axis, the other dimension depends on the + // other axes so will have to wait + + var lw = axis.labelWidth, + lh = axis.labelHeight, + pos = axis.options.position, + isXAxis = axis.direction === "x", + tickLength = axis.options.tickLength, + axisMargin = options.grid.axisMargin, + padding = options.grid.labelMargin, + innermost = true, + outermost = true, + first = true, + found = false; + + // Determine the axis's position in its direction and on its side + + $.each(isXAxis ? xaxes : yaxes, function(i, a) { + if (a && (a.show || a.reserveSpace)) { + if (a === axis) { + found = true; + } else if (a.options.position === pos) { + if (found) { + outermost = false; + } else { + innermost = false; + } + } + if (!found) { + first = false; + } + } + }); + + // The outermost axis on each side has no margin + + if (outermost) { + axisMargin = 0; + } + + // The ticks for the first axis in each direction stretch across + + if (tickLength == null) { + tickLength = first ? "full" : 5; + } + + if (!isNaN(+tickLength)) + padding += +tickLength; + + if (isXAxis) { + lh += padding; + + if (pos == "bottom") { + plotOffset.bottom += lh + axisMargin; + axis.box = { top: surface.height - plotOffset.bottom, height: lh }; + } + else { + axis.box = { top: plotOffset.top + axisMargin, height: lh }; + plotOffset.top += lh + axisMargin; + } + } + else { + lw += padding; + + if (pos == "left") { + axis.box = { left: plotOffset.left + axisMargin, width: lw }; + plotOffset.left += lw + axisMargin; + } + else { + plotOffset.right += lw + axisMargin; + axis.box = { left: surface.width - plotOffset.right, width: lw }; + } + } + + // save for future reference + axis.position = pos; + axis.tickLength = tickLength; + axis.box.padding = padding; + axis.innermost = innermost; + } + + function allocateAxisBoxSecondPhase(axis) { + // now that all axis boxes have been placed in one + // dimension, we can set the remaining dimension coordinates + if (axis.direction == "x") { + axis.box.left = plotOffset.left - axis.labelWidth / 2; + axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; + } + else { + axis.box.top = plotOffset.top - axis.labelHeight / 2; + axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; + } + } + + function adjustLayoutForThingsStickingOut() { + // possibly adjust plot offset to ensure everything stays + // inside the canvas and isn't clipped off + + var minMargin = options.grid.minBorderMargin, + axis, i; + + // check stuff from the plot (FIXME: this should just read + // a value from the series, otherwise it's impossible to + // customize) + if (minMargin == null) { + minMargin = 0; + for (i = 0; i < series.length; ++i) + minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); + } + + var margins = { + left: minMargin, + right: minMargin, + top: minMargin, + bottom: minMargin + }; + + // check axis labels, note we don't check the actual + // labels but instead use the overall width/height to not + // jump as much around with replots + $.each(allAxes(), function (_, axis) { + if (axis.reserveSpace && axis.ticks && axis.ticks.length) { + if (axis.direction === "x") { + margins.left = Math.max(margins.left, axis.labelWidth / 2); + margins.right = Math.max(margins.right, axis.labelWidth / 2); + } else { + margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); + margins.top = Math.max(margins.top, axis.labelHeight / 2); + } + } + }); + + plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); + plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); + plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); + plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); + } + + function setupGrid() { + var i, axes = allAxes(), showGrid = options.grid.show; + + // Initialize the plot's offset from the edge of the canvas + + for (var a in plotOffset) { + var margin = options.grid.margin || 0; + plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; + } + + executeHooks(hooks.processOffset, [plotOffset]); + + // If the grid is visible, add its border width to the offset + + for (var a in plotOffset) { + if(typeof(options.grid.borderWidth) == "object") { + plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; + } + else { + plotOffset[a] += showGrid ? options.grid.borderWidth : 0; + } + } + + $.each(axes, function (_, axis) { + var axisOpts = axis.options; + axis.show = axisOpts.show == null ? axis.used : axisOpts.show; + axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; + setRange(axis); + }); + + if (showGrid) { + + var allocatedAxes = $.grep(axes, function (axis) { + return axis.show || axis.reserveSpace; + }); + + $.each(allocatedAxes, function (_, axis) { + // make the ticks + setupTickGeneration(axis); + setTicks(axis); + snapRangeToTicks(axis, axis.ticks); + // find labelWidth/Height for axis + measureTickLabels(axis); + }); + + // with all dimensions calculated, we can compute the + // axis bounding boxes, start from the outside + // (reverse order) + for (i = allocatedAxes.length - 1; i >= 0; --i) + allocateAxisBoxFirstPhase(allocatedAxes[i]); + + // make sure we've got enough space for things that + // might stick out + adjustLayoutForThingsStickingOut(); + + $.each(allocatedAxes, function (_, axis) { + allocateAxisBoxSecondPhase(axis); + }); + } + + plotWidth = surface.width - plotOffset.left - plotOffset.right; + plotHeight = surface.height - plotOffset.bottom - plotOffset.top; + + // now we got the proper plot dimensions, we can compute the scaling + $.each(axes, function (_, axis) { + setTransformationHelpers(axis); + }); + + if (showGrid) { + drawAxisLabels(); + } + + insertLegend(); + } + + function setRange(axis) { + var opts = axis.options, + min = +(opts.min != null ? opts.min : axis.datamin), + max = +(opts.max != null ? opts.max : axis.datamax), + delta = max - min; + + if (delta == 0.0) { + // degenerate case + var widen = max == 0 ? 1 : 0.01; + + if (opts.min == null) + min -= widen; + // always widen max if we couldn't widen min to ensure we + // don't fall into min == max which doesn't work + if (opts.max == null || opts.min != null) + max += widen; + } + else { + // consider autoscaling + var margin = opts.autoscaleMargin; + if (margin != null) { + if (opts.min == null) { + min -= delta * margin; + // make sure we don't go below zero if all values + // are positive + if (min < 0 && axis.datamin != null && axis.datamin >= 0) + min = 0; + } + if (opts.max == null) { + max += delta * margin; + if (max > 0 && axis.datamax != null && axis.datamax <= 0) + max = 0; + } + } + } + axis.min = min; + axis.max = max; + } + + function setupTickGeneration(axis) { + var opts = axis.options; + + // estimate number of ticks + var noTicks; + if (typeof opts.ticks == "number" && opts.ticks > 0) + noTicks = opts.ticks; + else + // heuristic based on the model a*sqrt(x) fitted to + // some data points that seemed reasonable + noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); + + var delta = (axis.max - axis.min) / noTicks, + dec = -Math.floor(Math.log(delta) / Math.LN10), + maxDec = opts.tickDecimals; + + if (maxDec != null && dec > maxDec) { + dec = maxDec; + } + + var magn = Math.pow(10, -dec), + norm = delta / magn, // norm is between 1.0 and 10.0 + size; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + // special case for 2.5, requires an extra decimal + if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { + size = 2.5; + ++dec; + } + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + + if (opts.minTickSize != null && size < opts.minTickSize) { + size = opts.minTickSize; + } + + axis.delta = delta; + axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); + axis.tickSize = opts.tickSize || size; + + // Time mode was moved to a plug-in in 0.8, and since so many people use it + // we'll add an especially friendly reminder to make sure they included it. + + if (opts.mode == "time" && !axis.tickGenerator) { + throw new Error("Time mode requires the flot.time plugin."); + } + + // Flot supports base-10 axes; any other mode else is handled by a plug-in, + // like flot.time.js. + + if (!axis.tickGenerator) { + + axis.tickGenerator = function (axis) { + + var ticks = [], + start = floorInBase(axis.min, axis.tickSize), + i = 0, + v = Number.NaN, + prev; + + do { + prev = v; + v = start + i * axis.tickSize; + ticks.push(v); + ++i; + } while (v < axis.max && v != prev); + return ticks; + }; + + axis.tickFormatter = function (value, axis) { + + var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; + var formatted = "" + Math.round(value * factor) / factor; + + // If tickDecimals was specified, ensure that we have exactly that + // much precision; otherwise default to the value's own precision. + + if (axis.tickDecimals != null) { + var decimal = formatted.indexOf("."); + var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; + if (precision < axis.tickDecimals) { + return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); + } + } + + return formatted; + }; + } + + if ($.isFunction(opts.tickFormatter)) + axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; + + if (opts.alignTicksWithAxis != null) { + var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; + if (otherAxis && otherAxis.used && otherAxis != axis) { + // consider snapping min/max to outermost nice ticks + var niceTicks = axis.tickGenerator(axis); + if (niceTicks.length > 0) { + if (opts.min == null) + axis.min = Math.min(axis.min, niceTicks[0]); + if (opts.max == null && niceTicks.length > 1) + axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); + } + + axis.tickGenerator = function (axis) { + // copy ticks, scaled to this axis + var ticks = [], v, i; + for (i = 0; i < otherAxis.ticks.length; ++i) { + v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); + v = axis.min + v * (axis.max - axis.min); + ticks.push(v); + } + return ticks; + }; + + // we might need an extra decimal since forced + // ticks don't necessarily fit naturally + if (!axis.mode && opts.tickDecimals == null) { + var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), + ts = axis.tickGenerator(axis); + + // only proceed if the tick interval rounded + // with an extra decimal doesn't give us a + // zero at end + if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) + axis.tickDecimals = extraDec; + } + } + } + } + + function setTicks(axis) { + var oticks = axis.options.ticks, ticks = []; + if (oticks == null || (typeof oticks == "number" && oticks > 0)) + ticks = axis.tickGenerator(axis); + else if (oticks) { + if ($.isFunction(oticks)) + // generate the ticks + ticks = oticks(axis); + else + ticks = oticks; + } + + // clean up/labelify the supplied ticks, copy them over + var i, v; + axis.ticks = []; + for (i = 0; i < ticks.length; ++i) { + var label = null; + var t = ticks[i]; + if (typeof t == "object") { + v = +t[0]; + if (t.length > 1) + label = t[1]; + } + else + v = +t; + if (label == null) + label = axis.tickFormatter(v, axis); + if (!isNaN(v)) + axis.ticks.push({ v: v, label: label }); + } + } + + function snapRangeToTicks(axis, ticks) { + if (axis.options.autoscaleMargin && ticks.length > 0) { + // snap to ticks + if (axis.options.min == null) + axis.min = Math.min(axis.min, ticks[0].v); + if (axis.options.max == null && ticks.length > 1) + axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); + } + } + + function draw() { + + surface.clear(); + + executeHooks(hooks.drawBackground, [ctx]); + + var grid = options.grid; + + // draw background, if any + if (grid.show && grid.backgroundColor) + drawBackground(); + + if (grid.show && !grid.aboveData) { + drawGrid(); + } + + for (var i = 0; i < series.length; ++i) { + executeHooks(hooks.drawSeries, [ctx, series[i]]); + drawSeries(series[i]); + } + + executeHooks(hooks.draw, [ctx]); + + if (grid.show && grid.aboveData) { + drawGrid(); + } + + surface.render(); + + // A draw implies that either the axes or data have changed, so we + // should probably update the overlay highlights as well. + + triggerRedrawOverlay(); + } + + function extractRange(ranges, coord) { + var axis, from, to, key, axes = allAxes(); + + for (var i = 0; i < axes.length; ++i) { + axis = axes[i]; + if (axis.direction == coord) { + key = coord + axis.n + "axis"; + if (!ranges[key] && axis.n == 1) + key = coord + "axis"; // support x1axis as xaxis + if (ranges[key]) { + from = ranges[key].from; + to = ranges[key].to; + break; + } + } + } + + // backwards-compat stuff - to be removed in future + if (!ranges[key]) { + axis = coord == "x" ? xaxes[0] : yaxes[0]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) { + var tmp = from; + from = to; + to = tmp; + } + + return { from: from, to: to, axis: axis }; + } + + function drawBackground() { + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); + ctx.fillRect(0, 0, plotWidth, plotHeight); + ctx.restore(); + } + + function drawGrid() { + var i, axes, bw, bc; + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // draw markings + var markings = options.grid.markings; + if (markings) { + if ($.isFunction(markings)) { + axes = plot.getAxes(); + // xmin etc. is backwards compatibility, to be + // removed in the future + axes.xmin = axes.xaxis.min; + axes.xmax = axes.xaxis.max; + axes.ymin = axes.yaxis.min; + axes.ymax = axes.yaxis.max; + + markings = markings(axes); + } + + for (i = 0; i < markings.length; ++i) { + var m = markings[i], + xrange = extractRange(m, "x"), + yrange = extractRange(m, "y"); + + // fill in missing + if (xrange.from == null) + xrange.from = xrange.axis.min; + if (xrange.to == null) + xrange.to = xrange.axis.max; + if (yrange.from == null) + yrange.from = yrange.axis.min; + if (yrange.to == null) + yrange.to = yrange.axis.max; + + // clip + if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || + yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) + continue; + + xrange.from = Math.max(xrange.from, xrange.axis.min); + xrange.to = Math.min(xrange.to, xrange.axis.max); + yrange.from = Math.max(yrange.from, yrange.axis.min); + yrange.to = Math.min(yrange.to, yrange.axis.max); + + var xequal = xrange.from === xrange.to, + yequal = yrange.from === yrange.to; + + if (xequal && yequal) { + continue; + } + + // then draw + xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); + xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); + yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); + yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); + + if (xequal || yequal) { + var lineWidth = m.lineWidth || options.grid.markingsLineWidth, + subPixel = lineWidth % 2 ? 0.5 : 0; + ctx.beginPath(); + ctx.strokeStyle = m.color || options.grid.markingsColor; + ctx.lineWidth = lineWidth; + if (xequal) { + ctx.moveTo(xrange.to + subPixel, yrange.from); + ctx.lineTo(xrange.to + subPixel, yrange.to); + } else { + ctx.moveTo(xrange.from, yrange.to + subPixel); + ctx.lineTo(xrange.to, yrange.to + subPixel); + } + ctx.stroke(); + } else { + ctx.fillStyle = m.color || options.grid.markingsColor; + ctx.fillRect(xrange.from, yrange.to, + xrange.to - xrange.from, + yrange.from - yrange.to); + } + } + } + + // draw the ticks + axes = allAxes(); + bw = options.grid.borderWidth; + + for (var j = 0; j < axes.length; ++j) { + var axis = axes[j], box = axis.box, + t = axis.tickLength, x, y, xoff, yoff; + if (!axis.show || axis.ticks.length == 0) + continue; + + ctx.lineWidth = 1; + + // find the edges + if (axis.direction == "x") { + x = 0; + if (t == "full") + y = (axis.position == "top" ? 0 : plotHeight); + else + y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); + } + else { + y = 0; + if (t == "full") + x = (axis.position == "left" ? 0 : plotWidth); + else + x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); + } + + // draw tick bar + if (!axis.innermost) { + ctx.strokeStyle = axis.options.color; + ctx.beginPath(); + xoff = yoff = 0; + if (axis.direction == "x") + xoff = plotWidth + 1; + else + yoff = plotHeight + 1; + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") { + y = Math.floor(y) + 0.5; + } else { + x = Math.floor(x) + 0.5; + } + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + ctx.stroke(); + } + + // draw ticks + + ctx.strokeStyle = axis.options.tickColor; + + ctx.beginPath(); + for (i = 0; i < axis.ticks.length; ++i) { + var v = axis.ticks[i].v; + + xoff = yoff = 0; + + if (isNaN(v) || v < axis.min || v > axis.max + // skip those lying on the axes if we got a border + || (t == "full" + && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) + && (v == axis.min || v == axis.max))) + continue; + + if (axis.direction == "x") { + x = axis.p2c(v); + yoff = t == "full" ? -plotHeight : t; + + if (axis.position == "top") + yoff = -yoff; + } + else { + y = axis.p2c(v); + xoff = t == "full" ? -plotWidth : t; + + if (axis.position == "left") + xoff = -xoff; + } + + if (ctx.lineWidth == 1) { + if (axis.direction == "x") + x = Math.floor(x) + 0.5; + else + y = Math.floor(y) + 0.5; + } + + ctx.moveTo(x, y); + ctx.lineTo(x + xoff, y + yoff); + } + + ctx.stroke(); + } + + + // draw border + if (bw) { + // If either borderWidth or borderColor is an object, then draw the border + // line by line instead of as one rectangle + bc = options.grid.borderColor; + if(typeof bw == "object" || typeof bc == "object") { + if (typeof bw !== "object") { + bw = {top: bw, right: bw, bottom: bw, left: bw}; + } + if (typeof bc !== "object") { + bc = {top: bc, right: bc, bottom: bc, left: bc}; + } + + if (bw.top > 0) { + ctx.strokeStyle = bc.top; + ctx.lineWidth = bw.top; + ctx.beginPath(); + ctx.moveTo(0 - bw.left, 0 - bw.top/2); + ctx.lineTo(plotWidth, 0 - bw.top/2); + ctx.stroke(); + } + + if (bw.right > 0) { + ctx.strokeStyle = bc.right; + ctx.lineWidth = bw.right; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); + ctx.lineTo(plotWidth + bw.right / 2, plotHeight); + ctx.stroke(); + } + + if (bw.bottom > 0) { + ctx.strokeStyle = bc.bottom; + ctx.lineWidth = bw.bottom; + ctx.beginPath(); + ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); + ctx.lineTo(0, plotHeight + bw.bottom / 2); + ctx.stroke(); + } + + if (bw.left > 0) { + ctx.strokeStyle = bc.left; + ctx.lineWidth = bw.left; + ctx.beginPath(); + ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); + ctx.lineTo(0- bw.left/2, 0); + ctx.stroke(); + } + } + else { + ctx.lineWidth = bw; + ctx.strokeStyle = options.grid.borderColor; + ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); + } + } + + ctx.restore(); + } + + function drawAxisLabels() { + + $.each(allAxes(), function (_, axis) { + var box = axis.box, + legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", + layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, + font = axis.options.font || "flot-tick-label tickLabel", + tick, x, y, halign, valign; + + // Remove text before checking for axis.show and ticks.length; + // otherwise plugins, like flot-tickrotor, that draw their own + // tick labels will end up with both theirs and the defaults. + + surface.removeText(layer); + + if (!axis.show || axis.ticks.length == 0) + return; + + for (var i = 0; i < axis.ticks.length; ++i) { + + tick = axis.ticks[i]; + if (!tick.label || tick.v < axis.min || tick.v > axis.max) + continue; + + if (axis.direction == "x") { + halign = "center"; + x = plotOffset.left + axis.p2c(tick.v); + if (axis.position == "bottom") { + y = box.top + box.padding; + } else { + y = box.top + box.height - box.padding; + valign = "bottom"; + } + } else { + valign = "middle"; + y = plotOffset.top + axis.p2c(tick.v); + if (axis.position == "left") { + x = box.left + box.width - box.padding; + halign = "right"; + } else { + x = box.left + box.padding; + } + } + + surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); + } + }); + } + + function drawSeries(series) { + if (series.lines.show) + drawSeriesLines(series); + if (series.bars.show) + drawSeriesBars(series); + if (series.points.show) + drawSeriesPoints(series); + } + + function drawSeriesLines(series) { + function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + prevx = null, prevy = null; + + ctx.beginPath(); + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1], + x2 = points[i], y2 = points[i + 1]; + + if (x1 == null || x2 == null) + continue; + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min) { + if (y2 < axisy.min) + continue; // line segment is outside + // compute new intersection point + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min) { + if (y1 < axisy.min) + continue; + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max) { + if (y2 > axisy.max) + continue; + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max) { + if (y1 > axisy.max) + continue; + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (x1 != prevx || y1 != prevy) + ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); + } + ctx.stroke(); + } + + function plotLineArea(datapoints, axisx, axisy) { + var points = datapoints.points, + ps = datapoints.pointsize, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + i = 0, top, areaOpen = false, + ypos = 1, segmentStart = 0, segmentEnd = 0; + + // we process each segment in two turns, first forward + // direction to sketch out top, then once we hit the + // end we go backwards to sketch the bottom + while (true) { + if (ps > 0 && i > points.length + ps) + break; + + i += ps; // ps is negative if going backwards + + var x1 = points[i - ps], + y1 = points[i - ps + ypos], + x2 = points[i], y2 = points[i + ypos]; + + if (areaOpen) { + if (ps > 0 && x1 != null && x2 == null) { + // at turning point + segmentEnd = i; + ps = -ps; + ypos = 2; + continue; + } + + if (ps < 0 && i == segmentStart + ps) { + // done with the reverse sweep + ctx.fill(); + areaOpen = false; + ps = -ps; + ypos = 1; + i = segmentStart = segmentEnd + ps; + continue; + } + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + continue; + } + + // else it's a bit more complicated, there might + // be a flat maxed out rectangle first, then a + // triangular cutout or reverse; to find these + // keep track of the current x values + var x1old = x1, x2old = x2; + + // clip the y values, without shortcutting, we + // go through all cases in turn + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); + // it goes to (x1, y1), but we fill that below + } + + // fill triangular section, this sometimes result + // in redundant points if (x1, y1) hasn't changed + // from previous line to, but we just ignore that + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); + } + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + ctx.lineJoin = "round"; + + var lw = series.lines.lineWidth, + sw = series.shadowSize; + // FIXME: consider another form of shadow when filling is turned on + if (lw > 0 && sw > 0) { + // draw shadow as a thick and thin line with transparency + ctx.lineWidth = sw; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + // position shadow at angle from the mid of line + var angle = Math.PI/18; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); + ctx.lineWidth = sw/2; + plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); + if (fillStyle) { + ctx.fillStyle = fillStyle; + plotLineArea(series.datapoints, series.xaxis, series.yaxis); + } + + if (lw > 0) + plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); + ctx.restore(); + } + + function drawSeriesPoints(series) { + function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + var x = points[i], y = points[i + 1]; + if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + continue; + + ctx.beginPath(); + x = axisx.p2c(x); + y = axisy.p2c(y) + offset; + if (symbol == "circle") + ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); + else + symbol(ctx, x, y, radius, shadow); + ctx.closePath(); + + if (fillStyle) { + ctx.fillStyle = fillStyle; + ctx.fill(); + } + ctx.stroke(); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var lw = series.points.lineWidth, + sw = series.shadowSize, + radius = series.points.radius, + symbol = series.points.symbol; + + // If the user sets the line width to 0, we change it to a very + // small value. A line width of 0 seems to force the default of 1. + // Doing the conditional here allows the shadow setting to still be + // optional even with a lineWidth of 0. + + if( lw == 0 ) + lw = 0.0001; + + if (lw > 0 && sw > 0) { + // draw shadow in two steps + var w = sw / 2; + ctx.lineWidth = w; + ctx.strokeStyle = "rgba(0,0,0,0.1)"; + plotPoints(series.datapoints, radius, null, w + w/2, true, + series.xaxis, series.yaxis, symbol); + + ctx.strokeStyle = "rgba(0,0,0,0.2)"; + plotPoints(series.datapoints, radius, null, w/2, true, + series.xaxis, series.yaxis, symbol); + } + + ctx.lineWidth = lw; + ctx.strokeStyle = series.color; + plotPoints(series.datapoints, radius, + getFillStyle(series.points, series.color), 0, false, + series.xaxis, series.yaxis, symbol); + ctx.restore(); + } + + function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { + var left, right, bottom, top, + drawLeft, drawRight, drawTop, drawBottom, + tmp; + + // in horizontal mode, we start the bar from the left + // instead of from the bottom so it appears to be + // horizontal rather than vertical + if (horizontal) { + drawBottom = drawRight = drawTop = true; + drawLeft = false; + left = b; + right = x; + top = y + barLeft; + bottom = y + barRight; + + // account for negative bars + if (right < left) { + tmp = right; + right = left; + left = tmp; + drawLeft = true; + drawRight = false; + } + } + else { + drawLeft = drawRight = drawTop = true; + drawBottom = false; + left = x + barLeft; + right = x + barRight; + bottom = b; + top = y; + + // account for negative bars + if (top < bottom) { + tmp = top; + top = bottom; + bottom = tmp; + drawBottom = true; + drawTop = false; + } + } + + // clip + if (right < axisx.min || left > axisx.max || + top < axisy.min || bottom > axisy.max) + return; + + if (left < axisx.min) { + left = axisx.min; + drawLeft = false; + } + + if (right > axisx.max) { + right = axisx.max; + drawRight = false; + } + + if (bottom < axisy.min) { + bottom = axisy.min; + drawBottom = false; + } + + if (top > axisy.max) { + top = axisy.max; + drawTop = false; + } + + left = axisx.p2c(left); + bottom = axisy.p2c(bottom); + right = axisx.p2c(right); + top = axisy.p2c(top); + + // fill the bar + if (fillStyleCallback) { + c.fillStyle = fillStyleCallback(bottom, top); + c.fillRect(left, top, right - left, bottom - top) + } + + // draw outline + if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { + c.beginPath(); + + // FIXME: inline moveTo is buggy with excanvas + c.moveTo(left, bottom); + if (drawLeft) + c.lineTo(left, top); + else + c.moveTo(left, top); + if (drawTop) + c.lineTo(right, top); + else + c.moveTo(right, top); + if (drawRight) + c.lineTo(right, bottom); + else + c.moveTo(right, bottom); + if (drawBottom) + c.lineTo(left, bottom); + else + c.moveTo(left, bottom); + c.stroke(); + } + } + + function drawSeriesBars(series) { + function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { + var points = datapoints.points, ps = datapoints.pointsize; + + for (var i = 0; i < points.length; i += ps) { + if (points[i] == null) + continue; + drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); + } + } + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + // FIXME: figure out a way to add shadows (for instance along the right edge) + ctx.lineWidth = series.bars.lineWidth; + ctx.strokeStyle = series.color; + + var barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + default: + barLeft = -series.bars.barWidth / 2; + } + + var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; + plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); + ctx.restore(); + } + + function getFillStyle(filloptions, seriesColor, bottom, top) { + var fill = filloptions.fill; + if (!fill) + return null; + + if (filloptions.fillColor) + return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); + + var c = $.color.parse(seriesColor); + c.a = typeof fill == "number" ? fill : 0.4; + c.normalize(); + return c.toString(); + } + + function insertLegend() { + + if (options.legend.container != null) { + $(options.legend.container).html(""); + } else { + placeholder.find(".legend").remove(); + } + + if (!options.legend.show) { + return; + } + + var fragments = [], entries = [], rowStarted = false, + lf = options.legend.labelFormatter, s, label; + + // Build a list of legend entries, with each having a label and a color + + for (var i = 0; i < series.length; ++i) { + s = series[i]; + if (s.label) { + label = lf ? lf(s.label, s) : s.label; + if (label) { + entries.push({ + label: label, + color: s.color + }); + } + } + } + + // Sort the legend using either the default or a custom comparator + + if (options.legend.sorted) { + if ($.isFunction(options.legend.sorted)) { + entries.sort(options.legend.sorted); + } else if (options.legend.sorted == "reverse") { + entries.reverse(); + } else { + var ascending = options.legend.sorted != "descending"; + entries.sort(function(a, b) { + return a.label == b.label ? 0 : ( + (a.label < b.label) != ascending ? 1 : -1 // Logical XOR + ); + }); + } + } + + // Generate markup for the list of entries, in their final order + + for (var i = 0; i < entries.length; ++i) { + + var entry = entries[i]; + + if (i % options.legend.noColumns == 0) { + if (rowStarted) + fragments.push(''); + fragments.push(''); + rowStarted = true; + } + + fragments.push( + '
' + + '' + entry.label + '' + ); + } + + if (rowStarted) + fragments.push(''); + + if (fragments.length == 0) + return; + + var table = '' + fragments.join("") + '
'; + if (options.legend.container != null) + $(options.legend.container).html(table); + else { + var pos = "", + p = options.legend.position, + m = options.legend.margin; + if (m[0] == null) + m = [m, m]; + if (p.charAt(0) == "n") + pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; + else if (p.charAt(0) == "s") + pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; + if (p.charAt(1) == "e") + pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; + else if (p.charAt(1) == "w") + pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; + var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); + if (options.legend.backgroundOpacity != 0.0) { + // put in the transparent background + // separately to avoid blended labels and + // label boxes + var c = options.legend.backgroundColor; + if (c == null) { + c = options.grid.backgroundColor; + if (c && typeof c == "string") + c = $.color.parse(c); + else + c = $.color.extract(legend, 'background-color'); + c.a = 1; + c = c.toString(); + } + var div = legend.children(); + $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); + } + } + } + + + // interactive features + + var highlights = [], + redrawTimeout = null; + + // returns the data item the mouse is over, or null if none is found + function findNearbyItem(mouseX, mouseY, seriesFilter) { + var maxDistance = options.grid.mouseActiveRadius, + smallestDistance = maxDistance * maxDistance + 1, + item = null, foundPoint = false, i, j, ps; + + for (i = series.length - 1; i >= 0; --i) { + if (!seriesFilter(series[i])) + continue; + + var s = series[i], + axisx = s.xaxis, + axisy = s.yaxis, + points = s.datapoints.points, + mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster + my = axisy.c2p(mouseY), + maxx = maxDistance / axisx.scale, + maxy = maxDistance / axisy.scale; + + ps = s.datapoints.pointsize; + // with inverse transforms, we can't use the maxx/maxy + // optimization, sadly + if (axisx.options.inverseTransform) + maxx = Number.MAX_VALUE; + if (axisy.options.inverseTransform) + maxy = Number.MAX_VALUE; + + if (s.lines.show || s.points.show) { + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1]; + if (x == null) + continue; + + // For points and lines, the cursor must be within a + // certain distance to the data point + if (x - mx > maxx || x - mx < -maxx || + y - my > maxy || y - my < -maxy) + continue; + + // We have to calculate distances in pixels, not in + // data units, because the scales of the axes may be different + var dx = Math.abs(axisx.p2c(x) - mouseX), + dy = Math.abs(axisy.p2c(y) - mouseY), + dist = dx * dx + dy * dy; // we save the sqrt + + // use <= to ensure last point takes precedence + // (last generally means on top of) + if (dist < smallestDistance) { + smallestDistance = dist; + item = [i, j / ps]; + } + } + } + + if (s.bars.show && !item) { // no other point can be nearby + + var barLeft, barRight; + + switch (s.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -s.bars.barWidth; + break; + default: + barLeft = -s.bars.barWidth / 2; + } + + barRight = barLeft + s.bars.barWidth; + + for (j = 0; j < points.length; j += ps) { + var x = points[j], y = points[j + 1], b = points[j + 2]; + if (x == null) + continue; + + // for a bar graph, the cursor must be inside the bar + if (series[i].bars.horizontal ? + (mx <= Math.max(b, x) && mx >= Math.min(b, x) && + my >= y + barLeft && my <= y + barRight) : + (mx >= x + barLeft && mx <= x + barRight && + my >= Math.min(b, y) && my <= Math.max(b, y))) + item = [i, j / ps]; + } + } + } + + if (item) { + i = item[0]; + j = item[1]; + ps = series[i].datapoints.pointsize; + + return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), + dataIndex: j, + series: series[i], + seriesIndex: i }; + } + + return null; + } + + function onMouseMove(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return s["hoverable"] != false; }); + } + + function onMouseLeave(e) { + if (options.grid.hoverable) + triggerClickHoverEvent("plothover", e, + function (s) { return false; }); + } + + function onClick(e) { + triggerClickHoverEvent("plotclick", e, + function (s) { return s["clickable"] != false; }); + } + + // trigger click or hover event (they send the same parameters + // so we share their code) + function triggerClickHoverEvent(eventname, event, seriesFilter) { + var offset = eventHolder.offset(), + canvasX = event.pageX - offset.left - plotOffset.left, + canvasY = event.pageY - offset.top - plotOffset.top, + pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); + + pos.pageX = event.pageX; + pos.pageY = event.pageY; + + var item = findNearbyItem(canvasX, canvasY, seriesFilter); + + if (item) { + // fill in mouse pos for any listeners out there + item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); + item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); + } + + if (options.grid.autoHighlight) { + // clear auto-highlights + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.auto == eventname && + !(item && h.series == item.series && + h.point[0] == item.datapoint[0] && + h.point[1] == item.datapoint[1])) + unhighlight(h.series, h.point); + } + + if (item) + highlight(item.series, item.datapoint, eventname); + } + + placeholder.trigger(eventname, [ pos, item ]); + } + + function triggerRedrawOverlay() { + var t = options.interaction.redrawOverlayInterval; + if (t == -1) { // skip event queue + drawOverlay(); + return; + } + + if (!redrawTimeout) + redrawTimeout = setTimeout(drawOverlay, t); + } + + function drawOverlay() { + redrawTimeout = null; + + // draw highlights + octx.save(); + overlay.clear(); + octx.translate(plotOffset.left, plotOffset.top); + + var i, hi; + for (i = 0; i < highlights.length; ++i) { + hi = highlights[i]; + + if (hi.series.bars.show) + drawBarHighlight(hi.series, hi.point); + else + drawPointHighlight(hi.series, hi.point); + } + octx.restore(); + + executeHooks(hooks.drawOverlay, [octx]); + } + + function highlight(s, point, auto) { + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i == -1) { + highlights.push({ series: s, point: point, auto: auto }); + + triggerRedrawOverlay(); + } + else if (!auto) + highlights[i].auto = false; + } + + function unhighlight(s, point) { + if (s == null && point == null) { + highlights = []; + triggerRedrawOverlay(); + return; + } + + if (typeof s == "number") + s = series[s]; + + if (typeof point == "number") { + var ps = s.datapoints.pointsize; + point = s.datapoints.points.slice(ps * point, ps * (point + 1)); + } + + var i = indexOfHighlight(s, point); + if (i != -1) { + highlights.splice(i, 1); + + triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s, p) { + for (var i = 0; i < highlights.length; ++i) { + var h = highlights[i]; + if (h.series == s && h.point[0] == p[0] + && h.point[1] == p[1]) + return i; + } + return -1; + } + + function drawPointHighlight(series, point) { + var x = point[0], y = point[1], + axisx = series.xaxis, axisy = series.yaxis, + highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); + + if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) + return; + + var pointRadius = series.points.radius + series.points.lineWidth / 2; + octx.lineWidth = pointRadius; + octx.strokeStyle = highlightColor; + var radius = 1.5 * pointRadius; + x = axisx.p2c(x); + y = axisy.p2c(y); + + octx.beginPath(); + if (series.points.symbol == "circle") + octx.arc(x, y, radius, 0, 2 * Math.PI, false); + else + series.points.symbol(octx, x, y, radius, false); + octx.closePath(); + octx.stroke(); + } + + function drawBarHighlight(series, point) { + var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), + fillStyle = highlightColor, + barLeft; + + switch (series.bars.align) { + case "left": + barLeft = 0; + break; + case "right": + barLeft = -series.bars.barWidth; + break; + default: + barLeft = -series.bars.barWidth / 2; + } + + octx.lineWidth = series.bars.lineWidth; + octx.strokeStyle = highlightColor; + + drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, + function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); + } + + function getColorOrGradient(spec, bottom, top, defaultColor) { + if (typeof spec == "string") + return spec; + else { + // assume this is a gradient spec; IE currently only + // supports a simple vertical gradient properly, so that's + // what we support too + var gradient = ctx.createLinearGradient(0, top, 0, bottom); + + for (var i = 0, l = spec.colors.length; i < l; ++i) { + var c = spec.colors[i]; + if (typeof c != "string") { + var co = $.color.parse(defaultColor); + if (c.brightness != null) + co = co.scale('rgb', c.brightness); + if (c.opacity != null) + co.a *= c.opacity; + c = co.toString(); + } + gradient.addColorStop(i / (l - 1), c); + } + + return gradient; + } + } + } + + // Add the plot function to the top level of the jQuery object + + $.plot = function(placeholder, data, options) { + //var t0 = new Date(); + var plot = new Plot($(placeholder), data, options, $.plot.plugins); + //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); + return plot; + }; + + $.plot.version = "0.8.3"; + + $.plot.plugins = []; + + // Also add the plot function as a chainable property + + $.fn.plot = function(data, options) { + return this.each(function() { + $.plot(this, data, options); + }); + }; + + // round to nearby lower multiple of base + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js new file mode 100644 index 0000000000000..c8707b30f4e6f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js @@ -0,0 +1,360 @@ +/* Flot plugin for selecting regions of a plot. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +The plugin supports these options: + +selection: { + mode: null or "x" or "y" or "xy", + color: color, + shape: "round" or "miter" or "bevel", + minSize: number of pixels +} + +Selection support is enabled by setting the mode to one of "x", "y" or "xy". +In "x" mode, the user will only be able to specify the x range, similarly for +"y" mode. For "xy", the selection becomes a rectangle where both ranges can be +specified. "color" is color of the selection (if you need to change the color +later on, you can get to it with plot.getOptions().selection.color). "shape" +is the shape of the corners of the selection. + +"minSize" is the minimum size a selection can be in pixels. This value can +be customized to determine the smallest size a selection can be and still +have the selection rectangle be displayed. When customizing this value, the +fact that it refers to pixels, not axis units must be taken into account. +Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 +minute, setting "minSize" to 1 will not make the minimum selection size 1 +minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent +"plotunselected" events from being fired when the user clicks the mouse without +dragging. + +When selection support is enabled, a "plotselected" event will be emitted on +the DOM element you passed into the plot function. The event handler gets a +parameter with the ranges selected on the axes, like this: + + placeholder.bind( "plotselected", function( event, ranges ) { + alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) + // similar for yaxis - with multiple axes, the extra ones are in + // x2axis, x3axis, ... + }); + +The "plotselected" event is only fired when the user has finished making the +selection. A "plotselecting" event is fired during the process with the same +parameters as the "plotselected" event, in case you want to know what's +happening while it's happening, + +A "plotunselected" event with no arguments is emitted when the user clicks the +mouse to remove the selection. As stated above, setting "minSize" to 0 will +destroy this behavior. + +The plugin also adds the following methods to the plot object: + +- setSelection( ranges, preventEvent ) + + Set the selection rectangle. The passed in ranges is on the same form as + returned in the "plotselected" event. If the selection mode is "x", you + should put in either an xaxis range, if the mode is "y" you need to put in + an yaxis range and both xaxis and yaxis if the selection mode is "xy", like + this: + + setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); + + setSelection will trigger the "plotselected" event when called. If you don't + want that to happen, e.g. if you're inside a "plotselected" handler, pass + true as the second parameter. If you are using multiple axes, you can + specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of + xaxis, the plugin picks the first one it sees. + +- clearSelection( preventEvent ) + + Clear the selection rectangle. Pass in true to avoid getting a + "plotunselected" event. + +- getSelection() + + Returns the current selection in the same format as the "plotselected" + event. If there's currently no selection, the function returns null. + +*/ + +(function ($) { + function init(plot) { + var selection = { + first: { x: -1, y: -1}, second: { x: -1, y: -1}, + show: false, + active: false + }; + + // FIXME: The drag handling implemented here should be + // abstracted out, there's some similar code from a library in + // the navigation plugin, this should be massaged a bit to fit + // the Flot cases here better and reused. Doing this would + // make this plugin much slimmer. + var savedhandlers = {}; + + var mouseUpHandler = null; + + function onMouseMove(e) { + if (selection.active) { + updateSelection(e); + + plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); + } + } + + function onMouseDown(e) { + if (e.which != 1) // only accept left-click + return; + + // cancel out any text selections + document.body.focus(); + + // prevent text selection and drag in old-school browsers + if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { + savedhandlers.onselectstart = document.onselectstart; + document.onselectstart = function () { return false; }; + } + if (document.ondrag !== undefined && savedhandlers.ondrag == null) { + savedhandlers.ondrag = document.ondrag; + document.ondrag = function () { return false; }; + } + + setSelectionPos(selection.first, e); + + selection.active = true; + + // this is a bit silly, but we have to use a closure to be + // able to whack the same handler again + mouseUpHandler = function (e) { onMouseUp(e); }; + + $(document).one("mouseup", mouseUpHandler); + } + + function onMouseUp(e) { + mouseUpHandler = null; + + // revert drag stuff for old-school browsers + if (document.onselectstart !== undefined) + document.onselectstart = savedhandlers.onselectstart; + if (document.ondrag !== undefined) + document.ondrag = savedhandlers.ondrag; + + // no more dragging + selection.active = false; + updateSelection(e); + + if (selectionIsSane()) + triggerSelectedEvent(); + else { + // this counts as a clear + plot.getPlaceholder().trigger("plotunselected", [ ]); + plot.getPlaceholder().trigger("plotselecting", [ null ]); + } + + return false; + } + + function getSelection() { + if (!selectionIsSane()) + return null; + + if (!selection.show) return null; + + var r = {}, c1 = selection.first, c2 = selection.second; + $.each(plot.getAxes(), function (name, axis) { + if (axis.used) { + var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); + r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; + } + }); + return r; + } + + function triggerSelectedEvent() { + var r = getSelection(); + + plot.getPlaceholder().trigger("plotselected", [ r ]); + + // backwards-compat stuff, to be removed in future + if (r.xaxis && r.yaxis) + plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); + } + + function clamp(min, value, max) { + return value < min ? min: (value > max ? max: value); + } + + function setSelectionPos(pos, e) { + var o = plot.getOptions(); + var offset = plot.getPlaceholder().offset(); + var plotOffset = plot.getPlotOffset(); + pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); + pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); + + if (o.selection.mode == "y") + pos.x = pos == selection.first ? 0 : plot.width(); + + if (o.selection.mode == "x") + pos.y = pos == selection.first ? 0 : plot.height(); + } + + function updateSelection(pos) { + if (pos.pageX == null) + return; + + setSelectionPos(selection.second, pos); + if (selectionIsSane()) { + selection.show = true; + plot.triggerRedrawOverlay(); + } + else + clearSelection(true); + } + + function clearSelection(preventEvent) { + if (selection.show) { + selection.show = false; + plot.triggerRedrawOverlay(); + if (!preventEvent) + plot.getPlaceholder().trigger("plotunselected", [ ]); + } + } + + // function taken from markings support in Flot + function extractRange(ranges, coord) { + var axis, from, to, key, axes = plot.getAxes(); + + for (var k in axes) { + axis = axes[k]; + if (axis.direction == coord) { + key = coord + axis.n + "axis"; + if (!ranges[key] && axis.n == 1) + key = coord + "axis"; // support x1axis as xaxis + if (ranges[key]) { + from = ranges[key].from; + to = ranges[key].to; + break; + } + } + } + + // backwards-compat stuff - to be removed in future + if (!ranges[key]) { + axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; + from = ranges[coord + "1"]; + to = ranges[coord + "2"]; + } + + // auto-reverse as an added bonus + if (from != null && to != null && from > to) { + var tmp = from; + from = to; + to = tmp; + } + + return { from: from, to: to, axis: axis }; + } + + function setSelection(ranges, preventEvent) { + var axis, range, o = plot.getOptions(); + + if (o.selection.mode == "y") { + selection.first.x = 0; + selection.second.x = plot.width(); + } + else { + range = extractRange(ranges, "x"); + + selection.first.x = range.axis.p2c(range.from); + selection.second.x = range.axis.p2c(range.to); + } + + if (o.selection.mode == "x") { + selection.first.y = 0; + selection.second.y = plot.height(); + } + else { + range = extractRange(ranges, "y"); + + selection.first.y = range.axis.p2c(range.from); + selection.second.y = range.axis.p2c(range.to); + } + + selection.show = true; + plot.triggerRedrawOverlay(); + if (!preventEvent && selectionIsSane()) + triggerSelectedEvent(); + } + + function selectionIsSane() { + var minSize = plot.getOptions().selection.minSize; + return Math.abs(selection.second.x - selection.first.x) >= minSize && + Math.abs(selection.second.y - selection.first.y) >= minSize; + } + + plot.clearSelection = clearSelection; + plot.setSelection = setSelection; + plot.getSelection = getSelection; + + plot.hooks.bindEvents.push(function(plot, eventHolder) { + var o = plot.getOptions(); + if (o.selection.mode != null) { + eventHolder.mousemove(onMouseMove); + eventHolder.mousedown(onMouseDown); + } + }); + + + plot.hooks.drawOverlay.push(function (plot, ctx) { + // draw selection + if (selection.show && selectionIsSane()) { + var plotOffset = plot.getPlotOffset(); + var o = plot.getOptions(); + + ctx.save(); + ctx.translate(plotOffset.left, plotOffset.top); + + var c = $.color.parse(o.selection.color); + + ctx.strokeStyle = c.scale('a', 0.8).toString(); + ctx.lineWidth = 1; + ctx.lineJoin = o.selection.shape; + ctx.fillStyle = c.scale('a', 0.4).toString(); + + var x = Math.min(selection.first.x, selection.second.x) + 0.5, + y = Math.min(selection.first.y, selection.second.y) + 0.5, + w = Math.abs(selection.second.x - selection.first.x) - 1, + h = Math.abs(selection.second.y - selection.first.y) - 1; + + ctx.fillRect(x, y, w, h); + ctx.strokeRect(x, y, w, h); + + ctx.restore(); + } + }); + + plot.hooks.shutdown.push(function (plot, eventHolder) { + eventHolder.unbind("mousemove", onMouseMove); + eventHolder.unbind("mousedown", onMouseDown); + + if (mouseUpHandler) + $(document).unbind("mouseup", mouseUpHandler); + }); + + } + + $.plot.plugins.push({ + init: init, + options: { + selection: { + mode: null, // one of null, "x", "y" or "xy" + color: "#e8cfac", + shape: "round", // one of "round", "miter", or "bevel" + minSize: 5 // minimum number of pixels + } + }, + name: 'selection', + version: '1.1' + }); +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js new file mode 100644 index 0000000000000..0d91c0f3c0160 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js @@ -0,0 +1,188 @@ +/* Flot plugin for stacking data sets rather than overlaying them. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +The plugin assumes the data is sorted on x (or y if stacking horizontally). +For line charts, it is assumed that if a line has an undefined gap (from a +null point), then the line above it should have the same gap - insert zeros +instead of "null" if you want another behaviour. This also holds for the start +and end of the chart. Note that stacking a mix of positive and negative values +in most instances doesn't make sense (so it looks weird). + +Two or more series are stacked when their "stack" attribute is set to the same +key (which can be any number or string or just "true"). To specify the default +stack, you can set the stack option like this: + + series: { + stack: null/false, true, or a key (number/string) + } + +You can also specify it for a single series, like this: + + $.plot( $("#placeholder"), [{ + data: [ ... ], + stack: true + }]) + +The stacking order is determined by the order of the data series in the array +(later series end up on top of the previous). + +Internally, the plugin modifies the datapoints in each series, adding an +offset to the y value. For line series, extra data points are inserted through +interpolation. If there's a second y value, it's also adjusted (e.g for bar +charts or filled areas). + +*/ + +(function ($) { + var options = { + series: { stack: null } // or number/string + }; + + function init(plot) { + function findMatchingSeries(s, allseries) { + var res = null; + for (var i = 0; i < allseries.length; ++i) { + if (s == allseries[i]) + break; + + if (allseries[i].stack == s.stack) + res = allseries[i]; + } + + return res; + } + + function stackData(plot, s, datapoints) { + if (s.stack == null || s.stack === false) + return; + + var other = findMatchingSeries(s, plot.getData()); + if (!other) + return; + + var ps = datapoints.pointsize, + points = datapoints.points, + otherps = other.datapoints.pointsize, + otherpoints = other.datapoints.points, + newpoints = [], + px, py, intery, qx, qy, bottom, + withlines = s.lines.show, + horizontal = s.bars.horizontal, + withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), + withsteps = withlines && s.lines.steps, + fromgap = true, + keyOffset = horizontal ? 1 : 0, + accumulateOffset = horizontal ? 0 : 1, + i = 0, j = 0, l, m; + + while (true) { + if (i >= points.length) + break; + + l = newpoints.length; + + if (points[i] == null) { + // copy gaps + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + i += ps; + } + else if (j >= otherpoints.length) { + // for lines, we can't use the rest of the points + if (!withlines) { + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + } + i += ps; + } + else if (otherpoints[j] == null) { + // oops, got a gap + for (m = 0; m < ps; ++m) + newpoints.push(null); + fromgap = true; + j += otherps; + } + else { + // cases where we actually got two points + px = points[i + keyOffset]; + py = points[i + accumulateOffset]; + qx = otherpoints[j + keyOffset]; + qy = otherpoints[j + accumulateOffset]; + bottom = 0; + + if (px == qx) { + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + + newpoints[l + accumulateOffset] += qy; + bottom = qy; + + i += ps; + j += otherps; + } + else if (px > qx) { + // we got past point below, might need to + // insert interpolated extra point + if (withlines && i > 0 && points[i - ps] != null) { + intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); + newpoints.push(qx); + newpoints.push(intery + qy); + for (m = 2; m < ps; ++m) + newpoints.push(points[i + m]); + bottom = qy; + } + + j += otherps; + } + else { // px < qx + if (fromgap && withlines) { + // if we come from a gap, we just skip this point + i += ps; + continue; + } + + for (m = 0; m < ps; ++m) + newpoints.push(points[i + m]); + + // we might be able to interpolate a point below, + // this can give us a better y + if (withlines && j > 0 && otherpoints[j - otherps] != null) + bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); + + newpoints[l + accumulateOffset] += bottom; + + i += ps; + } + + fromgap = false; + + if (l != newpoints.length && withbottom) + newpoints[l + 2] += bottom; + } + + // maintain the line steps invariant + if (withsteps && l != newpoints.length && l > 0 + && newpoints[l] != null + && newpoints[l] != newpoints[l - ps] + && newpoints[l + 1] != newpoints[l - ps + 1]) { + for (m = 0; m < ps; ++m) + newpoints[l + ps + m] = newpoints[l + m]; + newpoints[l + 1] = newpoints[l - ps + 1]; + } + } + + datapoints.points = newpoints; + } + + plot.hooks.processDatapoints.push(stackData); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'stack', + version: '1.2' + }); +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js new file mode 100644 index 0000000000000..79f634971b6fa --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js @@ -0,0 +1,71 @@ +/* Flot plugin that adds some extra symbols for plotting points. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +The symbols are accessed as strings through the standard symbol options: + + series: { + points: { + symbol: "square" // or "diamond", "triangle", "cross" + } + } + +*/ + +(function ($) { + function processRawData(plot, series, datapoints) { + // we normalize the area of each symbol so it is approximately the + // same as a circle of the given radius + + var handlers = { + square: function (ctx, x, y, radius, shadow) { + // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 + var size = radius * Math.sqrt(Math.PI) / 2; + ctx.rect(x - size, y - size, size + size, size + size); + }, + diamond: function (ctx, x, y, radius, shadow) { + // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) + var size = radius * Math.sqrt(Math.PI / 2); + ctx.moveTo(x - size, y); + ctx.lineTo(x, y - size); + ctx.lineTo(x + size, y); + ctx.lineTo(x, y + size); + ctx.lineTo(x - size, y); + }, + triangle: function (ctx, x, y, radius, shadow) { + // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) + var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); + var height = size * Math.sin(Math.PI / 3); + ctx.moveTo(x - size/2, y + height/2); + ctx.lineTo(x + size/2, y + height/2); + if (!shadow) { + ctx.lineTo(x, y - height/2); + ctx.lineTo(x - size/2, y + height/2); + } + }, + cross: function (ctx, x, y, radius, shadow) { + // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 + var size = radius * Math.sqrt(Math.PI) / 2; + ctx.moveTo(x - size, y - size); + ctx.lineTo(x + size, y + size); + ctx.moveTo(x - size, y + size); + ctx.lineTo(x + size, y - size); + } + }; + + var s = series.points.symbol; + if (handlers[s]) + series.points.symbol = handlers[s]; + } + + function init(plot) { + plot.hooks.processDatapoints.push(processRawData); + } + + $.plot.plugins.push({ + init: init, + name: 'symbols', + version: '1.0' + }); +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js new file mode 100644 index 0000000000000..34c1d121259a2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js @@ -0,0 +1,432 @@ +/* Pretty handling of time axes. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +Set axis.mode to "time" to enable. See the section "Time series data" in +API.txt for details. + +*/ + +(function($) { + + var options = { + xaxis: { + timezone: null, // "browser" for local to the client or timezone for timezone-js + timeformat: null, // format string to use + twelveHourClock: false, // 12 or 24 time in time mode + monthNames: null // list of names of months + } + }; + + // round to nearby lower multiple of base + + function floorInBase(n, base) { + return base * Math.floor(n / base); + } + + // Returns a string with the date d formatted according to fmt. + // A subset of the Open Group's strftime format is supported. + + function formatDate(d, fmt, monthNames, dayNames) { + + if (typeof d.strftime == "function") { + return d.strftime(fmt); + } + + var leftPad = function(n, pad) { + n = "" + n; + pad = "" + (pad == null ? "0" : pad); + return n.length == 1 ? pad + n : n; + }; + + var r = []; + var escape = false; + var hours = d.getHours(); + var isAM = hours < 12; + + if (monthNames == null) { + monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + } + + if (dayNames == null) { + dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + + var hours12; + + if (hours > 12) { + hours12 = hours - 12; + } else if (hours == 0) { + hours12 = 12; + } else { + hours12 = hours; + } + + for (var i = 0; i < fmt.length; ++i) { + + var c = fmt.charAt(i); + + if (escape) { + switch (c) { + case 'a': c = "" + dayNames[d.getDay()]; break; + case 'b': c = "" + monthNames[d.getMonth()]; break; + case 'd': c = leftPad(d.getDate()); break; + case 'e': c = leftPad(d.getDate(), " "); break; + case 'h': // For back-compat with 0.7; remove in 1.0 + case 'H': c = leftPad(hours); break; + case 'I': c = leftPad(hours12); break; + case 'l': c = leftPad(hours12, " "); break; + case 'm': c = leftPad(d.getMonth() + 1); break; + case 'M': c = leftPad(d.getMinutes()); break; + // quarters not in Open Group's strftime specification + case 'q': + c = "" + (Math.floor(d.getMonth() / 3) + 1); break; + case 'S': c = leftPad(d.getSeconds()); break; + case 'y': c = leftPad(d.getFullYear() % 100); break; + case 'Y': c = "" + d.getFullYear(); break; + case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; + case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; + case 'w': c = "" + d.getDay(); break; + } + r.push(c); + escape = false; + } else { + if (c == "%") { + escape = true; + } else { + r.push(c); + } + } + } + + return r.join(""); + } + + // To have a consistent view of time-based data independent of which time + // zone the client happens to be in we need a date-like object independent + // of time zones. This is done through a wrapper that only calls the UTC + // versions of the accessor methods. + + function makeUtcWrapper(d) { + + function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { + sourceObj[sourceMethod] = function() { + return targetObj[targetMethod].apply(targetObj, arguments); + }; + }; + + var utc = { + date: d + }; + + // support strftime, if found + + if (d.strftime != undefined) { + addProxyMethod(utc, "strftime", d, "strftime"); + } + + addProxyMethod(utc, "getTime", d, "getTime"); + addProxyMethod(utc, "setTime", d, "setTime"); + + var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; + + for (var p = 0; p < props.length; p++) { + addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); + addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); + } + + return utc; + }; + + // select time zone strategy. This returns a date-like object tied to the + // desired timezone + + function dateGenerator(ts, opts) { + if (opts.timezone == "browser") { + return new Date(ts); + } else if (!opts.timezone || opts.timezone == "utc") { + return makeUtcWrapper(new Date(ts)); + } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { + var d = new timezoneJS.Date(); + // timezone-js is fickle, so be sure to set the time zone before + // setting the time. + d.setTimezone(opts.timezone); + d.setTime(ts); + return d; + } else { + return makeUtcWrapper(new Date(ts)); + } + } + + // map of app. size of time units in milliseconds + + var timeUnitSize = { + "second": 1000, + "minute": 60 * 1000, + "hour": 60 * 60 * 1000, + "day": 24 * 60 * 60 * 1000, + "month": 30 * 24 * 60 * 60 * 1000, + "quarter": 3 * 30 * 24 * 60 * 60 * 1000, + "year": 365.2425 * 24 * 60 * 60 * 1000 + }; + + // the allowed tick sizes, after 1 year we use + // an integer algorithm + + var baseSpec = [ + [1, "second"], [2, "second"], [5, "second"], [10, "second"], + [30, "second"], + [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], + [30, "minute"], + [1, "hour"], [2, "hour"], [4, "hour"], + [8, "hour"], [12, "hour"], + [1, "day"], [2, "day"], [3, "day"], + [0.25, "month"], [0.5, "month"], [1, "month"], + [2, "month"] + ]; + + // we don't know which variant(s) we'll need yet, but generating both is + // cheap + + var specMonths = baseSpec.concat([[3, "month"], [6, "month"], + [1, "year"]]); + var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], + [1, "year"]]); + + function init(plot) { + plot.hooks.processOptions.push(function (plot, options) { + $.each(plot.getAxes(), function(axisName, axis) { + + var opts = axis.options; + + if (opts.mode == "time") { + axis.tickGenerator = function(axis) { + + var ticks = []; + var d = dateGenerator(axis.min, opts); + var minSize = 0; + + // make quarter use a possibility if quarters are + // mentioned in either of these options + + var spec = (opts.tickSize && opts.tickSize[1] === + "quarter") || + (opts.minTickSize && opts.minTickSize[1] === + "quarter") ? specQuarters : specMonths; + + if (opts.minTickSize != null) { + if (typeof opts.tickSize == "number") { + minSize = opts.tickSize; + } else { + minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; + } + } + + for (var i = 0; i < spec.length - 1; ++i) { + if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] + + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 + && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { + break; + } + } + + var size = spec[i][0]; + var unit = spec[i][1]; + + // special-case the possibility of several years + + if (unit == "year") { + + // if given a minTickSize in years, just use it, + // ensuring that it's an integer + + if (opts.minTickSize != null && opts.minTickSize[1] == "year") { + size = Math.floor(opts.minTickSize[0]); + } else { + + var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); + var norm = (axis.delta / timeUnitSize.year) / magn; + + if (norm < 1.5) { + size = 1; + } else if (norm < 3) { + size = 2; + } else if (norm < 7.5) { + size = 5; + } else { + size = 10; + } + + size *= magn; + } + + // minimum size for years is 1 + + if (size < 1) { + size = 1; + } + } + + axis.tickSize = opts.tickSize || [size, unit]; + var tickSize = axis.tickSize[0]; + unit = axis.tickSize[1]; + + var step = tickSize * timeUnitSize[unit]; + + if (unit == "second") { + d.setSeconds(floorInBase(d.getSeconds(), tickSize)); + } else if (unit == "minute") { + d.setMinutes(floorInBase(d.getMinutes(), tickSize)); + } else if (unit == "hour") { + d.setHours(floorInBase(d.getHours(), tickSize)); + } else if (unit == "month") { + d.setMonth(floorInBase(d.getMonth(), tickSize)); + } else if (unit == "quarter") { + d.setMonth(3 * floorInBase(d.getMonth() / 3, + tickSize)); + } else if (unit == "year") { + d.setFullYear(floorInBase(d.getFullYear(), tickSize)); + } + + // reset smaller components + + d.setMilliseconds(0); + + if (step >= timeUnitSize.minute) { + d.setSeconds(0); + } + if (step >= timeUnitSize.hour) { + d.setMinutes(0); + } + if (step >= timeUnitSize.day) { + d.setHours(0); + } + if (step >= timeUnitSize.day * 4) { + d.setDate(1); + } + if (step >= timeUnitSize.month * 2) { + d.setMonth(floorInBase(d.getMonth(), 3)); + } + if (step >= timeUnitSize.quarter * 2) { + d.setMonth(floorInBase(d.getMonth(), 6)); + } + if (step >= timeUnitSize.year) { + d.setMonth(0); + } + + var carry = 0; + var v = Number.NaN; + var prev; + + do { + + prev = v; + v = d.getTime(); + ticks.push(v); + + if (unit == "month" || unit == "quarter") { + if (tickSize < 1) { + + // a bit complicated - we'll divide the + // month/quarter up but we need to take + // care of fractions so we don't end up in + // the middle of a day + + d.setDate(1); + var start = d.getTime(); + d.setMonth(d.getMonth() + + (unit == "quarter" ? 3 : 1)); + var end = d.getTime(); + d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); + carry = d.getHours(); + d.setHours(0); + } else { + d.setMonth(d.getMonth() + + tickSize * (unit == "quarter" ? 3 : 1)); + } + } else if (unit == "year") { + d.setFullYear(d.getFullYear() + tickSize); + } else { + d.setTime(v + step); + } + } while (v < axis.max && v != prev); + + return ticks; + }; + + axis.tickFormatter = function (v, axis) { + + var d = dateGenerator(v, axis.options); + + // first check global format + + if (opts.timeformat != null) { + return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); + } + + // possibly use quarters if quarters are mentioned in + // any of these places + + var useQuarters = (axis.options.tickSize && + axis.options.tickSize[1] == "quarter") || + (axis.options.minTickSize && + axis.options.minTickSize[1] == "quarter"); + + var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; + var span = axis.max - axis.min; + var suffix = (opts.twelveHourClock) ? " %p" : ""; + var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; + var fmt; + + if (t < timeUnitSize.minute) { + fmt = hourCode + ":%M:%S" + suffix; + } else if (t < timeUnitSize.day) { + if (span < 2 * timeUnitSize.day) { + fmt = hourCode + ":%M" + suffix; + } else { + fmt = "%b %d " + hourCode + ":%M" + suffix; + } + } else if (t < timeUnitSize.month) { + fmt = "%b %d"; + } else if ((useQuarters && t < timeUnitSize.quarter) || + (!useQuarters && t < timeUnitSize.year)) { + if (span < timeUnitSize.year) { + fmt = "%b"; + } else { + fmt = "%b %Y"; + } + } else if (useQuarters && t < timeUnitSize.year) { + if (span < timeUnitSize.year) { + fmt = "Q%q"; + } else { + fmt = "Q%q %Y"; + } + } else { + fmt = "%Y"; + } + + var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); + + return rt; + }; + } + }); + }); + } + + $.plot.plugins.push({ + init: init, + options: options, + name: 'time', + version: '1.0' + }); + + // Time-axis support used to be in Flot core, which exposed the + // formatDate function on the plot object. Various plugins depend + // on the function, so we need to re-expose it here. + + $.plot.formatDate = formatDate; + $.plot.dateGenerator = dateGenerator; + +})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js new file mode 100644 index 0000000000000..d54574d2cf4ba --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../average`); +import moment from 'moment'; +const expect = require('chai').expect; +import _ from 'lodash'; + +describe('average.js', function () { + + describe('average', function () { + it('fills holes in the data', function () { + const data = [ + [moment.utc('1980', 'YYYY').valueOf(), 10], + [moment.utc('1983', 'YYYY').valueOf(), 40], + [moment.utc('1984', 'YYYY').valueOf(), 50], + ]; + + const target = [ + [moment.utc('1980', 'YYYY').valueOf(), null], + [moment.utc('1981', 'YYYY').valueOf(), null], + [moment.utc('1982', 'YYYY').valueOf(), null], + [moment.utc('1983', 'YYYY').valueOf(), null], + [moment.utc('1984', 'YYYY').valueOf(), null], + ]; + + expect(_.map(fn(data, target), 1)).to.eql([10, 20, 30, 40, 50]); + }); + + describe('sampling', function () { + it('up', function () { + const data = [ + [moment.utc('1981', 'YYYY').valueOf(), 10], + [moment.utc('1983', 'YYYY').valueOf(), 30], + [moment.utc('1985', 'YYYY').valueOf(), 70], + ]; + + const target = [ + [moment.utc('1981', 'YYYY').valueOf(), null], + [moment.utc('1982', 'YYYY').valueOf(), null], + [moment.utc('1983', 'YYYY').valueOf(), null], + [moment.utc('1984', 'YYYY').valueOf(), null], + [moment.utc('1985', 'YYYY').valueOf(), null], + ]; + + expect(_.map(fn(data, target), 1)).to.eql([10, 20, 30, 50, 70]); + }); + + + it('down', function () { + const data = [ + [moment.utc('1980', 'YYYY').valueOf(), 0], + [moment.utc('1981', 'YYYY').valueOf(), 2], + [moment.utc('1982', 'YYYY').valueOf(), 4], + [moment.utc('1983', 'YYYY').valueOf(), 6], + [moment.utc('1984', 'YYYY').valueOf(), 8], + [moment.utc('1985', 'YYYY').valueOf(), 10], + [moment.utc('1986', 'YYYY').valueOf(), 12], + ]; + + const target = [ + [moment.utc('1981', 'YYYY').valueOf(), null], + [moment.utc('1983', 'YYYY').valueOf(), null], + [moment.utc('1985', 'YYYY').valueOf(), null], + ]; + + // This returns 1, 5, 9 instead of the expected 2, 6, 10 because the average function does not consider "future" + // values, rather just the next upcoming value from the end of the previously predicted bucket. E.g., When + // interpolating a weekly series into daily, in which the buckets fall on sundays, this coming Sunday's bucket + // will be distributed Mon-Sun instead of say Thur-Wed. + // Essentially the algorithm is left aligned instead of centered + expect(_.map(fn(data, target), 1)).to.eql([1, 5, 9]); + }); + + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js new file mode 100644 index 0000000000000..972f220111edc --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../carry`); +import moment from 'moment'; +const expect = require('chai').expect; +import _ from 'lodash'; + +describe('carry.js', function () { + it('fills holes in the data', function () { + const data = [ + [moment.utc('1980', 'YYYY').valueOf(), 10], + [moment.utc('1983', 'YYYY').valueOf(), 40], + [moment.utc('1984', 'YYYY').valueOf(), 50], + ]; + + const target = [ + [moment.utc('1980', 'YYYY').valueOf(), null], + [moment.utc('1981', 'YYYY').valueOf(), null], + [moment.utc('1982', 'YYYY').valueOf(), null], + [moment.utc('1983', 'YYYY').valueOf(), null], + [moment.utc('1984', 'YYYY').valueOf(), null], + ]; + + expect(_.map(fn(data, target), 1)).to.eql([10, 10, 10, 40, 50]); + }); + + describe('sampling', function () { + it('up', function () { + const data = [ + [moment.utc('1981', 'YYYY').valueOf(), 10], + [moment.utc('1983', 'YYYY').valueOf(), 30], + [moment.utc('1985', 'YYYY').valueOf(), 70], + ]; + + const target = [ + [moment.utc('1981', 'YYYY').valueOf(), null], + [moment.utc('1982', 'YYYY').valueOf(), null], + [moment.utc('1983', 'YYYY').valueOf(), null], + [moment.utc('1984', 'YYYY').valueOf(), null], + [moment.utc('1985', 'YYYY').valueOf(), null], + ]; + + expect(_.map(fn(data, target), 1)).to.eql([10, 10, 30, 30, 70]); + }); + + + it('down does not make sense', function () { + const data = [ + [moment.utc('1980', 'YYYY').valueOf(), 0], + [moment.utc('1981', 'YYYY').valueOf(), 2], + [moment.utc('1982', 'YYYY').valueOf(), 4], + [moment.utc('1983', 'YYYY').valueOf(), 6], + [moment.utc('1984', 'YYYY').valueOf(), 8], + [moment.utc('1985', 'YYYY').valueOf(), 10], + [moment.utc('1986', 'YYYY').valueOf(), 12], + ]; + + const target = [ + [moment.utc('1981', 'YYYY').valueOf(), null], + [moment.utc('1983', 'YYYY').valueOf(), null], + [moment.utc('1985', 'YYYY').valueOf(), null], + ]; + + // carry doesn't down sample, it simply doesn't make any sense. Use average or scale + try { + fn(data, target); + expect.fail('Success. Doh.'); + } catch (e) { + expect(e).to.be.an('error'); + } + }); + + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js new file mode 100644 index 0000000000000..48dc9120ea015 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +// Upsampling and down sampling of non-cumulative sets +// Good: min, max, average +// Bad: sum, count + +export default function average(dataTuples, targetTuples) { + + // Phase 1: Downsample + // We necessarily won't well match the dataSource here as we don't know how much data + // they had when creating their own average + const resultTimes = _.pluck(targetTuples, 0); + const dataTuplesQueue = _.clone(dataTuples); + const resultValues = _.map(targetTuples, function (bucket) { + const time = bucket[0]; + let i = 0; + const avgSet = []; + + // This is naive, it doesn't consider where the line is going next, + // It simply writes the point and moves on once it hits <= time. + // Thus this algorithm will tend to lag the trend. + // Deal with it, or write something better. + while (i < dataTuplesQueue.length && dataTuplesQueue[i][0] <= time) { + avgSet.push(dataTuplesQueue[i][1]); + i++; + } + + dataTuplesQueue.splice(0, i); + + const sum = avgSet.reduce((sum, num) => sum + num, 0); + + return avgSet.length ? (sum / avgSet.length) : NaN; + }); + + // Phase 2: Upsample if needed + // If we have any NaNs we are probably resampling from a big interval to a small one (eg, 1M as 1d) + // So look for the missing stuff in the array, and smooth it out + const naNIndex = _.findIndex(resultValues, function (val) { + return isNaN(val); + }); + + if (naNIndex > -1) { + let i = 0; + let naNCount = 0; + const filledValues = []; + let previousRealNumber; + let stepSize; + while (i < resultValues.length) { + if (isNaN(resultValues[i])) { + if (i === 0) { + // If our first number is NaN, initialize from dataTuples; + previousRealNumber = dataTuples[0][1]; + } + naNCount++; + } else { + // Otherwise, backfill the NaNs with averaged out data + if (naNCount > 0) { + stepSize = (resultValues[i] - previousRealNumber) / (naNCount + 1); + while (naNCount > 0) { + resultValues[i - naNCount] = previousRealNumber + stepSize; + previousRealNumber = resultValues[i - naNCount]; + naNCount--; + } + } + previousRealNumber = resultValues[i]; + filledValues.push(resultValues[i]); + } + i++; + } + + } + + const resultTuples = _.zip(resultTimes, resultValues); + return resultTuples; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js new file mode 100644 index 0000000000000..5702eb6d25119 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +// Upsampling of non-cumulative sets +// Good: average, min, max +// Bad: sum, count + +// Don't use this to down sample, it simply won't do the right thing. +export default function carry(dataTuples, targetTuples) { + + if (dataTuples.length > targetTuples.length) { + throw new Error ( + i18n.translate('timelion.fitFunctions.carry.downSampleErrorMessage', { + defaultMessage: `Don't use the 'carry' fit method to down sample, use 'scale' or 'average'`, + description: '"carry", "scale" and "average" are parameter values that must not be translated.', + }) + ); + } + + let currentCarry = dataTuples[0][1]; + return _.map(targetTuples, function (bucket) { + const targetTime = bucket[0]; + const dataTime = dataTuples[0][0]; + + if (dataTuples[0] && targetTime >= dataTime) { + currentCarry = dataTuples[0][1]; + if (dataTuples.length > 1) { dataTuples.shift(); } + } + + return [bucket[0], currentCarry]; + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js new file mode 100644 index 0000000000000..feacbb02ce195 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +// Upsampling and downsampling of non-cumulative sets +// Good: average, min, max +// Bad: sum, count +export default function nearest(dataTuples, targetTuples) { + return _.map(targetTuples, function (bucket) { + const time = bucket[0]; + let i = 0; + while (i < dataTuples.length - 1 && + (Math.abs(dataTuples[i + 1][0] - time) < Math.abs(dataTuples[i][0] - time) || + // TODO: Certain offset= args can cause buckets with duplicate times, e.g., offset=-1M + // check for that, and only use the last of the duplicates. The reason this happens? + // What is 1M before Mar 30th? What about 1M before Mar 31st? Both are the last day + // in Feb. Something has to be chucked. If offsetting by M user might want to use + // fit=average + Math.abs(dataTuples[i + 1][0] - time) === Math.abs(dataTuples[i][0] - time)) + ) { + i++; + } + + const closest = dataTuples[i]; + dataTuples.splice(0, i); + + return [bucket[0], closest[1]]; + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js new file mode 100644 index 0000000000000..e46d9f44a96be --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +// **DON'T USE THIS** +// Performing joins/math with other sets that don't match perfectly will be wrong +// Does not resample at all. +export default function none(dataTuples) { + return dataTuples; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js new file mode 100644 index 0000000000000..36c7fb691f9e3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js @@ -0,0 +1,92 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +// Downsampling of cumulative metrics +// Good: count, sum +// Bad: avg, min, max + + +// For upsampling cumulative metrics (eg sum from 1M to 1d), could rename this scale. +// Really only the 0s that screws this up, need to distribute contents of spikes to empty buckets +// Empty is currently 0, which is not right + +function sum(set) { + return _.reduce(set, function (sum, num) { return sum + num; }, 0); +} + +export default function scale(dataTuples, targetTuples) { + + let i = 0; + let j = 0; + let spreadCount = 0; + const result = []; + let bucket; + let time; + let scaleSet; + let step; + let nextRealNumber; + + while (i < targetTuples.length) { + scaleSet = []; + bucket = targetTuples[i]; + time = bucket[0]; + + // Find stuff to sum + j = 0; + while (j < dataTuples.length && Math.abs(dataTuples[j][0] <= time)) { + scaleSet.push(dataTuples[j][1]); + j++; + } + dataTuples.splice(0, j); + + // We hit a real number, or the end + if (scaleSet.length > 0 || i === targetTuples.length - 1) { + + nextRealNumber = sum(scaleSet); + + step = nextRealNumber; + // Backfill null buckets + if (spreadCount > 0) { + // Naively distribute the nextRealNumber amongst the buckets + // Without considering where it is headed next + // We do spreadCount + 1 so that we include nextRealNumber when we smooth things out, + // since we'll overwrite it anyway. + // Thus [5, null, null, 30] becomes [5, 10, 10, 10] + step = nextRealNumber / (spreadCount + 1); + while (spreadCount > 0) { + + result[i - spreadCount][1] = step; + spreadCount--; + } + } + result.push([time, step]); + + } else { + result.push([time, null]); + spreadCount++; + } + + i++; + + } + + return result; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js new file mode 100644 index 0000000000000..c94b021e85831 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const parseSheet = require('../lib/parse_sheet'); + +const expect = require('chai').expect; + +describe('timelion parse_sheet function', function () { + it(`doesn't split expressions on whitespace`, async function () { + const data = ['.es() .es(404)']; + const ast = parseSheet(data); + + const expressions = ast[0]; + expect(expressions.length).to.equal(1); + expect(expressions[0].type).to.equal('chain'); + }); + + it('splits expressions on commas', function () { + const data = ['.es(), .es(404)']; + const ast = parseSheet(data); + + const expressions = ast[0]; + expect(expressions.length).to.equal(2); + expect(expressions[0].type).to.equal('chain'); + expect(expressions[1].type).to.equal('chain'); + }); + + it('splits expressions on newlines', function () { + const data = [`.es()\n\r ,\n\r .es(404)`]; + const ast = parseSheet(data); + + const expressions = ast[0]; + expect(expressions.length).to.equal(2); + expect(expressions[0].type).to.equal('chain'); + expect(expressions[1].type).to.equal('chain'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js new file mode 100644 index 0000000000000..cf82a82fd2c12 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js @@ -0,0 +1,228 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +import _ from 'lodash'; +import Bluebird from 'bluebird'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; + +import parseSheet from './lib/parse_sheet.js'; +import repositionArguments from './lib/reposition_arguments.js'; +import indexArguments from './lib/index_arguments.js'; +import validateTime from './lib/validate_time.js'; +import { calculateInterval } from '../../common/lib'; + +export default function chainRunner(tlConfig) { + const preprocessChain = require('./lib/preprocess_chain')(tlConfig); + + let queryCache = {}; + const stats = {}; + let sheet; + + function throwWithCell(cell, exception) { + throw new Error(' in cell #' + (cell + 1) + ': ' + exception.message); + } + + // Invokes a modifier function, resolving arguments into series as needed + function invoke(fnName, args) { + const functionDef = tlConfig.server.plugins.timelion.getFunction(fnName); + + function resolveArgument(item) { + if (Array.isArray(item)) { + return Bluebird.all(_.map(item, resolveArgument)); + } + + if (_.isObject(item)) { + switch (item.type) { + case 'function': { + const itemFunctionDef = tlConfig.server.plugins.timelion.getFunction(item.function); + if (itemFunctionDef.cacheKey && queryCache[itemFunctionDef.cacheKey(item)]) { + stats.queryCount++; + return Bluebird.resolve(_.cloneDeep(queryCache[itemFunctionDef.cacheKey(item)])); + } + return invoke(item.function, item.arguments); + } + case 'reference': { + let reference; + if (item.series) { + reference = sheet[item.plot - 1][item.series - 1]; + } else { + reference = { + type: 'chainList', + list: sheet[item.plot - 1] + }; + } + return invoke('first', [reference]); + } + case 'chain': + return invokeChain(item); + case 'chainList': + return resolveChainList(item.list); + case 'literal': + return item.value; + case 'requestList': + case 'seriesList': + return item; + } + throw new Error( + i18n.translate('timelion.serverSideErrors.unknownArgumentTypeErrorMessage', { + defaultMessage: 'Argument type not supported: {argument}', + values: { + argument: JSON.stringify(item), + }, + }) + ); + } else { + return item; + } + } + + args = repositionArguments(functionDef, args); + + args = _.map(args, resolveArgument); + + return Bluebird.all(args).then(function (args) { + args.byName = indexArguments(functionDef, args); + return functionDef.fn(args, tlConfig); + }); + } + + function invokeChain(chainObj, result) { + if (chainObj.chain.length === 0) return result[0]; + + const chain = _.clone(chainObj.chain); + const link = chain.shift(); + + let promise; + if (link.type === 'chain') { + promise = invokeChain(link); + } else if (!result) { + promise = invoke('first', [link]); + } else { + const args = link.arguments ? result.concat(link.arguments) : result; + promise = invoke(link.function, args); + } + + return promise.then(function (result) { + return invokeChain({ type: 'chain', chain: chain }, [result]); + }); + + } + + function resolveChainList(chainList) { + const seriesList = _.map(chainList, function (chain) { + const values = invoke('first', [chain]); + return values.then(function (args) { + return args; + }); + }); + return Bluebird.all(seriesList).then(function (args) { + const list = _.chain(args).pluck('list').flatten().value(); + const seriesList = _.merge.apply(this, _.flatten([{}, args])); + seriesList.list = list; + return seriesList; + }); + } + + function preProcessSheet(sheet) { + + let queries = {}; + _.each(sheet, function (chainList, i) { + try { + const queriesInCell = _.mapValues(preprocessChain(chainList), function (val) { + val.cell = i; + return val; + }); + queries = _.extend(queries, queriesInCell); + } catch (e) { + throwWithCell(i, e); + } + }); + queries = _.values(queries); + + const promises = _.chain(queries).values().map(function (query) { + return invoke(query.function, query.arguments); + }).value(); + + return Bluebird.settle(promises).then(function (resolvedDatasources) { + + stats.queryTime = (new Date()).getTime(); + + _.each(queries, function (query, i) { + const functionDef = tlConfig.server.plugins.timelion.getFunction(query.function); + const resolvedDatasource = resolvedDatasources[i]; + + if (resolvedDatasource.isRejected()) { + if (resolvedDatasource.reason().isBoom) { + throw resolvedDatasource.reason(); + } else { + throwWithCell(query.cell, resolvedDatasource.reason()); + } + } + + queryCache[functionDef.cacheKey(query)] = resolvedDatasource.value(); + }); + + stats.cacheCount = _.keys(queryCache).length; + return sheet; + }); + } + + function processRequest(request) { + if (!request) throw new Error('Empty request body'); + + validateTime(request.time, tlConfig); + + tlConfig.time = request.time; + tlConfig.time.to = moment(request.time.to).valueOf(); + tlConfig.time.from = moment(request.time.from).valueOf(); + tlConfig.time.interval = calculateInterval( + tlConfig.time.from, + tlConfig.time.to, + tlConfig.settings['timelion:target_buckets'] || 200, + tlConfig.time.interval, + tlConfig.settings['timelion:min_interval'] || '1ms', + ); + + tlConfig.setTargetSeries(); + + stats.invokeTime = (new Date()).getTime(); + stats.queryCount = 0; + queryCache = {}; + + // This is setting the "global" sheet, required for resolving references + sheet = parseSheet(request.sheet); + return preProcessSheet(sheet).then(function () { + return _.map(sheet, function (chainList, i) { + return resolveChainList(chainList).then(function (seriesList) { + stats.sheetTime = (new Date()).getTime(); + return seriesList; + }).catch(function (e) { + throwWithCell(i, e); + }); + }); + }); + } + + return { + processRequest: processRequest, + getStats: function () { return stats; } + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js new file mode 100644 index 0000000000000..59741827fdad2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function argType(arg) { + if (Array.isArray(arg)) { + return _.chain(arg) + .map(argType) + .flattenDeep() + .value(); + } + + if (_.isObject(arg) && arg) { + return arg.type; + } + if (arg == null) { + return 'null'; + } + return typeof arg; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js new file mode 100644 index 0000000000000..5c5be4ab39dac --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +// Only applies to already resolved arguments +export default function indexArguments(functionDef, orderedArgs) { + + const validateArg = require('./validate_arg')(functionDef); + + // This almost certainly is not required + const allowedLength = functionDef.extended ? functionDef.args.length + 2 : functionDef.args.length; + if (orderedArgs.length > allowedLength) { + throw new Error ( + i18n.translate('timelion.serverSideErrors.argumentsOverflowErrorMessage', { + defaultMessage: 'Too many arguments passed to: {functionName}', + values: { + functionName: functionDef.name, + }, + }) + ); + } + + const indexedArgs = {}; + // Check and index each known argument + _.each(functionDef.args, function (argDef, i) { + const value = orderedArgs[i]; + validateArg(value, argDef.name, argDef); + indexedArgs[argDef.name] = value; + }); + + // Also check and index the extended arguments if enabled + if (functionDef.extended) { + const values = orderedArgs[orderedArgs.length - 1]; + const names = orderedArgs[orderedArgs.length - 2]; + _.each(values, function (value, i) { + validateArg(value, names[i], functionDef.extended); + indexedArgs[names[i]] = value; + }); + } + + return indexedArgs; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js new file mode 100644 index 0000000000000..8aca7f516b7e0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import fs from 'fs'; +import path from 'path'; +import _ from 'lodash'; +const grammar = fs.readFileSync(path.resolve(__dirname, '../../../public/chain.peg'), 'utf8'); +import PEG from 'pegjs'; +const Parser = PEG.generate(grammar); + +export default function parseSheet(sheet) { + return _.map(sheet, function (plot) { + try { + return Parser.parse(plot).tree; + } catch (e) { + if (e.expected) { + throw new Error( + i18n.translate('timelion.serverSideErrors.sheetParseErrorMessage', { + defaultMessage: 'Expected: {expectedDescription} at character {column}', + description: 'This would be for example: "Expected: a quote at character 5"', + values: { + expectedDescription: e.expected[0].description, + column: e.column, + }, + }) + ); + } else { + throw e; + } + } + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js new file mode 100644 index 0000000000000..992d34c22232b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function preProcessChainFn(tlConfig) { + return function preProcessChain(chain, queries) { + queries = queries || {}; + function validateAndStore(item) { + if (_.isObject(item) && item.type === 'function') { + const functionDef = tlConfig.server.plugins.timelion.getFunction(item.function); + + if (functionDef.datasource) { + queries[functionDef.cacheKey(item)] = item; + return true; + } + return false; + } + } + + // Is this thing a function? + if (validateAndStore(chain)) { + return; + } + + if (!Array.isArray(chain)) return; + + _.each(chain, function (operator) { + if (!_.isObject(operator)) { + return; + } + switch (operator.type) { + case 'chain': + preProcessChain(operator.chain, queries); + break; + case 'chainList': + preProcessChain(operator.list, queries); + break; + case 'function': + if (validateAndStore(operator)) { + break; + } else { + preProcessChain(operator.arguments, queries); + } + break; + } + }); + + return queries; + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js new file mode 100644 index 0000000000000..0bb7e641ad679 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +// Applies to unresolved arguments in the AST +export default function repositionArguments(functionDef, unorderedArgs) { + const args = []; + + _.each(unorderedArgs, function (unorderedArg, i) { + let argDef; + let targetIndex; + let value; + let storeAsArray; + + if (_.isObject(unorderedArg) && unorderedArg.type === 'namedArg') { + argDef = functionDef.argsByName[unorderedArg.name]; + + if (!argDef) { + if (functionDef.extended) { + const namesIndex = functionDef.args.length; + targetIndex = functionDef.args.length + 1; + + args[namesIndex] = args[namesIndex] || []; + args[namesIndex].push(unorderedArg.name); + + argDef = functionDef.extended; + storeAsArray = true; + } + } else { + targetIndex = _.findIndex(functionDef.args, function (orderedArg) { + return unorderedArg.name === orderedArg.name; + }); + storeAsArray = argDef.multi; + + } + value = unorderedArg.value; + } else { + argDef = functionDef.args[i]; + storeAsArray = argDef.multi; + targetIndex = i; + value = unorderedArg; + } + + if (!argDef) { + throw new Error( + i18n.translate('timelion.serverSideErrors.unknownArgumentErrorMessage', { + defaultMessage: 'Unknown argument to {functionName}: {argumentName}', + values: { + functionName: functionDef.name, + argumentName: (unorderedArg.name || ('#' + i)), + }, + }) + ); + } + + if (storeAsArray) { + args[targetIndex] = args[targetIndex] || []; + args[targetIndex].push(value); + } else { + args[targetIndex] = value; + } + }); + + return args; + +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js new file mode 100644 index 0000000000000..51235c26c1d5e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import buildTarget from '../../lib/build_target.js'; + +export default function tlConfigFn(setup) { + let targetSeries; + + let tlConfig = { + getTargetSeries: function () { + return _.map(targetSeries, function (bucket) { // eslint-disable-line no-use-before-define + return [bucket, null]; + }); + }, + setTargetSeries: function () { + targetSeries = buildTarget(this); + }, + writeTargetSeries: function (series) { + targetSeries = _.map(series, function (p) {return p[0];}); + } + }; + + tlConfig = _.extend(tlConfig, setup); + return tlConfig; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js new file mode 100644 index 0000000000000..22b904a8a1fea --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import argType from './arg_type'; +import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + +export default function validateArgFn(functionDef) { + return function validateArg(value, name, argDef) { + const type = argType(value); + const required = argDef.types; + const multi = argDef.multi; + const isCorrectType = (function () { + // If argument is not allow to be specified multiple times, we're dealing with a plain value for type + if (!multi) return _.contains(required, type); + // If it is, we'll get an array for type + return _.difference(type, required).length === 0; + }()); + + if (isCorrectType) return true; + else return false; + + if (!isCorrectType) { + throw new Error( + i18n.translate('timelion.serverSideErrors.wrongFunctionArgumentTypeErrorMessage', { + defaultMessage: '{functionName}({argumentName}) must be one of {requiredTypes}. Got: {actualType}', + values: { + functionName: functionDef.name, + argumentName: name, + requiredTypes: JSON.stringify(required), + actualType: type, + }, + }) + ); + } + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js new file mode 100644 index 0000000000000..8b1f8998557be --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; + +import toMS from '../../lib/to_milliseconds.js'; + +export default function validateTime(time, tlConfig) { + const span = moment.duration(moment(time.to).diff(moment(time.from))).asMilliseconds(); + const interval = toMS(time.interval); + const bucketCount = span / interval; + const maxBuckets = tlConfig.settings['timelion:max_buckets']; + if (bucketCount > maxBuckets) { + throw new Error( + i18n.translate('timelion.serverSideErrors.bucketsOverflowErrorMessage', { + defaultMessage: + 'Max buckets exceeded: {bucketCount} of {maxBuckets} allowed. ' + + 'Choose a larger interval or a shorter time span', + values: { + bucketCount: Math.round(bucketCount), + maxBuckets, + }, + }) + ); + } + return true; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/index.ts b/src/legacy/core_plugins/vis_type_timelion/server/index.ts new file mode 100644 index 0000000000000..36af9ce7b85df --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'kibana/server'; +import { TimelionServerPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js new file mode 100644 index 0000000000000..45dd436be6943 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../load_functions`); + +const expect = require('chai').expect; + +describe('load_functions.js', () => { + it('exports a function', () => { + expect(fn).to.be.a('function'); + }); + + it('returns an object with keys named for the javascript files in the directory', () => { + const fnList = fn('series_functions'); + + expect(fnList).to.be.an('object'); + expect(fnList.sum).to.be.a('object'); + }); + + it('also includes index.js files in direct subdirectories, and names the keys for the directory', () => { + const fnList = fn('series_functions'); + + expect(fnList).to.be.an('object'); + expect(fnList.es).to.be.a('object'); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js new file mode 100644 index 0000000000000..154392fd66253 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Bluebird from 'bluebird'; +import _ from 'lodash'; + +/* @param {Array} args + * - args[0] must be a seriesList + + * @params {Function} fn - Function to apply to each series in the seriesList + * @return {seriesList} + */ + +export default function alter(args, fn) { + // In theory none of the args should ever be promises. This is probably a waste. + return Bluebird.all(args).then(function (args) { + + const seriesList = args.shift(); + + if (seriesList.type !== 'seriesList') { + throw new Error ('args[0] must be a seriesList'); + } + + const list = _.chain(seriesList.list).map(function (series) { + return fn.apply(this, [series].concat(args)); + }).flatten().value(); + + seriesList.list = list; + return seriesList; + }).catch(function (e) { + throw e; + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js new file mode 100644 index 0000000000000..ff20af78b4362 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import unzipPairs from './unzipPairs.js'; + +export default function asSorted(timeValObject, fn) { + const data = unzipPairs(timeValObject); + return _.zipObject(fn(data)); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js new file mode 100644 index 0000000000000..639a7a1783b56 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; +import splitInterval from './split_interval.js'; + +export default function (tlConfig) { + const min = moment(tlConfig.time.from); + const max = moment(tlConfig.time.to); + + const intervalParts = splitInterval(tlConfig.time.interval); + + let current = min.startOf(intervalParts.unit); + + const targetSeries = []; + + while (current.valueOf() < max.valueOf()) { + targetSeries.push(current.valueOf()); + current = current.add(intervalParts.count, intervalParts.unit); + } + + return targetSeries; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js new file mode 100644 index 0000000000000..8733212c9eed3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import TimelionFunction from './timelion_function'; + +export default class Chainable extends TimelionFunction { + constructor(name, config) { + super(name, config); + this.chainable = true; + Object.freeze(this); + } +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js new file mode 100644 index 0000000000000..99162df59044b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js @@ -0,0 +1,101 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import loadFunctions from '../load_functions.js'; +const fitFunctions = loadFunctions('fit_functions'); +import TimelionFunction from './timelion_function'; +import { offsetTime, preprocessOffset } from '../offset_time'; +import _ from 'lodash'; + + +function offsetSeries(response, offset) { + if (offset) { + response = _.map(response, function (point) { + return [offsetTime(point[0], offset, true), point[1]]; + }); + } + return response; +} + +export default class Datasource extends TimelionFunction { + constructor(name, config) { + + // Additional arguments that every dataSource take + config.args.push({ + name: 'offset', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.common.args.offsetHelpText', { + defaultMessage: + 'Offset the series retrieval by a date expression, e.g., -1M to make events from ' + + 'one month ago appear as if they are happening now. Offset the series relative to the charts ' + + 'overall time range, by using the value "timerange", e.g. "timerange:-2" will specify an offset ' + + 'that is twice the overall chart time range to the past.', + }), + }); + + config.args.push({ + name: 'fit', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.common.args.fitHelpText', { + defaultMessage: + 'Algorithm to use for fitting series to the target time span and interval. Available: {fitFunctions}', + values: { + fitFunctions: _.keys(fitFunctions).join(', '), + }, + }) + }); + + // Wrap the original function so we can modify inputs/outputs with offset & fit + const originalFunction = config.fn; + config.fn = function (args, tlConfig) { + const config = _.clone(tlConfig); + let offset = args.byName.offset; + if (offset) { + offset = preprocessOffset(offset, tlConfig.time.from, tlConfig.time.to); + config.time = _.cloneDeep(tlConfig.time); + config.time.from = offsetTime(config.time.from, offset); + config.time.to = offsetTime(config.time.to, offset); + } + + return Promise.resolve(originalFunction(args, config)).then(function (seriesList) { + seriesList.list = _.map(seriesList.list, function (series) { + if (series.data.length === 0) throw new Error(name + '() returned no results'); + series.data = offsetSeries(series.data, offset); + series.fit = args.byName.fit || series.fit || 'nearest'; + return series; + }); + return seriesList; + + }); + }; + + super(name, config); + + // You need to call timelionFn if calling up a datasource from another datasource, + // otherwise teh series will end up being offset twice. + this.timelionFn = originalFunction; + this.datasource = true; + this.cacheKey = function (item) { + return item.text; + }; + Object.freeze(this); + } + +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts new file mode 100644 index 0000000000000..6e32a4454e707 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface TimelionFunctionInterface extends TimelionFunctionConfig { + chainable: boolean; + originalFn: Function; + argsByName: TimelionFunctionArgs[]; +} + +export interface TimelionFunctionConfig { + name: string; + help: string; + extended: boolean; + aliases: string[]; + fn: Function; + args: TimelionFunctionArgs[]; +} + +export interface TimelionFunctionArgs { + name: string; + help?: string; + multi?: boolean; + types: TimelionFunctionArgsTypes[]; + suggestions?: TimelionFunctionArgsSuggestion[]; +} + +export type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null'; + +export interface TimelionFunctionArgsSuggestion { + name: string; + help: string; +} + +// eslint-disable-next-line import/no-default-export +export default class TimelionFunction { + constructor(name: string, config: TimelionFunctionConfig); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js new file mode 100644 index 0000000000000..4fc584bd591cc --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import loadFunctions from '../load_functions.js'; +const fitFunctions = loadFunctions('fit_functions'); + +export default class TimelionFunction { + constructor(name, config) { + this.name = name; + this.args = config.args || []; + this.argsByName = _.indexBy(this.args, 'name'); + this.help = config.help || ''; + this.aliases = config.aliases || []; + this.extended = config.extended || false; + + // WTF is this? How could you not have a fn? Wtf would the thing be used for? + const originalFunction = config.fn || function (input) { return input; }; + + // Currently only re-fits the series. + this.originalFn = originalFunction; + + this.fn = function (args, tlConfig) { + const config = _.clone(tlConfig); + return Promise.resolve(originalFunction(args, config)).then(function (seriesList) { + seriesList.list = _.map(seriesList.list, function (series) { + const target = tlConfig.getTargetSeries(); + + // Don't fit if the series are already the same + if (_.isEqual(_.map(series.data, 0), _.map(target, 0))) return series; + + let fit; + if (args.byName.fit) { + fit = args.byName.fit; + } else if (series.fit) { + fit = series.fit; + } else { + fit = 'nearest'; + } + + series.data = fitFunctions[fit](series.data, tlConfig.getTargetSeries()); + return series; + }); + return seriesList; + }); + }; + } +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js new file mode 100644 index 0000000000000..34189314f64d7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import loadFunctions from './load_functions.js'; +const functions = loadFunctions('series_functions/'); +import _ from 'lodash'; + + +export default (function () { + const functionArray = _.map(functions, function (val, key) { + // TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables + return _.extend({}, val, { name: key }); + }); + + function toDocBlock(fn) { + let help = ''; + + if (fn.isAlias) return help; + + help += '#### .' + fn.name + '()\n'; + help += fn.help + '\n\n'; + + // If chainable, drop first argument from help + const args = fn.chainable ? fn.args.slice(1) : fn.args.slice(); + if (!args.length) { + help += '*This function does not accept any arguments.*\n\n'; + return help; + } + + help += 'Argument | Accepts | Description\n'; + help += '--- | --- | ---\n'; + + _.each(args, function (arg) { + help += arg.name + ' | *' + _.without(arg.types, 'null').join('/') + '* | '; + help += arg.help ? arg.help : '*no help available*'; + help += ' \n'; + }); + + help += '\n'; + + return help; + } + + function createDocs() { + let help = ''; + help += '## Timelion function reference\n'; + help += 'This document is auto generated from the timelion code. ' + + 'Do not submit pulls against this document. You want to submit a pull against something in the ' + + '`series_functions/` directory.\n\n'; + + help += '### Data sources\n'; + help += 'Data sources can start a chain, they don\'t need to be attached to anything, but they still need to start' + + ' with a `.` (dot). Data retrieved from a data source can be passed into the chainable functions in the next section.\n\n'; + + help += _.chain(functionArray) + .filter('datasource') + .map(toDocBlock) + .value() + .join(''); + + help += '### Chainable functions\n'; + help += 'Chainable functions can not start a chain. Somewhere before them must be a data source function. Chainable' + + ' functions modify the data output directly from a data source, or from another chainable function that has a data' + + ' source somewhere before it.\n\n'; + + + help += _.chain(functionArray) + .filter('chainable') + .map(toDocBlock) + .value() + .join(''); + + return help; + } + + return createDocs(); +}()); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js new file mode 100644 index 0000000000000..5a608914171bc --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import configFile from '../../timelion.json'; + +export default function () { + function flattenWith(dot, nestedObj, flattenArrays) { + const stack = []; // track key stack + const flatObj = {}; + (function flattenObj(obj) { + _.keys(obj).forEach(function (key) { + stack.push(key); + if (!flattenArrays && Array.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; + else if (_.isObject(obj[key])) flattenObj(obj[key]); + else flatObj[stack.join(dot)] = obj[key]; + stack.pop(); + }); + }(nestedObj)); + return flatObj; + } + + const timelionDefaults = flattenWith('.', configFile); + return _.reduce(timelionDefaults, (result, value, key) => { + result['timelion:' + key] = value; + return result; + }, {}); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts new file mode 100644 index 0000000000000..f6d04ee875831 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { TimelionFunctionInterface } from '../types'; + +declare function loadFunctions(directory: string): LoadFunctions; + +export interface LoadFunctions { + [key: string]: TimelionFunctionInterface; +} + +// eslint-disable-next-line import/no-default-export +export default loadFunctions; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js new file mode 100644 index 0000000000000..b1c9c986d14ad --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import glob from 'glob'; +import path from 'path'; +import processFunctionDefinition from './process_function_definition'; + +export default function (directory) { + + function getTuple(directory, name) { + return [name, require('../' + directory + '/' + name)]; // eslint-disable-line import/no-dynamic-require + } + + // Get a list of all files and use the filename as the object key + const files = _.map(glob.sync(path.resolve(__dirname, '../' + directory + '/*.js')), function (file) { + const name = file.substring(file.lastIndexOf('/') + 1, file.lastIndexOf('.')); + return getTuple(directory, name); + }); + + // Get a list of all directories with an index.js, use the directory name as the key in the object + const directories = _.chain(glob.sync(path.resolve(__dirname, '../' + directory + '/*/index.js'))) + .filter(function (file) { + return file.match(/__test__/) == null; + }) + .map(function (file) { + const parts = file.split('/'); + const name = parts[parts.length - 2]; + return getTuple(directory, name); + }).value(); + + const functions = _.zipObject(files.concat(directories)); + + _.each(functions, function (func) { + _.assign(functions, processFunctionDefinition(func)); + }); + + return functions; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js new file mode 100644 index 0000000000000..4c9f364c8c4be --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; + +// usually reverse = false on the request, true on the response +export function offsetTime(milliseconds, offset, reverse) { + if (!offset.match(/[-+][0-9]+[mshdwMy]/g)) { + throw new Error ('Malformed `offset` at ' + offset); + } + const parts = offset.match(/[-+]|[0-9]+|[mshdwMy]/g); + + let add = parts[0] === '+'; + add = reverse ? !add : add; + + const mode = add ? 'add' : 'subtract'; + + const momentObj = moment(milliseconds)[mode](parts[1], parts[2]); + return momentObj.valueOf(); +} + +function timeRangeErrorMsg(offset) { + return `Malformed timerange offset, expecting "timerange:", received: ${offset}`; +} + +/* + * Calculate offset when parameter is requesting a relative offset based on requested time range. + * + * @param {string} offset - offset parameter value + * @param {number} from - kibana global time 'from' in milliseconds + * @param {number} to - kibana global time 'to' in milliseconds + */ +export function preprocessOffset(offset, from, to) { + if (!offset.startsWith('timerange')) { + return offset; + } + + const parts = offset.split(':'); + if (parts.length === 1) { + throw new Error(timeRangeErrorMsg(offset)); + } + + const factor = parseFloat(parts[1]); + if (isNaN(factor)) { + throw new Error(timeRangeErrorMsg(offset)); + } + if (factor >= 0) { + throw new Error('Malformed timerange offset, factor must be negative number.'); + } + + const deltaSeconds = (to - from) / 1000; + const processedOffset = Math.round(deltaSeconds * factor); + return `${processedOffset}s`; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js new file mode 100644 index 0000000000000..9250d3898638d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import moment from 'moment'; +import { preprocessOffset } from './offset_time'; + +describe('offset', () => { + + describe('preprocessOffset', () => { + const from = moment('2018-01-01T00:00:00.000Z').valueOf(); + const to = moment('2018-01-01T00:15:00.000Z').valueOf(); + + test('throws error when no number is provided', () => { + expect(() => preprocessOffset('timerange', from, to)).to.throwError(); + }); + + test('throws error when zero is provided', () => { + expect(() => preprocessOffset('timerange:0', from, to)).to.throwError(); + }); + + test('throws error when factor is larger than zero', () => { + expect(() => preprocessOffset('timerange:1', from, to)).to.throwError(); + }); + + test('throws error with malformed', () => { + expect(() => preprocessOffset('timerange:notANumber', from, to)).to.throwError(); + }); + + test('does not modify offset when value is not requesting relative offset', () => { + const offset = '-1d'; + expect(preprocessOffset(offset, from, to)).to.eql(offset); + }); + + test('converts offset when value is requesting relative offset with multiplier', () => { + const offset = 'timerange:-2'; + expect(preprocessOffset(offset, from, to)).to.eql('-1800s'); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js new file mode 100644 index 0000000000000..3159d33c884e8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (func) { + const functions = {}; + functions[func.name] = func; + if (func.aliases) { + _.each(func.aliases, function (alias) { + const aliasFn = _.clone(func); + aliasFn.isAlias = true; + functions[alias] = aliasFn; + }); + } + + return functions; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js new file mode 100644 index 0000000000000..e9cbfd76b21ac --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js @@ -0,0 +1,146 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +function allSeriesContainKey(seriesList, key) { + const containsKeyInitialValue = true; + return seriesList.list.reduce((containsKey, series) => { + return containsKey && _.has(series, key); + }, containsKeyInitialValue); +} + +/** + * Pairwise reduce seriesList + * @params {seriesList} left + * @params {seriesList} right + * @params {Function} fn - Function used to combine points at same index in each array of each series in the seriesList. + * @return {seriesList} + */ +async function pairwiseReduce(left, right, fn) { + if (left.list.length !== right.list.length) { + throw new Error ('Unable to pairwise reduce seriesLists, number of series are not the same'); + } + + let pairwiseField = 'label'; + if (allSeriesContainKey(left, 'split') && allSeriesContainKey(right, 'split')) { + pairwiseField = 'split'; + } + const indexedList = _.indexBy(right.list, pairwiseField); + + // ensure seriesLists contain same pairwise labels + left.list.forEach((leftSeries) => { + if (!indexedList[leftSeries[pairwiseField]]) { + const rightSeriesLabels = right.list.map((rightSeries) => { + return `"${rightSeries[pairwiseField]}"`; + }).join(','); + throw new Error (`Matching series could not be found for "${leftSeries[pairwiseField]}" in [${rightSeriesLabels}]`); + } + }); + + // pairwise reduce seriesLists + const pairwiseSeriesList = { type: 'seriesList', list: [] }; + left.list.forEach(async (leftSeries) => { + const first = { type: 'seriesList', list: [leftSeries] }; + const second = { type: 'seriesList', list: [indexedList[leftSeries[pairwiseField]]] }; + const reducedSeriesList = await reduce([first, second], fn); + const reducedSeries = reducedSeriesList.list[0]; + reducedSeries.label = leftSeries[pairwiseField]; + pairwiseSeriesList.list.push(reducedSeries); + }); + return pairwiseSeriesList; +} + +/** + * Reduces multiple arrays into a single array using a function + * @param {Array} args - args[0] must always be a {type: 'seriesList'} + * + * - If only arg[0] exists, the seriesList will be reduced to a seriesList containing a single series + * - If multiple arguments are passed, each argument will be mapped onto each series in the seriesList. + + * @params {Function} fn - Function used to combine points at same index in each array of each series in the seriesList. + * @return {seriesList} + */ +async function reduce(argsPromises, fn) { + const args = await Promise.all(argsPromises); + + const seriesList = args.shift(); + let argument = args.shift(); + + if (seriesList.type !== 'seriesList') { + throw new Error ('input must be a seriesList'); + } + + if (_.isObject(argument) && argument.type === 'seriesList') { + if (argument.list.length > 1) { + return await pairwiseReduce(seriesList, argument, fn); + } else { + argument = argument.list[0]; + } + } + + + function reduceSeries(series) { + return _.reduce(series, function (destinationObject, argument, i, p) { + + let output = _.map(destinationObject.data, function (point, index) { + + const value = point[1]; + + if (value == null) { + return [point[0], null]; + } + + if (_.isNumber(argument)) { + return [point[0], fn(value, argument, i, p)]; + } + + if (argument.data[index] == null || argument.data[index][1] == null) { + return [point[0], null]; + } + return [point[0], fn(value, argument.data[index][1], i, p)]; + }); + + // Output = single series + + output = { + data: output + }; + output = _.defaults(output, destinationObject); + return output; + + }); + + } + + let reduced; + + if (argument != null) { + reduced = _.map(seriesList.list, function (series) { + return reduceSeries([series].concat(argument)); + }); + } else { + reduced = [reduceSeries(seriesList.list)]; + } + + seriesList.list = reduced; + return seriesList; +} + +export default reduce; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js new file mode 100644 index 0000000000000..41c6af9466d95 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function splitInterval(interval) { + if (!interval.match(/[0-9]+[mshdwMy]+/g)) { + throw new Error ('Malformed `interval`: ' + interval); + } + const parts = interval.match(/[0-9]+|[mshdwMy]+/g); + + return { + count: parts[0], + unit: parts[1] + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js new file mode 100644 index 0000000000000..2dd5cad4cad82 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import moment from 'moment'; + +// map of moment's short/long unit ids and elasticsearch's long unit ids +// to their value in milliseconds +const vals = _.transform([ + ['ms', 'milliseconds', 'millisecond'], + ['s', 'seconds', 'second', 'sec'], + ['m', 'minutes', 'minute', 'min'], + ['h', 'hours', 'hour'], + ['d', 'days', 'day'], + ['w', 'weeks', 'week'], + ['M', 'months', 'month'], + ['quarter'], + ['y', 'years', 'year'] +], function (vals, units) { + const normal = moment.normalizeUnits(units[0]); + const val = moment.duration(1, normal).asMilliseconds(); + [].concat(normal, units).forEach(function (unit) { + vals[unit] = val; + }); +}, {}); +// match any key from the vals object preceded by an optional number +const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); + +export default function (expr) { + const match = expr.match(parseRE); + if (match) { + if (match[2] === 'M' && match[1] !== '1') { + throw new Error ('Invalid interval. 1M is only valid monthly interval.'); + } + + return parseFloat(match[1] || 1) * vals[match[2]]; + } +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js new file mode 100644 index 0000000000000..7dc7eed89b6b1 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function unzipPairs(timeValObject) { + const paired = _.chain(timeValObject).pairs().map(function (point) { + return [parseInt(point[0], 10), point[1]]; + }).sortBy(function (point) { + return point[0]; + }).value(); + return paired; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/server/plugin.ts new file mode 100644 index 0000000000000..c94277ebc7adc --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/plugin.ts @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Legacy } from 'kibana'; +import { i18n } from '@kbn/i18n'; +import { PluginInitializerContext, CoreSetup } from 'kibana/server'; + +import loadFunctions, { LoadFunctions } from './lib/load_functions'; +import { initRoutes } from './routes'; + +function getFunction(functions: LoadFunctions, name: string) { + if (functions[name]) { + return functions[name]; + } + + throw new Error( + i18n.translate('timelion.noFunctionErrorMessage', { + defaultMessage: 'No such function: {name}', + values: { name }, + }) + ); +} + +// TODO: Remove as CoreSetup is completed. +export interface CustomCoreSetup { + http: { + server: Legacy.Server; + }; +} + +export class TimelionServerPlugin { + public initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public setup(core: CoreSetup & CustomCoreSetup) { + const { server } = core.http; + const functions = loadFunctions('series_functions'); + + server.expose('functions', functions); + server.expose('getFunction', (name: string) => getFunction(functions, name)); + + initRoutes(server); + } +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js new file mode 100644 index 0000000000000..ff8b750cf1ec2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export function functionsRoute(server) { + server.route({ + method: 'GET', + path: '/api/timelion/functions', + handler: () => { + const functionArray = _.map(server.plugins.timelion.functions, function (val, key) { + // TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables + return _.extend({}, val, { name: key }); + }); + + return _.sortBy(functionArray, 'name'); + } + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts b/src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts new file mode 100644 index 0000000000000..b6438c81ef002 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { Legacy } from 'kibana'; + +// @ts-ignore +import { runRoute } from './run'; +// @ts-ignore +import { functionsRoute } from './functions'; +// @ts-ignore +import { validateEsRoute } from './validate_es'; + +export function initRoutes(server: Legacy.Server) { + runRoute(server); + functionsRoute(server); + validateEsRoute(server); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/run.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/run.js new file mode 100644 index 0000000000000..d868f868aaf3e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/run.js @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Bluebird from 'bluebird'; +import _ from 'lodash'; +import chainRunnerFn from '../handlers/chain_runner.js'; +const timelionDefaults = require('../lib/get_namespaced_settings')(); + +function formatErrorResponse(e, h) { + return h.response({ + title: e.toString(), + message: e.toString() + }).code(500); +} + +export function runRoute(server) { + server.route({ + method: ['POST', 'GET'], + path: '/api/timelion/run', + handler: async (request, h) => { + try { + const uiSettings = await request.getUiSettingsService().getAll(); + + const tlConfig = require('../handlers/lib/tl_config.js')({ + server, + request, + settings: _.defaults(uiSettings, timelionDefaults) // Just in case they delete some setting. + }); + + const chainRunner = chainRunnerFn(tlConfig); + const sheet = await Bluebird.all(chainRunner.processRequest(request.payload || { + sheet: [request.query.expression], + time: { + from: request.query.from, + to: request.query.to, + interval: request.query.interval, + timezone: request.query.timezone + } + })); + + return { + sheet, + stats: chainRunner.getStats() + }; + + } catch (err) { + server.log(['timelion', 'error'], `${err.toString()}: ${err.stack}`); + // TODO Maybe we should just replace everywhere we throw with Boom? Probably. + if (err.isBoom) { + return err; + } else { + return formatErrorResponse(err, h); + } + } + } + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js new file mode 100644 index 0000000000000..350f6fd656010 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js @@ -0,0 +1,74 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export function validateEsRoute(server) { + server.route({ + method: 'GET', + path: '/api/timelion/validate/es', + handler: async function (request) { + const uiSettings = await request.getUiSettingsService().getAll(); + + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + + const timefield = uiSettings['timelion:es.timefield']; + + const body = { + index: uiSettings['es.default_index'], + body: { + aggs: { + maxAgg: { + max: { + field: timefield + } + }, + minAgg: { + min: { + field: timefield + } + } + }, + size: 0 + } + }; + + let resp = {}; + try { + resp = await callWithRequest(request, 'search', body); + } catch (errResp) { + resp = errResp; + } + + if (_.has(resp, 'aggregations.maxAgg.value') && _.has(resp, 'aggregations.minAgg.value')) { + return { + ok: true, + field: timefield, + min: _.get(resp, 'aggregations.minAgg.value'), + max: _.get(resp, 'aggregations.maxAgg.value') + }; + } + + return { + ok: false, + resp: resp + }; + } + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js new file mode 100644 index 0000000000000..9d90a55bcb71e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../abs`); + +import _ from 'lodash'; +const expect = require('chai').expect; +const seriesList = require('./fixtures/seriesList.js')(); +import invoke from './helpers/invoke_series_fn.js'; + +describe('abs.js', function () { + it('should return the positive value of every value', function () { + + return invoke(fn, [seriesList]).then(function (result) { + const before = _.filter(result.input[0].list[0].data, function (point) { + return (point[1] < 0); + }); + + const after = _.filter(result.output.list[0].data, function (point) { + return (point[1] < 0); + }); + + expect(before.length > 0).to.eql(true); + expect(result.output.list[0].data.length > 0).to.eql(true); + expect(after.length).to.eql(0); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js new file mode 100644 index 0000000000000..26f853b86c091 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const filename = require('path').basename(__filename); +const fn = require(`../aggregate/index.js`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe(filename, () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('first', () => { + return invoke(fn, [seriesList, 'first']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([100, 100, 100, 100]); + }); + }); + + it('last', () => { + return invoke(fn, [seriesList, 'last']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([20, 20, 20, 20]); + }); + }); + + it('min', () => { + return invoke(fn, [seriesList, 'min']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([20, 20, 20, 20]); + }); + }); + + it('max', () => { + return invoke(fn, [seriesList, 'max']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([100, 100, 100, 100]); + }); + }); + + it('sum', () => { + return invoke(fn, [seriesList, 'sum']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([220, 220, 220, 220]); + }); + }); + + it('cardinality', () => { + return invoke(fn, [seriesList, 'cardinality']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([3, 3, 3, 3]); + }); + }); + + it('avg', () => { + return invoke(fn, [seriesList, 'avg']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([55, 55, 55, 55]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js new file mode 100644 index 0000000000000..e64e5f8b87dde --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../bars`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('bars.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('creates the bars property, with defaults, on all series', () => { + return invoke(fn, [seriesList]).then((r) => { + const bars = _.map(r.output.list, 'bars'); + _.each(bars, (bar) => expect(bar).to.be.a('object')); + _.each(bars, (bar) => expect(bar.lineWidth).to.equal(6)); + _.each(bars, (bar) => expect(bar.show).to.equal(1)); + }); + }); + + it('leaves existing bars alone when called without option, if they exist', () => { + seriesList.list[0].bars = { foo: true }; + return invoke(fn, [seriesList]).then((r) => { + const bars = _.map(r.output.list, 'bars'); + expect(bars[0].foo).to.equal(true); + expect(bars[1].foo).to.equal(undefined); + }); + }); + + it('sets lineWidth and show to the same value', () => { + return invoke(fn, [seriesList, 0]).then((r) => { + const bars = _.map(r.output.list, 'bars'); + expect(bars[0].lineWidth).to.equal(0); + expect(bars[0].show).to.equal(0); + + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js new file mode 100644 index 0000000000000..dfe9bfc3988e7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../color`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('color.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('sets the color, on all series', () => { + return invoke(fn, [seriesList, '#eee']).then((r) => { + const colors = _.map(r.output.list, 'color'); + _.each(colors, (color) => expect(color).to.equal('#eee')); + }); + }); + + it('generates a gradient', () => { + const expected = ['#000000', '#111111', '#222222', '#333333']; + const fourLongList = { + type: 'seriesList', + list: seriesList.list.slice(0, 4) + }; + return invoke(fn, [fourLongList, '#000:#333']).then((r) => { + const colors = _.map(r.output.list, 'color'); + _.each(colors, (color, i) => expect(color).to.equal(expected[i])); + }); + }); + + it('should handle more colors than number of series', async () => { + const colorsArg = '#000:#111:#222:#333:#444:#555'; + const numColors = colorsArg.split(':').length; + expect(numColors).to.be.above(seriesList.list.length); + + const r = await invoke(fn, [seriesList, colorsArg]); + const seriesColors = _.map(r.output.list, 'color'); + expect(seriesColors).to.eql(['#000000', '#111111', '#222222', '#333333', '#444444']); + }); + + it('should work with series.length=1 and more colors', async () => { + const oneLongList = { + type: 'seriesList', + list: seriesList.list.slice(0, 1) + }; + const colorsArg = '#000:#111'; + + const r = await invoke(fn, [oneLongList, colorsArg]); + const seriesColors = _.map(r.output.list, 'color'); + expect(seriesColors).to.eql(['#000']); + }); + + it('throws if you do not pass a color', () => { + invoke(fn, [seriesList, '']).catch((e) => { + expect(e).to.be.an(Error); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js new file mode 100644 index 0000000000000..67bfa0cfdea95 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../condition`); +import moment from 'moment'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; +import getSeriesList from './helpers/get_single_series_list'; +import _ from 'lodash'; + +describe('condition.js', function () { + + let comparable; + let seriesList; + beforeEach(function () { + seriesList = require('./fixtures/seriesList.js')(); + comparable = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 12], + [moment.utc('1981-01-01T00:00:00.000Z'), 33], + [moment.utc('1982-01-01T00:00:00.000Z'), 82], + [moment.utc('1983-01-01T00:00:00.000Z'), -101], + ]); + }); + + describe('a single number with', function () { + it('eq', function () { + return invoke(fn, [seriesList, 'eq', 17, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 0, 82, 20]); + }); + }); + + it('ne', function () { + return invoke(fn, [seriesList, 'ne', 17, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 17, 0, 0]); + }); + }); + + it('gte', function () { + return invoke(fn, [seriesList, 'gte', 17, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 0, 0, 0]); + }); + }); + + it('gt', function () { + return invoke(fn, [seriesList, 'gt', 17, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 17, 0, 0]); + }); + }); + + it('lt', function () { + return invoke(fn, [seriesList, 'lt', 17, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 17, 82, 20]); + }); + }); + + it('lte', function () { + return invoke(fn, [seriesList, 'lte', 17, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 20]); + }); + }); + }); + + it('can compare against another series', function () { + return invoke(fn, [seriesList, 'ne', comparable, 0]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 0]); + }); + }); + + it('can set the resultant value to that of another series', function () { + return invoke(fn, [seriesList, 'lt', comparable, comparable]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([12, 33, 82, 20]); + }); + }); + + it('can set the resultant value to null', function () { + return invoke(fn, [seriesList, 'lt', 17, null]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([null, 17, 82, 20]); + }); + }); + + describe('else', function () { + it('has else', function () { + return invoke(fn, [seriesList, 'lt', 30, 0, 1]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 1, 0]); + }); + }); + + it('works with other series', function () { + return invoke(fn, [seriesList, 'lt', 30, 0, comparable]).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 0]); + }); + }); + }); + + + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js new file mode 100644 index 0000000000000..dc0e88177f0c4 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../cusum`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('cusum.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('progressively adds the numbers in the list', () => { + return invoke(fn, [seriesList]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([100, 150, 200, 220]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js new file mode 100644 index 0000000000000..dd0c134ff4b33 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../derivative`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('derivative.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('gets the change in the set', () => { + return invoke(fn, [seriesList]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([null, -50, 0, -30]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js new file mode 100644 index 0000000000000..bee664c7a4831 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../divide`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('divide.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('divides by a single number', () => { + return invoke(fn, [seriesList, 2]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([50, 25, 25, 10]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js new file mode 100644 index 0000000000000..f370c3439125b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js @@ -0,0 +1,498 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const filename = require('path').basename(__filename); +import es from '../es'; + +import tlConfigFn from './fixtures/tlConfig'; +import * as aggResponse from '../es/lib/agg_response_to_series_list'; +import buildRequest from '../es/lib/build_request'; +import createDateAgg from '../es/lib/create_date_agg'; +import esResponse from './fixtures/es_response'; + +import Bluebird from 'bluebird'; +import _ from 'lodash'; +import { expect } from 'chai'; +import sinon from 'sinon'; +import invoke from './helpers/invoke_series_fn.js'; + +function stubRequestAndServer(response, indexPatternSavedObjects = []) { + return { + server: { + plugins: { + elasticsearch: { + getCluster: sinon.stub().withArgs('data').returns({ + callWithRequest: function () { + return Bluebird.resolve(response); + } + }) + } + } + }, + request: { + getSavedObjectsClient: function () { + return { + find: function () { + return Bluebird.resolve({ + saved_objects: indexPatternSavedObjects + }); + } + }; + } + } + }; +} + +describe(filename, () => { + let tlConfig; + + describe('seriesList processor', () => { + it('throws an error then the index is missing', () => { + tlConfig = stubRequestAndServer({ + _shards: { total: 0 } + }); + return invoke(es, [5], tlConfig) + .then(expect.fail) + .catch((e) => { + expect(e).to.be.an('error'); + }); + }); + + it('returns a seriesList', () => { + tlConfig = stubRequestAndServer(esResponse); + return invoke(es, [5], tlConfig) + .then((r) => { + expect(r.output.type).to.eql('seriesList'); + }); + }); + }); + + + describe('createDateAgg', () => { + let tlConfig; + let config; + let agg; + beforeEach(() => { + tlConfig = tlConfigFn(); + config = { + timefield: '@timestamp', + interval: '1y' + }; + agg = createDateAgg(config, tlConfig); + }); + + + it('creates a date_histogram with meta.type of time_buckets', () => { + expect(agg.time_buckets.meta.type).to.eql('time_buckets'); + expect(agg.time_buckets.date_histogram).to.be.an('object'); + }); + + it('has extended_bounds that match tlConfig', () => { + expect(agg.time_buckets.date_histogram.extended_bounds.min).to.equal(tlConfig.time.from); + expect(agg.time_buckets.date_histogram.extended_bounds.max).to.equal(tlConfig.time.to); + }); + + it('sets the timezone', () => { + expect(agg.time_buckets.date_histogram.time_zone).to.equal('Etc/UTC'); + }); + + it('sets the field and interval', () => { + expect(agg.time_buckets.date_histogram.field).to.equal('@timestamp'); + expect(agg.time_buckets.date_histogram.interval).to.equal('1y'); + }); + + it('sets min_doc_count to 0', () => { + expect(agg.time_buckets.date_histogram.min_doc_count).to.equal(0); + }); + + describe('metric aggs', () => { + const emptyScriptedFields = []; + + it('adds a metric agg for each metric', () => { + config.metric = ['sum:beer', 'avg:bytes', 'percentiles:bytes']; + agg = createDateAgg(config, tlConfig, emptyScriptedFields); + expect(agg.time_buckets.aggs['sum(beer)']).to.eql({ sum: { field: 'beer' } }); + expect(agg.time_buckets.aggs['avg(bytes)']).to.eql({ avg: { field: 'bytes' } }); + expect(agg.time_buckets.aggs['percentiles(bytes)']).to.eql({ percentiles: { field: 'bytes' } }); + }); + + it('adds a scripted metric agg for each scripted metric', () => { + config.metric = ['avg:scriptedBytes']; + const scriptedFields = [{ + name: 'scriptedBytes', + script: 'doc["bytes"].value', + lang: 'painless' + }]; + agg = createDateAgg(config, tlConfig, scriptedFields); + expect(agg.time_buckets.aggs['avg(scriptedBytes)']).to.eql({ + avg: { + script: { + source: 'doc["bytes"].value', + lang: 'painless' + } + } + }); + }); + + it('has a special `count` metric that uses a script', () => { + config.metric = ['count']; + agg = createDateAgg(config, tlConfig, emptyScriptedFields); + expect(agg.time_buckets.aggs.count.bucket_script).to.be.an('object'); + expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).to.eql('_count'); + }); + }); + }); + + describe('buildRequest', () => { + const fn = buildRequest; + const emptyScriptedFields = []; + let tlConfig; + let config; + beforeEach(() => { + tlConfig = tlConfigFn(); + config = { + timefield: '@timestamp', + interval: '1y', + index: 'beer' + }; + }); + + it('sets the index on the request', () => { + config.index = 'beer'; + const request = fn(config, tlConfig, emptyScriptedFields); + + expect(request.index).to.equal('beer'); + }); + + it('always sets body.size to 0', () => { + const request = fn(config, tlConfig, emptyScriptedFields); + + expect(request.body.size).to.equal(0); + }); + + it('creates a filters agg that contains each of the queries passed', () => { + config.q = ['foo', 'bar']; + const request = fn(config, tlConfig, emptyScriptedFields); + + expect(request.body.aggs.q.meta.type).to.equal('split'); + + const filters = request.body.aggs.q.filters.filters; + expect(filters.foo.query_string.query).to.eql('foo'); + expect(filters.bar.query_string.query).to.eql('bar'); + }); + + describe('timeouts', () => { + it('sets the timeout on the request', () => { + config.index = 'beer'; + const request = fn(config, tlConfig, emptyScriptedFields, 30000); + + expect(request.timeout).to.equal('30000ms'); + }); + + it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { + config.index = 'beer'; + const request = fn(config, tlConfig, emptyScriptedFields, 0); + + expect(request).to.not.have.property('timeout'); + }); + }); + + describe('frozen indices', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('sets ignore_throttled=true on the request', () => { + config.index = 'beer'; + tlConfig.settings['search:includeFrozen'] = false; + const request = fn(config, tlConfig, emptyScriptedFields); + + expect(request.ignore_throttled).to.equal(true); + }); + + it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { + tlConfig.settings['search:includeFrozen'] = true; + config.index = 'beer'; + const request = fn(config, tlConfig, emptyScriptedFields); + + expect(request.ignore_throttled).to.equal(false); + }); + }); + + describe('query body', () => { + beforeEach(() => { + tlConfig = _.merge(tlConfigFn(), { + time: { + from: 1, + to: 5, + }, + request: { payload: { extended: { es: { filter: { + bool: { + must: [ + { query: { query_string: { query: 'foo' } } } + ], + must_not: [ + { query: { query_string: { query: 'bar' } } }, + { query: { query_string: { query: 'baz' } } } + ] + } + } } } } } + }); + }); + + it('adds the contents of payload.extended.es.filter to a filter clause of the bool', () => { + config.kibana = true; + const request = fn(config, tlConfig, emptyScriptedFields); + const filter = request.body.query.bool.filter.bool; + expect(filter.must.length).to.eql(1); + expect(filter.must_not.length).to.eql(2); + }); + + it('does not include filters if config.kibana = false', () => { + config.kibana = false; + const request = fn(config, tlConfig, emptyScriptedFields); + expect(request.body.query.bool.filter).to.eql(undefined); + }); + + it('adds a time filter to the bool querys must clause', () => { + let request = fn(config, tlConfig, emptyScriptedFields); + expect(request.body.query.bool.must.length).to.eql(1); + expect(request.body.query.bool.must[0]).to.eql({ range: { '@timestamp': { + format: 'strict_date_optional_time', + gte: '1970-01-01T00:00:00.001Z', + lte: '1970-01-01T00:00:00.005Z' + } } }); + + config.kibana = true; + request = fn(config, tlConfig, emptyScriptedFields); + expect(request.body.query.bool.must.length).to.eql(1); + }); + }); + + describe('config.split', () => { + it('adds terms aggs, in order, under the filters agg', () => { + config.split = ['beer:5', 'wine:10']; + const request = fn(config, tlConfig, emptyScriptedFields); + + const aggs = request.body.aggs.q.aggs; + + expect(aggs.beer.meta.type).to.eql('split'); + expect(aggs.beer.terms.field).to.eql('beer'); + expect(aggs.beer.terms.size).to.eql(5); + + expect(aggs.beer.aggs.wine.meta.type).to.eql('split'); + expect(aggs.beer.aggs.wine.terms.field).to.eql('wine'); + expect(aggs.beer.aggs.wine.terms.size).to.eql(10); + }); + + it('adds scripted terms aggs, in order, under the filters agg', () => { + config.split = ['scriptedBeer:5', 'scriptedWine:10']; + const scriptedFields = [ + { + name: 'scriptedBeer', + script: 'doc["beer"].value', + lang: 'painless' + }, + { + name: 'scriptedWine', + script: 'doc["wine"].value', + lang: 'painless' + } + ]; + const request = fn(config, tlConfig, scriptedFields); + + const aggs = request.body.aggs.q.aggs; + + expect(aggs.scriptedBeer.meta.type).to.eql('split'); + expect(aggs.scriptedBeer.terms.script).to.eql({ + source: 'doc["beer"].value', + lang: 'painless' + }); + expect(aggs.scriptedBeer.terms.size).to.eql(5); + + expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).to.eql('split'); + expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).to.eql({ + source: 'doc["wine"].value', + lang: 'painless' + }); + expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).to.eql(10); + }); + }); + }); + + describe('Aggregation flattening', () => { + let config; + beforeEach(() => { + config = { fit: 'nearest' }; + }); + + describe('timeBucketsToPairs', () => { + const fn = aggResponse.timeBucketsToPairs; + + it('Should convert a single metric agg', () => { + const buckets = [ + { key: 1000, count: { value: 3 } }, + { key: 2000, count: { value: 14 } }, + { key: 3000, count: { value: 15 } } + ]; + + expect(fn(buckets)).to.eql({ + count: [[1000, 3], [2000, 14], [3000, 15]] + }); + }); + + it('Should convert multiple metric aggs', () => { + const buckets = [ + { key: 1000, count: { value: 3 }, max: { value: 92 } }, + { key: 2000, count: { value: 14 }, max: { value: 65 } }, + { key: 3000, count: { value: 15 }, max: { value: 35 } } + ]; + + expect(fn(buckets)).to.eql({ + count: [[1000, 3], [2000, 14], [3000, 15]], + max: [[1000, 92], [2000, 65], [3000, 35]] + }); + }); + + it('Should convert percentiles metric aggs', () => { + const buckets = [ + { key: 1000, percentiles: { values: { '50.0': 'NaN', '75.0': 65, '95.0': 73, '99.0': 75 } } }, + { key: 2000, percentiles: { values: { '50.0': 25, '75.0': 32, '95.0': 'NaN', '99.0': 67 } } }, + { key: 3000, percentiles: { values: { '50.0': 15, '75.0': 15, '95.0': 15, '99.0': 15 } } } + ]; + + expect(fn(buckets)).to.eql({ + 'percentiles:50.0': [[1000, NaN], [2000, 25], [3000, 15]], + 'percentiles:75.0': [[1000, 65], [2000, 32], [3000, 15]], + 'percentiles:95.0': [[1000, 73], [2000, NaN], [3000, 15]], + 'percentiles:99.0': [[1000, 75], [2000, 67], [3000, 15]] + }); + }); + }); + + it('should throw an error', () => { + expect(aggResponse.default(esResponse.aggregations, config)).to.eql([ + { + data: [[1000, 264], [2000, 264]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueA > FieldB:Value2A > MetricA', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 398], [2000, 1124]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueA > FieldB:Value2A > MetricB', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 699], [2000, 110]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueA > FieldB:Value2B > MetricA', + split: 'Value2B', + type: 'series', + }, { + data: [[1000, 457], [2000, 506]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueA > FieldB:Value2B > MetricB', + split: 'Value2B', + type: 'series', + }, { + data: [[1000, 152], [2000, 518]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueB > FieldB:Value2B > MetricA', + split: 'Value2B', + type: 'series', + }, { + data: [[1000, 61], [2000, 77]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueB > FieldB:Value2B > MetricB', + split: 'Value2B', + type: 'series', + }, { + data: [[1000, 114], [2000, 264]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueB > FieldB:Value2A > MetricA', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 23], [2000, 45]], + fit: 'nearest', + label: 'q:QueryA > FieldA:ValueB > FieldB:Value2A > MetricB', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 621], [2000, 751]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueA > FieldB:Value2B > MetricA', + split: 'Value2B', + type: 'series', + }, { + data: [[1000, 12], [2000, 12]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueA > FieldB:Value2B > MetricB', + split: 'Value2B', + type: 'series', + }, { + data: [[1000, 110], [2000, 648]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueA > FieldB:Value2A > MetricA', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 11], [2000, 12]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueA > FieldB:Value2A > MetricB', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 755], [2000, 713]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueC > FieldB:Value2C > MetricA', + split: 'Value2C', + type: 'series', + }, { + data: [[1000, 10], [2000, 18]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueC > FieldB:Value2C > MetricB', + split: 'Value2C', + type: 'series', + }, { + data: [[1000, 391], [2000, 802]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueC > FieldB:Value2A > MetricA', + split: 'Value2A', + type: 'series', + }, { + data: [[1000, 4], [2000, 4]], + fit: 'nearest', + label: 'q:QueryB > FieldA:ValueC > FieldB:Value2A > MetricB', + split: 'Value2A', + type: 'series', + } + ]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js new file mode 100644 index 0000000000000..73d729631c037 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../first`); + +const expect = require('chai').expect; +const seriesList = require('./fixtures/seriesList.js')(); +import invoke from './helpers/invoke_series_fn.js'; + +describe('first.js', function () { + it('should return exactly the data input', function () { + return invoke(fn, [seriesList]).then(function (result) { + expect(result.input[0]).to.eql(result.output); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js new file mode 100644 index 0000000000000..adb3a3a7b11a8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js @@ -0,0 +1,159 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../fit`); +import moment from 'moment'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; +import getSeriesList from './helpers/get_single_series_list'; +import _ from 'lodash'; + +describe('fit.js', function () { + + describe('should not filter out zeros', function () { + it('all zeros', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 0], + [moment.utc('1981-01-01T00:00:00.000Z'), null], + [moment.utc('1982-01-01T00:00:00.000Z'), 0], + [moment.utc('1983-01-01T00:00:00.000Z'), 0], + ]); + + return invoke(fn, [seriesList, 'carry']).then(function (r) { + expect(r.input[0].list[0].data[1][1]).to.equal(null); + expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 0, 0]); + expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); + }); + }); + + it('mixed zeros and numbers', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 26], + [moment.utc('1981-01-01T00:00:00.000Z'), 42], + [moment.utc('1982-01-01T00:00:00.000Z'), 0], + [moment.utc('1983-01-01T00:00:00.000Z'), null], + [moment.utc('1984-01-01T00:00:00.000Z'), 1], + ]); + + return invoke(fn, [seriesList, 'carry']).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([26, 42, 0, 0, 1]); + }); + }); + }); + + it('should return original series when all values are null', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), null], + [moment.utc('1981-01-01T00:00:00.000Z'), null], + [moment.utc('1982-01-01T00:00:00.000Z'), null], + [moment.utc('1983-01-01T00:00:00.000Z'), null], + ]); + + return invoke(fn, [seriesList, 'carry']).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, null, null]); + }); + }); + + describe('carry', function () { + it('should maintain the previous value until it changes', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 5], + [moment.utc('1981-01-01T00:00:00.000Z'), null], + [moment.utc('1982-01-01T00:00:00.000Z'), 3.4], + [moment.utc('1983-01-01T00:00:00.000Z'), 171], + ]); + + return invoke(fn, [seriesList, 'carry']).then(function (r) { + expect(r.input[0].list[0].data[1][1]).to.equal(null); + expect(_.map(r.output.list[0].data, 1)).to.eql([5, 5, 3.4, 171]); + expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); + }); + }); + }); + + describe('nearest', function () { + it('should use the closest temporal value to fill the null', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 5], + [moment.utc('1981-01-01T00:00:00.000Z'), null], + [moment.utc('1981-05-01T00:00:00.000Z'), 3.4], + [moment.utc('1983-01-01T00:00:00.000Z'), 171], + ]); + + return invoke(fn, [seriesList, 'nearest']).then(function (r) { + expect(r.input[0].list[0].data[1][1]).to.equal(null); + expect(_.map(r.output.list[0].data, 1)).to.eql([5, 3.4, 3.4, 171]); + expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); + }); + }); + }); + + + + describe('average', function () { + it('should produce a smooth, straight line between points', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 10], + [moment.utc('1981-07-01T00:00:00.000Z'), null], + [moment.utc('1982-01-01T00:00:00.000Z'), null], + [moment.utc('1983-01-01T00:00:00.000Z'), 40], + [moment.utc('1984-01-01T00:00:00.000Z'), 50], + ]); + + return invoke(fn, [seriesList, 'average']).then(function (r) { + expect(r.input[0].list[0].data[1][1]).to.eql(null); + expect(_.map(r.output.list[0].data, 1)).to.eql([10, 20, 30, 40, 50]); + + }); + }); + }); + + describe('scale', function () { + it('should distribute the next points value across the preceeding nulls', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 10], + [moment.utc('1981-07-01T00:00:00.000Z'), null], + [moment.utc('1982-01-01T00:00:00.000Z'), null], + [moment.utc('1983-01-01T00:00:00.000Z'), 60], + [moment.utc('1984-01-01T00:00:00.000Z'), 50], + ]); + + return invoke(fn, [seriesList, 'scale']).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([10, 20, 20, 20, 50]); + }); + }); + }); + + describe('none', function () { + it('basically just drops the nulls. This is going to screw you', function () { + const seriesList = getSeriesList('', [ + [moment.utc('1980-01-01T00:00:00.000Z'), 10], + [moment.utc('1981-07-01T00:00:00.000Z'), null], + [moment.utc('1982-01-01T00:00:00.000Z'), null], + [moment.utc('1983-01-01T00:00:00.000Z'), 40], + [moment.utc('1984-01-01T00:00:00.000Z'), 50], + ]); + + return invoke(fn, [seriesList, 'none']).then(function (r) { + expect(_.map(r.output.list[0].data, 1)).to.eql([10, 40, 50]); + }); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js new file mode 100644 index 0000000000000..f51b301efb35f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; + +export default [ + moment('1980-01-01T00:00:00.000Z'), + moment('1981-01-01T00:00:00.000Z'), + moment('1982-01-01T00:00:00.000Z'), + moment('1983-01-01T00:00:00.000Z') +]; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js new file mode 100644 index 0000000000000..3b2b7189f707a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js @@ -0,0 +1,231 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* eslint-disable quotes */ + +/* + Really didn't want to do this, but testing the agg flatten logic + in units isn't really possible since the functions depend on each other + + You ValueBn test each function, but at the end you really have to check the + output of the entire thing. +*/ + +export default { + _shards: { + total: 1 + }, + aggregations: { + q: { + meta: { type: 'split' }, + buckets: { + QueryA: { + FieldA: { + meta: { type: 'split' }, + buckets: [ + { + key: 'ValueA', + FieldB: { + meta: { type: 'split' }, + buckets: [ + { + key: 'Value2A', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 264 }, + MetricB: { value: 398 } + }, + { + key: 2000, + MetricA: { value: 264 }, + MetricB: { value: 1124 } + } + ] + } + }, + { + key: 'Value2B', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 699 }, + MetricB: { value: 457 } + }, + { + key: 2000, + MetricA: { value: 110 }, + MetricB: { value: 506 } + } + ] + } + } + ] + } + }, + { + key: 'ValueB', + FieldB: { + meta: { type: 'split' }, + buckets: [ + { + key: 'Value2B', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 152 }, + MetricB: { value: 61 } + }, + { + key: 2000, + MetricA: { value: 518 }, + MetricB: { value: 77 } + } + ] + } + }, + { + key: 'Value2A', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 114 }, + MetricB: { value: 23 } + }, + { + key: 2000, + MetricA: { value: 264 }, + MetricB: { value: 45 } + } + ] + } + } + ] + } + } + ] + } + }, + QueryB: { + FieldA: { + meta: { type: 'split' }, + buckets: [ + { + key: 'ValueA', + FieldB: { + meta: { type: 'split' }, + buckets: [ + { + key: 'Value2B', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 621 }, + MetricB: { value: 12 } + }, + { + key: 2000, + MetricA: { value: 751 }, + MetricB: { value: 12 } + } + ] + } + }, + { + key: 'Value2A', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 110 }, + MetricB: { value: 11 } + }, + { + key: 2000, + MetricA: { value: 648 }, + MetricB: { value: 12 } + } + ] + } + } + ] + } + }, + { + key: 'ValueC', + FieldB: { + meta: { type: 'split' }, + buckets: [ + { + key: 'Value2C', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 755 }, + MetricB: { value: 10 } + }, + { + key: 2000, + MetricA: { value: 713 }, + MetricB: { value: 18 } + } + ] + } + }, + { + key: 'Value2A', + time_buckets: { + meta: { type: 'time_buckets' }, + buckets: [ + { + key: 1000, + MetricA: { value: 391 }, + MetricB: { value: 4 } + }, + { + key: 2000, + MetricA: { value: 802 }, + MetricB: { value: 4 } + } + ] + } + } + ] + } + } + ] + } + } + } + } + } +}; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js new file mode 100644 index 0000000000000..e3334784b4fa5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import buckets from './bucketList'; +import getSeries from '../helpers/get_series'; +import getSeriesList from '../helpers/get_series_list'; + +export default function () { + return getSeriesList([ + getSeries('Negative', buckets, [-51, 17, 82, 20]), + getSeries('Nice', buckets, [100, 50, 50, 20]), + getSeries('All the same', buckets, [1, 1, 1, 1]), + getSeries('Decimals', buckets, [3.1415926535, 2, 1.439, 0.3424235]), + getSeries('PowerOfTen', buckets, [10, 100, 10, 1]), + ]); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js new file mode 100644 index 0000000000000..87f76c82e0492 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js @@ -0,0 +1,74 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; +import { of } from 'rxjs'; +import sinon from 'sinon'; +import timelionDefaults from '../../../lib/get_namespaced_settings'; +import esResponse from './es_response'; + +export default function () { + const functions = require('../../../lib/load_functions')('series_functions'); + const kibanaServerConfigs = { + 'timelion.graphiteUrls': ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], + }; + const server = { + plugins: { + timelion: { + getFunction: (name) => { + if (!functions[name]) throw new Error ('No such function: ' + name); + return functions[name]; + } + }, + elasticsearch: { + getCluster: sinon.stub().withArgs('data').returns({ + callWithRequest: function () { + return Promise.resolve(esResponse); + } + }) + } + }, + newPlatform: { + __internals: { + elasticsearch: { + legacy: { config$: of({ shardTimeout: moment.duration(30000) }) } + } + } + }, + config: () => ({ get: (key) => kibanaServerConfigs[key] }) + }; + + const tlConfig = require('../../../handlers/lib/tl_config.js')({ + server, + request: {}, + }); + + tlConfig.time = { + interval: '1y', + from: moment('1980-01-01T00:00:00Z').valueOf(), + to: moment('1983-01-01T00:00:00Z').valueOf(), + timezone: 'Etc/UTC' + }; + + tlConfig.settings = timelionDefaults(); + + tlConfig.setTargetSeries(); + + return tlConfig; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js new file mode 100644 index 0000000000000..8d5e25b43938d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import proxyquire from 'proxyquire'; +import Bluebird from 'bluebird'; +const expect = require('chai').expect; + +const graphiteResponse = function () { + return Bluebird.resolve({ + json: function () { + return [{ + target: '__beer__', + datapoints: [ + [3, 1000], + [14, 2000], + [1.5, 3000], + [92.6535, 4000], + ] + }]; + } + }); +}; + +const filename = require('path').basename(__filename); +const fn = proxyquire(`../${filename}`, { 'node-fetch': graphiteResponse }); + +import invoke from './helpers/invoke_series_fn.js'; + +describe(filename, function () { + it('should wrap the graphite response up in a seriesList', function () { + return invoke(fn, []).then(function (result) { + expect(result.output.list[0].data[0][1]).to.eql(3); + expect(result.output.list[0].data[1][1]).to.eql(14); + }); + }); + + it('should convert the seconds to milliseconds', function () { + return invoke(fn, []).then(function (result) { + expect(result.output.list[0].data[1][0]).to.eql(2000 * 1000); + }); + }); + + it('should set the label to that of the graphite target', function () { + return invoke(fn, []).then(function (result) { + expect(result.output.list[0].label).to.eql('__beer__'); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js new file mode 100644 index 0000000000000..195606b70c86b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function getSeries(name, buckets, points) { + const fill = _.partial(_.zip, _.map(buckets, function (bucket) { return bucket.valueOf(); })); + return { + data: fill(points), + type: 'series', + label: name + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js new file mode 100644 index 0000000000000..5ae32167c6507 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (list, overrides) { + return _.merge({ + type: 'seriesList', + list: list + }, overrides); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js new file mode 100644 index 0000000000000..27e2f85821d88 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import getSeries from '../helpers/get_series'; +import getSeriesList from '../helpers/get_series_list'; +import _ from 'lodash'; + +export default function (name, data) { + return getSeriesList([getSeries(name, _.map(data, 0), _.map(data, 1))]); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js new file mode 100644 index 0000000000000..642b7a7297ee2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// invokes a series_function with the specified arguments +import _ from 'lodash'; + +import indexArguments from '../../../handlers/lib/index_arguments'; + +export default function invokeSeriesFn(fnDef, args, tlConfigOverrides) { + const tlConfig = _.merge(require('../fixtures/tlConfig')(), tlConfigOverrides); + + return Promise.all(args).then(function (args) { + args.byName = indexArguments(fnDef, args); + + const input = _.cloneDeep(args); + + return Promise.resolve(fnDef.originalFn(args, tlConfig)).then(function (output) { + + const result = { + output: output, + input: input + }; + return result; + }); + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js new file mode 100644 index 0000000000000..ed7df1fd5f215 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../hide`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('hide.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('hides a series', () => { + return invoke(fn, [seriesList, true]).then((r) => { + _.each(r.output.list, (series) => expect(series._hide).to.equal(true)); + }); + }); + + it('unhides a series', () => { + return invoke(fn, [seriesList, false]).then((r) => { + _.each(r.output.list, (series) => expect(series._hide).to.equal(false)); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js new file mode 100644 index 0000000000000..c89aa1d5197c6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../label`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('label.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('changes the label on the series', () => { + return invoke(fn, [seriesList, 'free beer']).then((r) => { + _.each(r.output.list, (series) => expect(series.label).to.equal('free beer')); + }); + }); + + it('can use a regex to capture parts of a series label', () => { + return invoke(fn, [seriesList, 'beer$1', 'Neg(.*)']).then((r) => { + expect(r.output.list[0].label).to.equal('beerative'); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js new file mode 100644 index 0000000000000..f85b2cefffd73 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../legend`); + +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('legend.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('should create the _global object if it does not exist', () => { + expect(seriesList.list[0]._global).to.equal(undefined); + return invoke(fn, [seriesList, 'nw', 3, true, 'YYYY']).then((r) => { + expect(r.output.list[0]._global).to.eql({ legend: { noColumns: 3, position: 'nw', showTime: true, timeFormat: 'YYYY' } }); + }); + }); + + it('should provide default values for time axis display', () => { + return invoke(fn, [seriesList, 'nw', 3]).then((r) => { + expect(r.output.list[0]._global.legend.showTime).to.equal(true); + expect(r.output.list[0]._global.legend.timeFormat).to.equal('MMMM Do YYYY, HH:mm:ss.SSS'); + }); + }); + + it('should hide the legend is position is false', () => { + return invoke(fn, [seriesList, false]).then((r) => { + expect(r.output.list[0]._global.legend.show).to.equal(false); + expect(r.output.list[0]._global.legend.showTime).to.equal(false); + }); + }); + + it('should set legend.showTime to false when showTime parameter is false', () => { + return invoke(fn, [seriesList, 'nw', 3, false]).then((r) => { + expect(r.output.list[0]._global.legend.showTime).to.equal(false); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js new file mode 100644 index 0000000000000..83cd066b6c7e4 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../lines`); + +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('lines.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('should simply set show, steps, stack and lineWidth', () => { + expect(seriesList.list[0]._global).to.equal(undefined); + return invoke(fn, [seriesList, 1, 2, true, true, false]).then((r) => { + expect(r.output.list[0].lines.lineWidth).to.equal(1); + expect(r.output.list[0].lines.show).to.equal(true); + expect(r.output.list[0].stack).to.equal(true); + expect(r.output.list[0].lines.steps).to.equal(false); + }); + }); + + it('should set lineWidth to 3 by default, and nothing else', () => { + expect(seriesList.list[0]._global).to.equal(undefined); + return invoke(fn, [seriesList]).then((r) => { + expect(r.output.list[0].lines.lineWidth).to.equal(3); + expect(r.output.list[0].lines.fill).to.equal(undefined); + expect(r.output.list[0].lines.show).to.equal(undefined); + expect(r.output.list[0].stack).to.equal(undefined); + expect(r.output.list[0].lines.steps).to.equal(undefined); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js new file mode 100644 index 0000000000000..41ccb5671649c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../log`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('log.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('should return the log10 value of every value', () => { + return invoke(fn, [seriesList]).then((r) => { + expect(_.map(r.output.list[4].data, 1)).to.eql([1, 2, 1, 0]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js new file mode 100644 index 0000000000000..a5ed446cdba5e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../max`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('max.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('keeps the max of a series vs a number', () => { + return invoke(fn, [seriesList, 20]).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([20, 20, 82, 20]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js new file mode 100644 index 0000000000000..e3f29805787e5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../min`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('min.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('keeps the min of a series vs a number', () => { + return invoke(fn, [seriesList, 20]).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 17, 20, 20]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js new file mode 100644 index 0000000000000..a502e23b9cddf --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../movingaverage`); +const expect = require('chai').expect; + +import moment from 'moment'; +import _ from 'lodash'; +import buckets from './fixtures/bucketList'; +import getSeries from './helpers/get_series'; +import getSeriesList from './helpers/get_series_list'; +import invoke from './helpers/invoke_series_fn.js'; + +function getFivePointSeries() { + return getSeriesList([ + getSeries('Five', [].concat(buckets).push(moment('1984-01-01T00:00:00.000Z')), [10, 20, 30, 40, 50]), + ]); +} + +describe('movingaverage.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = getFivePointSeries(); + }); + + it('centers the averaged series by default', () => { + return invoke(fn, [seriesList, 3]).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([null, 20, 30, 40, null]); + }); + }); + + + it('aligns the moving average to the left', () => { + return invoke(fn, [seriesList, 3, 'left']).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, 20, 30, 40]); + }); + }); + + it('aligns the moving average to the right', () => { + return invoke(fn, [seriesList, 3, 'right']).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([20, 30, 40, null, null]); + }); + }); + + describe('date math', () => { + it('accepts 2 years', () => { + return invoke(fn, [seriesList, '2y', 'left']).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([null, 15, 25, 35, 45]); + }); + }); + + it('accepts 3 years', () => { + return invoke(fn, [seriesList, '3y', 'left']).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, 20, 30, 40]); + }); + }); + }); + + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js new file mode 100644 index 0000000000000..e7470ecd0888f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../movingstd`); + +import moment from 'moment'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; +import getSeries from './helpers/get_series'; +import getSeriesList from './helpers/get_series_list'; + +describe('movingstd.js', () => { + + it('computes the moving standard deviation of a list', async () => { + const points = [ + 108.48, 111.56, 112.13, 113.75, 114.25, 110.79, 111.21, 116.82, 117.16, 120.38, 116.96, + 119.56, 118.97, 117.54, 114.42, 111.01, 114.20, 116.43, 117.74, 119.90, 124.65, 124.98, + 124.70, 123.60, 124.5, 126.85]; + const buckets = []; + buckets[0] = moment('2018-01-01T00:00:00.000Z'); + for (let i = 1; i < points.length; i++) { + buckets[i] = buckets[i - 1].add(1, 'hours'); + } + const series = getSeries('test data', buckets, points); + const seriesList = getSeriesList([series]); + const numWindows = 5; + const position = 'left'; + const results = await invoke(fn, [seriesList, numWindows, position]); + + const resultPoints = results.output.list[0].data.map((row) => { + // row is an array; index 0 is the time bucket, index 1 is the value + return row[1]; + }); + // First 5 result buckets are null since moving window is filling up. + const trimmedResultPoints = resultPoints.slice(numWindows); + + const expectedPoints = [ + 2.28, 1.46, 1.53, 2.46, 3.00, 4.14, 3.31, 1.67, 1.50, 1.41, + 2.01, 3.56, 3.12, 2.50, 2.56, 3.41, 3.97, 3.92, 3.35, 2.12, 0.52]; + + expectedPoints.forEach((value, index) => { + expect(trimmedResultPoints[index]).to.be.within(value - 0.01, value + 0.01); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js new file mode 100644 index 0000000000000..025ed81da1cf1 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../multiply`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('multiply.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('multiplies by a number', () => { + return invoke(fn, [seriesList, 2]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([200, 100, 100, 40]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js new file mode 100644 index 0000000000000..1ab86348620dd --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../points`); + +import _ from 'lodash'; +import assert from 'chai'; +const expect = assert.expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('points.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('should set the point radius', () => { + return invoke(fn, [seriesList, 1]).then((r) => { + expect(r.output.list[0].points.radius).to.equal(1); + }); + }); + + it('should set the point lineWidth', () => { + return invoke(fn, [seriesList, null, 3]).then((r) => { + expect(r.output.list[0].points.lineWidth).to.equal(3); + }); + }); + + it('should set the point fill', () => { + return invoke(fn, [seriesList, null, null, 3]).then((r) => { + expect(r.output.list[0].points.fill).to.equal(3 / 10); + }); + }); + + it('should not set the fill color if fill is not specified', () => { + return invoke(fn, [seriesList, null, null, null, '#333']).then((r) => { + expect(r.output.list[0].points.fillColor).to.equal(undefined); + }); + }); + + it('should set the fill color ', () => { + return invoke(fn, [seriesList, null, null, 10, '#333']).then((r) => { + expect(r.output.list[0].points.fillColor).to.equal('#333'); + }); + }); + + describe('symbol', () => { + const symbols = ['triangle', 'cross', 'square', 'diamond', 'circle']; + _.each(symbols, (symbol) => { + it(`is ${symbol}`, () => { + return invoke(fn, [seriesList, null, null, null, null, symbol]).then((r) => { + expect(r.output.list[0].points.symbol).to.equal(symbol); + }); + }); + }); + + it('does not allow undefined symbols', () => { + return invoke(fn, [seriesList, null, null, null, null, 'beer']) + .then(expect.fail) + .catch((e) => { + expect(e).to.be.an('error'); + }); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js new file mode 100644 index 0000000000000..68de2eb04a09d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../precision`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('precision.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('keeps the min of a series vs a number', () => { + return invoke(fn, [seriesList, 2]).then((r) => { + expect(_.map(r.output.list[3].data, 1)).to.eql([3.14, 2, 1.43, 0.34]); + }); + }); + + + it('Adds a _meta to describe the precision to display', () => { + return invoke(fn, [seriesList, 2]).then((r) => { + expect(r.output.list[3]._meta.precision).to.eql(2); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js new file mode 100644 index 0000000000000..4fcc8fadcd013 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js @@ -0,0 +1,100 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import proxyquire from 'proxyquire'; +import Bluebird from 'bluebird'; +import assert from 'chai'; +const expect = assert.expect; + +const parseURL = require('url').parse; +const parseQueryString = require('querystring').parse; +const tlConfig = require('./fixtures/tlConfig')(); +import moment from 'moment'; + +const filename = require('path').basename(__filename); +import invoke from './helpers/invoke_series_fn.js'; + +let fn; +let response; +let calledWith; +describe(filename, function () { + + beforeEach(function () { + response = function (url) { + calledWith = { + params: parseQueryString(parseURL(url).query), + code: url.match(/datasets\/(.*).json/)[1] + }; + return Bluebird.resolve({ + json: function () { + return { + name: '__beer__', + data: [ + ['2015-01-01', 3], + ['2015-01-02', 14], + ['2015-01-03', 15.92], + ['2015-01-04', 65.35], + ] + }; + } + }); + }; + fn = proxyquire(`../${filename}`, { 'node-fetch': response }); + }); + + it('should wrap the quandl response up in a seriesList', function () { + return invoke(fn, []).then(function (result) { + expect(result.output.list[0].data[0][1]).to.eql(3); + expect(result.output.list[0].data[1][1]).to.eql(14); + }); + }); + + it('should set the label to that of the quandl name', function () { + return invoke(fn, []).then(function (result) { + expect(result.output.list[0].label).to.eql('__beer__'); + }); + }); + + it('should call the quandl API with the quandl code that has been passed', function () { + return invoke(fn, ['BEER/IS_GOOD']).then(function () { + expect(calledWith.code).to.eql('BEER/IS_GOOD'); + }); + }); + + it('should limit the time span and interval to the stuff attached to tlConfig', function () { + return invoke(fn, []).then(function () { + expect(calledWith.params.trim_start).to.eql(moment.utc(tlConfig.time.from).format('YYYY-MM-DD')); + expect(calledWith.params.trim_end).to.eql(moment.utc(tlConfig.time.to).format('YYYY-MM-DD')); + }); + }); + + it('should throw an error is passed an unsupported interval', function () { + return invoke(fn, [], { time: { interval: '2d' } }) + .then(expect.fail) + .catch(function (r) { + expect(r).to.be.an('error'); + }); + }); + + it('should use the configured API key when talking to quandl', function () { + return invoke(fn, [], { settings: { 'timelion:quandl.key': 'bEeR' } }).then(function () { + expect(calledWith.params.auth_token).to.eql('bEeR'); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js new file mode 100644 index 0000000000000..e98d8a527e693 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../range`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('range.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + seriesList.list[0].data = [[1000, 20], [2000, 10], [3000, 30], [4000, 40]]; + }); + + it('keeps the min of a series vs a number', () => { + return invoke(fn, [seriesList, 1, 4]).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([2, 1, 3, 4]); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js new file mode 100644 index 0000000000000..b9c92c164170a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../scale_interval`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('scale_interval.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('Can multiply to transform one interval to another', () => { + return invoke(fn, [seriesList, '5y']).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([500, 250, 250, 100]); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js new file mode 100644 index 0000000000000..4e5500d55a4a9 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../static`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('static.js', () => { + it('returns a series in which all numbers are the same', () => { + return invoke(fn, [5]).then((r) => { + expect(_.unique(_.map(r.output.list[0].data, 1))).to.eql([5]); + }); + }); + + it('plots a provided series', () => { + return invoke(fn, ['4:3:2:1']).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([4, 3, 2, 1]); + }); + }); + + it('leaves interpolation up to the data source wrapper', () => { + return invoke(fn, ['1:4']).then((r) => { + expect(_.map(r.output.list[0].data, 1)).to.eql([1, 4]); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js new file mode 100644 index 0000000000000..f34c46e7834f8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../subtract`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('subtract.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('it throws an error if first argument is not seriesList', async () => { + const notSeriesList = [1, 2, 3, 4]; + try { + await invoke(fn, [notSeriesList]); + expect.fail(); + } catch (e) { + expect(e.message).to.eql('input must be a seriesList'); + } + }); + + it('it subtracts all series in seriesList to single series when only one argument is supplied', async () => { + const outputSeries = await invoke(fn, [seriesList]); + expect(outputSeries.output.list.length).to.eql(1); + expect(_.map(outputSeries.output.list[0].data, 1)).to.eql([-165.1415926535, -136, 19.561, -2.3424234999999998]); + }); + + it('it subtracts a number', async () => { + const outputSeries = await invoke(fn, [seriesList, 2]); + expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([98, 48, 48, 18]); + }); + + it('it subtracts an array of numbers', async () => { + const outputSeries = await invoke(fn, [seriesList, [5, 10, 15]]); + expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([70, 20, 20, -10]); + }); + + it('it subtracts a seriesList with one series', async () => { + const seriesListWithOneSeries = { + type: 'seriesList', + list: [ + _.cloneDeep(seriesList.list[1]) + ] + }; + const outputSeries = await invoke(fn, [seriesList, seriesListWithOneSeries]); + expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([0, 0, 0, 0]); + }); + + it('it subtracts a seriesList with multiple series', async () => { + const outputSeries = await invoke(fn, [seriesList, seriesList]); + expect(_.map(outputSeries.output.list[0].data, 1)).to.eql([0, 0, 0, 0]); + expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([0, 0, 0, 0]); + expect(_.map(outputSeries.output.list[2].data, 1)).to.eql([0, 0, 0, 0]); + expect(_.map(outputSeries.output.list[3].data, 1)).to.eql([0, 0, 0, 0]); + expect(_.map(outputSeries.output.list[4].data, 1)).to.eql([0, 0, 0, 0]); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js new file mode 100644 index 0000000000000..488301132ccf5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../sum`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('sum.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('it adds a number', () => { + return invoke(fn, [seriesList, 2]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([102, 52, 52, 22]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js new file mode 100644 index 0000000000000..5fac9f8a2783c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../title`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('title.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('sets the title property', () => { + return invoke(fn, [seriesList, 'beer']).then((r) => { + _.each(r.output.list, (series) => expect(series._title).to.equal('beer')); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js new file mode 100644 index 0000000000000..183e22fc49a0b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../trim`); + +import _ from 'lodash'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('trim.js', () => { + + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('Sets the first and last values to null by default', () => { + return invoke(fn, [seriesList]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, 50, null]); + }); + }); + + it('Trims more from the beginning', () => { + return invoke(fn, [seriesList, 2]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([null, null, 50, null]); + }); + }); + + it('Trims more from the end', () => { + return invoke(fn, [seriesList, null, 2]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, null, null]); + }); + }); + + it('Trims nothing from the end', () => { + return invoke(fn, [seriesList, 1, 0]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, 50, 20]); + }); + }); + + it('Trims nothing from the beginning', () => { + return invoke(fn, [seriesList, 0, 2]).then((r) => { + expect(_.map(r.output.list[1].data, 1)).to.eql([100, 50, null, null]); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js new file mode 100644 index 0000000000000..2aa4b9a471c48 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js @@ -0,0 +1,111 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fn = require(`../yaxis`); +import Bluebird from 'bluebird'; +const expect = require('chai').expect; +import invoke from './helpers/invoke_series_fn.js'; + +describe('yaxis.js', () => { + let seriesList; + beforeEach(() => { + seriesList = require('./fixtures/seriesList.js')(); + }); + + it('creates the yaxes array', () => { + expect(seriesList._global).to.equal(undefined); + return invoke(fn, [seriesList, 2]).then((r) => { + expect(r.output.list[0]._global.yaxes).to.be.an('array'); + }); + }); + + it('puts odd numbers of the left, even on the right, by default', () => { + return Bluebird.all([ + invoke(fn, [seriesList, 1]).then((r) => { + expect(r.output.list[0]._global.yaxes[0].position).to.equal('left'); + }), + invoke(fn, [seriesList, 2]).then((r) => { + expect(r.output.list[0]._global.yaxes[1].position).to.equal('right'); + }), + invoke(fn, [seriesList, 3]).then((r) => { + expect(r.output.list[0]._global.yaxes[2].position).to.equal('left'); + }), + ]); + }); + + it('it lets you override default positions', () => { + return Bluebird.all([ + invoke(fn, [seriesList, 1, null, null, 'right']).then((r) => { + expect(r.output.list[0]._global.yaxes[0].position).to.equal('right'); + }), + invoke(fn, [seriesList, 2, null, null, 'right']).then((r) => { + expect(r.output.list[0]._global.yaxes[1].position).to.equal('right'); + }), + ]); + }); + + it('sets the minimum (default: no min)', () => { + return Bluebird.all([ + invoke(fn, [seriesList, 1, null]).then((r) => { + expect(r.output.list[0]._global.yaxes[0].min).to.equal(null); + }), + invoke(fn, [seriesList, 2, 10]).then((r) => { + expect(r.output.list[0]._global.yaxes[1].min).to.equal(10); + }), + ]); + }); + + it('sets the max (default: no max)', () => { + return Bluebird.all([ + invoke(fn, [seriesList, 1, null]).then((r) => { + expect(r.output.list[0]._global.yaxes[0].max).to.equal(undefined); + }), + invoke(fn, [seriesList, 2, null, 10]).then((r) => { + expect(r.output.list[0]._global.yaxes[1].max).to.equal(10); + }), + ]); + }); + + it('sets the units (default: no unit', () => { + return Bluebird.all([ + invoke(fn, [seriesList, 1, null, null, null, null, null, null]).then((r) => { + expect(r.output.list[0]._global.yaxes[0].units).to.equal(undefined); + }), + invoke(fn, [seriesList, 2, null, null, null, null, null, 'bits']).then((r) => { + expect(r.output.list[0]._global.yaxes[1].units).to.be.an('object'); + }), + ]); + }); + + it('throws an error if currency is not three letter code', () => { + invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:abcde']).catch(e => { + expect(e).to.be.an.instanceof(Error); + }); + invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:12']).catch(e => { + expect(e).to.be.an.instanceof(Error); + }); + invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:$#']).catch(e => { + expect(e).to.be.an.instanceof(Error); + }); + invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:ab']).catch(e => { + expect(e).to.be.an.instanceof(Error); + }); + }); + +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js new file mode 100644 index 0000000000000..a7c5a8eb88a53 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('abs', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + } + ], + help: i18n.translate('timelion.help.functions.absHelpText', { + defaultMessage: 'Return the absolute value of each value in the series list', + }), + fn: function absFn(args) { + return alter(args, function (eachSeries) { + const data = _.map(eachSeries.data, function (point) { + return [point[0], Math.abs(point[1])]; + }); + eachSeries.data = data; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js new file mode 100644 index 0000000000000..853ce337efac2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.sum(points) / points.length; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js new file mode 100644 index 0000000000000..807e620ee941b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.uniq(points).length; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js new file mode 100644 index 0000000000000..05e560b84335f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.first(points); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js new file mode 100644 index 0000000000000..275a5d9bd7f9d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../../lib/alter.js'; +import Chainable from '../../lib/classes/chainable'; +import _ from 'lodash'; + +const functions = { + avg: require('./avg'), + cardinality: require('./cardinality'), + min: require('./min'), + max: require('./max'), + last: require('./last'), + first: require('./first'), + sum: require('./sum') +}; + +export default new Chainable('aggregate', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'function', + types: ['string'], + help: i18n.translate('timelion.help.functions.aggregate.args.functionHelpText', { + defaultMessage: 'One of {functions}', + values: { + functions: _.keys(functions).join(', '), + }, + }), + } + ], + help: i18n.translate('timelion.help.functions.aggregateHelpText', { + defaultMessage: + 'Creates a static line based on result of processing all points in the series. Available functions: {functions}', + values: { + functions: _.keys(functions).join(', '), + }, + }), + fn: function aggregateFn(args) { + const fn = functions[args.byName.function]; + if (!fn) throw new Error('.aggregate() function must be one of: ' + _.keys(functions).join(', ')); + + return alter(args, function (eachSeries) { + const times = _.map(eachSeries.data, 0); + const values = _.map(eachSeries.data, 1); + + eachSeries.data = _.zip(times, _.fill(values, fn(values))); + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js new file mode 100644 index 0000000000000..8f252fb451027 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.last(points); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js new file mode 100644 index 0000000000000..5b2610f07bbea --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.max(points); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js new file mode 100644 index 0000000000000..1ebf9be2f9146 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.min(points); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js new file mode 100644 index 0000000000000..34b5fe277dd34 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export default function (points) { + return _.sum(points); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js new file mode 100644 index 0000000000000..161afdf1050c4 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('bars', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'width', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.bars.args.widthHelpText', { + defaultMessage: 'Width of bars in pixels', + }), + }, + { + name: 'stack', + types: ['boolean', 'null'], + help: i18n.translate('timelion.help.functions.bars.args.stackHelpText', { + defaultMessage: 'Should bars be stacked, true by default', + }), + } + ], + help: i18n.translate('timelion.help.functions.barsHelpText', { + defaultMessage: 'Show the seriesList as bars', + }), + fn: function barsFn(args) { + return alter(args, function (eachSeries, width, stack) { + eachSeries.bars = eachSeries.bars || {}; + eachSeries.bars.show = width == null ? 1 : width; + eachSeries.bars.lineWidth = width == null ? 6 : width; + eachSeries.stack = stack == null ? true : stack; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js new file mode 100644 index 0000000000000..fa13a4463db36 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js @@ -0,0 +1,74 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; +import tinygradient from 'tinygradient'; + +export default new Chainable('color', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'color', + types: ['string'], + help: i18n.translate('timelion.help.functions.color.args.colorHelpText', { + defaultMessage: + 'Color of series, as hex, e.g., #c6c6c6 is a lovely light grey. If you specify multiple \ +colors, and have multiple series, you will get a gradient, e.g., "#00B1CC:#00FF94:#FF3A39:#CC1A6F"', + }), + } + ], + help: i18n.translate('timelion.help.functions.colorHelpText', { + defaultMessage: 'Change the color of the series', + }), + fn: function colorFn(args) { + const colors = args.byName.color.split(':'); + const gradientStops = args.byName.inputSeries.list.length; + let gradient; + if (colors.length > 1 && gradientStops > 1) { + // trim number of colors to avoid exception thrown by having more colors than gradient stops + let trimmedColors = colors; + if (colors.length > gradientStops) { + trimmedColors = colors.slice(0, gradientStops); + } + gradient = tinygradient(trimmedColors).rgb(gradientStops); + } + + let i = 0; + return alter(args, function (eachSeries) { + if (gradient) { + eachSeries.color = gradient[i++].toHexString(); + } else if (colors.length === 1 || gradientStops === 1) { + eachSeries.color = colors[0]; + } else { + throw new Error( + i18n.translate('timelion.serverSideErrors.colorFunction.colorNotProvidedErrorMessage', { + defaultMessage: 'color not provided', + }) + ); + } + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js new file mode 100644 index 0000000000000..5a52f8802f138 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js @@ -0,0 +1,158 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; +import argType from '../handlers/lib/arg_type.js'; + +export default new Chainable('condition', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'operator', // <, <=, >, >=, ==, != + types: ['string'], + help: i18n.translate('timelion.help.functions.condition.args.operatorHelpText', { + defaultMessage: + 'comparison operator to use for comparison, valid operators are eq (equal), ' + + 'ne (not equal), lt (less than), lte (less than equal), gt (greater than), gte (greater than equal)', + }), + suggestions: [ + { + name: 'eq', + help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.eqHelpText', { + defaultMessage: 'equal', + }), + }, + { + name: 'ne', + help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.neHelpText', { + defaultMessage: 'not equal', + }), + }, + { + name: 'lt', + help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.ltHelpText', { + defaultMessage: 'less than', + }), + }, + { + name: 'lte', + help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.lteHelpText', { + defaultMessage: 'less than equal', + }), + }, + { + name: 'gt', + help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.gtHelpText', { + defaultMessage: 'greater than', + }), + }, + { + name: 'gte', + help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.gteHelpText', { + defaultMessage: 'greater than equal', + }), + }, + ], + }, + { + name: 'if', + types: ['number', 'seriesList', 'null'], + help: i18n.translate('timelion.help.functions.condition.args.ifHelpText', { + defaultMessage: + 'The value to which the point will be compared. If you pass a seriesList here the first series will be used', + }), + }, + { + name: 'then', + types: ['number', 'seriesList', 'null'], + help: i18n.translate('timelion.help.functions.condition.args.thenHelpText', { + defaultMessage: + 'The value the point will be set to if the comparison is true. If you pass a seriesList here the first series will be used', + }), + }, + { + name: 'else', + types: ['number', 'seriesList', 'null'], + help: i18n.translate('timelion.help.functions.condition.args.elseHelpText', { + defaultMessage: + 'The value the point will be set to if the comparison is false. If you pass a seriesList here the first series will be used', + }), + } + ], + help: i18n.translate('timelion.help.functions.conditionHelpText', { + defaultMessage: + 'Compares each point to a number, or the same point in another series using an operator, ' + + 'then sets its value to the result if the condition proves true, with an optional else.', + }), + aliases: ['if'], + fn: function conditionFn(args) { + const config = args.byName; + return alter(args, function (eachSeries) { + const data = _.map(eachSeries.data, function (point, i) { + function getNumber(source) { + if (argType(source) === 'number') return source; + if (argType(source) === 'null') return null; + if (argType(source) === 'seriesList') return source.list[0].data[i][1]; + throw new Error( + i18n.translate('timelion.serverSideErrors.conditionFunction.wrongArgTypeErrorMessage', { + defaultMessage: 'must be a number or a seriesList', + }) + ); + } + + const ifVal = getNumber(config.if); + const thenVal = getNumber(config.then); + const elseVal = _.isUndefined(config.else) ? point[1] : getNumber(config.else); + + const newValue = (function () { + switch (config.operator) { + case 'lt': + return point[1] < ifVal ? thenVal : elseVal; + case 'lte': + return point[1] <= ifVal ? thenVal : elseVal; + case 'gt': + return point[1] > ifVal ? thenVal : elseVal; + case 'gte': + return point[1] >= ifVal ? thenVal : elseVal; + case 'eq': + return point[1] === ifVal ? thenVal : elseVal; + case 'ne': + return point[1] !== ifVal ? thenVal : elseVal; + default: + throw new Error( + i18n.translate('timelion.serverSideErrors.conditionFunction.unknownOperatorErrorMessage', { + defaultMessage: 'Unknown operator', + }) + ); + } + }()); + + return [point[0], newValue]; + }); + eachSeries.data = data; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js new file mode 100644 index 0000000000000..39ce367a2592c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('cusum', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'base', + types: ['number'], + help: i18n.translate('timelion.help.functions.cusum.args.baseHelpText', { + defaultMessage: 'Number to start at. Basically just adds this to the beginning of the series', + }), + } + ], + help: i18n.translate('timelion.help.functions.cusumHelpText', { + defaultMessage: 'Return the cumulative sum of a series, starting at a base.', + }), + fn: function cusumFn(args) { + return alter(args, function (eachSeries, base) { + const pairs = eachSeries.data; + let total = base || 0; + eachSeries.data = _.map(pairs, function (point) { + total += point[1]; + return [point[0], total]; + }); + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js new file mode 100644 index 0000000000000..e19b9d801fe42 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('derivative', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + } + ], + help: i18n.translate('timelion.help.functions.derivativeHelpText', { + defaultMessage: 'Plot the change in values over time.', + }), + fn: function derivativeFn(args) { + return alter(args, function (eachSeries) { + const pairs = eachSeries.data; + eachSeries.data = _.map(pairs, function (point, i) { + if (i === 0 || pairs[i - 1][1] == null || point[1] == null) { return [point[0], null]; } + return [point[0], point[1] - pairs[i - 1][1]]; + }); + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js new file mode 100644 index 0000000000000..1448d31005632 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('divide', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'divisor', + types: ['seriesList', 'number'], + help: i18n.translate('timelion.help.functions.divide.args.divisorHelpText', { + defaultMessage: + 'Number or series to divide by. SeriesList with multiple series will be applied label-wise.', + }), + } + ], + help: i18n.translate('timelion.help.functions.divideHelpText', { + defaultMessage: + 'Divides the values of one or more series in a seriesList to each position, in each series, of the input seriesList', + }), + fn: function divideFn(args) { + return reduce(args, function (a, b) { + return a / b; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js new file mode 100644 index 0000000000000..3ca3a745aee1d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js @@ -0,0 +1,154 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { first, map } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import Datasource from '../../lib/classes/datasource'; +import buildRequest from './lib/build_request'; +import toSeriesList from './lib/agg_response_to_series_list'; + +export default new Datasource('es', { + args: [ + { + name: 'q', + types: ['string', 'null'], + multi: true, + help: i18n.translate('timelion.help.functions.es.args.qHelpText', { + defaultMessage: 'Query in lucene query string syntax', + }), + }, + { + name: 'metric', + types: ['string', 'null'], + multi: true, + help: i18n.translate('timelion.help.functions.es.args.metricHelpText', { + defaultMessage: + 'An elasticsearch metric agg: avg, sum, min, max, percentiles or cardinality, followed by a field. ' + + 'E.g., "sum:bytes", "percentiles:bytes:95,99,99.9" or just "count"', + description: + `avg, sum, min, max, percentiles and cardinality are keywords in the expression ` + + `and must not be translated. Also don't translate the examples.`, + }), + }, + { + name: 'split', + types: ['string', 'null'], + multi: true, + help: i18n.translate('timelion.help.functions.es.args.splitHelpText', { + defaultMessage: + 'An elasticsearch field to split the series on and a limit. E.g., "{hostnameSplitArg}" to get the top 10 hostnames', + values: { + hostnameSplitArg: 'hostname:10', + }, + }), + }, + { + name: 'index', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.es.args.indexHelpText', { + defaultMessage: + 'Index to query, wildcards accepted. Provide Index Pattern name for scripted fields and ' + + 'field name type ahead suggestions for metrics, split, and timefield arguments.', + description: '"metrics", "split" and "timefield" are referring to parameter names and should not be translated.', + }), + }, + { + name: 'timefield', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.es.args.timefieldHelpText', { + defaultMessage: 'Field of type "date" to use for x-axis', + description: '"date" is a field type and should not be translated.', + }), + }, + { + name: 'kibana', + types: ['boolean', 'null'], + help: i18n.translate('timelion.help.functions.es.args.kibanaHelpText', { + defaultMessage: + 'Respect filters on Kibana dashboards. Only has an effect when using on Kibana dashboards', + }), + }, + { + name: 'interval', // You really shouldn't use this, use the interval picker instead + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.es.args.intervalHelpText', { + defaultMessage: + `**DO NOT USE THIS**. It's fun for debugging fit functions, but you really should use the interval picker`, + }), + } + ], + help: i18n.translate('timelion.help.functions.esHelpText', { + defaultMessage: 'Pull data from an elasticsearch instance', + }), + aliases: ['elasticsearch'], + fn: async function esFn(args, tlConfig) { + + const config = _.defaults(_.clone(args.byName), { + q: '*', + metric: ['count'], + index: tlConfig.settings['timelion:es.default_index'], + timefield: tlConfig.settings['timelion:es.timefield'], + interval: tlConfig.time.interval, + kibana: true, + fit: 'nearest' + }); + + const findResp = await tlConfig.request.getSavedObjectsClient().find({ + type: 'index-pattern', + fields: ['title', 'fields'], + search: `"${config.index}"`, + search_fields: ['title'] + }); + const indexPatternSavedObject = findResp.saved_objects.find(savedObject => { + return savedObject.attributes.title === config.index; + }); + let scriptedFields = []; + if (indexPatternSavedObject) { + const fields = JSON.parse(indexPatternSavedObject.attributes.fields); + scriptedFields = fields.filter(field => { + return field.scripted; + }); + } + + const esShardTimeout = await tlConfig.server.newPlatform.__internals.elasticsearch.legacy.config$.pipe( + first(), + map(config => config.shardTimeout.asMilliseconds()) + ).toPromise(); + + const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); + + const { callWithRequest } = tlConfig.server.plugins.elasticsearch.getCluster('data'); + const resp = await callWithRequest(tlConfig.request, 'search', body); + if (!resp._shards.total) { + throw new Error( + i18n.translate('timelion.serverSideErrors.esFunction.indexNotFoundErrorMessage', { + defaultMessage: 'Elasticsearch index not found: {index}', + values: { + index: config.index, + }, + }), + ); + } + return { + type: 'seriesList', + list: toSeriesList(resp.aggregations, config) + }; + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js new file mode 100644 index 0000000000000..331337a1ed491 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + +export function buildAggBody(fieldName, scriptedFields) { + + const scriptedField = scriptedFields.find(field => { + return field.name === fieldName; + }); + + if (scriptedField) { + return { + script: { + source: scriptedField.script, + lang: scriptedField.lang + } + }; + } + + return { + field: fieldName + }; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js new file mode 100644 index 0000000000000..77c351d924012 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +export function timeBucketsToPairs(buckets) { + const timestamps = _.pluck(buckets, 'key'); + const series = {}; + _.each(buckets, function (bucket) { + _.forOwn(bucket, function (val, key) { + if (_.isPlainObject(val)) { + if (val.values) { + _.forOwn(val.values, function (bucketValue, bucketKey) { + const k = key + ':' + bucketKey; + const v = isNaN(bucketValue) ? NaN : bucketValue; + series[k] = series[k] || []; + series[k].push(v); + }); + } else { + series[key] = series[key] || []; + series[key].push(val.value); + } + } + }); + }); + + return _.mapValues(series, function (values) { + return _.zip(timestamps, values); + }); +} + +export function flattenBucket(bucket, splitKey, path, result) { + result = result || {}; + path = path || []; + _.forOwn(bucket, function (val, key) { + if (!_.isPlainObject(val)) return; + if (_.get(val, 'meta.type') === 'split') { + _.each(val.buckets, function (bucket, bucketKey) { + if (bucket.key == null) bucket.key = bucketKey; // For handling "keyed" response formats, e.g., filters agg + flattenBucket(bucket, bucket.key, path.concat([key + ':' + bucket.key]), result); + }); + } else if (_.get(val, 'meta.type') === 'time_buckets') { + const metrics = timeBucketsToPairs(val.buckets); + _.each(metrics, function (pairs, metricName) { + result[path.concat([metricName]).join(' > ')] = { + data: pairs, + splitKey: splitKey + }; + }); + } + }); + return result; +} + +export default function toSeriesList(aggs, config) { + return _.map(flattenBucket(aggs), function (metrics, name) { + return { + data: metrics.data, + type: 'series', + fit: config.fit, + label: name, + split: metrics.splitKey + }; + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js new file mode 100644 index 0000000000000..26d868d08d2e5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import moment from 'moment'; +import { buildAggBody } from './agg_body'; +import createDateAgg from './create_date_agg'; + +export default function buildRequest(config, tlConfig, scriptedFields, timeout) { + + const bool = { must: [] }; + + const timeFilter = { + range: { + [config.timefield]: { + gte: moment(tlConfig.time.from).toISOString(), + lte: moment(tlConfig.time.to).toISOString(), + format: 'strict_date_optional_time' + } + } + }; + bool.must.push(timeFilter); + + // Use the kibana filter bar filters + if (config.kibana) { + bool.filter = _.get(tlConfig, 'request.payload.extended.es.filter'); + } + + const aggs = { + 'q': { + meta: { type: 'split' }, + filters: { + filters: _.chain(config.q).map(function (q) { + return [q, { query_string: { query: q } }]; + }).zipObject().value(), + }, + aggs: {} + } + }; + + let aggCursor = aggs.q.aggs; + + _.each(config.split, function (clause) { + clause = clause.split(':'); + if (clause[0] && clause[1]) { + const termsAgg = buildAggBody(clause[0], scriptedFields); + termsAgg.size = parseInt(clause[1], 10); + aggCursor[clause[0]] = { + meta: { type: 'split' }, + terms: termsAgg, + aggs: {} + }; + aggCursor = aggCursor[clause[0]].aggs; + } else { + throw new Error ('`split` requires field:limit'); + } + }); + + _.assign(aggCursor, createDateAgg(config, tlConfig, scriptedFields)); + + const request = { + index: config.index, + ignore_throttled: !tlConfig.settings['search:includeFrozen'], + body: { + query: { + bool: bool + }, + aggs: aggs, + size: 0 + } + }; + + if (timeout) { + request.timeout = `${timeout}ms`; + } + + return request; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js new file mode 100644 index 0000000000000..9a5c5dd4e9595 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import { buildAggBody } from './agg_body'; + +export default function createDateAgg(config, tlConfig, scriptedFields) { + const dateAgg = { + time_buckets: { + meta: { type: 'time_buckets' }, + date_histogram: { + field: config.timefield, + interval: config.interval, + time_zone: tlConfig.time.timezone, + extended_bounds: { + min: tlConfig.time.from, + max: tlConfig.time.to + }, + min_doc_count: 0 + } + } + }; + + dateAgg.time_buckets.aggs = {}; + _.each(config.metric, function (metric) { + metric = metric.split(':'); + if (metric[0] === 'count') { + // This is pretty lame, but its how the "doc_count" metric has to be implemented at the moment + // It simplifies the aggregation tree walking code considerably + dateAgg.time_buckets.aggs[metric] = { + bucket_script: { + buckets_path: '_count', + script: { source: '_value', lang: 'expression' } + } + }; + } else if (metric[0] && metric[1]) { + const metricName = metric[0] + '(' + metric[1] + ')'; + dateAgg.time_buckets.aggs[metricName] = {}; + dateAgg.time_buckets.aggs[metricName][metric[0]] = buildAggBody(metric[1], scriptedFields); + if (metric[0] === 'percentiles' && metric[2]) { + let percentList = metric[2].split(','); + percentList = percentList.map(x => parseFloat(x)); + dateAgg.time_buckets.aggs[metricName][metric[0]].percents = percentList; + } + } else { + throw new Error ('`metric` requires metric:field or simply count'); + } + }); + + return dateAgg; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js new file mode 100644 index 0000000000000..ace36b49b85c7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('first', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + } + ], + help: i18n.translate('timelion.help.functions.firstHelpText', { + defaultMessage: `This is an internal function that simply returns the input seriesList. Don't use this`, + }), + fn: function firstFn(args) { + return alter(args, function (eachSeries) { + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js new file mode 100644 index 0000000000000..572338d29a6cf --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; +import loadFunctions from '../lib/load_functions.js'; +const fitFunctions = loadFunctions('fit_functions'); + +export default new Chainable('fit', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'mode', + types: ['string'], + help: i18n.translate('timelion.help.functions.fit.args.modeHelpText', { + defaultMessage: + 'The algorithm to use for fitting the series to the target. One of: {fitFunctions}', + values: { + fitFunctions: _.keys(fitFunctions).join(', '), + }, + }), + suggestions: _.keys(fitFunctions).map(key => { + return { name: key }; + }) + } + ], + help: i18n.translate('timelion.help.functions.fitHelpText', { + defaultMessage: 'Fills null values using a defined fit function', + }), + fn: function absFn(args) { + return alter(args, function (eachSeries, mode) { + + const noNulls = eachSeries.data.filter((item) => (item[1] === 0 || item[1])); + + if (noNulls.length === 0) { + return eachSeries; + } + + eachSeries.data = fitFunctions[mode](noNulls, eachSeries.data); + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js new file mode 100644 index 0000000000000..b29cd829229c9 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import fetch from 'node-fetch'; +import moment from 'moment'; +import Datasource from '../lib/classes/datasource'; + +export default new Datasource ('graphite', { + args: [ + { + name: 'metric', // _test-data.users.*.data + types: ['string'], + help: i18n.translate('timelion.help.functions.graphite.args.metricHelpText', { + defaultMessage: 'Graphite metric to pull, e.g., {metricExample}', + values: { + metricExample: '_test-data.users.*.data', + }, + }), + } + ], + help: i18n.translate('timelion.help.functions.graphiteHelpText', { + defaultMessage: + `[experimental] Pull data from graphite. Configure your graphite server in Kibana's Advanced Settings`, + }), + fn: function graphite(args, tlConfig) { + const config = args.byName; + + const time = { + min: moment(tlConfig.time.from).format('HH:mm[_]YYYYMMDD'), + max: moment(tlConfig.time.to).format('HH:mm[_]YYYYMMDD') + }; + const allowedUrls = tlConfig.server.config().get('timelion.graphiteUrls'); + const configuredUrl = tlConfig.settings['timelion:graphite.url']; + if (!allowedUrls.includes(configuredUrl)) { + throw new Error(i18n.translate('timelion.help.functions.notAllowedGraphiteUrl', { + defaultMessage: + `This graphite URL is not configured on the kibana.yml file. + Please configure your graphite server list in the kibana.yml file under 'timelion.graphiteUrls' and + select one from Kibana's Advanced Settings`, + })); + } + + const URL = tlConfig.settings['timelion:graphite.url'] + '/render/' + + '?format=json' + + '&from=' + time.min + + '&until=' + time.max + + '&target=' + config.metric; + + return fetch(URL).then(function (resp) { + return resp.json(); + }).then(function (resp) { + const list = _.map(resp, function (series) { + const data = _.map(series.datapoints, function (point) { + return [point[1] * 1000, point[0]]; + }); + return { + data: data, + type: 'series', + fit: 'nearest', // TODO make this customizable + label: series.target + }; + }); + + return { + type: 'seriesList', + list: list + }; + }).catch(function (e) { + throw e; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js new file mode 100644 index 0000000000000..b643afe9937e8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('hide', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'hide', + types: ['boolean', 'null'], + help: i18n.translate('timelion.help.functions.hide.args.hideHelpText', { + defaultMessage: 'Hide or unhide the series', + }), + } + ], + help: i18n.translate('timelion.help.functions.hideHelpText', { + defaultMessage: 'Hide the series by default', + }), + fn: function hideFn(args) { + return alter(args, function (eachSeries, hide) { + eachSeries._hide = hide == null ? true : hide; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js new file mode 100644 index 0000000000000..f048a6a04b59c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import Chainable from '../../lib/classes/chainable'; +import ses from './lib/ses'; +import des from './lib/des'; +import tes from './lib/tes'; +import toMilliseconds from '../../lib/to_milliseconds'; + +export default new Chainable('holt', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'alpha', + types: ['number'], + help: i18n.translate('timelion.help.functions.holt.args.alphaHelpText', { + defaultMessage: + ` + Smoothing weight from 0 to 1. + Increasing alpha will make the new series more closely follow the original. + Lowering it will make the series smoother`, + }), + }, + { + name: 'beta', + types: ['number'], + help: i18n.translate('timelion.help.functions.holt.args.betaHelpText', { + defaultMessage: + ` + Trending weight from 0 to 1. + Increasing beta will make rising/falling lines continue to rise/fall longer. + Lowering it will make the function learn the new trend faster`, + }), + }, + { + name: 'gamma', + types: ['number'], + help: i18n.translate('timelion.help.functions.holt.args.gammaHelpText', { + defaultMessage: + ` + Seasonal weight from 0 to 1. Does your data look like a wave? + Increasing this will give recent seasons more importance, thus changing the wave form faster. + Lowering it will reduce the importance of new seasons, making history more important. + `, + }), + }, + { + name: 'season', + types: ['string'], + help: i18n.translate('timelion.help.functions.holt.args.seasonHelpText', { + defaultMessage: + 'How long is the season, e.g., 1w if your pattern repeats weekly. (Only useful with gamma)', + description: + '"1w" is an expression value and should not be translated. "gamma" is a parameter name and should not be translated.', + }), + }, + { + name: 'sample', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.holt.args.sampleHelpText', { + defaultMessage: + ` + The number of seasons to sample before starting to "predict" in a seasonal series. + (Only useful with gamma, Default: all)`, + description: '"gamma" and "all" are parameter names and values and must not be translated.', + }), + } + ], + help: i18n.translate('timelion.help.functions.holtHelpText', { + defaultMessage: + ` + Sample the beginning of a series and use it to forecast what should happen + via several optional parameters. In general, this doesn't really predict the + future, but predicts what should be happening right now according to past data, + which can be useful for anomaly detection. Note that nulls will be filled with forecasted values.`, + description: '"null" is a data value here and must not be translated.', + }), + fn: function expsmoothFn(args, tlConfig) { + + const newSeries = _.cloneDeep(args.byName.inputSeries); + + const alpha = args.byName.alpha; + const beta = args.byName.beta; + const gamma = args.byName.gamma; + + _.each(newSeries.list, function (series) { + const sample = args.byName.sample || series.data.length; // If we use length it should simply never predict + + + // Single exponential smoothing + // This is basically a weighted moving average in which the older + // points exponentially degrade relative to the alpha, e.g.: + // 0.8^1, 0.8^2, 0.8^3, etc + + const times = _.map(series.data, 0); + let points = _.map(series.data, 1); + + if (alpha != null && beta == null && gamma == null) { + points = ses(points, alpha); + } + + if (alpha != null && beta != null && gamma == null) { + points = des(points, alpha, beta); + } + + if (alpha != null && beta != null && gamma != null) { + if (!sample || !args.byName.season || sample < 2) { + throw new Error( + i18n.translate('timelion.serverSideErrors.holtFunction.missingParamsErrorMessage', { + defaultMessage: 'Must specify a season length and a sample size >= 2', + }) + ); + } + const season = Math.round(toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval)); + points = tes(points, alpha, beta, gamma, season, sample); + } + + _.assign(series.data, _.zip(times, points)); + }); + + return newSeries; + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js new file mode 100644 index 0000000000000..19d4d9fcbc518 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; + +export default function des(points, alpha, beta) { + let level; + let prevLevel; + let trend; + let prevTrend; + let unknownCount = 0; + + if (points.length < 2) { + throw new Error( + i18n.translate('timelion.serverSideErrors.holtFunction.notEnoughPointsErrorMessage', { + defaultMessage: 'You need at least 2 points to use double exponential smoothing', + }), + ); + } + + const smoothedPoints = _.map(points, (point, i) => { + if (i === 0) { + return point; + } + + if (i === 1) { + // Establish initial values for level and trend; + level = points[0]; + trend = points[1] - points[0]; // This is sort of a lame way to do this + } + + if (point == null) { + unknownCount++; + } else { + unknownCount = 0; + // These 2 variables are not required, but are used for clarity. + prevLevel = level; + prevTrend = trend; + level = (alpha * point) + (1 - alpha) * (prevLevel + prevTrend); + trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; + } + + return (level + (unknownCount * trend)); + }, []); + + return smoothedPoints; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js new file mode 100644 index 0000000000000..6236067b5a0de --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* + Single exponential smoothing. Assuming even interval +*/ + +import _ from 'lodash'; + +export default function ses(points, alpha) { + let origin; + let level; + + const smoothedPoints = _.reduce(points, (result, point, i) => { + if (i === 0) { + origin = point; + level = point; + } else { + // In the case that point[1] is null, we keep origin the same + // and forecast the point based on the previous smoothed point + if (point != null) { + origin = point; + } + if (origin == null) { + level = null; + } else { + const prevSmoothed = result[i - 1]; + level = alpha * origin + (1 - alpha) * prevSmoothed; + } + } + + result.push(level); + return result; + }, []); + + return smoothedPoints; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js new file mode 100644 index 0000000000000..fee235abe2b63 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Frequency = number of points per season +// Season = 1 hump + +/* +Hourly data might have: + - Daily seasonality (frequency=24) + - Weekly seasonality (frequency=24×7=168) + - Annual seasonality (frequency=24×365.25=8766) +*/ + +import _ from 'lodash'; + +// Uh, I don't think this will work when you have nulls in the initial seasonal components +function initSeasonalComponents(samplePoints, seasonLength) { + const sampledSeasonCount = samplePoints.length / seasonLength; + let currentSeason = []; + const seasonalAverages = _.reduce(samplePoints, (result, point, i) => { + currentSeason.push(point); + // If this is the end of the season, add it to the result; + if (i % seasonLength === seasonLength - 1) { + result.push(_.sum(currentSeason) / seasonLength); + currentSeason = []; + } + + return result; + }, []); + + const seasonals = _.times(seasonLength, (i) => { + let sumOfValsOverAvg = 0; + _.times(sampledSeasonCount, (j) => { + sumOfValsOverAvg += samplePoints[seasonLength * j + i] - seasonalAverages[j]; + }); + + return sumOfValsOverAvg / sampledSeasonCount; + }); + + return seasonals; +} + +// This is different from the DES method of establishing trend because it looks for +// the difference in points between seasons +function initTrend(samplePoints, seasonLength) { + let sum = 0; + _.times(seasonLength, (i) => { + sum += (samplePoints[i + seasonLength] - samplePoints[i]) / seasonLength; + }); + return sum / seasonLength; +} + +export default function tes(points, alpha, beta, gamma, seasonLength, seasonsToSample) { + + const samplePoints = points.slice(0, seasonLength * seasonsToSample); + const seasonals = initSeasonalComponents(samplePoints, seasonLength); + let level; + let prevLevel; + let trend; + let prevTrend; + let unknownCount = 0; + + const result = _.map(points, (point, i) => { + const seasonalPosition = i % seasonLength; + // For the first samplePoints.length we use the actual points + // After that we switch to the forecast + if (i === 0) { + trend = initTrend(points, seasonLength); + level = points[0]; + return points[0]; + } + + // Beta isn't actually used once we're forecasting? + if (point == null || i >= samplePoints.length) { + unknownCount++; + // Don't know this point, make it up! + return (level + (unknownCount * trend)) + seasonals[seasonalPosition]; + } else { + unknownCount = 0; + // These 2 variables are not required, but are used for clarity. + prevLevel = level; + prevTrend = trend; + level = alpha * (point - seasonals[seasonalPosition]) + (1 - alpha) * (prevLevel + prevTrend); + trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; + seasonals[seasonalPosition] = gamma * (point - level) + (1 - gamma) * seasonals[seasonalPosition]; + return level + trend + seasonals[seasonalPosition]; + } + + }); + + return result; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js new file mode 100644 index 0000000000000..ee92b68441d9e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('label', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'label', + types: ['string'], + help: i18n.translate('timelion.help.functions.label.args.labelHelpText', { + defaultMessage: + 'Legend value for series. You can use $1, $2, etc, in the string to match up with the regex capture groups', + description: '"$1" and "$2" are part of the expression and must not be translated.', + }), + }, + { + name: 'regex', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.label.args.regexHelpText', { + defaultMessage: 'A regex with capture group support', + }), + } + ], + help: i18n.translate('timelion.help.functions.labelHelpText', { + defaultMessage: 'Change the label of the series. Use %s to reference the existing label', + }), + fn: function labelFn(args) { + const config = args.byName; + return alter(args, function (eachSeries) { + if (config.regex) { + eachSeries.label = eachSeries.label.replace(new RegExp(config.regex), config.label); + } else { + eachSeries.label = config.label; + } + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js new file mode 100644 index 0000000000000..83acfb69d4709 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js @@ -0,0 +1,133 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; + +export default new Chainable('legend', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'position', + types: ['string', 'boolean', 'null'], + help: i18n.translate('timelion.help.functions.legend.args.positionHelpText', { + defaultMessage: + 'Corner to place the legend in: nw, ne, se, or sw. You can also pass false to disable the legend', + description: '"nw", "ne", "se", "sw" and "false" are keywords and must not be translated.', + }), + suggestions: [ + { + name: 'false', + help: i18n.translate( + 'timelion.help.functions.legend.args.position.suggestions.falseHelpText', + { + defaultMessage: 'disable legend', + } + ), + }, + { + name: 'nw', + help: i18n.translate( + 'timelion.help.functions.legend.args.position.suggestions.nwHelpText', + { + defaultMessage: 'place legend in north west corner', + } + ), + }, + { + name: 'ne', + help: i18n.translate( + 'timelion.help.functions.legend.args.position.suggestions.neHelpText', + { + defaultMessage: 'place legend in north east corner', + } + ), + }, + { + name: 'se', + help: i18n.translate( + 'timelion.help.functions.legend.args.position.suggestions.seHelpText', + { + defaultMessage: 'place legend in south east corner', + } + ), + }, + { + name: 'sw', + help: i18n.translate( + 'timelion.help.functions.legend.args.position.suggestions.swHelpText', + { + defaultMessage: 'place legend in south west corner', + } + ), + } + ] + }, + { + name: 'columns', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.legend.args.columnsHelpText', { + defaultMessage: 'Number of columns to divide the legend into', + }), + }, + { + name: 'showTime', + types: ['boolean'], + help: i18n.translate('timelion.help.functions.legend.args.showTimeHelpText', { + defaultMessage: 'Show time value in legend when hovering over graph. Default: true', + }), + }, + { + name: 'timeFormat', + types: ['string'], + help: i18n.translate('timelion.help.functions.legend.args.timeFormatHelpText', { + defaultMessage: 'moment.js format pattern. Default: {defaultTimeFormat}', + values: { + defaultTimeFormat: DEFAULT_TIME_FORMAT, + }, + }), + } + ], + help: i18n.translate('timelion.help.functions.legendHelpText', { + defaultMessage: 'Set the position and style of the legend on the plot', + }), + fn: function legendFn(args) { + return alter(args, function (eachSeries, position, columns, showTime = true, timeFormat = DEFAULT_TIME_FORMAT) { + eachSeries._global = eachSeries._global || {}; + eachSeries._global.legend = eachSeries._global.legend || {}; + eachSeries._global.legend.noColumns = columns; + eachSeries._global.legend.showTime = showTime; + eachSeries._global.legend.timeFormat = timeFormat; + + if (position === false) { + eachSeries._global.legend.show = false; + eachSeries._global.legend.showTime = false; + } else { + eachSeries._global.legend.position = position; + } + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js new file mode 100644 index 0000000000000..6a557be7f60ac --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js @@ -0,0 +1,85 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('lines', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'width', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.lines.args.widthHelpText', { + defaultMessage: 'Line thickness', + }), + }, + { + name: 'fill', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.lines.args.fillHelpText', { + defaultMessage: 'Number between 0 and 10. Use for making area charts', + }), + }, + { + name: 'stack', + types: ['boolean', 'null'], + help: i18n.translate('timelion.help.functions.lines.args.stackHelpText', { + defaultMessage: 'Stack lines, often misleading. At least use some fill if you use this.', + }), + }, + { + name: 'show', + types: ['number', 'boolean', 'null'], + help: i18n.translate('timelion.help.functions.lines.args.showHelpText', { + defaultMessage: 'Show or hide lines', + }), + }, + { + name: 'steps', + types: ['number', 'boolean', 'null'], + help: i18n.translate('timelion.help.functions.lines.args.stepsHelpText', { + defaultMessage: 'Show line as step, e.g., do not interpolate between points', + }), + } + ], + help: i18n.translate('timelion.help.functions.linesHelpText', { + defaultMessage: 'Show the seriesList as lines', + }), + fn: function linesFn(args) { + return alter(args, function (eachSeries, width, fill, stack, show, steps) { + eachSeries.lines = eachSeries.lines || {}; + + // Defaults + if (eachSeries.lines.lineWidth == null) eachSeries.lines.lineWidth = 3; + + if (width != null) eachSeries.lines.lineWidth = width; + if (fill != null) eachSeries.lines.fill = fill / 10; + if (stack != null) eachSeries.stack = stack; + if (show != null) eachSeries.lines.show = show; + if (steps != null) eachSeries.lines.steps = steps; + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js new file mode 100644 index 0000000000000..76f0d76c39f59 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('log', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'base', + types: ['number'], + help: i18n.translate('timelion.help.functions.log.args.baseHelpText', { + defaultMessage: 'Set logarithmic base, 10 by default', + }), + } + ], + help: i18n.translate('timelion.help.functions.logHelpText', { + defaultMessage: + 'Return the logarithm value of each value in the series list (default base: 10)', + }), + fn: function logFn(args) { + const config = args.byName; + return alter(args, function (eachSeries) { + const data = _.map(eachSeries.data, function (point) { + return [point[0], Math.log(point[1]) / Math.log(config.base || 10)]; + }); + eachSeries.data = data; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js new file mode 100644 index 0000000000000..8177c8bd49bdb --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('max', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'value', + types: ['seriesList', 'number'], + help: i18n.translate('timelion.help.functions.max.args.valueHelpText', { + defaultMessage: + 'Sets the point to whichever is higher, the existing value, or the one passed. ' + + 'If passing a seriesList it must contain exactly 1 series.', + }), + } + + ], + help: i18n.translate('timelion.help.functions.maxHelpText', { + defaultMessage: + 'Maximum values of one or more series in a seriesList to each position, in each series, of the input seriesList', + }), + fn: function maxFn(args) { + return reduce(args, function (a, b) { + return Math.max(a, b); + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js new file mode 100644 index 0000000000000..75741951b5475 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js @@ -0,0 +1,50 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('min', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'value', + types: ['seriesList', 'number'], + help: i18n.translate('timelion.help.functions.min.args.valueHelpText', { + defaultMessage: + 'Sets the point to whichever is lower, the existing value, or the one passed. ' + + 'If passing a seriesList it must contain exactly 1 series.', + }), + } + + ], + help: i18n.translate('timelion.help.functions.minHelpText', { + defaultMessage: + 'Minimum values of one or more series in a seriesList to each position, in each series, of the input seriesList', + }), + fn: function minFn(args) { + return reduce(args, function (a, b) { + return Math.min(a, b); + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js new file mode 100644 index 0000000000000..b8de3749b30b4 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js @@ -0,0 +1,134 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; +import toMS from '../lib/to_milliseconds.js'; + +const validPositions = ['left', 'right', 'center']; +const defaultPosition = 'center'; + +export default new Chainable('movingaverage', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'window', + types: ['number', 'string'], + help: i18n.translate('timelion.help.functions.movingaverage.args.windowHelpText', { + defaultMessage: + 'Number of points, or a date math expression (eg 1d, 1M) to average over. If a date math expression ' + + 'is specified, the function will get as close as possible given the currently select interval. ' + + 'If the date math expression is not evenly divisible by the interval the results may appear abnormal.', + }), + }, + { + name: 'position', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.movingaverage.args.positionHelpText', { + defaultMessage: + 'Position of the averaged points relative to the result time. One of: {validPositions}', + values: { + validPositions: validPositions.join(', '), + }, + }), + suggestions: validPositions.map(position => { + const suggestion = { name: position }; + if (position === defaultPosition) { + suggestion.help = 'default'; + } + return suggestion; + }) + } + ], + aliases: ['mvavg'], + help: i18n.translate('timelion.help.functions.movingaverageHelpText', { + defaultMessage: + 'Calculate the moving average over a given window. Nice for smoothing noisy series', + }), + fn: function movingaverageFn(args, tlConfig) { + return alter(args, function (eachSeries, _window, _position) { + + // _window always needs to be a number, if isn't we have to make it into one. + if (typeof _window !== 'number') { + // Ok, I guess its a datemath expression + const windowMilliseconds = toMS(_window); + + // calculate how many buckets that _window represents + const intervalMilliseconds = toMS(tlConfig.time.interval); + + // Round, floor, ceil? We're going with round because it splits the difference. + _window = Math.round(windowMilliseconds / intervalMilliseconds) || 1; + } + + _position = _position || defaultPosition; + if (!_.contains(validPositions, _position)) { + throw new Error( + i18n.translate('timelion.serverSideErrors.movingaverageFunction.notValidPositionErrorMessage', { + defaultMessage: 'Valid positions are: {validPositions}', + values: { + validPositions: validPositions.join(', '), + }, + }) + ); + } + + const pairs = eachSeries.data; + const pairsLen = pairs.length; + eachSeries.label = eachSeries.label + ' mvavg=' + _window; + + function toPoint(point, pairSlice) { + const average = _.chain(pairSlice) + .map(1).reduce(function (memo, num) { + return (memo + num); + }).value() / _window; + + return [point[0], average]; + } + + if (_position === 'center') { + const windowLeft = Math.floor(_window / 2); + const windowRight = _window - windowLeft; + eachSeries.data = _.map(pairs, function (point, i) { + if (i < windowLeft || i > pairsLen - windowRight) return [point[0], null]; + return toPoint(point, pairs.slice(i - windowLeft, i + windowRight)); + }); + } else if (_position === 'left') { + eachSeries.data = _.map(pairs, function (point, i) { + const cursor = i + 1; + if (cursor < _window) return [point[0], null]; + return toPoint(point, pairs.slice(cursor - _window, cursor)); + }); + + } else if (_position === 'right') { + eachSeries.data = _.map(pairs, function (point, i) { + if (i > pairsLen - _window) return [point[0], null]; + return toPoint(point, pairs.slice(i, i + _window)); + }); + + } + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js new file mode 100644 index 0000000000000..396e88fa601a8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +const positions = ['left', 'right', 'center']; +const defaultPosition = positions[0]; + +export default new Chainable('movingstd', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'window', + types: ['number'], + help: i18n.translate('timelion.help.functions.movingstd.args.windowHelpText', { + defaultMessage: 'Number of points to compute the standard deviation over.', + }), + }, + { + name: 'position', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.movingstd.args.positionHelpText', { + defaultMessage: + 'Position of the window slice relative to the result time. Options are {positions}. Default: {defaultPosition}', + values: { + positions: positions.join(', '), + defaultPosition, + }, + }), + } + ], + aliases: ['mvstd'], + help: i18n.translate('timelion.help.functions.movingstdHelpText', { + defaultMessage: + 'Calculate the moving standard deviation over a given window. Uses naive two-pass algorithm. ' + + 'Rounding errors may become more noticeable with very long series, or series with very large numbers.', + }), + fn: function movingstdFn(args) { + return alter(args, function (eachSeries, _window, _position) { + + _position = _position || defaultPosition; + + if (!_.contains(positions, _position)) { + throw new Error( + i18n.translate('timelion.serverSideErrors.movingstdFunction.notValidPositionErrorMessage', { + defaultMessage: 'Valid positions are: {validPositions}', + values: { + validPositions: positions.join(', '), + }, + }), + ); + } + + const pairs = eachSeries.data; + const pairsLen = pairs.length; + eachSeries.label = eachSeries.label + ' mvstd=' + _window; + + function toPoint(point, pairSlice) { + const average = _.chain(pairSlice).map(1).reduce(function (memo, num) { + return memo + num; + }).value() / _window; + + const variance = _.chain(pairSlice).map(function (point) { + return Math.pow(point[1] - average, 2); + }).reduce(function (memo, num) { + return memo + num; + }).value() / (_window - 1); + + return [point[0], Math.sqrt(variance)]; + } + + if (_position === 'center') { + const windowLeft = Math.floor(_window / 2); + const windowRight = _window - windowLeft; + eachSeries.data = _.map(pairs, function (point, i) { + if (i < windowLeft || i >= pairsLen - windowRight) return [point[0], null]; + return toPoint(point, pairs.slice(i - windowLeft, i + windowRight)); + }); + } else if (_position === 'left') { + eachSeries.data = _.map(pairs, function (point, i) { + if (i < _window) return [point[0], null]; + return toPoint(point, pairs.slice(i - _window, i)); + }); + } else if (_position === 'right') { + eachSeries.data = _.map(pairs, function (point, i) { + if (i >= pairsLen - _window) return [point[0], null]; + return toPoint(point, pairs.slice(i, i + _window)); + }); + } + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js new file mode 100644 index 0000000000000..1ecda7f781190 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('multiply', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'multiplier', + types: ['seriesList', 'number'], + help: i18n.translate('timelion.help.functions.multiply.args.multiplierHelpText', { + defaultMessage: + 'Number or series by which to multiply. SeriesList with multiple series will be applied label-wise.', + }), + } + ], + help: i18n.translate('timelion.help.functions.multiplyHelpText', { + defaultMessage: + 'Multiply the values of one or more series in a seriesList to each position, in each series, of the input seriesList', + }), + fn: function multiplyFn(args) { + return reduce(args, function (a, b) { + return a * b; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js new file mode 100644 index 0000000000000..6817d833b5544 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js @@ -0,0 +1,128 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +const validSymbols = ['triangle', 'cross', 'square', 'diamond', 'circle']; +const defaultSymbol = 'circle'; + +export default new Chainable('points', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'radius', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.points.args.radiusHelpText', { + defaultMessage: 'Size of points', + }), + }, + { + name: 'weight', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.points.args.weightHelpText', { + defaultMessage: 'Thickness of line around point', + }), + }, + { + name: 'fill', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.points.args.fillHelpText', { + defaultMessage: 'Number between 0 and 10 representing opacity of fill', + }), + }, + { + name: 'fillColor', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.points.args.fillColorHelpText', { + defaultMessage: 'Color with which to fill point', + }), + }, + { + name: 'symbol', + help: i18n.translate('timelion.help.functions.points.args.symbolHelpText', { + defaultMessage: 'point symbol. One of: {validSymbols}', + values: { + validSymbols: validSymbols.join(', '), + }, + }), + types: ['string', 'null'], + suggestions: validSymbols.map(symbol => { + const suggestion = { name: symbol }; + if (symbol === defaultSymbol) { + suggestion.help = 'default'; + } + return suggestion; + }) + }, + { + name: 'show', + types: ['boolean', 'null'], + help: i18n.translate('timelion.help.functions.points.args.showHelpText', { + defaultMessage: 'Show points or not', + }), + } + ], + help: i18n.translate('timelion.help.functions.pointsHelpText', { + defaultMessage: 'Show the series as points', + }), + fn: function pointsFn(args) { + return alter(args, function (eachSeries, radius, weight, fill, fillColor, symbol, show) { + eachSeries.points = eachSeries.points || {}; + eachSeries.points.radius = radius == null ? undefined : radius; + + if (fill) { + eachSeries.points.fillColor = fillColor == null ? false : fillColor; + } + + if (fill != null) { + eachSeries.points.fill = fill / 10; + } + + if (weight != null) { + eachSeries.points.lineWidth = weight; + } + + symbol = symbol || defaultSymbol; + if (!_.contains(validSymbols, symbol)) { + throw new Error( + i18n.translate('timelion.serverSideErrors.pointsFunction.notValidSymbolErrorMessage', { + defaultMessage: 'Valid symbols are: {validSymbols}', + values: { + validSymbols: validSymbols.join(', '), + }, + }) + ); + } + + eachSeries.points.symbol = symbol; + + eachSeries.points.show = show == null ? true : show; + + + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js new file mode 100644 index 0000000000000..98eec9b57dabc --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('precision', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'precision', + types: ['number'], + help: i18n.translate('timelion.help.functions.precision.args.precisionHelpText', { + defaultMessage: 'Number of digits to round each value to', + }), + } + ], + help: i18n.translate('timelion.help.functions.precisionHelpText', { + defaultMessage: 'number of digits to round the decimal portion of the value to', + }), + fn: async function precisionFn(args) { + await alter(args, function (eachSeries, precision) { + eachSeries._meta = eachSeries._meta || {}; + eachSeries._meta.precision = precision; + return eachSeries; + }); + + return reduce(args, function (a, b) { + return parseInt(a * Math.pow(10, b), 10) / Math.pow(10, b); + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js new file mode 100644 index 0000000000000..80e9cafd67120 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; +import _ from 'lodash'; + +function unflatten(data) { + if (Object(data) !== data || Array.isArray(data)) return data; + + const regex = new RegExp(/\.?([^.\[\]]+)|\[(\d+)\]/g); + const result = {}; + _.each(data, function (val, p) { + let cur = result; + let prop = ''; + let m; + while (m = regex.exec(p)) { + cur = (cur.hasOwnProperty(prop) && cur[prop]) || (cur[prop] = (m[2] ? [] : {})); + prop = m[2] || m[1]; + } + cur[prop] = data[p]; + }); + + return result[''] || result; +} + +export default new Chainable('props', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'global', + types: ['boolean', 'null'], + help: i18n.translate('timelion.help.functions.props.args.globalHelpText', { + defaultMessage: 'Set props on the seriesList vs on each series', + }), + } + ], + extended: { + types: ['seriesList', 'number', 'string', 'boolean', 'null'], + // Extended args can not currently be multivalued, + // multi: false is not required and is shown here for demonstration purposes + multi: false + }, + // extended means you can pass arguments that aren't listed. They just won't be in the ordered array + // They will be passed as args._extended:{} + help: i18n.translate('timelion.help.functions.propsHelpText', { + defaultMessage: + 'Use at your own risk, sets arbitrary properties on the series. For example {example}', + values: { + example: '.props(label=bears!)', + }, + }), + fn: function firstFn(args) { + const properties = unflatten(_.omit(args.byName, 'inputSeries', 'global')); + + if (args.byName.global) { + _.assign(args.byName.inputSeries, properties); + return args.byName.inputSeries; + } else { + return alter(args, function (eachSeries) { + _.assign(eachSeries, properties); + return eachSeries; + }); + } + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js new file mode 100644 index 0000000000000..dc7c27b8abc1a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js @@ -0,0 +1,121 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import fetch from 'node-fetch'; +import moment from 'moment'; +fetch.Promise = require('bluebird'); + +import Datasource from '../lib/classes/datasource'; + +export default new Datasource ('quandl', { + dataSource: true, + args: [ + { + name: 'code', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.quandl.args.codeHelpText', { + defaultMessage: 'The quandl code to plot. You can find these on quandl.com.', + }), + }, + { + name: 'position', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.quandl.args.positionHelpText', { + defaultMessage: + 'Some quandl sources return multiple series, which one should I use? 1 based index.', + }), + } + ], + help: i18n.translate('timelion.help.functions.quandlHelpText', { + defaultMessage: + ` + [experimental] + Pull data from quandl.com using the quandl code. Set {quandlKeyField} to your free API key in Kibana's + Advanced Settings. The API has a really low rate limit without a key.`, + values: { + quandlKeyField: '"timelion:quandl.key"', + }, + }), + fn: function quandlFn(args, tlConfig) { + const intervalMap = { + '1d': 'daily', + '1w': 'weekly', + '1M': 'monthly', + '1y': 'annual', + }; + + const config = _.defaults(args.byName, { + code: 'WIKI/AAPL', + position: 1, + interval: intervalMap[tlConfig.time.interval], + apikey: tlConfig.settings['timelion:quandl.key'] + }); + + if (!config.interval) { + throw new Error( + i18n.translate('timelion.serverSideErrors.quandlFunction.unsupportedIntervalErrorMessage', { + defaultMessage: 'quandl() unsupported interval: {interval}. quandl() supports: {intervals}', + values: { + interval: tlConfig.time.interval, + intervals: _.keys(intervalMap).join(', '), + }, + }) + ); + } + + const time = { + min: moment.utc(tlConfig.time.from).format('YYYY-MM-DD'), + max: moment.utc(tlConfig.time.to).format('YYYY-MM-DD') + }; + + // POSITIONS + // 1. open + // 2. high + // 3. low + // 4. close + // 5. volume + + const URL = 'https://www.quandl.com/api/v1/datasets/' + config.code + '.json' + + '?sort_order=asc' + + '&trim_start=' + time.min + + '&trim_end=' + time.max + + '&collapse=' + config.interval + + '&auth_token=' + config.apikey; + + return fetch(URL).then(function (resp) { return resp.json(); }).then(function (resp) { + const data = _.map(resp.data, function (bucket) { + return [moment(bucket[0]).valueOf(), bucket[config.position]]; + }); + + return { + type: 'seriesList', + list: [{ + data: data, + type: 'series', + fit: 'nearest', + label: resp.name + }] + }; + }).catch(function (e) { + throw e; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js new file mode 100644 index 0000000000000..89dab33ff2c6f --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('range', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'min', + types: ['number'], + help: i18n.translate('timelion.help.functions.range.args.minHelpText', { + defaultMessage: 'New minimum value', + }), + }, + { + name: 'max', + types: ['number'], + help: i18n.translate('timelion.help.functions.range.args.maxHelpText', { + defaultMessage: 'New maximum value', + }), + } + ], + help: i18n.translate('timelion.help.functions.rangeHelpText', { + defaultMessage: 'Changes the max and min of a series while keeping the same shape', + }), + fn: function range(args) { + return alter(args, function (eachSeries) { + const values = _.map(eachSeries.data, 1); + const min = _.min(values); + const max = _.max(values); + + // newvalue= (max'-min')/(max-min)*(value-min)+min'. + const data = _.map(eachSeries.data, function (point) { + const val = (args.byName.max - args.byName.min) / + (max - min) * (point[1] - min) + args.byName.min; + return [point[0], val]; + }); + eachSeries.data = data; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js new file mode 100644 index 0000000000000..9652fbe8b5720 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import toMS from '../lib/to_milliseconds.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('scale_interval', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'interval', + types: ['string'], + help: i18n.translate('timelion.help.functions.scaleInterval.args.intervalHelpText', { + defaultMessage: + 'The new interval in date math notation, e.g., 1s for 1 second. 1m, 5m, 1M, 1w, 1y, etc.', + }), + } + ], + help: i18n.translate('timelion.help.functions.scaleIntervalHelpText', { + defaultMessage: + 'Changes scales a value (usually a sum or a count) to a new interval. For example, as a per-second rate', + }), + fn: function scaleIntervalFn(args, tlConfig) { + const currentInterval = toMS(tlConfig.time.interval); + const scaleInterval = toMS(args.byName.interval); + + return alter(args, function (eachSeries) { + const data = _.map(eachSeries.data, function (point) { + return [point[0], (point[1] / currentInterval) * scaleInterval]; + }); + eachSeries.data = data; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js new file mode 100644 index 0000000000000..e348afbec95b8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import Datasource from '../lib/classes/datasource'; +import Bluebird from 'bluebird'; + +export default new Datasource ('static', { + aliases: ['value'], + args: [ + { + name: 'value', // _test-data.users.*.data + types: ['number', 'string'], + help: i18n.translate('timelion.help.functions.static.args.valueHelpText', { + defaultMessage: + 'The single value to to display, you can also pass several values and I will interpolate them evenly across your time range.', + }), + }, + { + name: 'label', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.static.args.labelHelpText', { + defaultMessage: + 'A quick way to set the label for the series. You could also use the .label() function', + }), + } + ], + help: i18n.translate('timelion.help.functions.staticHelpText', { + defaultMessage: 'Draws a single value across the chart', + }), + fn: function staticFn(args, tlConfig) { + + let data; + const target = tlConfig.getTargetSeries(); + if (typeof args.byName.value === 'string') { + const points = args.byName.value.split(':'); + const begin = _.first(target)[0]; + const end = _.last(target)[0]; + const step = (end - begin) / (points.length - 1); + data = _.map(points, function (point, i) { + return [begin + (i * step), parseFloat(point)]; + }); + } else { + data = _.map(target, function (bucket) { + return [bucket[0], args.byName.value]; + }); + } + + return Bluebird.resolve({ + type: 'seriesList', + list: [ + { + data: data, + type: 'series', + label: args.byName.label == null ? String(args.byName.value) : args.byName.label, + fit: args.byName.fit || 'average' + } + ] + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js new file mode 100644 index 0000000000000..c71da4ec49cc5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('subtract', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'term', + types: ['seriesList', 'number'], + help: i18n.translate('timelion.help.functions.subtract.args.termHelpText', { + defaultMessage: + 'Number or series to subtract from input. SeriesList with multiple series will be applied label-wise.', + }), + } + ], + help: i18n.translate('timelion.help.functions.subtractHelpText', { + defaultMessage: + 'Subtract the values of one or more series in a seriesList to each position, in each series, of the input seriesList', + }), + fn: function subtractFn(args) { + return reduce(args, function (a, b) { + return a - b; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js new file mode 100644 index 0000000000000..0ad371e761f0d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import reduce from '../lib/reduce.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('sum', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'term', + types: ['seriesList', 'number'], + help: i18n.translate('timelion.help.functions.sum.args.termHelpText', { + defaultMessage: + 'Number or series to sum with the input series. SeriesList with multiple series will be applied label-wise.', + }), + } + ], + help: i18n.translate('timelion.help.functions.sumHelpText', { + defaultMessage: + 'Adds the values of one or more series in a seriesList to each position, in each series, of the input seriesList', + }), + aliases: ['add', 'plus'], + fn: function sumFn(args) { + return reduce(args, function (a, b) { + return a + b; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js new file mode 100644 index 0000000000000..b39c88121efb3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('title', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'title', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.title.args.titleHelpText', { + defaultMessage: 'Title for the plot.', + }), + } + ], + help: i18n.translate('timelion.help.functions.titleHelpText', { + defaultMessage: + 'Adds a title to the top of the plot. If called on more than 1 seriesList the last call will be used.', + }), + fn: function hideFn(args) { + return alter(args, function (eachSeries, title) { + eachSeries._title = title; + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js new file mode 100644 index 0000000000000..088124641f5b7 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js @@ -0,0 +1,96 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import Chainable from '../../lib/classes/chainable'; +import { linear, log } from './lib/regress'; + +const validRegressions = { + linear: 'linear', + log: 'logarithmic', +}; + +export default new Chainable('trend', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'mode', + types: ['string'], + help: i18n.translate('timelion.help.functions.trend.args.modeHelpText', { + defaultMessage: + 'The algorithm to use for generating the trend line. One of: {validRegressions}', + values: { + validRegressions: _.keys(validRegressions).join(', '), + }, + }), + suggestions: _.keys(validRegressions).map(key => { + return { name: key, help: validRegressions[key] }; + }) + }, + { + name: 'start', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.trend.args.startHelpText', { + defaultMessage: + 'Where to start calculating from the beginning or end. For example -10 would start ' + + 'calculating 10 points from the end, +15 would start 15 points from the beginning. Default: 0', + }), + }, + { + name: 'end', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.trend.args.endHelpText', { + defaultMessage: + 'Where to stop calculating from the beginning or end. For example -10 would stop ' + + 'calculating 10 points from the end, +15 would stop 15 points from the beginning. Default: 0', + }), + }, + ], + help: i18n.translate('timelion.help.functions.trendHelpText', { + defaultMessage: 'Draws a trend line using a specified regression algorithm', + }), + fn: function absFn(args) { + const newSeries = _.cloneDeep(args.byName.inputSeries); + + _.each(newSeries.list, function (series) { + const length = series.data.length; + let start = args.byName.start == null ? 0 : args.byName.start; + let end = args.byName.end == null ? length : args.byName.end; + start = start >= 0 ? start : length + start; + end = end > 0 ? end : length + end; + + const subset = series.data.slice(start, end); + + const result = (args.byName.mode === 'log') ? log(subset) : linear(subset); + + _.each(series.data, function (point) { + point[1] = null; + }); + + _.each(result, function (point, i) { + series.data[start + i] = point; + }); + }); + return newSeries; + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js new file mode 100644 index 0000000000000..ea54eccc8208d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/* +* Algorithms from +* copyright(c) 2013 Tom Alexander +* Licensed under the MIT license. +*/ + +import _ from 'lodash'; + +function sum(data, fn) { + return _.reduce(data, function (sum, d) { + return sum + (d[1] == null ? 0 : fn(d)); + }, 0); +} + +function count(data) { + return _.filter(data, function (d) { + return d[1] == null ? false : true; + }).length; +} + +function mapTuples(data, fn) { + return _.map(data, function (d) { + return [d[0], fn(d)]; + }); +} + +export function linear(data) { + const xSum = sum(data, (d) => {return d[0]; }); + const ySum = sum(data, (d) => {return d[1]; }); + const xSqSum = sum(data, (d) => {return d[0] * d[0]; }); + const xySum = sum(data, (d) => {return d[0] * d[1]; }); + const observations = count(data); + + const gradient = + ((observations * xySum) - (xSum * ySum)) / + ((observations * xSqSum) - (xSum * xSum)); + + const intercept = + (ySum / observations) - (gradient * xSum) / observations; + + return mapTuples(data, (d) => { return d[0] * gradient + intercept; }); +} + +export function log(data) { + const logXSum = sum(data, (d) => {return Math.log(d[0]); }); + const yLogXSum = sum(data, (d) => {return d[1] * Math.log(d[0]); }); + const ySum = sum(data, (d) => {return d[1]; }); + const logXsqSum = sum(data, (d) => {return Math.pow(Math.log(d[0]), 2); }); + const observations = count(data); + + const b = + ((observations * yLogXSum) - (ySum * logXSum)) / + ((observations * logXsqSum) - (logXSum * logXSum)); + + const a = + (ySum - b * logXSum) / + observations; + + return mapTuples(data, (d) => { return a + b * Math.log(d[0]); }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js new file mode 100644 index 0000000000000..fdfab5a63c9b3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import alter from '../lib/alter.js'; +import _ from 'lodash'; +import Chainable from '../lib/classes/chainable'; + +export default new Chainable('trim', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'start', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.trim.args.startHelpText', { + defaultMessage: 'Buckets to trim from the beginning of the series. Default: 1', + }), + }, + { + name: 'end', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.trim.args.endHelpText', { + defaultMessage: 'Buckets to trim from the end of the series. Default: 1', + }), + } + ], + help: i18n.translate('timelion.help.functions.trimHelpText', { + defaultMessage: + 'Set N buckets at the start or end of a series to null to fit the "partial bucket issue"', + }), + fn: function conditionFn(args) { + const config = args.byName; + if (config.start == null) config.start = 1; + if (config.end == null) config.end = 1; + + return alter(args, function (eachSeries) { + + _.times(config.start, function (i) { + eachSeries.data[i][1] = null; + }); + + _.times(config.end, function (i) { + eachSeries.data[(eachSeries.data.length - 1) - i][1] = null; + }); + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js new file mode 100644 index 0000000000000..6a23b0f169dd4 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js @@ -0,0 +1,115 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import fetch from 'node-fetch'; +import moment from 'moment'; +import Datasource from '../lib/classes/datasource'; + +export default new Datasource ('worldbank', { + args: [ + { + name: 'code', // countries/all/indicators/SP.POP.TOTL + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.worldbank.args.codeHelpText', { + defaultMessage: + 'Worldbank API path. This is usually everything after the domain, before the querystring. E.g.: {apiPathExample}.', + values: { + apiPathExample: '/en/countries/ind;chn/indicators/DPANUSSPF', + }, + }), + } + ], + aliases: ['wb'], + help: i18n.translate('timelion.help.functions.worldbankHelpText', { + defaultMessage: + ` + [experimental] + Pull data from {worldbankUrl} using path to series. + The worldbank provides mostly yearly data, and often has no data for the current year. + Try {offsetQuery} if you get no data for recent time ranges.`, + values: { + worldbankUrl: 'http://data.worldbank.org/', + offsetQuery: 'offset=-1y', + }, + }), + fn: function worldbank(args, tlConfig) { + // http://api.worldbank.org/en/countries/ind;chn/indicators/DPANUSSPF?date=2000:2006&MRV=5 + + const config = _.defaults(args.byName, { + code: 'countries/wld/indicators/SP.POP.TOTL' + }); + + const time = { + min: moment(tlConfig.time.from).format('YYYY'), + max: moment(tlConfig.time.to).format('YYYY') + }; + + const URL = 'http://api.worldbank.org/' + config.code + + '?date=' + time.min + ':' + time.max + + '&format=json' + + '&per_page=1000'; + + return fetch(URL).then(function (resp) { return resp.json(); }).then(function (resp) { + let hasData = false; + + const respSeries = resp[1]; + + const deduped = {}; + let description; + _.each (respSeries, function (bucket) { + if (bucket.value != null) hasData = true; + description = bucket.country.value + ' ' + bucket.indicator.value; + deduped[bucket.date] = bucket.value; + }); + + const data = _.compact(_.map(deduped, function (val, date) { + // Discard nulls + if (val == null) return; + return [moment(date, 'YYYY').valueOf(), Number(val)]; + })); + + if (!hasData) { + throw new Error( + i18n.translate('timelion.serverSideErrors.worldbankFunction.noDataErrorMessage', { + defaultMessage: 'Worldbank request succeeded, but there was no data for {code}', + values: { + code: config.code, + }, + }) + ); + } + + return { + type: 'seriesList', + list: [{ + data: data, + type: 'series', + label: description, + _meta: { + worldbank_request: URL + } + }] + }; + }).catch(function (e) { + throw e; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js new file mode 100644 index 0000000000000..5b8703a6abe25 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import worldbank from './worldbank.js'; +import Bluebird from 'bluebird'; +import Datasource from '../lib/classes/datasource'; + +export default new Datasource ('worldbank_indicators', { + args: [ + { + name: 'country', // countries/all/indicators/SP.POP.TOTL + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.worldbankIndicators.args.countryHelpText', { + defaultMessage: `Worldbank country identifier. Usually the country's 2 letter code`, + }), + }, + { + name: 'indicator', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.worldbankIndicators.args.indicatorHelpText', { + defaultMessage: + `The indicator code to use. You'll have to look this up on {worldbankUrl}. ` + + 'Often pretty obtuse. E.g., {indicatorExample} is population', + values: { + worldbankUrl: 'data.worldbank.org', + indicatorExample: 'SP.POP.TOTL', + }, + }), + } + ], + aliases: ['wbi'], + help: i18n.translate('timelion.help.functions.worldbankIndicatorsHelpText', { + defaultMessage: + ` + [experimental] + Pull data from {worldbankUrl} using the country name and indicator. The worldbank provides + mostly yearly data, and often has no data for the current year. Try {offsetQuery} if you get no data for recent + time ranges.`, + values: { + worldbankUrl: 'http://data.worldbank.org/', + offsetQuery: 'offset=-1y', + }, + }), + fn: function worldbankIndicators(args, tlConfig) { + const config = _.defaults(args.byName, { + country: 'wld', + indicator: 'SP.POP.TOTL' + }); + + const countries = config.country.split(':'); + const seriesLists = _.map(countries, function (country) { + const code = 'countries/' + country + '/indicators/' + config.indicator; + const wbArgs = [code]; + wbArgs.byName = { code: code }; + return worldbank.timelionFn(wbArgs, tlConfig); + }); + + return Bluebird.map(seriesLists, function (seriesList) { + return seriesList.list[0]; + }).then(function (list) { + return { + type: 'seriesList', + list: list + }; + }); + + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js new file mode 100644 index 0000000000000..707c33837af0b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js @@ -0,0 +1,177 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import alter from '../lib/alter.js'; +import Chainable from '../lib/classes/chainable'; +const tickFormatters = { + 'bits': 'bits', + 'bits/s': 'bits/s', + 'bytes': 'bytes', + 'bytes/s': 'bytes/s', + 'currency': 'currency(:ISO 4217 currency code)', + 'percent': 'percent', + 'custom': 'custom(:prefix:suffix)' +}; + +export default new Chainable('yaxis', { + args: [ + { + name: 'inputSeries', + types: ['seriesList'] + }, + { + name: 'yaxis', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.yaxisHelpText', { + defaultMessage: + 'The numbered y-axis to plot this series on, e.g., .yaxis(2) for a 2nd y-axis.', + }), + }, + { + name: 'min', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.minHelpText', { + defaultMessage: 'Min value', + }), + }, + { + name: 'max', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.maxHelpText', { + defaultMessage: 'Max value', + }), + }, + { + name: 'position', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.positionHelpText', { + defaultMessage: 'left or right', + }), + }, + { + name: 'label', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.labelHelpText', { + defaultMessage: 'Label for axis', + }), + }, + { + name: 'color', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.colorHelpText', { + defaultMessage: 'Color of axis label', + }), + }, + { + name: 'units', + types: ['string', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.unitsHelpText', { + defaultMessage: 'The function to use for formatting y-axis labels. One of: {formatters}', + values: { + formatters: _.values(tickFormatters).join(', '), + }, + }), + suggestions: _.keys(tickFormatters).map(key => { + return { name: key, help: tickFormatters[key] }; + }) + }, + { + name: 'tickDecimals', + types: ['number', 'null'], + help: i18n.translate('timelion.help.functions.yaxis.args.tickDecimalsHelpText', { + defaultMessage: 'The number of decimal places for the y-axis tick labels.', + }), + }, + ], + help: i18n.translate('timelion.help.functions.yaxisHelpText', { + defaultMessage: + 'Configures a variety of y-axis options, the most important likely being the ability to add an Nth (eg 2nd) y-axis', + }), + fn: function yaxisFn(args) { + return alter(args, function (eachSeries, yaxis, min, max, position, label, color, units, tickDecimals) { + yaxis = yaxis || 1; + + eachSeries.yaxis = yaxis; + eachSeries._global = eachSeries._global || {}; + + eachSeries._global.yaxes = eachSeries._global.yaxes || []; + eachSeries._global.yaxes[yaxis - 1] = eachSeries._global.yaxes[yaxis - 1] || {}; + + const myAxis = eachSeries._global.yaxes[yaxis - 1]; + myAxis.position = position || (yaxis % 2 ? 'left' : 'right'); + myAxis.min = min; + myAxis.max = max; + myAxis.axisLabelFontSizePixels = 11; + myAxis.axisLabel = label; + myAxis.axisLabelColour = color; + myAxis.axisLabelUseCanvas = true; + + if (tickDecimals) { + myAxis.tickDecimals = tickDecimals < 0 ? 0 : tickDecimals; + } + + if (units) { + const unitTokens = units.split(':'); + const unitType = unitTokens[0]; + if (!tickFormatters[unitType]) { + throw new Error ( + i18n.translate( + 'timelion.serverSideErrors.yaxisFunction.notSupportedUnitTypeErrorMessage', + { + defaultMessage: '{units} is not a supported unit type.', + values: { units }, + }) + ); + } + if (unitType === 'currency') { + const threeLetterCode = /^[A-Za-z]{3}$/; + const currency = unitTokens[1]; + if (currency && !threeLetterCode.test(currency)) { + throw new Error( + i18n.translate('timelion.serverSideErrors.yaxisFunction.notValidCurrencyFormatErrorMessage', { + defaultMessage: 'Currency must be a three letter code', + }) + ); + } + } + + myAxis.units = { + type: unitType, + prefix: unitTokens[1] || '', + suffix: unitTokens[2] || '' + }; + + if (unitType === 'percent') { + // jquery.flot uses axis.tickDecimals to generate tick values + // need 2 extra decimal places to preserve precision when percent shifts value to left + myAxis.units.tickDecimalsShift = 2; + if (tickDecimals) { + myAxis.tickDecimals += myAxis.units.tickDecimalsShift; + } else { + myAxis.tickDecimals = myAxis.units.tickDecimalsShift; + } + } + } + + return eachSeries; + }); + } +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/types.ts b/src/legacy/core_plugins/vis_type_timelion/server/types.ts new file mode 100644 index 0000000000000..4971fa34863d3 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/types.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + TimelionFunctionInterface, + TimelionFunctionConfig, + TimelionFunctionArgs, + TimelionFunctionArgsSuggestion, + TimelionFunctionArgsTypes, +} from './lib/classes/timelion_function'; diff --git a/visualize_app.ts.~LOCAL b/visualize_app.ts.~LOCAL new file mode 100644 index 0000000000000..ce44a5051d20d --- /dev/null +++ b/visualize_app.ts.~LOCAL @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { VisualizeKibanaServices } from './kibana_services'; + +// @ts-ignore +import { initEditorDirective } from './editor/editor'; +// @ts-ignore +import { initListingDirective } from './listing/visualize_listing'; + +export function initVisualizeAppDirective(app: any, deps: VisualizeKibanaServices) { + initEditorDirective(app, deps); + initListingDirective(app); +} From 7f3cebb540b96a0ad6894bc89bac9a01c7b72889 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 11 Dec 2019 18:44:56 +0300 Subject: [PATCH 07/59] Create ChartComponent --- .../public/components/chart.tsx | 39 +- .../public/components/timelion_vis.tsx | 8 +- .../vis_type_timelion/public/panels/panel.tsx | 402 ++++++++++++++++++ .../public/panels/timechart/schema.ts | 1 + .../timechart/{timechart.ts => timechart.tsx} | 9 +- .../panels/{panel.ts => useEventListener.ts} | 37 +- .../vis_type_timelion/public/plugin.ts | 12 +- .../public/timelion_vis_type.ts | 2 - .../public/vis_controller.ts | 92 ---- 9 files changed, 469 insertions(+), 133 deletions(-) create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx rename src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/{timechart.ts => timechart.tsx} (83%) rename src/legacy/core_plugins/vis_type_timelion/public/panels/{panel.ts => useEventListener.ts} (59%) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index 9ab3295a971a0..4bbb60b3fc253 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -18,9 +18,44 @@ */ import React from 'react'; +import { EuiFormErrorText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; -function ChartComponent(props: any) { - return
Charts
; +import { getServices } from '../kibana_services'; + +interface ChartComponentProp { + className?: string; + seriesList: any; + search?(): void; + interval: any; + rerenderTrigger: boolean; +} + +function ChartComponent({ seriesList, interval, search }: ChartComponentProp) { + if (!seriesList) { + return null; + } + + const panelScope = { seriesList, interval, search } as any; + panelScope.seriesList.render = seriesList.render || { + type: 'timechart', + }; + + const panelSchema = getServices().timelionPanels.get(panelScope.seriesList.render.type); + + if (!panelSchema) { + return ( + + + + ); + } + + return panelSchema(panelScope); } export { ChartComponent }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx index 6546297527903..c3b5676d066e8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -25,13 +25,13 @@ function TimelionVisComponent(props: any) { return (
- ) + ); } export { TimelionVisComponent }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx new file mode 100644 index 0000000000000..2dab2070f73ac --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -0,0 +1,402 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useRef } from 'react'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { debounce, compact, get,each } from 'lodash'; + +import './timechart/flot'; + +import { tickFormatters } from '../services/tick_formatters'; +// @ts-ignore +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { xaxisFormatterProvider } from './timechart/xaxis_formatter'; +import { generateTicksProvider } from './timechart/tick_generator'; + +import { useEventListener } from './useEventListener'; + +import { getServices } from '../kibana_services'; + +interface PanelProps { + name: string; +} + +const DEBOUNCE_DELAY = 50; + +function Panel(props: PanelProps) { + const { uiSettings, timefilter } = getServices(); + const formatters = tickFormatters() as any; + const getxAxisFormatter = xaxisFormatterProvider(uiSettings); + const generateTicks = generateTicksProvider(); + + moment.tz.setDefault(uiSettings.get('dateFormat:tz')); + + let canvasElem: any; + // @ts-ignore + const plot = $.plot(canvasElem, compact(series), options); + + let legendValueNumbers: any; + let legendCaption: any; + + const clearLegendNumbers = () => { + if (legendCaption) { + legendCaption.html(emptyCaption); + } + each(legendValueNumbers, function(num) { + $(num).empty(); + }); + }; + + const plothoverHandler = (event: any, pos: any, item: any) => { + if (!plot) { + return; + } + plot.setCrosshair(item); + debouncedSetLegendNumbers(item); + }; + const mouseleaveHandler = () => { + if (!plot) { + return; + } + plot.clearCrosshair(); + clearLegendNumbers(); + }; + const plotselectedHandler = (event: any, ranges: any) => { + timefilter.setTime({ + from: moment(ranges.xaxis.from), + to: moment(ranges.xaxis.to), + }); + }; + + const elementRef = useRef(null); + useEventListener(elementRef, 'plothover', plothoverHandler); + useEventListener(elementRef, 'plotselected', plotselectedHandler); + useEventListener(elementRef, 'mouseleave', mouseleaveHandler); + + const { + interval, + search, + seriesList: { list: chart }, + } = props; + + + let highlightedSeries: any; + let focusedSeries: any; + const originalColorMap = new Map(); + + + const unhighlightSeries = () => { + if (highlightedSeries === null) { + return; + } + + highlightedSeries = null; + focusedSeries = null; + chart.forEach((series: any) => { + series.color = originalColorMap.get(series); // reset the colors + }); + // drawPlot(chart); + }; + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = (pos: any) => { + unhighlightSeries(); + + const axes = plot.getAxes(); + if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { + return; + } + + let i; + const dataset = plot.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) + ); + } + for (i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines.show && !series.lines.steps; + const precision = get(series, '_meta.precision', 2); + + if (series._hide) continue; + + const currentPoint = series.data.find((point: any, index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (y != null) { + let label = y.toFixed(precision); + if (series.yaxis.tickFormatter) { + label = series.yaxis.tickFormatter(label, series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + }; + + const debouncedSetLegendNumbers = debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }); + + // ensure legend is the same height with or without a caption so legend items do not move around + const emptyCaption = '
'; + + const highlightSeries = debounce((id: any) => { + if (highlightedSeries === id) { + return; + } + + highlightedSeries = id; + chart.forEach((series: any, seriesIndex: any) => { + if (seriesIndex !== id) { + series.color = 'rgba(128,128,128,0.1)'; // mark as grey + } else { + series.color = originalColorMap.get(series); // color it like it was + } + }); + // drawPlot(chart); + }, DEBOUNCE_DELAY); + + const focusSeries = (id: any) => { + focusedSeries = id; + highlightSeries(id); + }; + + const toggleSeries = (id: any) => { + const series = chart[id]; + series._hide = !series._hide; + // drawPlot(chart); + }; + + const defaultOptions = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + grid: { + show: props.seriesList.render.grid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: any, series: any) { + return ( + toggleSeries(series._id)} + onFocus={() => focusSeries(series._id)} + onMouseover={() => highlightSeries(series._id)} + > + {label} + {label} + + ); + }, + }, + colors: [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', + ], + }; + + chart.forEach((series: any, seriesIndex: any) => { + if (!series.color) { + const colorIndex = seriesIndex % defaultOptions.colors.length; + series.color = defaultOptions.colors[colorIndex]; + } + originalColorMap.set(series, series.color); + }); + + // const cancelResize = observeResize($elem, function() { + // drawPlot($scope.chart); + // }); + + // $scope.$on('$destroy', function() { + // cancelResize(); + // $elem.off('plothover'); + // $elem.off('plotselected'); + // $elem.off('mouseleave'); + // }); + + let legendScope = {}; + + // const drawPlot = (plotConfig: any) => { + // if (!$('.chart-canvas', $elem).length) $elem.html(template); + // const canvasElem = $('.chart-canvas', $elem); + + // // we can't use `$.plot` to draw the chart when the height or width is 0 + // // so, we'll need another event to trigger drawPlot to actually draw it + // if (canvasElem.height() === 0 || canvasElem.width() === 0) { + // return; + // } + + // const title = _(plotConfig) + // .map('_title') + // .compact() + // .last() as any; + // $('.chart-top-title', $elem).text(title == null ? '' : title); + + // const options = _.cloneDeep(defaultOptions) as any; + + // // Get the X-axis tick format + // const time = timefilter.getBounds() as any; + // const interval = calculateInterval( + // time.min.valueOf(), + // time.max.valueOf(), + // uiSettings.get('timelion:target_buckets') || 200, + // $scope.interval, + // uiSettings.get('timelion:min_interval') || '1ms' + // ); + // const format = getxAxisFormatter(interval); + + // // Use moment to format ticks so we get timezone correction + // options.xaxis.tickFormatter = function(val: any) { + // return moment(val).format(format); + // }; + + // // Calculate how many ticks can fit on the axis + // const tickLetterWidth = 7; + // const tickPadding = 45; + // options.xaxis.ticks = Math.floor( + // $elem.width() / (format.length * tickLetterWidth + tickPadding) + // ); + + // const series = _.map(plotConfig, function(serie: any, index) { + // serie = _.cloneDeep( + // _.defaults(serie, { + // shadowSize: 0, + // lines: { + // lineWidth: 3, + // }, + // }) + // ); + // serie._id = index; + + // if (serie.color) { + // const span = document.createElement('span'); + // span.style.color = serie.color; + // serie.color = span.style.color; + // } + + // if (serie._hide) { + // serie.data = []; + // serie.stack = false; + // // serie.color = "#ddd"; + // serie.label = '(hidden) ' + serie.label; + // } + + // if (serie._global) { + // _.merge(options, serie._global, function(objVal, srcVal) { + // // This is kind of gross, it means that you can't replace a global value with a null + // // best you can do is an empty string. Deal with it. + // if (objVal == null) return srcVal; + // if (srcVal == null) return objVal; + // }); + // } + + // return serie; + // }); + + // if (options.yaxes) { + // options.yaxes.forEach((yaxis: any) => { + // if (yaxis && yaxis.units) { + // yaxis.tickFormatter = formatters[yaxis.units.type]; + // const byteModes = ['bytes', 'bytes/s']; + // if (byteModes.includes(yaxis.units.type)) { + // yaxis.tickGenerator = generateTicks; + // } + // } + // }); + // } + + // // @ts-ignore + // $scope.plot = $.plot(canvasElem, _.compact(series), options); + + // if ($scope.plot) { + // $scope.$emit('timelionChartRendered'); + // } + + // legendScope.$destroy(); + // legendScope = $scope.$new(); + // // Used to toggle the series, and for displaying values on hover + // legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); + // _.each(canvasElem.find('.ngLegendValue'), function(elem) { + // $compile(elem)(legendScope); + // }); + + // if (_.get($scope.plot.getData(), '[0]._global.legend.showTime', true)) { + // legendCaption = $(''); + // legendCaption.html(emptyCaption); + // canvasElem.find('div.legend table').append(legendCaption); + + // // legend has been re-created. Apply focus on legend element when previously set + // if (focusedSeries || focusedSeries === 0) { + // const $legendLabels = canvasElem.find('div.legend table .legendLabel>span'); + // $legendLabels.get(focusedSeries).focus(); + // } + // } + // } + + return ( +
+
New Panel
+
+ ); +} + +export { Panel }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts index 3c663d664c519..afe28deed3d1a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts @@ -17,6 +17,7 @@ * under the License. */ +import React from 'react'; import './flot'; import _ from 'lodash'; import $ from 'jquery'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx similarity index 83% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts rename to src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx index 4173bfeb331e2..e51c6b3520108 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx @@ -17,12 +17,15 @@ * under the License. */ -import { timechartFn } from './schema'; +import React from 'react'; + import { Panel } from '../panel'; import { TimelionVisualizationDependencies } from '../../plugin'; -export function getTimeChart(dependencies: TimelionVisualizationDependencies) { +function getTimeChart(dependencies: TimelionVisualizationDependencies) { // Schema is broken out so that it may be extended for use in other plugins // Its also easier to test. - return new Panel('timechart', timechartFn(dependencies)()); + return ['timechart', (props: any) => ]; } + +export { getTimeChart }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts similarity index 59% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts rename to src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts index 3512cd96a9596..56d5de3fef715 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts @@ -17,29 +17,18 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; +import { useEffect } from 'react'; -interface PanelConfig { - help?: string; - render?: Function; -} - -export class Panel { - name: string; - help: string; - render: Function | undefined; - - constructor(name: string, config: PanelConfig) { - this.name = name; - this.help = config.help || ''; - this.render = config.render; - - if (!config.render) { - throw new Error( - i18n.translate('timelion.panels.noRenderFunctionErrorMessage', { - defaultMessage: 'Panel must have a rendering function', - }) - ); +export const useEventListener = (target: any, type: any, listener: any) => { + useEffect(() => { + const targetIsRef = target.hasOwnProperty('current'); + const currentTarget = targetIsRef ? target.current : target; + if (currentTarget) { + currentTarget.addEventListener(type, listener); } - } -} + return () => { + if (currentTarget) { + currentTarget.removeEventListener(type, listener);} + }; + }, [target, type, listener]); +}; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 1d5b965bf5d66..5d1ee63d96677 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -39,7 +39,7 @@ import { getTimelionVisDefinition } from './timelion_vis_type'; export interface TimelionVisualizationDependencies { uiSettings: IUiSettingsClient; http: HttpSetup; - timelionPanels: Map; + timelionPanels: Map; timefilter: TimefilterContract; } @@ -62,26 +62,26 @@ export class TimelionVisPlugin implements Plugin { core: CoreSetup, { expressions, visualizations, data }: TimelionPluginSetupDependencies ) { - const timelionPanels: Map = new Map(); + const timelionPanels: Map = new Map(); - setServices({ timelionPanels }); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, http: core.http, timelionPanels, timefilter: data.query.timefilter.timefilter, }; + setServices(dependencies); this.registerPanels(dependencies); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(getTimelionVisDefinition(dependencies)); + visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { - const timeChartPanel: Panel = getTimeChart(dependencies); + const [name, timeChartPanel] = getTimeChart(dependencies); - dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); + dependencies.timelionPanels.set(name as string, timeChartPanel); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts index 42a08cb9724f0..141e0d2312c0b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts @@ -25,7 +25,6 @@ import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; import { TimelionVisComponent } from './components/timelion_vis'; import editorConfigTemplate from './timelion_vis_params.html'; import { TimelionVisualizationDependencies } from './plugin'; -import { VisController } from './vis_controller'; export const TIMELION_VIS_NAME = 'timelion'; @@ -41,7 +40,6 @@ export function getTimelionVisDefinition(dependencies: TimelionVisualizationDepe description: i18n.translate('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - visualization: VisController, visConfig: { defaults: { expression: '.es(*)', diff --git a/src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts deleted file mode 100644 index 5c427fa09ac81..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/vis_controller.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { Fragment } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; - - class VisController { - el: any; - vis: any; - - constructor(el: any, vis: any) { - this.el = el; - this.vis = vis; - } - - async render(visData, visParams, status) { - if (status.params || (visParams.useTimeFilter && status.time)) { - this.visParams = visParams; - this.drawVis(); - } - } - - destroy() { - unmountComponentAtNode(this.el); - } - - drawVis = () => { - const text = 'Vis controller'; - render(null, this.el); - }; - - async initControls() { - // const controlParamsList = this.visParams.controls.filter((controlParams) => { - // // ignore controls that do not have indexPattern or field - // return controlParams.indexPattern && controlParams.fieldName; - // }); - - // const controlFactoryPromises = controlParamsList.map((controlParams) => { - // const factory = controlFactory(controlParams); - // return factory(controlParams, this.visParams.useTimeFilter, SearchSource); - // }); - // const controls = await Promise.all(controlFactoryPromises); - - // const getControl = (id) => { - // return controls.find(control => { - // return id === control.id; - // }); - // }; - - // const controlInitPromises = []; - // getLineageMap(controlParamsList).forEach((lineage, controlId) => { - // // first lineage item is the control. remove it - // lineage.shift(); - // const ancestors = []; - // lineage.forEach(ancestorId => { - // ancestors.push(getControl(ancestorId)); - // }); - // const control = getControl(controlId); - // control.setAncestors(ancestors); - // controlInitPromises.push(control.fetch()); - // }); - - // await Promise.all(controlInitPromises); - // return controls; - } - - stageFilter = async (controlIndex, newValue) => { - if (this.visParams.updateFiltersOnChange) { - } else { - this.drawVis(); - } - } -} - -export { VisController }; From f428d437de1066d940a0131cfcf18993c24d46ed Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 12 Dec 2019 17:47:49 +0300 Subject: [PATCH 08/59] Render chart in react --- .../public/kibana_services.ts | 2 + .../public/panels/constants.ts | 53 +++ .../vis_type_timelion/public/panels/panel.tsx | 444 +++++++++--------- .../public/panels/useEventListener.ts | 3 +- 4 files changed, 275 insertions(+), 227 deletions(-) create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts index 93d9ad7c39c5a..3963ea9dd15d4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts @@ -21,6 +21,8 @@ import { Panel } from './panels/panel'; export interface TimelionKibanaServices { timelionPanels: Map; + uiSettings: any; + timefilter: any; } let services: TimelionKibanaServices | null = null; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts new file mode 100644 index 0000000000000..9793e70fd7cfc --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const DEBOUNCE_DELAY = 50; +// ensure legend is the same height with or without a caption so legend items do not move around +const emptyCaption = '
'; + +const staticDefaultOptions = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + colors: [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', + ], +}; + +export { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index 2dab2070f73ac..d321b9b6926eb 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -20,7 +20,7 @@ import React, { useRef } from 'react'; import $ from 'jquery'; import moment from 'moment-timezone'; -import { debounce, compact, get,each } from 'lodash'; +import { debounce, compact, get, each, noop } from 'lodash'; import './timechart/flot'; @@ -33,33 +33,208 @@ import { generateTicksProvider } from './timechart/tick_generator'; import { useEventListener } from './useEventListener'; import { getServices } from '../kibana_services'; +import { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions } from './constants'; interface PanelProps { name: string; + interval: any; + search?: any; + seriesList: any; } -const DEBOUNCE_DELAY = 50; - -function Panel(props: PanelProps) { +function Panel({ interval: intervalProp, search = noop, seriesList }: PanelProps) { + console.log('Panel') + const chart = seriesList.list; + const elementRef = useRef(null); + const canvasElem = useRef(null); + const legendElem = useRef(null); const { uiSettings, timefilter } = getServices(); const formatters = tickFormatters() as any; const getxAxisFormatter = xaxisFormatterProvider(uiSettings); const generateTicks = generateTicksProvider(); - moment.tz.setDefault(uiSettings.get('dateFormat:tz')); + if (!canvasElem.current) { + //return null; + } + + let legendValueNumbers: any = []; + let legendCaption: any; + + let highlightedSeries: any; + let focusedSeries: any; + const originalColorMap = new Map(); + + let legendScope = {}; + + const highlightSeries = debounce((id: any) => { + if (highlightedSeries === id) { + return; + } + + highlightedSeries = id; + chart.forEach((series: any, seriesIndex: any) => { + if (seriesIndex !== id) { + series.color = 'rgba(128,128,128,0.1)'; // mark as grey + } else { + series.color = originalColorMap.get(series); // color it like it was + } + }); + // drawPlot(chart); + }, DEBOUNCE_DELAY); + + const focusSeries = (id: any) => { + focusedSeries = id; + highlightSeries(id); + }; + + const toggleSeries = (id: any) => { + const series = chart[id]; + series._hide = !series._hide; + // drawPlot(chart); + }; + + const reactLegend = ( + {}}> + + + + ); + + const defaultOptions = { + ...staticDefaultOptions, + grid: { + show: seriesList.render.grid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: any, series: any) { + const legend = $( + ` + ${label} + + ` + ); + legend.find('.ngLegendValue').click(() => toggleSeries(series._id)); + const legendNumber = ``; + legendValueNumbers.push(legendNumber); + // if (legendElem.current) { + // legendElem.current.firstElementChild.innerText = label; + // legendElem.current.onclick = () => toggleSeries(series._id); + // } + // return ` + // ${label} + // ${legendNumber} + // `; + return legend.html(); + }, + }, + }; + + $(canvasElem.current).find('.legendLabel').click(() => toggleSeries(0)); + const options = _.cloneDeep(defaultOptions) as any; + + // Get the X-axis tick format + const time = timefilter.getBounds() as any; + const interval = calculateInterval( + time.min.valueOf(), + time.max.valueOf(), + uiSettings.get('timelion:target_buckets') || 200, + intervalProp, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = getxAxisFormatter(interval); + + // Use moment to format ticks so we get timezone correction + options.xaxis.tickFormatter = function (val: any) { + return moment(val).format(format); + }; + + // Calculate how many ticks can fit on the axis + const tickLetterWidth = 7; + const tickPadding = 45; + options.xaxis.ticks = Math.floor( + elementRef.current + ? elementRef.current.clientWidth + : 0 / (format.length * tickLetterWidth + tickPadding) + ); + + const series = _.map(chart, function (serie: any, index) { + serie = _.cloneDeep( + _.defaults(serie, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + serie._id = index; + + if (serie.color) { + const span = document.createElement('span'); + span.style.color = serie.color; + serie.color = span.style.color; + } + + if (serie._hide) { + serie.data = []; + serie.stack = false; + // serie.color = "#ddd"; + serie.label = '(hidden) ' + serie.label; + } + + if (serie._global) { + _.merge(options, serie._global, function (objVal, srcVal) { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) return srcVal; + if (srcVal == null) return objVal; + }); + } + + return serie; + }); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: any) => { + if (yaxis && yaxis.units) { + yaxis.tickFormatter = formatters[yaxis.units.type]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicks; + } + } + }); + } - let canvasElem: any; // @ts-ignore - const plot = $.plot(canvasElem, compact(series), options); + const plot = + canvasElem.current && + canvasElem.current.clientHeight > 0 && + canvasElem.current.clientWidth > 0 + ? $.plot(canvasElem.current, compact(series), options) + : null; - let legendValueNumbers: any; - let legendCaption: any; + moment.tz.setDefault(uiSettings.get('dateFormat:tz')); const clearLegendNumbers = () => { if (legendCaption) { legendCaption.html(emptyCaption); } - each(legendValueNumbers, function(num) { + each(legendValueNumbers, (num: any) => { $(num).empty(); }); }; @@ -85,23 +260,10 @@ function Panel(props: PanelProps) { }); }; - const elementRef = useRef(null); useEventListener(elementRef, 'plothover', plothoverHandler); useEventListener(elementRef, 'plotselected', plotselectedHandler); useEventListener(elementRef, 'mouseleave', mouseleaveHandler); - const { - interval, - search, - seriesList: { list: chart }, - } = props; - - - let highlightedSeries: any; - let focusedSeries: any; - const originalColorMap = new Map(); - - const unhighlightSeries = () => { if (highlightedSeries === null) { return; @@ -169,91 +331,7 @@ function Panel(props: PanelProps) { trailing: false, }); - // ensure legend is the same height with or without a caption so legend items do not move around - const emptyCaption = '
'; - - const highlightSeries = debounce((id: any) => { - if (highlightedSeries === id) { - return; - } - - highlightedSeries = id; - chart.forEach((series: any, seriesIndex: any) => { - if (seriesIndex !== id) { - series.color = 'rgba(128,128,128,0.1)'; // mark as grey - } else { - series.color = originalColorMap.get(series); // color it like it was - } - }); - // drawPlot(chart); - }, DEBOUNCE_DELAY); - - const focusSeries = (id: any) => { - focusedSeries = id; - highlightSeries(id); - }; - - const toggleSeries = (id: any) => { - const series = chart[id]; - series._hide = !series._hide; - // drawPlot(chart); - }; - - const defaultOptions = { - xaxis: { - mode: 'time', - tickLength: 5, - timezone: 'browser', - }, - selection: { - mode: 'x', - color: '#ccc', - }, - crosshair: { - mode: 'x', - color: '#C66', - lineWidth: 2, - }, - grid: { - show: props.seriesList.render.grid, - borderWidth: 0, - borderColor: null, - margin: 10, - hoverable: true, - autoHighlight: false, - }, - legend: { - backgroundColor: 'rgb(255,255,255,0)', - position: 'nw', - labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: any, series: any) { - return ( - toggleSeries(series._id)} - onFocus={() => focusSeries(series._id)} - onMouseover={() => highlightSeries(series._id)} - > - {label} - {label} - - ); - }, - }, - colors: [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', - ], - }; - + // setting originalColorMap chart.forEach((series: any, seriesIndex: any) => { if (!series.color) { const colorIndex = seriesIndex % defaultOptions.colors.length; @@ -273,128 +351,42 @@ function Panel(props: PanelProps) { // $elem.off('mouseleave'); // }); - let legendScope = {}; - // const drawPlot = (plotConfig: any) => { - // if (!$('.chart-canvas', $elem).length) $elem.html(template); - // const canvasElem = $('.chart-canvas', $elem); - - // // we can't use `$.plot` to draw the chart when the height or width is 0 - // // so, we'll need another event to trigger drawPlot to actually draw it - // if (canvasElem.height() === 0 || canvasElem.width() === 0) { - // return; - // } - - // const title = _(plotConfig) - // .map('_title') - // .compact() - // .last() as any; - // $('.chart-top-title', $elem).text(title == null ? '' : title); - - // const options = _.cloneDeep(defaultOptions) as any; - - // // Get the X-axis tick format - // const time = timefilter.getBounds() as any; - // const interval = calculateInterval( - // time.min.valueOf(), - // time.max.valueOf(), - // uiSettings.get('timelion:target_buckets') || 200, - // $scope.interval, - // uiSettings.get('timelion:min_interval') || '1ms' - // ); - // const format = getxAxisFormatter(interval); - - // // Use moment to format ticks so we get timezone correction - // options.xaxis.tickFormatter = function(val: any) { - // return moment(val).format(format); - // }; - - // // Calculate how many ticks can fit on the axis - // const tickLetterWidth = 7; - // const tickPadding = 45; - // options.xaxis.ticks = Math.floor( - // $elem.width() / (format.length * tickLetterWidth + tickPadding) - // ); - - // const series = _.map(plotConfig, function(serie: any, index) { - // serie = _.cloneDeep( - // _.defaults(serie, { - // shadowSize: 0, - // lines: { - // lineWidth: 3, - // }, - // }) - // ); - // serie._id = index; - - // if (serie.color) { - // const span = document.createElement('span'); - // span.style.color = serie.color; - // serie.color = span.style.color; - // } - - // if (serie._hide) { - // serie.data = []; - // serie.stack = false; - // // serie.color = "#ddd"; - // serie.label = '(hidden) ' + serie.label; - // } - - // if (serie._global) { - // _.merge(options, serie._global, function(objVal, srcVal) { - // // This is kind of gross, it means that you can't replace a global value with a null - // // best you can do is an empty string. Deal with it. - // if (objVal == null) return srcVal; - // if (srcVal == null) return objVal; - // }); - // } - - // return serie; - // }); - - // if (options.yaxes) { - // options.yaxes.forEach((yaxis: any) => { - // if (yaxis && yaxis.units) { - // yaxis.tickFormatter = formatters[yaxis.units.type]; - // const byteModes = ['bytes', 'bytes/s']; - // if (byteModes.includes(yaxis.units.type)) { - // yaxis.tickGenerator = generateTicks; - // } - // } - // }); - // } - - // // @ts-ignore - // $scope.plot = $.plot(canvasElem, _.compact(series), options); - - // if ($scope.plot) { - // $scope.$emit('timelionChartRendered'); - // } - - // legendScope.$destroy(); - // legendScope = $scope.$new(); - // // Used to toggle the series, and for displaying values on hover - // legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); - // _.each(canvasElem.find('.ngLegendValue'), function(elem) { - // $compile(elem)(legendScope); - // }); - - // if (_.get($scope.plot.getData(), '[0]._global.legend.showTime', true)) { - // legendCaption = $(''); - // legendCaption.html(emptyCaption); - // canvasElem.find('div.legend table').append(legendCaption); - - // // legend has been re-created. Apply focus on legend element when previously set - // if (focusedSeries || focusedSeries === 0) { - // const $legendLabels = canvasElem.find('div.legend table .legendLabel>span'); - // $legendLabels.get(focusedSeries).focus(); - // } - // } + // } + // we can't use `$.plot` to draw the chart when the height or width is 0 + // so, we'll need another event to trigger drawPlot to actually draw it + + const title = + (_(chart) + .map('_title') + .compact() + .last() as any) || ''; + + // // Used to toggle the series, and for displaying values on hover + // legendValueNumbers = + // canvasElem && canvasElem.current && canvasElem.current.find('.ngLegendValueNumber'); + + if (plot && get(plot.getData(), '[0]._global.legend.showTime', true)) { + legendCaption = $(''); + legendCaption.html(emptyCaption); + // canvasElem.current.find('div.legend table').append(legendCaption); + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + // const $legendLabels = canvasElem.current.find('div.legend table .legendLabel>span'); + //$legendLabels.get(focusedSeries).focus(); + } + } + return ( -
-
New Panel
+
+
+ {title} +
+
+ {reactLegend}
); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts index 56d5de3fef715..43f47d5032680 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts @@ -28,7 +28,8 @@ export const useEventListener = (target: any, type: any, listener: any) => { } return () => { if (currentTarget) { - currentTarget.removeEventListener(type, listener);} + currentTarget.removeEventListener(type, listener); + } }; }, [target, type, listener]); }; From 8287a0e6e1a859e31e4e193d379b6566bfc5f54f Mon Sep 17 00:00:00 2001 From: sulemanof Date: Thu, 12 Dec 2019 16:29:23 +0300 Subject: [PATCH 09/59] Reactify timelion editor --- package.json | 1 + .../core_plugins/timelion/common/types.ts | 46 +++ .../timelion/public/components/index.ts | 21 ++ .../components/timelion_expression_input.tsx | 135 +++++++++ .../timelion_expression_input_helpers.ts | 268 ++++++++++++++++++ .../public/components/timelion_interval.tsx | 112 ++++++++ .../public/services/arg_value_suggestions.ts | 216 ++++++++++++++ .../timelion/public/timelion_vis_fn.ts | 6 +- .../public/vis/{index.ts => index.tsx} | 20 +- .../timelion/public/vis/timelion_options.tsx | 60 ++++ .../public/vis/timelion_vis_params.html | 27 -- .../server/lib/classes/timelion_function.d.ts | 17 +- .../core_plugins/timelion/server/types.ts | 8 +- .../public/code_editor/code_editor.tsx | 14 + yarn.lock | 5 + 15 files changed, 902 insertions(+), 54 deletions(-) create mode 100644 src/legacy/core_plugins/timelion/common/types.ts create mode 100644 src/legacy/core_plugins/timelion/public/components/index.ts create mode 100644 src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx create mode 100644 src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts create mode 100644 src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx create mode 100644 src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts rename src/legacy/core_plugins/timelion/public/vis/{index.ts => index.tsx} (75%) create mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx delete mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html diff --git a/package.json b/package.json index 847f09b4ab4cf..9be664ec29473 100644 --- a/package.json +++ b/package.json @@ -334,6 +334,7 @@ "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", "@types/opn": "^5.1.0", + "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", diff --git a/src/legacy/core_plugins/timelion/common/types.ts b/src/legacy/core_plugins/timelion/common/types.ts new file mode 100644 index 0000000000000..f7084948a14f7 --- /dev/null +++ b/src/legacy/core_plugins/timelion/common/types.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null'; + +interface TimelionFunctionArgsSuggestion { + name: string; + help: string; +} + +export interface TimelionFunctionArgs { + name: string; + help?: string; + multi?: boolean; + types: TimelionFunctionArgsTypes[]; + suggestions?: TimelionFunctionArgsSuggestion[]; +} + +export interface ITimelionFunction { + aliases: string[]; + args: TimelionFunctionArgs[]; + name: string; + help: string; + chainable: boolean; + extended: boolean; + isAlias: boolean; + argsByName: { + [key: string]: TimelionFunctionArgs[]; + }; +} diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/timelion/public/components/index.ts new file mode 100644 index 0000000000000..8d7d32a3ba262 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './timelion_expression_input'; +export * from './timelion_interval'; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx new file mode 100644 index 0000000000000..ed0b2dbb9e18f --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -0,0 +1,135 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useCallback, useRef } from 'react'; +import { EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; + +import { IUiSettingsClient, HttpSetup } from 'kibana/public'; +import { CodeEditor, KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; +import { suggest, getSuggestion } from './timelion_expression_input_helpers'; +import { ArgValueSuggestions } from '../services/arg_value_suggestions'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; + +const LANGUAGE_ID = 'timelion_expression'; +monacoEditor.languages.register({ id: LANGUAGE_ID }); + +export interface TimelionExpressionInputDependencies { + argValueSuggestions: ArgValueSuggestions; + http: HttpSetup; + uiSettings: IUiSettingsClient; +} + +interface TimelionExpressionInputProps { + value: string; + setValue(value: string): void; +} + +function TimelionExpressionInput({ + argValueSuggestions, + http, + uiSettings, + value, + setValue, +}: TimelionExpressionInputProps & TimelionExpressionInputDependencies) { + const functionList = useRef([]); + + const provideCompletionItems = useCallback( + async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { + const text = model.getValue(); + const wordUntil = model.getWordUntilPosition(position); + const wordRange = new monacoEditor.Range( + position.lineNumber, + wordUntil.startColumn, + position.lineNumber, + wordUntil.endColumn + ); + + const suggestions = await suggest( + text, + functionList.current, + // it's important to offset the cursor position on 1 point left + // because of PEG parser starts the line with 0, but monaco with 1 + position.column - 1, + argValueSuggestions + ); + + return { + suggestions: suggestions + ? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => + getSuggestion(s, suggestions.type, wordRange) + ) + : [], + }; + }, + [argValueSuggestions] + ); + + useEffect(() => { + http.get('../api/timelion/functions').then(data => { + functionList.current = data; + }); + }, [http]); + + return ( + + +
+ +
+
+
+ ); +} + +export { TimelionExpressionInput }; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts new file mode 100644 index 0000000000000..06793eeab99e2 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts @@ -0,0 +1,268 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get, startsWith } from 'lodash'; +import PEG from 'pegjs'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; + +// @ts-ignore +import grammar from 'raw-loader!../chain.peg'; + +import { i18n } from '@kbn/i18n'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; +import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions'; + +const Parser = PEG.generate(grammar); + +export enum SUGGESTION_TYPE { + ARGUMENTS = 'arguments', + ARGUMENT_VALUE = 'argument_value', + FUNCTIONS = 'functions', +} + +function inLocation(cursorPosition: number, location: Location) { + return cursorPosition >= location.min && cursorPosition <= location.max; +} + +function getArgumentsHelp( + functionHelp: ITimelionFunction | undefined, + functionArgs: FunctionArg[] = [] +) { + if (!functionHelp) { + return []; + } + + // Do not provide 'inputSeries' as argument suggestion for chainable functions + const argsHelp = functionHelp.chainable ? functionHelp.args.slice(1) : functionHelp.args.slice(0); + + // ignore arguments that are already provided in function declaration + const functionArgNames = functionArgs.map(arg => arg.name); + return argsHelp.filter(arg => !functionArgNames.includes(arg.name)); +} + +async function extractSuggestionsFromParsedResult( + result: ReturnType, + cursorPosition: number, + functionList: ITimelionFunction[], + argValueSuggestions: ArgValueSuggestions +) { + const activeFunc = result.functions.find(({ location }: { location: Location }) => + inLocation(cursorPosition, location) + ); + + if (!activeFunc) { + return; + } + + const functionHelp = functionList.find(({ name }) => name === activeFunc.function); + + if (!functionHelp) { + return; + } + + // return function suggestion when cursor is outside of parentheses + // location range includes '.', function name, and '('. + const openParen = activeFunc.location.min + activeFunc.function.length + 2; + if (cursorPosition < openParen) { + return { list: [functionHelp], type: SUGGESTION_TYPE.FUNCTIONS }; + } + + // return argument value suggestions when cursor is inside argument value + const activeArg = activeFunc.arguments.find((argument: FunctionArg) => { + return inLocation(cursorPosition, argument.location); + }); + if ( + activeArg && + activeArg.type === 'namedArg' && + inLocation(cursorPosition, activeArg.value.location) + ) { + const { function: functionName, arguments: functionArgs } = activeFunc; + + const { + name: argName, + value: { text: partialInput }, + } = activeArg; + + let valueSuggestions; + if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { + valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( + functionName, + argName, + functionArgs, + partialInput + ); + } else { + const { suggestions: staticSuggestions } = + functionHelp.args.find(arg => arg.name === activeArg.name) || {}; + valueSuggestions = argValueSuggestions.getStaticSuggestionsForInput( + partialInput, + staticSuggestions + ); + } + return { + list: valueSuggestions, + type: SUGGESTION_TYPE.ARGUMENT_VALUE, + }; + } + + // return argument suggestions + const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments); + const argumentSuggestions = argsHelp.filter(arg => { + if (get(activeArg, 'type') === 'namedArg') { + return startsWith(arg.name, activeArg.name); + } else if (activeArg) { + return startsWith(arg.name, activeArg.text); + } + return true; + }); + return { list: argumentSuggestions, type: SUGGESTION_TYPE.ARGUMENTS }; +} + +export async function suggest( + expression: string, + functionList: ITimelionFunction[], + cursorPosition: number, + argValueSuggestions: ArgValueSuggestions +) { + try { + const result = await Parser.parse(expression); + + return await extractSuggestionsFromParsedResult( + result, + cursorPosition, + functionList, + argValueSuggestions + ); + } catch (err) { + let message: any; + try { + // The grammar will throw an error containing a message if the expression is formatted + // correctly and is prepared to accept suggestions. If the expression is not formatted + // correctly the grammar will just throw a regular PEG SyntaxError, and this JSON.parse + // attempt will throw an error. + message = JSON.parse(err.message); + } catch (e) { + // The expression isn't correctly formatted, so JSON.parse threw an error. + return; + } + + switch (message.type) { + case 'incompleteFunction': { + let list; + if (message.function) { + // The user has start typing a function name, so we'll filter the list down to only + // possible matches. + list = functionList.filter(func => startsWith(func.name, message.function)); + } else { + // The user hasn't typed anything yet, so we'll just return the entire list. + list = functionList; + } + return { list, type: SUGGESTION_TYPE.FUNCTIONS }; + } + case 'incompleteArgument': { + const { currentFunction: functionName, currentArgs: functionArgs } = message; + const functionHelp = functionList.find(func => func.name === functionName); + return { + list: getArgumentsHelp(functionHelp, functionArgs), + type: SUGGESTION_TYPE.ARGUMENTS, + }; + } + case 'incompleteArgumentValue': { + const { name: argName, currentFunction: functionName, currentArgs: functionArgs } = message; + let valueSuggestions = []; + if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { + valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( + functionName, + argName, + functionArgs + ); + } else { + const functionHelp = functionList.find(func => func.name === functionName); + if (functionHelp) { + const argHelp = functionHelp.args.find(arg => arg.name === argName); + if (argHelp && argHelp.suggestions) { + valueSuggestions = argHelp.suggestions; + } + } + } + return { + list: valueSuggestions, + type: SUGGESTION_TYPE.ARGUMENT_VALUE, + }; + } + } + } +} + +export function getSuggestion( + suggestion: ITimelionFunction | TimelionFunctionArgs, + type: SUGGESTION_TYPE, + range: monacoEditor.Range +) { + let kind: monacoEditor.languages.CompletionItemKind = + monacoEditor.languages.CompletionItemKind.Method; + let insertText: string = suggestion.name; + let detail: string = ''; + + switch (type) { + case SUGGESTION_TYPE.ARGUMENTS: + kind = monacoEditor.languages.CompletionItemKind.Property; + insertText = `${insertText}=`; + detail = `${i18n.translate( + 'timelion.expressionSuggestions.argument.description.acceptsText', + { + defaultMessage: 'Accepts', + } + )}: ${(suggestion as TimelionFunctionArgs).types}`; + + break; + case SUGGESTION_TYPE.FUNCTIONS: + kind = monacoEditor.languages.CompletionItemKind.Function; + detail = `(${ + (suggestion as ITimelionFunction).chainable + ? i18n.translate('timelion.expressionSuggestions.func.description.chainableText', { + defaultMessage: 'Chainable', + }) + : i18n.translate('timelion.expressionSuggestions.func.description.dataSourceText', { + defaultMessage: 'Data source', + }) + })`; + + break; + case SUGGESTION_TYPE.ARGUMENT_VALUE: + kind = monacoEditor.languages.CompletionItemKind.Property; + insertText = suggestion.name.split(':')[1] || suggestion.name; + detail = suggestion.help || ''; + + break; + } + + return { + detail, + insertText, + kind, + label: suggestion.name, + documentation: suggestion.help, + command: { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }, + range, + }; +} diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx new file mode 100644 index 0000000000000..e7cac126321fd --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx @@ -0,0 +1,112 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useMemo } from 'react'; +import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +const intervalOptions = [ + { + label: i18n.translate('timelion.vis.interval.auto', { + defaultMessage: 'Auto', + }), + value: 'auto', + }, + { + label: i18n.translate('timelion.vis.interval.second', { + defaultMessage: '1 second', + }), + value: '1s', + }, + { + label: i18n.translate('timelion.vis.interval.minute', { + defaultMessage: '1 minute', + }), + value: '1m', + }, + { + label: i18n.translate('timelion.vis.interval.hour', { + defaultMessage: '1 hour', + }), + value: '1h', + }, + { + label: i18n.translate('timelion.vis.interval.week', { + defaultMessage: '1 week', + }), + value: '1w', + }, + { + label: i18n.translate('timelion.vis.interval.month', { + defaultMessage: '1 month', + }), + value: '1M', + }, + { + label: i18n.translate('timelion.vis.interval.year', { + defaultMessage: '1 year', + }), + value: '1y', + }, +]; + +interface TimelionIntervalProps { + value: string; + setValue(value: string): void; +} + +function TimelionInterval({ value, setValue }: TimelionIntervalProps) { + const onCustomInterval = (customValue: string) => { + setValue(customValue.trim()); + }; + + const onChange = (opts: Array>) => { + setValue((opts[0] && opts[0].value) || ''); + }; + + const selectedOptions = useMemo( + () => [intervalOptions.find(op => op.value === value) || { label: value, value }], + [value] + ); + + return ( + + + + ); +} + +export { TimelionInterval }; diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts new file mode 100644 index 0000000000000..998562098719d --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts @@ -0,0 +1,216 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get } from 'lodash'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { IndexPatternsContract } from 'src/plugins/data/public'; +import { TimelionFunctionArgs } from '../../common/types'; + +export interface Location { + min: number; + max: number; +} + +export interface FunctionArg { + function: string; + location: Location; + name: string; + text: string; + type: string; + value: { + location: Location; + text: string; + type: string; + value: string; + }; +} + +export function getArgValueSuggestions( + indexPatterns: IndexPatternsContract, + savedObjectsClient: SavedObjectsClientContract +) { + async function getIndexPattern(functionArgs: FunctionArg[]) { + const indexPatternArg = functionArgs.find(({ name }) => name === 'index'); + if (!indexPatternArg) { + // index argument not provided + return; + } + const indexPatternTitle = get(indexPatternArg, 'value.text'); + + const { savedObjects } = await savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title'], + search: `"${indexPatternTitle}"`, + searchFields: ['title'], + perPage: 10, + }); + const indexPatternSavedObject = savedObjects.find( + ({ attributes }) => attributes.title === indexPatternTitle + ); + if (!indexPatternSavedObject) { + // index argument does not match an index pattern + return; + } + + return await indexPatterns.get(indexPatternSavedObject.id); + } + + function containsFieldName(partial: string, field: { name: string }) { + if (!partial) { + return true; + } + return field.name.includes(partial); + } + + // Argument value suggestion handlers requiring custom client side code + // Could not put with function definition since functions are defined on server + const customHandlers = { + es: { + async index(partial: string) { + const search = partial ? `${partial}*` : '*'; + const resp = await savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title', 'type'], + search: `${search}`, + searchFields: ['title'], + perPage: 25, + }); + return resp.savedObjects + .filter(savedObject => !savedObject.get('type')) + .map(savedObject => { + return { name: savedObject.attributes.title }; + }); + }, + async metric(partial: string, functionArgs: FunctionArg[]) { + if (!partial || !partial.includes(':')) { + return [ + { name: 'avg:' }, + { name: 'cardinality:' }, + { name: 'count' }, + { name: 'max:' }, + { name: 'min:' }, + { name: 'percentiles:' }, + { name: 'sum:' }, + ]; + } + + const indexPattern = await getIndexPattern(functionArgs); + if (!indexPattern) { + return []; + } + + const valueSplit = partial.split(':'); + return indexPattern.fields + .filter(field => { + return ( + field.aggregatable && + 'number' === field.type && + containsFieldName(valueSplit[1], field) + ); + }) + .map(field => { + return { name: `${valueSplit[0]}:${field.name}`, help: field.type }; + }); + }, + async split(partial: string, functionArgs: FunctionArg[]) { + const indexPattern = await getIndexPattern(functionArgs); + if (!indexPattern) { + return []; + } + + return indexPattern.fields + .filter(field => { + return ( + field.aggregatable && + ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) && + containsFieldName(partial, field) + ); + }) + .map(field => { + return { name: field.name, help: field.type }; + }); + }, + async timefield(partial: string, functionArgs: FunctionArg[]) { + const indexPattern = await getIndexPattern(functionArgs); + if (!indexPattern) { + return []; + } + + return indexPattern.fields + .filter(field => { + return 'date' === field.type && containsFieldName(partial, field); + }) + .map(field => { + return { name: field.name }; + }); + }, + }, + }; + + return { + /** + * @param {string} functionName - user provided function name containing argument + * @param {string} argName - user provided argument name + * @return {boolean} true when dynamic suggestion handler provided for function argument + */ + hasDynamicSuggestionsForArgument: ( + functionName: T, + argName: keyof typeof customHandlers[T] + ) => { + return customHandlers[functionName] && customHandlers[functionName][argName]; + }, + + /** + * @param {string} functionName - user provided function name containing argument + * @param {string} argName - user provided argument name + * @param {object} functionArgs - user provided function arguments parsed ahead of current argument + * @param {string} partial - user provided argument value + * @return {array} array of dynamic suggestions matching partial + */ + getDynamicSuggestionsForArgument: async ( + functionName: T, + argName: keyof typeof customHandlers[T], + functionArgs: FunctionArg[], + partialInput = '' + ) => { + // @ts-ignore + return await customHandlers[functionName][argName](partialInput, functionArgs); + }, + + /** + * @param {string} partial - user provided argument value + * @param {array} staticSuggestions - argument value suggestions + * @return {array} array of static suggestions matching partial + */ + getStaticSuggestionsForInput: ( + partialInput = '', + staticSuggestions: TimelionFunctionArgs['suggestions'] = [] + ) => { + if (partialInput) { + return staticSuggestions.filter(suggestion => { + return suggestion.name.includes(partialInput); + }); + } + + return staticSuggestions; + }, + }; +} + +export type ArgValueSuggestions = ReturnType; diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts index 474f464a550cd..206f9f5d8368d 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts @@ -28,7 +28,7 @@ const name = 'timelion_vis'; interface Arguments { expression: string; - interval: any; + interval: string; } interface RenderValue { @@ -38,7 +38,7 @@ interface RenderValue { } type Context = KibanaContext | null; -type VisParams = Arguments; +export type VisParams = Arguments; type Return = Promise>; export const getTimelionVisualizationConfig = ( @@ -60,7 +60,7 @@ export const getTimelionVisualizationConfig = ( help: '', }, interval: { - types: ['string', 'null'], + types: ['string'], default: 'auto', help: '', }, diff --git a/src/legacy/core_plugins/timelion/public/vis/index.ts b/src/legacy/core_plugins/timelion/public/vis/index.tsx similarity index 75% rename from src/legacy/core_plugins/timelion/public/vis/index.ts rename to src/legacy/core_plugins/timelion/public/vis/index.tsx index 7b82553a24e5b..cd4e53e5a50e4 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.ts +++ b/src/legacy/core_plugins/timelion/public/vis/index.tsx @@ -17,20 +17,29 @@ * under the License. */ +import React from 'react'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { DefaultEditorSize } from 'ui/vis/editor_size'; +import { VisOptionsProps } from 'ui/vis/editors/default'; +import { npStart } from 'ui/new_platform'; import { getTimelionRequestHandler } from './timelion_request_handler'; import visConfigTemplate from './timelion_vis.html'; -import editorConfigTemplate from './timelion_vis_params.html'; import { TimelionVisualizationDependencies } from '../plugin'; // @ts-ignore import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; +import { TimelionOptions } from './timelion_options'; +import { VisParams } from '../timelion_vis_fn'; +import { getArgValueSuggestions } from '../services/arg_value_suggestions'; export const TIMELION_VIS_NAME = 'timelion'; export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { const timelionRequestHandler = getTimelionRequestHandler(dependencies); + const argValueSuggestions = getArgValueSuggestions( + npStart.plugins.data.indexPatterns, + npStart.core.savedObjects.client + ); // return the visType object, which kibana will use to display and configure new // Vis object of this type. @@ -50,7 +59,14 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe template: visConfigTemplate, }, editorConfig: { - optionsTemplate: editorConfigTemplate, + optionsTemplate: (props: VisOptionsProps) => ( + + ), defaultSize: DefaultEditorSize.MEDIUM, }, requestHandler: timelionRequestHandler, diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx new file mode 100644 index 0000000000000..9eee9ff512a5b --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useCallback } from 'react'; +import { EuiPanel } from '@elastic/eui'; + +import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisParams } from '../timelion_vis_fn'; +import { + TimelionInterval, + TimelionExpressionInput, + TimelionExpressionInputDependencies, +} from '../components'; + +function TimelionOptions({ + argValueSuggestions, + http, + stateParams, + setValue, + uiSettings, +}: VisOptionsProps & TimelionExpressionInputDependencies) { + const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ + setValue, + ]); + const setExpressionInput = useCallback( + (value: VisParams['expression']) => setValue('expression', value), + [setValue] + ); + + return ( + + + + + ); +} + +export { TimelionOptions }; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html deleted file mode 100644 index 9f2d2094fb1f7..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html +++ /dev/null @@ -1,27 +0,0 @@ -
-
- -
- -
-
- -
-
- -
- - -
- -
diff --git a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 6e32a4454e707..798902aa133de 100644 --- a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts +++ b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts @@ -17,6 +17,8 @@ * under the License. */ +import { TimelionFunctionArgs } from '../../../common/types'; + export interface TimelionFunctionInterface extends TimelionFunctionConfig { chainable: boolean; originalFn: Function; @@ -32,21 +34,6 @@ export interface TimelionFunctionConfig { args: TimelionFunctionArgs[]; } -export interface TimelionFunctionArgs { - name: string; - help?: string; - multi?: boolean; - types: TimelionFunctionArgsTypes[]; - suggestions?: TimelionFunctionArgsSuggestion[]; -} - -export type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null'; - -export interface TimelionFunctionArgsSuggestion { - name: string; - help: string; -} - // eslint-disable-next-line import/no-default-export export default class TimelionFunction { constructor(name: string, config: TimelionFunctionConfig); diff --git a/src/legacy/core_plugins/timelion/server/types.ts b/src/legacy/core_plugins/timelion/server/types.ts index 4971fa34863d3..9fc8734a560b7 100644 --- a/src/legacy/core_plugins/timelion/server/types.ts +++ b/src/legacy/core_plugins/timelion/server/types.ts @@ -17,10 +17,4 @@ * under the License. */ -export { - TimelionFunctionInterface, - TimelionFunctionConfig, - TimelionFunctionArgs, - TimelionFunctionArgsSuggestion, - TimelionFunctionArgsTypes, -} from './lib/classes/timelion_function'; +export { TimelionFunctionInterface, TimelionFunctionConfig } from './lib/classes/timelion_function'; diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index 0ae77995c0502..62440f12c6d84 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -78,6 +78,13 @@ export interface Props { */ hoverProvider?: monacoEditor.languages.HoverProvider; + /** + * Language config provider for bracket + * Documentation for the provider can be found here: + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.languageconfiguration.html + */ + languageConfiguration?: monacoEditor.languages.LanguageConfiguration; + /** * Function called before the editor is mounted in the view */ @@ -130,6 +137,13 @@ export class CodeEditor extends React.Component { if (this.props.hoverProvider) { monaco.languages.registerHoverProvider(this.props.languageId, this.props.hoverProvider); } + + if (this.props.languageConfiguration) { + monaco.languages.setLanguageConfiguration( + this.props.languageId, + this.props.languageConfiguration + ); + } }); // Register the theme diff --git a/yarn.lock b/yarn.lock index 57249563f4566..05c6c62594b51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3744,6 +3744,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a" integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA== +"@types/pegjs@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@types/pegjs/-/pegjs-0.10.1.tgz#9a2f3961dc62430fdb21061eb0ddbd890f9e3b94" + integrity sha512-ra8IchO9odGQmYKbm+94K58UyKCEKdZh9y0vxhG4pIpOJOBlC1C+ZtBVr6jLs+/oJ4pl+1p/4t3JtBA8J10Vvw== + "@types/pngjs@^3.3.2": version "3.3.2" resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" From b390d1de245d9d1cb39fddb609b187bc679ffdec Mon Sep 17 00:00:00 2001 From: sulemanof Date: Fri, 13 Dec 2019 17:18:13 +0300 Subject: [PATCH 10/59] Change translation ids --- .../public/components/timelion_expression_input_helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts index 06793eeab99e2..b1b7b2e7fe8d9 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts @@ -236,10 +236,10 @@ export function getSuggestion( kind = monacoEditor.languages.CompletionItemKind.Function; detail = `(${ (suggestion as ITimelionFunction).chainable - ? i18n.translate('timelion.expressionSuggestions.func.description.chainableText', { + ? i18n.translate('timelion.expressionSuggestions.func.description.chainableHelpText', { defaultMessage: 'Chainable', }) - : i18n.translate('timelion.expressionSuggestions.func.description.dataSourceText', { + : i18n.translate('timelion.expressionSuggestions.func.description.dataSourceHelpText', { defaultMessage: 'Data source', }) })`; From d7d491a94a8c12dc9ae593b4a451819487d8f6c4 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 13 Dec 2019 17:31:16 +0300 Subject: [PATCH 11/59] Use hooks --- .../public/components/chart.tsx | 6 +- .../vis_type_timelion/public/panels/panel.tsx | 511 +++++++++--------- .../public/panels/useEventListener.ts | 12 +- .../vis_type_timelion/public/panels/utils.ts | 62 +++ 4 files changed, 326 insertions(+), 265 deletions(-) create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index 4bbb60b3fc253..17fecf098c56b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -28,15 +28,15 @@ interface ChartComponentProp { seriesList: any; search?(): void; interval: any; - rerenderTrigger: boolean; + renderComplete: boolean; } -function ChartComponent({ seriesList, interval, search }: ChartComponentProp) { +function ChartComponent({ seriesList, interval, search, renderComplete }: ChartComponentProp) { if (!seriesList) { return null; } - const panelScope = { seriesList, interval, search } as any; + const panelScope = { seriesList, interval, search, renderComplete } as any; panelScope.seriesList.render = seriesList.render || { type: 'timechart', }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index d321b9b6926eb..1e640122bd559 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -17,10 +17,10 @@ * under the License. */ -import React, { useRef } from 'react'; +import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react'; import $ from 'jquery'; import moment from 'moment-timezone'; -import { debounce, compact, get, each, noop } from 'lodash'; +import { debounce, compact, get, each, noop, cloneDeep, last, map } from 'lodash'; import './timechart/flot'; @@ -34,118 +34,181 @@ import { useEventListener } from './useEventListener'; import { getServices } from '../kibana_services'; import { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions } from './constants'; +import { buildSeriesData } from './utils'; + +export interface Series { + data: any[]; + fit: string; + label: string; + split: string; + type: string; + _hide?: boolean; + _id?: number; + color?: string; + stack?: boolean; + _global?: boolean; + _title?: string; +} +interface SeriesList { + list: Series[]; + render: { + type: string; + grid?: unknown; + }; + type: string; +} interface PanelProps { name: string; - interval: any; - search?: any; - seriesList: any; + interval: string; + search?: string; + seriesList: SeriesList; + renderComplete(): void; } -function Panel({ interval: intervalProp, search = noop, seriesList }: PanelProps) { - console.log('Panel') - const chart = seriesList.list; +function Panel({ interval: intervalProp, search = noop, seriesList, renderComplete }: PanelProps) { + console.log('Panel'); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const elementRef = useRef(null); - const canvasElem = useRef(null); - const legendElem = useRef(null); + const [canvasElem, setCanvasElem] = useState(); const { uiSettings, timefilter } = getServices(); const formatters = tickFormatters() as any; - const getxAxisFormatter = xaxisFormatterProvider(uiSettings); + const getxAxisFormatter = useMemo(() => xaxisFormatterProvider(uiSettings), [uiSettings]); const generateTicks = generateTicksProvider(); - if (!canvasElem.current) { - //return null; - } + const [originalColorMap, setOriginalColorMap] = useState(new Map()); - let legendValueNumbers: any = []; - let legendCaption: any; + const [highlightedSeries, setHighlightedSeries] = useState(); + const [focusedSeries, setFocusedSeries] = useState(); + const [plot, setPlot] = useState(); + + const canvasRef = useCallback(node => { + if (node !== null) { + setCanvasElem(node); + } + }, []); + + useEffect(() => { + setChart( + seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % staticDefaultOptions.colors.length; + newSeries.color = staticDefaultOptions.colors[colorIndex]; + } + // setting originalColorMap + setOriginalColorMap(stateMap => new Map(stateMap.set(newSeries, newSeries.color))); + return newSeries; + }) + ); + }, [seriesList.list]); - let highlightedSeries: any; - let focusedSeries: any; - const originalColorMap = new Map(); + // Used to toggle the series, and for displaying values on hover + const [legendValueNumbers, setLegendValueNumbers] = useState(); + const [legendCaption, setLegendCaption] = useState(); let legendScope = {}; - const highlightSeries = debounce((id: any) => { - if (highlightedSeries === id) { - return; + useEffect(() => { + if (plot && get(plot.getData(), '[0]._global.legend.showTime', true)) { + const caption = $(''); + caption.html(emptyCaption); + setLegendCaption(caption); + + const canvasNode = $(canvasElem); + + canvasNode.find('div.legend table').append(caption); + + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + const $legendLabels = canvasNode.find('div.legend table .legendLabel>span'); + $legendLabels.get(focusedSeries).focus(); + } } + }, [plot, focusedSeries, canvasElem]); - highlightedSeries = id; - chart.forEach((series: any, seriesIndex: any) => { - if (seriesIndex !== id) { - series.color = 'rgba(128,128,128,0.1)'; // mark as grey - } else { - series.color = originalColorMap.get(series); // color it like it was + const highlightSeries = useCallback( + debounce((id: number) => { + if (highlightedSeries === id) { + return; } - }); - // drawPlot(chart); - }, DEBOUNCE_DELAY); - const focusSeries = (id: any) => { - focusedSeries = id; - highlightSeries(id); - }; + setHighlightedSeries(id); + setChart( + chart.map((series: Series, seriesIndex: number) => { + const color = + seriesIndex === id + ? originalColorMap.get(series) // color it like it was + : 'rgba(128,128,128,0.1)'; // mark as grey - const toggleSeries = (id: any) => { - const series = chart[id]; - series._hide = !series._hide; - // drawPlot(chart); - }; + return { ...series, color }; + }) + ); + // drawPlot(chart); + }, DEBOUNCE_DELAY), + [highlightedSeries, chart, originalColorMap] + ); - const reactLegend = ( - {}}> - - - + const focusSeries = useCallback( + (id: number) => { + setFocusedSeries(id); + highlightSeries(id); + }, + [highlightSeries] ); - const defaultOptions = { - ...staticDefaultOptions, - grid: { - show: seriesList.render.grid, - borderWidth: 0, - borderColor: null, - margin: 10, - hoverable: true, - autoHighlight: false, + const toggleSeries = useCallback( + (id: number) => { + setChart( + chart.map((series: Series, seriesIndex: number) => { + return seriesIndex === id ? { ...series, _hide: !series._hide } : { ...series }; + }) + ); + // drawPlot(chart); }, - legend: { - backgroundColor: 'rgb(255,255,255,0)', - position: 'nw', - labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: any, series: any) { - const legend = $( - ` - ${label} - - ` - ); - legend.find('.ngLegendValue').click(() => toggleSeries(series._id)); - const legendNumber = ``; - legendValueNumbers.push(legendNumber); - // if (legendElem.current) { - // legendElem.current.firstElementChild.innerText = label; - // legendElem.current.onclick = () => toggleSeries(series._id); - // } - // return ` - // ${label} - // ${legendNumber} - // `; - return legend.html(); + [chart] + ); + + const defaultOptions = useMemo( + () => ({ + ...staticDefaultOptions, + grid: { + show: seriesList.render.grid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, }, - }, - }; + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: any, series: any) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.addEventListener('click', () => toggleSeries(series._id)); + wrapperSpan.addEventListener('onFocus', () => focusSeries(series._id)); + wrapperSpan.addEventListener('onMouseOver', () => highlightSeries(series._id)); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + }), + [seriesList.render.grid, toggleSeries, focusSeries, highlightSeries] + ); - $(canvasElem.current).find('.legendLabel').click(() => toggleSeries(0)); - const options = _.cloneDeep(defaultOptions) as any; + const options = useMemo(() => cloneDeep(defaultOptions), [defaultOptions]); // Get the X-axis tick format const time = timefilter.getBounds() as any; @@ -159,9 +222,7 @@ function Panel({ interval: intervalProp, search = noop, seriesList }: PanelProps const format = getxAxisFormatter(interval); // Use moment to format ticks so we get timezone correction - options.xaxis.tickFormatter = function (val: any) { - return moment(val).format(format); - }; + options.xaxis.tickFormatter = (val: any) => moment(val).format(format); // Calculate how many ticks can fit on the axis const tickLetterWidth = 7; @@ -172,42 +233,6 @@ function Panel({ interval: intervalProp, search = noop, seriesList }: PanelProps : 0 / (format.length * tickLetterWidth + tickPadding) ); - const series = _.map(chart, function (serie: any, index) { - serie = _.cloneDeep( - _.defaults(serie, { - shadowSize: 0, - lines: { - lineWidth: 3, - }, - }) - ); - serie._id = index; - - if (serie.color) { - const span = document.createElement('span'); - span.style.color = serie.color; - serie.color = span.style.color; - } - - if (serie._hide) { - serie.data = []; - serie.stack = false; - // serie.color = "#ddd"; - serie.label = '(hidden) ' + serie.label; - } - - if (serie._global) { - _.merge(options, serie._global, function (objVal, srcVal) { - // This is kind of gross, it means that you can't replace a global value with a null - // best you can do is an empty string. Deal with it. - if (objVal == null) return srcVal; - if (srcVal == null) return objVal; - }); - } - - return serie; - }); - if (options.yaxes) { options.yaxes.forEach((yaxis: any) => { if (yaxis && yaxis.units) { @@ -220,173 +245,143 @@ function Panel({ interval: intervalProp, search = noop, seriesList }: PanelProps }); } - // @ts-ignore - const plot = - canvasElem.current && - canvasElem.current.clientHeight > 0 && - canvasElem.current.clientWidth > 0 - ? $.plot(canvasElem.current, compact(series), options) - : null; + const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); + + useEffect(() => { + // @ts-ignore + setPlot($.plot(canvasElem, compact(updatedSeries), options)); + }, [canvasElem, options, updatedSeries]); moment.tz.setDefault(uiSettings.get('dateFormat:tz')); - const clearLegendNumbers = () => { + const unhighlightSeries = useCallback(() => { + if (highlightedSeries === null) { + return; + } + + setHighlightedSeries(null); + setFocusedSeries(null); + setChart( + chart.map((series: Series) => { + return { ...series, color: originalColorMap.get(series) }; // reset the colors + }) + ); + // drawPlot(chart); + }, [chart, originalColorMap, highlightedSeries]); + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = useCallback( + (pos: any) => { + unhighlightSeries(); + + const axes = plot.getAxes(); + if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { + return; + } + + const dataset = plot.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) + ); + } + for (let i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines.show && !series.lines.steps; + const precision = get(series, '_meta.precision', 2); + + if (series._hide) continue; + + const currentPoint = series.data.find((point: any, index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (y != null && legendValueNumbers) { + let label = y.toFixed(precision); + if (series.yaxis.tickFormatter) { + label = series.yaxis.tickFormatter(label, series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + }, + [unhighlightSeries, legendCaption, legendValueNumbers, plot] + ); + + const debouncedSetLegendNumbers = useCallback( + debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }), + [setLegendNumbers] + ); + + const clearLegendNumbers = useCallback(() => { if (legendCaption) { legendCaption.html(emptyCaption); } each(legendValueNumbers, (num: any) => { $(num).empty(); }); - }; + }, [legendCaption, legendValueNumbers]); - const plothoverHandler = (event: any, pos: any, item: any) => { - if (!plot) { - return; - } - plot.setCrosshair(item); - debouncedSetLegendNumbers(item); - }; - const mouseleaveHandler = () => { + const plothoverHandler = useCallback( + (event: any, pos: any, item: any) => { + if (!plot) { + return; + } + plot.setCrosshair(item); + debouncedSetLegendNumbers(item); + }, + [plot, debouncedSetLegendNumbers] + ); + const mouseleaveHandler = useCallback(() => { if (!plot) { return; } plot.clearCrosshair(); clearLegendNumbers(); - }; - const plotselectedHandler = (event: any, ranges: any) => { - timefilter.setTime({ + }, [plot, clearLegendNumbers]); + const plotselectedHandler = useCallback((event: any, ranges: any) => { + getServices().timefilter.setTime({ from: moment(ranges.xaxis.from), to: moment(ranges.xaxis.to), }); - }; + }, []); useEventListener(elementRef, 'plothover', plothoverHandler); useEventListener(elementRef, 'plotselected', plotselectedHandler); useEventListener(elementRef, 'mouseleave', mouseleaveHandler); - const unhighlightSeries = () => { - if (highlightedSeries === null) { - return; - } - - highlightedSeries = null; - focusedSeries = null; - chart.forEach((series: any) => { - series.color = originalColorMap.get(series); // reset the colors - }); - // drawPlot(chart); - }; - - // Shamelessly borrowed from the flotCrosshairs example - const setLegendNumbers = (pos: any) => { - unhighlightSeries(); - - const axes = plot.getAxes(); - if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { - return; - } - - let i; - const dataset = plot.getData(); - if (legendCaption) { - legendCaption.text( - moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) - ); - } - for (i = 0; i < dataset.length; ++i) { - const series = dataset[i]; - const useNearestPoint = series.lines.show && !series.lines.steps; - const precision = get(series, '_meta.precision', 2); - - if (series._hide) continue; - - const currentPoint = series.data.find((point: any, index: number) => { - if (index + 1 === series.data.length) { - return true; - } - if (useNearestPoint) { - return pos.x - point[0] < series.data[index + 1][0] - pos.x; - } else { - return pos.x < series.data[index + 1][0]; - } - }); - - const y = currentPoint[1]; - - if (y != null) { - let label = y.toFixed(precision); - if (series.yaxis.tickFormatter) { - label = series.yaxis.tickFormatter(label, series.yaxis); - } - legendValueNumbers.eq(i).text(`(${label})`); - } else { - legendValueNumbers.eq(i).empty(); - } - } - }; - - const debouncedSetLegendNumbers = debounce(setLegendNumbers, DEBOUNCE_DELAY, { - maxWait: DEBOUNCE_DELAY, - leading: true, - trailing: false, - }); - - // setting originalColorMap - chart.forEach((series: any, seriesIndex: any) => { - if (!series.color) { - const colorIndex = seriesIndex % defaultOptions.colors.length; - series.color = defaultOptions.colors[colorIndex]; - } - originalColorMap.set(series, series.color); - }); - // const cancelResize = observeResize($elem, function() { // drawPlot($scope.chart); // }); // $scope.$on('$destroy', function() { // cancelResize(); - // $elem.off('plothover'); - // $elem.off('plotselected'); - // $elem.off('mouseleave'); // }); - // const drawPlot = (plotConfig: any) => { - - // } - // we can't use `$.plot` to draw the chart when the height or width is 0 // so, we'll need another event to trigger drawPlot to actually draw it - const title = - (_(chart) - .map('_title') - .compact() - .last() as any) || ''; - - // // Used to toggle the series, and for displaying values on hover - // legendValueNumbers = - // canvasElem && canvasElem.current && canvasElem.current.find('.ngLegendValueNumber'); - - if (plot && get(plot.getData(), '[0]._global.legend.showTime', true)) { - legendCaption = $(''); - legendCaption.html(emptyCaption); - // canvasElem.current.find('div.legend table').append(legendCaption); - - // legend has been re-created. Apply focus on legend element when previously set - if (focusedSeries || focusedSeries === 0) { - // const $legendLabels = canvasElem.current.find('div.legend table .legendLabel>span'); - //$legendLabels.get(focusedSeries).focus(); - } - } + const title: string = last(compact(map(chart, '_title'))) || ''; return ( -
-
- {title} -
-
- {reactLegend} +
+
{title}
+
); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts index 43f47d5032680..bae8187327694 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts @@ -21,11 +21,15 @@ import { useEffect } from 'react'; export const useEventListener = (target: any, type: any, listener: any) => { useEffect(() => { - const targetIsRef = target.hasOwnProperty('current'); - const currentTarget = targetIsRef ? target.current : target; - if (currentTarget) { - currentTarget.addEventListener(type, listener); + let currentTarget: any; + if (target) { + const targetIsRef = target.hasOwnProperty('current'); + currentTarget = targetIsRef ? target.current : target; + if (currentTarget) { + currentTarget.addEventListener(type, listener); + } } + return () => { if (currentTarget) { currentTarget.removeEventListener(type, listener); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts new file mode 100644 index 0000000000000..d396260fe2172 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { debounce, compact, get, each, noop, cloneDeep, defaults, merge } from 'lodash'; + +import { Series } from './panel'; + +function buildSeriesData(chart: Series[], options: object) { + return chart.map((series: Series, seriesIndex: number) => { + const newSeries: Series = cloneDeep( + defaults(series, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + + newSeries._id = seriesIndex; + + if (series.color) { + const span = document.createElement('span'); + span.style.color = series.color; + newSeries.color = span.style.color; + } + + if (series._hide) { + newSeries.data = []; + newSeries.stack = false; + newSeries.label = `(hidden) ${series.label}`; + } + + if (series._global) { + merge(options, series._global, (objVal, srcVal) => { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) return srcVal; + if (srcVal == null) return objVal; + }); + } + + return newSeries; + }); +} + +export { buildSeriesData }; From 6a600b03423d6690404c51c9a605c5266d8e8c07 Mon Sep 17 00:00:00 2001 From: sulemanof Date: Mon, 16 Dec 2019 14:33:25 +0300 Subject: [PATCH 12/59] Add @types/pegjs into renovate.json5 --- renovate.json5 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/renovate.json5 b/renovate.json5 index 49474b28e7908..45a044d8dcddc 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -501,6 +501,14 @@ '@types/opn', ], }, + { + groupSlug: 'pegjs', + groupName: 'pegjs related packages', + packageNames: [ + 'pegjs', + '@types/pegjs', + ], + }, { groupSlug: 'pngjs', groupName: 'pngjs related packages', From 73fbc3840653f108889c08192efeecf7d56fbb48 Mon Sep 17 00:00:00 2001 From: sulemanof Date: Mon, 16 Dec 2019 16:15:36 +0300 Subject: [PATCH 13/59] Add validation, add hover suggestions --- .../components/timelion_expression_input.tsx | 23 +++++++++++++++++++ .../public/components/timelion_interval.tsx | 12 ++++++++-- .../timelion/public/vis/timelion_options.tsx | 7 +++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx index ed0b2dbb9e18f..f3d5fafc54b2a 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -82,6 +82,28 @@ function TimelionExpressionInput({ [argValueSuggestions] ); + const provideHover = useCallback( + async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { + const suggestions = await suggest( + model.getValue(), + functionList.current, + // it's important to offset the cursor position on 1 point left + // because of PEG parser starts the line with 0, but monaco with 1 + position.column - 1, + argValueSuggestions + ); + + return { + contents: suggestions + ? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => ({ + value: s.help, + })) + : [], + }; + }, + [argValueSuggestions] + ); + useEffect(() => { http.get('../api/timelion/functions').then(data => { functionList.current = data; @@ -106,6 +128,7 @@ function TimelionExpressionInput({ triggerCharacters: ['.', '(', '=', ':'], provideCompletionItems, }} + hoverProvider={{ provideHover }} options={{ fontSize: 16, scrollBeyondLastLine: false, diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx index e7cac126321fd..978c37b318d99 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx @@ -20,6 +20,7 @@ import React, { useMemo } from 'react'; import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; const intervalOptions = [ { @@ -69,9 +70,10 @@ const intervalOptions = [ interface TimelionIntervalProps { value: string; setValue(value: string): void; + setValidity(valid: boolean): void; } -function TimelionInterval({ value, setValue }: TimelionIntervalProps) { +function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProps) { const onCustomInterval = (customValue: string) => { setValue(customValue.trim()); }; @@ -85,6 +87,10 @@ function TimelionInterval({ value, setValue }: TimelionIntervalProps) { [value] ); + const isValid = !!value; + + useValidation(setValidity, isValid); + return ( diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx index 9eee9ff512a5b..d23b10bf5f1ce 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx @@ -34,6 +34,7 @@ function TimelionOptions({ stateParams, setValue, uiSettings, + setValidity, }: VisOptionsProps & TimelionExpressionInputDependencies) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ setValue, @@ -45,7 +46,11 @@ function TimelionOptions({ return ( - + Date: Tue, 17 Dec 2019 16:33:06 +0300 Subject: [PATCH 14/59] Style fixes --- .../components/timelion_expression_input.tsx | 6 ++-- .../timelion/public/vis/_index.scss | 1 + .../timelion/public/vis/_timelion_editor.scss | 31 +++++++++++++++++++ .../timelion/public/vis/timelion_options.tsx | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx index f3d5fafc54b2a..1267af445a08d 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -113,13 +113,13 @@ function TimelionExpressionInput({ return ( -
+
* { + flex-shrink: 1; + } + } +} + +.visEditor__timelionExpressionInput { + .euiFormRow__fieldWrapper { + flex-grow: 1; + } +} + +.timelionExpressionInput__editor { + height: 100%; + padding-top: $euiSize; + + @include euiBreakpoint('xs', 's', 'm') { + height: $euiSize * 15; + max-height: $euiSize * 15; + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx index d23b10bf5f1ce..bba8ef2b57586 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx @@ -45,7 +45,7 @@ function TimelionOptions({ ); return ( - + Date: Thu, 19 Dec 2019 11:21:49 +0300 Subject: [PATCH 15/59] Change plugin setup, use kibana context --- .../components/timelion_expression_input.tsx | 119 ++++++++---------- .../timelion_expression_input_helpers.ts | 31 ++++- .../public/components/timelion_interval.tsx | 6 + .../public/panels/timechart/schema.ts | 7 +- .../core_plugins/timelion/public/plugin.ts | 20 +-- .../timelion/public/services/types.ts | 24 ++++ .../timelion/public/vis/_timelion_editor.scss | 18 ++- .../timelion/public/vis/index.tsx | 28 +++-- .../timelion/public/vis/timelion_options.tsx | 23 +--- .../public/vis/timelion_request_handler.ts | 6 +- 10 files changed, 153 insertions(+), 129 deletions(-) create mode 100644 src/legacy/core_plugins/timelion/public/services/types.ts diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx index 1267af445a08d..7e9051e012b03 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -22,34 +22,22 @@ import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import { IUiSettingsClient, HttpSetup } from 'kibana/public'; -import { CodeEditor, KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; +import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; -import { ArgValueSuggestions } from '../services/arg_value_suggestions'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; +import { TimelionServices } from '../services/types'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); -export interface TimelionExpressionInputDependencies { - argValueSuggestions: ArgValueSuggestions; - http: HttpSetup; - uiSettings: IUiSettingsClient; -} - interface TimelionExpressionInputProps { value: string; setValue(value: string): void; } -function TimelionExpressionInput({ - argValueSuggestions, - http, - uiSettings, - value, - setValue, -}: TimelionExpressionInputProps & TimelionExpressionInputDependencies) { +function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputProps) { const functionList = useRef([]); + const kibana = useKibana(); const provideCompletionItems = useCallback( async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { @@ -68,7 +56,7 @@ function TimelionExpressionInput({ // it's important to offset the cursor position on 1 point left // because of PEG parser starts the line with 0, but monaco with 1 position.column - 1, - argValueSuggestions + kibana.services.argValueSuggestions ); return { @@ -79,7 +67,7 @@ function TimelionExpressionInput({ : [], }; }, - [argValueSuggestions] + [kibana.services.argValueSuggestions] ); const provideHover = useCallback( @@ -90,7 +78,7 @@ function TimelionExpressionInput({ // it's important to offset the cursor position on 1 point left // because of PEG parser starts the line with 0, but monaco with 1 position.column - 1, - argValueSuggestions + kibana.services.argValueSuggestions ); return { @@ -101,59 +89,58 @@ function TimelionExpressionInput({ : [], }; }, - [argValueSuggestions] + [kibana.services.argValueSuggestions] ); useEffect(() => { - http.get('../api/timelion/functions').then(data => { - functionList.current = data; - }); - }, [http]); + if (kibana.services.http) { + kibana.services.http.get('../api/timelion/functions').then(data => { + functionList.current = data; + }); + } + }, [kibana.services.http]); return ( - - -
- +
+ -
- - + ], + }} + /> +
+
); } diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts index b1b7b2e7fe8d9..fc90c276eeca2 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts @@ -214,14 +214,20 @@ export function getSuggestion( suggestion: ITimelionFunction | TimelionFunctionArgs, type: SUGGESTION_TYPE, range: monacoEditor.Range -) { +): monacoEditor.languages.CompletionItem { let kind: monacoEditor.languages.CompletionItemKind = monacoEditor.languages.CompletionItemKind.Method; let insertText: string = suggestion.name; + let insertTextRules: monacoEditor.languages.CompletionItem['insertTextRules']; let detail: string = ''; + let command: monacoEditor.languages.CompletionItem['command']; switch (type) { case SUGGESTION_TYPE.ARGUMENTS: + command = { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }; kind = monacoEditor.languages.CompletionItemKind.Property; insertText = `${insertText}=`; detail = `${i18n.translate( @@ -233,7 +239,13 @@ export function getSuggestion( break; case SUGGESTION_TYPE.FUNCTIONS: + command = { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }; kind = monacoEditor.languages.CompletionItemKind.Function; + insertText = `${insertText}($0)`; + insertTextRules = monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet; detail = `(${ (suggestion as ITimelionFunction).chainable ? i18n.translate('timelion.expressionSuggestions.func.description.chainableHelpText', { @@ -246,8 +258,17 @@ export function getSuggestion( break; case SUGGESTION_TYPE.ARGUMENT_VALUE: + const param = suggestion.name.split(':'); + + if (param.length === 1 || param[1]) { + insertText = `${param.length === 1 ? insertText : param[1]},`; + } + + command = { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }; kind = monacoEditor.languages.CompletionItemKind.Property; - insertText = suggestion.name.split(':')[1] || suggestion.name; detail = suggestion.help || ''; break; @@ -256,13 +277,11 @@ export function getSuggestion( return { detail, insertText, + insertTextRules, kind, label: suggestion.name, documentation: suggestion.help, - command: { - title: 'Trigger Suggestion Dialog', - id: 'editor.action.triggerSuggest', - }, + command, range, }; } diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx index 978c37b318d99..df8f7471eb71b 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx @@ -47,6 +47,12 @@ const intervalOptions = [ }), value: '1h', }, + { + label: i18n.translate('timelion.vis.interval.day', { + defaultMessage: '1 day', + }), + value: '1d', + }, { label: i18n.translate('timelion.vis.interval.week', { defaultMessage: '1 week', diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 04b27c4020ce3..432430b0083e8 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -34,7 +34,12 @@ import { generateTicksProvider } from './tick_generator'; const DEBOUNCE_DELAY = 50; export function timechartFn(dependencies: TimelionVisualizationDependencies) { - const { $rootScope, $compile, uiSettings } = dependencies; + const { + $rootScope, + $compile, + core: { uiSettings }, + } = dependencies; + return function() { return { help: 'Draw a timeseries chart', diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index ba8c25c20abea..6c85804632f6d 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -16,16 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, - IUiSettingsClient, - HttpSetup, -} from 'kibana/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; +import { PluginsStart } from 'ui/new_platform/new_platform'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisualization } from './vis'; @@ -35,8 +29,7 @@ import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim' /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { - uiSettings: IUiSettingsClient; - http: HttpSetup; + core: CoreSetup; timelionPanels: Map; timefilter: TimefilterContract; } @@ -60,14 +53,13 @@ export class TimelionPlugin implements Plugin, void> { } public async setup( - core: CoreSetup, + core: CoreSetup, { __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies ) { const timelionPanels: Map = new Map(); const dependencies: TimelionVisualizationDependencies = { - uiSettings: core.uiSettings, - http: core.http, + core, timelionPanels, timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), @@ -76,7 +68,7 @@ export class TimelionPlugin implements Plugin, void> { this.registerPanels(dependencies); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); + visualizations.types.createBaseVisualization(await getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { diff --git a/src/legacy/core_plugins/timelion/public/services/types.ts b/src/legacy/core_plugins/timelion/public/services/types.ts new file mode 100644 index 0000000000000..135bf953d5a50 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/services/types.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ArgValueSuggestions } from './arg_value_suggestions'; + +export interface TimelionServices { + argValueSuggestions: ArgValueSuggestions; +} diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss b/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss index 83ee71f2497c4..1a7b8a9e77f3b 100644 --- a/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss +++ b/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss @@ -1,16 +1,12 @@ -.visEditor--timelion{ +.visEditor--timelion { vis-options-react-wrapper, .visEditorSidebar__options, .visEditorSidebar__timelionOptions, .visEditor__timelionExpressionInput { - @include flex-parent(1, 1, auto, column); - - height: 100%; - - > * { - flex-shrink: 1; - } + flex: 1 1 auto; + display: flex; + flex-direction: column; } } @@ -21,11 +17,11 @@ } .timelionExpressionInput__editor { - height: 100%; - padding-top: $euiSize; - @include euiBreakpoint('xs', 's', 'm') { height: $euiSize * 15; max-height: $euiSize * 15; } + + height: 100%; + padding-top: $euiSize; } diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/timelion/public/vis/index.tsx index cd4e53e5a50e4..4ad0433d7613d 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.tsx +++ b/src/legacy/core_plugins/timelion/public/vis/index.tsx @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { DefaultEditorSize } from 'ui/vis/editor_size'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { npStart } from 'ui/new_platform'; +import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; import { getTimelionRequestHandler } from './timelion_request_handler'; import visConfigTemplate from './timelion_vis.html'; import { TimelionVisualizationDependencies } from '../plugin'; @@ -34,11 +34,16 @@ import { getArgValueSuggestions } from '../services/arg_value_suggestions'; export const TIMELION_VIS_NAME = 'timelion'; -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { +export async function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { + const { core } = dependencies; + const { http, uiSettings } = core; const timelionRequestHandler = getTimelionRequestHandler(dependencies); + + const [coreStart, pluginsStart] = await core.getStartServices(); + const argValueSuggestions = getArgValueSuggestions( - npStart.plugins.data.indexPatterns, - npStart.core.savedObjects.client + pluginsStart.data.indexPatterns, + coreStart.savedObjects.client ); // return the visType object, which kibana will use to display and configure new @@ -60,12 +65,15 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( - + + + ), defaultSize: DefaultEditorSize.MEDIUM, }, diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx index bba8ef2b57586..527fcc3bc6ce8 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx @@ -22,20 +22,9 @@ import { EuiPanel } from '@elastic/eui'; import { VisOptionsProps } from 'ui/vis/editors/default'; import { VisParams } from '../timelion_vis_fn'; -import { - TimelionInterval, - TimelionExpressionInput, - TimelionExpressionInputDependencies, -} from '../components'; +import { TimelionInterval, TimelionExpressionInput } from '../components'; -function TimelionOptions({ - argValueSuggestions, - http, - stateParams, - setValue, - uiSettings, - setValidity, -}: VisOptionsProps & TimelionExpressionInputDependencies) { +function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ setValue, ]); @@ -51,13 +40,7 @@ function TimelionOptions({ setValue={setInterval} setValidity={setValidity} /> - +
); } diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 14cd3d0083e6a..74f51da1ebd76 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -47,7 +47,11 @@ export interface TimelionSuccessResponse { } export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; + const { + core: { uiSettings, http }, + timefilter, + } = dependencies; + const timezone = timezoneProvider(uiSettings)(); return async function({ From cd66a645c92171c689d13ba1a1a1779329c1aa7b Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 19 Dec 2019 12:00:51 +0300 Subject: [PATCH 16/59] Update --- .../common/lib/calculate_interval.js | 30 +- .../public/services/_saved_sheet.js | 83 ----- .../public/services/_saved_sheet.ts | 79 +++++ ...et_register.js => saved_sheet_register.ts} | 6 +- .../{saved_sheets.js => saved_sheets.ts} | 32 +- .../server/fit_functions/__tests__/average.js | 15 +- .../server/fit_functions/__tests__/carry.js | 12 +- .../server/fit_functions/average.js | 8 +- .../server/fit_functions/carry.js | 12 +- .../server/fit_functions/nearest.js | 17 +- .../server/fit_functions/none.js | 1 - .../server/fit_functions/scale.js | 14 +- .../server/handlers/__tests__/parse_sheet.js | 8 +- .../server/handlers/chain_runner.js | 66 ++-- .../server/handlers/lib/index_arguments.js | 11 +- .../server/handlers/lib/parse_sheet.js | 2 +- .../server/handlers/lib/preprocess_chain.js | 2 +- .../handlers/lib/reposition_arguments.js | 8 +- .../server/handlers/lib/tl_config.js | 15 +- .../server/handlers/lib/validate_arg.js | 7 +- .../vis_type_timelion/server/lib/alter.js | 38 ++- .../server/lib/build_target.js | 2 +- .../server/lib/classes/datasource.js | 18 +- .../server/lib/classes/timelion_function.js | 14 +- .../server/lib/functions_md.js | 29 +- .../server/lib/get_namespaced_settings.js | 18 +- .../server/lib/load_functions.js | 16 +- .../server/lib/offset_time.js | 2 +- .../server/lib/offset_time.test.js | 1 - .../server/lib/process_function_definition.js | 4 +- .../vis_type_timelion/server/lib/reduce.js | 33 +- .../server/lib/split_interval.js | 4 +- .../server/lib/to_milliseconds.js | 42 +-- .../server/lib/unzipPairs.js | 14 +- .../server/routes/functions.js | 4 +- .../vis_type_timelion/server/routes/run.js | 73 ---- .../vis_type_timelion/server/routes/run.ts | 125 +++++++ .../server/routes/validate_es.js | 22 +- .../server/series_functions/__tests__/abs.js | 15 +- .../series_functions/__tests__/aggregate.js | 16 +- .../server/series_functions/__tests__/bars.js | 15 +- .../series_functions/__tests__/color.js | 14 +- .../series_functions/__tests__/condition.js | 56 ++-- .../series_functions/__tests__/cusum.js | 4 +- .../series_functions/__tests__/derivative.js | 4 +- .../series_functions/__tests__/divide.js | 4 +- .../server/series_functions/__tests__/es.js | 311 ++++++++++++------ .../series_functions/__tests__/first.js | 6 +- .../server/series_functions/__tests__/fit.js | 51 ++- .../__tests__/fixtures/bucketList.js | 2 +- .../__tests__/fixtures/es_response.js | 128 ++++--- .../__tests__/fixtures/seriesList.js | 10 +- .../__tests__/fixtures/tlConfig.js | 33 +- .../series_functions/__tests__/graphite.js | 40 +-- .../__tests__/helpers/get_series.js | 9 +- .../__tests__/helpers/get_series_list.js | 13 +- .../helpers/get_single_series_list.js | 2 +- .../__tests__/helpers/invoke_series_fn.js | 7 +- .../server/series_functions/__tests__/hide.js | 10 +- .../series_functions/__tests__/label.js | 8 +- .../series_functions/__tests__/legend.js | 14 +- .../series_functions/__tests__/lines.js | 6 +- .../server/series_functions/__tests__/log.js | 4 +- .../server/series_functions/__tests__/max.js | 4 +- .../server/series_functions/__tests__/min.js | 4 +- .../__tests__/movingaverage.js | 22 +- .../series_functions/__tests__/movingstd.js | 58 +++- .../series_functions/__tests__/multiply.js | 4 +- .../series_functions/__tests__/points.js | 18 +- .../series_functions/__tests__/precision.js | 6 +- .../series_functions/__tests__/quandl.js | 43 +-- .../series_functions/__tests__/range.js | 10 +- .../__tests__/scale_interval.js | 3 +- .../series_functions/__tests__/static.js | 6 +- .../series_functions/__tests__/subtract.js | 13 +- .../server/series_functions/__tests__/sum.js | 4 +- .../series_functions/__tests__/title.js | 6 +- .../server/series_functions/__tests__/trim.js | 12 +- .../series_functions/__tests__/yaxis.js | 25 +- .../server/series_functions/abs.js | 10 +- .../server/series_functions/aggregate/avg.js | 2 +- .../series_functions/aggregate/cardinality.js | 2 +- .../series_functions/aggregate/first.js | 2 +- .../series_functions/aggregate/index.js | 13 +- .../server/series_functions/aggregate/last.js | 2 +- .../server/series_functions/aggregate/max.js | 2 +- .../server/series_functions/aggregate/min.js | 2 +- .../server/series_functions/aggregate/sum.js | 2 +- .../server/series_functions/bars.js | 8 +- .../server/series_functions/color.js | 8 +- .../server/series_functions/condition.js | 85 +++-- .../server/series_functions/cusum.js | 13 +- .../server/series_functions/derivative.js | 14 +- .../server/series_functions/divide.js | 8 +- .../server/series_functions/es/index.js | 29 +- .../series_functions/es/lib/agg_body.js | 8 +- .../es/lib/agg_response_to_series_list.js | 20 +- .../series_functions/es/lib/build_request.js | 34 +- .../es/lib/create_date_agg.js | 16 +- .../server/series_functions/first.js | 8 +- .../server/series_functions/fit.js | 15 +- .../server/series_functions/graphite.js | 72 ++-- .../server/series_functions/hide.js | 8 +- .../server/series_functions/holt/index.js | 29 +- .../server/series_functions/holt/lib/des.js | 48 +-- .../server/series_functions/holt/lib/ses.js | 38 ++- .../server/series_functions/holt/lib/tes.js | 35 +- .../server/series_functions/label.js | 8 +- .../server/series_functions/legend.js | 18 +- .../server/series_functions/lines.js | 12 +- .../server/series_functions/log.js | 10 +- .../server/series_functions/max.js | 9 +- .../server/series_functions/min.js | 9 +- .../server/series_functions/movingaverage.js | 45 +-- .../server/series_functions/movingstd.js | 55 ++-- .../server/series_functions/multiply.js | 8 +- .../server/series_functions/points.js | 14 +- .../server/series_functions/precision.js | 10 +- .../server/series_functions/props.js | 16 +- .../server/series_functions/quandl.js | 72 ++-- .../server/series_functions/range.js | 14 +- .../server/series_functions/scale_interval.js | 10 +- .../server/series_functions/static.js | 19 +- .../server/series_functions/subtract.js | 8 +- .../server/series_functions/sum.js | 8 +- .../server/series_functions/title.js | 8 +- .../server/series_functions/trend/index.js | 14 +- .../series_functions/trend/lib/regress.js | 76 +++-- .../server/series_functions/trim.js | 15 +- .../server/series_functions/worldbank.js | 107 +++--- .../series_functions/worldbank_indicators.js | 20 +- .../server/series_functions/yaxis.js | 44 ++- .../vis_type_timelion/server/types.ts | 2 + 133 files changed, 1694 insertions(+), 1377 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts rename src/legacy/core_plugins/vis_type_timelion/public/services/{saved_sheet_register.js => saved_sheet_register.ts} (84%) rename src/legacy/core_plugins/vis_type_timelion/public/services/{saved_sheets.js => saved_sheets.ts} (65%) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/run.js create mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js index 185b1df402c16..7c6b3c2816e67 100644 --- a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js @@ -23,35 +23,35 @@ import toMS from '../../server/lib/to_milliseconds.js'; // I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. function roundInterval(interval) { switch (true) { - case (interval <= 500): // <= 0.5s + case interval <= 500: // <= 0.5s return '100ms'; - case (interval <= 5000): // <= 5s + case interval <= 5000: // <= 5s return '1s'; - case (interval <= 7500): // <= 7.5s + case interval <= 7500: // <= 7.5s return '5s'; - case (interval <= 15000): // <= 15s + case interval <= 15000: // <= 15s return '10s'; - case (interval <= 45000): // <= 45s + case interval <= 45000: // <= 45s return '30s'; - case (interval <= 180000): // <= 3m + case interval <= 180000: // <= 3m return '1m'; - case (interval <= 450000): // <= 9m + case interval <= 450000: // <= 9m return '5m'; - case (interval <= 1200000): // <= 20m + case interval <= 1200000: // <= 20m return '10m'; - case (interval <= 2700000): // <= 45m + case interval <= 2700000: // <= 45m return '30m'; - case (interval <= 7200000): // <= 2h + case interval <= 7200000: // <= 2h return '1h'; - case (interval <= 21600000): // <= 6h + case interval <= 21600000: // <= 6h return '3h'; - case (interval <= 86400000): // <= 24h + case interval <= 86400000: // <= 24h return '12h'; - case (interval <= 604800000): // <= 1w + case interval <= 604800000: // <= 1w return '24h'; - case (interval <= 1814400000): // <= 3w + case interval <= 1814400000: // <= 3w return '1w'; - case (interval < 3628800000): // < 2y + case interval < 3628800000: // < 2y return '30d'; default: return '1y'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js b/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js deleted file mode 100644 index 6e01351c15030..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.js +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { uiModules } from 'ui/modules'; -import { createLegacyClass } from 'ui/utils/legacy_class'; -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -const module = uiModules.get('app/timelion'); - -// Used only by the savedSheets service, usually no reason to change this -module.factory('SavedSheet', function (Private, config) { - - // SavedSheet constructor. Usually you'd interact with an instance of this. - // ID is option, without it one will be generated on save. - const SavedObject = Private(SavedObjectProvider); - createLegacyClass(SavedSheet).inherits(SavedObject); - function SavedSheet(id) { - // Gives our SavedSheet the properties of a SavedObject - SavedObject.call(this, { - type: SavedSheet.type, - mapping: SavedSheet.mapping, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id: id, - - // default values that will get assigned if the doc is new - defaults: { - title: 'New TimeLion Sheet', - hits: 0, - description: '', - timelion_sheet: ['.es(*)'], - timelion_interval: 'auto', - timelion_chart_height: 275, - timelion_columns: config.get('timelion:default_columns') || 2, - timelion_rows: config.get('timelion:default_rows') || 2, - version: 1, - } - }); - - this.showInRecentlyAccessed = true; - } - - // save these objects with the 'sheet' type - SavedSheet.type = 'timelion-sheet'; - - // if type:sheet has no mapping, we push this mapping into ES - SavedSheet.mapping = { - title: 'text', - hits: 'integer', - description: 'text', - timelion_sheet: 'text', - timelion_interval: 'keyword', - timelion_other_interval: 'keyword', - timelion_chart_height: 'integer', - timelion_columns: 'integer', - timelion_rows: 'integer', - version: 'integer' - }; - - // Order these fields to the top, the rest are alphabetical - SavedSheet.fieldOrder = ['title', 'description']; - - SavedSheet.prototype.getFullPath = function () { - return `/app/timelion#/${this.id}`; - }; - - return SavedSheet; -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts b/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts new file mode 100644 index 0000000000000..1e956cbd3e5ac --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; +import { SavedObjectKibanaServices } from 'ui/saved_objects/types'; +import { IUiSettingsClient } from 'kibana/public'; + +// Used only by the savedSheets service, usually no reason to change this +export function createSavedSheetClass( + services: SavedObjectKibanaServices, + config: IUiSettingsClient +) { + const SavedObjectClass = createSavedObjectClass(services); + + class SavedSheet extends SavedObjectClass { + static type = 'timelion-sheet'; + + // if type:sheet has no mapping, we push this mapping into ES + static mapping = { + title: 'text', + hits: 'integer', + description: 'text', + timelion_sheet: 'text', + timelion_interval: 'keyword', + timelion_other_interval: 'keyword', + timelion_chart_height: 'integer', + timelion_columns: 'integer', + timelion_rows: 'integer', + version: 'integer', + }; + + // Order these fields to the top, the rest are alphabetical + static fieldOrder = ['title', 'description']; + // SavedSheet constructor. Usually you'd interact with an instance of this. + // ID is option, without it one will be generated on save. + constructor(id: string) { + super({ + type: SavedSheet.type, + mapping: SavedSheet.mapping, + + // if this is null/undefined then the SavedObject will be assigned the defaults + id, + + // default values that will get assigned if the doc is new + defaults: { + title: 'New TimeLion Sheet', + hits: 0, + description: '', + timelion_sheet: ['.es(*)'], + timelion_interval: 'auto', + timelion_chart_height: 275, + timelion_columns: config.get('timelion:default_columns') || 2, + timelion_rows: config.get('timelion:default_rows') || 2, + version: 1, + }, + }); + this.showInRecentlyAccessed = true; + this.getFullPath = () => `/app/timelion#/${this.id}`; + } + } + + return SavedSheet; +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts similarity index 84% rename from src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js rename to src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts index 0353a4d742dd3..6bfbacb95b62e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import './saved_sheets'; -export default function savedSearchObjectFn(savedSheets) { +SavedObjectRegistryProvider.register((savedSheets: any) => { return savedSheets; -} +}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts similarity index 65% rename from src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js rename to src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts index d303069e74dea..d851b5a863658 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts @@ -16,11 +16,13 @@ * specific language governing permissions and limitations * under the License. */ - -import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects'; +import { npStart } from 'ui/new_platform'; +import { SavedObjectLoader } from 'ui/saved_objects'; +// @ts-ignore import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_object_registry'; +// @ts-ignore import { uiModules } from 'ui/modules'; -import './_saved_sheet.js'; +import { createSavedSheetClass } from './_saved_sheet'; const module = uiModules.get('app/sheet'); @@ -28,22 +30,32 @@ const module = uiModules.get('app/sheet'); // edited by the object editor. savedObjectManagementRegistry.register({ service: 'savedSheets', - title: 'sheets' + title: 'sheets', }); // This is the only thing that gets injected into controllers -module.service('savedSheets', function (Private, SavedSheet, kbnUrl, chrome) { - const savedObjectClient = Private(SavedObjectsClientProvider); - const savedSheetLoader = new SavedObjectLoader(SavedSheet, kbnUrl, chrome, savedObjectClient); - savedSheetLoader.urlFor = function (id) { - return kbnUrl.eval('#/{{id}}', { id: id }); +module.service('savedSheets', function() { + const savedObjectsClient = npStart.core.savedObjects.client; + const services = { + savedObjectsClient, + indexPatterns: npStart.plugins.data.indexPatterns, + chrome: npStart.core.chrome, + overlays: npStart.core.overlays, }; + const SavedSheet = createSavedSheetClass(services, npStart.core.uiSettings); + + const savedSheetLoader = new SavedObjectLoader( + SavedSheet, + savedObjectsClient, + npStart.core.chrome + ); + savedSheetLoader.urlFor = id => `#/${encodeURIComponent(id)}`; // Customize loader properties since adding an 's' on type doesn't work for type 'timelion-sheet'. savedSheetLoader.loaderProperties = { name: 'timelion-sheet', noun: 'Saved Sheets', - nouns: 'saved sheets' + nouns: 'saved sheets', }; return savedSheetLoader; }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js index d54574d2cf4ba..d30244610a124 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js @@ -22,10 +22,9 @@ import moment from 'moment'; const expect = require('chai').expect; import _ from 'lodash'; -describe('average.js', function () { - - describe('average', function () { - it('fills holes in the data', function () { +describe('average.js', function() { + describe('average', function() { + it('fills holes in the data', function() { const data = [ [moment.utc('1980', 'YYYY').valueOf(), 10], [moment.utc('1983', 'YYYY').valueOf(), 40], @@ -43,8 +42,8 @@ describe('average.js', function () { expect(_.map(fn(data, target), 1)).to.eql([10, 20, 30, 40, 50]); }); - describe('sampling', function () { - it('up', function () { + describe('sampling', function() { + it('up', function() { const data = [ [moment.utc('1981', 'YYYY').valueOf(), 10], [moment.utc('1983', 'YYYY').valueOf(), 30], @@ -62,8 +61,7 @@ describe('average.js', function () { expect(_.map(fn(data, target), 1)).to.eql([10, 20, 30, 50, 70]); }); - - it('down', function () { + it('down', function() { const data = [ [moment.utc('1980', 'YYYY').valueOf(), 0], [moment.utc('1981', 'YYYY').valueOf(), 2], @@ -87,7 +85,6 @@ describe('average.js', function () { // Essentially the algorithm is left aligned instead of centered expect(_.map(fn(data, target), 1)).to.eql([1, 5, 9]); }); - }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js index 972f220111edc..8e35d63618ae4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js @@ -22,8 +22,8 @@ import moment from 'moment'; const expect = require('chai').expect; import _ from 'lodash'; -describe('carry.js', function () { - it('fills holes in the data', function () { +describe('carry.js', function() { + it('fills holes in the data', function() { const data = [ [moment.utc('1980', 'YYYY').valueOf(), 10], [moment.utc('1983', 'YYYY').valueOf(), 40], @@ -41,8 +41,8 @@ describe('carry.js', function () { expect(_.map(fn(data, target), 1)).to.eql([10, 10, 10, 40, 50]); }); - describe('sampling', function () { - it('up', function () { + describe('sampling', function() { + it('up', function() { const data = [ [moment.utc('1981', 'YYYY').valueOf(), 10], [moment.utc('1983', 'YYYY').valueOf(), 30], @@ -60,8 +60,7 @@ describe('carry.js', function () { expect(_.map(fn(data, target), 1)).to.eql([10, 10, 30, 30, 70]); }); - - it('down does not make sense', function () { + it('down does not make sense', function() { const data = [ [moment.utc('1980', 'YYYY').valueOf(), 0], [moment.utc('1981', 'YYYY').valueOf(), 2], @@ -86,6 +85,5 @@ describe('carry.js', function () { expect(e).to.be.an('error'); } }); - }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js index 48dc9120ea015..10baeb8552a4e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js @@ -24,13 +24,12 @@ import _ from 'lodash'; // Bad: sum, count export default function average(dataTuples, targetTuples) { - // Phase 1: Downsample // We necessarily won't well match the dataSource here as we don't know how much data // they had when creating their own average const resultTimes = _.pluck(targetTuples, 0); const dataTuplesQueue = _.clone(dataTuples); - const resultValues = _.map(targetTuples, function (bucket) { + const resultValues = _.map(targetTuples, function(bucket) { const time = bucket[0]; let i = 0; const avgSet = []; @@ -48,13 +47,13 @@ export default function average(dataTuples, targetTuples) { const sum = avgSet.reduce((sum, num) => sum + num, 0); - return avgSet.length ? (sum / avgSet.length) : NaN; + return avgSet.length ? sum / avgSet.length : NaN; }); // Phase 2: Upsample if needed // If we have any NaNs we are probably resampling from a big interval to a small one (eg, 1M as 1d) // So look for the missing stuff in the array, and smooth it out - const naNIndex = _.findIndex(resultValues, function (val) { + const naNIndex = _.findIndex(resultValues, function(val) { return isNaN(val); }); @@ -86,7 +85,6 @@ export default function average(dataTuples, targetTuples) { } i++; } - } const resultTuples = _.zip(resultTimes, resultValues); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js index 5702eb6d25119..c08932bacb69e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js @@ -26,24 +26,26 @@ import { i18n } from '@kbn/i18n'; // Don't use this to down sample, it simply won't do the right thing. export default function carry(dataTuples, targetTuples) { - if (dataTuples.length > targetTuples.length) { - throw new Error ( + throw new Error( i18n.translate('timelion.fitFunctions.carry.downSampleErrorMessage', { defaultMessage: `Don't use the 'carry' fit method to down sample, use 'scale' or 'average'`, - description: '"carry", "scale" and "average" are parameter values that must not be translated.', + description: + '"carry", "scale" and "average" are parameter values that must not be translated.', }) ); } let currentCarry = dataTuples[0][1]; - return _.map(targetTuples, function (bucket) { + return _.map(targetTuples, function(bucket) { const targetTime = bucket[0]; const dataTime = dataTuples[0][0]; if (dataTuples[0] && targetTime >= dataTime) { currentCarry = dataTuples[0][1]; - if (dataTuples.length > 1) { dataTuples.shift(); } + if (dataTuples.length > 1) { + dataTuples.shift(); + } } return [bucket[0], currentCarry]; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js index feacbb02ce195..f1907f9603de3 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js @@ -23,17 +23,18 @@ import _ from 'lodash'; // Good: average, min, max // Bad: sum, count export default function nearest(dataTuples, targetTuples) { - return _.map(targetTuples, function (bucket) { + return _.map(targetTuples, function(bucket) { const time = bucket[0]; let i = 0; - while (i < dataTuples.length - 1 && + while ( + i < dataTuples.length - 1 && (Math.abs(dataTuples[i + 1][0] - time) < Math.abs(dataTuples[i][0] - time) || - // TODO: Certain offset= args can cause buckets with duplicate times, e.g., offset=-1M - // check for that, and only use the last of the duplicates. The reason this happens? - // What is 1M before Mar 30th? What about 1M before Mar 31st? Both are the last day - // in Feb. Something has to be chucked. If offsetting by M user might want to use - // fit=average - Math.abs(dataTuples[i + 1][0] - time) === Math.abs(dataTuples[i][0] - time)) + // TODO: Certain offset= args can cause buckets with duplicate times, e.g., offset=-1M + // check for that, and only use the last of the duplicates. The reason this happens? + // What is 1M before Mar 30th? What about 1M before Mar 31st? Both are the last day + // in Feb. Something has to be chucked. If offsetting by M user might want to use + // fit=average + Math.abs(dataTuples[i + 1][0] - time) === Math.abs(dataTuples[i][0] - time)) ) { i++; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js index e46d9f44a96be..aa0a4cd8aee61 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js @@ -17,7 +17,6 @@ * under the License. */ - // **DON'T USE THIS** // Performing joins/math with other sets that don't match perfectly will be wrong // Does not resample at all. diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js index 36c7fb691f9e3..3f5ece1d6e61a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js @@ -23,17 +23,21 @@ import _ from 'lodash'; // Good: count, sum // Bad: avg, min, max - // For upsampling cumulative metrics (eg sum from 1M to 1d), could rename this scale. // Really only the 0s that screws this up, need to distribute contents of spikes to empty buckets // Empty is currently 0, which is not right function sum(set) { - return _.reduce(set, function (sum, num) { return sum + num; }, 0); + return _.reduce( + set, + function(sum, num) { + return sum + num; + }, + 0 + ); } export default function scale(dataTuples, targetTuples) { - let i = 0; let j = 0; let spreadCount = 0; @@ -59,7 +63,6 @@ export default function scale(dataTuples, targetTuples) { // We hit a real number, or the end if (scaleSet.length > 0 || i === targetTuples.length - 1) { - nextRealNumber = sum(scaleSet); step = nextRealNumber; @@ -72,20 +75,17 @@ export default function scale(dataTuples, targetTuples) { // Thus [5, null, null, 30] becomes [5, 10, 10, 10] step = nextRealNumber / (spreadCount + 1); while (spreadCount > 0) { - result[i - spreadCount][1] = step; spreadCount--; } } result.push([time, step]); - } else { result.push([time, null]); spreadCount++; } i++; - } return result; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js index c94b021e85831..4ec2a88d3e68b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js @@ -21,8 +21,8 @@ const parseSheet = require('../lib/parse_sheet'); const expect = require('chai').expect; -describe('timelion parse_sheet function', function () { - it(`doesn't split expressions on whitespace`, async function () { +describe('timelion parse_sheet function', function() { + it(`doesn't split expressions on whitespace`, async function() { const data = ['.es() .es(404)']; const ast = parseSheet(data); @@ -31,7 +31,7 @@ describe('timelion parse_sheet function', function () { expect(expressions[0].type).to.equal('chain'); }); - it('splits expressions on commas', function () { + it('splits expressions on commas', function() { const data = ['.es(), .es(404)']; const ast = parseSheet(data); @@ -41,7 +41,7 @@ describe('timelion parse_sheet function', function () { expect(expressions[1].type).to.equal('chain'); }); - it('splits expressions on newlines', function () { + it('splits expressions on newlines', function() { const data = [`.es()\n\r ,\n\r .es(404)`]; const ast = parseSheet(data); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js index cf82a82fd2c12..9514e479d36f4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js @@ -17,7 +17,6 @@ * under the License. */ - import _ from 'lodash'; import Bluebird from 'bluebird'; import { i18n } from '@kbn/i18n'; @@ -66,7 +65,7 @@ export default function chainRunner(tlConfig) { } else { reference = { type: 'chainList', - list: sheet[item.plot - 1] + list: sheet[item.plot - 1], }; } return invoke('first', [reference]); @@ -98,7 +97,7 @@ export default function chainRunner(tlConfig) { args = _.map(args, resolveArgument); - return Bluebird.all(args).then(function (args) { + return Bluebird.all(args).then(function(args) { args.byName = indexArguments(functionDef, args); return functionDef.fn(args, tlConfig); }); @@ -120,21 +119,23 @@ export default function chainRunner(tlConfig) { promise = invoke(link.function, args); } - return promise.then(function (result) { + return promise.then(function(result) { return invokeChain({ type: 'chain', chain: chain }, [result]); }); - } function resolveChainList(chainList) { - const seriesList = _.map(chainList, function (chain) { + const seriesList = _.map(chainList, function(chain) { const values = invoke('first', [chain]); - return values.then(function (args) { + return values.then(function(args) { return args; }); }); - return Bluebird.all(seriesList).then(function (args) { - const list = _.chain(args).pluck('list').flatten().value(); + return Bluebird.all(seriesList).then(function(args) { + const list = _.chain(args) + .pluck('list') + .flatten() + .value(); const seriesList = _.merge.apply(this, _.flatten([{}, args])); seriesList.list = list; return seriesList; @@ -142,11 +143,10 @@ export default function chainRunner(tlConfig) { } function preProcessSheet(sheet) { - let queries = {}; - _.each(sheet, function (chainList, i) { + _.each(sheet, function(chainList, i) { try { - const queriesInCell = _.mapValues(preprocessChain(chainList), function (val) { + const queriesInCell = _.mapValues(preprocessChain(chainList), function(val) { val.cell = i; return val; }); @@ -157,15 +157,17 @@ export default function chainRunner(tlConfig) { }); queries = _.values(queries); - const promises = _.chain(queries).values().map(function (query) { - return invoke(query.function, query.arguments); - }).value(); + const promises = _.chain(queries) + .values() + .map(function(query) { + return invoke(query.function, query.arguments); + }) + .value(); - return Bluebird.settle(promises).then(function (resolvedDatasources) { + return Bluebird.settle(promises).then(function(resolvedDatasources) { + stats.queryTime = new Date().getTime(); - stats.queryTime = (new Date()).getTime(); - - _.each(queries, function (query, i) { + _.each(queries, function(query, i) { const functionDef = tlConfig.server.plugins.timelion.getFunction(query.function); const resolvedDatasource = resolvedDatasources[i]; @@ -198,31 +200,35 @@ export default function chainRunner(tlConfig) { tlConfig.time.to, tlConfig.settings['timelion:target_buckets'] || 200, tlConfig.time.interval, - tlConfig.settings['timelion:min_interval'] || '1ms', + tlConfig.settings['timelion:min_interval'] || '1ms' ); tlConfig.setTargetSeries(); - stats.invokeTime = (new Date()).getTime(); + stats.invokeTime = new Date().getTime(); stats.queryCount = 0; queryCache = {}; // This is setting the "global" sheet, required for resolving references sheet = parseSheet(request.sheet); - return preProcessSheet(sheet).then(function () { - return _.map(sheet, function (chainList, i) { - return resolveChainList(chainList).then(function (seriesList) { - stats.sheetTime = (new Date()).getTime(); - return seriesList; - }).catch(function (e) { - throwWithCell(i, e); - }); + return preProcessSheet(sheet).then(function() { + return _.map(sheet, function(chainList, i) { + return resolveChainList(chainList) + .then(function(seriesList) { + stats.sheetTime = new Date().getTime(); + return seriesList; + }) + .catch(function(e) { + throwWithCell(i, e); + }); }); }); } return { processRequest: processRequest, - getStats: function () { return stats; } + getStats: function() { + return stats; + }, }; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js index 5c5be4ab39dac..20e46383baa3a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js @@ -22,13 +22,14 @@ import { i18n } from '@kbn/i18n'; // Only applies to already resolved arguments export default function indexArguments(functionDef, orderedArgs) { - const validateArg = require('./validate_arg')(functionDef); // This almost certainly is not required - const allowedLength = functionDef.extended ? functionDef.args.length + 2 : functionDef.args.length; + const allowedLength = functionDef.extended + ? functionDef.args.length + 2 + : functionDef.args.length; if (orderedArgs.length > allowedLength) { - throw new Error ( + throw new Error( i18n.translate('timelion.serverSideErrors.argumentsOverflowErrorMessage', { defaultMessage: 'Too many arguments passed to: {functionName}', values: { @@ -40,7 +41,7 @@ export default function indexArguments(functionDef, orderedArgs) { const indexedArgs = {}; // Check and index each known argument - _.each(functionDef.args, function (argDef, i) { + _.each(functionDef.args, function(argDef, i) { const value = orderedArgs[i]; validateArg(value, argDef.name, argDef); indexedArgs[argDef.name] = value; @@ -50,7 +51,7 @@ export default function indexArguments(functionDef, orderedArgs) { if (functionDef.extended) { const values = orderedArgs[orderedArgs.length - 1]; const names = orderedArgs[orderedArgs.length - 2]; - _.each(values, function (value, i) { + _.each(values, function(value, i) { validateArg(value, names[i], functionDef.extended); indexedArgs[names[i]] = value; }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js index 8aca7f516b7e0..74ef76d1a50cd 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js @@ -26,7 +26,7 @@ import PEG from 'pegjs'; const Parser = PEG.generate(grammar); export default function parseSheet(sheet) { - return _.map(sheet, function (plot) { + return _.map(sheet, function(plot) { try { return Parser.parse(plot).tree; } catch (e) { diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js index 992d34c22232b..5e5f274115ee2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js @@ -41,7 +41,7 @@ export default function preProcessChainFn(tlConfig) { if (!Array.isArray(chain)) return; - _.each(chain, function (operator) { + _.each(chain, function(operator) { if (!_.isObject(operator)) { return; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js index 0bb7e641ad679..85a78564bf506 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js @@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n'; export default function repositionArguments(functionDef, unorderedArgs) { const args = []; - _.each(unorderedArgs, function (unorderedArg, i) { + _.each(unorderedArgs, function(unorderedArg, i) { let argDef; let targetIndex; let value; @@ -45,11 +45,10 @@ export default function repositionArguments(functionDef, unorderedArgs) { storeAsArray = true; } } else { - targetIndex = _.findIndex(functionDef.args, function (orderedArg) { + targetIndex = _.findIndex(functionDef.args, function(orderedArg) { return unorderedArg.name === orderedArg.name; }); storeAsArray = argDef.multi; - } value = unorderedArg.value; } else { @@ -65,7 +64,7 @@ export default function repositionArguments(functionDef, unorderedArgs) { defaultMessage: 'Unknown argument to {functionName}: {argumentName}', values: { functionName: functionDef.name, - argumentName: (unorderedArg.name || ('#' + i)), + argumentName: unorderedArg.name || '#' + i, }, }) ); @@ -80,5 +79,4 @@ export default function repositionArguments(functionDef, unorderedArgs) { }); return args; - } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js index 51235c26c1d5e..28edafbc3be67 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js @@ -24,17 +24,20 @@ export default function tlConfigFn(setup) { let targetSeries; let tlConfig = { - getTargetSeries: function () { - return _.map(targetSeries, function (bucket) { // eslint-disable-line no-use-before-define + getTargetSeries: function() { + return _.map(targetSeries, function(bucket) { + // eslint-disable-line no-use-before-define return [bucket, null]; }); }, - setTargetSeries: function () { + setTargetSeries: function() { targetSeries = buildTarget(this); }, - writeTargetSeries: function (series) { - targetSeries = _.map(series, function (p) {return p[0];}); - } + writeTargetSeries: function(series) { + targetSeries = _.map(series, function(p) { + return p[0]; + }); + }, }; tlConfig = _.extend(tlConfig, setup); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js index 22b904a8a1fea..1fb325bb6ee86 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js @@ -26,12 +26,12 @@ export default function validateArgFn(functionDef) { const type = argType(value); const required = argDef.types; const multi = argDef.multi; - const isCorrectType = (function () { + const isCorrectType = (function() { // If argument is not allow to be specified multiple times, we're dealing with a plain value for type if (!multi) return _.contains(required, type); // If it is, we'll get an array for type return _.difference(type, required).length === 0; - }()); + })(); if (isCorrectType) return true; else return false; @@ -39,7 +39,8 @@ export default function validateArgFn(functionDef) { if (!isCorrectType) { throw new Error( i18n.translate('timelion.serverSideErrors.wrongFunctionArgumentTypeErrorMessage', { - defaultMessage: '{functionName}({argumentName}) must be one of {requiredTypes}. Got: {actualType}', + defaultMessage: + '{functionName}({argumentName}) must be one of {requiredTypes}. Got: {actualType}', values: { functionName: functionDef.name, argumentName: name, diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js index 154392fd66253..c49d5f5f3ba25 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js @@ -29,21 +29,25 @@ import _ from 'lodash'; export default function alter(args, fn) { // In theory none of the args should ever be promises. This is probably a waste. - return Bluebird.all(args).then(function (args) { - - const seriesList = args.shift(); - - if (seriesList.type !== 'seriesList') { - throw new Error ('args[0] must be a seriesList'); - } - - const list = _.chain(seriesList.list).map(function (series) { - return fn.apply(this, [series].concat(args)); - }).flatten().value(); - - seriesList.list = list; - return seriesList; - }).catch(function (e) { - throw e; - }); + return Bluebird.all(args) + .then(function(args) { + const seriesList = args.shift(); + + if (seriesList.type !== 'seriesList') { + throw new Error('args[0] must be a seriesList'); + } + + const list = _.chain(seriesList.list) + .map(function(series) { + return fn.apply(this, [series].concat(args)); + }) + .flatten() + .value(); + + seriesList.list = list; + return seriesList; + }) + .catch(function(e) { + throw e; + }); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js index 639a7a1783b56..5ef11e79d9baa 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js @@ -20,7 +20,7 @@ import moment from 'moment'; import splitInterval from './split_interval.js'; -export default function (tlConfig) { +export default function(tlConfig) { const min = moment(tlConfig.time.from); const max = moment(tlConfig.time.to); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js index 99162df59044b..e9d5fa8774b24 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js @@ -19,15 +19,14 @@ import { i18n } from '@kbn/i18n'; import loadFunctions from '../load_functions.js'; -const fitFunctions = loadFunctions('fit_functions'); +const fitFunctions = loadFunctions('fit_functions'); import TimelionFunction from './timelion_function'; import { offsetTime, preprocessOffset } from '../offset_time'; import _ from 'lodash'; - function offsetSeries(response, offset) { if (offset) { - response = _.map(response, function (point) { + response = _.map(response, function(point) { return [offsetTime(point[0], offset, true), point[1]]; }); } @@ -36,7 +35,6 @@ function offsetSeries(response, offset) { export default class Datasource extends TimelionFunction { constructor(name, config) { - // Additional arguments that every dataSource take config.args.push({ name: 'offset', @@ -59,12 +57,12 @@ export default class Datasource extends TimelionFunction { values: { fitFunctions: _.keys(fitFunctions).join(', '), }, - }) + }), }); // Wrap the original function so we can modify inputs/outputs with offset & fit const originalFunction = config.fn; - config.fn = function (args, tlConfig) { + config.fn = function(args, tlConfig) { const config = _.clone(tlConfig); let offset = args.byName.offset; if (offset) { @@ -74,15 +72,14 @@ export default class Datasource extends TimelionFunction { config.time.to = offsetTime(config.time.to, offset); } - return Promise.resolve(originalFunction(args, config)).then(function (seriesList) { - seriesList.list = _.map(seriesList.list, function (series) { + return Promise.resolve(originalFunction(args, config)).then(function(seriesList) { + seriesList.list = _.map(seriesList.list, function(series) { if (series.data.length === 0) throw new Error(name + '() returned no results'); series.data = offsetSeries(series.data, offset); series.fit = args.byName.fit || series.fit || 'nearest'; return series; }); return seriesList; - }); }; @@ -92,10 +89,9 @@ export default class Datasource extends TimelionFunction { // otherwise teh series will end up being offset twice. this.timelionFn = originalFunction; this.datasource = true; - this.cacheKey = function (item) { + this.cacheKey = function(item) { return item.text; }; Object.freeze(this); } - } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js index 4fc584bd591cc..2b6919dbcb350 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import loadFunctions from '../load_functions.js'; -const fitFunctions = loadFunctions('fit_functions'); +const fitFunctions = loadFunctions('fit_functions'); export default class TimelionFunction { constructor(name, config) { @@ -31,15 +31,19 @@ export default class TimelionFunction { this.extended = config.extended || false; // WTF is this? How could you not have a fn? Wtf would the thing be used for? - const originalFunction = config.fn || function (input) { return input; }; + const originalFunction = + config.fn || + function(input) { + return input; + }; // Currently only re-fits the series. this.originalFn = originalFunction; - this.fn = function (args, tlConfig) { + this.fn = function(args, tlConfig) { const config = _.clone(tlConfig); - return Promise.resolve(originalFunction(args, config)).then(function (seriesList) { - seriesList.list = _.map(seriesList.list, function (series) { + return Promise.resolve(originalFunction(args, config)).then(function(seriesList) { + seriesList.list = _.map(seriesList.list, function(series) { const target = tlConfig.getTargetSeries(); // Don't fit if the series are already the same diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js index 34189314f64d7..79302cef588d6 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js @@ -18,12 +18,11 @@ */ import loadFunctions from './load_functions.js'; -const functions = loadFunctions('series_functions/'); +const functions = loadFunctions('series_functions/'); import _ from 'lodash'; - -export default (function () { - const functionArray = _.map(functions, function (val, key) { +export default (function() { + const functionArray = _.map(functions, function(val, key) { // TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables return _.extend({}, val, { name: key }); }); @@ -46,7 +45,7 @@ export default (function () { help += 'Argument | Accepts | Description\n'; help += '--- | --- | ---\n'; - _.each(args, function (arg) { + _.each(args, function(arg) { help += arg.name + ' | *' + _.without(arg.types, 'null').join('/') + '* | '; help += arg.help ? arg.help : '*no help available*'; help += ' \n'; @@ -60,12 +59,14 @@ export default (function () { function createDocs() { let help = ''; help += '## Timelion function reference\n'; - help += 'This document is auto generated from the timelion code. ' + - 'Do not submit pulls against this document. You want to submit a pull against something in the ' + - '`series_functions/` directory.\n\n'; + help += + 'This document is auto generated from the timelion code. ' + + 'Do not submit pulls against this document. You want to submit a pull against something in the ' + + '`series_functions/` directory.\n\n'; help += '### Data sources\n'; - help += 'Data sources can start a chain, they don\'t need to be attached to anything, but they still need to start' + + help += + "Data sources can start a chain, they don't need to be attached to anything, but they still need to start" + ' with a `.` (dot). Data retrieved from a data source can be passed into the chainable functions in the next section.\n\n'; help += _.chain(functionArray) @@ -75,10 +76,10 @@ export default (function () { .join(''); help += '### Chainable functions\n'; - help += 'Chainable functions can not start a chain. Somewhere before them must be a data source function. Chainable' + - ' functions modify the data output directly from a data source, or from another chainable function that has a data' + - ' source somewhere before it.\n\n'; - + help += + 'Chainable functions can not start a chain. Somewhere before them must be a data source function. Chainable' + + ' functions modify the data output directly from a data source, or from another chainable function that has a data' + + ' source somewhere before it.\n\n'; help += _.chain(functionArray) .filter('chainable') @@ -90,4 +91,4 @@ export default (function () { } return createDocs(); -}()); +})(); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js index 5a608914171bc..5d18d87e8e054 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js @@ -20,25 +20,29 @@ import _ from 'lodash'; import configFile from '../../timelion.json'; -export default function () { +export default function() { function flattenWith(dot, nestedObj, flattenArrays) { const stack = []; // track key stack const flatObj = {}; (function flattenObj(obj) { - _.keys(obj).forEach(function (key) { + _.keys(obj).forEach(function(key) { stack.push(key); if (!flattenArrays && Array.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; else if (_.isObject(obj[key])) flattenObj(obj[key]); else flatObj[stack.join(dot)] = obj[key]; stack.pop(); }); - }(nestedObj)); + })(nestedObj); return flatObj; } const timelionDefaults = flattenWith('.', configFile); - return _.reduce(timelionDefaults, (result, value, key) => { - result['timelion:' + key] = value; - return result; - }, {}); + return _.reduce( + timelionDefaults, + (result, value, key) => { + result['timelion:' + key] = value; + return result; + }, + {} + ); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js index b1c9c986d14ad..11501ce3f102b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js @@ -22,32 +22,34 @@ import glob from 'glob'; import path from 'path'; import processFunctionDefinition from './process_function_definition'; -export default function (directory) { - +export default function(directory) { function getTuple(directory, name) { return [name, require('../' + directory + '/' + name)]; // eslint-disable-line import/no-dynamic-require } // Get a list of all files and use the filename as the object key - const files = _.map(glob.sync(path.resolve(__dirname, '../' + directory + '/*.js')), function (file) { + const files = _.map(glob.sync(path.resolve(__dirname, '../' + directory + '/*.js')), function( + file + ) { const name = file.substring(file.lastIndexOf('/') + 1, file.lastIndexOf('.')); return getTuple(directory, name); }); // Get a list of all directories with an index.js, use the directory name as the key in the object const directories = _.chain(glob.sync(path.resolve(__dirname, '../' + directory + '/*/index.js'))) - .filter(function (file) { + .filter(function(file) { return file.match(/__test__/) == null; }) - .map(function (file) { + .map(function(file) { const parts = file.split('/'); const name = parts[parts.length - 2]; return getTuple(directory, name); - }).value(); + }) + .value(); const functions = _.zipObject(files.concat(directories)); - _.each(functions, function (func) { + _.each(functions, function(func) { _.assign(functions, processFunctionDefinition(func)); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js index 4c9f364c8c4be..05aeb07554316 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js @@ -22,7 +22,7 @@ import moment from 'moment'; // usually reverse = false on the request, true on the response export function offsetTime(milliseconds, offset, reverse) { if (!offset.match(/[-+][0-9]+[mshdwMy]/g)) { - throw new Error ('Malformed `offset` at ' + offset); + throw new Error('Malformed `offset` at ' + offset); } const parts = offset.match(/[-+]|[0-9]+|[mshdwMy]/g); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js index 9250d3898638d..a535f30afaf99 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js @@ -22,7 +22,6 @@ import moment from 'moment'; import { preprocessOffset } from './offset_time'; describe('offset', () => { - describe('preprocessOffset', () => { const from = moment('2018-01-01T00:00:00.000Z').valueOf(); const to = moment('2018-01-01T00:15:00.000Z').valueOf(); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js index 3159d33c884e8..e569b9b062218 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js @@ -19,11 +19,11 @@ import _ from 'lodash'; -export default function (func) { +export default function(func) { const functions = {}; functions[func.name] = func; if (func.aliases) { - _.each(func.aliases, function (alias) { + _.each(func.aliases, function(alias) { const aliasFn = _.clone(func); aliasFn.isAlias = true; functions[alias] = aliasFn; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js index e9cbfd76b21ac..be36e0695bea6 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js @@ -35,7 +35,7 @@ function allSeriesContainKey(seriesList, key) { */ async function pairwiseReduce(left, right, fn) { if (left.list.length !== right.list.length) { - throw new Error ('Unable to pairwise reduce seriesLists, number of series are not the same'); + throw new Error('Unable to pairwise reduce seriesLists, number of series are not the same'); } let pairwiseField = 'label'; @@ -45,18 +45,22 @@ async function pairwiseReduce(left, right, fn) { const indexedList = _.indexBy(right.list, pairwiseField); // ensure seriesLists contain same pairwise labels - left.list.forEach((leftSeries) => { + left.list.forEach(leftSeries => { if (!indexedList[leftSeries[pairwiseField]]) { - const rightSeriesLabels = right.list.map((rightSeries) => { - return `"${rightSeries[pairwiseField]}"`; - }).join(','); - throw new Error (`Matching series could not be found for "${leftSeries[pairwiseField]}" in [${rightSeriesLabels}]`); + const rightSeriesLabels = right.list + .map(rightSeries => { + return `"${rightSeries[pairwiseField]}"`; + }) + .join(','); + throw new Error( + `Matching series could not be found for "${leftSeries[pairwiseField]}" in [${rightSeriesLabels}]` + ); } }); // pairwise reduce seriesLists const pairwiseSeriesList = { type: 'seriesList', list: [] }; - left.list.forEach(async (leftSeries) => { + left.list.forEach(async leftSeries => { const first = { type: 'seriesList', list: [leftSeries] }; const second = { type: 'seriesList', list: [indexedList[leftSeries[pairwiseField]]] }; const reducedSeriesList = await reduce([first, second], fn); @@ -84,7 +88,7 @@ async function reduce(argsPromises, fn) { let argument = args.shift(); if (seriesList.type !== 'seriesList') { - throw new Error ('input must be a seriesList'); + throw new Error('input must be a seriesList'); } if (_.isObject(argument) && argument.type === 'seriesList') { @@ -95,12 +99,9 @@ async function reduce(argsPromises, fn) { } } - function reduceSeries(series) { - return _.reduce(series, function (destinationObject, argument, i, p) { - - let output = _.map(destinationObject.data, function (point, index) { - + return _.reduce(series, function(destinationObject, argument, i, p) { + let output = _.map(destinationObject.data, function(point, index) { const value = point[1]; if (value == null) { @@ -120,19 +121,17 @@ async function reduce(argsPromises, fn) { // Output = single series output = { - data: output + data: output, }; output = _.defaults(output, destinationObject); return output; - }); - } let reduced; if (argument != null) { - reduced = _.map(seriesList.list, function (series) { + reduced = _.map(seriesList.list, function(series) { return reduceSeries([series].concat(argument)); }); } else { diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js index 41c6af9466d95..398a96c359e53 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js @@ -19,12 +19,12 @@ export default function splitInterval(interval) { if (!interval.match(/[0-9]+[mshdwMy]+/g)) { - throw new Error ('Malformed `interval`: ' + interval); + throw new Error('Malformed `interval`: ' + interval); } const parts = interval.match(/[0-9]+|[mshdwMy]+/g); return { count: parts[0], - unit: parts[1] + unit: parts[1], }; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js index 2dd5cad4cad82..0d62d848daba5 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js @@ -22,31 +22,35 @@ import moment from 'moment'; // map of moment's short/long unit ids and elasticsearch's long unit ids // to their value in milliseconds -const vals = _.transform([ - ['ms', 'milliseconds', 'millisecond'], - ['s', 'seconds', 'second', 'sec'], - ['m', 'minutes', 'minute', 'min'], - ['h', 'hours', 'hour'], - ['d', 'days', 'day'], - ['w', 'weeks', 'week'], - ['M', 'months', 'month'], - ['quarter'], - ['y', 'years', 'year'] -], function (vals, units) { - const normal = moment.normalizeUnits(units[0]); - const val = moment.duration(1, normal).asMilliseconds(); - [].concat(normal, units).forEach(function (unit) { - vals[unit] = val; - }); -}, {}); +const vals = _.transform( + [ + ['ms', 'milliseconds', 'millisecond'], + ['s', 'seconds', 'second', 'sec'], + ['m', 'minutes', 'minute', 'min'], + ['h', 'hours', 'hour'], + ['d', 'days', 'day'], + ['w', 'weeks', 'week'], + ['M', 'months', 'month'], + ['quarter'], + ['y', 'years', 'year'], + ], + function(vals, units) { + const normal = moment.normalizeUnits(units[0]); + const val = moment.duration(1, normal).asMilliseconds(); + [].concat(normal, units).forEach(function(unit) { + vals[unit] = val; + }); + }, + {} +); // match any key from the vals object preceded by an optional number const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); -export default function (expr) { +export default function(expr) { const match = expr.match(parseRE); if (match) { if (match[2] === 'M' && match[1] !== '1') { - throw new Error ('Invalid interval. 1M is only valid monthly interval.'); + throw new Error('Invalid interval. 1M is only valid monthly interval.'); } return parseFloat(match[1] || 1) * vals[match[2]]; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js index 7dc7eed89b6b1..071e3873ccb30 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js @@ -20,10 +20,14 @@ import _ from 'lodash'; export default function unzipPairs(timeValObject) { - const paired = _.chain(timeValObject).pairs().map(function (point) { - return [parseInt(point[0], 10), point[1]]; - }).sortBy(function (point) { - return point[0]; - }).value(); + const paired = _.chain(timeValObject) + .pairs() + .map(function(point) { + return [parseInt(point[0], 10), point[1]]; + }) + .sortBy(function(point) { + return point[0]; + }) + .value(); return paired; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js index ff8b750cf1ec2..813d006225f43 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js @@ -24,12 +24,12 @@ export function functionsRoute(server) { method: 'GET', path: '/api/timelion/functions', handler: () => { - const functionArray = _.map(server.plugins.timelion.functions, function (val, key) { + const functionArray = _.map(server.plugins.timelion.functions, function(val, key) { // TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables return _.extend({}, val, { name: key }); }); return _.sortBy(functionArray, 'name'); - } + }, }); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/run.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/run.js deleted file mode 100644 index d868f868aaf3e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/run.js +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Bluebird from 'bluebird'; -import _ from 'lodash'; -import chainRunnerFn from '../handlers/chain_runner.js'; -const timelionDefaults = require('../lib/get_namespaced_settings')(); - -function formatErrorResponse(e, h) { - return h.response({ - title: e.toString(), - message: e.toString() - }).code(500); -} - -export function runRoute(server) { - server.route({ - method: ['POST', 'GET'], - path: '/api/timelion/run', - handler: async (request, h) => { - try { - const uiSettings = await request.getUiSettingsService().getAll(); - - const tlConfig = require('../handlers/lib/tl_config.js')({ - server, - request, - settings: _.defaults(uiSettings, timelionDefaults) // Just in case they delete some setting. - }); - - const chainRunner = chainRunnerFn(tlConfig); - const sheet = await Bluebird.all(chainRunner.processRequest(request.payload || { - sheet: [request.query.expression], - time: { - from: request.query.from, - to: request.query.to, - interval: request.query.interval, - timezone: request.query.timezone - } - })); - - return { - sheet, - stats: chainRunner.getStats() - }; - - } catch (err) { - server.log(['timelion', 'error'], `${err.toString()}: ${err.stack}`); - // TODO Maybe we should just replace everywhere we throw with Boom? Probably. - if (err.isBoom) { - return err; - } else { - return formatErrorResponse(err, h); - } - } - } - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts b/src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts new file mode 100644 index 0000000000000..17f87825cd8b0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import Joi from 'joi'; +import Bluebird from 'bluebird'; +import _ from 'lodash'; +import { Legacy } from 'kibana'; +// @ts-ignore +import chainRunnerFn from '../handlers/chain_runner.js'; +// @ts-ignore +import getNamespacesSettings from '../lib/get_namespaced_settings'; +// @ts-ignore +import getTlConfig from '../handlers/lib/tl_config'; + +const timelionDefaults = getNamespacesSettings(); + +export interface TimelionRequestQuery { + payload: { + sheet: string[]; + extended?: { + es: { + filter: { + bool: { + filter: string[] | object; + must: string[]; + should: string[]; + must_not: string[]; + }; + }; + }; + }; + }; + time?: { + from?: string; + interval: string; + timezone: string; + to?: string; + }; +} + +function formatErrorResponse(e: Error, h: Legacy.ResponseToolkit) { + return h + .response({ + title: e.toString(), + message: e.toString(), + }) + .code(500); +} + +const requestPayload = { + payload: Joi.object({ + sheet: Joi.array() + .items(Joi.string()) + .required(), + extended: Joi.object({ + es: Joi.object({ + filter: Joi.object({ + bool: Joi.object({ + filter: Joi.array().allow(null), + must: Joi.array().allow(null), + should: Joi.array().allow(null), + must_not: Joi.array().allow(null), + }), + }), + }), + }).optional(), + time: Joi.object({ + from: Joi.string(), + interval: Joi.string().required(), + timezone: Joi.string().required(), + to: Joi.string(), + }).required(), + }), +}; + +export function runRoute(server: Legacy.Server) { + server.route({ + method: 'POST', + path: '/api/timelion/run', + options: { + validate: requestPayload, + }, + handler: async (request: Legacy.Request & TimelionRequestQuery, h: Legacy.ResponseToolkit) => { + try { + const uiSettings = await request.getUiSettingsService().getAll(); + + const tlConfig = getTlConfig({ + server, + request, + settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. + }); + const chainRunner = chainRunnerFn(tlConfig); + const sheet = await Bluebird.all(chainRunner.processRequest(request.payload)); + + return { + sheet, + stats: chainRunner.getStats(), + }; + } catch (err) { + server.log(['timelion', 'error'], `${err.toString()}: ${err.stack}`); + // TODO Maybe we should just replace everywhere we throw with Boom? Probably. + if (err.isBoom) { + return err; + } else { + return formatErrorResponse(err, h); + } + } + }, + }); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js index 350f6fd656010..5e39069f2a698 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js @@ -23,7 +23,7 @@ export function validateEsRoute(server) { server.route({ method: 'GET', path: '/api/timelion/validate/es', - handler: async function (request) { + handler: async function(request) { const uiSettings = await request.getUiSettingsService().getAll(); const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); @@ -36,17 +36,17 @@ export function validateEsRoute(server) { aggs: { maxAgg: { max: { - field: timefield - } + field: timefield, + }, }, minAgg: { min: { - field: timefield - } - } + field: timefield, + }, + }, }, - size: 0 - } + size: 0, + }, }; let resp = {}; @@ -61,14 +61,14 @@ export function validateEsRoute(server) { ok: true, field: timefield, min: _.get(resp, 'aggregations.minAgg.value'), - max: _.get(resp, 'aggregations.maxAgg.value') + max: _.get(resp, 'aggregations.maxAgg.value'), }; } return { ok: false, - resp: resp + resp: resp, }; - } + }, }); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js index 9d90a55bcb71e..28538d4da2f79 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js @@ -24,16 +24,15 @@ const expect = require('chai').expect; const seriesList = require('./fixtures/seriesList.js')(); import invoke from './helpers/invoke_series_fn.js'; -describe('abs.js', function () { - it('should return the positive value of every value', function () { - - return invoke(fn, [seriesList]).then(function (result) { - const before = _.filter(result.input[0].list[0].data, function (point) { - return (point[1] < 0); +describe('abs.js', function() { + it('should return the positive value of every value', function() { + return invoke(fn, [seriesList]).then(function(result) { + const before = _.filter(result.input[0].list[0].data, function(point) { + return point[1] < 0; }); - const after = _.filter(result.output.list[0].data, function (point) { - return (point[1] < 0); + const after = _.filter(result.output.list[0].data, function(point) { + return point[1] < 0; }); expect(before.length > 0).to.eql(true); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js index 26f853b86c091..6177f7cb7bac4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js @@ -25,52 +25,50 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe(filename, () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('first', () => { - return invoke(fn, [seriesList, 'first']).then((r) => { + return invoke(fn, [seriesList, 'first']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([100, 100, 100, 100]); }); }); it('last', () => { - return invoke(fn, [seriesList, 'last']).then((r) => { + return invoke(fn, [seriesList, 'last']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([20, 20, 20, 20]); }); }); it('min', () => { - return invoke(fn, [seriesList, 'min']).then((r) => { + return invoke(fn, [seriesList, 'min']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([20, 20, 20, 20]); }); }); it('max', () => { - return invoke(fn, [seriesList, 'max']).then((r) => { + return invoke(fn, [seriesList, 'max']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([100, 100, 100, 100]); }); }); it('sum', () => { - return invoke(fn, [seriesList, 'sum']).then((r) => { + return invoke(fn, [seriesList, 'sum']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([220, 220, 220, 220]); }); }); it('cardinality', () => { - return invoke(fn, [seriesList, 'cardinality']).then((r) => { + return invoke(fn, [seriesList, 'cardinality']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([3, 3, 3, 3]); }); }); it('avg', () => { - return invoke(fn, [seriesList, 'avg']).then((r) => { + return invoke(fn, [seriesList, 'avg']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([55, 55, 55, 55]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js index e64e5f8b87dde..90b66759f7341 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js @@ -24,24 +24,23 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('bars.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('creates the bars property, with defaults, on all series', () => { - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { const bars = _.map(r.output.list, 'bars'); - _.each(bars, (bar) => expect(bar).to.be.a('object')); - _.each(bars, (bar) => expect(bar.lineWidth).to.equal(6)); - _.each(bars, (bar) => expect(bar.show).to.equal(1)); + _.each(bars, bar => expect(bar).to.be.a('object')); + _.each(bars, bar => expect(bar.lineWidth).to.equal(6)); + _.each(bars, bar => expect(bar.show).to.equal(1)); }); }); it('leaves existing bars alone when called without option, if they exist', () => { seriesList.list[0].bars = { foo: true }; - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { const bars = _.map(r.output.list, 'bars'); expect(bars[0].foo).to.equal(true); expect(bars[1].foo).to.equal(undefined); @@ -49,12 +48,10 @@ describe('bars.js', () => { }); it('sets lineWidth and show to the same value', () => { - return invoke(fn, [seriesList, 0]).then((r) => { + return invoke(fn, [seriesList, 0]).then(r => { const bars = _.map(r.output.list, 'bars'); expect(bars[0].lineWidth).to.equal(0); expect(bars[0].show).to.equal(0); - }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js index dfe9bfc3988e7..f333a39bec5ba 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js @@ -24,16 +24,15 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('color.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('sets the color, on all series', () => { - return invoke(fn, [seriesList, '#eee']).then((r) => { + return invoke(fn, [seriesList, '#eee']).then(r => { const colors = _.map(r.output.list, 'color'); - _.each(colors, (color) => expect(color).to.equal('#eee')); + _.each(colors, color => expect(color).to.equal('#eee')); }); }); @@ -41,9 +40,9 @@ describe('color.js', () => { const expected = ['#000000', '#111111', '#222222', '#333333']; const fourLongList = { type: 'seriesList', - list: seriesList.list.slice(0, 4) + list: seriesList.list.slice(0, 4), }; - return invoke(fn, [fourLongList, '#000:#333']).then((r) => { + return invoke(fn, [fourLongList, '#000:#333']).then(r => { const colors = _.map(r.output.list, 'color'); _.each(colors, (color, i) => expect(color).to.equal(expected[i])); }); @@ -62,7 +61,7 @@ describe('color.js', () => { it('should work with series.length=1 and more colors', async () => { const oneLongList = { type: 'seriesList', - list: seriesList.list.slice(0, 1) + list: seriesList.list.slice(0, 1), }; const colorsArg = '#000:#111'; @@ -72,9 +71,8 @@ describe('color.js', () => { }); it('throws if you do not pass a color', () => { - invoke(fn, [seriesList, '']).catch((e) => { + invoke(fn, [seriesList, '']).catch(e => { expect(e).to.be.an(Error); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js index 67bfa0cfdea95..533c5adfd62ab 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js @@ -24,11 +24,10 @@ import invoke from './helpers/invoke_series_fn.js'; import getSeriesList from './helpers/get_single_series_list'; import _ from 'lodash'; -describe('condition.js', function () { - +describe('condition.js', function() { let comparable; let seriesList; - beforeEach(function () { + beforeEach(function() { seriesList = require('./fixtures/seriesList.js')(); comparable = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 12], @@ -38,76 +37,73 @@ describe('condition.js', function () { ]); }); - describe('a single number with', function () { - it('eq', function () { - return invoke(fn, [seriesList, 'eq', 17, 0]).then(function (r) { + describe('a single number with', function() { + it('eq', function() { + return invoke(fn, [seriesList, 'eq', 17, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 0, 82, 20]); }); }); - it('ne', function () { - return invoke(fn, [seriesList, 'ne', 17, 0]).then(function (r) { + it('ne', function() { + return invoke(fn, [seriesList, 'ne', 17, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([0, 17, 0, 0]); }); }); - it('gte', function () { - return invoke(fn, [seriesList, 'gte', 17, 0]).then(function (r) { + it('gte', function() { + return invoke(fn, [seriesList, 'gte', 17, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 0, 0, 0]); }); }); - it('gt', function () { - return invoke(fn, [seriesList, 'gt', 17, 0]).then(function (r) { + it('gt', function() { + return invoke(fn, [seriesList, 'gt', 17, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 17, 0, 0]); }); }); - it('lt', function () { - return invoke(fn, [seriesList, 'lt', 17, 0]).then(function (r) { + it('lt', function() { + return invoke(fn, [seriesList, 'lt', 17, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([0, 17, 82, 20]); }); }); - it('lte', function () { - return invoke(fn, [seriesList, 'lte', 17, 0]).then(function (r) { + it('lte', function() { + return invoke(fn, [seriesList, 'lte', 17, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 20]); }); }); }); - it('can compare against another series', function () { - return invoke(fn, [seriesList, 'ne', comparable, 0]).then(function (r) { + it('can compare against another series', function() { + return invoke(fn, [seriesList, 'ne', comparable, 0]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 0]); }); }); - it('can set the resultant value to that of another series', function () { - return invoke(fn, [seriesList, 'lt', comparable, comparable]).then(function (r) { + it('can set the resultant value to that of another series', function() { + return invoke(fn, [seriesList, 'lt', comparable, comparable]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([12, 33, 82, 20]); }); }); - it('can set the resultant value to null', function () { - return invoke(fn, [seriesList, 'lt', 17, null]).then(function (r) { + it('can set the resultant value to null', function() { + return invoke(fn, [seriesList, 'lt', 17, null]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([null, 17, 82, 20]); }); }); - describe('else', function () { - it('has else', function () { - return invoke(fn, [seriesList, 'lt', 30, 0, 1]).then(function (r) { + describe('else', function() { + it('has else', function() { + return invoke(fn, [seriesList, 'lt', 30, 0, 1]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 1, 0]); }); }); - it('works with other series', function () { - return invoke(fn, [seriesList, 'lt', 30, 0, comparable]).then(function (r) { + it('works with other series', function() { + return invoke(fn, [seriesList, 'lt', 30, 0, comparable]).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 0]); }); }); }); - - - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js index dc0e88177f0c4..d9f534555b9d7 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('cusum.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('progressively adds the numbers in the list', () => { - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([100, 150, 200, 220]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js index dd0c134ff4b33..88ef4778ef2f1 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('derivative.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('gets the change in the set', () => { - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([null, -50, 0, -30]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js index bee664c7a4831..afe531922522f 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('divide.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('divides by a single number', () => { - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([50, 25, 25, 10]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js index f370c3439125b..f2b364afb723b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js @@ -37,25 +37,28 @@ function stubRequestAndServer(response, indexPatternSavedObjects = []) { server: { plugins: { elasticsearch: { - getCluster: sinon.stub().withArgs('data').returns({ - callWithRequest: function () { - return Bluebird.resolve(response); - } - }) - } - } + getCluster: sinon + .stub() + .withArgs('data') + .returns({ + callWithRequest: function() { + return Bluebird.resolve(response); + }, + }), + }, + }, }, request: { - getSavedObjectsClient: function () { + getSavedObjectsClient: function() { return { - find: function () { + find: function() { return Bluebird.resolve({ - saved_objects: indexPatternSavedObjects + saved_objects: indexPatternSavedObjects, }); - } + }, }; - } - } + }, + }, }; } @@ -65,25 +68,23 @@ describe(filename, () => { describe('seriesList processor', () => { it('throws an error then the index is missing', () => { tlConfig = stubRequestAndServer({ - _shards: { total: 0 } + _shards: { total: 0 }, }); return invoke(es, [5], tlConfig) .then(expect.fail) - .catch((e) => { + .catch(e => { expect(e).to.be.an('error'); }); }); it('returns a seriesList', () => { tlConfig = stubRequestAndServer(esResponse); - return invoke(es, [5], tlConfig) - .then((r) => { - expect(r.output.type).to.eql('seriesList'); - }); + return invoke(es, [5], tlConfig).then(r => { + expect(r.output.type).to.eql('seriesList'); + }); }); }); - describe('createDateAgg', () => { let tlConfig; let config; @@ -92,12 +93,11 @@ describe(filename, () => { tlConfig = tlConfigFn(); config = { timefield: '@timestamp', - interval: '1y' + interval: '1y', }; agg = createDateAgg(config, tlConfig); }); - it('creates a date_histogram with meta.type of time_buckets', () => { expect(agg.time_buckets.meta.type).to.eql('time_buckets'); expect(agg.time_buckets.date_histogram).to.be.an('object'); @@ -129,24 +129,28 @@ describe(filename, () => { agg = createDateAgg(config, tlConfig, emptyScriptedFields); expect(agg.time_buckets.aggs['sum(beer)']).to.eql({ sum: { field: 'beer' } }); expect(agg.time_buckets.aggs['avg(bytes)']).to.eql({ avg: { field: 'bytes' } }); - expect(agg.time_buckets.aggs['percentiles(bytes)']).to.eql({ percentiles: { field: 'bytes' } }); + expect(agg.time_buckets.aggs['percentiles(bytes)']).to.eql({ + percentiles: { field: 'bytes' }, + }); }); it('adds a scripted metric agg for each scripted metric', () => { config.metric = ['avg:scriptedBytes']; - const scriptedFields = [{ - name: 'scriptedBytes', - script: 'doc["bytes"].value', - lang: 'painless' - }]; + const scriptedFields = [ + { + name: 'scriptedBytes', + script: 'doc["bytes"].value', + lang: 'painless', + }, + ]; agg = createDateAgg(config, tlConfig, scriptedFields); expect(agg.time_buckets.aggs['avg(scriptedBytes)']).to.eql({ avg: { script: { source: 'doc["bytes"].value', - lang: 'painless' - } - } + lang: 'painless', + }, + }, }); }); @@ -169,7 +173,7 @@ describe(filename, () => { config = { timefield: '@timestamp', interval: '1y', - index: 'beer' + index: 'beer', }; }); @@ -248,17 +252,23 @@ describe(filename, () => { from: 1, to: 5, }, - request: { payload: { extended: { es: { filter: { - bool: { - must: [ - { query: { query_string: { query: 'foo' } } } - ], - must_not: [ - { query: { query_string: { query: 'bar' } } }, - { query: { query_string: { query: 'baz' } } } - ] - } - } } } } } + request: { + payload: { + extended: { + es: { + filter: { + bool: { + must: [{ query: { query_string: { query: 'foo' } } }], + must_not: [ + { query: { query_string: { query: 'bar' } } }, + { query: { query_string: { query: 'baz' } } }, + ], + }, + }, + }, + }, + }, + }, }); }); @@ -279,11 +289,15 @@ describe(filename, () => { it('adds a time filter to the bool querys must clause', () => { let request = fn(config, tlConfig, emptyScriptedFields); expect(request.body.query.bool.must.length).to.eql(1); - expect(request.body.query.bool.must[0]).to.eql({ range: { '@timestamp': { - format: 'strict_date_optional_time', - gte: '1970-01-01T00:00:00.001Z', - lte: '1970-01-01T00:00:00.005Z' - } } }); + expect(request.body.query.bool.must[0]).to.eql({ + range: { + '@timestamp': { + format: 'strict_date_optional_time', + gte: '1970-01-01T00:00:00.001Z', + lte: '1970-01-01T00:00:00.005Z', + }, + }, + }); config.kibana = true; request = fn(config, tlConfig, emptyScriptedFields); @@ -313,13 +327,13 @@ describe(filename, () => { { name: 'scriptedBeer', script: 'doc["beer"].value', - lang: 'painless' + lang: 'painless', }, { name: 'scriptedWine', script: 'doc["wine"].value', - lang: 'painless' - } + lang: 'painless', + }, ]; const request = fn(config, tlConfig, scriptedFields); @@ -328,14 +342,14 @@ describe(filename, () => { expect(aggs.scriptedBeer.meta.type).to.eql('split'); expect(aggs.scriptedBeer.terms.script).to.eql({ source: 'doc["beer"].value', - lang: 'painless' + lang: 'painless', }); expect(aggs.scriptedBeer.terms.size).to.eql(5); expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).to.eql('split'); expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).to.eql({ source: 'doc["wine"].value', - lang: 'painless' + lang: 'painless', }); expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).to.eql(10); }); @@ -355,39 +369,76 @@ describe(filename, () => { const buckets = [ { key: 1000, count: { value: 3 } }, { key: 2000, count: { value: 14 } }, - { key: 3000, count: { value: 15 } } + { key: 3000, count: { value: 15 } }, ]; expect(fn(buckets)).to.eql({ - count: [[1000, 3], [2000, 14], [3000, 15]] + count: [ + [1000, 3], + [2000, 14], + [3000, 15], + ], }); }); it('Should convert multiple metric aggs', () => { const buckets = [ - { key: 1000, count: { value: 3 }, max: { value: 92 } }, + { key: 1000, count: { value: 3 }, max: { value: 92 } }, { key: 2000, count: { value: 14 }, max: { value: 65 } }, - { key: 3000, count: { value: 15 }, max: { value: 35 } } + { key: 3000, count: { value: 15 }, max: { value: 35 } }, ]; expect(fn(buckets)).to.eql({ - count: [[1000, 3], [2000, 14], [3000, 15]], - max: [[1000, 92], [2000, 65], [3000, 35]] + count: [ + [1000, 3], + [2000, 14], + [3000, 15], + ], + max: [ + [1000, 92], + [2000, 65], + [3000, 35], + ], }); }); it('Should convert percentiles metric aggs', () => { const buckets = [ - { key: 1000, percentiles: { values: { '50.0': 'NaN', '75.0': 65, '95.0': 73, '99.0': 75 } } }, - { key: 2000, percentiles: { values: { '50.0': 25, '75.0': 32, '95.0': 'NaN', '99.0': 67 } } }, - { key: 3000, percentiles: { values: { '50.0': 15, '75.0': 15, '95.0': 15, '99.0': 15 } } } + { + key: 1000, + percentiles: { values: { '50.0': 'NaN', '75.0': 65, '95.0': 73, '99.0': 75 } }, + }, + { + key: 2000, + percentiles: { values: { '50.0': 25, '75.0': 32, '95.0': 'NaN', '99.0': 67 } }, + }, + { + key: 3000, + percentiles: { values: { '50.0': 15, '75.0': 15, '95.0': 15, '99.0': 15 } }, + }, ]; expect(fn(buckets)).to.eql({ - 'percentiles:50.0': [[1000, NaN], [2000, 25], [3000, 15]], - 'percentiles:75.0': [[1000, 65], [2000, 32], [3000, 15]], - 'percentiles:95.0': [[1000, 73], [2000, NaN], [3000, 15]], - 'percentiles:99.0': [[1000, 75], [2000, 67], [3000, 15]] + 'percentiles:50.0': [ + [1000, NaN], + [2000, 25], + [3000, 15], + ], + 'percentiles:75.0': [ + [1000, 65], + [2000, 32], + [3000, 15], + ], + 'percentiles:95.0': [ + [1000, 73], + [2000, NaN], + [3000, 15], + ], + 'percentiles:99.0': [ + [1000, 75], + [2000, 67], + [3000, 15], + ], }); }); }); @@ -395,104 +446,166 @@ describe(filename, () => { it('should throw an error', () => { expect(aggResponse.default(esResponse.aggregations, config)).to.eql([ { - data: [[1000, 264], [2000, 264]], + data: [ + [1000, 264], + [2000, 264], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueA > FieldB:Value2A > MetricA', split: 'Value2A', type: 'series', - }, { - data: [[1000, 398], [2000, 1124]], + }, + { + data: [ + [1000, 398], + [2000, 1124], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueA > FieldB:Value2A > MetricB', split: 'Value2A', type: 'series', - }, { - data: [[1000, 699], [2000, 110]], + }, + { + data: [ + [1000, 699], + [2000, 110], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueA > FieldB:Value2B > MetricA', split: 'Value2B', type: 'series', - }, { - data: [[1000, 457], [2000, 506]], + }, + { + data: [ + [1000, 457], + [2000, 506], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueA > FieldB:Value2B > MetricB', split: 'Value2B', type: 'series', - }, { - data: [[1000, 152], [2000, 518]], + }, + { + data: [ + [1000, 152], + [2000, 518], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueB > FieldB:Value2B > MetricA', split: 'Value2B', type: 'series', - }, { - data: [[1000, 61], [2000, 77]], + }, + { + data: [ + [1000, 61], + [2000, 77], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueB > FieldB:Value2B > MetricB', split: 'Value2B', type: 'series', - }, { - data: [[1000, 114], [2000, 264]], + }, + { + data: [ + [1000, 114], + [2000, 264], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueB > FieldB:Value2A > MetricA', split: 'Value2A', type: 'series', - }, { - data: [[1000, 23], [2000, 45]], + }, + { + data: [ + [1000, 23], + [2000, 45], + ], fit: 'nearest', label: 'q:QueryA > FieldA:ValueB > FieldB:Value2A > MetricB', split: 'Value2A', type: 'series', - }, { - data: [[1000, 621], [2000, 751]], + }, + { + data: [ + [1000, 621], + [2000, 751], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueA > FieldB:Value2B > MetricA', split: 'Value2B', type: 'series', - }, { - data: [[1000, 12], [2000, 12]], + }, + { + data: [ + [1000, 12], + [2000, 12], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueA > FieldB:Value2B > MetricB', split: 'Value2B', type: 'series', - }, { - data: [[1000, 110], [2000, 648]], + }, + { + data: [ + [1000, 110], + [2000, 648], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueA > FieldB:Value2A > MetricA', split: 'Value2A', type: 'series', - }, { - data: [[1000, 11], [2000, 12]], + }, + { + data: [ + [1000, 11], + [2000, 12], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueA > FieldB:Value2A > MetricB', split: 'Value2A', type: 'series', - }, { - data: [[1000, 755], [2000, 713]], + }, + { + data: [ + [1000, 755], + [2000, 713], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueC > FieldB:Value2C > MetricA', split: 'Value2C', type: 'series', - }, { - data: [[1000, 10], [2000, 18]], + }, + { + data: [ + [1000, 10], + [2000, 18], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueC > FieldB:Value2C > MetricB', split: 'Value2C', type: 'series', - }, { - data: [[1000, 391], [2000, 802]], + }, + { + data: [ + [1000, 391], + [2000, 802], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueC > FieldB:Value2A > MetricA', split: 'Value2A', type: 'series', - }, { - data: [[1000, 4], [2000, 4]], + }, + { + data: [ + [1000, 4], + [2000, 4], + ], fit: 'nearest', label: 'q:QueryB > FieldA:ValueC > FieldB:Value2A > MetricB', split: 'Value2A', type: 'series', - } + }, ]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js index 73d729631c037..3e86554e843b0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js @@ -23,9 +23,9 @@ const expect = require('chai').expect; const seriesList = require('./fixtures/seriesList.js')(); import invoke from './helpers/invoke_series_fn.js'; -describe('first.js', function () { - it('should return exactly the data input', function () { - return invoke(fn, [seriesList]).then(function (result) { +describe('first.js', function() { + it('should return exactly the data input', function() { + return invoke(fn, [seriesList]).then(function(result) { expect(result.input[0]).to.eql(result.output); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js index adb3a3a7b11a8..db9360da3f592 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js @@ -24,10 +24,9 @@ import invoke from './helpers/invoke_series_fn.js'; import getSeriesList from './helpers/get_single_series_list'; import _ from 'lodash'; -describe('fit.js', function () { - - describe('should not filter out zeros', function () { - it('all zeros', function () { +describe('fit.js', function() { + describe('should not filter out zeros', function() { + it('all zeros', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 0], [moment.utc('1981-01-01T00:00:00.000Z'), null], @@ -35,14 +34,14 @@ describe('fit.js', function () { [moment.utc('1983-01-01T00:00:00.000Z'), 0], ]); - return invoke(fn, [seriesList, 'carry']).then(function (r) { + return invoke(fn, [seriesList, 'carry']).then(function(r) { expect(r.input[0].list[0].data[1][1]).to.equal(null); expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 0, 0]); expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); }); }); - it('mixed zeros and numbers', function () { + it('mixed zeros and numbers', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 26], [moment.utc('1981-01-01T00:00:00.000Z'), 42], @@ -51,13 +50,13 @@ describe('fit.js', function () { [moment.utc('1984-01-01T00:00:00.000Z'), 1], ]); - return invoke(fn, [seriesList, 'carry']).then(function (r) { + return invoke(fn, [seriesList, 'carry']).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([26, 42, 0, 0, 1]); }); }); }); - it('should return original series when all values are null', function () { + it('should return original series when all values are null', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), null], [moment.utc('1981-01-01T00:00:00.000Z'), null], @@ -65,13 +64,13 @@ describe('fit.js', function () { [moment.utc('1983-01-01T00:00:00.000Z'), null], ]); - return invoke(fn, [seriesList, 'carry']).then(function (r) { + return invoke(fn, [seriesList, 'carry']).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, null, null]); }); }); - describe('carry', function () { - it('should maintain the previous value until it changes', function () { + describe('carry', function() { + it('should maintain the previous value until it changes', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 5], [moment.utc('1981-01-01T00:00:00.000Z'), null], @@ -79,7 +78,7 @@ describe('fit.js', function () { [moment.utc('1983-01-01T00:00:00.000Z'), 171], ]); - return invoke(fn, [seriesList, 'carry']).then(function (r) { + return invoke(fn, [seriesList, 'carry']).then(function(r) { expect(r.input[0].list[0].data[1][1]).to.equal(null); expect(_.map(r.output.list[0].data, 1)).to.eql([5, 5, 3.4, 171]); expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); @@ -87,8 +86,8 @@ describe('fit.js', function () { }); }); - describe('nearest', function () { - it('should use the closest temporal value to fill the null', function () { + describe('nearest', function() { + it('should use the closest temporal value to fill the null', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 5], [moment.utc('1981-01-01T00:00:00.000Z'), null], @@ -96,7 +95,7 @@ describe('fit.js', function () { [moment.utc('1983-01-01T00:00:00.000Z'), 171], ]); - return invoke(fn, [seriesList, 'nearest']).then(function (r) { + return invoke(fn, [seriesList, 'nearest']).then(function(r) { expect(r.input[0].list[0].data[1][1]).to.equal(null); expect(_.map(r.output.list[0].data, 1)).to.eql([5, 3.4, 3.4, 171]); expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); @@ -104,10 +103,8 @@ describe('fit.js', function () { }); }); - - - describe('average', function () { - it('should produce a smooth, straight line between points', function () { + describe('average', function() { + it('should produce a smooth, straight line between points', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 10], [moment.utc('1981-07-01T00:00:00.000Z'), null], @@ -116,16 +113,15 @@ describe('fit.js', function () { [moment.utc('1984-01-01T00:00:00.000Z'), 50], ]); - return invoke(fn, [seriesList, 'average']).then(function (r) { + return invoke(fn, [seriesList, 'average']).then(function(r) { expect(r.input[0].list[0].data[1][1]).to.eql(null); expect(_.map(r.output.list[0].data, 1)).to.eql([10, 20, 30, 40, 50]); - }); }); }); - describe('scale', function () { - it('should distribute the next points value across the preceeding nulls', function () { + describe('scale', function() { + it('should distribute the next points value across the preceeding nulls', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 10], [moment.utc('1981-07-01T00:00:00.000Z'), null], @@ -134,14 +130,14 @@ describe('fit.js', function () { [moment.utc('1984-01-01T00:00:00.000Z'), 50], ]); - return invoke(fn, [seriesList, 'scale']).then(function (r) { + return invoke(fn, [seriesList, 'scale']).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([10, 20, 20, 20, 50]); }); }); }); - describe('none', function () { - it('basically just drops the nulls. This is going to screw you', function () { + describe('none', function() { + it('basically just drops the nulls. This is going to screw you', function() { const seriesList = getSeriesList('', [ [moment.utc('1980-01-01T00:00:00.000Z'), 10], [moment.utc('1981-07-01T00:00:00.000Z'), null], @@ -150,10 +146,9 @@ describe('fit.js', function () { [moment.utc('1984-01-01T00:00:00.000Z'), 50], ]); - return invoke(fn, [seriesList, 'none']).then(function (r) { + return invoke(fn, [seriesList, 'none']).then(function(r) { expect(_.map(r.output.list[0].data, 1)).to.eql([10, 40, 50]); }); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js index f51b301efb35f..4e49510d70fb8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js @@ -23,5 +23,5 @@ export default [ moment('1980-01-01T00:00:00.000Z'), moment('1981-01-01T00:00:00.000Z'), moment('1982-01-01T00:00:00.000Z'), - moment('1983-01-01T00:00:00.000Z') + moment('1983-01-01T00:00:00.000Z'), ]; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js index 3b2b7189f707a..65aed311e232b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js @@ -17,8 +17,6 @@ * under the License. */ -/* eslint-disable quotes */ - /* Really didn't want to do this, but testing the agg flatten logic in units isn't really possible since the functions depend on each other @@ -29,7 +27,7 @@ export default { _shards: { - total: 1 + total: 1, }, aggregations: { q: { @@ -52,15 +50,15 @@ export default { { key: 1000, MetricA: { value: 264 }, - MetricB: { value: 398 } + MetricB: { value: 398 }, }, { key: 2000, MetricA: { value: 264 }, - MetricB: { value: 1124 } - } - ] - } + MetricB: { value: 1124 }, + }, + ], + }, }, { key: 'Value2B', @@ -70,18 +68,18 @@ export default { { key: 1000, MetricA: { value: 699 }, - MetricB: { value: 457 } + MetricB: { value: 457 }, }, { key: 2000, MetricA: { value: 110 }, - MetricB: { value: 506 } - } - ] - } - } - ] - } + MetricB: { value: 506 }, + }, + ], + }, + }, + ], + }, }, { key: 'ValueB', @@ -96,15 +94,15 @@ export default { { key: 1000, MetricA: { value: 152 }, - MetricB: { value: 61 } + MetricB: { value: 61 }, }, { key: 2000, MetricA: { value: 518 }, - MetricB: { value: 77 } - } - ] - } + MetricB: { value: 77 }, + }, + ], + }, }, { key: 'Value2A', @@ -114,21 +112,21 @@ export default { { key: 1000, MetricA: { value: 114 }, - MetricB: { value: 23 } + MetricB: { value: 23 }, }, { key: 2000, MetricA: { value: 264 }, - MetricB: { value: 45 } - } - ] - } - } - ] - } - } - ] - } + MetricB: { value: 45 }, + }, + ], + }, + }, + ], + }, + }, + ], + }, }, QueryB: { FieldA: { @@ -147,15 +145,15 @@ export default { { key: 1000, MetricA: { value: 621 }, - MetricB: { value: 12 } + MetricB: { value: 12 }, }, { key: 2000, MetricA: { value: 751 }, - MetricB: { value: 12 } - } - ] - } + MetricB: { value: 12 }, + }, + ], + }, }, { key: 'Value2A', @@ -165,18 +163,18 @@ export default { { key: 1000, MetricA: { value: 110 }, - MetricB: { value: 11 } + MetricB: { value: 11 }, }, { key: 2000, MetricA: { value: 648 }, - MetricB: { value: 12 } - } - ] - } - } - ] - } + MetricB: { value: 12 }, + }, + ], + }, + }, + ], + }, }, { key: 'ValueC', @@ -191,15 +189,15 @@ export default { { key: 1000, MetricA: { value: 755 }, - MetricB: { value: 10 } + MetricB: { value: 10 }, }, { key: 2000, MetricA: { value: 713 }, - MetricB: { value: 18 } - } - ] - } + MetricB: { value: 18 }, + }, + ], + }, }, { key: 'Value2A', @@ -209,23 +207,23 @@ export default { { key: 1000, MetricA: { value: 391 }, - MetricB: { value: 4 } + MetricB: { value: 4 }, }, { key: 2000, MetricA: { value: 802 }, - MetricB: { value: 4 } - } - ] - } - } - ] - } - } - ] - } - } - } - } - } + MetricB: { value: 4 }, + }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + }, + }, + }, }; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js index e3334784b4fa5..29b759af521ed 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js @@ -21,12 +21,12 @@ import buckets from './bucketList'; import getSeries from '../helpers/get_series'; import getSeriesList from '../helpers/get_series_list'; -export default function () { +export default function() { return getSeriesList([ - getSeries('Negative', buckets, [-51, 17, 82, 20]), - getSeries('Nice', buckets, [100, 50, 50, 20]), + getSeries('Negative', buckets, [-51, 17, 82, 20]), + getSeries('Nice', buckets, [100, 50, 50, 20]), getSeries('All the same', buckets, [1, 1, 1, 1]), - getSeries('Decimals', buckets, [3.1415926535, 2, 1.439, 0.3424235]), - getSeries('PowerOfTen', buckets, [10, 100, 10, 1]), + getSeries('Decimals', buckets, [3.1415926535, 2, 1.439, 0.3424235]), + getSeries('PowerOfTen', buckets, [10, 100, 10, 1]), ]); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js index 87f76c82e0492..6eea99424c4ab 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js @@ -23,7 +23,7 @@ import sinon from 'sinon'; import timelionDefaults from '../../../lib/get_namespaced_settings'; import esResponse from './es_response'; -export default function () { +export default function() { const functions = require('../../../lib/load_functions')('series_functions'); const kibanaServerConfigs = { 'timelion.graphiteUrls': ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], @@ -31,27 +31,30 @@ export default function () { const server = { plugins: { timelion: { - getFunction: (name) => { - if (!functions[name]) throw new Error ('No such function: ' + name); + getFunction: name => { + if (!functions[name]) throw new Error('No such function: ' + name); return functions[name]; - } + }, }, elasticsearch: { - getCluster: sinon.stub().withArgs('data').returns({ - callWithRequest: function () { - return Promise.resolve(esResponse); - } - }) - } + getCluster: sinon + .stub() + .withArgs('data') + .returns({ + callWithRequest: function() { + return Promise.resolve(esResponse); + }, + }), + }, }, newPlatform: { __internals: { elasticsearch: { - legacy: { config$: of({ shardTimeout: moment.duration(30000) }) } - } - } + legacy: { config$: of({ shardTimeout: moment.duration(30000) }) }, + }, + }, }, - config: () => ({ get: (key) => kibanaServerConfigs[key] }) + config: () => ({ get: key => kibanaServerConfigs[key] }), }; const tlConfig = require('../../../handlers/lib/tl_config.js')({ @@ -63,7 +66,7 @@ export default function () { interval: '1y', from: moment('1980-01-01T00:00:00Z').valueOf(), to: moment('1983-01-01T00:00:00Z').valueOf(), - timezone: 'Etc/UTC' + timezone: 'Etc/UTC', }; tlConfig.settings = timelionDefaults(); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js index 8d5e25b43938d..b7ee96ef77575 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js @@ -21,19 +21,21 @@ import proxyquire from 'proxyquire'; import Bluebird from 'bluebird'; const expect = require('chai').expect; -const graphiteResponse = function () { +const graphiteResponse = function() { return Bluebird.resolve({ - json: function () { - return [{ - target: '__beer__', - datapoints: [ - [3, 1000], - [14, 2000], - [1.5, 3000], - [92.6535, 4000], - ] - }]; - } + json: function() { + return [ + { + target: '__beer__', + datapoints: [ + [3, 1000], + [14, 2000], + [1.5, 3000], + [92.6535, 4000], + ], + }, + ]; + }, }); }; @@ -42,22 +44,22 @@ const fn = proxyquire(`../${filename}`, { 'node-fetch': graphiteResponse }); import invoke from './helpers/invoke_series_fn.js'; -describe(filename, function () { - it('should wrap the graphite response up in a seriesList', function () { - return invoke(fn, []).then(function (result) { +describe(filename, function() { + it('should wrap the graphite response up in a seriesList', function() { + return invoke(fn, []).then(function(result) { expect(result.output.list[0].data[0][1]).to.eql(3); expect(result.output.list[0].data[1][1]).to.eql(14); }); }); - it('should convert the seconds to milliseconds', function () { - return invoke(fn, []).then(function (result) { + it('should convert the seconds to milliseconds', function() { + return invoke(fn, []).then(function(result) { expect(result.output.list[0].data[1][0]).to.eql(2000 * 1000); }); }); - it('should set the label to that of the graphite target', function () { - return invoke(fn, []).then(function (result) { + it('should set the label to that of the graphite target', function() { + return invoke(fn, []).then(function(result) { expect(result.output.list[0].label).to.eql('__beer__'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js index 195606b70c86b..0c5c61923d6e7 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js @@ -20,10 +20,15 @@ import _ from 'lodash'; export default function getSeries(name, buckets, points) { - const fill = _.partial(_.zip, _.map(buckets, function (bucket) { return bucket.valueOf(); })); + const fill = _.partial( + _.zip, + _.map(buckets, function(bucket) { + return bucket.valueOf(); + }) + ); return { data: fill(points), type: 'series', - label: name + label: name, }; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js index 5ae32167c6507..b8e64caeeade7 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js @@ -19,9 +19,12 @@ import _ from 'lodash'; -export default function (list, overrides) { - return _.merge({ - type: 'seriesList', - list: list - }, overrides); +export default function(list, overrides) { + return _.merge( + { + type: 'seriesList', + list: list, + }, + overrides + ); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js index 27e2f85821d88..cef5bed5d3218 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js @@ -21,6 +21,6 @@ import getSeries from '../helpers/get_series'; import getSeriesList from '../helpers/get_series_list'; import _ from 'lodash'; -export default function (name, data) { +export default function(name, data) { return getSeriesList([getSeries(name, _.map(data, 0), _.map(data, 1))]); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js index 642b7a7297ee2..51ef4c61a95e8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js @@ -25,16 +25,15 @@ import indexArguments from '../../../handlers/lib/index_arguments'; export default function invokeSeriesFn(fnDef, args, tlConfigOverrides) { const tlConfig = _.merge(require('../fixtures/tlConfig')(), tlConfigOverrides); - return Promise.all(args).then(function (args) { + return Promise.all(args).then(function(args) { args.byName = indexArguments(fnDef, args); const input = _.cloneDeep(args); - return Promise.resolve(fnDef.originalFn(args, tlConfig)).then(function (output) { - + return Promise.resolve(fnDef.originalFn(args, tlConfig)).then(function(output) { const result = { output: output, - input: input + input: input, }; return result; }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js index ed7df1fd5f215..5d4b624670847 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js @@ -24,22 +24,20 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('hide.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('hides a series', () => { - return invoke(fn, [seriesList, true]).then((r) => { - _.each(r.output.list, (series) => expect(series._hide).to.equal(true)); + return invoke(fn, [seriesList, true]).then(r => { + _.each(r.output.list, series => expect(series._hide).to.equal(true)); }); }); it('unhides a series', () => { - return invoke(fn, [seriesList, false]).then((r) => { - _.each(r.output.list, (series) => expect(series._hide).to.equal(false)); + return invoke(fn, [seriesList, false]).then(r => { + _.each(r.output.list, series => expect(series._hide).to.equal(false)); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js index c89aa1d5197c6..9e0a92b1e4004 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js @@ -24,22 +24,20 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('label.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('changes the label on the series', () => { - return invoke(fn, [seriesList, 'free beer']).then((r) => { - _.each(r.output.list, (series) => expect(series.label).to.equal('free beer')); + return invoke(fn, [seriesList, 'free beer']).then(r => { + _.each(r.output.list, series => expect(series.label).to.equal('free beer')); }); }); it('can use a regex to capture parts of a series label', () => { - return invoke(fn, [seriesList, 'beer$1', 'Neg(.*)']).then((r) => { + return invoke(fn, [seriesList, 'beer$1', 'Neg(.*)']).then(r => { expect(r.output.list[0].label).to.equal('beerative'); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js index f85b2cefffd73..205f0c4431fcc 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js @@ -23,7 +23,6 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('legend.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); @@ -31,29 +30,30 @@ describe('legend.js', () => { it('should create the _global object if it does not exist', () => { expect(seriesList.list[0]._global).to.equal(undefined); - return invoke(fn, [seriesList, 'nw', 3, true, 'YYYY']).then((r) => { - expect(r.output.list[0]._global).to.eql({ legend: { noColumns: 3, position: 'nw', showTime: true, timeFormat: 'YYYY' } }); + return invoke(fn, [seriesList, 'nw', 3, true, 'YYYY']).then(r => { + expect(r.output.list[0]._global).to.eql({ + legend: { noColumns: 3, position: 'nw', showTime: true, timeFormat: 'YYYY' }, + }); }); }); it('should provide default values for time axis display', () => { - return invoke(fn, [seriesList, 'nw', 3]).then((r) => { + return invoke(fn, [seriesList, 'nw', 3]).then(r => { expect(r.output.list[0]._global.legend.showTime).to.equal(true); expect(r.output.list[0]._global.legend.timeFormat).to.equal('MMMM Do YYYY, HH:mm:ss.SSS'); }); }); it('should hide the legend is position is false', () => { - return invoke(fn, [seriesList, false]).then((r) => { + return invoke(fn, [seriesList, false]).then(r => { expect(r.output.list[0]._global.legend.show).to.equal(false); expect(r.output.list[0]._global.legend.showTime).to.equal(false); }); }); it('should set legend.showTime to false when showTime parameter is false', () => { - return invoke(fn, [seriesList, 'nw', 3, false]).then((r) => { + return invoke(fn, [seriesList, 'nw', 3, false]).then(r => { expect(r.output.list[0]._global.legend.showTime).to.equal(false); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js index 83cd066b6c7e4..32974495b40eb 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js @@ -23,7 +23,6 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('lines.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); @@ -31,7 +30,7 @@ describe('lines.js', () => { it('should simply set show, steps, stack and lineWidth', () => { expect(seriesList.list[0]._global).to.equal(undefined); - return invoke(fn, [seriesList, 1, 2, true, true, false]).then((r) => { + return invoke(fn, [seriesList, 1, 2, true, true, false]).then(r => { expect(r.output.list[0].lines.lineWidth).to.equal(1); expect(r.output.list[0].lines.show).to.equal(true); expect(r.output.list[0].stack).to.equal(true); @@ -41,7 +40,7 @@ describe('lines.js', () => { it('should set lineWidth to 3 by default, and nothing else', () => { expect(seriesList.list[0]._global).to.equal(undefined); - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { expect(r.output.list[0].lines.lineWidth).to.equal(3); expect(r.output.list[0].lines.fill).to.equal(undefined); expect(r.output.list[0].lines.show).to.equal(undefined); @@ -49,5 +48,4 @@ describe('lines.js', () => { expect(r.output.list[0].lines.steps).to.equal(undefined); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js index 41ccb5671649c..8cd2e2caa2c47 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('log.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('should return the log10 value of every value', () => { - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { expect(_.map(r.output.list[4].data, 1)).to.eql([1, 2, 1, 0]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js index a5ed446cdba5e..9cc4afffb22ba 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('max.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('keeps the max of a series vs a number', () => { - return invoke(fn, [seriesList, 20]).then((r) => { + return invoke(fn, [seriesList, 20]).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([20, 20, 82, 20]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js index e3f29805787e5..a89183ee90c6b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('min.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('keeps the min of a series vs a number', () => { - return invoke(fn, [seriesList, 20]).then((r) => { + return invoke(fn, [seriesList, 20]).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 17, 20, 20]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js index a502e23b9cddf..dceef96b1d166 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js @@ -29,49 +29,51 @@ import invoke from './helpers/invoke_series_fn.js'; function getFivePointSeries() { return getSeriesList([ - getSeries('Five', [].concat(buckets).push(moment('1984-01-01T00:00:00.000Z')), [10, 20, 30, 40, 50]), + getSeries('Five', [].concat(buckets).push(moment('1984-01-01T00:00:00.000Z')), [ + 10, + 20, + 30, + 40, + 50, + ]), ]); } describe('movingaverage.js', () => { - let seriesList; beforeEach(() => { seriesList = getFivePointSeries(); }); it('centers the averaged series by default', () => { - return invoke(fn, [seriesList, 3]).then((r) => { + return invoke(fn, [seriesList, 3]).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([null, 20, 30, 40, null]); }); }); - it('aligns the moving average to the left', () => { - return invoke(fn, [seriesList, 3, 'left']).then((r) => { + return invoke(fn, [seriesList, 3, 'left']).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, 20, 30, 40]); }); }); it('aligns the moving average to the right', () => { - return invoke(fn, [seriesList, 3, 'right']).then((r) => { + return invoke(fn, [seriesList, 3, 'right']).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([20, 30, 40, null, null]); }); }); describe('date math', () => { it('accepts 2 years', () => { - return invoke(fn, [seriesList, '2y', 'left']).then((r) => { + return invoke(fn, [seriesList, '2y', 'left']).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([null, 15, 25, 35, 45]); }); }); it('accepts 3 years', () => { - return invoke(fn, [seriesList, '3y', 'left']).then((r) => { + return invoke(fn, [seriesList, '3y', 'left']).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, 20, 30, 40]); }); }); }); - - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js index e7470ecd0888f..d2ef271293afc 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js @@ -26,12 +26,35 @@ import getSeries from './helpers/get_series'; import getSeriesList from './helpers/get_series_list'; describe('movingstd.js', () => { - it('computes the moving standard deviation of a list', async () => { const points = [ - 108.48, 111.56, 112.13, 113.75, 114.25, 110.79, 111.21, 116.82, 117.16, 120.38, 116.96, - 119.56, 118.97, 117.54, 114.42, 111.01, 114.20, 116.43, 117.74, 119.90, 124.65, 124.98, - 124.70, 123.60, 124.5, 126.85]; + 108.48, + 111.56, + 112.13, + 113.75, + 114.25, + 110.79, + 111.21, + 116.82, + 117.16, + 120.38, + 116.96, + 119.56, + 118.97, + 117.54, + 114.42, + 111.01, + 114.2, + 116.43, + 117.74, + 119.9, + 124.65, + 124.98, + 124.7, + 123.6, + 124.5, + 126.85, + ]; const buckets = []; buckets[0] = moment('2018-01-01T00:00:00.000Z'); for (let i = 1; i < points.length; i++) { @@ -43,7 +66,7 @@ describe('movingstd.js', () => { const position = 'left'; const results = await invoke(fn, [seriesList, numWindows, position]); - const resultPoints = results.output.list[0].data.map((row) => { + const resultPoints = results.output.list[0].data.map(row => { // row is an array; index 0 is the time bucket, index 1 is the value return row[1]; }); @@ -51,12 +74,31 @@ describe('movingstd.js', () => { const trimmedResultPoints = resultPoints.slice(numWindows); const expectedPoints = [ - 2.28, 1.46, 1.53, 2.46, 3.00, 4.14, 3.31, 1.67, 1.50, 1.41, - 2.01, 3.56, 3.12, 2.50, 2.56, 3.41, 3.97, 3.92, 3.35, 2.12, 0.52]; + 2.28, + 1.46, + 1.53, + 2.46, + 3.0, + 4.14, + 3.31, + 1.67, + 1.5, + 1.41, + 2.01, + 3.56, + 3.12, + 2.5, + 2.56, + 3.41, + 3.97, + 3.92, + 3.35, + 2.12, + 0.52, + ]; expectedPoints.forEach((value, index) => { expect(trimmedResultPoints[index]).to.be.within(value - 0.01, value + 0.01); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js index 025ed81da1cf1..0cc5665fb919a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('multiply.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('multiplies by a number', () => { - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([200, 100, 100, 40]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js index 1ab86348620dd..53831f0f6138d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js @@ -25,47 +25,46 @@ const expect = assert.expect; import invoke from './helpers/invoke_series_fn.js'; describe('points.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('should set the point radius', () => { - return invoke(fn, [seriesList, 1]).then((r) => { + return invoke(fn, [seriesList, 1]).then(r => { expect(r.output.list[0].points.radius).to.equal(1); }); }); it('should set the point lineWidth', () => { - return invoke(fn, [seriesList, null, 3]).then((r) => { + return invoke(fn, [seriesList, null, 3]).then(r => { expect(r.output.list[0].points.lineWidth).to.equal(3); }); }); it('should set the point fill', () => { - return invoke(fn, [seriesList, null, null, 3]).then((r) => { + return invoke(fn, [seriesList, null, null, 3]).then(r => { expect(r.output.list[0].points.fill).to.equal(3 / 10); }); }); it('should not set the fill color if fill is not specified', () => { - return invoke(fn, [seriesList, null, null, null, '#333']).then((r) => { + return invoke(fn, [seriesList, null, null, null, '#333']).then(r => { expect(r.output.list[0].points.fillColor).to.equal(undefined); }); }); it('should set the fill color ', () => { - return invoke(fn, [seriesList, null, null, 10, '#333']).then((r) => { + return invoke(fn, [seriesList, null, null, 10, '#333']).then(r => { expect(r.output.list[0].points.fillColor).to.equal('#333'); }); }); describe('symbol', () => { const symbols = ['triangle', 'cross', 'square', 'diamond', 'circle']; - _.each(symbols, (symbol) => { + _.each(symbols, symbol => { it(`is ${symbol}`, () => { - return invoke(fn, [seriesList, null, null, null, null, symbol]).then((r) => { + return invoke(fn, [seriesList, null, null, null, null, symbol]).then(r => { expect(r.output.list[0].points.symbol).to.equal(symbol); }); }); @@ -74,10 +73,9 @@ describe('points.js', () => { it('does not allow undefined symbols', () => { return invoke(fn, [seriesList, null, null, null, null, 'beer']) .then(expect.fail) - .catch((e) => { + .catch(e => { expect(e).to.be.an('error'); }); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js index 68de2eb04a09d..29e3bc1ab66f8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js @@ -24,21 +24,19 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('precision.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('keeps the min of a series vs a number', () => { - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(_.map(r.output.list[3].data, 1)).to.eql([3.14, 2, 1.43, 0.34]); }); }); - it('Adds a _meta to describe the precision to display', () => { - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(r.output.list[3]._meta.precision).to.eql(2); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js index 4fcc8fadcd013..009c0e4e025cd 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js @@ -33,16 +33,15 @@ import invoke from './helpers/invoke_series_fn.js'; let fn; let response; let calledWith; -describe(filename, function () { - - beforeEach(function () { - response = function (url) { +describe(filename, function() { + beforeEach(function() { + response = function(url) { calledWith = { params: parseQueryString(parseURL(url).query), - code: url.match(/datasets\/(.*).json/)[1] + code: url.match(/datasets\/(.*).json/)[1], }; return Bluebird.resolve({ - json: function () { + json: function() { return { name: '__beer__', data: [ @@ -50,50 +49,52 @@ describe(filename, function () { ['2015-01-02', 14], ['2015-01-03', 15.92], ['2015-01-04', 65.35], - ] + ], }; - } + }, }); }; fn = proxyquire(`../${filename}`, { 'node-fetch': response }); }); - it('should wrap the quandl response up in a seriesList', function () { - return invoke(fn, []).then(function (result) { + it('should wrap the quandl response up in a seriesList', function() { + return invoke(fn, []).then(function(result) { expect(result.output.list[0].data[0][1]).to.eql(3); expect(result.output.list[0].data[1][1]).to.eql(14); }); }); - it('should set the label to that of the quandl name', function () { - return invoke(fn, []).then(function (result) { + it('should set the label to that of the quandl name', function() { + return invoke(fn, []).then(function(result) { expect(result.output.list[0].label).to.eql('__beer__'); }); }); - it('should call the quandl API with the quandl code that has been passed', function () { - return invoke(fn, ['BEER/IS_GOOD']).then(function () { + it('should call the quandl API with the quandl code that has been passed', function() { + return invoke(fn, ['BEER/IS_GOOD']).then(function() { expect(calledWith.code).to.eql('BEER/IS_GOOD'); }); }); - it('should limit the time span and interval to the stuff attached to tlConfig', function () { - return invoke(fn, []).then(function () { - expect(calledWith.params.trim_start).to.eql(moment.utc(tlConfig.time.from).format('YYYY-MM-DD')); + it('should limit the time span and interval to the stuff attached to tlConfig', function() { + return invoke(fn, []).then(function() { + expect(calledWith.params.trim_start).to.eql( + moment.utc(tlConfig.time.from).format('YYYY-MM-DD') + ); expect(calledWith.params.trim_end).to.eql(moment.utc(tlConfig.time.to).format('YYYY-MM-DD')); }); }); - it('should throw an error is passed an unsupported interval', function () { + it('should throw an error is passed an unsupported interval', function() { return invoke(fn, [], { time: { interval: '2d' } }) .then(expect.fail) - .catch(function (r) { + .catch(function(r) { expect(r).to.be.an('error'); }); }); - it('should use the configured API key when talking to quandl', function () { - return invoke(fn, [], { settings: { 'timelion:quandl.key': 'bEeR' } }).then(function () { + it('should use the configured API key when talking to quandl', function() { + return invoke(fn, [], { settings: { 'timelion:quandl.key': 'bEeR' } }).then(function() { expect(calledWith.params.auth_token).to.eql('bEeR'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js index e98d8a527e693..38bee7d45565e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js @@ -24,15 +24,19 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('range.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); - seriesList.list[0].data = [[1000, 20], [2000, 10], [3000, 30], [4000, 40]]; + seriesList.list[0].data = [ + [1000, 20], + [2000, 10], + [3000, 30], + [4000, 40], + ]; }); it('keeps the min of a series vs a number', () => { - return invoke(fn, [seriesList, 1, 4]).then((r) => { + return invoke(fn, [seriesList, 1, 4]).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([2, 1, 3, 4]); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js index b9c92c164170a..12ad5503e69bf 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js @@ -24,14 +24,13 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('scale_interval.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('Can multiply to transform one interval to another', () => { - return invoke(fn, [seriesList, '5y']).then((r) => { + return invoke(fn, [seriesList, '5y']).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([500, 250, 250, 100]); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js index 4e5500d55a4a9..cea9525694ab3 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js @@ -25,19 +25,19 @@ import invoke from './helpers/invoke_series_fn.js'; describe('static.js', () => { it('returns a series in which all numbers are the same', () => { - return invoke(fn, [5]).then((r) => { + return invoke(fn, [5]).then(r => { expect(_.unique(_.map(r.output.list[0].data, 1))).to.eql([5]); }); }); it('plots a provided series', () => { - return invoke(fn, ['4:3:2:1']).then((r) => { + return invoke(fn, ['4:3:2:1']).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([4, 3, 2, 1]); }); }); it('leaves interpolation up to the data source wrapper', () => { - return invoke(fn, ['1:4']).then((r) => { + return invoke(fn, ['1:4']).then(r => { expect(_.map(r.output.list[0].data, 1)).to.eql([1, 4]); }); }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js index f34c46e7834f8..55d661ea95485 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js @@ -24,7 +24,6 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('subtract.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); @@ -43,7 +42,12 @@ describe('subtract.js', () => { it('it subtracts all series in seriesList to single series when only one argument is supplied', async () => { const outputSeries = await invoke(fn, [seriesList]); expect(outputSeries.output.list.length).to.eql(1); - expect(_.map(outputSeries.output.list[0].data, 1)).to.eql([-165.1415926535, -136, 19.561, -2.3424234999999998]); + expect(_.map(outputSeries.output.list[0].data, 1)).to.eql([ + -165.1415926535, + -136, + 19.561, + -2.3424234999999998, + ]); }); it('it subtracts a number', async () => { @@ -59,9 +63,7 @@ describe('subtract.js', () => { it('it subtracts a seriesList with one series', async () => { const seriesListWithOneSeries = { type: 'seriesList', - list: [ - _.cloneDeep(seriesList.list[1]) - ] + list: [_.cloneDeep(seriesList.list[1])], }; const outputSeries = await invoke(fn, [seriesList, seriesListWithOneSeries]); expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([0, 0, 0, 0]); @@ -75,5 +77,4 @@ describe('subtract.js', () => { expect(_.map(outputSeries.output.list[3].data, 1)).to.eql([0, 0, 0, 0]); expect(_.map(outputSeries.output.list[4].data, 1)).to.eql([0, 0, 0, 0]); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js index 488301132ccf5..61e3a254d0b5d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('sum.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('it adds a number', () => { - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([102, 52, 52, 22]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js index 5fac9f8a2783c..973bb2ed0ea32 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js @@ -24,16 +24,14 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('title.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('sets the title property', () => { - return invoke(fn, [seriesList, 'beer']).then((r) => { - _.each(r.output.list, (series) => expect(series._title).to.equal('beer')); + return invoke(fn, [seriesList, 'beer']).then(r => { + _.each(r.output.list, series => expect(series._title).to.equal('beer')); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js index 183e22fc49a0b..ed7a8999d706a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js @@ -24,40 +24,38 @@ const expect = require('chai').expect; import invoke from './helpers/invoke_series_fn.js'; describe('trim.js', () => { - let seriesList; beforeEach(() => { seriesList = require('./fixtures/seriesList.js')(); }); it('Sets the first and last values to null by default', () => { - return invoke(fn, [seriesList]).then((r) => { + return invoke(fn, [seriesList]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, 50, null]); }); }); it('Trims more from the beginning', () => { - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([null, null, 50, null]); }); }); it('Trims more from the end', () => { - return invoke(fn, [seriesList, null, 2]).then((r) => { + return invoke(fn, [seriesList, null, 2]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, null, null]); }); }); it('Trims nothing from the end', () => { - return invoke(fn, [seriesList, 1, 0]).then((r) => { + return invoke(fn, [seriesList, 1, 0]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, 50, 20]); }); }); it('Trims nothing from the beginning', () => { - return invoke(fn, [seriesList, 0, 2]).then((r) => { + return invoke(fn, [seriesList, 0, 2]).then(r => { expect(_.map(r.output.list[1].data, 1)).to.eql([100, 50, null, null]); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js index 2aa4b9a471c48..9210a2cd300b0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js @@ -30,20 +30,20 @@ describe('yaxis.js', () => { it('creates the yaxes array', () => { expect(seriesList._global).to.equal(undefined); - return invoke(fn, [seriesList, 2]).then((r) => { + return invoke(fn, [seriesList, 2]).then(r => { expect(r.output.list[0]._global.yaxes).to.be.an('array'); }); }); it('puts odd numbers of the left, even on the right, by default', () => { return Bluebird.all([ - invoke(fn, [seriesList, 1]).then((r) => { + invoke(fn, [seriesList, 1]).then(r => { expect(r.output.list[0]._global.yaxes[0].position).to.equal('left'); }), - invoke(fn, [seriesList, 2]).then((r) => { + invoke(fn, [seriesList, 2]).then(r => { expect(r.output.list[0]._global.yaxes[1].position).to.equal('right'); }), - invoke(fn, [seriesList, 3]).then((r) => { + invoke(fn, [seriesList, 3]).then(r => { expect(r.output.list[0]._global.yaxes[2].position).to.equal('left'); }), ]); @@ -51,10 +51,10 @@ describe('yaxis.js', () => { it('it lets you override default positions', () => { return Bluebird.all([ - invoke(fn, [seriesList, 1, null, null, 'right']).then((r) => { + invoke(fn, [seriesList, 1, null, null, 'right']).then(r => { expect(r.output.list[0]._global.yaxes[0].position).to.equal('right'); }), - invoke(fn, [seriesList, 2, null, null, 'right']).then((r) => { + invoke(fn, [seriesList, 2, null, null, 'right']).then(r => { expect(r.output.list[0]._global.yaxes[1].position).to.equal('right'); }), ]); @@ -62,10 +62,10 @@ describe('yaxis.js', () => { it('sets the minimum (default: no min)', () => { return Bluebird.all([ - invoke(fn, [seriesList, 1, null]).then((r) => { + invoke(fn, [seriesList, 1, null]).then(r => { expect(r.output.list[0]._global.yaxes[0].min).to.equal(null); }), - invoke(fn, [seriesList, 2, 10]).then((r) => { + invoke(fn, [seriesList, 2, 10]).then(r => { expect(r.output.list[0]._global.yaxes[1].min).to.equal(10); }), ]); @@ -73,10 +73,10 @@ describe('yaxis.js', () => { it('sets the max (default: no max)', () => { return Bluebird.all([ - invoke(fn, [seriesList, 1, null]).then((r) => { + invoke(fn, [seriesList, 1, null]).then(r => { expect(r.output.list[0]._global.yaxes[0].max).to.equal(undefined); }), - invoke(fn, [seriesList, 2, null, 10]).then((r) => { + invoke(fn, [seriesList, 2, null, 10]).then(r => { expect(r.output.list[0]._global.yaxes[1].max).to.equal(10); }), ]); @@ -84,10 +84,10 @@ describe('yaxis.js', () => { it('sets the units (default: no unit', () => { return Bluebird.all([ - invoke(fn, [seriesList, 1, null, null, null, null, null, null]).then((r) => { + invoke(fn, [seriesList, 1, null, null, null, null, null, null]).then(r => { expect(r.output.list[0]._global.yaxes[0].units).to.equal(undefined); }), - invoke(fn, [seriesList, 2, null, null, null, null, null, 'bits']).then((r) => { + invoke(fn, [seriesList, 2, null, null, null, null, null, 'bits']).then(r => { expect(r.output.list[0]._global.yaxes[1].units).to.be.an('object'); }), ]); @@ -107,5 +107,4 @@ describe('yaxis.js', () => { expect(e).to.be.an.instanceof(Error); }); }); - }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js index a7c5a8eb88a53..c072522ebd906 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js @@ -26,19 +26,19 @@ export default new Chainable('abs', { args: [ { name: 'inputSeries', - types: ['seriesList'] - } + types: ['seriesList'], + }, ], help: i18n.translate('timelion.help.functions.absHelpText', { defaultMessage: 'Return the absolute value of each value in the series list', }), fn: function absFn(args) { - return alter(args, function (eachSeries) { - const data = _.map(eachSeries.data, function (point) { + return alter(args, function(eachSeries) { + const data = _.map(eachSeries.data, function(point) { return [point[0], Math.abs(point[1])]; }); eachSeries.data = data; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js index 853ce337efac2..26e768a76f84e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.sum(points) / points.length; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js index 807e620ee941b..938039a465c31 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.uniq(points).length; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js index 05e560b84335f..8eb9565d2711c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.first(points); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js index 275a5d9bd7f9d..81ed1dff15967 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js @@ -29,14 +29,14 @@ const functions = { max: require('./max'), last: require('./last'), first: require('./first'), - sum: require('./sum') + sum: require('./sum'), }; export default new Chainable('aggregate', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'function', @@ -47,7 +47,7 @@ export default new Chainable('aggregate', { functions: _.keys(functions).join(', '), }, }), - } + }, ], help: i18n.translate('timelion.help.functions.aggregateHelpText', { defaultMessage: @@ -58,14 +58,15 @@ export default new Chainable('aggregate', { }), fn: function aggregateFn(args) { const fn = functions[args.byName.function]; - if (!fn) throw new Error('.aggregate() function must be one of: ' + _.keys(functions).join(', ')); + if (!fn) + throw new Error('.aggregate() function must be one of: ' + _.keys(functions).join(', ')); - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { const times = _.map(eachSeries.data, 0); const values = _.map(eachSeries.data, 1); eachSeries.data = _.zip(times, _.fill(values, fn(values))); return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js index 8f252fb451027..751f6e87fff99 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.last(points); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js index 5b2610f07bbea..7cac21932001f 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.max(points); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js index 1ebf9be2f9146..6ff6812dde527 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.min(points); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js index 34b5fe277dd34..481f6f529fa66 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js @@ -19,6 +19,6 @@ import _ from 'lodash'; -export default function (points) { +export default function(points) { return _.sum(points); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js index 161afdf1050c4..4fe08caed0986 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js @@ -25,7 +25,7 @@ export default new Chainable('bars', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'width', @@ -40,18 +40,18 @@ export default new Chainable('bars', { help: i18n.translate('timelion.help.functions.bars.args.stackHelpText', { defaultMessage: 'Should bars be stacked, true by default', }), - } + }, ], help: i18n.translate('timelion.help.functions.barsHelpText', { defaultMessage: 'Show the seriesList as bars', }), fn: function barsFn(args) { - return alter(args, function (eachSeries, width, stack) { + return alter(args, function(eachSeries, width, stack) { eachSeries.bars = eachSeries.bars || {}; eachSeries.bars.show = width == null ? 1 : width; eachSeries.bars.lineWidth = width == null ? 6 : width; eachSeries.stack = stack == null ? true : stack; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js index fa13a4463db36..85a622ceb7d07 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js @@ -26,7 +26,7 @@ export default new Chainable('color', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'color', @@ -36,7 +36,7 @@ export default new Chainable('color', { 'Color of series, as hex, e.g., #c6c6c6 is a lovely light grey. If you specify multiple \ colors, and have multiple series, you will get a gradient, e.g., "#00B1CC:#00FF94:#FF3A39:#CC1A6F"', }), - } + }, ], help: i18n.translate('timelion.help.functions.colorHelpText', { defaultMessage: 'Change the color of the series', @@ -55,7 +55,7 @@ colors, and have multiple series, you will get a gradient, e.g., "#00B1CC:#00FF9 } let i = 0; - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { if (gradient) { eachSeries.color = gradient[i++].toHexString(); } else if (colors.length === 1 || gradientStops === 1) { @@ -70,5 +70,5 @@ colors, and have multiple series, you will get a gradient, e.g., "#00B1CC:#00FF9 return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js index 5a52f8802f138..625b25762d04f 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js @@ -27,7 +27,7 @@ export default new Chainable('condition', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'operator', // <, <=, >, >=, ==, != @@ -40,39 +40,57 @@ export default new Chainable('condition', { suggestions: [ { name: 'eq', - help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.eqHelpText', { - defaultMessage: 'equal', - }), + help: i18n.translate( + 'timelion.help.functions.condition.args.operator.suggestions.eqHelpText', + { + defaultMessage: 'equal', + } + ), }, { name: 'ne', - help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.neHelpText', { - defaultMessage: 'not equal', - }), + help: i18n.translate( + 'timelion.help.functions.condition.args.operator.suggestions.neHelpText', + { + defaultMessage: 'not equal', + } + ), }, { name: 'lt', - help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.ltHelpText', { - defaultMessage: 'less than', - }), + help: i18n.translate( + 'timelion.help.functions.condition.args.operator.suggestions.ltHelpText', + { + defaultMessage: 'less than', + } + ), }, { name: 'lte', - help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.lteHelpText', { - defaultMessage: 'less than equal', - }), + help: i18n.translate( + 'timelion.help.functions.condition.args.operator.suggestions.lteHelpText', + { + defaultMessage: 'less than equal', + } + ), }, { name: 'gt', - help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.gtHelpText', { - defaultMessage: 'greater than', - }), + help: i18n.translate( + 'timelion.help.functions.condition.args.operator.suggestions.gtHelpText', + { + defaultMessage: 'greater than', + } + ), }, { name: 'gte', - help: i18n.translate('timelion.help.functions.condition.args.operator.suggestions.gteHelpText', { - defaultMessage: 'greater than equal', - }), + help: i18n.translate( + 'timelion.help.functions.condition.args.operator.suggestions.gteHelpText', + { + defaultMessage: 'greater than equal', + } + ), }, ], }, @@ -99,7 +117,7 @@ export default new Chainable('condition', { defaultMessage: 'The value the point will be set to if the comparison is false. If you pass a seriesList here the first series will be used', }), - } + }, ], help: i18n.translate('timelion.help.functions.conditionHelpText', { defaultMessage: @@ -109,8 +127,8 @@ export default new Chainable('condition', { aliases: ['if'], fn: function conditionFn(args) { const config = args.byName; - return alter(args, function (eachSeries) { - const data = _.map(eachSeries.data, function (point, i) { + return alter(args, function(eachSeries) { + const data = _.map(eachSeries.data, function(point, i) { function getNumber(source) { if (argType(source) === 'number') return source; if (argType(source) === 'null') return null; @@ -126,33 +144,36 @@ export default new Chainable('condition', { const thenVal = getNumber(config.then); const elseVal = _.isUndefined(config.else) ? point[1] : getNumber(config.else); - const newValue = (function () { + const newValue = (function() { switch (config.operator) { case 'lt': - return point[1] < ifVal ? thenVal : elseVal; + return point[1] < ifVal ? thenVal : elseVal; case 'lte': - return point[1] <= ifVal ? thenVal : elseVal; + return point[1] <= ifVal ? thenVal : elseVal; case 'gt': - return point[1] > ifVal ? thenVal : elseVal; + return point[1] > ifVal ? thenVal : elseVal; case 'gte': - return point[1] >= ifVal ? thenVal : elseVal; + return point[1] >= ifVal ? thenVal : elseVal; case 'eq': return point[1] === ifVal ? thenVal : elseVal; case 'ne': return point[1] !== ifVal ? thenVal : elseVal; default: throw new Error( - i18n.translate('timelion.serverSideErrors.conditionFunction.unknownOperatorErrorMessage', { - defaultMessage: 'Unknown operator', - }) + i18n.translate( + 'timelion.serverSideErrors.conditionFunction.unknownOperatorErrorMessage', + { + defaultMessage: 'Unknown operator', + } + ) ); } - }()); + })(); return [point[0], newValue]; }); eachSeries.data = data; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js index 39ce367a2592c..cf4dd21b98261 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js @@ -26,29 +26,30 @@ export default new Chainable('cusum', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'base', types: ['number'], help: i18n.translate('timelion.help.functions.cusum.args.baseHelpText', { - defaultMessage: 'Number to start at. Basically just adds this to the beginning of the series', + defaultMessage: + 'Number to start at. Basically just adds this to the beginning of the series', }), - } + }, ], help: i18n.translate('timelion.help.functions.cusumHelpText', { defaultMessage: 'Return the cumulative sum of a series, starting at a base.', }), fn: function cusumFn(args) { - return alter(args, function (eachSeries, base) { + return alter(args, function(eachSeries, base) { const pairs = eachSeries.data; let total = base || 0; - eachSeries.data = _.map(pairs, function (point) { + eachSeries.data = _.map(pairs, function(point) { total += point[1]; return [point[0], total]; }); return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js index e19b9d801fe42..bde4fe9f29abc 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js @@ -26,21 +26,23 @@ export default new Chainable('derivative', { args: [ { name: 'inputSeries', - types: ['seriesList'] - } + types: ['seriesList'], + }, ], help: i18n.translate('timelion.help.functions.derivativeHelpText', { defaultMessage: 'Plot the change in values over time.', }), fn: function derivativeFn(args) { - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { const pairs = eachSeries.data; - eachSeries.data = _.map(pairs, function (point, i) { - if (i === 0 || pairs[i - 1][1] == null || point[1] == null) { return [point[0], null]; } + eachSeries.data = _.map(pairs, function(point, i) { + if (i === 0 || pairs[i - 1][1] == null || point[1] == null) { + return [point[0], null]; + } return [point[0], point[1] - pairs[i - 1][1]]; }); return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js index 1448d31005632..5d7eab23197ef 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js @@ -25,7 +25,7 @@ export default new Chainable('divide', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'divisor', @@ -34,15 +34,15 @@ export default new Chainable('divide', { defaultMessage: 'Number or series to divide by. SeriesList with multiple series will be applied label-wise.', }), - } + }, ], help: i18n.translate('timelion.help.functions.divideHelpText', { defaultMessage: 'Divides the values of one or more series in a seriesList to each position, in each series, of the input seriesList', }), fn: function divideFn(args) { - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return a / b; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js index 3ca3a745aee1d..4ce2752fbf9be 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js @@ -66,7 +66,8 @@ export default new Datasource('es', { defaultMessage: 'Index to query, wildcards accepted. Provide Index Pattern name for scripted fields and ' + 'field name type ahead suggestions for metrics, split, and timefield arguments.', - description: '"metrics", "split" and "timefield" are referring to parameter names and should not be translated.', + description: + '"metrics", "split" and "timefield" are referring to parameter names and should not be translated.', }), }, { @@ -89,17 +90,15 @@ export default new Datasource('es', { name: 'interval', // You really shouldn't use this, use the interval picker instead types: ['string', 'null'], help: i18n.translate('timelion.help.functions.es.args.intervalHelpText', { - defaultMessage: - `**DO NOT USE THIS**. It's fun for debugging fit functions, but you really should use the interval picker`, + defaultMessage: `**DO NOT USE THIS**. It's fun for debugging fit functions, but you really should use the interval picker`, }), - } + }, ], help: i18n.translate('timelion.help.functions.esHelpText', { defaultMessage: 'Pull data from an elasticsearch instance', }), aliases: ['elasticsearch'], fn: async function esFn(args, tlConfig) { - const config = _.defaults(_.clone(args.byName), { q: '*', metric: ['count'], @@ -107,14 +106,14 @@ export default new Datasource('es', { timefield: tlConfig.settings['timelion:es.timefield'], interval: tlConfig.time.interval, kibana: true, - fit: 'nearest' + fit: 'nearest', }); const findResp = await tlConfig.request.getSavedObjectsClient().find({ type: 'index-pattern', fields: ['title', 'fields'], search: `"${config.index}"`, - search_fields: ['title'] + search_fields: ['title'], }); const indexPatternSavedObject = findResp.saved_objects.find(savedObject => { return savedObject.attributes.title === config.index; @@ -127,10 +126,12 @@ export default new Datasource('es', { }); } - const esShardTimeout = await tlConfig.server.newPlatform.__internals.elasticsearch.legacy.config$.pipe( - first(), - map(config => config.shardTimeout.asMilliseconds()) - ).toPromise(); + const esShardTimeout = await tlConfig.server.newPlatform.__internals.elasticsearch.legacy.config$ + .pipe( + first(), + map(config => config.shardTimeout.asMilliseconds()) + ) + .toPromise(); const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); @@ -143,12 +144,12 @@ export default new Datasource('es', { values: { index: config.index, }, - }), + }) ); } return { type: 'seriesList', - list: toSeriesList(resp.aggregations, config) + list: toSeriesList(resp.aggregations, config), }; - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js index 331337a1ed491..c9ffe07793803 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js @@ -17,9 +17,7 @@ * under the License. */ - export function buildAggBody(fieldName, scriptedFields) { - const scriptedField = scriptedFields.find(field => { return field.name === fieldName; }); @@ -28,12 +26,12 @@ export function buildAggBody(fieldName, scriptedFields) { return { script: { source: scriptedField.script, - lang: scriptedField.lang - } + lang: scriptedField.lang, + }, }; } return { - field: fieldName + field: fieldName, }; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js index 77c351d924012..a5876944db250 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js @@ -22,11 +22,11 @@ import _ from 'lodash'; export function timeBucketsToPairs(buckets) { const timestamps = _.pluck(buckets, 'key'); const series = {}; - _.each(buckets, function (bucket) { - _.forOwn(bucket, function (val, key) { + _.each(buckets, function(bucket) { + _.forOwn(bucket, function(val, key) { if (_.isPlainObject(val)) { if (val.values) { - _.forOwn(val.values, function (bucketValue, bucketKey) { + _.forOwn(val.values, function(bucketValue, bucketKey) { const k = key + ':' + bucketKey; const v = isNaN(bucketValue) ? NaN : bucketValue; series[k] = series[k] || []; @@ -40,7 +40,7 @@ export function timeBucketsToPairs(buckets) { }); }); - return _.mapValues(series, function (values) { + return _.mapValues(series, function(values) { return _.zip(timestamps, values); }); } @@ -48,19 +48,19 @@ export function timeBucketsToPairs(buckets) { export function flattenBucket(bucket, splitKey, path, result) { result = result || {}; path = path || []; - _.forOwn(bucket, function (val, key) { + _.forOwn(bucket, function(val, key) { if (!_.isPlainObject(val)) return; if (_.get(val, 'meta.type') === 'split') { - _.each(val.buckets, function (bucket, bucketKey) { + _.each(val.buckets, function(bucket, bucketKey) { if (bucket.key == null) bucket.key = bucketKey; // For handling "keyed" response formats, e.g., filters agg flattenBucket(bucket, bucket.key, path.concat([key + ':' + bucket.key]), result); }); } else if (_.get(val, 'meta.type') === 'time_buckets') { const metrics = timeBucketsToPairs(val.buckets); - _.each(metrics, function (pairs, metricName) { + _.each(metrics, function(pairs, metricName) { result[path.concat([metricName]).join(' > ')] = { data: pairs, - splitKey: splitKey + splitKey: splitKey, }; }); } @@ -69,13 +69,13 @@ export function flattenBucket(bucket, splitKey, path, result) { } export default function toSeriesList(aggs, config) { - return _.map(flattenBucket(aggs), function (metrics, name) { + return _.map(flattenBucket(aggs), function(metrics, name) { return { data: metrics.data, type: 'series', fit: config.fit, label: name, - split: metrics.splitKey + split: metrics.splitKey, }; }); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js index 26d868d08d2e5..2149e44125be0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js @@ -23,7 +23,6 @@ import { buildAggBody } from './agg_body'; import createDateAgg from './create_date_agg'; export default function buildRequest(config, tlConfig, scriptedFields, timeout) { - const bool = { must: [] }; const timeFilter = { @@ -31,9 +30,9 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) [config.timefield]: { gte: moment(tlConfig.time.from).toISOString(), lte: moment(tlConfig.time.to).toISOString(), - format: 'strict_date_optional_time' - } - } + format: 'strict_date_optional_time', + }, + }, }; bool.must.push(timeFilter); @@ -43,20 +42,23 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) } const aggs = { - 'q': { + q: { meta: { type: 'split' }, filters: { - filters: _.chain(config.q).map(function (q) { - return [q, { query_string: { query: q } }]; - }).zipObject().value(), + filters: _.chain(config.q) + .map(function(q) { + return [q, { query_string: { query: q } }]; + }) + .zipObject() + .value(), }, - aggs: {} - } + aggs: {}, + }, }; let aggCursor = aggs.q.aggs; - _.each(config.split, function (clause) { + _.each(config.split, function(clause) { clause = clause.split(':'); if (clause[0] && clause[1]) { const termsAgg = buildAggBody(clause[0], scriptedFields); @@ -64,11 +66,11 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) aggCursor[clause[0]] = { meta: { type: 'split' }, terms: termsAgg, - aggs: {} + aggs: {}, }; aggCursor = aggCursor[clause[0]].aggs; } else { - throw new Error ('`split` requires field:limit'); + throw new Error('`split` requires field:limit'); } }); @@ -79,11 +81,11 @@ export default function buildRequest(config, tlConfig, scriptedFields, timeout) ignore_throttled: !tlConfig.settings['search:includeFrozen'], body: { query: { - bool: bool + bool: bool, }, aggs: aggs, - size: 0 - } + size: 0, + }, }; if (timeout) { diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js index 9a5c5dd4e9595..b8bf83116b43b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js @@ -30,15 +30,15 @@ export default function createDateAgg(config, tlConfig, scriptedFields) { time_zone: tlConfig.time.timezone, extended_bounds: { min: tlConfig.time.from, - max: tlConfig.time.to + max: tlConfig.time.to, }, - min_doc_count: 0 - } - } + min_doc_count: 0, + }, + }, }; dateAgg.time_buckets.aggs = {}; - _.each(config.metric, function (metric) { + _.each(config.metric, function(metric) { metric = metric.split(':'); if (metric[0] === 'count') { // This is pretty lame, but its how the "doc_count" metric has to be implemented at the moment @@ -46,8 +46,8 @@ export default function createDateAgg(config, tlConfig, scriptedFields) { dateAgg.time_buckets.aggs[metric] = { bucket_script: { buckets_path: '_count', - script: { source: '_value', lang: 'expression' } - } + script: { source: '_value', lang: 'expression' }, + }, }; } else if (metric[0] && metric[1]) { const metricName = metric[0] + '(' + metric[1] + ')'; @@ -59,7 +59,7 @@ export default function createDateAgg(config, tlConfig, scriptedFields) { dateAgg.time_buckets.aggs[metricName][metric[0]].percents = percentList; } } else { - throw new Error ('`metric` requires metric:field or simply count'); + throw new Error('`metric` requires metric:field or simply count'); } }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js index ace36b49b85c7..06b8c1e81b4c4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js @@ -25,15 +25,15 @@ export default new Chainable('first', { args: [ { name: 'inputSeries', - types: ['seriesList'] - } + types: ['seriesList'], + }, ], help: i18n.translate('timelion.help.functions.firstHelpText', { defaultMessage: `This is an internal function that simply returns the input seriesList. Don't use this`, }), fn: function firstFn(args) { - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js index 572338d29a6cf..c47f8dc84908c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js @@ -22,13 +22,13 @@ import alter from '../lib/alter.js'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; import loadFunctions from '../lib/load_functions.js'; -const fitFunctions = loadFunctions('fit_functions'); +const fitFunctions = loadFunctions('fit_functions'); export default new Chainable('fit', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'mode', @@ -42,16 +42,15 @@ export default new Chainable('fit', { }), suggestions: _.keys(fitFunctions).map(key => { return { name: key }; - }) - } + }), + }, ], help: i18n.translate('timelion.help.functions.fitHelpText', { defaultMessage: 'Fills null values using a defined fit function', }), fn: function absFn(args) { - return alter(args, function (eachSeries, mode) { - - const noNulls = eachSeries.data.filter((item) => (item[1] === 0 || item[1])); + return alter(args, function(eachSeries, mode) { + const noNulls = eachSeries.data.filter(item => item[1] === 0 || item[1]); if (noNulls.length === 0) { return eachSeries; @@ -60,5 +59,5 @@ export default new Chainable('fit', { eachSeries.data = fitFunctions[mode](noNulls, eachSeries.data); return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js index b29cd829229c9..a80dd2f3ff29e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js @@ -23,7 +23,7 @@ import fetch from 'node-fetch'; import moment from 'moment'; import Datasource from '../lib/classes/datasource'; -export default new Datasource ('graphite', { +export default new Datasource('graphite', { args: [ { name: 'metric', // _test-data.users.*.data @@ -34,57 +34,65 @@ export default new Datasource ('graphite', { metricExample: '_test-data.users.*.data', }, }), - } + }, ], help: i18n.translate('timelion.help.functions.graphiteHelpText', { - defaultMessage: - `[experimental] Pull data from graphite. Configure your graphite server in Kibana's Advanced Settings`, + defaultMessage: `[experimental] Pull data from graphite. Configure your graphite server in Kibana's Advanced Settings`, }), fn: function graphite(args, tlConfig) { const config = args.byName; const time = { min: moment(tlConfig.time.from).format('HH:mm[_]YYYYMMDD'), - max: moment(tlConfig.time.to).format('HH:mm[_]YYYYMMDD') + max: moment(tlConfig.time.to).format('HH:mm[_]YYYYMMDD'), }; const allowedUrls = tlConfig.server.config().get('timelion.graphiteUrls'); const configuredUrl = tlConfig.settings['timelion:graphite.url']; if (!allowedUrls.includes(configuredUrl)) { - throw new Error(i18n.translate('timelion.help.functions.notAllowedGraphiteUrl', { - defaultMessage: - `This graphite URL is not configured on the kibana.yml file. + throw new Error( + i18n.translate('timelion.help.functions.notAllowedGraphiteUrl', { + defaultMessage: `This graphite URL is not configured on the kibana.yml file. Please configure your graphite server list in the kibana.yml file under 'timelion.graphiteUrls' and select one from Kibana's Advanced Settings`, - })); + }) + ); } - const URL = tlConfig.settings['timelion:graphite.url'] + '/render/' + + const URL = + tlConfig.settings['timelion:graphite.url'] + + '/render/' + '?format=json' + - '&from=' + time.min + - '&until=' + time.max + - '&target=' + config.metric; + '&from=' + + time.min + + '&until=' + + time.max + + '&target=' + + config.metric; - return fetch(URL).then(function (resp) { - return resp.json(); - }).then(function (resp) { - const list = _.map(resp, function (series) { - const data = _.map(series.datapoints, function (point) { - return [point[1] * 1000, point[0]]; + return fetch(URL) + .then(function(resp) { + return resp.json(); + }) + .then(function(resp) { + const list = _.map(resp, function(series) { + const data = _.map(series.datapoints, function(point) { + return [point[1] * 1000, point[0]]; + }); + return { + data: data, + type: 'series', + fit: 'nearest', // TODO make this customizable + label: series.target, + }; }); + return { - data: data, - type: 'series', - fit: 'nearest', // TODO make this customizable - label: series.target + type: 'seriesList', + list: list, }; + }) + .catch(function(e) { + throw e; }); - - return { - type: 'seriesList', - list: list - }; - }).catch(function (e) { - throw e; - }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js index b643afe9937e8..5a461e833b75a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js @@ -25,7 +25,7 @@ export default new Chainable('hide', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'hide', @@ -33,15 +33,15 @@ export default new Chainable('hide', { help: i18n.translate('timelion.help.functions.hide.args.hideHelpText', { defaultMessage: 'Hide or unhide the series', }), - } + }, ], help: i18n.translate('timelion.help.functions.hideHelpText', { defaultMessage: 'Hide the series by default', }), fn: function hideFn(args) { - return alter(args, function (eachSeries, hide) { + return alter(args, function(eachSeries, hide) { eachSeries._hide = hide == null ? true : hide; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js index f048a6a04b59c..970d146c45b91 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js @@ -29,14 +29,13 @@ export default new Chainable('holt', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'alpha', types: ['number'], help: i18n.translate('timelion.help.functions.holt.args.alphaHelpText', { - defaultMessage: - ` + defaultMessage: ` Smoothing weight from 0 to 1. Increasing alpha will make the new series more closely follow the original. Lowering it will make the series smoother`, @@ -46,8 +45,7 @@ export default new Chainable('holt', { name: 'beta', types: ['number'], help: i18n.translate('timelion.help.functions.holt.args.betaHelpText', { - defaultMessage: - ` + defaultMessage: ` Trending weight from 0 to 1. Increasing beta will make rising/falling lines continue to rise/fall longer. Lowering it will make the function learn the new trend faster`, @@ -57,8 +55,7 @@ export default new Chainable('holt', { name: 'gamma', types: ['number'], help: i18n.translate('timelion.help.functions.holt.args.gammaHelpText', { - defaultMessage: - ` + defaultMessage: ` Seasonal weight from 0 to 1. Does your data look like a wave? Increasing this will give recent seasons more importance, thus changing the wave form faster. Lowering it will reduce the importance of new seasons, making history more important. @@ -79,17 +76,15 @@ export default new Chainable('holt', { name: 'sample', types: ['number', 'null'], help: i18n.translate('timelion.help.functions.holt.args.sampleHelpText', { - defaultMessage: - ` + defaultMessage: ` The number of seasons to sample before starting to "predict" in a seasonal series. (Only useful with gamma, Default: all)`, description: '"gamma" and "all" are parameter names and values and must not be translated.', }), - } + }, ], help: i18n.translate('timelion.help.functions.holtHelpText', { - defaultMessage: - ` + defaultMessage: ` Sample the beginning of a series and use it to forecast what should happen via several optional parameters. In general, this doesn't really predict the future, but predicts what should be happening right now according to past data, @@ -97,17 +92,15 @@ export default new Chainable('holt', { description: '"null" is a data value here and must not be translated.', }), fn: function expsmoothFn(args, tlConfig) { - const newSeries = _.cloneDeep(args.byName.inputSeries); const alpha = args.byName.alpha; const beta = args.byName.beta; const gamma = args.byName.gamma; - _.each(newSeries.list, function (series) { + _.each(newSeries.list, function(series) { const sample = args.byName.sample || series.data.length; // If we use length it should simply never predict - // Single exponential smoothing // This is basically a weighted moving average in which the older // points exponentially degrade relative to the alpha, e.g.: @@ -132,7 +125,9 @@ export default new Chainable('holt', { }) ); } - const season = Math.round(toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval)); + const season = Math.round( + toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval) + ); points = tes(points, alpha, beta, gamma, season, sample); } @@ -140,5 +135,5 @@ export default new Chainable('holt', { }); return newSeries; - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js index 19d4d9fcbc518..a1fe4b1f1f52c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js @@ -31,34 +31,38 @@ export default function des(points, alpha, beta) { throw new Error( i18n.translate('timelion.serverSideErrors.holtFunction.notEnoughPointsErrorMessage', { defaultMessage: 'You need at least 2 points to use double exponential smoothing', - }), + }) ); } - const smoothedPoints = _.map(points, (point, i) => { - if (i === 0) { - return point; - } + const smoothedPoints = _.map( + points, + (point, i) => { + if (i === 0) { + return point; + } - if (i === 1) { - // Establish initial values for level and trend; - level = points[0]; - trend = points[1] - points[0]; // This is sort of a lame way to do this - } + if (i === 1) { + // Establish initial values for level and trend; + level = points[0]; + trend = points[1] - points[0]; // This is sort of a lame way to do this + } - if (point == null) { - unknownCount++; - } else { - unknownCount = 0; - // These 2 variables are not required, but are used for clarity. - prevLevel = level; - prevTrend = trend; - level = (alpha * point) + (1 - alpha) * (prevLevel + prevTrend); - trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; - } + if (point == null) { + unknownCount++; + } else { + unknownCount = 0; + // These 2 variables are not required, but are used for clarity. + prevLevel = level; + prevTrend = trend; + level = alpha * point + (1 - alpha) * (prevLevel + prevTrend); + trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; + } - return (level + (unknownCount * trend)); - }, []); + return level + unknownCount * trend; + }, + [] + ); return smoothedPoints; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js index 6236067b5a0de..5fe4eddc4ab15 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js @@ -27,27 +27,31 @@ export default function ses(points, alpha) { let origin; let level; - const smoothedPoints = _.reduce(points, (result, point, i) => { - if (i === 0) { - origin = point; - level = point; - } else { - // In the case that point[1] is null, we keep origin the same - // and forecast the point based on the previous smoothed point - if (point != null) { + const smoothedPoints = _.reduce( + points, + (result, point, i) => { + if (i === 0) { origin = point; - } - if (origin == null) { - level = null; + level = point; } else { - const prevSmoothed = result[i - 1]; - level = alpha * origin + (1 - alpha) * prevSmoothed; + // In the case that point[1] is null, we keep origin the same + // and forecast the point based on the previous smoothed point + if (point != null) { + origin = point; + } + if (origin == null) { + level = null; + } else { + const prevSmoothed = result[i - 1]; + level = alpha * origin + (1 - alpha) * prevSmoothed; + } } - } - result.push(level); - return result; - }, []); + result.push(level); + return result; + }, + [] + ); return smoothedPoints; } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js index fee235abe2b63..cafb32614db74 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js @@ -33,20 +33,24 @@ import _ from 'lodash'; function initSeasonalComponents(samplePoints, seasonLength) { const sampledSeasonCount = samplePoints.length / seasonLength; let currentSeason = []; - const seasonalAverages = _.reduce(samplePoints, (result, point, i) => { - currentSeason.push(point); - // If this is the end of the season, add it to the result; - if (i % seasonLength === seasonLength - 1) { - result.push(_.sum(currentSeason) / seasonLength); - currentSeason = []; - } + const seasonalAverages = _.reduce( + samplePoints, + (result, point, i) => { + currentSeason.push(point); + // If this is the end of the season, add it to the result; + if (i % seasonLength === seasonLength - 1) { + result.push(_.sum(currentSeason) / seasonLength); + currentSeason = []; + } - return result; - }, []); + return result; + }, + [] + ); - const seasonals = _.times(seasonLength, (i) => { + const seasonals = _.times(seasonLength, i => { let sumOfValsOverAvg = 0; - _.times(sampledSeasonCount, (j) => { + _.times(sampledSeasonCount, j => { sumOfValsOverAvg += samplePoints[seasonLength * j + i] - seasonalAverages[j]; }); @@ -60,14 +64,13 @@ function initSeasonalComponents(samplePoints, seasonLength) { // the difference in points between seasons function initTrend(samplePoints, seasonLength) { let sum = 0; - _.times(seasonLength, (i) => { + _.times(seasonLength, i => { sum += (samplePoints[i + seasonLength] - samplePoints[i]) / seasonLength; }); return sum / seasonLength; } export default function tes(points, alpha, beta, gamma, seasonLength, seasonsToSample) { - const samplePoints = points.slice(0, seasonLength * seasonsToSample); const seasonals = initSeasonalComponents(samplePoints, seasonLength); let level; @@ -90,7 +93,7 @@ export default function tes(points, alpha, beta, gamma, seasonLength, seasonsToS if (point == null || i >= samplePoints.length) { unknownCount++; // Don't know this point, make it up! - return (level + (unknownCount * trend)) + seasonals[seasonalPosition]; + return level + unknownCount * trend + seasonals[seasonalPosition]; } else { unknownCount = 0; // These 2 variables are not required, but are used for clarity. @@ -98,10 +101,10 @@ export default function tes(points, alpha, beta, gamma, seasonLength, seasonsToS prevTrend = trend; level = alpha * (point - seasonals[seasonalPosition]) + (1 - alpha) * (prevLevel + prevTrend); trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; - seasonals[seasonalPosition] = gamma * (point - level) + (1 - gamma) * seasonals[seasonalPosition]; + seasonals[seasonalPosition] = + gamma * (point - level) + (1 - gamma) * seasonals[seasonalPosition]; return level + trend + seasonals[seasonalPosition]; } - }); return result; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js index ee92b68441d9e..1e4782e5a381e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js @@ -25,7 +25,7 @@ export default new Chainable('label', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'label', @@ -42,14 +42,14 @@ export default new Chainable('label', { help: i18n.translate('timelion.help.functions.label.args.regexHelpText', { defaultMessage: 'A regex with capture group support', }), - } + }, ], help: i18n.translate('timelion.help.functions.labelHelpText', { defaultMessage: 'Change the label of the series. Use %s to reference the existing label', }), fn: function labelFn(args) { const config = args.byName; - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { if (config.regex) { eachSeries.label = eachSeries.label.replace(new RegExp(config.regex), config.label); } else { @@ -58,5 +58,5 @@ export default new Chainable('label', { return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js index 83acfb69d4709..b467318686729 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js @@ -26,7 +26,7 @@ export default new Chainable('legend', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'position', @@ -81,8 +81,8 @@ export default new Chainable('legend', { defaultMessage: 'place legend in south west corner', } ), - } - ] + }, + ], }, { name: 'columns', @@ -107,13 +107,19 @@ export default new Chainable('legend', { defaultTimeFormat: DEFAULT_TIME_FORMAT, }, }), - } + }, ], help: i18n.translate('timelion.help.functions.legendHelpText', { defaultMessage: 'Set the position and style of the legend on the plot', }), fn: function legendFn(args) { - return alter(args, function (eachSeries, position, columns, showTime = true, timeFormat = DEFAULT_TIME_FORMAT) { + return alter(args, function( + eachSeries, + position, + columns, + showTime = true, + timeFormat = DEFAULT_TIME_FORMAT + ) { eachSeries._global = eachSeries._global || {}; eachSeries._global.legend = eachSeries._global.legend || {}; eachSeries._global.legend.noColumns = columns; @@ -129,5 +135,5 @@ export default new Chainable('legend', { return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js index 6a557be7f60ac..39b8893692619 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js @@ -25,7 +25,7 @@ export default new Chainable('lines', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'width', @@ -61,25 +61,25 @@ export default new Chainable('lines', { help: i18n.translate('timelion.help.functions.lines.args.stepsHelpText', { defaultMessage: 'Show line as step, e.g., do not interpolate between points', }), - } + }, ], help: i18n.translate('timelion.help.functions.linesHelpText', { defaultMessage: 'Show the seriesList as lines', }), fn: function linesFn(args) { - return alter(args, function (eachSeries, width, fill, stack, show, steps) { + return alter(args, function(eachSeries, width, fill, stack, show, steps) { eachSeries.lines = eachSeries.lines || {}; // Defaults if (eachSeries.lines.lineWidth == null) eachSeries.lines.lineWidth = 3; if (width != null) eachSeries.lines.lineWidth = width; - if (fill != null) eachSeries.lines.fill = fill / 10; + if (fill != null) eachSeries.lines.fill = fill / 10; if (stack != null) eachSeries.stack = stack; - if (show != null) eachSeries.lines.show = show; + if (show != null) eachSeries.lines.show = show; if (steps != null) eachSeries.lines.steps = steps; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js index 76f0d76c39f59..4dc356cb9e7df 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js @@ -26,7 +26,7 @@ export default new Chainable('log', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'base', @@ -34,7 +34,7 @@ export default new Chainable('log', { help: i18n.translate('timelion.help.functions.log.args.baseHelpText', { defaultMessage: 'Set logarithmic base, 10 by default', }), - } + }, ], help: i18n.translate('timelion.help.functions.logHelpText', { defaultMessage: @@ -42,12 +42,12 @@ export default new Chainable('log', { }), fn: function logFn(args) { const config = args.byName; - return alter(args, function (eachSeries) { - const data = _.map(eachSeries.data, function (point) { + return alter(args, function(eachSeries) { + const data = _.map(eachSeries.data, function(point) { return [point[0], Math.log(point[1]) / Math.log(config.base || 10)]; }); eachSeries.data = data; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js index 8177c8bd49bdb..26680df7df1c4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js @@ -25,7 +25,7 @@ export default new Chainable('max', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'value', @@ -35,16 +35,15 @@ export default new Chainable('max', { 'Sets the point to whichever is higher, the existing value, or the one passed. ' + 'If passing a seriesList it must contain exactly 1 series.', }), - } - + }, ], help: i18n.translate('timelion.help.functions.maxHelpText', { defaultMessage: 'Maximum values of one or more series in a seriesList to each position, in each series, of the input seriesList', }), fn: function maxFn(args) { - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return Math.max(a, b); }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js index 75741951b5475..a3dc912d1d14b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js @@ -25,7 +25,7 @@ export default new Chainable('min', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'value', @@ -35,16 +35,15 @@ export default new Chainable('min', { 'Sets the point to whichever is lower, the existing value, or the one passed. ' + 'If passing a seriesList it must contain exactly 1 series.', }), - } - + }, ], help: i18n.translate('timelion.help.functions.minHelpText', { defaultMessage: 'Minimum values of one or more series in a seriesList to each position, in each series, of the input seriesList', }), fn: function minFn(args) { - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return Math.min(a, b); }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js index b8de3749b30b4..361cd1f9dfb67 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js @@ -30,7 +30,7 @@ export default new Chainable('movingaverage', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'window', @@ -58,8 +58,8 @@ export default new Chainable('movingaverage', { suggestion.help = 'default'; } return suggestion; - }) - } + }), + }, ], aliases: ['mvavg'], help: i18n.translate('timelion.help.functions.movingaverageHelpText', { @@ -67,8 +67,7 @@ export default new Chainable('movingaverage', { 'Calculate the moving average over a given window. Nice for smoothing noisy series', }), fn: function movingaverageFn(args, tlConfig) { - return alter(args, function (eachSeries, _window, _position) { - + return alter(args, function(eachSeries, _window, _position) { // _window always needs to be a number, if isn't we have to make it into one. if (typeof _window !== 'number') { // Ok, I guess its a datemath expression @@ -84,12 +83,15 @@ export default new Chainable('movingaverage', { _position = _position || defaultPosition; if (!_.contains(validPositions, _position)) { throw new Error( - i18n.translate('timelion.serverSideErrors.movingaverageFunction.notValidPositionErrorMessage', { - defaultMessage: 'Valid positions are: {validPositions}', - values: { - validPositions: validPositions.join(', '), - }, - }) + i18n.translate( + 'timelion.serverSideErrors.movingaverageFunction.notValidPositionErrorMessage', + { + defaultMessage: 'Valid positions are: {validPositions}', + values: { + validPositions: validPositions.join(', '), + }, + } + ) ); } @@ -98,10 +100,13 @@ export default new Chainable('movingaverage', { eachSeries.label = eachSeries.label + ' mvavg=' + _window; function toPoint(point, pairSlice) { - const average = _.chain(pairSlice) - .map(1).reduce(function (memo, num) { - return (memo + num); - }).value() / _window; + const average = + _.chain(pairSlice) + .map(1) + .reduce(function(memo, num) { + return memo + num; + }) + .value() / _window; return [point[0], average]; } @@ -109,26 +114,24 @@ export default new Chainable('movingaverage', { if (_position === 'center') { const windowLeft = Math.floor(_window / 2); const windowRight = _window - windowLeft; - eachSeries.data = _.map(pairs, function (point, i) { + eachSeries.data = _.map(pairs, function(point, i) { if (i < windowLeft || i > pairsLen - windowRight) return [point[0], null]; return toPoint(point, pairs.slice(i - windowLeft, i + windowRight)); }); } else if (_position === 'left') { - eachSeries.data = _.map(pairs, function (point, i) { + eachSeries.data = _.map(pairs, function(point, i) { const cursor = i + 1; if (cursor < _window) return [point[0], null]; return toPoint(point, pairs.slice(cursor - _window, cursor)); }); - } else if (_position === 'right') { - eachSeries.data = _.map(pairs, function (point, i) { + eachSeries.data = _.map(pairs, function(point, i) { if (i > pairsLen - _window) return [point[0], null]; return toPoint(point, pairs.slice(i, i + _window)); }); - } return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js index 396e88fa601a8..318bfd1d06300 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js @@ -29,7 +29,7 @@ export default new Chainable('movingstd', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'window', @@ -49,7 +49,7 @@ export default new Chainable('movingstd', { defaultPosition, }, }), - } + }, ], aliases: ['mvstd'], help: i18n.translate('timelion.help.functions.movingstdHelpText', { @@ -58,18 +58,20 @@ export default new Chainable('movingstd', { 'Rounding errors may become more noticeable with very long series, or series with very large numbers.', }), fn: function movingstdFn(args) { - return alter(args, function (eachSeries, _window, _position) { - + return alter(args, function(eachSeries, _window, _position) { _position = _position || defaultPosition; if (!_.contains(positions, _position)) { throw new Error( - i18n.translate('timelion.serverSideErrors.movingstdFunction.notValidPositionErrorMessage', { - defaultMessage: 'Valid positions are: {validPositions}', - values: { - validPositions: positions.join(', '), - }, - }), + i18n.translate( + 'timelion.serverSideErrors.movingstdFunction.notValidPositionErrorMessage', + { + defaultMessage: 'Valid positions are: {validPositions}', + values: { + validPositions: positions.join(', '), + }, + } + ) ); } @@ -78,15 +80,24 @@ export default new Chainable('movingstd', { eachSeries.label = eachSeries.label + ' mvstd=' + _window; function toPoint(point, pairSlice) { - const average = _.chain(pairSlice).map(1).reduce(function (memo, num) { - return memo + num; - }).value() / _window; + const average = + _.chain(pairSlice) + .map(1) + .reduce(function(memo, num) { + return memo + num; + }) + .value() / _window; - const variance = _.chain(pairSlice).map(function (point) { - return Math.pow(point[1] - average, 2); - }).reduce(function (memo, num) { - return memo + num; - }).value() / (_window - 1); + const variance = + _.chain(pairSlice) + .map(function(point) { + return Math.pow(point[1] - average, 2); + }) + .reduce(function(memo, num) { + return memo + num; + }) + .value() / + (_window - 1); return [point[0], Math.sqrt(variance)]; } @@ -94,17 +105,17 @@ export default new Chainable('movingstd', { if (_position === 'center') { const windowLeft = Math.floor(_window / 2); const windowRight = _window - windowLeft; - eachSeries.data = _.map(pairs, function (point, i) { + eachSeries.data = _.map(pairs, function(point, i) { if (i < windowLeft || i >= pairsLen - windowRight) return [point[0], null]; return toPoint(point, pairs.slice(i - windowLeft, i + windowRight)); }); } else if (_position === 'left') { - eachSeries.data = _.map(pairs, function (point, i) { + eachSeries.data = _.map(pairs, function(point, i) { if (i < _window) return [point[0], null]; return toPoint(point, pairs.slice(i - _window, i)); }); } else if (_position === 'right') { - eachSeries.data = _.map(pairs, function (point, i) { + eachSeries.data = _.map(pairs, function(point, i) { if (i >= pairsLen - _window) return [point[0], null]; return toPoint(point, pairs.slice(i, i + _window)); }); @@ -112,5 +123,5 @@ export default new Chainable('movingstd', { return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js index 1ecda7f781190..3bab685b530e8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js @@ -25,7 +25,7 @@ export default new Chainable('multiply', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'multiplier', @@ -34,15 +34,15 @@ export default new Chainable('multiply', { defaultMessage: 'Number or series by which to multiply. SeriesList with multiple series will be applied label-wise.', }), - } + }, ], help: i18n.translate('timelion.help.functions.multiplyHelpText', { defaultMessage: 'Multiply the values of one or more series in a seriesList to each position, in each series, of the input seriesList', }), fn: function multiplyFn(args) { - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return a * b; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js index 6817d833b5544..f2c0472c70ab2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js @@ -29,7 +29,7 @@ export default new Chainable('points', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'radius', @@ -74,7 +74,7 @@ export default new Chainable('points', { suggestion.help = 'default'; } return suggestion; - }) + }), }, { name: 'show', @@ -82,13 +82,13 @@ export default new Chainable('points', { help: i18n.translate('timelion.help.functions.points.args.showHelpText', { defaultMessage: 'Show points or not', }), - } + }, ], help: i18n.translate('timelion.help.functions.pointsHelpText', { defaultMessage: 'Show the series as points', }), fn: function pointsFn(args) { - return alter(args, function (eachSeries, radius, weight, fill, fillColor, symbol, show) { + return alter(args, function(eachSeries, radius, weight, fill, fillColor, symbol, show) { eachSeries.points = eachSeries.points || {}; eachSeries.points.radius = radius == null ? undefined : radius; @@ -97,7 +97,7 @@ export default new Chainable('points', { } if (fill != null) { - eachSeries.points.fill = fill / 10; + eachSeries.points.fill = fill / 10; } if (weight != null) { @@ -120,9 +120,7 @@ export default new Chainable('points', { eachSeries.points.show = show == null ? true : show; - - return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js index 98eec9b57dabc..756fb067f2335 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js @@ -26,7 +26,7 @@ export default new Chainable('precision', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'precision', @@ -34,20 +34,20 @@ export default new Chainable('precision', { help: i18n.translate('timelion.help.functions.precision.args.precisionHelpText', { defaultMessage: 'Number of digits to round each value to', }), - } + }, ], help: i18n.translate('timelion.help.functions.precisionHelpText', { defaultMessage: 'number of digits to round the decimal portion of the value to', }), fn: async function precisionFn(args) { - await alter(args, function (eachSeries, precision) { + await alter(args, function(eachSeries, precision) { eachSeries._meta = eachSeries._meta || {}; eachSeries._meta.precision = precision; return eachSeries; }); - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return parseInt(a * Math.pow(10, b), 10) / Math.pow(10, b); }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js index 80e9cafd67120..0ec8534d8fff2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js @@ -27,12 +27,12 @@ function unflatten(data) { const regex = new RegExp(/\.?([^.\[\]]+)|\[(\d+)\]/g); const result = {}; - _.each(data, function (val, p) { + _.each(data, function(val, p) { let cur = result; let prop = ''; let m; - while (m = regex.exec(p)) { - cur = (cur.hasOwnProperty(prop) && cur[prop]) || (cur[prop] = (m[2] ? [] : {})); + while ((m = regex.exec(p))) { + cur = (cur.hasOwnProperty(prop) && cur[prop]) || (cur[prop] = m[2] ? [] : {}); prop = m[2] || m[1]; } cur[prop] = data[p]; @@ -45,7 +45,7 @@ export default new Chainable('props', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'global', @@ -53,13 +53,13 @@ export default new Chainable('props', { help: i18n.translate('timelion.help.functions.props.args.globalHelpText', { defaultMessage: 'Set props on the seriesList vs on each series', }), - } + }, ], extended: { types: ['seriesList', 'number', 'string', 'boolean', 'null'], // Extended args can not currently be multivalued, // multi: false is not required and is shown here for demonstration purposes - multi: false + multi: false, }, // extended means you can pass arguments that aren't listed. They just won't be in the ordered array // They will be passed as args._extended:{} @@ -77,10 +77,10 @@ export default new Chainable('props', { _.assign(args.byName.inputSeries, properties); return args.byName.inputSeries; } else { - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { _.assign(eachSeries, properties); return eachSeries; }); } - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js index dc7c27b8abc1a..fd7de05464da3 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js @@ -25,7 +25,7 @@ fetch.Promise = require('bluebird'); import Datasource from '../lib/classes/datasource'; -export default new Datasource ('quandl', { +export default new Datasource('quandl', { dataSource: true, args: [ { @@ -42,11 +42,10 @@ export default new Datasource ('quandl', { defaultMessage: 'Some quandl sources return multiple series, which one should I use? 1 based index.', }), - } + }, ], help: i18n.translate('timelion.help.functions.quandlHelpText', { - defaultMessage: - ` + defaultMessage: ` [experimental] Pull data from quandl.com using the quandl code. Set {quandlKeyField} to your free API key in Kibana's Advanced Settings. The API has a really low rate limit without a key.`, @@ -66,13 +65,14 @@ export default new Datasource ('quandl', { code: 'WIKI/AAPL', position: 1, interval: intervalMap[tlConfig.time.interval], - apikey: tlConfig.settings['timelion:quandl.key'] + apikey: tlConfig.settings['timelion:quandl.key'], }); if (!config.interval) { throw new Error( i18n.translate('timelion.serverSideErrors.quandlFunction.unsupportedIntervalErrorMessage', { - defaultMessage: 'quandl() unsupported interval: {interval}. quandl() supports: {intervals}', + defaultMessage: + 'quandl() unsupported interval: {interval}. quandl() supports: {intervals}', values: { interval: tlConfig.time.interval, intervals: _.keys(intervalMap).join(', '), @@ -83,7 +83,7 @@ export default new Datasource ('quandl', { const time = { min: moment.utc(tlConfig.time.from).format('YYYY-MM-DD'), - max: moment.utc(tlConfig.time.to).format('YYYY-MM-DD') + max: moment.utc(tlConfig.time.to).format('YYYY-MM-DD'), }; // POSITIONS @@ -93,29 +93,43 @@ export default new Datasource ('quandl', { // 4. close // 5. volume - const URL = 'https://www.quandl.com/api/v1/datasets/' + config.code + '.json' + + const URL = + 'https://www.quandl.com/api/v1/datasets/' + + config.code + + '.json' + '?sort_order=asc' + - '&trim_start=' + time.min + - '&trim_end=' + time.max + - '&collapse=' + config.interval + - '&auth_token=' + config.apikey; + '&trim_start=' + + time.min + + '&trim_end=' + + time.max + + '&collapse=' + + config.interval + + '&auth_token=' + + config.apikey; - return fetch(URL).then(function (resp) { return resp.json(); }).then(function (resp) { - const data = _.map(resp.data, function (bucket) { - return [moment(bucket[0]).valueOf(), bucket[config.position]]; - }); + return fetch(URL) + .then(function(resp) { + return resp.json(); + }) + .then(function(resp) { + const data = _.map(resp.data, function(bucket) { + return [moment(bucket[0]).valueOf(), bucket[config.position]]; + }); - return { - type: 'seriesList', - list: [{ - data: data, - type: 'series', - fit: 'nearest', - label: resp.name - }] - }; - }).catch(function (e) { - throw e; - }); - } + return { + type: 'seriesList', + list: [ + { + data: data, + type: 'series', + fit: 'nearest', + label: resp.name, + }, + ], + }; + }) + .catch(function(e) { + throw e; + }); + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js index 89dab33ff2c6f..5305d0b20cd26 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js @@ -26,7 +26,7 @@ export default new Chainable('range', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'min', @@ -41,25 +41,25 @@ export default new Chainable('range', { help: i18n.translate('timelion.help.functions.range.args.maxHelpText', { defaultMessage: 'New maximum value', }), - } + }, ], help: i18n.translate('timelion.help.functions.rangeHelpText', { defaultMessage: 'Changes the max and min of a series while keeping the same shape', }), fn: function range(args) { - return alter(args, function (eachSeries) { + return alter(args, function(eachSeries) { const values = _.map(eachSeries.data, 1); const min = _.min(values); const max = _.max(values); // newvalue= (max'-min')/(max-min)*(value-min)+min'. - const data = _.map(eachSeries.data, function (point) { - const val = (args.byName.max - args.byName.min) / - (max - min) * (point[1] - min) + args.byName.min; + const data = _.map(eachSeries.data, function(point) { + const val = + ((args.byName.max - args.byName.min) / (max - min)) * (point[1] - min) + args.byName.min; return [point[0], val]; }); eachSeries.data = data; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js index 9652fbe8b5720..778c91d30f2cb 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js @@ -27,7 +27,7 @@ export default new Chainable('scale_interval', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'interval', @@ -36,7 +36,7 @@ export default new Chainable('scale_interval', { defaultMessage: 'The new interval in date math notation, e.g., 1s for 1 second. 1m, 5m, 1M, 1w, 1y, etc.', }), - } + }, ], help: i18n.translate('timelion.help.functions.scaleIntervalHelpText', { defaultMessage: @@ -46,12 +46,12 @@ export default new Chainable('scale_interval', { const currentInterval = toMS(tlConfig.time.interval); const scaleInterval = toMS(args.byName.interval); - return alter(args, function (eachSeries) { - const data = _.map(eachSeries.data, function (point) { + return alter(args, function(eachSeries) { + const data = _.map(eachSeries.data, function(point) { return [point[0], (point[1] / currentInterval) * scaleInterval]; }); eachSeries.data = data; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js index e348afbec95b8..405f595cd4d32 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js @@ -22,7 +22,7 @@ import _ from 'lodash'; import Datasource from '../lib/classes/datasource'; import Bluebird from 'bluebird'; -export default new Datasource ('static', { +export default new Datasource('static', { aliases: ['value'], args: [ { @@ -40,13 +40,12 @@ export default new Datasource ('static', { defaultMessage: 'A quick way to set the label for the series. You could also use the .label() function', }), - } + }, ], help: i18n.translate('timelion.help.functions.staticHelpText', { defaultMessage: 'Draws a single value across the chart', }), fn: function staticFn(args, tlConfig) { - let data; const target = tlConfig.getTargetSeries(); if (typeof args.byName.value === 'string') { @@ -54,11 +53,11 @@ export default new Datasource ('static', { const begin = _.first(target)[0]; const end = _.last(target)[0]; const step = (end - begin) / (points.length - 1); - data = _.map(points, function (point, i) { - return [begin + (i * step), parseFloat(point)]; + data = _.map(points, function(point, i) { + return [begin + i * step, parseFloat(point)]; }); } else { - data = _.map(target, function (bucket) { + data = _.map(target, function(bucket) { return [bucket[0], args.byName.value]; }); } @@ -70,9 +69,9 @@ export default new Datasource ('static', { data: data, type: 'series', label: args.byName.label == null ? String(args.byName.value) : args.byName.label, - fit: args.byName.fit || 'average' - } - ] + fit: args.byName.fit || 'average', + }, + ], }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js index c71da4ec49cc5..c2d83e5d24a61 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js @@ -25,7 +25,7 @@ export default new Chainable('subtract', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'term', @@ -34,15 +34,15 @@ export default new Chainable('subtract', { defaultMessage: 'Number or series to subtract from input. SeriesList with multiple series will be applied label-wise.', }), - } + }, ], help: i18n.translate('timelion.help.functions.subtractHelpText', { defaultMessage: 'Subtract the values of one or more series in a seriesList to each position, in each series, of the input seriesList', }), fn: function subtractFn(args) { - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return a - b; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js index 0ad371e761f0d..269c44badc90c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js @@ -25,7 +25,7 @@ export default new Chainable('sum', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'term', @@ -34,7 +34,7 @@ export default new Chainable('sum', { defaultMessage: 'Number or series to sum with the input series. SeriesList with multiple series will be applied label-wise.', }), - } + }, ], help: i18n.translate('timelion.help.functions.sumHelpText', { defaultMessage: @@ -42,8 +42,8 @@ export default new Chainable('sum', { }), aliases: ['add', 'plus'], fn: function sumFn(args) { - return reduce(args, function (a, b) { + return reduce(args, function(a, b) { return a + b; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js index b39c88121efb3..30202b682cf1b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js @@ -25,7 +25,7 @@ export default new Chainable('title', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'title', @@ -33,16 +33,16 @@ export default new Chainable('title', { help: i18n.translate('timelion.help.functions.title.args.titleHelpText', { defaultMessage: 'Title for the plot.', }), - } + }, ], help: i18n.translate('timelion.help.functions.titleHelpText', { defaultMessage: 'Adds a title to the top of the plot. If called on more than 1 seriesList the last call will be used.', }), fn: function hideFn(args) { - return alter(args, function (eachSeries, title) { + return alter(args, function(eachSeries, title) { eachSeries._title = title; return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js index 088124641f5b7..a194e6c4c161c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js @@ -31,7 +31,7 @@ export default new Chainable('trend', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'mode', @@ -45,7 +45,7 @@ export default new Chainable('trend', { }), suggestions: _.keys(validRegressions).map(key => { return { name: key, help: validRegressions[key] }; - }) + }), }, { name: 'start', @@ -72,7 +72,7 @@ export default new Chainable('trend', { fn: function absFn(args) { const newSeries = _.cloneDeep(args.byName.inputSeries); - _.each(newSeries.list, function (series) { + _.each(newSeries.list, function(series) { const length = series.data.length; let start = args.byName.start == null ? 0 : args.byName.start; let end = args.byName.end == null ? length : args.byName.end; @@ -81,16 +81,16 @@ export default new Chainable('trend', { const subset = series.data.slice(start, end); - const result = (args.byName.mode === 'log') ? log(subset) : linear(subset); + const result = args.byName.mode === 'log' ? log(subset) : linear(subset); - _.each(series.data, function (point) { + _.each(series.data, function(point) { point[1] = null; }); - _.each(result, function (point, i) { + _.each(result, function(point, i) { series.data[start + i] = point; }); }); return newSeries; - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js index ea54eccc8208d..da72e92be9312 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js @@ -18,62 +18,80 @@ */ /* -* Algorithms from -* copyright(c) 2013 Tom Alexander -* Licensed under the MIT license. -*/ + * Algorithms from + * copyright(c) 2013 Tom Alexander + * Licensed under the MIT license. + */ import _ from 'lodash'; function sum(data, fn) { - return _.reduce(data, function (sum, d) { - return sum + (d[1] == null ? 0 : fn(d)); - }, 0); + return _.reduce( + data, + function(sum, d) { + return sum + (d[1] == null ? 0 : fn(d)); + }, + 0 + ); } function count(data) { - return _.filter(data, function (d) { + return _.filter(data, function(d) { return d[1] == null ? false : true; }).length; } function mapTuples(data, fn) { - return _.map(data, function (d) { + return _.map(data, function(d) { return [d[0], fn(d)]; }); } export function linear(data) { - const xSum = sum(data, (d) => {return d[0]; }); - const ySum = sum(data, (d) => {return d[1]; }); - const xSqSum = sum(data, (d) => {return d[0] * d[0]; }); - const xySum = sum(data, (d) => {return d[0] * d[1]; }); + const xSum = sum(data, d => { + return d[0]; + }); + const ySum = sum(data, d => { + return d[1]; + }); + const xSqSum = sum(data, d => { + return d[0] * d[0]; + }); + const xySum = sum(data, d => { + return d[0] * d[1]; + }); const observations = count(data); - const gradient = - ((observations * xySum) - (xSum * ySum)) / - ((observations * xSqSum) - (xSum * xSum)); + const gradient = (observations * xySum - xSum * ySum) / (observations * xSqSum - xSum * xSum); - const intercept = - (ySum / observations) - (gradient * xSum) / observations; + const intercept = ySum / observations - (gradient * xSum) / observations; - return mapTuples(data, (d) => { return d[0] * gradient + intercept; }); + return mapTuples(data, d => { + return d[0] * gradient + intercept; + }); } export function log(data) { - const logXSum = sum(data, (d) => {return Math.log(d[0]); }); - const yLogXSum = sum(data, (d) => {return d[1] * Math.log(d[0]); }); - const ySum = sum(data, (d) => {return d[1]; }); - const logXsqSum = sum(data, (d) => {return Math.pow(Math.log(d[0]), 2); }); + const logXSum = sum(data, d => { + return Math.log(d[0]); + }); + const yLogXSum = sum(data, d => { + return d[1] * Math.log(d[0]); + }); + const ySum = sum(data, d => { + return d[1]; + }); + const logXsqSum = sum(data, d => { + return Math.pow(Math.log(d[0]), 2); + }); const observations = count(data); const b = - ((observations * yLogXSum) - (ySum * logXSum)) / - ((observations * logXsqSum) - (logXSum * logXSum)); + (observations * yLogXSum - ySum * logXSum) / (observations * logXsqSum - logXSum * logXSum); - const a = - (ySum - b * logXSum) / - observations; + const a = (ySum - b * logXSum) / observations; - return mapTuples(data, (d) => { return a + b * Math.log(d[0]); }); + return mapTuples(data, d => { + return a + b * Math.log(d[0]); + }); } diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js index fdfab5a63c9b3..7c4ecbbe89e1f 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js @@ -26,7 +26,7 @@ export default new Chainable('trim', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'start', @@ -41,7 +41,7 @@ export default new Chainable('trim', { help: i18n.translate('timelion.help.functions.trim.args.endHelpText', { defaultMessage: 'Buckets to trim from the end of the series. Default: 1', }), - } + }, ], help: i18n.translate('timelion.help.functions.trimHelpText', { defaultMessage: @@ -52,17 +52,16 @@ export default new Chainable('trim', { if (config.start == null) config.start = 1; if (config.end == null) config.end = 1; - return alter(args, function (eachSeries) { - - _.times(config.start, function (i) { + return alter(args, function(eachSeries) { + _.times(config.start, function(i) { eachSeries.data[i][1] = null; }); - _.times(config.end, function (i) { - eachSeries.data[(eachSeries.data.length - 1) - i][1] = null; + _.times(config.end, function(i) { + eachSeries.data[eachSeries.data.length - 1 - i][1] = null; }); return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js index 6a23b0f169dd4..3630d6c956b68 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js @@ -23,7 +23,7 @@ import fetch from 'node-fetch'; import moment from 'moment'; import Datasource from '../lib/classes/datasource'; -export default new Datasource ('worldbank', { +export default new Datasource('worldbank', { args: [ { name: 'code', // countries/all/indicators/SP.POP.TOTL @@ -35,12 +35,11 @@ export default new Datasource ('worldbank', { apiPathExample: '/en/countries/ind;chn/indicators/DPANUSSPF', }, }), - } + }, ], aliases: ['wb'], help: i18n.translate('timelion.help.functions.worldbankHelpText', { - defaultMessage: - ` + defaultMessage: ` [experimental] Pull data from {worldbankUrl} using path to series. The worldbank provides mostly yearly data, and often has no data for the current year. @@ -54,62 +53,76 @@ export default new Datasource ('worldbank', { // http://api.worldbank.org/en/countries/ind;chn/indicators/DPANUSSPF?date=2000:2006&MRV=5 const config = _.defaults(args.byName, { - code: 'countries/wld/indicators/SP.POP.TOTL' + code: 'countries/wld/indicators/SP.POP.TOTL', }); const time = { min: moment(tlConfig.time.from).format('YYYY'), - max: moment(tlConfig.time.to).format('YYYY') + max: moment(tlConfig.time.to).format('YYYY'), }; - const URL = 'http://api.worldbank.org/' + config.code + - '?date=' + time.min + ':' + time.max + + const URL = + 'http://api.worldbank.org/' + + config.code + + '?date=' + + time.min + + ':' + + time.max + '&format=json' + '&per_page=1000'; - return fetch(URL).then(function (resp) { return resp.json(); }).then(function (resp) { - let hasData = false; - - const respSeries = resp[1]; + return fetch(URL) + .then(function(resp) { + return resp.json(); + }) + .then(function(resp) { + let hasData = false; - const deduped = {}; - let description; - _.each (respSeries, function (bucket) { - if (bucket.value != null) hasData = true; - description = bucket.country.value + ' ' + bucket.indicator.value; - deduped[bucket.date] = bucket.value; - }); + const respSeries = resp[1]; - const data = _.compact(_.map(deduped, function (val, date) { - // Discard nulls - if (val == null) return; - return [moment(date, 'YYYY').valueOf(), Number(val)]; - })); + const deduped = {}; + let description; + _.each(respSeries, function(bucket) { + if (bucket.value != null) hasData = true; + description = bucket.country.value + ' ' + bucket.indicator.value; + deduped[bucket.date] = bucket.value; + }); - if (!hasData) { - throw new Error( - i18n.translate('timelion.serverSideErrors.worldbankFunction.noDataErrorMessage', { - defaultMessage: 'Worldbank request succeeded, but there was no data for {code}', - values: { - code: config.code, - }, + const data = _.compact( + _.map(deduped, function(val, date) { + // Discard nulls + if (val == null) return; + return [moment(date, 'YYYY').valueOf(), Number(val)]; }) ); - } - return { - type: 'seriesList', - list: [{ - data: data, - type: 'series', - label: description, - _meta: { - worldbank_request: URL - } - }] - }; - }).catch(function (e) { - throw e; - }); - } + if (!hasData) { + throw new Error( + i18n.translate('timelion.serverSideErrors.worldbankFunction.noDataErrorMessage', { + defaultMessage: 'Worldbank request succeeded, but there was no data for {code}', + values: { + code: config.code, + }, + }) + ); + } + + return { + type: 'seriesList', + list: [ + { + data: data, + type: 'series', + label: description, + _meta: { + worldbank_request: URL, + }, + }, + ], + }; + }) + .catch(function(e) { + throw e; + }); + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js index 5b8703a6abe25..dc9a3b4a67b33 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js @@ -23,7 +23,7 @@ import worldbank from './worldbank.js'; import Bluebird from 'bluebird'; import Datasource from '../lib/classes/datasource'; -export default new Datasource ('worldbank_indicators', { +export default new Datasource('worldbank_indicators', { args: [ { name: 'country', // countries/all/indicators/SP.POP.TOTL @@ -44,12 +44,11 @@ export default new Datasource ('worldbank_indicators', { indicatorExample: 'SP.POP.TOTL', }, }), - } + }, ], aliases: ['wbi'], help: i18n.translate('timelion.help.functions.worldbankIndicatorsHelpText', { - defaultMessage: - ` + defaultMessage: ` [experimental] Pull data from {worldbankUrl} using the country name and indicator. The worldbank provides mostly yearly data, and often has no data for the current year. Try {offsetQuery} if you get no data for recent @@ -62,25 +61,24 @@ export default new Datasource ('worldbank_indicators', { fn: function worldbankIndicators(args, tlConfig) { const config = _.defaults(args.byName, { country: 'wld', - indicator: 'SP.POP.TOTL' + indicator: 'SP.POP.TOTL', }); const countries = config.country.split(':'); - const seriesLists = _.map(countries, function (country) { + const seriesLists = _.map(countries, function(country) { const code = 'countries/' + country + '/indicators/' + config.indicator; const wbArgs = [code]; wbArgs.byName = { code: code }; return worldbank.timelionFn(wbArgs, tlConfig); }); - return Bluebird.map(seriesLists, function (seriesList) { + return Bluebird.map(seriesLists, function(seriesList) { return seriesList.list[0]; - }).then(function (list) { + }).then(function(list) { return { type: 'seriesList', - list: list + list: list, }; }); - - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js index 707c33837af0b..2653ea398aa14 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js +++ b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js @@ -22,20 +22,20 @@ import _ from 'lodash'; import alter from '../lib/alter.js'; import Chainable from '../lib/classes/chainable'; const tickFormatters = { - 'bits': 'bits', + bits: 'bits', 'bits/s': 'bits/s', - 'bytes': 'bytes', + bytes: 'bytes', 'bytes/s': 'bytes/s', - 'currency': 'currency(:ISO 4217 currency code)', - 'percent': 'percent', - 'custom': 'custom(:prefix:suffix)' + currency: 'currency(:ISO 4217 currency code)', + percent: 'percent', + custom: 'custom(:prefix:suffix)', }; export default new Chainable('yaxis', { args: [ { name: 'inputSeries', - types: ['seriesList'] + types: ['seriesList'], }, { name: 'yaxis', @@ -91,7 +91,7 @@ export default new Chainable('yaxis', { }), suggestions: _.keys(tickFormatters).map(key => { return { name: key, help: tickFormatters[key] }; - }) + }), }, { name: 'tickDecimals', @@ -106,7 +106,17 @@ export default new Chainable('yaxis', { 'Configures a variety of y-axis options, the most important likely being the ability to add an Nth (eg 2nd) y-axis', }), fn: function yaxisFn(args) { - return alter(args, function (eachSeries, yaxis, min, max, position, label, color, units, tickDecimals) { + return alter(args, function( + eachSeries, + yaxis, + min, + max, + position, + label, + color, + units, + tickDecimals + ) { yaxis = yaxis || 1; eachSeries.yaxis = yaxis; @@ -132,13 +142,14 @@ export default new Chainable('yaxis', { const unitTokens = units.split(':'); const unitType = unitTokens[0]; if (!tickFormatters[unitType]) { - throw new Error ( + throw new Error( i18n.translate( 'timelion.serverSideErrors.yaxisFunction.notSupportedUnitTypeErrorMessage', { defaultMessage: '{units} is not a supported unit type.', values: { units }, - }) + } + ) ); } if (unitType === 'currency') { @@ -146,9 +157,12 @@ export default new Chainable('yaxis', { const currency = unitTokens[1]; if (currency && !threeLetterCode.test(currency)) { throw new Error( - i18n.translate('timelion.serverSideErrors.yaxisFunction.notValidCurrencyFormatErrorMessage', { - defaultMessage: 'Currency must be a three letter code', - }) + i18n.translate( + 'timelion.serverSideErrors.yaxisFunction.notValidCurrencyFormatErrorMessage', + { + defaultMessage: 'Currency must be a three letter code', + } + ) ); } } @@ -156,7 +170,7 @@ export default new Chainable('yaxis', { myAxis.units = { type: unitType, prefix: unitTokens[1] || '', - suffix: unitTokens[2] || '' + suffix: unitTokens[2] || '', }; if (unitType === 'percent') { @@ -173,5 +187,5 @@ export default new Chainable('yaxis', { return eachSeries; }); - } + }, }); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/types.ts b/src/legacy/core_plugins/vis_type_timelion/server/types.ts index 4971fa34863d3..e612bc14a0daa 100644 --- a/src/legacy/core_plugins/vis_type_timelion/server/types.ts +++ b/src/legacy/core_plugins/vis_type_timelion/server/types.ts @@ -24,3 +24,5 @@ export { TimelionFunctionArgsSuggestion, TimelionFunctionArgsTypes, } from './lib/classes/timelion_function'; + +export { TimelionRequestQuery } from './routes/run'; From eb19db4b69e587f4f7e483b6244d190f2c99a25e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 19 Dec 2019 15:09:07 +0300 Subject: [PATCH 17/59] Fix ticks --- .../vis_type_timelion/public/panels/panel.tsx | 68 ++++++------------- .../vis_type_timelion/public/panels/utils.ts | 49 ++++++++++++- 2 files changed, 67 insertions(+), 50 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index 1e640122bd559..440716dcbd939 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -20,7 +20,7 @@ import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react'; import $ from 'jquery'; import moment from 'moment-timezone'; -import { debounce, compact, get, each, noop, cloneDeep, last, map } from 'lodash'; +import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; import './timechart/flot'; @@ -34,20 +34,20 @@ import { useEventListener } from './useEventListener'; import { getServices } from '../kibana_services'; import { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions } from './constants'; -import { buildSeriesData } from './utils'; +import { buildSeriesData, buildOptions } from './utils'; export interface Series { + _global?: boolean; + _hide?: boolean; + _id?: number; + _title?: string; + color?: string; data: any[]; fit: string; label: string; split: string; - type: string; - _hide?: boolean; - _id?: number; - color?: string; stack?: boolean; - _global?: boolean; - _title?: string; + type: string; } interface SeriesList { @@ -61,15 +61,14 @@ interface SeriesList { interface PanelProps { name: string; interval: string; - search?: string; seriesList: SeriesList; renderComplete(): void; } -function Panel({ interval: intervalProp, search = noop, seriesList, renderComplete }: PanelProps) { +function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProps) { console.log('Panel'); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const elementRef = useRef(null); + const elementRef = useRef(null); const [canvasElem, setCanvasElem] = useState(); const { uiSettings, timefilter } = getServices(); const formatters = tickFormatters() as any; @@ -107,8 +106,6 @@ function Panel({ interval: intervalProp, search = noop, seriesList, renderComple const [legendValueNumbers, setLegendValueNumbers] = useState(); const [legendCaption, setLegendCaption] = useState(); - let legendScope = {}; - useEffect(() => { if (plot && get(plot.getData(), '[0]._global.legend.showTime', true)) { const caption = $(''); @@ -208,43 +205,18 @@ function Panel({ interval: intervalProp, search = noop, seriesList, renderComple [seriesList.render.grid, toggleSeries, focusSeries, highlightSeries] ); - const options = useMemo(() => cloneDeep(defaultOptions), [defaultOptions]); - - // Get the X-axis tick format - const time = timefilter.getBounds() as any; - const interval = calculateInterval( - time.min.valueOf(), - time.max.valueOf(), - uiSettings.get('timelion:target_buckets') || 200, - intervalProp, - uiSettings.get('timelion:min_interval') || '1ms' - ); - const format = getxAxisFormatter(interval); - - // Use moment to format ticks so we get timezone correction - options.xaxis.tickFormatter = (val: any) => moment(val).format(format); - - // Calculate how many ticks can fit on the axis - const tickLetterWidth = 7; - const tickPadding = 45; - options.xaxis.ticks = Math.floor( - elementRef.current - ? elementRef.current.clientWidth - : 0 / (format.length * tickLetterWidth + tickPadding) + const options = useMemo( + () => + buildOptions( + defaultOptions, + timefilter, + intervalProp, + uiSettings, + elementRef && elementRef.current && elementRef.current.clientWidth || undefined + ), + [defaultOptions, timefilter, intervalProp, uiSettings, elementRef.current] ); - if (options.yaxes) { - options.yaxes.forEach((yaxis: any) => { - if (yaxis && yaxis.units) { - yaxis.tickFormatter = formatters[yaxis.units.type]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicks; - } - } - }); - } - const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); useEffect(() => { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts index d396260fe2172..7946b89ec4c28 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts @@ -17,8 +17,14 @@ * under the License. */ -import { debounce, compact, get, each, noop, cloneDeep, defaults, merge } from 'lodash'; +import { cloneDeep, defaults, merge } from 'lodash'; +import moment from 'moment-timezone'; +// @ts-ignore +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { tickFormatters } from '../services/tick_formatters'; +import { xaxisFormatterProvider } from './timechart/xaxis_formatter'; +import { generateTicksProvider } from './timechart/tick_generator'; import { Series } from './panel'; function buildSeriesData(chart: Series[], options: object) { @@ -59,4 +65,43 @@ function buildSeriesData(chart: Series[], options: object) { }); } -export { buildSeriesData }; +function buildOptions(defaultOptions: any, timefilter: any, intervalValue: string, uiSettings: any, clientWidth = 0) { + const options = cloneDeep(defaultOptions); + // Get the X-axis tick format + const time = timefilter.getBounds() as any; + const interval = calculateInterval( + time.min.valueOf(), + time.max.valueOf(), + uiSettings.get('timelion:target_buckets') || 200, + intervalValue, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = xaxisFormatterProvider(uiSettings)(interval); + + // Use moment to format ticks so we get timezone correction + options.xaxis.tickFormatter = (val: any) => moment(val).format(format); + + // Calculate how many ticks can fit on the axis + const tickLetterWidth = 7; + const tickPadding = 45; + options.xaxis.ticks = Math.floor( + clientWidth / (format.length * tickLetterWidth + tickPadding) + ); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: any) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters() as any; + yaxis.tickFormatter = formatters[yaxis.units.type]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + + return options; +} + +export { buildSeriesData, buildOptions}; From 8618f005f0bec7dc830d5d930f7d34021a3ffc7c Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 19 Dec 2019 15:44:21 +0300 Subject: [PATCH 18/59] Fix plotselected listener --- .../vis_type_timelion/public/panels/panel.tsx | 44 +++++++++++-------- .../public/panels/useEventListener.ts | 39 ---------------- 2 files changed, 25 insertions(+), 58 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index 440716dcbd939..3fdd8475dede2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -24,13 +24,8 @@ import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; import './timechart/flot'; -import { tickFormatters } from '../services/tick_formatters'; // @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../common/lib'; -import { xaxisFormatterProvider } from './timechart/xaxis_formatter'; -import { generateTicksProvider } from './timechart/tick_generator'; - -import { useEventListener } from './useEventListener'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; import { getServices } from '../kibana_services'; import { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions } from './constants'; @@ -68,12 +63,8 @@ interface PanelProps { function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProps) { console.log('Panel'); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); - const elementRef = useRef(null); const [canvasElem, setCanvasElem] = useState(); - const { uiSettings, timefilter } = getServices(); - const formatters = tickFormatters() as any; - const getxAxisFormatter = useMemo(() => xaxisFormatterProvider(uiSettings), [uiSettings]); - const generateTicks = generateTicksProvider(); + const [chartElem, setChartElem] = useState(); const [originalColorMap, setOriginalColorMap] = useState(new Map()); @@ -87,6 +78,23 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp } }, []); + const elementRef = useCallback(node => { + if (node !== null) { + const nodeJQ = $(node); + nodeJQ.on('plotselected', plotselectedHandler); + nodeJQ.on('plothover', plothoverHandler); + nodeJQ.on('mouseleave', mouseleaveHandler); + setChartElem(node); + } + }, []); + + useEffect(() => () => { + const nodeJQ = $(chartElem); + nodeJQ.off('plotselected'); + nodeJQ.off('plothover'); + nodeJQ.off('mouseleave'); + }, []); + useEffect(() => { setChart( seriesList.list.map((series: Series, seriesIndex: number) => { @@ -209,12 +217,12 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp () => buildOptions( defaultOptions, - timefilter, + getServices().timefilter, intervalProp, - uiSettings, - elementRef && elementRef.current && elementRef.current.clientWidth || undefined + getServices().uiSettings, + chartElem && chartElem.clientWidth ), - [defaultOptions, timefilter, intervalProp, uiSettings, elementRef.current] + [defaultOptions, intervalProp, chartElem] ); const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); @@ -224,7 +232,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp setPlot($.plot(canvasElem, compact(updatedSeries), options)); }, [canvasElem, options, updatedSeries]); - moment.tz.setDefault(uiSettings.get('dateFormat:tz')); + moment.tz.setDefault(getServices().uiSettings.get('dateFormat:tz')); const unhighlightSeries = useCallback(() => { if (highlightedSeries === null) { @@ -333,9 +341,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }); }, []); - useEventListener(elementRef, 'plothover', plothoverHandler); - useEventListener(elementRef, 'plotselected', plotselectedHandler); - useEventListener(elementRef, 'mouseleave', mouseleaveHandler); + // const cancelResize = observeResize($elem, function() { // drawPlot($scope.chart); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts deleted file mode 100644 index bae8187327694..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/useEventListener.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { useEffect } from 'react'; - -export const useEventListener = (target: any, type: any, listener: any) => { - useEffect(() => { - let currentTarget: any; - if (target) { - const targetIsRef = target.hasOwnProperty('current'); - currentTarget = targetIsRef ? target.current : target; - if (currentTarget) { - currentTarget.addEventListener(type, listener); - } - } - - return () => { - if (currentTarget) { - currentTarget.removeEventListener(type, listener); - } - }; - }, [target, type, listener]); -}; From a94441cb13596980ab482e776a28f258cd352b79 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 19 Dec 2019 17:03:31 +0300 Subject: [PATCH 19/59] Fix plothover handler --- .../vis_type_timelion/public/panels/panel.tsx | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index 3fdd8475dede2..6e0ac6f7b81e6 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useRef, useState, useEffect, useMemo, useCallback } from 'react'; +import React, { useState, useEffect, useMemo, useCallback } from 'react'; import $ from 'jquery'; import moment from 'moment-timezone'; import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; @@ -60,6 +60,8 @@ interface PanelProps { renderComplete(): void; } +const SERIES_ID_ATTR = 'data-series-id'; + function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProps) { console.log('Panel'); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); @@ -80,10 +82,6 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp const elementRef = useCallback(node => { if (node !== null) { - const nodeJQ = $(node); - nodeJQ.on('plotselected', plotselectedHandler); - nodeJQ.on('plothover', plothoverHandler); - nodeJQ.on('mouseleave', mouseleaveHandler); setChartElem(node); } }, []); @@ -124,7 +122,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp canvasNode.find('div.legend table').append(caption); - setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + setLegendValueNumbers(canvasNode.find('.legendValueNumber')); // legend has been re-created. Apply focus on legend element when previously set if (focusedSeries || focusedSeries === 0) { const $legendLabels = canvasNode.find('div.legend table .legendLabel>span'); @@ -134,7 +132,8 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }, [plot, focusedSeries, canvasElem]); const highlightSeries = useCallback( - debounce((id: number) => { + debounce((event: JQuery.Event) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); if (highlightedSeries === id) { return; } @@ -156,15 +155,17 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp ); const focusSeries = useCallback( - (id: number) => { + (event: JQuery.Event) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); setFocusedSeries(id); - highlightSeries(id); + highlightSeries(event); }, [highlightSeries] ); const toggleSeries = useCallback( - (id: number) => { + ({ currentTarget }: JQuery.Event) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); setChart( chart.map((series: Series, seriesIndex: number) => { return seriesIndex === id ? { ...series, _hide: !series._hide } : { ...series }; @@ -195,13 +196,11 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp const labelSpan = document.createElement('span'); const numberSpan = document.createElement('span'); - wrapperSpan.setAttribute('class', 'ngLegendValue'); - wrapperSpan.addEventListener('click', () => toggleSeries(series._id)); - wrapperSpan.addEventListener('onFocus', () => focusSeries(series._id)); - wrapperSpan.addEventListener('onMouseOver', () => highlightSeries(series._id)); + wrapperSpan.setAttribute('class', 'legendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, series._id); labelSpan.appendChild(document.createTextNode(label)); - numberSpan.setAttribute('class', 'ngLegendValueNumber'); + numberSpan.setAttribute('class', 'legendValueNumber'); wrapperSpan.appendChild(labelSpan); wrapperSpan.appendChild(numberSpan); @@ -210,7 +209,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }, }, }), - [seriesList.render.grid, toggleSeries, focusSeries, highlightSeries] + [seriesList.render.grid] ); const options = useMemo( @@ -228,9 +227,17 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); useEffect(() => { - // @ts-ignore - setPlot($.plot(canvasElem, compact(updatedSeries), options)); - }, [canvasElem, options, updatedSeries]); + if (canvasElem) { + // @ts-ignore + setPlot($.plot(canvasElem, compact(updatedSeries), options)); + const legend = $(canvasElem).find('.legendValue'); + if (legend) { + legend.click(toggleSeries); + legend.focus(focusSeries); + legend.mouseover(highlightSeries); + } + } + }, [canvasElem, options, updatedSeries, toggleSeries, focusSeries, highlightSeries]); moment.tz.setDefault(getServices().uiSettings.get('dateFormat:tz')); @@ -318,12 +325,12 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }, [legendCaption, legendValueNumbers]); const plothoverHandler = useCallback( - (event: any, pos: any, item: any) => { + (event: any, pos: any) => { if (!plot) { return; } - plot.setCrosshair(item); - debouncedSetLegendNumbers(item); + plot.setCrosshair(pos); + debouncedSetLegendNumbers(pos); }, [plot, debouncedSetLegendNumbers] ); @@ -342,6 +349,15 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }, []); + useEffect(() => { + if (chartElem) { + const nodeJQ = $(chartElem); + nodeJQ.off('plotselected').off('plothover').off('mouseleave'); + nodeJQ.on('plotselected', plotselectedHandler); + nodeJQ.on('plothover', plothoverHandler); + nodeJQ.on('mouseleave', mouseleaveHandler); + } + }, [plotselectedHandler, plothoverHandler, mouseleaveHandler]) // const cancelResize = observeResize($elem, function() { // drawPlot($scope.chart); From c942bbb3a34c86cb60d6ad4d71a18fc151f3223d Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 19 Dec 2019 17:56:25 +0300 Subject: [PATCH 20/59] Add TS for options --- .../public/panels/constants.ts | 53 -------- .../vis_type_timelion/public/panels/panel.tsx | 116 ++++++----------- .../vis_type_timelion/public/panels/utils.ts | 122 ++++++++++++++++-- 3 files changed, 149 insertions(+), 142 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts deleted file mode 100644 index 9793e70fd7cfc..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/constants.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const DEBOUNCE_DELAY = 50; -// ensure legend is the same height with or without a caption so legend items do not move around -const emptyCaption = '
'; - -const staticDefaultOptions = { - xaxis: { - mode: 'time', - tickLength: 5, - timezone: 'browser', - }, - selection: { - mode: 'x', - color: '#ccc', - }, - crosshair: { - mode: 'x', - color: '#C66', - lineWidth: 2, - }, - colors: [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', - ], -}; - -export { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index 6e0ac6f7b81e6..eab290a9f0757 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -28,8 +28,7 @@ import './timechart/flot'; import { DEFAULT_TIME_FORMAT } from '../../common/lib'; import { getServices } from '../kibana_services'; -import { DEBOUNCE_DELAY, emptyCaption, staticDefaultOptions } from './constants'; -import { buildSeriesData, buildOptions } from './utils'; +import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from './utils'; export interface Series { _global?: boolean; @@ -49,7 +48,7 @@ interface SeriesList { list: Series[]; render: { type: string; - grid?: unknown; + grid?: boolean; }; type: string; } @@ -60,7 +59,9 @@ interface PanelProps { renderComplete(): void; } -const SERIES_ID_ATTR = 'data-series-id'; +const DEBOUNCE_DELAY = 50; +// ensure legend is the same height with or without a caption so legend items do not move around +const emptyCaption = '
'; function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProps) { console.log('Panel'); @@ -86,20 +87,23 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp } }, []); - useEffect(() => () => { - const nodeJQ = $(chartElem); - nodeJQ.off('plotselected'); - nodeJQ.off('plothover'); - nodeJQ.off('mouseleave'); - }, []); + useEffect( + () => () => { + const nodeJQ = $(chartElem); + nodeJQ.off('plotselected'); + nodeJQ.off('plothover'); + nodeJQ.off('mouseleave'); + }, + [chartElem] + ); useEffect(() => { setChart( seriesList.list.map((series: Series, seriesIndex: number) => { const newSeries = { ...series }; if (!newSeries.color) { - const colorIndex = seriesIndex % staticDefaultOptions.colors.length; - newSeries.color = staticDefaultOptions.colors[colorIndex]; + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; } // setting originalColorMap setOriginalColorMap(stateMap => new Map(stateMap.set(newSeries, newSeries.color))); @@ -132,8 +136,8 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }, [plot, focusedSeries, canvasElem]); const highlightSeries = useCallback( - debounce((event: JQuery.Event) => { - const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); + debounce(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); if (highlightedSeries === id) { return; } @@ -149,13 +153,12 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp return { ...series, color }; }) ); - // drawPlot(chart); }, DEBOUNCE_DELAY), [highlightedSeries, chart, originalColorMap] ); const focusSeries = useCallback( - (event: JQuery.Event) => { + (event: JQuery.TriggeredEvent) => { const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); setFocusedSeries(id); highlightSeries(event); @@ -164,64 +167,27 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp ); const toggleSeries = useCallback( - ({ currentTarget }: JQuery.Event) => { + ({ currentTarget }: JQuery.TriggeredEvent) => { const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); setChart( chart.map((series: Series, seriesIndex: number) => { return seriesIndex === id ? { ...series, _hide: !series._hide } : { ...series }; }) ); - // drawPlot(chart); }, [chart] ); - const defaultOptions = useMemo( - () => ({ - ...staticDefaultOptions, - grid: { - show: seriesList.render.grid, - borderWidth: 0, - borderColor: null, - margin: 10, - hoverable: true, - autoHighlight: false, - }, - legend: { - backgroundColor: 'rgb(255,255,255,0)', - position: 'nw', - labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: any, series: any) { - const wrapperSpan = document.createElement('span'); - const labelSpan = document.createElement('span'); - const numberSpan = document.createElement('span'); - - wrapperSpan.setAttribute('class', 'legendValue'); - wrapperSpan.setAttribute(SERIES_ID_ATTR, series._id); - - labelSpan.appendChild(document.createTextNode(label)); - numberSpan.setAttribute('class', 'legendValueNumber'); - - wrapperSpan.appendChild(labelSpan); - wrapperSpan.appendChild(numberSpan); - - return wrapperSpan.outerHTML; - }, - }, - }), - [seriesList.render.grid] - ); - const options = useMemo( () => buildOptions( - defaultOptions, getServices().timefilter, intervalProp, getServices().uiSettings, - chartElem && chartElem.clientWidth + chartElem && chartElem.clientWidth, + seriesList.render.grid ), - [defaultOptions, intervalProp, chartElem] + [seriesList.render.grid, intervalProp, chartElem] ); const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); @@ -253,7 +219,6 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp return { ...series, color: originalColorMap.get(series) }; // reset the colors }) ); - // drawPlot(chart); }, [chart, originalColorMap, highlightedSeries]); // Shamelessly borrowed from the flotCrosshairs example @@ -324,7 +289,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }); }, [legendCaption, legendValueNumbers]); - const plothoverHandler = useCallback( + const plotHoverHandler = useCallback( (event: any, pos: any) => { if (!plot) { return; @@ -334,41 +299,34 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }, [plot, debouncedSetLegendNumbers] ); - const mouseleaveHandler = useCallback(() => { + const mouseLeaveHandler = useCallback(() => { if (!plot) { return; } plot.clearCrosshair(); clearLegendNumbers(); }, [plot, clearLegendNumbers]); - const plotselectedHandler = useCallback((event: any, ranges: any) => { + + const plotSelectedHandler = useCallback((event: any, ranges: any) => { getServices().timefilter.setTime({ from: moment(ranges.xaxis.from), to: moment(ranges.xaxis.to), }); }, []); - useEffect(() => { if (chartElem) { - const nodeJQ = $(chartElem); - nodeJQ.off('plotselected').off('plothover').off('mouseleave'); - nodeJQ.on('plotselected', plotselectedHandler); - nodeJQ.on('plothover', plothoverHandler); - nodeJQ.on('mouseleave', mouseleaveHandler); + const $chart = $(chartElem); + $chart + .off('plotselected') + .off('plothover') + .off('mouseleave'); + $chart + .on('plotselected', plotSelectedHandler) + .on('plothover', plotHoverHandler) + .on('mouseleave', mouseLeaveHandler); } - }, [plotselectedHandler, plothoverHandler, mouseleaveHandler]) - - // const cancelResize = observeResize($elem, function() { - // drawPlot($scope.chart); - // }); - - // $scope.$on('$destroy', function() { - // cancelResize(); - // }); - - // we can't use `$.plot` to draw the chart when the height or width is 0 - // so, we'll need another event to trigger drawPlot to actually draw it + }, [chartElem, plotSelectedHandler, plotHoverHandler, mouseLeaveHandler]); const title: string = last(compact(map(chart, '_title'))) || ''; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts index 7946b89ec4c28..ac27c47cf8afd 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts @@ -20,6 +20,9 @@ import { cloneDeep, defaults, merge } from 'lodash'; import moment from 'moment-timezone'; +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; + // @ts-ignore import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../common/lib'; import { tickFormatters } from '../services/tick_formatters'; @@ -65,8 +68,63 @@ function buildSeriesData(chart: Series[], options: object) { }); } -function buildOptions(defaultOptions: any, timefilter: any, intervalValue: string, uiSettings: any, clientWidth = 0) { - const options = cloneDeep(defaultOptions); +const SERIES_ID_ATTR = 'data-series-id'; + +interface IOptions { + xaxis: { + mode: string; + tickLength: number; + timezone: string; + ticks: number; + tickFormatter(val: number): string; + }; + yaxes?: []; + selection: { + mode: string; + color: string; + }; + crosshair: { + mode: string; + color: string; + lineWidth: number; + }; + colors: string[]; + grid: { + show?: boolean; + borderWidth: number; + borderColor: string | null; + margin: number; + hoverable: boolean; + autoHighlight: boolean; + }; + legend: { + backgroundColor: string; + position: string; + labelBoxBorderColor: string; + labelFormatter(label: string, series: any): string; + }; +} + +const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; + +function buildOptions( + timefilter: TimefilterContract, + intervalValue: string, + uiSettings: IUiSettingsClient, + clientWidth = 0, + showGrid?: boolean +) { // Get the X-axis tick format const time = timefilter.getBounds() as any; const interval = calculateInterval( @@ -78,15 +136,59 @@ function buildOptions(defaultOptions: any, timefilter: any, intervalValue: strin ); const format = xaxisFormatterProvider(uiSettings)(interval); - // Use moment to format ticks so we get timezone correction - options.xaxis.tickFormatter = (val: any) => moment(val).format(format); - - // Calculate how many ticks can fit on the axis const tickLetterWidth = 7; const tickPadding = 45; - options.xaxis.ticks = Math.floor( - clientWidth / (format.length * tickLetterWidth + tickPadding) - ); + + const options: IOptions = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + // Calculate how many ticks can fit on the axis + ticks: Math.floor(clientWidth / (format.length * tickLetterWidth + tickPadding)), + // Use moment to format ticks so we get timezone correction + tickFormatter: (val: number) => moment(val).format(format), + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + colors, + grid: { + show: showGrid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: string, series: any) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'legendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, series._id); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'legendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + }; if (options.yaxes) { options.yaxes.forEach((yaxis: any) => { @@ -104,4 +206,4 @@ function buildOptions(defaultOptions: any, timefilter: any, intervalValue: strin return options; } -export { buildSeriesData, buildOptions}; +export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; From 6c9ebfd1a0da26060eba3c103d970cc2852dd281 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 21 Dec 2019 15:29:42 +0300 Subject: [PATCH 21/59] Update TS --- .../core_plugins/vis_type_timelion/index.ts | 2 +- .../public/components/chart.tsx | 7 +- .../public/components/timelion_vis.tsx | 17 +- .../helpers/timelion_request_handler.ts | 29 +- .../public/kibana_services.ts | 13 +- .../vis_type_timelion/public/legacy.ts | 6 +- .../public/lib/_tests_/calculate_interval.js | 63 --- .../public/lib/observe_resize.js | 47 -- .../vis_type_timelion/public/panels/panel.tsx | 38 +- .../public/panels/timechart/schema.ts | 400 ------------------ .../public/panels/timechart/timechart.tsx | 9 +- .../vis_type_timelion/public/panels/utils.ts | 2 +- .../vis_type_timelion/public/plugin.ts | 41 +- .../public/services/_saved_sheet.ts | 79 ---- .../public/services/saved_sheet_register.ts | 24 -- .../public/services/saved_sheets.ts | 61 --- .../public/services/tick_formatters.ts | 4 +- .../public/timelion_vis_fn.ts | 16 +- .../public/timelion_vis_type.ts | 6 +- 19 files changed, 102 insertions(+), 762 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts index 4c0ce26e2cf86..4beba9627ed85 100644 --- a/src/legacy/core_plugins/vis_type_timelion/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -24,7 +24,7 @@ import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => new Plugin({ id: 'timelion_vis', - require: ['kibana', 'elasticsearch'], + require: ['kibana', 'elasticsearch', 'visualizations', 'data'], publicDir: resolve(__dirname, 'public'), uiExports: { // styleSheetPaths: resolve(__dirname, 'public/index.scss'), diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index 17fecf098c56b..2614ac6b87552 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -22,13 +22,14 @@ import { EuiFormErrorText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { getServices } from '../kibana_services'; +import { Sheet } from '../helpers/timelion_request_handler'; interface ChartComponentProp { className?: string; - seriesList: any; + interval: string; + renderComplete(): void; + seriesList: Sheet; search?(): void; - interval: any; - renderComplete: boolean; } function ChartComponent({ seriesList, interval, search, renderComplete }: ChartComponentProp) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx index c3b5676d066e8..872479e527ee1 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -19,14 +19,27 @@ import React from 'react'; +import { IUiSettingsClient } from 'kibana/public'; +import { Vis } from 'ui/vis'; import { ChartComponent } from './chart'; +import { VisParams } from '../timelion_vis_fn'; +import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; -function TimelionVisComponent(props: any) { +interface TimelionVisComponentProp { + config: IUiSettingsClient; + renderComplete: () => void; + updateStatus: object; + vis: Vis; + visData: TimelionSuccessResponse; + visParams: VisParams; +} + +function TimelionVisComponent(props: TimelionVisComponentProp) { return (
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 269923d37d8d8..1acb191c4a95d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -19,9 +19,9 @@ import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; import { i18n } from '@kbn/i18n'; -import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; import { timezoneProvider, VisParams } from '../legacy_imports'; +import { getServices } from '../kibana_services'; interface Stats { cacheCount: number; @@ -31,9 +31,26 @@ interface Stats { sheetTime: number; } -interface Sheet { - list: Array>; - render: Record; +export interface Series { + _global?: boolean; + _hide?: boolean; + _id?: number; + _title?: string; + color?: string; + data: Array>; + fit: string; + label: string; + split: string; + stack?: boolean; + type: string; +} + +export interface Sheet { + list: Series[]; + render: { + type: string; + grid?: boolean; + }; type: string; } @@ -44,8 +61,8 @@ export interface TimelionSuccessResponse { type: KIBANA_CONTEXT_NAME; } -export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; +export function getTimelionRequestHandler() { + const { uiSettings, http, timefilter } = getServices(); const timezone = timezoneProvider(uiSettings)(); return async function({ diff --git a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts index 3963ea9dd15d4..5495612ba22aa 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts @@ -17,12 +17,15 @@ * under the License. */ -import { Panel } from './panels/panel'; +import { IUiSettingsClient, HttpSetup } from 'kibana/public'; +import { TimefilterContract } from 'src/plugins/data/public'; +import { IPanelWrapper } from './panels/timechart/timechart'; export interface TimelionKibanaServices { - timelionPanels: Map; - uiSettings: any; - timefilter: any; + http: HttpSetup; + timelionPanels: Map; + uiSettings: IUiSettingsClient; + timefilter: TimefilterContract; } let services: TimelionKibanaServices | null = null; @@ -33,7 +36,7 @@ export function setServices(newServices: TimelionKibanaServices) { export function getServices() { if (!services) { throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the timelion app?' + 'Kibana services not set - are you trying to import this module from outside of the timelion vis?' ); } return services; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts index 7d171ecccf2f7..4b2e03a8e35bc 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -21,10 +21,10 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; -import { TimelionPluginSetupDependencies } from './plugin'; +import { TimelionVisSetupDependencies } from './plugin'; import { plugin } from '.'; -const plugins: Readonly = { +const setupPlugins: Readonly = { expressions: npSetup.plugins.expressions, data: npSetup.plugins.data, visualizations: visualizationsSetup, @@ -32,5 +32,5 @@ const plugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, plugins); +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js deleted file mode 100644 index 929e6b44602a8..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/lib/_tests_/calculate_interval.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const filename = require('path').basename(__filename); -const fn = require(`../calculate_interval`); -const moment = require('moment'); -const expect = require('chai').expect; - - -const from = (count, unit) => moment().subtract(count, unit).valueOf(); -const to = moment().valueOf(); -const size = 200; -const min = '1ms'; - -describe(filename, () => { - it('Exports a function', () => { - expect(fn).to.be.a('function'); - }); - - it('Only calculates when interval = auto', () => { - const partialFn = (interval) => fn(from(1, 'y'), to, size, interval, min); - expect(partialFn('1ms')).to.equal('1ms'); - expect(partialFn('bag_of_beans')).to.equal('bag_of_beans'); - expect(partialFn('auto')).to.not.equal('auto'); - }); - - it('Calculates nice round intervals', () => { - const partialFn = (count, unit) => fn(from(count, unit), to, size, 'auto', min); - expect(partialFn(15, 'm')).to.equal('1s'); - expect(partialFn(1, 'h')).to.equal('30s'); - expect(partialFn(3, 'd')).to.equal('30m'); - expect(partialFn(1, 'w')).to.equal('1h'); - expect(partialFn(1, 'y')).to.equal('24h'); - expect(partialFn(100, 'y')).to.equal('1y'); - }); - - it('Does not calculate an interval lower than the minimum', () => { - const partialFn = (count, unit) => fn(from(count, unit), to, size, 'auto', '1m'); - expect(partialFn(5, 's')).to.equal('1m'); - expect(partialFn(15, 'm')).to.equal('1m'); - expect(partialFn(1, 'h')).to.equal('1m'); - expect(partialFn(3, 'd')).to.equal('30m'); - expect(partialFn(1, 'w')).to.equal('1h'); - expect(partialFn(1, 'y')).to.equal('24h'); - expect(partialFn(100, 'y')).to.equal('1y'); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js b/src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js deleted file mode 100644 index d19ffe69c2110..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/lib/observe_resize.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default function ($elem, fn, frequency) { - - frequency = frequency || 500; - let currentHeight = $elem.height(); - let currentWidth = $elem.width(); - - let timeout; - - function checkLoop() { - timeout = setTimeout(function () { - if (currentHeight !== $elem.height() || currentWidth !== $elem.width()) { - currentHeight = $elem.height(); - currentWidth = $elem.width(); - - if (currentWidth > 0 && currentWidth > 0) fn(); - } - checkLoop(); - }, frequency); - } - - checkLoop(); - - return function () { - clearTimeout(timeout); - }; - - -} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx index eab290a9f0757..04b6bfd57fe83 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx @@ -29,33 +29,12 @@ import { DEFAULT_TIME_FORMAT } from '../../common/lib'; import { getServices } from '../kibana_services'; import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from './utils'; +import { Series, Sheet } from '../helpers/timelion_request_handler'; -export interface Series { - _global?: boolean; - _hide?: boolean; - _id?: number; - _title?: string; - color?: string; - data: any[]; - fit: string; - label: string; - split: string; - stack?: boolean; - type: string; -} - -interface SeriesList { - list: Series[]; - render: { - type: string; - grid?: boolean; - }; - type: string; -} interface PanelProps { name: string; interval: string; - seriesList: SeriesList; + seriesList: Sheet; renderComplete(): void; } @@ -64,7 +43,6 @@ const DEBOUNCE_DELAY = 50; const emptyCaption = '
'; function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProps) { - console.log('Panel'); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const [canvasElem, setCanvasElem] = useState(); const [chartElem, setChartElem] = useState(); @@ -196,6 +174,8 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp if (canvasElem) { // @ts-ignore setPlot($.plot(canvasElem, compact(updatedSeries), options)); + renderComplete(); + const legend = $(canvasElem).find('.legendValue'); if (legend) { legend.click(toggleSeries); @@ -203,7 +183,15 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp legend.mouseover(highlightSeries); } } - }, [canvasElem, options, updatedSeries, toggleSeries, focusSeries, highlightSeries]); + }, [ + canvasElem, + options, + updatedSeries, + toggleSeries, + focusSeries, + highlightSeries, + renderComplete, + ]); moment.tz.setDefault(getServices().uiSettings.get('dateFormat:tz')); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts deleted file mode 100644 index afe28deed3d1a..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/schema.ts +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import './flot'; -import _ from 'lodash'; -import $ from 'jquery'; -import moment from 'moment-timezone'; -import { timefilter } from 'ui/timefilter'; -// @ts-ignore -import observeResize from '../../lib/observe_resize'; -// @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; -import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../services/tick_formatters'; -import { xaxisFormatterProvider } from './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; - -const DEBOUNCE_DELAY = 50; - -export function timechartFn(dependencies: TimelionVisualizationDependencies) { - // @ts-ignore - const { $rootScope, $compile, uiSettings } = dependencies; - return function() { - return { - help: 'Draw a timeseries chart', - render($scope: any, $elem: any) { - const template = '
'; - const formatters = tickFormatters() as any; - const getxAxisFormatter = xaxisFormatterProvider(uiSettings); - const generateTicks = generateTicksProvider(); - - // TODO: I wonder if we should supply our own moment that sets this every time? - // could just use angular's injection to provide a moment service? - moment.tz.setDefault(uiSettings.get('dateFormat:tz')); - - const render = $scope.seriesList.render || {}; - - $scope.chart = $scope.seriesList.list; - $scope.interval = $scope.interval; - $scope.search = $scope.search || _.noop; - - let legendValueNumbers: any; - let legendCaption: any; - const debouncedSetLegendNumbers = _.debounce(setLegendNumbers, DEBOUNCE_DELAY, { - maxWait: DEBOUNCE_DELAY, - leading: true, - trailing: false, - }); - // ensure legend is the same height with or without a caption so legend items do not move around - const emptyCaption = '
'; - - const defaultOptions = { - xaxis: { - mode: 'time', - tickLength: 5, - timezone: 'browser', - }, - selection: { - mode: 'x', - color: '#ccc', - }, - crosshair: { - mode: 'x', - color: '#C66', - lineWidth: 2, - }, - grid: { - show: render.grid, - borderWidth: 0, - borderColor: null, - margin: 10, - hoverable: true, - autoHighlight: false, - }, - legend: { - backgroundColor: 'rgb(255,255,255,0)', - position: 'nw', - labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: any, series: any) { - const wrapperSpan = document.createElement('span'); - const labelSpan = document.createElement('span'); - const numberSpan = document.createElement('span'); - - wrapperSpan.setAttribute('class', 'ngLegendValue'); - wrapperSpan.setAttribute('kbn-accessible-click', ''); - wrapperSpan.setAttribute('ng-click', `toggleSeries(${series._id})`); - wrapperSpan.setAttribute('ng-focus', `focusSeries(${series._id})`); - wrapperSpan.setAttribute('ng-mouseover', `highlightSeries(${series._id})`); - - labelSpan.setAttribute('ng-non-bindable', ''); - labelSpan.appendChild(document.createTextNode(label)); - numberSpan.setAttribute('class', 'ngLegendValueNumber'); - - wrapperSpan.appendChild(labelSpan); - wrapperSpan.appendChild(numberSpan); - - return wrapperSpan.outerHTML; - }, - }, - colors: [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', - ], - }; - - const originalColorMap = new Map(); - $scope.chart.forEach((series: any, seriesIndex: any) => { - if (!series.color) { - const colorIndex = seriesIndex % defaultOptions.colors.length; - series.color = defaultOptions.colors[colorIndex]; - } - originalColorMap.set(series, series.color); - }); - - let highlightedSeries: any; - let focusedSeries: any; - function unhighlightSeries() { - if (highlightedSeries === null) { - return; - } - - highlightedSeries = null; - focusedSeries = null; - $scope.chart.forEach((series: any) => { - series.color = originalColorMap.get(series); // reset the colors - }); - drawPlot($scope.chart); - } - $scope.highlightSeries = _.debounce(function(id: any) { - if (highlightedSeries === id) { - return; - } - - highlightedSeries = id; - $scope.chart.forEach((series: any, seriesIndex: any) => { - if (seriesIndex !== id) { - series.color = 'rgba(128,128,128,0.1)'; // mark as grey - } else { - series.color = originalColorMap.get(series); // color it like it was - } - }); - drawPlot($scope.chart); - }, DEBOUNCE_DELAY); - $scope.focusSeries = function(id: any) { - focusedSeries = id; - $scope.highlightSeries(id); - }; - - $scope.toggleSeries = function(id: any) { - const series = $scope.chart[id]; - series._hide = !series._hide; - drawPlot($scope.chart); - }; - - const cancelResize = observeResize($elem, function() { - drawPlot($scope.chart); - }); - - $scope.$on('$destroy', function() { - cancelResize(); - $elem.off('plothover'); - $elem.off('plotselected'); - $elem.off('mouseleave'); - }); - - $elem.on('plothover', function(event: any, pos: any, item: any) { - $rootScope.$broadcast('timelionPlotHover', event, pos, item); - }); - - $elem.on('plotselected', function(event: any, ranges: any) { - timefilter.setTime({ - from: moment(ranges.xaxis.from), - to: moment(ranges.xaxis.to), - }); - }); - - $elem.on('mouseleave', function() { - $rootScope.$broadcast('timelionPlotLeave'); - }); - - $scope.$on('timelionPlotHover', function(angularEvent: any, flotEvent: any, pos: any) { - if (!$scope.plot) return; - $scope.plot.setCrosshair(pos); - debouncedSetLegendNumbers(pos); - }); - - $scope.$on('timelionPlotLeave', function() { - if (!$scope.plot) return; - $scope.plot.clearCrosshair(); - clearLegendNumbers(); - }); - - // Shamelessly borrowed from the flotCrosshairs example - function setLegendNumbers(pos: any) { - unhighlightSeries(); - - const plot = $scope.plot; - - const axes = plot.getAxes(); - if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { - return; - } - - let i; - const dataset = plot.getData(); - if (legendCaption) { - legendCaption.text( - moment(pos.x).format( - _.get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT) - ) - ); - } - for (i = 0; i < dataset.length; ++i) { - const series = dataset[i]; - const useNearestPoint = series.lines.show && !series.lines.steps; - const precision = _.get(series, '_meta.precision', 2); - - if (series._hide) continue; - - const currentPoint = series.data.find((point: any, index: number) => { - if (index + 1 === series.data.length) { - return true; - } - if (useNearestPoint) { - return pos.x - point[0] < series.data[index + 1][0] - pos.x; - } else { - return pos.x < series.data[index + 1][0]; - } - }); - - const y = currentPoint[1]; - - if (y != null) { - let label = y.toFixed(precision); - if (series.yaxis.tickFormatter) { - label = series.yaxis.tickFormatter(label, series.yaxis); - } - legendValueNumbers.eq(i).text(`(${label})`); - } else { - legendValueNumbers.eq(i).empty(); - } - } - } - - function clearLegendNumbers() { - if (legendCaption) { - legendCaption.html(emptyCaption); - } - _.each(legendValueNumbers, function(num) { - $(num).empty(); - }); - } - - let legendScope = $scope.$new(); - function drawPlot(plotConfig: any) { - if (!$('.chart-canvas', $elem).length) $elem.html(template); - const canvasElem = $('.chart-canvas', $elem); - - // we can't use `$.plot` to draw the chart when the height or width is 0 - // so, we'll need another event to trigger drawPlot to actually draw it - if (canvasElem.height() === 0 || canvasElem.width() === 0) { - return; - } - - const title = _(plotConfig) - .map('_title') - .compact() - .last() as any; - $('.chart-top-title', $elem).text(title == null ? '' : title); - - const options = _.cloneDeep(defaultOptions) as any; - - // Get the X-axis tick format - const time = timefilter.getBounds() as any; - const interval = calculateInterval( - time.min.valueOf(), - time.max.valueOf(), - uiSettings.get('timelion:target_buckets') || 200, - $scope.interval, - uiSettings.get('timelion:min_interval') || '1ms' - ); - const format = getxAxisFormatter(interval); - - // Use moment to format ticks so we get timezone correction - options.xaxis.tickFormatter = function(val: any) { - return moment(val).format(format); - }; - - // Calculate how many ticks can fit on the axis - const tickLetterWidth = 7; - const tickPadding = 45; - options.xaxis.ticks = Math.floor( - $elem.width() / (format.length * tickLetterWidth + tickPadding) - ); - - const series = _.map(plotConfig, function(serie: any, index) { - serie = _.cloneDeep( - _.defaults(serie, { - shadowSize: 0, - lines: { - lineWidth: 3, - }, - }) - ); - serie._id = index; - - if (serie.color) { - const span = document.createElement('span'); - span.style.color = serie.color; - serie.color = span.style.color; - } - - if (serie._hide) { - serie.data = []; - serie.stack = false; - // serie.color = "#ddd"; - serie.label = '(hidden) ' + serie.label; - } - - if (serie._global) { - _.merge(options, serie._global, function(objVal, srcVal) { - // This is kind of gross, it means that you can't replace a global value with a null - // best you can do is an empty string. Deal with it. - if (objVal == null) return srcVal; - if (srcVal == null) return objVal; - }); - } - - return serie; - }); - - if (options.yaxes) { - options.yaxes.forEach((yaxis: any) => { - if (yaxis && yaxis.units) { - yaxis.tickFormatter = formatters[yaxis.units.type]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicks; - } - } - }); - } - - // @ts-ignore - $scope.plot = $.plot(canvasElem, _.compact(series), options); - - if ($scope.plot) { - $scope.$emit('timelionChartRendered'); - } - - legendScope.$destroy(); - legendScope = $scope.$new(); - // Used to toggle the series, and for displaying values on hover - legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); - _.each(canvasElem.find('.ngLegendValue'), function(elem) { - $compile(elem)(legendScope); - }); - - if (_.get($scope.plot.getData(), '[0]._global.legend.showTime', true)) { - legendCaption = $(''); - legendCaption.html(emptyCaption); - canvasElem.find('div.legend table').append(legendCaption); - - // legend has been re-created. Apply focus on legend element when previously set - if (focusedSeries || focusedSeries === 0) { - const $legendLabels = canvasElem.find('div.legend table .legendLabel>span'); - $legendLabels.get(focusedSeries).focus(); - } - } - } - $scope.$watch('chart', drawPlot); - }, - }; - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx index e51c6b3520108..af5124d36b3e2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx @@ -17,14 +17,13 @@ * under the License. */ -import React from 'react'; +import React, { ReactElement } from 'react'; import { Panel } from '../panel'; -import { TimelionVisualizationDependencies } from '../../plugin'; -function getTimeChart(dependencies: TimelionVisualizationDependencies) { - // Schema is broken out so that it may be extended for use in other plugins - // Its also easier to test. +export type IPanelWrapper = (props: any) => ReactElement; + +function getTimeChart(): [string, IPanelWrapper] { return ['timechart', (props: any) => ]; } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts index ac27c47cf8afd..212625edd2761 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts @@ -28,7 +28,7 @@ import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../common/lib'; import { tickFormatters } from '../services/tick_formatters'; import { xaxisFormatterProvider } from './timechart/xaxis_formatter'; import { generateTicksProvider } from './timechart/tick_generator'; -import { Series } from './panel'; +import { Series } from '../helpers/timelion_request_handler'; function buildSeriesData(chart: Series[], options: object) { return chart.map((series: Series, seriesIndex: number) => { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 5d1ee63d96677..b3db4bf095244 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -28,15 +28,16 @@ import { import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; -import { getTimeChart } from './panels/timechart/timechart'; -import { Panel } from './panels/panel'; +import { getTimeChart, IPanelWrapper } from './panels/timechart/timechart'; import { setServices } from './kibana_services'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; +type TimelionVisCoreSetup = CoreSetup; + /** @internal */ -export interface TimelionVisualizationDependencies { +export interface TimelionVisDependencies { uiSettings: IUiSettingsClient; http: HttpSetup; timelionPanels: Map; @@ -44,7 +45,7 @@ export interface TimelionVisualizationDependencies { } /** @internal */ -export interface TimelionPluginSetupDependencies { +export interface TimelionVisSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; data: DataPublicPluginSetup; @@ -52,19 +53,15 @@ export interface TimelionPluginSetupDependencies { /** @internal */ export class TimelionVisPlugin implements Plugin { - initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } + constructor(public initializerContext: PluginInitializerContext) {} public async setup( - core: CoreSetup, - { expressions, visualizations, data }: TimelionPluginSetupDependencies + core: TimelionVisCoreSetup, + { expressions, visualizations, data }: TimelionVisSetupDependencies ) { - const timelionPanels: Map = new Map(); + const timelionPanels: Map = new Map(); - const dependencies: TimelionVisualizationDependencies = { + const dependencies: TimelionVisDependencies = { uiSettings: core.uiSettings, http: core.http, timelionPanels, @@ -72,23 +69,19 @@ export class TimelionVisPlugin implements Plugin { }; setServices(dependencies); - this.registerPanels(dependencies); + this.registerPanels(timelionPanels); - expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); + expressions.registerFunction(getTimelionVisualizationConfig); + visualizations.types.createReactVisualization(getTimelionVisDefinition()); } - private registerPanels(dependencies: TimelionVisualizationDependencies) { - const [name, timeChartPanel] = getTimeChart(dependencies); + private registerPanels(timelionPanels: Map) { + const [name, timeChartPanel] = getTimeChart(); - dependencies.timelionPanels.set(name as string, timeChartPanel); + timelionPanels.set(name, timeChartPanel); } public start(core: CoreStart) { - const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); - - if (timelionUiEnabled === false) { - core.chrome.navLinks.update('timelion', { hidden: true }); - } + // nothing to do here } } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts b/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts deleted file mode 100644 index 1e956cbd3e5ac..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/_saved_sheet.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { createSavedObjectClass } from 'ui/saved_objects/saved_object'; -import { SavedObjectKibanaServices } from 'ui/saved_objects/types'; -import { IUiSettingsClient } from 'kibana/public'; - -// Used only by the savedSheets service, usually no reason to change this -export function createSavedSheetClass( - services: SavedObjectKibanaServices, - config: IUiSettingsClient -) { - const SavedObjectClass = createSavedObjectClass(services); - - class SavedSheet extends SavedObjectClass { - static type = 'timelion-sheet'; - - // if type:sheet has no mapping, we push this mapping into ES - static mapping = { - title: 'text', - hits: 'integer', - description: 'text', - timelion_sheet: 'text', - timelion_interval: 'keyword', - timelion_other_interval: 'keyword', - timelion_chart_height: 'integer', - timelion_columns: 'integer', - timelion_rows: 'integer', - version: 'integer', - }; - - // Order these fields to the top, the rest are alphabetical - static fieldOrder = ['title', 'description']; - // SavedSheet constructor. Usually you'd interact with an instance of this. - // ID is option, without it one will be generated on save. - constructor(id: string) { - super({ - type: SavedSheet.type, - mapping: SavedSheet.mapping, - - // if this is null/undefined then the SavedObject will be assigned the defaults - id, - - // default values that will get assigned if the doc is new - defaults: { - title: 'New TimeLion Sheet', - hits: 0, - description: '', - timelion_sheet: ['.es(*)'], - timelion_interval: 'auto', - timelion_chart_height: 275, - timelion_columns: config.get('timelion:default_columns') || 2, - timelion_rows: config.get('timelion:default_rows') || 2, - version: 1, - }, - }); - this.showInRecentlyAccessed = true; - this.getFullPath = () => `/app/timelion#/${this.id}`; - } - } - - return SavedSheet; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts deleted file mode 100644 index 6bfbacb95b62e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheet_register.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import './saved_sheets'; - -SavedObjectRegistryProvider.register((savedSheets: any) => { - return savedSheets; -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts b/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts deleted file mode 100644 index d851b5a863658..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/saved_sheets.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { npStart } from 'ui/new_platform'; -import { SavedObjectLoader } from 'ui/saved_objects'; -// @ts-ignore -import { savedObjectManagementRegistry } from 'plugins/kibana/management/saved_object_registry'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { createSavedSheetClass } from './_saved_sheet'; - -const module = uiModules.get('app/sheet'); - -// Register this service with the saved object registry so it can be -// edited by the object editor. -savedObjectManagementRegistry.register({ - service: 'savedSheets', - title: 'sheets', -}); - -// This is the only thing that gets injected into controllers -module.service('savedSheets', function() { - const savedObjectsClient = npStart.core.savedObjects.client; - const services = { - savedObjectsClient, - indexPatterns: npStart.plugins.data.indexPatterns, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, - }; - - const SavedSheet = createSavedSheetClass(services, npStart.core.uiSettings); - - const savedSheetLoader = new SavedObjectLoader( - SavedSheet, - savedObjectsClient, - npStart.core.chrome - ); - savedSheetLoader.urlFor = id => `#/${encodeURIComponent(id)}`; - // Customize loader properties since adding an 's' on type doesn't work for type 'timelion-sheet'. - savedSheetLoader.loaderProperties = { - name: 'timelion-sheet', - noun: 'Saved Sheets', - nouns: 'saved sheets', - }; - return savedSheetLoader; -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts index 2c78d2423cc06..84da20da45f3c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts @@ -17,7 +17,7 @@ * under the License. */ -import _ from 'lodash'; +import { get } from 'lodash'; function baseTickFormatter(value: any, axis: any) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; @@ -68,7 +68,7 @@ export function tickFormatters() { }, percent(val: any, axis: any) { let precision = - _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); + get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 if (precision < 0) { precision = 0; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index f8186d4965f2a..7006b3f1e15e1 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -21,7 +21,6 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; -import { TimelionVisualizationDependencies } from './plugin'; import { TIMELION_VIS_NAME } from './timelion_vis_type'; const name = 'timelion_vis'; @@ -31,19 +30,22 @@ interface Arguments { interval: any; } -interface RenderValue { +export interface RenderValue { visData: Context; visType: 'timelion'; visParams: VisParams; } type Context = KibanaContext | null; -type VisParams = Arguments; +export type VisParams = Arguments; type Return = Promise>; -export const getTimelionVisualizationConfig = ( - dependencies: TimelionVisualizationDependencies -): ExpressionFunction => ({ +export const getTimelionVisualizationConfig = (): ExpressionFunction< + typeof name, + Context, + Arguments, + Return +> => ({ name, type: 'render', context: { @@ -66,7 +68,7 @@ export const getTimelionVisualizationConfig = ( }, }, async fn(context, args) { - const timelionRequestHandler = getTimelionRequestHandler(dependencies); + const timelionRequestHandler = getTimelionRequestHandler(); const visParams = { expression: args.expression, interval: args.interval }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts index 141e0d2312c0b..f11a3b2e12ca1 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.ts @@ -19,17 +19,15 @@ import { i18n } from '@kbn/i18n'; -// @ts-ignore import { DefaultEditorSize } from './legacy_imports'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; import { TimelionVisComponent } from './components/timelion_vis'; import editorConfigTemplate from './timelion_vis_params.html'; -import { TimelionVisualizationDependencies } from './plugin'; export const TIMELION_VIS_NAME = 'timelion'; -export function getTimelionVisDefinition(dependencies: TimelionVisualizationDependencies) { - const timelionRequestHandler = getTimelionRequestHandler(dependencies); +export function getTimelionVisDefinition() { + const timelionRequestHandler = getTimelionRequestHandler(); // return the visType object, which kibana will use to display and configure new // Vis object of this type. From 1b96f1153a17a60ae7138e966cbab0065e8f3f3b Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 21 Dec 2019 15:53:18 +0300 Subject: [PATCH 22/59] Restructuring --- .../common/lib/calculate_interval.js | 66 -- .../vis_type_timelion/common/lib/index.js | 21 - .../public/{panels => components}/panel.tsx | 6 +- .../public/{panels/timechart => }/flot.js | 0 .../utils.ts => helpers/panel_utils.ts} | 10 +- .../{services => helpers}/tick_formatters.ts | 0 .../timechart => helpers}/tick_generator.ts | 0 .../timechart => helpers}/xaxis_formatter.ts | 0 .../public/kibana_services.ts | 2 +- .../vis_type_timelion/public/plugin.ts | 2 +- .../{panels/timechart => }/timechart.tsx | 2 +- .../server/fit_functions/__tests__/average.js | 90 --- .../server/fit_functions/__tests__/carry.js | 89 --- .../server/fit_functions/average.js | 92 --- .../server/fit_functions/carry.js | 53 -- .../server/fit_functions/nearest.js | 47 -- .../server/fit_functions/none.js | 25 - .../server/fit_functions/scale.js | 92 --- .../server/handlers/__tests__/parse_sheet.js | 53 -- .../server/handlers/chain_runner.js | 234 ------- .../server/handlers/lib/arg_type.js | 37 -- .../server/handlers/lib/index_arguments.js | 61 -- .../server/handlers/lib/parse_sheet.js | 49 -- .../server/handlers/lib/preprocess_chain.js | 67 -- .../handlers/lib/reposition_arguments.js | 82 --- .../server/handlers/lib/tl_config.js | 45 -- .../server/handlers/lib/validate_arg.js | 54 -- .../server/handlers/lib/validate_time.js | 44 -- .../vis_type_timelion/server/index.ts | 25 - .../server/lib/__tests__/load_functions.js | 42 -- .../vis_type_timelion/server/lib/alter.js | 53 -- .../vis_type_timelion/server/lib/asSorted.js | 26 - .../server/lib/build_target.js | 39 -- .../server/lib/classes/chainable.js | 28 - .../server/lib/classes/datasource.js | 97 --- .../server/lib/classes/timelion_function.d.ts | 53 -- .../server/lib/classes/timelion_function.js | 68 -- .../server/lib/functions_md.js | 94 --- .../server/lib/get_namespaced_settings.js | 48 -- .../server/lib/load_functions.d.ts | 28 - .../server/lib/load_functions.js | 57 -- .../server/lib/offset_time.js | 70 -- .../server/lib/offset_time.test.js | 55 -- .../server/lib/process_function_definition.js | 34 - .../vis_type_timelion/server/lib/reduce.js | 145 ----- .../server/lib/split_interval.js | 30 - .../server/lib/to_milliseconds.js | 58 -- .../server/lib/unzipPairs.js | 33 - .../vis_type_timelion/server/plugin.ts | 62 -- .../server/routes/functions.js | 35 - .../vis_type_timelion/server/routes/index.ts | 32 - .../vis_type_timelion/server/routes/run.ts | 125 ---- .../server/routes/validate_es.js | 74 --- .../server/series_functions/__tests__/abs.js | 43 -- .../series_functions/__tests__/aggregate.js | 74 --- .../server/series_functions/__tests__/bars.js | 57 -- .../series_functions/__tests__/color.js | 78 --- .../series_functions/__tests__/condition.js | 109 ---- .../series_functions/__tests__/cusum.js | 37 -- .../series_functions/__tests__/derivative.js | 37 -- .../series_functions/__tests__/divide.js | 37 -- .../server/series_functions/__tests__/es.js | 611 ------------------ .../series_functions/__tests__/first.js | 32 - .../server/series_functions/__tests__/fit.js | 154 ----- .../__tests__/fixtures/bucketList.js | 27 - .../__tests__/fixtures/es_response.js | 229 ------- .../__tests__/fixtures/seriesList.js | 32 - .../__tests__/fixtures/tlConfig.js | 77 --- .../series_functions/__tests__/graphite.js | 66 -- .../__tests__/helpers/get_series.js | 34 - .../__tests__/helpers/get_series_list.js | 30 - .../helpers/get_single_series_list.js | 26 - .../__tests__/helpers/invoke_series_fn.js | 41 -- .../server/series_functions/__tests__/hide.js | 43 -- .../series_functions/__tests__/label.js | 43 -- .../series_functions/__tests__/legend.js | 59 -- .../series_functions/__tests__/lines.js | 51 -- .../server/series_functions/__tests__/log.js | 37 -- .../server/series_functions/__tests__/max.js | 37 -- .../server/series_functions/__tests__/min.js | 37 -- .../__tests__/movingaverage.js | 79 --- .../series_functions/__tests__/movingstd.js | 104 --- .../series_functions/__tests__/multiply.js | 37 -- .../series_functions/__tests__/points.js | 81 --- .../series_functions/__tests__/precision.js | 43 -- .../series_functions/__tests__/quandl.js | 101 --- .../series_functions/__tests__/range.js | 43 -- .../__tests__/scale_interval.js | 37 -- .../series_functions/__tests__/static.js | 44 -- .../series_functions/__tests__/subtract.js | 80 --- .../server/series_functions/__tests__/sum.js | 37 -- .../series_functions/__tests__/title.js | 37 -- .../server/series_functions/__tests__/trim.js | 61 -- .../series_functions/__tests__/yaxis.js | 110 ---- .../server/series_functions/abs.js | 44 -- .../server/series_functions/aggregate/avg.js | 24 - .../series_functions/aggregate/cardinality.js | 24 - .../series_functions/aggregate/first.js | 24 - .../series_functions/aggregate/index.js | 72 --- .../server/series_functions/aggregate/last.js | 24 - .../server/series_functions/aggregate/max.js | 24 - .../server/series_functions/aggregate/min.js | 24 - .../server/series_functions/aggregate/sum.js | 24 - .../server/series_functions/bars.js | 57 -- .../server/series_functions/color.js | 74 --- .../server/series_functions/condition.js | 179 ----- .../server/series_functions/cusum.js | 55 -- .../server/series_functions/derivative.js | 48 -- .../server/series_functions/divide.js | 48 -- .../server/series_functions/es/index.js | 155 ----- .../series_functions/es/lib/agg_body.js | 37 -- .../es/lib/agg_response_to_series_list.js | 81 --- .../series_functions/es/lib/build_request.js | 96 --- .../es/lib/create_date_agg.js | 67 -- .../server/series_functions/first.js | 39 -- .../server/series_functions/fit.js | 63 -- .../server/series_functions/graphite.js | 98 --- .../server/series_functions/hide.js | 47 -- .../server/series_functions/holt/index.js | 139 ---- .../server/series_functions/holt/lib/des.js | 68 -- .../server/series_functions/holt/lib/ses.js | 57 -- .../server/series_functions/holt/lib/tes.js | 111 ---- .../server/series_functions/label.js | 62 -- .../server/series_functions/legend.js | 139 ---- .../server/series_functions/lines.js | 85 --- .../server/series_functions/log.js | 53 -- .../server/series_functions/max.js | 49 -- .../server/series_functions/min.js | 49 -- .../server/series_functions/movingaverage.js | 137 ---- .../server/series_functions/movingstd.js | 127 ---- .../server/series_functions/multiply.js | 48 -- .../server/series_functions/points.js | 126 ---- .../server/series_functions/precision.js | 53 -- .../server/series_functions/props.js | 86 --- .../server/series_functions/quandl.js | 135 ---- .../server/series_functions/range.js | 65 -- .../server/series_functions/scale_interval.js | 57 -- .../server/series_functions/static.js | 77 --- .../server/series_functions/subtract.js | 48 -- .../server/series_functions/sum.js | 49 -- .../server/series_functions/title.js | 48 -- .../server/series_functions/trend/index.js | 96 --- .../series_functions/trend/lib/regress.js | 97 --- .../server/series_functions/trim.js | 67 -- .../server/series_functions/worldbank.js | 128 ---- .../series_functions/worldbank_indicators.js | 84 --- .../server/series_functions/yaxis.js | 191 ------ .../vis_type_timelion/server/types.ts | 28 - 148 files changed, 11 insertions(+), 9742 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/common/lib/index.js rename src/legacy/core_plugins/vis_type_timelion/public/{panels => components}/panel.tsx (98%) rename src/legacy/core_plugins/vis_type_timelion/public/{panels/timechart => }/flot.js (100%) rename src/legacy/core_plugins/vis_type_timelion/public/{panels/utils.ts => helpers/panel_utils.ts} (94%) rename src/legacy/core_plugins/vis_type_timelion/public/{services => helpers}/tick_formatters.ts (100%) rename src/legacy/core_plugins/vis_type_timelion/public/{panels/timechart => helpers}/tick_generator.ts (100%) rename src/legacy/core_plugins/vis_type_timelion/public/{panels/timechart => helpers}/xaxis_formatter.ts (100%) rename src/legacy/core_plugins/vis_type_timelion/public/{panels/timechart => }/timechart.tsx (96%) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/plugin.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js delete mode 100644 src/legacy/core_plugins/vis_type_timelion/server/types.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js deleted file mode 100644 index 7c6b3c2816e67..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import toMS from '../../server/lib/to_milliseconds.js'; - -// Totally cribbed this from Kibana 3. -// I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. -function roundInterval(interval) { - switch (true) { - case interval <= 500: // <= 0.5s - return '100ms'; - case interval <= 5000: // <= 5s - return '1s'; - case interval <= 7500: // <= 7.5s - return '5s'; - case interval <= 15000: // <= 15s - return '10s'; - case interval <= 45000: // <= 45s - return '30s'; - case interval <= 180000: // <= 3m - return '1m'; - case interval <= 450000: // <= 9m - return '5m'; - case interval <= 1200000: // <= 20m - return '10m'; - case interval <= 2700000: // <= 45m - return '30m'; - case interval <= 7200000: // <= 2h - return '1h'; - case interval <= 21600000: // <= 6h - return '3h'; - case interval <= 86400000: // <= 24h - return '12h'; - case interval <= 604800000: // <= 1w - return '24h'; - case interval <= 1814400000: // <= 3w - return '1w'; - case interval < 3628800000: // < 2y - return '30d'; - default: - return '1y'; - } -} - -export function calculateInterval(from, to, size, interval, min) { - if (interval !== 'auto') return interval; - const dateMathInterval = roundInterval((to - from) / size); - if (toMS(dateMathInterval) < toMS(min)) return min; - return dateMathInterval; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/index.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.js deleted file mode 100644 index 927331043f0b3..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/common/lib/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { calculateInterval } from './calculate_interval'; -export const DEFAULT_TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss.SSS'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx similarity index 98% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index 04b6bfd57fe83..b28033a10eca8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -22,13 +22,13 @@ import $ from 'jquery'; import moment from 'moment-timezone'; import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; -import './timechart/flot'; +import '../flot'; // @ts-ignore -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; import { getServices } from '../kibana_services'; -import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from './utils'; +import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from '../helpers/panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; interface PanelProps { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/flot.js b/src/legacy/core_plugins/vis_type_timelion/public/flot.js similarity index 100% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/flot.js diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts similarity index 94% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index 212625edd2761..c60798925f981 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -24,11 +24,11 @@ import { TimefilterContract } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'kibana/public'; // @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../common/lib'; -import { tickFormatters } from '../services/tick_formatters'; -import { xaxisFormatterProvider } from './timechart/xaxis_formatter'; -import { generateTicksProvider } from './timechart/tick_generator'; -import { Series } from '../helpers/timelion_request_handler'; +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; +import { tickFormatters } from './tick_formatters'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { generateTicksProvider } from './tick_generator'; +import { Series } from './timelion_request_handler'; function buildSeriesData(chart: Series[], options: object) { return chart.map((series: Series, seriesIndex: number) => { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timelion/public/services/tick_formatters.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/tick_generator.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/xaxis_formatter.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts index 5495612ba22aa..90ddbed0e1760 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts @@ -19,7 +19,7 @@ import { IUiSettingsClient, HttpSetup } from 'kibana/public'; import { TimefilterContract } from 'src/plugins/data/public'; -import { IPanelWrapper } from './panels/timechart/timechart'; +import { IPanelWrapper } from './timechart'; export interface TimelionKibanaServices { http: HttpSetup; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index b3db4bf095244..1fa6c6ed17c90 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -28,7 +28,7 @@ import { import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; -import { getTimeChart, IPanelWrapper } from './panels/timechart/timechart'; +import { getTimeChart, IPanelWrapper } from './timechart'; import { setServices } from './kibana_services'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx similarity index 96% rename from src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx index af5124d36b3e2..aae80d5731e76 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/panels/timechart/timechart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx @@ -19,7 +19,7 @@ import React, { ReactElement } from 'react'; -import { Panel } from '../panel'; +import { Panel } from './components/panel'; export type IPanelWrapper = (props: any) => ReactElement; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js deleted file mode 100644 index d30244610a124..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/average.js +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../average`); -import moment from 'moment'; -const expect = require('chai').expect; -import _ from 'lodash'; - -describe('average.js', function() { - describe('average', function() { - it('fills holes in the data', function() { - const data = [ - [moment.utc('1980', 'YYYY').valueOf(), 10], - [moment.utc('1983', 'YYYY').valueOf(), 40], - [moment.utc('1984', 'YYYY').valueOf(), 50], - ]; - - const target = [ - [moment.utc('1980', 'YYYY').valueOf(), null], - [moment.utc('1981', 'YYYY').valueOf(), null], - [moment.utc('1982', 'YYYY').valueOf(), null], - [moment.utc('1983', 'YYYY').valueOf(), null], - [moment.utc('1984', 'YYYY').valueOf(), null], - ]; - - expect(_.map(fn(data, target), 1)).to.eql([10, 20, 30, 40, 50]); - }); - - describe('sampling', function() { - it('up', function() { - const data = [ - [moment.utc('1981', 'YYYY').valueOf(), 10], - [moment.utc('1983', 'YYYY').valueOf(), 30], - [moment.utc('1985', 'YYYY').valueOf(), 70], - ]; - - const target = [ - [moment.utc('1981', 'YYYY').valueOf(), null], - [moment.utc('1982', 'YYYY').valueOf(), null], - [moment.utc('1983', 'YYYY').valueOf(), null], - [moment.utc('1984', 'YYYY').valueOf(), null], - [moment.utc('1985', 'YYYY').valueOf(), null], - ]; - - expect(_.map(fn(data, target), 1)).to.eql([10, 20, 30, 50, 70]); - }); - - it('down', function() { - const data = [ - [moment.utc('1980', 'YYYY').valueOf(), 0], - [moment.utc('1981', 'YYYY').valueOf(), 2], - [moment.utc('1982', 'YYYY').valueOf(), 4], - [moment.utc('1983', 'YYYY').valueOf(), 6], - [moment.utc('1984', 'YYYY').valueOf(), 8], - [moment.utc('1985', 'YYYY').valueOf(), 10], - [moment.utc('1986', 'YYYY').valueOf(), 12], - ]; - - const target = [ - [moment.utc('1981', 'YYYY').valueOf(), null], - [moment.utc('1983', 'YYYY').valueOf(), null], - [moment.utc('1985', 'YYYY').valueOf(), null], - ]; - - // This returns 1, 5, 9 instead of the expected 2, 6, 10 because the average function does not consider "future" - // values, rather just the next upcoming value from the end of the previously predicted bucket. E.g., When - // interpolating a weekly series into daily, in which the buckets fall on sundays, this coming Sunday's bucket - // will be distributed Mon-Sun instead of say Thur-Wed. - // Essentially the algorithm is left aligned instead of centered - expect(_.map(fn(data, target), 1)).to.eql([1, 5, 9]); - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js deleted file mode 100644 index 8e35d63618ae4..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/__tests__/carry.js +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../carry`); -import moment from 'moment'; -const expect = require('chai').expect; -import _ from 'lodash'; - -describe('carry.js', function() { - it('fills holes in the data', function() { - const data = [ - [moment.utc('1980', 'YYYY').valueOf(), 10], - [moment.utc('1983', 'YYYY').valueOf(), 40], - [moment.utc('1984', 'YYYY').valueOf(), 50], - ]; - - const target = [ - [moment.utc('1980', 'YYYY').valueOf(), null], - [moment.utc('1981', 'YYYY').valueOf(), null], - [moment.utc('1982', 'YYYY').valueOf(), null], - [moment.utc('1983', 'YYYY').valueOf(), null], - [moment.utc('1984', 'YYYY').valueOf(), null], - ]; - - expect(_.map(fn(data, target), 1)).to.eql([10, 10, 10, 40, 50]); - }); - - describe('sampling', function() { - it('up', function() { - const data = [ - [moment.utc('1981', 'YYYY').valueOf(), 10], - [moment.utc('1983', 'YYYY').valueOf(), 30], - [moment.utc('1985', 'YYYY').valueOf(), 70], - ]; - - const target = [ - [moment.utc('1981', 'YYYY').valueOf(), null], - [moment.utc('1982', 'YYYY').valueOf(), null], - [moment.utc('1983', 'YYYY').valueOf(), null], - [moment.utc('1984', 'YYYY').valueOf(), null], - [moment.utc('1985', 'YYYY').valueOf(), null], - ]; - - expect(_.map(fn(data, target), 1)).to.eql([10, 10, 30, 30, 70]); - }); - - it('down does not make sense', function() { - const data = [ - [moment.utc('1980', 'YYYY').valueOf(), 0], - [moment.utc('1981', 'YYYY').valueOf(), 2], - [moment.utc('1982', 'YYYY').valueOf(), 4], - [moment.utc('1983', 'YYYY').valueOf(), 6], - [moment.utc('1984', 'YYYY').valueOf(), 8], - [moment.utc('1985', 'YYYY').valueOf(), 10], - [moment.utc('1986', 'YYYY').valueOf(), 12], - ]; - - const target = [ - [moment.utc('1981', 'YYYY').valueOf(), null], - [moment.utc('1983', 'YYYY').valueOf(), null], - [moment.utc('1985', 'YYYY').valueOf(), null], - ]; - - // carry doesn't down sample, it simply doesn't make any sense. Use average or scale - try { - fn(data, target); - expect.fail('Success. Doh.'); - } catch (e) { - expect(e).to.be.an('error'); - } - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js deleted file mode 100644 index 10baeb8552a4e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/average.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -// Upsampling and down sampling of non-cumulative sets -// Good: min, max, average -// Bad: sum, count - -export default function average(dataTuples, targetTuples) { - // Phase 1: Downsample - // We necessarily won't well match the dataSource here as we don't know how much data - // they had when creating their own average - const resultTimes = _.pluck(targetTuples, 0); - const dataTuplesQueue = _.clone(dataTuples); - const resultValues = _.map(targetTuples, function(bucket) { - const time = bucket[0]; - let i = 0; - const avgSet = []; - - // This is naive, it doesn't consider where the line is going next, - // It simply writes the point and moves on once it hits <= time. - // Thus this algorithm will tend to lag the trend. - // Deal with it, or write something better. - while (i < dataTuplesQueue.length && dataTuplesQueue[i][0] <= time) { - avgSet.push(dataTuplesQueue[i][1]); - i++; - } - - dataTuplesQueue.splice(0, i); - - const sum = avgSet.reduce((sum, num) => sum + num, 0); - - return avgSet.length ? sum / avgSet.length : NaN; - }); - - // Phase 2: Upsample if needed - // If we have any NaNs we are probably resampling from a big interval to a small one (eg, 1M as 1d) - // So look for the missing stuff in the array, and smooth it out - const naNIndex = _.findIndex(resultValues, function(val) { - return isNaN(val); - }); - - if (naNIndex > -1) { - let i = 0; - let naNCount = 0; - const filledValues = []; - let previousRealNumber; - let stepSize; - while (i < resultValues.length) { - if (isNaN(resultValues[i])) { - if (i === 0) { - // If our first number is NaN, initialize from dataTuples; - previousRealNumber = dataTuples[0][1]; - } - naNCount++; - } else { - // Otherwise, backfill the NaNs with averaged out data - if (naNCount > 0) { - stepSize = (resultValues[i] - previousRealNumber) / (naNCount + 1); - while (naNCount > 0) { - resultValues[i - naNCount] = previousRealNumber + stepSize; - previousRealNumber = resultValues[i - naNCount]; - naNCount--; - } - } - previousRealNumber = resultValues[i]; - filledValues.push(resultValues[i]); - } - i++; - } - } - - const resultTuples = _.zip(resultTimes, resultValues); - return resultTuples; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js deleted file mode 100644 index c08932bacb69e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/carry.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; - -// Upsampling of non-cumulative sets -// Good: average, min, max -// Bad: sum, count - -// Don't use this to down sample, it simply won't do the right thing. -export default function carry(dataTuples, targetTuples) { - if (dataTuples.length > targetTuples.length) { - throw new Error( - i18n.translate('timelion.fitFunctions.carry.downSampleErrorMessage', { - defaultMessage: `Don't use the 'carry' fit method to down sample, use 'scale' or 'average'`, - description: - '"carry", "scale" and "average" are parameter values that must not be translated.', - }) - ); - } - - let currentCarry = dataTuples[0][1]; - return _.map(targetTuples, function(bucket) { - const targetTime = bucket[0]; - const dataTime = dataTuples[0][0]; - - if (dataTuples[0] && targetTime >= dataTime) { - currentCarry = dataTuples[0][1]; - if (dataTuples.length > 1) { - dataTuples.shift(); - } - } - - return [bucket[0], currentCarry]; - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js deleted file mode 100644 index f1907f9603de3..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/nearest.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -// Upsampling and downsampling of non-cumulative sets -// Good: average, min, max -// Bad: sum, count -export default function nearest(dataTuples, targetTuples) { - return _.map(targetTuples, function(bucket) { - const time = bucket[0]; - let i = 0; - while ( - i < dataTuples.length - 1 && - (Math.abs(dataTuples[i + 1][0] - time) < Math.abs(dataTuples[i][0] - time) || - // TODO: Certain offset= args can cause buckets with duplicate times, e.g., offset=-1M - // check for that, and only use the last of the duplicates. The reason this happens? - // What is 1M before Mar 30th? What about 1M before Mar 31st? Both are the last day - // in Feb. Something has to be chucked. If offsetting by M user might want to use - // fit=average - Math.abs(dataTuples[i + 1][0] - time) === Math.abs(dataTuples[i][0] - time)) - ) { - i++; - } - - const closest = dataTuples[i]; - dataTuples.splice(0, i); - - return [bucket[0], closest[1]]; - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js deleted file mode 100644 index aa0a4cd8aee61..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/none.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// **DON'T USE THIS** -// Performing joins/math with other sets that don't match perfectly will be wrong -// Does not resample at all. -export default function none(dataTuples) { - return dataTuples; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js b/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js deleted file mode 100644 index 3f5ece1d6e61a..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/fit_functions/scale.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -// Downsampling of cumulative metrics -// Good: count, sum -// Bad: avg, min, max - -// For upsampling cumulative metrics (eg sum from 1M to 1d), could rename this scale. -// Really only the 0s that screws this up, need to distribute contents of spikes to empty buckets -// Empty is currently 0, which is not right - -function sum(set) { - return _.reduce( - set, - function(sum, num) { - return sum + num; - }, - 0 - ); -} - -export default function scale(dataTuples, targetTuples) { - let i = 0; - let j = 0; - let spreadCount = 0; - const result = []; - let bucket; - let time; - let scaleSet; - let step; - let nextRealNumber; - - while (i < targetTuples.length) { - scaleSet = []; - bucket = targetTuples[i]; - time = bucket[0]; - - // Find stuff to sum - j = 0; - while (j < dataTuples.length && Math.abs(dataTuples[j][0] <= time)) { - scaleSet.push(dataTuples[j][1]); - j++; - } - dataTuples.splice(0, j); - - // We hit a real number, or the end - if (scaleSet.length > 0 || i === targetTuples.length - 1) { - nextRealNumber = sum(scaleSet); - - step = nextRealNumber; - // Backfill null buckets - if (spreadCount > 0) { - // Naively distribute the nextRealNumber amongst the buckets - // Without considering where it is headed next - // We do spreadCount + 1 so that we include nextRealNumber when we smooth things out, - // since we'll overwrite it anyway. - // Thus [5, null, null, 30] becomes [5, 10, 10, 10] - step = nextRealNumber / (spreadCount + 1); - while (spreadCount > 0) { - result[i - spreadCount][1] = step; - spreadCount--; - } - } - result.push([time, step]); - } else { - result.push([time, null]); - spreadCount++; - } - - i++; - } - - return result; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js deleted file mode 100644 index 4ec2a88d3e68b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/__tests__/parse_sheet.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const parseSheet = require('../lib/parse_sheet'); - -const expect = require('chai').expect; - -describe('timelion parse_sheet function', function() { - it(`doesn't split expressions on whitespace`, async function() { - const data = ['.es() .es(404)']; - const ast = parseSheet(data); - - const expressions = ast[0]; - expect(expressions.length).to.equal(1); - expect(expressions[0].type).to.equal('chain'); - }); - - it('splits expressions on commas', function() { - const data = ['.es(), .es(404)']; - const ast = parseSheet(data); - - const expressions = ast[0]; - expect(expressions.length).to.equal(2); - expect(expressions[0].type).to.equal('chain'); - expect(expressions[1].type).to.equal('chain'); - }); - - it('splits expressions on newlines', function() { - const data = [`.es()\n\r ,\n\r .es(404)`]; - const ast = parseSheet(data); - - const expressions = ast[0]; - expect(expressions.length).to.equal(2); - expect(expressions[0].type).to.equal('chain'); - expect(expressions[1].type).to.equal('chain'); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js deleted file mode 100644 index 9514e479d36f4..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/chain_runner.js +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import Bluebird from 'bluebird'; -import { i18n } from '@kbn/i18n'; -import moment from 'moment'; - -import parseSheet from './lib/parse_sheet.js'; -import repositionArguments from './lib/reposition_arguments.js'; -import indexArguments from './lib/index_arguments.js'; -import validateTime from './lib/validate_time.js'; -import { calculateInterval } from '../../common/lib'; - -export default function chainRunner(tlConfig) { - const preprocessChain = require('./lib/preprocess_chain')(tlConfig); - - let queryCache = {}; - const stats = {}; - let sheet; - - function throwWithCell(cell, exception) { - throw new Error(' in cell #' + (cell + 1) + ': ' + exception.message); - } - - // Invokes a modifier function, resolving arguments into series as needed - function invoke(fnName, args) { - const functionDef = tlConfig.server.plugins.timelion.getFunction(fnName); - - function resolveArgument(item) { - if (Array.isArray(item)) { - return Bluebird.all(_.map(item, resolveArgument)); - } - - if (_.isObject(item)) { - switch (item.type) { - case 'function': { - const itemFunctionDef = tlConfig.server.plugins.timelion.getFunction(item.function); - if (itemFunctionDef.cacheKey && queryCache[itemFunctionDef.cacheKey(item)]) { - stats.queryCount++; - return Bluebird.resolve(_.cloneDeep(queryCache[itemFunctionDef.cacheKey(item)])); - } - return invoke(item.function, item.arguments); - } - case 'reference': { - let reference; - if (item.series) { - reference = sheet[item.plot - 1][item.series - 1]; - } else { - reference = { - type: 'chainList', - list: sheet[item.plot - 1], - }; - } - return invoke('first', [reference]); - } - case 'chain': - return invokeChain(item); - case 'chainList': - return resolveChainList(item.list); - case 'literal': - return item.value; - case 'requestList': - case 'seriesList': - return item; - } - throw new Error( - i18n.translate('timelion.serverSideErrors.unknownArgumentTypeErrorMessage', { - defaultMessage: 'Argument type not supported: {argument}', - values: { - argument: JSON.stringify(item), - }, - }) - ); - } else { - return item; - } - } - - args = repositionArguments(functionDef, args); - - args = _.map(args, resolveArgument); - - return Bluebird.all(args).then(function(args) { - args.byName = indexArguments(functionDef, args); - return functionDef.fn(args, tlConfig); - }); - } - - function invokeChain(chainObj, result) { - if (chainObj.chain.length === 0) return result[0]; - - const chain = _.clone(chainObj.chain); - const link = chain.shift(); - - let promise; - if (link.type === 'chain') { - promise = invokeChain(link); - } else if (!result) { - promise = invoke('first', [link]); - } else { - const args = link.arguments ? result.concat(link.arguments) : result; - promise = invoke(link.function, args); - } - - return promise.then(function(result) { - return invokeChain({ type: 'chain', chain: chain }, [result]); - }); - } - - function resolveChainList(chainList) { - const seriesList = _.map(chainList, function(chain) { - const values = invoke('first', [chain]); - return values.then(function(args) { - return args; - }); - }); - return Bluebird.all(seriesList).then(function(args) { - const list = _.chain(args) - .pluck('list') - .flatten() - .value(); - const seriesList = _.merge.apply(this, _.flatten([{}, args])); - seriesList.list = list; - return seriesList; - }); - } - - function preProcessSheet(sheet) { - let queries = {}; - _.each(sheet, function(chainList, i) { - try { - const queriesInCell = _.mapValues(preprocessChain(chainList), function(val) { - val.cell = i; - return val; - }); - queries = _.extend(queries, queriesInCell); - } catch (e) { - throwWithCell(i, e); - } - }); - queries = _.values(queries); - - const promises = _.chain(queries) - .values() - .map(function(query) { - return invoke(query.function, query.arguments); - }) - .value(); - - return Bluebird.settle(promises).then(function(resolvedDatasources) { - stats.queryTime = new Date().getTime(); - - _.each(queries, function(query, i) { - const functionDef = tlConfig.server.plugins.timelion.getFunction(query.function); - const resolvedDatasource = resolvedDatasources[i]; - - if (resolvedDatasource.isRejected()) { - if (resolvedDatasource.reason().isBoom) { - throw resolvedDatasource.reason(); - } else { - throwWithCell(query.cell, resolvedDatasource.reason()); - } - } - - queryCache[functionDef.cacheKey(query)] = resolvedDatasource.value(); - }); - - stats.cacheCount = _.keys(queryCache).length; - return sheet; - }); - } - - function processRequest(request) { - if (!request) throw new Error('Empty request body'); - - validateTime(request.time, tlConfig); - - tlConfig.time = request.time; - tlConfig.time.to = moment(request.time.to).valueOf(); - tlConfig.time.from = moment(request.time.from).valueOf(); - tlConfig.time.interval = calculateInterval( - tlConfig.time.from, - tlConfig.time.to, - tlConfig.settings['timelion:target_buckets'] || 200, - tlConfig.time.interval, - tlConfig.settings['timelion:min_interval'] || '1ms' - ); - - tlConfig.setTargetSeries(); - - stats.invokeTime = new Date().getTime(); - stats.queryCount = 0; - queryCache = {}; - - // This is setting the "global" sheet, required for resolving references - sheet = parseSheet(request.sheet); - return preProcessSheet(sheet).then(function() { - return _.map(sheet, function(chainList, i) { - return resolveChainList(chainList) - .then(function(seriesList) { - stats.sheetTime = new Date().getTime(); - return seriesList; - }) - .catch(function(e) { - throwWithCell(i, e); - }); - }); - }); - } - - return { - processRequest: processRequest, - getStats: function() { - return stats; - }, - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js deleted file mode 100644 index 59741827fdad2..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/arg_type.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function argType(arg) { - if (Array.isArray(arg)) { - return _.chain(arg) - .map(argType) - .flattenDeep() - .value(); - } - - if (_.isObject(arg) && arg) { - return arg.type; - } - if (arg == null) { - return 'null'; - } - return typeof arg; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js deleted file mode 100644 index 20e46383baa3a..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/index_arguments.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; - -// Only applies to already resolved arguments -export default function indexArguments(functionDef, orderedArgs) { - const validateArg = require('./validate_arg')(functionDef); - - // This almost certainly is not required - const allowedLength = functionDef.extended - ? functionDef.args.length + 2 - : functionDef.args.length; - if (orderedArgs.length > allowedLength) { - throw new Error( - i18n.translate('timelion.serverSideErrors.argumentsOverflowErrorMessage', { - defaultMessage: 'Too many arguments passed to: {functionName}', - values: { - functionName: functionDef.name, - }, - }) - ); - } - - const indexedArgs = {}; - // Check and index each known argument - _.each(functionDef.args, function(argDef, i) { - const value = orderedArgs[i]; - validateArg(value, argDef.name, argDef); - indexedArgs[argDef.name] = value; - }); - - // Also check and index the extended arguments if enabled - if (functionDef.extended) { - const values = orderedArgs[orderedArgs.length - 1]; - const names = orderedArgs[orderedArgs.length - 2]; - _.each(values, function(value, i) { - validateArg(value, names[i], functionDef.extended); - indexedArgs[names[i]] = value; - }); - } - - return indexedArgs; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js deleted file mode 100644 index 74ef76d1a50cd..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import fs from 'fs'; -import path from 'path'; -import _ from 'lodash'; -const grammar = fs.readFileSync(path.resolve(__dirname, '../../../public/chain.peg'), 'utf8'); -import PEG from 'pegjs'; -const Parser = PEG.generate(grammar); - -export default function parseSheet(sheet) { - return _.map(sheet, function(plot) { - try { - return Parser.parse(plot).tree; - } catch (e) { - if (e.expected) { - throw new Error( - i18n.translate('timelion.serverSideErrors.sheetParseErrorMessage', { - defaultMessage: 'Expected: {expectedDescription} at character {column}', - description: 'This would be for example: "Expected: a quote at character 5"', - values: { - expectedDescription: e.expected[0].description, - column: e.column, - }, - }) - ); - } else { - throw e; - } - } - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js deleted file mode 100644 index 5e5f274115ee2..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/preprocess_chain.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function preProcessChainFn(tlConfig) { - return function preProcessChain(chain, queries) { - queries = queries || {}; - function validateAndStore(item) { - if (_.isObject(item) && item.type === 'function') { - const functionDef = tlConfig.server.plugins.timelion.getFunction(item.function); - - if (functionDef.datasource) { - queries[functionDef.cacheKey(item)] = item; - return true; - } - return false; - } - } - - // Is this thing a function? - if (validateAndStore(chain)) { - return; - } - - if (!Array.isArray(chain)) return; - - _.each(chain, function(operator) { - if (!_.isObject(operator)) { - return; - } - switch (operator.type) { - case 'chain': - preProcessChain(operator.chain, queries); - break; - case 'chainList': - preProcessChain(operator.list, queries); - break; - case 'function': - if (validateAndStore(operator)) { - break; - } else { - preProcessChain(operator.arguments, queries); - } - break; - } - }); - - return queries; - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js deleted file mode 100644 index 85a78564bf506..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/reposition_arguments.js +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; - -// Applies to unresolved arguments in the AST -export default function repositionArguments(functionDef, unorderedArgs) { - const args = []; - - _.each(unorderedArgs, function(unorderedArg, i) { - let argDef; - let targetIndex; - let value; - let storeAsArray; - - if (_.isObject(unorderedArg) && unorderedArg.type === 'namedArg') { - argDef = functionDef.argsByName[unorderedArg.name]; - - if (!argDef) { - if (functionDef.extended) { - const namesIndex = functionDef.args.length; - targetIndex = functionDef.args.length + 1; - - args[namesIndex] = args[namesIndex] || []; - args[namesIndex].push(unorderedArg.name); - - argDef = functionDef.extended; - storeAsArray = true; - } - } else { - targetIndex = _.findIndex(functionDef.args, function(orderedArg) { - return unorderedArg.name === orderedArg.name; - }); - storeAsArray = argDef.multi; - } - value = unorderedArg.value; - } else { - argDef = functionDef.args[i]; - storeAsArray = argDef.multi; - targetIndex = i; - value = unorderedArg; - } - - if (!argDef) { - throw new Error( - i18n.translate('timelion.serverSideErrors.unknownArgumentErrorMessage', { - defaultMessage: 'Unknown argument to {functionName}: {argumentName}', - values: { - functionName: functionDef.name, - argumentName: unorderedArg.name || '#' + i, - }, - }) - ); - } - - if (storeAsArray) { - args[targetIndex] = args[targetIndex] || []; - args[targetIndex].push(value); - } else { - args[targetIndex] = value; - } - }); - - return args; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js deleted file mode 100644 index 28edafbc3be67..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/tl_config.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import buildTarget from '../../lib/build_target.js'; - -export default function tlConfigFn(setup) { - let targetSeries; - - let tlConfig = { - getTargetSeries: function() { - return _.map(targetSeries, function(bucket) { - // eslint-disable-line no-use-before-define - return [bucket, null]; - }); - }, - setTargetSeries: function() { - targetSeries = buildTarget(this); - }, - writeTargetSeries: function(series) { - targetSeries = _.map(series, function(p) { - return p[0]; - }); - }, - }; - - tlConfig = _.extend(tlConfig, setup); - return tlConfig; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js deleted file mode 100644 index 1fb325bb6ee86..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_arg.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import argType from './arg_type'; -import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; - -export default function validateArgFn(functionDef) { - return function validateArg(value, name, argDef) { - const type = argType(value); - const required = argDef.types; - const multi = argDef.multi; - const isCorrectType = (function() { - // If argument is not allow to be specified multiple times, we're dealing with a plain value for type - if (!multi) return _.contains(required, type); - // If it is, we'll get an array for type - return _.difference(type, required).length === 0; - })(); - - if (isCorrectType) return true; - else return false; - - if (!isCorrectType) { - throw new Error( - i18n.translate('timelion.serverSideErrors.wrongFunctionArgumentTypeErrorMessage', { - defaultMessage: - '{functionName}({argumentName}) must be one of {requiredTypes}. Got: {actualType}', - values: { - functionName: functionDef.name, - argumentName: name, - requiredTypes: JSON.stringify(required), - actualType: type, - }, - }) - ); - } - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js deleted file mode 100644 index 8b1f8998557be..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/handlers/lib/validate_time.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import moment from 'moment'; - -import toMS from '../../lib/to_milliseconds.js'; - -export default function validateTime(time, tlConfig) { - const span = moment.duration(moment(time.to).diff(moment(time.from))).asMilliseconds(); - const interval = toMS(time.interval); - const bucketCount = span / interval; - const maxBuckets = tlConfig.settings['timelion:max_buckets']; - if (bucketCount > maxBuckets) { - throw new Error( - i18n.translate('timelion.serverSideErrors.bucketsOverflowErrorMessage', { - defaultMessage: - 'Max buckets exceeded: {bucketCount} of {maxBuckets} allowed. ' + - 'Choose a larger interval or a shorter time span', - values: { - bucketCount: Math.round(bucketCount), - maxBuckets, - }, - }) - ); - } - return true; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/index.ts b/src/legacy/core_plugins/vis_type_timelion/server/index.ts deleted file mode 100644 index 36af9ce7b85df..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PluginInitializerContext } from 'kibana/server'; -import { TimelionServerPlugin as Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js deleted file mode 100644 index 45dd436be6943..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/__tests__/load_functions.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../load_functions`); - -const expect = require('chai').expect; - -describe('load_functions.js', () => { - it('exports a function', () => { - expect(fn).to.be.a('function'); - }); - - it('returns an object with keys named for the javascript files in the directory', () => { - const fnList = fn('series_functions'); - - expect(fnList).to.be.an('object'); - expect(fnList.sum).to.be.a('object'); - }); - - it('also includes index.js files in direct subdirectories, and names the keys for the directory', () => { - const fnList = fn('series_functions'); - - expect(fnList).to.be.an('object'); - expect(fnList.es).to.be.a('object'); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js deleted file mode 100644 index c49d5f5f3ba25..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/alter.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Bluebird from 'bluebird'; -import _ from 'lodash'; - -/* @param {Array} args - * - args[0] must be a seriesList - - * @params {Function} fn - Function to apply to each series in the seriesList - * @return {seriesList} - */ - -export default function alter(args, fn) { - // In theory none of the args should ever be promises. This is probably a waste. - return Bluebird.all(args) - .then(function(args) { - const seriesList = args.shift(); - - if (seriesList.type !== 'seriesList') { - throw new Error('args[0] must be a seriesList'); - } - - const list = _.chain(seriesList.list) - .map(function(series) { - return fn.apply(this, [series].concat(args)); - }) - .flatten() - .value(); - - seriesList.list = list; - return seriesList; - }) - .catch(function(e) { - throw e; - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js deleted file mode 100644 index ff20af78b4362..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/asSorted.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import unzipPairs from './unzipPairs.js'; - -export default function asSorted(timeValObject, fn) { - const data = unzipPairs(timeValObject); - return _.zipObject(fn(data)); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js deleted file mode 100644 index 5ef11e79d9baa..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/build_target.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import moment from 'moment'; -import splitInterval from './split_interval.js'; - -export default function(tlConfig) { - const min = moment(tlConfig.time.from); - const max = moment(tlConfig.time.to); - - const intervalParts = splitInterval(tlConfig.time.interval); - - let current = min.startOf(intervalParts.unit); - - const targetSeries = []; - - while (current.valueOf() < max.valueOf()) { - targetSeries.push(current.valueOf()); - current = current.add(intervalParts.count, intervalParts.unit); - } - - return targetSeries; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js deleted file mode 100644 index 8733212c9eed3..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/chainable.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import TimelionFunction from './timelion_function'; - -export default class Chainable extends TimelionFunction { - constructor(name, config) { - super(name, config); - this.chainable = true; - Object.freeze(this); - } -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js deleted file mode 100644 index e9d5fa8774b24..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/datasource.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import loadFunctions from '../load_functions.js'; -const fitFunctions = loadFunctions('fit_functions'); -import TimelionFunction from './timelion_function'; -import { offsetTime, preprocessOffset } from '../offset_time'; -import _ from 'lodash'; - -function offsetSeries(response, offset) { - if (offset) { - response = _.map(response, function(point) { - return [offsetTime(point[0], offset, true), point[1]]; - }); - } - return response; -} - -export default class Datasource extends TimelionFunction { - constructor(name, config) { - // Additional arguments that every dataSource take - config.args.push({ - name: 'offset', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.common.args.offsetHelpText', { - defaultMessage: - 'Offset the series retrieval by a date expression, e.g., -1M to make events from ' + - 'one month ago appear as if they are happening now. Offset the series relative to the charts ' + - 'overall time range, by using the value "timerange", e.g. "timerange:-2" will specify an offset ' + - 'that is twice the overall chart time range to the past.', - }), - }); - - config.args.push({ - name: 'fit', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.common.args.fitHelpText', { - defaultMessage: - 'Algorithm to use for fitting series to the target time span and interval. Available: {fitFunctions}', - values: { - fitFunctions: _.keys(fitFunctions).join(', '), - }, - }), - }); - - // Wrap the original function so we can modify inputs/outputs with offset & fit - const originalFunction = config.fn; - config.fn = function(args, tlConfig) { - const config = _.clone(tlConfig); - let offset = args.byName.offset; - if (offset) { - offset = preprocessOffset(offset, tlConfig.time.from, tlConfig.time.to); - config.time = _.cloneDeep(tlConfig.time); - config.time.from = offsetTime(config.time.from, offset); - config.time.to = offsetTime(config.time.to, offset); - } - - return Promise.resolve(originalFunction(args, config)).then(function(seriesList) { - seriesList.list = _.map(seriesList.list, function(series) { - if (series.data.length === 0) throw new Error(name + '() returned no results'); - series.data = offsetSeries(series.data, offset); - series.fit = args.byName.fit || series.fit || 'nearest'; - return series; - }); - return seriesList; - }); - }; - - super(name, config); - - // You need to call timelionFn if calling up a datasource from another datasource, - // otherwise teh series will end up being offset twice. - this.timelionFn = originalFunction; - this.datasource = true; - this.cacheKey = function(item) { - return item.text; - }; - Object.freeze(this); - } -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts deleted file mode 100644 index 6e32a4454e707..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export interface TimelionFunctionInterface extends TimelionFunctionConfig { - chainable: boolean; - originalFn: Function; - argsByName: TimelionFunctionArgs[]; -} - -export interface TimelionFunctionConfig { - name: string; - help: string; - extended: boolean; - aliases: string[]; - fn: Function; - args: TimelionFunctionArgs[]; -} - -export interface TimelionFunctionArgs { - name: string; - help?: string; - multi?: boolean; - types: TimelionFunctionArgsTypes[]; - suggestions?: TimelionFunctionArgsSuggestion[]; -} - -export type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null'; - -export interface TimelionFunctionArgsSuggestion { - name: string; - help: string; -} - -// eslint-disable-next-line import/no-default-export -export default class TimelionFunction { - constructor(name: string, config: TimelionFunctionConfig); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js deleted file mode 100644 index 2b6919dbcb350..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/classes/timelion_function.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import loadFunctions from '../load_functions.js'; -const fitFunctions = loadFunctions('fit_functions'); - -export default class TimelionFunction { - constructor(name, config) { - this.name = name; - this.args = config.args || []; - this.argsByName = _.indexBy(this.args, 'name'); - this.help = config.help || ''; - this.aliases = config.aliases || []; - this.extended = config.extended || false; - - // WTF is this? How could you not have a fn? Wtf would the thing be used for? - const originalFunction = - config.fn || - function(input) { - return input; - }; - - // Currently only re-fits the series. - this.originalFn = originalFunction; - - this.fn = function(args, tlConfig) { - const config = _.clone(tlConfig); - return Promise.resolve(originalFunction(args, config)).then(function(seriesList) { - seriesList.list = _.map(seriesList.list, function(series) { - const target = tlConfig.getTargetSeries(); - - // Don't fit if the series are already the same - if (_.isEqual(_.map(series.data, 0), _.map(target, 0))) return series; - - let fit; - if (args.byName.fit) { - fit = args.byName.fit; - } else if (series.fit) { - fit = series.fit; - } else { - fit = 'nearest'; - } - - series.data = fitFunctions[fit](series.data, tlConfig.getTargetSeries()); - return series; - }); - return seriesList; - }); - }; - } -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js deleted file mode 100644 index 79302cef588d6..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/functions_md.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import loadFunctions from './load_functions.js'; -const functions = loadFunctions('series_functions/'); -import _ from 'lodash'; - -export default (function() { - const functionArray = _.map(functions, function(val, key) { - // TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables - return _.extend({}, val, { name: key }); - }); - - function toDocBlock(fn) { - let help = ''; - - if (fn.isAlias) return help; - - help += '#### .' + fn.name + '()\n'; - help += fn.help + '\n\n'; - - // If chainable, drop first argument from help - const args = fn.chainable ? fn.args.slice(1) : fn.args.slice(); - if (!args.length) { - help += '*This function does not accept any arguments.*\n\n'; - return help; - } - - help += 'Argument | Accepts | Description\n'; - help += '--- | --- | ---\n'; - - _.each(args, function(arg) { - help += arg.name + ' | *' + _.without(arg.types, 'null').join('/') + '* | '; - help += arg.help ? arg.help : '*no help available*'; - help += ' \n'; - }); - - help += '\n'; - - return help; - } - - function createDocs() { - let help = ''; - help += '## Timelion function reference\n'; - help += - 'This document is auto generated from the timelion code. ' + - 'Do not submit pulls against this document. You want to submit a pull against something in the ' + - '`series_functions/` directory.\n\n'; - - help += '### Data sources\n'; - help += - "Data sources can start a chain, they don't need to be attached to anything, but they still need to start" + - ' with a `.` (dot). Data retrieved from a data source can be passed into the chainable functions in the next section.\n\n'; - - help += _.chain(functionArray) - .filter('datasource') - .map(toDocBlock) - .value() - .join(''); - - help += '### Chainable functions\n'; - help += - 'Chainable functions can not start a chain. Somewhere before them must be a data source function. Chainable' + - ' functions modify the data output directly from a data source, or from another chainable function that has a data' + - ' source somewhere before it.\n\n'; - - help += _.chain(functionArray) - .filter('chainable') - .map(toDocBlock) - .value() - .join(''); - - return help; - } - - return createDocs(); -})(); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js deleted file mode 100644 index 5d18d87e8e054..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/get_namespaced_settings.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import configFile from '../../timelion.json'; - -export default function() { - function flattenWith(dot, nestedObj, flattenArrays) { - const stack = []; // track key stack - const flatObj = {}; - (function flattenObj(obj) { - _.keys(obj).forEach(function(key) { - stack.push(key); - if (!flattenArrays && Array.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key]; - else if (_.isObject(obj[key])) flattenObj(obj[key]); - else flatObj[stack.join(dot)] = obj[key]; - stack.pop(); - }); - })(nestedObj); - return flatObj; - } - - const timelionDefaults = flattenWith('.', configFile); - return _.reduce( - timelionDefaults, - (result, value, key) => { - result['timelion:' + key] = value; - return result; - }, - {} - ); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts deleted file mode 100644 index f6d04ee875831..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.d.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { TimelionFunctionInterface } from '../types'; - -declare function loadFunctions(directory: string): LoadFunctions; - -export interface LoadFunctions { - [key: string]: TimelionFunctionInterface; -} - -// eslint-disable-next-line import/no-default-export -export default loadFunctions; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js deleted file mode 100644 index 11501ce3f102b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/load_functions.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import glob from 'glob'; -import path from 'path'; -import processFunctionDefinition from './process_function_definition'; - -export default function(directory) { - function getTuple(directory, name) { - return [name, require('../' + directory + '/' + name)]; // eslint-disable-line import/no-dynamic-require - } - - // Get a list of all files and use the filename as the object key - const files = _.map(glob.sync(path.resolve(__dirname, '../' + directory + '/*.js')), function( - file - ) { - const name = file.substring(file.lastIndexOf('/') + 1, file.lastIndexOf('.')); - return getTuple(directory, name); - }); - - // Get a list of all directories with an index.js, use the directory name as the key in the object - const directories = _.chain(glob.sync(path.resolve(__dirname, '../' + directory + '/*/index.js'))) - .filter(function(file) { - return file.match(/__test__/) == null; - }) - .map(function(file) { - const parts = file.split('/'); - const name = parts[parts.length - 2]; - return getTuple(directory, name); - }) - .value(); - - const functions = _.zipObject(files.concat(directories)); - - _.each(functions, function(func) { - _.assign(functions, processFunctionDefinition(func)); - }); - - return functions; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js deleted file mode 100644 index 05aeb07554316..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import moment from 'moment'; - -// usually reverse = false on the request, true on the response -export function offsetTime(milliseconds, offset, reverse) { - if (!offset.match(/[-+][0-9]+[mshdwMy]/g)) { - throw new Error('Malformed `offset` at ' + offset); - } - const parts = offset.match(/[-+]|[0-9]+|[mshdwMy]/g); - - let add = parts[0] === '+'; - add = reverse ? !add : add; - - const mode = add ? 'add' : 'subtract'; - - const momentObj = moment(milliseconds)[mode](parts[1], parts[2]); - return momentObj.valueOf(); -} - -function timeRangeErrorMsg(offset) { - return `Malformed timerange offset, expecting "timerange:", received: ${offset}`; -} - -/* - * Calculate offset when parameter is requesting a relative offset based on requested time range. - * - * @param {string} offset - offset parameter value - * @param {number} from - kibana global time 'from' in milliseconds - * @param {number} to - kibana global time 'to' in milliseconds - */ -export function preprocessOffset(offset, from, to) { - if (!offset.startsWith('timerange')) { - return offset; - } - - const parts = offset.split(':'); - if (parts.length === 1) { - throw new Error(timeRangeErrorMsg(offset)); - } - - const factor = parseFloat(parts[1]); - if (isNaN(factor)) { - throw new Error(timeRangeErrorMsg(offset)); - } - if (factor >= 0) { - throw new Error('Malformed timerange offset, factor must be negative number.'); - } - - const deltaSeconds = (to - from) / 1000; - const processedOffset = Math.round(deltaSeconds * factor); - return `${processedOffset}s`; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js deleted file mode 100644 index a535f30afaf99..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/offset_time.test.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import moment from 'moment'; -import { preprocessOffset } from './offset_time'; - -describe('offset', () => { - describe('preprocessOffset', () => { - const from = moment('2018-01-01T00:00:00.000Z').valueOf(); - const to = moment('2018-01-01T00:15:00.000Z').valueOf(); - - test('throws error when no number is provided', () => { - expect(() => preprocessOffset('timerange', from, to)).to.throwError(); - }); - - test('throws error when zero is provided', () => { - expect(() => preprocessOffset('timerange:0', from, to)).to.throwError(); - }); - - test('throws error when factor is larger than zero', () => { - expect(() => preprocessOffset('timerange:1', from, to)).to.throwError(); - }); - - test('throws error with malformed', () => { - expect(() => preprocessOffset('timerange:notANumber', from, to)).to.throwError(); - }); - - test('does not modify offset when value is not requesting relative offset', () => { - const offset = '-1d'; - expect(preprocessOffset(offset, from, to)).to.eql(offset); - }); - - test('converts offset when value is requesting relative offset with multiplier', () => { - const offset = 'timerange:-2'; - expect(preprocessOffset(offset, from, to)).to.eql('-1800s'); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js deleted file mode 100644 index e569b9b062218..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/process_function_definition.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(func) { - const functions = {}; - functions[func.name] = func; - if (func.aliases) { - _.each(func.aliases, function(alias) { - const aliasFn = _.clone(func); - aliasFn.isAlias = true; - functions[alias] = aliasFn; - }); - } - - return functions; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js deleted file mode 100644 index be36e0695bea6..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/reduce.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -function allSeriesContainKey(seriesList, key) { - const containsKeyInitialValue = true; - return seriesList.list.reduce((containsKey, series) => { - return containsKey && _.has(series, key); - }, containsKeyInitialValue); -} - -/** - * Pairwise reduce seriesList - * @params {seriesList} left - * @params {seriesList} right - * @params {Function} fn - Function used to combine points at same index in each array of each series in the seriesList. - * @return {seriesList} - */ -async function pairwiseReduce(left, right, fn) { - if (left.list.length !== right.list.length) { - throw new Error('Unable to pairwise reduce seriesLists, number of series are not the same'); - } - - let pairwiseField = 'label'; - if (allSeriesContainKey(left, 'split') && allSeriesContainKey(right, 'split')) { - pairwiseField = 'split'; - } - const indexedList = _.indexBy(right.list, pairwiseField); - - // ensure seriesLists contain same pairwise labels - left.list.forEach(leftSeries => { - if (!indexedList[leftSeries[pairwiseField]]) { - const rightSeriesLabels = right.list - .map(rightSeries => { - return `"${rightSeries[pairwiseField]}"`; - }) - .join(','); - throw new Error( - `Matching series could not be found for "${leftSeries[pairwiseField]}" in [${rightSeriesLabels}]` - ); - } - }); - - // pairwise reduce seriesLists - const pairwiseSeriesList = { type: 'seriesList', list: [] }; - left.list.forEach(async leftSeries => { - const first = { type: 'seriesList', list: [leftSeries] }; - const second = { type: 'seriesList', list: [indexedList[leftSeries[pairwiseField]]] }; - const reducedSeriesList = await reduce([first, second], fn); - const reducedSeries = reducedSeriesList.list[0]; - reducedSeries.label = leftSeries[pairwiseField]; - pairwiseSeriesList.list.push(reducedSeries); - }); - return pairwiseSeriesList; -} - -/** - * Reduces multiple arrays into a single array using a function - * @param {Array} args - args[0] must always be a {type: 'seriesList'} - * - * - If only arg[0] exists, the seriesList will be reduced to a seriesList containing a single series - * - If multiple arguments are passed, each argument will be mapped onto each series in the seriesList. - - * @params {Function} fn - Function used to combine points at same index in each array of each series in the seriesList. - * @return {seriesList} - */ -async function reduce(argsPromises, fn) { - const args = await Promise.all(argsPromises); - - const seriesList = args.shift(); - let argument = args.shift(); - - if (seriesList.type !== 'seriesList') { - throw new Error('input must be a seriesList'); - } - - if (_.isObject(argument) && argument.type === 'seriesList') { - if (argument.list.length > 1) { - return await pairwiseReduce(seriesList, argument, fn); - } else { - argument = argument.list[0]; - } - } - - function reduceSeries(series) { - return _.reduce(series, function(destinationObject, argument, i, p) { - let output = _.map(destinationObject.data, function(point, index) { - const value = point[1]; - - if (value == null) { - return [point[0], null]; - } - - if (_.isNumber(argument)) { - return [point[0], fn(value, argument, i, p)]; - } - - if (argument.data[index] == null || argument.data[index][1] == null) { - return [point[0], null]; - } - return [point[0], fn(value, argument.data[index][1], i, p)]; - }); - - // Output = single series - - output = { - data: output, - }; - output = _.defaults(output, destinationObject); - return output; - }); - } - - let reduced; - - if (argument != null) { - reduced = _.map(seriesList.list, function(series) { - return reduceSeries([series].concat(argument)); - }); - } else { - reduced = [reduceSeries(seriesList.list)]; - } - - seriesList.list = reduced; - return seriesList; -} - -export default reduce; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js deleted file mode 100644 index 398a96c359e53..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/split_interval.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export default function splitInterval(interval) { - if (!interval.match(/[0-9]+[mshdwMy]+/g)) { - throw new Error('Malformed `interval`: ' + interval); - } - const parts = interval.match(/[0-9]+|[mshdwMy]+/g); - - return { - count: parts[0], - unit: parts[1], - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js deleted file mode 100644 index 0d62d848daba5..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/to_milliseconds.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import moment from 'moment'; - -// map of moment's short/long unit ids and elasticsearch's long unit ids -// to their value in milliseconds -const vals = _.transform( - [ - ['ms', 'milliseconds', 'millisecond'], - ['s', 'seconds', 'second', 'sec'], - ['m', 'minutes', 'minute', 'min'], - ['h', 'hours', 'hour'], - ['d', 'days', 'day'], - ['w', 'weeks', 'week'], - ['M', 'months', 'month'], - ['quarter'], - ['y', 'years', 'year'], - ], - function(vals, units) { - const normal = moment.normalizeUnits(units[0]); - const val = moment.duration(1, normal).asMilliseconds(); - [].concat(normal, units).forEach(function(unit) { - vals[unit] = val; - }); - }, - {} -); -// match any key from the vals object preceded by an optional number -const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); - -export default function(expr) { - const match = expr.match(parseRE); - if (match) { - if (match[2] === 'M' && match[1] !== '1') { - throw new Error('Invalid interval. 1M is only valid monthly interval.'); - } - - return parseFloat(match[1] || 1) * vals[match[2]]; - } -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js b/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js deleted file mode 100644 index 071e3873ccb30..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/lib/unzipPairs.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function unzipPairs(timeValObject) { - const paired = _.chain(timeValObject) - .pairs() - .map(function(point) { - return [parseInt(point[0], 10), point[1]]; - }) - .sortBy(function(point) { - return point[0]; - }) - .value(); - return paired; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/server/plugin.ts deleted file mode 100644 index c94277ebc7adc..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/plugin.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Legacy } from 'kibana'; -import { i18n } from '@kbn/i18n'; -import { PluginInitializerContext, CoreSetup } from 'kibana/server'; - -import loadFunctions, { LoadFunctions } from './lib/load_functions'; -import { initRoutes } from './routes'; - -function getFunction(functions: LoadFunctions, name: string) { - if (functions[name]) { - return functions[name]; - } - - throw new Error( - i18n.translate('timelion.noFunctionErrorMessage', { - defaultMessage: 'No such function: {name}', - values: { name }, - }) - ); -} - -// TODO: Remove as CoreSetup is completed. -export interface CustomCoreSetup { - http: { - server: Legacy.Server; - }; -} - -export class TimelionServerPlugin { - public initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } - - public setup(core: CoreSetup & CustomCoreSetup) { - const { server } = core.http; - const functions = loadFunctions('series_functions'); - - server.expose('functions', functions); - server.expose('getFunction', (name: string) => getFunction(functions, name)); - - initRoutes(server); - } -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js deleted file mode 100644 index 813d006225f43..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/functions.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export function functionsRoute(server) { - server.route({ - method: 'GET', - path: '/api/timelion/functions', - handler: () => { - const functionArray = _.map(server.plugins.timelion.functions, function(val, key) { - // TODO: This won't work on frozen objects, it should be removed when everything is converted to datasources and chainables - return _.extend({}, val, { name: key }); - }); - - return _.sortBy(functionArray, 'name'); - }, - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts b/src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts deleted file mode 100644 index b6438c81ef002..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Legacy } from 'kibana'; - -// @ts-ignore -import { runRoute } from './run'; -// @ts-ignore -import { functionsRoute } from './functions'; -// @ts-ignore -import { validateEsRoute } from './validate_es'; - -export function initRoutes(server: Legacy.Server) { - runRoute(server); - functionsRoute(server); - validateEsRoute(server); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts b/src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts deleted file mode 100644 index 17f87825cd8b0..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/run.ts +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import Joi from 'joi'; -import Bluebird from 'bluebird'; -import _ from 'lodash'; -import { Legacy } from 'kibana'; -// @ts-ignore -import chainRunnerFn from '../handlers/chain_runner.js'; -// @ts-ignore -import getNamespacesSettings from '../lib/get_namespaced_settings'; -// @ts-ignore -import getTlConfig from '../handlers/lib/tl_config'; - -const timelionDefaults = getNamespacesSettings(); - -export interface TimelionRequestQuery { - payload: { - sheet: string[]; - extended?: { - es: { - filter: { - bool: { - filter: string[] | object; - must: string[]; - should: string[]; - must_not: string[]; - }; - }; - }; - }; - }; - time?: { - from?: string; - interval: string; - timezone: string; - to?: string; - }; -} - -function formatErrorResponse(e: Error, h: Legacy.ResponseToolkit) { - return h - .response({ - title: e.toString(), - message: e.toString(), - }) - .code(500); -} - -const requestPayload = { - payload: Joi.object({ - sheet: Joi.array() - .items(Joi.string()) - .required(), - extended: Joi.object({ - es: Joi.object({ - filter: Joi.object({ - bool: Joi.object({ - filter: Joi.array().allow(null), - must: Joi.array().allow(null), - should: Joi.array().allow(null), - must_not: Joi.array().allow(null), - }), - }), - }), - }).optional(), - time: Joi.object({ - from: Joi.string(), - interval: Joi.string().required(), - timezone: Joi.string().required(), - to: Joi.string(), - }).required(), - }), -}; - -export function runRoute(server: Legacy.Server) { - server.route({ - method: 'POST', - path: '/api/timelion/run', - options: { - validate: requestPayload, - }, - handler: async (request: Legacy.Request & TimelionRequestQuery, h: Legacy.ResponseToolkit) => { - try { - const uiSettings = await request.getUiSettingsService().getAll(); - - const tlConfig = getTlConfig({ - server, - request, - settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. - }); - const chainRunner = chainRunnerFn(tlConfig); - const sheet = await Bluebird.all(chainRunner.processRequest(request.payload)); - - return { - sheet, - stats: chainRunner.getStats(), - }; - } catch (err) { - server.log(['timelion', 'error'], `${err.toString()}: ${err.stack}`); - // TODO Maybe we should just replace everywhere we throw with Boom? Probably. - if (err.isBoom) { - return err; - } else { - return formatErrorResponse(err, h); - } - } - }, - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js b/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js deleted file mode 100644 index 5e39069f2a698..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/routes/validate_es.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export function validateEsRoute(server) { - server.route({ - method: 'GET', - path: '/api/timelion/validate/es', - handler: async function(request) { - const uiSettings = await request.getUiSettingsService().getAll(); - - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - - const timefield = uiSettings['timelion:es.timefield']; - - const body = { - index: uiSettings['es.default_index'], - body: { - aggs: { - maxAgg: { - max: { - field: timefield, - }, - }, - minAgg: { - min: { - field: timefield, - }, - }, - }, - size: 0, - }, - }; - - let resp = {}; - try { - resp = await callWithRequest(request, 'search', body); - } catch (errResp) { - resp = errResp; - } - - if (_.has(resp, 'aggregations.maxAgg.value') && _.has(resp, 'aggregations.minAgg.value')) { - return { - ok: true, - field: timefield, - min: _.get(resp, 'aggregations.minAgg.value'), - max: _.get(resp, 'aggregations.maxAgg.value'), - }; - } - - return { - ok: false, - resp: resp, - }; - }, - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js deleted file mode 100644 index 28538d4da2f79..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/abs.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../abs`); - -import _ from 'lodash'; -const expect = require('chai').expect; -const seriesList = require('./fixtures/seriesList.js')(); -import invoke from './helpers/invoke_series_fn.js'; - -describe('abs.js', function() { - it('should return the positive value of every value', function() { - return invoke(fn, [seriesList]).then(function(result) { - const before = _.filter(result.input[0].list[0].data, function(point) { - return point[1] < 0; - }); - - const after = _.filter(result.output.list[0].data, function(point) { - return point[1] < 0; - }); - - expect(before.length > 0).to.eql(true); - expect(result.output.list[0].data.length > 0).to.eql(true); - expect(after.length).to.eql(0); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js deleted file mode 100644 index 6177f7cb7bac4..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/aggregate.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const filename = require('path').basename(__filename); -const fn = require(`../aggregate/index.js`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe(filename, () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('first', () => { - return invoke(fn, [seriesList, 'first']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([100, 100, 100, 100]); - }); - }); - - it('last', () => { - return invoke(fn, [seriesList, 'last']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([20, 20, 20, 20]); - }); - }); - - it('min', () => { - return invoke(fn, [seriesList, 'min']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([20, 20, 20, 20]); - }); - }); - - it('max', () => { - return invoke(fn, [seriesList, 'max']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([100, 100, 100, 100]); - }); - }); - - it('sum', () => { - return invoke(fn, [seriesList, 'sum']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([220, 220, 220, 220]); - }); - }); - - it('cardinality', () => { - return invoke(fn, [seriesList, 'cardinality']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([3, 3, 3, 3]); - }); - }); - - it('avg', () => { - return invoke(fn, [seriesList, 'avg']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([55, 55, 55, 55]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js deleted file mode 100644 index 90b66759f7341..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/bars.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../bars`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('bars.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('creates the bars property, with defaults, on all series', () => { - return invoke(fn, [seriesList]).then(r => { - const bars = _.map(r.output.list, 'bars'); - _.each(bars, bar => expect(bar).to.be.a('object')); - _.each(bars, bar => expect(bar.lineWidth).to.equal(6)); - _.each(bars, bar => expect(bar.show).to.equal(1)); - }); - }); - - it('leaves existing bars alone when called without option, if they exist', () => { - seriesList.list[0].bars = { foo: true }; - return invoke(fn, [seriesList]).then(r => { - const bars = _.map(r.output.list, 'bars'); - expect(bars[0].foo).to.equal(true); - expect(bars[1].foo).to.equal(undefined); - }); - }); - - it('sets lineWidth and show to the same value', () => { - return invoke(fn, [seriesList, 0]).then(r => { - const bars = _.map(r.output.list, 'bars'); - expect(bars[0].lineWidth).to.equal(0); - expect(bars[0].show).to.equal(0); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js deleted file mode 100644 index f333a39bec5ba..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/color.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../color`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('color.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('sets the color, on all series', () => { - return invoke(fn, [seriesList, '#eee']).then(r => { - const colors = _.map(r.output.list, 'color'); - _.each(colors, color => expect(color).to.equal('#eee')); - }); - }); - - it('generates a gradient', () => { - const expected = ['#000000', '#111111', '#222222', '#333333']; - const fourLongList = { - type: 'seriesList', - list: seriesList.list.slice(0, 4), - }; - return invoke(fn, [fourLongList, '#000:#333']).then(r => { - const colors = _.map(r.output.list, 'color'); - _.each(colors, (color, i) => expect(color).to.equal(expected[i])); - }); - }); - - it('should handle more colors than number of series', async () => { - const colorsArg = '#000:#111:#222:#333:#444:#555'; - const numColors = colorsArg.split(':').length; - expect(numColors).to.be.above(seriesList.list.length); - - const r = await invoke(fn, [seriesList, colorsArg]); - const seriesColors = _.map(r.output.list, 'color'); - expect(seriesColors).to.eql(['#000000', '#111111', '#222222', '#333333', '#444444']); - }); - - it('should work with series.length=1 and more colors', async () => { - const oneLongList = { - type: 'seriesList', - list: seriesList.list.slice(0, 1), - }; - const colorsArg = '#000:#111'; - - const r = await invoke(fn, [oneLongList, colorsArg]); - const seriesColors = _.map(r.output.list, 'color'); - expect(seriesColors).to.eql(['#000']); - }); - - it('throws if you do not pass a color', () => { - invoke(fn, [seriesList, '']).catch(e => { - expect(e).to.be.an(Error); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js deleted file mode 100644 index 533c5adfd62ab..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/condition.js +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../condition`); -import moment from 'moment'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; -import getSeriesList from './helpers/get_single_series_list'; -import _ from 'lodash'; - -describe('condition.js', function() { - let comparable; - let seriesList; - beforeEach(function() { - seriesList = require('./fixtures/seriesList.js')(); - comparable = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 12], - [moment.utc('1981-01-01T00:00:00.000Z'), 33], - [moment.utc('1982-01-01T00:00:00.000Z'), 82], - [moment.utc('1983-01-01T00:00:00.000Z'), -101], - ]); - }); - - describe('a single number with', function() { - it('eq', function() { - return invoke(fn, [seriesList, 'eq', 17, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 0, 82, 20]); - }); - }); - - it('ne', function() { - return invoke(fn, [seriesList, 'ne', 17, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 17, 0, 0]); - }); - }); - - it('gte', function() { - return invoke(fn, [seriesList, 'gte', 17, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 0, 0, 0]); - }); - }); - - it('gt', function() { - return invoke(fn, [seriesList, 'gt', 17, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 17, 0, 0]); - }); - }); - - it('lt', function() { - return invoke(fn, [seriesList, 'lt', 17, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 17, 82, 20]); - }); - }); - - it('lte', function() { - return invoke(fn, [seriesList, 'lte', 17, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 20]); - }); - }); - }); - - it('can compare against another series', function() { - return invoke(fn, [seriesList, 'ne', comparable, 0]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 0]); - }); - }); - - it('can set the resultant value to that of another series', function() { - return invoke(fn, [seriesList, 'lt', comparable, comparable]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([12, 33, 82, 20]); - }); - }); - - it('can set the resultant value to null', function() { - return invoke(fn, [seriesList, 'lt', 17, null]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([null, 17, 82, 20]); - }); - }); - - describe('else', function() { - it('has else', function() { - return invoke(fn, [seriesList, 'lt', 30, 0, 1]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 1, 0]); - }); - }); - - it('works with other series', function() { - return invoke(fn, [seriesList, 'lt', 30, 0, comparable]).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 82, 0]); - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js deleted file mode 100644 index d9f534555b9d7..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/cusum.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../cusum`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('cusum.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('progressively adds the numbers in the list', () => { - return invoke(fn, [seriesList]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([100, 150, 200, 220]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js deleted file mode 100644 index 88ef4778ef2f1..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/derivative.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../derivative`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('derivative.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('gets the change in the set', () => { - return invoke(fn, [seriesList]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([null, -50, 0, -30]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js deleted file mode 100644 index afe531922522f..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/divide.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../divide`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('divide.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('divides by a single number', () => { - return invoke(fn, [seriesList, 2]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([50, 25, 25, 10]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js deleted file mode 100644 index f2b364afb723b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/es.js +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const filename = require('path').basename(__filename); -import es from '../es'; - -import tlConfigFn from './fixtures/tlConfig'; -import * as aggResponse from '../es/lib/agg_response_to_series_list'; -import buildRequest from '../es/lib/build_request'; -import createDateAgg from '../es/lib/create_date_agg'; -import esResponse from './fixtures/es_response'; - -import Bluebird from 'bluebird'; -import _ from 'lodash'; -import { expect } from 'chai'; -import sinon from 'sinon'; -import invoke from './helpers/invoke_series_fn.js'; - -function stubRequestAndServer(response, indexPatternSavedObjects = []) { - return { - server: { - plugins: { - elasticsearch: { - getCluster: sinon - .stub() - .withArgs('data') - .returns({ - callWithRequest: function() { - return Bluebird.resolve(response); - }, - }), - }, - }, - }, - request: { - getSavedObjectsClient: function() { - return { - find: function() { - return Bluebird.resolve({ - saved_objects: indexPatternSavedObjects, - }); - }, - }; - }, - }, - }; -} - -describe(filename, () => { - let tlConfig; - - describe('seriesList processor', () => { - it('throws an error then the index is missing', () => { - tlConfig = stubRequestAndServer({ - _shards: { total: 0 }, - }); - return invoke(es, [5], tlConfig) - .then(expect.fail) - .catch(e => { - expect(e).to.be.an('error'); - }); - }); - - it('returns a seriesList', () => { - tlConfig = stubRequestAndServer(esResponse); - return invoke(es, [5], tlConfig).then(r => { - expect(r.output.type).to.eql('seriesList'); - }); - }); - }); - - describe('createDateAgg', () => { - let tlConfig; - let config; - let agg; - beforeEach(() => { - tlConfig = tlConfigFn(); - config = { - timefield: '@timestamp', - interval: '1y', - }; - agg = createDateAgg(config, tlConfig); - }); - - it('creates a date_histogram with meta.type of time_buckets', () => { - expect(agg.time_buckets.meta.type).to.eql('time_buckets'); - expect(agg.time_buckets.date_histogram).to.be.an('object'); - }); - - it('has extended_bounds that match tlConfig', () => { - expect(agg.time_buckets.date_histogram.extended_bounds.min).to.equal(tlConfig.time.from); - expect(agg.time_buckets.date_histogram.extended_bounds.max).to.equal(tlConfig.time.to); - }); - - it('sets the timezone', () => { - expect(agg.time_buckets.date_histogram.time_zone).to.equal('Etc/UTC'); - }); - - it('sets the field and interval', () => { - expect(agg.time_buckets.date_histogram.field).to.equal('@timestamp'); - expect(agg.time_buckets.date_histogram.interval).to.equal('1y'); - }); - - it('sets min_doc_count to 0', () => { - expect(agg.time_buckets.date_histogram.min_doc_count).to.equal(0); - }); - - describe('metric aggs', () => { - const emptyScriptedFields = []; - - it('adds a metric agg for each metric', () => { - config.metric = ['sum:beer', 'avg:bytes', 'percentiles:bytes']; - agg = createDateAgg(config, tlConfig, emptyScriptedFields); - expect(agg.time_buckets.aggs['sum(beer)']).to.eql({ sum: { field: 'beer' } }); - expect(agg.time_buckets.aggs['avg(bytes)']).to.eql({ avg: { field: 'bytes' } }); - expect(agg.time_buckets.aggs['percentiles(bytes)']).to.eql({ - percentiles: { field: 'bytes' }, - }); - }); - - it('adds a scripted metric agg for each scripted metric', () => { - config.metric = ['avg:scriptedBytes']; - const scriptedFields = [ - { - name: 'scriptedBytes', - script: 'doc["bytes"].value', - lang: 'painless', - }, - ]; - agg = createDateAgg(config, tlConfig, scriptedFields); - expect(agg.time_buckets.aggs['avg(scriptedBytes)']).to.eql({ - avg: { - script: { - source: 'doc["bytes"].value', - lang: 'painless', - }, - }, - }); - }); - - it('has a special `count` metric that uses a script', () => { - config.metric = ['count']; - agg = createDateAgg(config, tlConfig, emptyScriptedFields); - expect(agg.time_buckets.aggs.count.bucket_script).to.be.an('object'); - expect(agg.time_buckets.aggs.count.bucket_script.buckets_path).to.eql('_count'); - }); - }); - }); - - describe('buildRequest', () => { - const fn = buildRequest; - const emptyScriptedFields = []; - let tlConfig; - let config; - beforeEach(() => { - tlConfig = tlConfigFn(); - config = { - timefield: '@timestamp', - interval: '1y', - index: 'beer', - }; - }); - - it('sets the index on the request', () => { - config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields); - - expect(request.index).to.equal('beer'); - }); - - it('always sets body.size to 0', () => { - const request = fn(config, tlConfig, emptyScriptedFields); - - expect(request.body.size).to.equal(0); - }); - - it('creates a filters agg that contains each of the queries passed', () => { - config.q = ['foo', 'bar']; - const request = fn(config, tlConfig, emptyScriptedFields); - - expect(request.body.aggs.q.meta.type).to.equal('split'); - - const filters = request.body.aggs.q.filters.filters; - expect(filters.foo.query_string.query).to.eql('foo'); - expect(filters.bar.query_string.query).to.eql('bar'); - }); - - describe('timeouts', () => { - it('sets the timeout on the request', () => { - config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields, 30000); - - expect(request.timeout).to.equal('30000ms'); - }); - - it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { - config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields, 0); - - expect(request).to.not.have.property('timeout'); - }); - }); - - describe('frozen indices', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('sets ignore_throttled=true on the request', () => { - config.index = 'beer'; - tlConfig.settings['search:includeFrozen'] = false; - const request = fn(config, tlConfig, emptyScriptedFields); - - expect(request.ignore_throttled).to.equal(true); - }); - - it('sets no timeout if elasticsearch.shardTimeout is set to 0', () => { - tlConfig.settings['search:includeFrozen'] = true; - config.index = 'beer'; - const request = fn(config, tlConfig, emptyScriptedFields); - - expect(request.ignore_throttled).to.equal(false); - }); - }); - - describe('query body', () => { - beforeEach(() => { - tlConfig = _.merge(tlConfigFn(), { - time: { - from: 1, - to: 5, - }, - request: { - payload: { - extended: { - es: { - filter: { - bool: { - must: [{ query: { query_string: { query: 'foo' } } }], - must_not: [ - { query: { query_string: { query: 'bar' } } }, - { query: { query_string: { query: 'baz' } } }, - ], - }, - }, - }, - }, - }, - }, - }); - }); - - it('adds the contents of payload.extended.es.filter to a filter clause of the bool', () => { - config.kibana = true; - const request = fn(config, tlConfig, emptyScriptedFields); - const filter = request.body.query.bool.filter.bool; - expect(filter.must.length).to.eql(1); - expect(filter.must_not.length).to.eql(2); - }); - - it('does not include filters if config.kibana = false', () => { - config.kibana = false; - const request = fn(config, tlConfig, emptyScriptedFields); - expect(request.body.query.bool.filter).to.eql(undefined); - }); - - it('adds a time filter to the bool querys must clause', () => { - let request = fn(config, tlConfig, emptyScriptedFields); - expect(request.body.query.bool.must.length).to.eql(1); - expect(request.body.query.bool.must[0]).to.eql({ - range: { - '@timestamp': { - format: 'strict_date_optional_time', - gte: '1970-01-01T00:00:00.001Z', - lte: '1970-01-01T00:00:00.005Z', - }, - }, - }); - - config.kibana = true; - request = fn(config, tlConfig, emptyScriptedFields); - expect(request.body.query.bool.must.length).to.eql(1); - }); - }); - - describe('config.split', () => { - it('adds terms aggs, in order, under the filters agg', () => { - config.split = ['beer:5', 'wine:10']; - const request = fn(config, tlConfig, emptyScriptedFields); - - const aggs = request.body.aggs.q.aggs; - - expect(aggs.beer.meta.type).to.eql('split'); - expect(aggs.beer.terms.field).to.eql('beer'); - expect(aggs.beer.terms.size).to.eql(5); - - expect(aggs.beer.aggs.wine.meta.type).to.eql('split'); - expect(aggs.beer.aggs.wine.terms.field).to.eql('wine'); - expect(aggs.beer.aggs.wine.terms.size).to.eql(10); - }); - - it('adds scripted terms aggs, in order, under the filters agg', () => { - config.split = ['scriptedBeer:5', 'scriptedWine:10']; - const scriptedFields = [ - { - name: 'scriptedBeer', - script: 'doc["beer"].value', - lang: 'painless', - }, - { - name: 'scriptedWine', - script: 'doc["wine"].value', - lang: 'painless', - }, - ]; - const request = fn(config, tlConfig, scriptedFields); - - const aggs = request.body.aggs.q.aggs; - - expect(aggs.scriptedBeer.meta.type).to.eql('split'); - expect(aggs.scriptedBeer.terms.script).to.eql({ - source: 'doc["beer"].value', - lang: 'painless', - }); - expect(aggs.scriptedBeer.terms.size).to.eql(5); - - expect(aggs.scriptedBeer.aggs.scriptedWine.meta.type).to.eql('split'); - expect(aggs.scriptedBeer.aggs.scriptedWine.terms.script).to.eql({ - source: 'doc["wine"].value', - lang: 'painless', - }); - expect(aggs.scriptedBeer.aggs.scriptedWine.terms.size).to.eql(10); - }); - }); - }); - - describe('Aggregation flattening', () => { - let config; - beforeEach(() => { - config = { fit: 'nearest' }; - }); - - describe('timeBucketsToPairs', () => { - const fn = aggResponse.timeBucketsToPairs; - - it('Should convert a single metric agg', () => { - const buckets = [ - { key: 1000, count: { value: 3 } }, - { key: 2000, count: { value: 14 } }, - { key: 3000, count: { value: 15 } }, - ]; - - expect(fn(buckets)).to.eql({ - count: [ - [1000, 3], - [2000, 14], - [3000, 15], - ], - }); - }); - - it('Should convert multiple metric aggs', () => { - const buckets = [ - { key: 1000, count: { value: 3 }, max: { value: 92 } }, - { key: 2000, count: { value: 14 }, max: { value: 65 } }, - { key: 3000, count: { value: 15 }, max: { value: 35 } }, - ]; - - expect(fn(buckets)).to.eql({ - count: [ - [1000, 3], - [2000, 14], - [3000, 15], - ], - max: [ - [1000, 92], - [2000, 65], - [3000, 35], - ], - }); - }); - - it('Should convert percentiles metric aggs', () => { - const buckets = [ - { - key: 1000, - percentiles: { values: { '50.0': 'NaN', '75.0': 65, '95.0': 73, '99.0': 75 } }, - }, - { - key: 2000, - percentiles: { values: { '50.0': 25, '75.0': 32, '95.0': 'NaN', '99.0': 67 } }, - }, - { - key: 3000, - percentiles: { values: { '50.0': 15, '75.0': 15, '95.0': 15, '99.0': 15 } }, - }, - ]; - - expect(fn(buckets)).to.eql({ - 'percentiles:50.0': [ - [1000, NaN], - [2000, 25], - [3000, 15], - ], - 'percentiles:75.0': [ - [1000, 65], - [2000, 32], - [3000, 15], - ], - 'percentiles:95.0': [ - [1000, 73], - [2000, NaN], - [3000, 15], - ], - 'percentiles:99.0': [ - [1000, 75], - [2000, 67], - [3000, 15], - ], - }); - }); - }); - - it('should throw an error', () => { - expect(aggResponse.default(esResponse.aggregations, config)).to.eql([ - { - data: [ - [1000, 264], - [2000, 264], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueA > FieldB:Value2A > MetricA', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 398], - [2000, 1124], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueA > FieldB:Value2A > MetricB', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 699], - [2000, 110], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueA > FieldB:Value2B > MetricA', - split: 'Value2B', - type: 'series', - }, - { - data: [ - [1000, 457], - [2000, 506], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueA > FieldB:Value2B > MetricB', - split: 'Value2B', - type: 'series', - }, - { - data: [ - [1000, 152], - [2000, 518], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueB > FieldB:Value2B > MetricA', - split: 'Value2B', - type: 'series', - }, - { - data: [ - [1000, 61], - [2000, 77], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueB > FieldB:Value2B > MetricB', - split: 'Value2B', - type: 'series', - }, - { - data: [ - [1000, 114], - [2000, 264], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueB > FieldB:Value2A > MetricA', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 23], - [2000, 45], - ], - fit: 'nearest', - label: 'q:QueryA > FieldA:ValueB > FieldB:Value2A > MetricB', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 621], - [2000, 751], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueA > FieldB:Value2B > MetricA', - split: 'Value2B', - type: 'series', - }, - { - data: [ - [1000, 12], - [2000, 12], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueA > FieldB:Value2B > MetricB', - split: 'Value2B', - type: 'series', - }, - { - data: [ - [1000, 110], - [2000, 648], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueA > FieldB:Value2A > MetricA', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 11], - [2000, 12], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueA > FieldB:Value2A > MetricB', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 755], - [2000, 713], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueC > FieldB:Value2C > MetricA', - split: 'Value2C', - type: 'series', - }, - { - data: [ - [1000, 10], - [2000, 18], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueC > FieldB:Value2C > MetricB', - split: 'Value2C', - type: 'series', - }, - { - data: [ - [1000, 391], - [2000, 802], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueC > FieldB:Value2A > MetricA', - split: 'Value2A', - type: 'series', - }, - { - data: [ - [1000, 4], - [2000, 4], - ], - fit: 'nearest', - label: 'q:QueryB > FieldA:ValueC > FieldB:Value2A > MetricB', - split: 'Value2A', - type: 'series', - }, - ]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js deleted file mode 100644 index 3e86554e843b0..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/first.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../first`); - -const expect = require('chai').expect; -const seriesList = require('./fixtures/seriesList.js')(); -import invoke from './helpers/invoke_series_fn.js'; - -describe('first.js', function() { - it('should return exactly the data input', function() { - return invoke(fn, [seriesList]).then(function(result) { - expect(result.input[0]).to.eql(result.output); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js deleted file mode 100644 index db9360da3f592..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fit.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../fit`); -import moment from 'moment'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; -import getSeriesList from './helpers/get_single_series_list'; -import _ from 'lodash'; - -describe('fit.js', function() { - describe('should not filter out zeros', function() { - it('all zeros', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 0], - [moment.utc('1981-01-01T00:00:00.000Z'), null], - [moment.utc('1982-01-01T00:00:00.000Z'), 0], - [moment.utc('1983-01-01T00:00:00.000Z'), 0], - ]); - - return invoke(fn, [seriesList, 'carry']).then(function(r) { - expect(r.input[0].list[0].data[1][1]).to.equal(null); - expect(_.map(r.output.list[0].data, 1)).to.eql([0, 0, 0, 0]); - expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); - }); - }); - - it('mixed zeros and numbers', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 26], - [moment.utc('1981-01-01T00:00:00.000Z'), 42], - [moment.utc('1982-01-01T00:00:00.000Z'), 0], - [moment.utc('1983-01-01T00:00:00.000Z'), null], - [moment.utc('1984-01-01T00:00:00.000Z'), 1], - ]); - - return invoke(fn, [seriesList, 'carry']).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([26, 42, 0, 0, 1]); - }); - }); - }); - - it('should return original series when all values are null', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), null], - [moment.utc('1981-01-01T00:00:00.000Z'), null], - [moment.utc('1982-01-01T00:00:00.000Z'), null], - [moment.utc('1983-01-01T00:00:00.000Z'), null], - ]); - - return invoke(fn, [seriesList, 'carry']).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, null, null]); - }); - }); - - describe('carry', function() { - it('should maintain the previous value until it changes', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 5], - [moment.utc('1981-01-01T00:00:00.000Z'), null], - [moment.utc('1982-01-01T00:00:00.000Z'), 3.4], - [moment.utc('1983-01-01T00:00:00.000Z'), 171], - ]); - - return invoke(fn, [seriesList, 'carry']).then(function(r) { - expect(r.input[0].list[0].data[1][1]).to.equal(null); - expect(_.map(r.output.list[0].data, 1)).to.eql([5, 5, 3.4, 171]); - expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); - }); - }); - }); - - describe('nearest', function() { - it('should use the closest temporal value to fill the null', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 5], - [moment.utc('1981-01-01T00:00:00.000Z'), null], - [moment.utc('1981-05-01T00:00:00.000Z'), 3.4], - [moment.utc('1983-01-01T00:00:00.000Z'), 171], - ]); - - return invoke(fn, [seriesList, 'nearest']).then(function(r) { - expect(r.input[0].list[0].data[1][1]).to.equal(null); - expect(_.map(r.output.list[0].data, 1)).to.eql([5, 3.4, 3.4, 171]); - expect(r.output.list[0].data[1][0]).to.not.equal(r.output.list[0].data[0][0]); - }); - }); - }); - - describe('average', function() { - it('should produce a smooth, straight line between points', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 10], - [moment.utc('1981-07-01T00:00:00.000Z'), null], - [moment.utc('1982-01-01T00:00:00.000Z'), null], - [moment.utc('1983-01-01T00:00:00.000Z'), 40], - [moment.utc('1984-01-01T00:00:00.000Z'), 50], - ]); - - return invoke(fn, [seriesList, 'average']).then(function(r) { - expect(r.input[0].list[0].data[1][1]).to.eql(null); - expect(_.map(r.output.list[0].data, 1)).to.eql([10, 20, 30, 40, 50]); - }); - }); - }); - - describe('scale', function() { - it('should distribute the next points value across the preceeding nulls', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 10], - [moment.utc('1981-07-01T00:00:00.000Z'), null], - [moment.utc('1982-01-01T00:00:00.000Z'), null], - [moment.utc('1983-01-01T00:00:00.000Z'), 60], - [moment.utc('1984-01-01T00:00:00.000Z'), 50], - ]); - - return invoke(fn, [seriesList, 'scale']).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([10, 20, 20, 20, 50]); - }); - }); - }); - - describe('none', function() { - it('basically just drops the nulls. This is going to screw you', function() { - const seriesList = getSeriesList('', [ - [moment.utc('1980-01-01T00:00:00.000Z'), 10], - [moment.utc('1981-07-01T00:00:00.000Z'), null], - [moment.utc('1982-01-01T00:00:00.000Z'), null], - [moment.utc('1983-01-01T00:00:00.000Z'), 40], - [moment.utc('1984-01-01T00:00:00.000Z'), 50], - ]); - - return invoke(fn, [seriesList, 'none']).then(function(r) { - expect(_.map(r.output.list[0].data, 1)).to.eql([10, 40, 50]); - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js deleted file mode 100644 index 4e49510d70fb8..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/bucketList.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import moment from 'moment'; - -export default [ - moment('1980-01-01T00:00:00.000Z'), - moment('1981-01-01T00:00:00.000Z'), - moment('1982-01-01T00:00:00.000Z'), - moment('1983-01-01T00:00:00.000Z'), -]; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js deleted file mode 100644 index 65aed311e232b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/es_response.js +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - Really didn't want to do this, but testing the agg flatten logic - in units isn't really possible since the functions depend on each other - - You ValueBn test each function, but at the end you really have to check the - output of the entire thing. -*/ - -export default { - _shards: { - total: 1, - }, - aggregations: { - q: { - meta: { type: 'split' }, - buckets: { - QueryA: { - FieldA: { - meta: { type: 'split' }, - buckets: [ - { - key: 'ValueA', - FieldB: { - meta: { type: 'split' }, - buckets: [ - { - key: 'Value2A', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 264 }, - MetricB: { value: 398 }, - }, - { - key: 2000, - MetricA: { value: 264 }, - MetricB: { value: 1124 }, - }, - ], - }, - }, - { - key: 'Value2B', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 699 }, - MetricB: { value: 457 }, - }, - { - key: 2000, - MetricA: { value: 110 }, - MetricB: { value: 506 }, - }, - ], - }, - }, - ], - }, - }, - { - key: 'ValueB', - FieldB: { - meta: { type: 'split' }, - buckets: [ - { - key: 'Value2B', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 152 }, - MetricB: { value: 61 }, - }, - { - key: 2000, - MetricA: { value: 518 }, - MetricB: { value: 77 }, - }, - ], - }, - }, - { - key: 'Value2A', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 114 }, - MetricB: { value: 23 }, - }, - { - key: 2000, - MetricA: { value: 264 }, - MetricB: { value: 45 }, - }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - QueryB: { - FieldA: { - meta: { type: 'split' }, - buckets: [ - { - key: 'ValueA', - FieldB: { - meta: { type: 'split' }, - buckets: [ - { - key: 'Value2B', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 621 }, - MetricB: { value: 12 }, - }, - { - key: 2000, - MetricA: { value: 751 }, - MetricB: { value: 12 }, - }, - ], - }, - }, - { - key: 'Value2A', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 110 }, - MetricB: { value: 11 }, - }, - { - key: 2000, - MetricA: { value: 648 }, - MetricB: { value: 12 }, - }, - ], - }, - }, - ], - }, - }, - { - key: 'ValueC', - FieldB: { - meta: { type: 'split' }, - buckets: [ - { - key: 'Value2C', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 755 }, - MetricB: { value: 10 }, - }, - { - key: 2000, - MetricA: { value: 713 }, - MetricB: { value: 18 }, - }, - ], - }, - }, - { - key: 'Value2A', - time_buckets: { - meta: { type: 'time_buckets' }, - buckets: [ - { - key: 1000, - MetricA: { value: 391 }, - MetricB: { value: 4 }, - }, - { - key: 2000, - MetricA: { value: 802 }, - MetricB: { value: 4 }, - }, - ], - }, - }, - ], - }, - }, - ], - }, - }, - }, - }, - }, -}; diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js deleted file mode 100644 index 29b759af521ed..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/seriesList.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import buckets from './bucketList'; -import getSeries from '../helpers/get_series'; -import getSeriesList from '../helpers/get_series_list'; - -export default function() { - return getSeriesList([ - getSeries('Negative', buckets, [-51, 17, 82, 20]), - getSeries('Nice', buckets, [100, 50, 50, 20]), - getSeries('All the same', buckets, [1, 1, 1, 1]), - getSeries('Decimals', buckets, [3.1415926535, 2, 1.439, 0.3424235]), - getSeries('PowerOfTen', buckets, [10, 100, 10, 1]), - ]); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js deleted file mode 100644 index 6eea99424c4ab..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/fixtures/tlConfig.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import moment from 'moment'; -import { of } from 'rxjs'; -import sinon from 'sinon'; -import timelionDefaults from '../../../lib/get_namespaced_settings'; -import esResponse from './es_response'; - -export default function() { - const functions = require('../../../lib/load_functions')('series_functions'); - const kibanaServerConfigs = { - 'timelion.graphiteUrls': ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite'], - }; - const server = { - plugins: { - timelion: { - getFunction: name => { - if (!functions[name]) throw new Error('No such function: ' + name); - return functions[name]; - }, - }, - elasticsearch: { - getCluster: sinon - .stub() - .withArgs('data') - .returns({ - callWithRequest: function() { - return Promise.resolve(esResponse); - }, - }), - }, - }, - newPlatform: { - __internals: { - elasticsearch: { - legacy: { config$: of({ shardTimeout: moment.duration(30000) }) }, - }, - }, - }, - config: () => ({ get: key => kibanaServerConfigs[key] }), - }; - - const tlConfig = require('../../../handlers/lib/tl_config.js')({ - server, - request: {}, - }); - - tlConfig.time = { - interval: '1y', - from: moment('1980-01-01T00:00:00Z').valueOf(), - to: moment('1983-01-01T00:00:00Z').valueOf(), - timezone: 'Etc/UTC', - }; - - tlConfig.settings = timelionDefaults(); - - tlConfig.setTargetSeries(); - - return tlConfig; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js deleted file mode 100644 index b7ee96ef77575..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/graphite.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import proxyquire from 'proxyquire'; -import Bluebird from 'bluebird'; -const expect = require('chai').expect; - -const graphiteResponse = function() { - return Bluebird.resolve({ - json: function() { - return [ - { - target: '__beer__', - datapoints: [ - [3, 1000], - [14, 2000], - [1.5, 3000], - [92.6535, 4000], - ], - }, - ]; - }, - }); -}; - -const filename = require('path').basename(__filename); -const fn = proxyquire(`../${filename}`, { 'node-fetch': graphiteResponse }); - -import invoke from './helpers/invoke_series_fn.js'; - -describe(filename, function() { - it('should wrap the graphite response up in a seriesList', function() { - return invoke(fn, []).then(function(result) { - expect(result.output.list[0].data[0][1]).to.eql(3); - expect(result.output.list[0].data[1][1]).to.eql(14); - }); - }); - - it('should convert the seconds to milliseconds', function() { - return invoke(fn, []).then(function(result) { - expect(result.output.list[0].data[1][0]).to.eql(2000 * 1000); - }); - }); - - it('should set the label to that of the graphite target', function() { - return invoke(fn, []).then(function(result) { - expect(result.output.list[0].label).to.eql('__beer__'); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js deleted file mode 100644 index 0c5c61923d6e7..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function getSeries(name, buckets, points) { - const fill = _.partial( - _.zip, - _.map(buckets, function(bucket) { - return bucket.valueOf(); - }) - ); - return { - data: fill(points), - type: 'series', - label: name, - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js deleted file mode 100644 index b8e64caeeade7..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_series_list.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(list, overrides) { - return _.merge( - { - type: 'seriesList', - list: list, - }, - overrides - ); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js deleted file mode 100644 index cef5bed5d3218..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/get_single_series_list.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import getSeries from '../helpers/get_series'; -import getSeriesList from '../helpers/get_series_list'; -import _ from 'lodash'; - -export default function(name, data) { - return getSeriesList([getSeries(name, _.map(data, 0), _.map(data, 1))]); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js deleted file mode 100644 index 51ef4c61a95e8..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/helpers/invoke_series_fn.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// invokes a series_function with the specified arguments -import _ from 'lodash'; - -import indexArguments from '../../../handlers/lib/index_arguments'; - -export default function invokeSeriesFn(fnDef, args, tlConfigOverrides) { - const tlConfig = _.merge(require('../fixtures/tlConfig')(), tlConfigOverrides); - - return Promise.all(args).then(function(args) { - args.byName = indexArguments(fnDef, args); - - const input = _.cloneDeep(args); - - return Promise.resolve(fnDef.originalFn(args, tlConfig)).then(function(output) { - const result = { - output: output, - input: input, - }; - return result; - }); - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js deleted file mode 100644 index 5d4b624670847..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/hide.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../hide`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('hide.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('hides a series', () => { - return invoke(fn, [seriesList, true]).then(r => { - _.each(r.output.list, series => expect(series._hide).to.equal(true)); - }); - }); - - it('unhides a series', () => { - return invoke(fn, [seriesList, false]).then(r => { - _.each(r.output.list, series => expect(series._hide).to.equal(false)); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js deleted file mode 100644 index 9e0a92b1e4004..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/label.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../label`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('label.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('changes the label on the series', () => { - return invoke(fn, [seriesList, 'free beer']).then(r => { - _.each(r.output.list, series => expect(series.label).to.equal('free beer')); - }); - }); - - it('can use a regex to capture parts of a series label', () => { - return invoke(fn, [seriesList, 'beer$1', 'Neg(.*)']).then(r => { - expect(r.output.list[0].label).to.equal('beerative'); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js deleted file mode 100644 index 205f0c4431fcc..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/legend.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../legend`); - -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('legend.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('should create the _global object if it does not exist', () => { - expect(seriesList.list[0]._global).to.equal(undefined); - return invoke(fn, [seriesList, 'nw', 3, true, 'YYYY']).then(r => { - expect(r.output.list[0]._global).to.eql({ - legend: { noColumns: 3, position: 'nw', showTime: true, timeFormat: 'YYYY' }, - }); - }); - }); - - it('should provide default values for time axis display', () => { - return invoke(fn, [seriesList, 'nw', 3]).then(r => { - expect(r.output.list[0]._global.legend.showTime).to.equal(true); - expect(r.output.list[0]._global.legend.timeFormat).to.equal('MMMM Do YYYY, HH:mm:ss.SSS'); - }); - }); - - it('should hide the legend is position is false', () => { - return invoke(fn, [seriesList, false]).then(r => { - expect(r.output.list[0]._global.legend.show).to.equal(false); - expect(r.output.list[0]._global.legend.showTime).to.equal(false); - }); - }); - - it('should set legend.showTime to false when showTime parameter is false', () => { - return invoke(fn, [seriesList, 'nw', 3, false]).then(r => { - expect(r.output.list[0]._global.legend.showTime).to.equal(false); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js deleted file mode 100644 index 32974495b40eb..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/lines.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../lines`); - -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('lines.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('should simply set show, steps, stack and lineWidth', () => { - expect(seriesList.list[0]._global).to.equal(undefined); - return invoke(fn, [seriesList, 1, 2, true, true, false]).then(r => { - expect(r.output.list[0].lines.lineWidth).to.equal(1); - expect(r.output.list[0].lines.show).to.equal(true); - expect(r.output.list[0].stack).to.equal(true); - expect(r.output.list[0].lines.steps).to.equal(false); - }); - }); - - it('should set lineWidth to 3 by default, and nothing else', () => { - expect(seriesList.list[0]._global).to.equal(undefined); - return invoke(fn, [seriesList]).then(r => { - expect(r.output.list[0].lines.lineWidth).to.equal(3); - expect(r.output.list[0].lines.fill).to.equal(undefined); - expect(r.output.list[0].lines.show).to.equal(undefined); - expect(r.output.list[0].stack).to.equal(undefined); - expect(r.output.list[0].lines.steps).to.equal(undefined); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js deleted file mode 100644 index 8cd2e2caa2c47..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/log.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../log`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('log.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('should return the log10 value of every value', () => { - return invoke(fn, [seriesList]).then(r => { - expect(_.map(r.output.list[4].data, 1)).to.eql([1, 2, 1, 0]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js deleted file mode 100644 index 9cc4afffb22ba..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/max.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../max`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('max.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('keeps the max of a series vs a number', () => { - return invoke(fn, [seriesList, 20]).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([20, 20, 82, 20]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js deleted file mode 100644 index a89183ee90c6b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/min.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../min`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('min.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('keeps the min of a series vs a number', () => { - return invoke(fn, [seriesList, 20]).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([-51, 17, 20, 20]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js deleted file mode 100644 index dceef96b1d166..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingaverage.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../movingaverage`); -const expect = require('chai').expect; - -import moment from 'moment'; -import _ from 'lodash'; -import buckets from './fixtures/bucketList'; -import getSeries from './helpers/get_series'; -import getSeriesList from './helpers/get_series_list'; -import invoke from './helpers/invoke_series_fn.js'; - -function getFivePointSeries() { - return getSeriesList([ - getSeries('Five', [].concat(buckets).push(moment('1984-01-01T00:00:00.000Z')), [ - 10, - 20, - 30, - 40, - 50, - ]), - ]); -} - -describe('movingaverage.js', () => { - let seriesList; - beforeEach(() => { - seriesList = getFivePointSeries(); - }); - - it('centers the averaged series by default', () => { - return invoke(fn, [seriesList, 3]).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([null, 20, 30, 40, null]); - }); - }); - - it('aligns the moving average to the left', () => { - return invoke(fn, [seriesList, 3, 'left']).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, 20, 30, 40]); - }); - }); - - it('aligns the moving average to the right', () => { - return invoke(fn, [seriesList, 3, 'right']).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([20, 30, 40, null, null]); - }); - }); - - describe('date math', () => { - it('accepts 2 years', () => { - return invoke(fn, [seriesList, '2y', 'left']).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([null, 15, 25, 35, 45]); - }); - }); - - it('accepts 3 years', () => { - return invoke(fn, [seriesList, '3y', 'left']).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([null, null, 20, 30, 40]); - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js deleted file mode 100644 index d2ef271293afc..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/movingstd.js +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../movingstd`); - -import moment from 'moment'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; -import getSeries from './helpers/get_series'; -import getSeriesList from './helpers/get_series_list'; - -describe('movingstd.js', () => { - it('computes the moving standard deviation of a list', async () => { - const points = [ - 108.48, - 111.56, - 112.13, - 113.75, - 114.25, - 110.79, - 111.21, - 116.82, - 117.16, - 120.38, - 116.96, - 119.56, - 118.97, - 117.54, - 114.42, - 111.01, - 114.2, - 116.43, - 117.74, - 119.9, - 124.65, - 124.98, - 124.7, - 123.6, - 124.5, - 126.85, - ]; - const buckets = []; - buckets[0] = moment('2018-01-01T00:00:00.000Z'); - for (let i = 1; i < points.length; i++) { - buckets[i] = buckets[i - 1].add(1, 'hours'); - } - const series = getSeries('test data', buckets, points); - const seriesList = getSeriesList([series]); - const numWindows = 5; - const position = 'left'; - const results = await invoke(fn, [seriesList, numWindows, position]); - - const resultPoints = results.output.list[0].data.map(row => { - // row is an array; index 0 is the time bucket, index 1 is the value - return row[1]; - }); - // First 5 result buckets are null since moving window is filling up. - const trimmedResultPoints = resultPoints.slice(numWindows); - - const expectedPoints = [ - 2.28, - 1.46, - 1.53, - 2.46, - 3.0, - 4.14, - 3.31, - 1.67, - 1.5, - 1.41, - 2.01, - 3.56, - 3.12, - 2.5, - 2.56, - 3.41, - 3.97, - 3.92, - 3.35, - 2.12, - 0.52, - ]; - - expectedPoints.forEach((value, index) => { - expect(trimmedResultPoints[index]).to.be.within(value - 0.01, value + 0.01); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js deleted file mode 100644 index 0cc5665fb919a..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/multiply.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../multiply`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('multiply.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('multiplies by a number', () => { - return invoke(fn, [seriesList, 2]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([200, 100, 100, 40]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js deleted file mode 100644 index 53831f0f6138d..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/points.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../points`); - -import _ from 'lodash'; -import assert from 'chai'; -const expect = assert.expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('points.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('should set the point radius', () => { - return invoke(fn, [seriesList, 1]).then(r => { - expect(r.output.list[0].points.radius).to.equal(1); - }); - }); - - it('should set the point lineWidth', () => { - return invoke(fn, [seriesList, null, 3]).then(r => { - expect(r.output.list[0].points.lineWidth).to.equal(3); - }); - }); - - it('should set the point fill', () => { - return invoke(fn, [seriesList, null, null, 3]).then(r => { - expect(r.output.list[0].points.fill).to.equal(3 / 10); - }); - }); - - it('should not set the fill color if fill is not specified', () => { - return invoke(fn, [seriesList, null, null, null, '#333']).then(r => { - expect(r.output.list[0].points.fillColor).to.equal(undefined); - }); - }); - - it('should set the fill color ', () => { - return invoke(fn, [seriesList, null, null, 10, '#333']).then(r => { - expect(r.output.list[0].points.fillColor).to.equal('#333'); - }); - }); - - describe('symbol', () => { - const symbols = ['triangle', 'cross', 'square', 'diamond', 'circle']; - _.each(symbols, symbol => { - it(`is ${symbol}`, () => { - return invoke(fn, [seriesList, null, null, null, null, symbol]).then(r => { - expect(r.output.list[0].points.symbol).to.equal(symbol); - }); - }); - }); - - it('does not allow undefined symbols', () => { - return invoke(fn, [seriesList, null, null, null, null, 'beer']) - .then(expect.fail) - .catch(e => { - expect(e).to.be.an('error'); - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js deleted file mode 100644 index 29e3bc1ab66f8..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/precision.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../precision`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('precision.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('keeps the min of a series vs a number', () => { - return invoke(fn, [seriesList, 2]).then(r => { - expect(_.map(r.output.list[3].data, 1)).to.eql([3.14, 2, 1.43, 0.34]); - }); - }); - - it('Adds a _meta to describe the precision to display', () => { - return invoke(fn, [seriesList, 2]).then(r => { - expect(r.output.list[3]._meta.precision).to.eql(2); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js deleted file mode 100644 index 009c0e4e025cd..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/quandl.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import proxyquire from 'proxyquire'; -import Bluebird from 'bluebird'; -import assert from 'chai'; -const expect = assert.expect; - -const parseURL = require('url').parse; -const parseQueryString = require('querystring').parse; -const tlConfig = require('./fixtures/tlConfig')(); -import moment from 'moment'; - -const filename = require('path').basename(__filename); -import invoke from './helpers/invoke_series_fn.js'; - -let fn; -let response; -let calledWith; -describe(filename, function() { - beforeEach(function() { - response = function(url) { - calledWith = { - params: parseQueryString(parseURL(url).query), - code: url.match(/datasets\/(.*).json/)[1], - }; - return Bluebird.resolve({ - json: function() { - return { - name: '__beer__', - data: [ - ['2015-01-01', 3], - ['2015-01-02', 14], - ['2015-01-03', 15.92], - ['2015-01-04', 65.35], - ], - }; - }, - }); - }; - fn = proxyquire(`../${filename}`, { 'node-fetch': response }); - }); - - it('should wrap the quandl response up in a seriesList', function() { - return invoke(fn, []).then(function(result) { - expect(result.output.list[0].data[0][1]).to.eql(3); - expect(result.output.list[0].data[1][1]).to.eql(14); - }); - }); - - it('should set the label to that of the quandl name', function() { - return invoke(fn, []).then(function(result) { - expect(result.output.list[0].label).to.eql('__beer__'); - }); - }); - - it('should call the quandl API with the quandl code that has been passed', function() { - return invoke(fn, ['BEER/IS_GOOD']).then(function() { - expect(calledWith.code).to.eql('BEER/IS_GOOD'); - }); - }); - - it('should limit the time span and interval to the stuff attached to tlConfig', function() { - return invoke(fn, []).then(function() { - expect(calledWith.params.trim_start).to.eql( - moment.utc(tlConfig.time.from).format('YYYY-MM-DD') - ); - expect(calledWith.params.trim_end).to.eql(moment.utc(tlConfig.time.to).format('YYYY-MM-DD')); - }); - }); - - it('should throw an error is passed an unsupported interval', function() { - return invoke(fn, [], { time: { interval: '2d' } }) - .then(expect.fail) - .catch(function(r) { - expect(r).to.be.an('error'); - }); - }); - - it('should use the configured API key when talking to quandl', function() { - return invoke(fn, [], { settings: { 'timelion:quandl.key': 'bEeR' } }).then(function() { - expect(calledWith.params.auth_token).to.eql('bEeR'); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js deleted file mode 100644 index 38bee7d45565e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/range.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../range`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('range.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - seriesList.list[0].data = [ - [1000, 20], - [2000, 10], - [3000, 30], - [4000, 40], - ]; - }); - - it('keeps the min of a series vs a number', () => { - return invoke(fn, [seriesList, 1, 4]).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([2, 1, 3, 4]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js deleted file mode 100644 index 12ad5503e69bf..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/scale_interval.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../scale_interval`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('scale_interval.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('Can multiply to transform one interval to another', () => { - return invoke(fn, [seriesList, '5y']).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([500, 250, 250, 100]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js deleted file mode 100644 index cea9525694ab3..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/static.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../static`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('static.js', () => { - it('returns a series in which all numbers are the same', () => { - return invoke(fn, [5]).then(r => { - expect(_.unique(_.map(r.output.list[0].data, 1))).to.eql([5]); - }); - }); - - it('plots a provided series', () => { - return invoke(fn, ['4:3:2:1']).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([4, 3, 2, 1]); - }); - }); - - it('leaves interpolation up to the data source wrapper', () => { - return invoke(fn, ['1:4']).then(r => { - expect(_.map(r.output.list[0].data, 1)).to.eql([1, 4]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js deleted file mode 100644 index 55d661ea95485..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/subtract.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../subtract`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('subtract.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('it throws an error if first argument is not seriesList', async () => { - const notSeriesList = [1, 2, 3, 4]; - try { - await invoke(fn, [notSeriesList]); - expect.fail(); - } catch (e) { - expect(e.message).to.eql('input must be a seriesList'); - } - }); - - it('it subtracts all series in seriesList to single series when only one argument is supplied', async () => { - const outputSeries = await invoke(fn, [seriesList]); - expect(outputSeries.output.list.length).to.eql(1); - expect(_.map(outputSeries.output.list[0].data, 1)).to.eql([ - -165.1415926535, - -136, - 19.561, - -2.3424234999999998, - ]); - }); - - it('it subtracts a number', async () => { - const outputSeries = await invoke(fn, [seriesList, 2]); - expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([98, 48, 48, 18]); - }); - - it('it subtracts an array of numbers', async () => { - const outputSeries = await invoke(fn, [seriesList, [5, 10, 15]]); - expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([70, 20, 20, -10]); - }); - - it('it subtracts a seriesList with one series', async () => { - const seriesListWithOneSeries = { - type: 'seriesList', - list: [_.cloneDeep(seriesList.list[1])], - }; - const outputSeries = await invoke(fn, [seriesList, seriesListWithOneSeries]); - expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([0, 0, 0, 0]); - }); - - it('it subtracts a seriesList with multiple series', async () => { - const outputSeries = await invoke(fn, [seriesList, seriesList]); - expect(_.map(outputSeries.output.list[0].data, 1)).to.eql([0, 0, 0, 0]); - expect(_.map(outputSeries.output.list[1].data, 1)).to.eql([0, 0, 0, 0]); - expect(_.map(outputSeries.output.list[2].data, 1)).to.eql([0, 0, 0, 0]); - expect(_.map(outputSeries.output.list[3].data, 1)).to.eql([0, 0, 0, 0]); - expect(_.map(outputSeries.output.list[4].data, 1)).to.eql([0, 0, 0, 0]); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js deleted file mode 100644 index 61e3a254d0b5d..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/sum.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../sum`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('sum.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('it adds a number', () => { - return invoke(fn, [seriesList, 2]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([102, 52, 52, 22]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js deleted file mode 100644 index 973bb2ed0ea32..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/title.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../title`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('title.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('sets the title property', () => { - return invoke(fn, [seriesList, 'beer']).then(r => { - _.each(r.output.list, series => expect(series._title).to.equal('beer')); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js deleted file mode 100644 index ed7a8999d706a..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/trim.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../trim`); - -import _ from 'lodash'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('trim.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('Sets the first and last values to null by default', () => { - return invoke(fn, [seriesList]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, 50, null]); - }); - }); - - it('Trims more from the beginning', () => { - return invoke(fn, [seriesList, 2]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([null, null, 50, null]); - }); - }); - - it('Trims more from the end', () => { - return invoke(fn, [seriesList, null, 2]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, null, null]); - }); - }); - - it('Trims nothing from the end', () => { - return invoke(fn, [seriesList, 1, 0]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([null, 50, 50, 20]); - }); - }); - - it('Trims nothing from the beginning', () => { - return invoke(fn, [seriesList, 0, 2]).then(r => { - expect(_.map(r.output.list[1].data, 1)).to.eql([100, 50, null, null]); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js deleted file mode 100644 index 9210a2cd300b0..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/__tests__/yaxis.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const fn = require(`../yaxis`); -import Bluebird from 'bluebird'; -const expect = require('chai').expect; -import invoke from './helpers/invoke_series_fn.js'; - -describe('yaxis.js', () => { - let seriesList; - beforeEach(() => { - seriesList = require('./fixtures/seriesList.js')(); - }); - - it('creates the yaxes array', () => { - expect(seriesList._global).to.equal(undefined); - return invoke(fn, [seriesList, 2]).then(r => { - expect(r.output.list[0]._global.yaxes).to.be.an('array'); - }); - }); - - it('puts odd numbers of the left, even on the right, by default', () => { - return Bluebird.all([ - invoke(fn, [seriesList, 1]).then(r => { - expect(r.output.list[0]._global.yaxes[0].position).to.equal('left'); - }), - invoke(fn, [seriesList, 2]).then(r => { - expect(r.output.list[0]._global.yaxes[1].position).to.equal('right'); - }), - invoke(fn, [seriesList, 3]).then(r => { - expect(r.output.list[0]._global.yaxes[2].position).to.equal('left'); - }), - ]); - }); - - it('it lets you override default positions', () => { - return Bluebird.all([ - invoke(fn, [seriesList, 1, null, null, 'right']).then(r => { - expect(r.output.list[0]._global.yaxes[0].position).to.equal('right'); - }), - invoke(fn, [seriesList, 2, null, null, 'right']).then(r => { - expect(r.output.list[0]._global.yaxes[1].position).to.equal('right'); - }), - ]); - }); - - it('sets the minimum (default: no min)', () => { - return Bluebird.all([ - invoke(fn, [seriesList, 1, null]).then(r => { - expect(r.output.list[0]._global.yaxes[0].min).to.equal(null); - }), - invoke(fn, [seriesList, 2, 10]).then(r => { - expect(r.output.list[0]._global.yaxes[1].min).to.equal(10); - }), - ]); - }); - - it('sets the max (default: no max)', () => { - return Bluebird.all([ - invoke(fn, [seriesList, 1, null]).then(r => { - expect(r.output.list[0]._global.yaxes[0].max).to.equal(undefined); - }), - invoke(fn, [seriesList, 2, null, 10]).then(r => { - expect(r.output.list[0]._global.yaxes[1].max).to.equal(10); - }), - ]); - }); - - it('sets the units (default: no unit', () => { - return Bluebird.all([ - invoke(fn, [seriesList, 1, null, null, null, null, null, null]).then(r => { - expect(r.output.list[0]._global.yaxes[0].units).to.equal(undefined); - }), - invoke(fn, [seriesList, 2, null, null, null, null, null, 'bits']).then(r => { - expect(r.output.list[0]._global.yaxes[1].units).to.be.an('object'); - }), - ]); - }); - - it('throws an error if currency is not three letter code', () => { - invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:abcde']).catch(e => { - expect(e).to.be.an.instanceof(Error); - }); - invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:12']).catch(e => { - expect(e).to.be.an.instanceof(Error); - }); - invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:$#']).catch(e => { - expect(e).to.be.an.instanceof(Error); - }); - invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:ab']).catch(e => { - expect(e).to.be.an.instanceof(Error); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js deleted file mode 100644 index c072522ebd906..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/abs.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('abs', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - ], - help: i18n.translate('timelion.help.functions.absHelpText', { - defaultMessage: 'Return the absolute value of each value in the series list', - }), - fn: function absFn(args) { - return alter(args, function(eachSeries) { - const data = _.map(eachSeries.data, function(point) { - return [point[0], Math.abs(point[1])]; - }); - eachSeries.data = data; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js deleted file mode 100644 index 26e768a76f84e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/avg.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.sum(points) / points.length; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js deleted file mode 100644 index 938039a465c31..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/cardinality.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.uniq(points).length; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js deleted file mode 100644 index 8eb9565d2711c..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/first.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.first(points); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js deleted file mode 100644 index 81ed1dff15967..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/index.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../../lib/alter.js'; -import Chainable from '../../lib/classes/chainable'; -import _ from 'lodash'; - -const functions = { - avg: require('./avg'), - cardinality: require('./cardinality'), - min: require('./min'), - max: require('./max'), - last: require('./last'), - first: require('./first'), - sum: require('./sum'), -}; - -export default new Chainable('aggregate', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'function', - types: ['string'], - help: i18n.translate('timelion.help.functions.aggregate.args.functionHelpText', { - defaultMessage: 'One of {functions}', - values: { - functions: _.keys(functions).join(', '), - }, - }), - }, - ], - help: i18n.translate('timelion.help.functions.aggregateHelpText', { - defaultMessage: - 'Creates a static line based on result of processing all points in the series. Available functions: {functions}', - values: { - functions: _.keys(functions).join(', '), - }, - }), - fn: function aggregateFn(args) { - const fn = functions[args.byName.function]; - if (!fn) - throw new Error('.aggregate() function must be one of: ' + _.keys(functions).join(', ')); - - return alter(args, function(eachSeries) { - const times = _.map(eachSeries.data, 0); - const values = _.map(eachSeries.data, 1); - - eachSeries.data = _.zip(times, _.fill(values, fn(values))); - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js deleted file mode 100644 index 751f6e87fff99..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/last.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.last(points); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js deleted file mode 100644 index 7cac21932001f..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/max.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.max(points); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js deleted file mode 100644 index 6ff6812dde527..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/min.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.min(points); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js deleted file mode 100644 index 481f6f529fa66..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/aggregate/sum.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export default function(points) { - return _.sum(points); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js deleted file mode 100644 index 4fe08caed0986..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/bars.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('bars', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'width', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.bars.args.widthHelpText', { - defaultMessage: 'Width of bars in pixels', - }), - }, - { - name: 'stack', - types: ['boolean', 'null'], - help: i18n.translate('timelion.help.functions.bars.args.stackHelpText', { - defaultMessage: 'Should bars be stacked, true by default', - }), - }, - ], - help: i18n.translate('timelion.help.functions.barsHelpText', { - defaultMessage: 'Show the seriesList as bars', - }), - fn: function barsFn(args) { - return alter(args, function(eachSeries, width, stack) { - eachSeries.bars = eachSeries.bars || {}; - eachSeries.bars.show = width == null ? 1 : width; - eachSeries.bars.lineWidth = width == null ? 6 : width; - eachSeries.stack = stack == null ? true : stack; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js deleted file mode 100644 index 85a622ceb7d07..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/color.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; -import tinygradient from 'tinygradient'; - -export default new Chainable('color', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'color', - types: ['string'], - help: i18n.translate('timelion.help.functions.color.args.colorHelpText', { - defaultMessage: - 'Color of series, as hex, e.g., #c6c6c6 is a lovely light grey. If you specify multiple \ -colors, and have multiple series, you will get a gradient, e.g., "#00B1CC:#00FF94:#FF3A39:#CC1A6F"', - }), - }, - ], - help: i18n.translate('timelion.help.functions.colorHelpText', { - defaultMessage: 'Change the color of the series', - }), - fn: function colorFn(args) { - const colors = args.byName.color.split(':'); - const gradientStops = args.byName.inputSeries.list.length; - let gradient; - if (colors.length > 1 && gradientStops > 1) { - // trim number of colors to avoid exception thrown by having more colors than gradient stops - let trimmedColors = colors; - if (colors.length > gradientStops) { - trimmedColors = colors.slice(0, gradientStops); - } - gradient = tinygradient(trimmedColors).rgb(gradientStops); - } - - let i = 0; - return alter(args, function(eachSeries) { - if (gradient) { - eachSeries.color = gradient[i++].toHexString(); - } else if (colors.length === 1 || gradientStops === 1) { - eachSeries.color = colors[0]; - } else { - throw new Error( - i18n.translate('timelion.serverSideErrors.colorFunction.colorNotProvidedErrorMessage', { - defaultMessage: 'color not provided', - }) - ); - } - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js deleted file mode 100644 index 625b25762d04f..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/condition.js +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; -import argType from '../handlers/lib/arg_type.js'; - -export default new Chainable('condition', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'operator', // <, <=, >, >=, ==, != - types: ['string'], - help: i18n.translate('timelion.help.functions.condition.args.operatorHelpText', { - defaultMessage: - 'comparison operator to use for comparison, valid operators are eq (equal), ' + - 'ne (not equal), lt (less than), lte (less than equal), gt (greater than), gte (greater than equal)', - }), - suggestions: [ - { - name: 'eq', - help: i18n.translate( - 'timelion.help.functions.condition.args.operator.suggestions.eqHelpText', - { - defaultMessage: 'equal', - } - ), - }, - { - name: 'ne', - help: i18n.translate( - 'timelion.help.functions.condition.args.operator.suggestions.neHelpText', - { - defaultMessage: 'not equal', - } - ), - }, - { - name: 'lt', - help: i18n.translate( - 'timelion.help.functions.condition.args.operator.suggestions.ltHelpText', - { - defaultMessage: 'less than', - } - ), - }, - { - name: 'lte', - help: i18n.translate( - 'timelion.help.functions.condition.args.operator.suggestions.lteHelpText', - { - defaultMessage: 'less than equal', - } - ), - }, - { - name: 'gt', - help: i18n.translate( - 'timelion.help.functions.condition.args.operator.suggestions.gtHelpText', - { - defaultMessage: 'greater than', - } - ), - }, - { - name: 'gte', - help: i18n.translate( - 'timelion.help.functions.condition.args.operator.suggestions.gteHelpText', - { - defaultMessage: 'greater than equal', - } - ), - }, - ], - }, - { - name: 'if', - types: ['number', 'seriesList', 'null'], - help: i18n.translate('timelion.help.functions.condition.args.ifHelpText', { - defaultMessage: - 'The value to which the point will be compared. If you pass a seriesList here the first series will be used', - }), - }, - { - name: 'then', - types: ['number', 'seriesList', 'null'], - help: i18n.translate('timelion.help.functions.condition.args.thenHelpText', { - defaultMessage: - 'The value the point will be set to if the comparison is true. If you pass a seriesList here the first series will be used', - }), - }, - { - name: 'else', - types: ['number', 'seriesList', 'null'], - help: i18n.translate('timelion.help.functions.condition.args.elseHelpText', { - defaultMessage: - 'The value the point will be set to if the comparison is false. If you pass a seriesList here the first series will be used', - }), - }, - ], - help: i18n.translate('timelion.help.functions.conditionHelpText', { - defaultMessage: - 'Compares each point to a number, or the same point in another series using an operator, ' + - 'then sets its value to the result if the condition proves true, with an optional else.', - }), - aliases: ['if'], - fn: function conditionFn(args) { - const config = args.byName; - return alter(args, function(eachSeries) { - const data = _.map(eachSeries.data, function(point, i) { - function getNumber(source) { - if (argType(source) === 'number') return source; - if (argType(source) === 'null') return null; - if (argType(source) === 'seriesList') return source.list[0].data[i][1]; - throw new Error( - i18n.translate('timelion.serverSideErrors.conditionFunction.wrongArgTypeErrorMessage', { - defaultMessage: 'must be a number or a seriesList', - }) - ); - } - - const ifVal = getNumber(config.if); - const thenVal = getNumber(config.then); - const elseVal = _.isUndefined(config.else) ? point[1] : getNumber(config.else); - - const newValue = (function() { - switch (config.operator) { - case 'lt': - return point[1] < ifVal ? thenVal : elseVal; - case 'lte': - return point[1] <= ifVal ? thenVal : elseVal; - case 'gt': - return point[1] > ifVal ? thenVal : elseVal; - case 'gte': - return point[1] >= ifVal ? thenVal : elseVal; - case 'eq': - return point[1] === ifVal ? thenVal : elseVal; - case 'ne': - return point[1] !== ifVal ? thenVal : elseVal; - default: - throw new Error( - i18n.translate( - 'timelion.serverSideErrors.conditionFunction.unknownOperatorErrorMessage', - { - defaultMessage: 'Unknown operator', - } - ) - ); - } - })(); - - return [point[0], newValue]; - }); - eachSeries.data = data; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js deleted file mode 100644 index cf4dd21b98261..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/cusum.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('cusum', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'base', - types: ['number'], - help: i18n.translate('timelion.help.functions.cusum.args.baseHelpText', { - defaultMessage: - 'Number to start at. Basically just adds this to the beginning of the series', - }), - }, - ], - help: i18n.translate('timelion.help.functions.cusumHelpText', { - defaultMessage: 'Return the cumulative sum of a series, starting at a base.', - }), - fn: function cusumFn(args) { - return alter(args, function(eachSeries, base) { - const pairs = eachSeries.data; - let total = base || 0; - eachSeries.data = _.map(pairs, function(point) { - total += point[1]; - return [point[0], total]; - }); - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js deleted file mode 100644 index bde4fe9f29abc..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/derivative.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('derivative', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - ], - help: i18n.translate('timelion.help.functions.derivativeHelpText', { - defaultMessage: 'Plot the change in values over time.', - }), - fn: function derivativeFn(args) { - return alter(args, function(eachSeries) { - const pairs = eachSeries.data; - eachSeries.data = _.map(pairs, function(point, i) { - if (i === 0 || pairs[i - 1][1] == null || point[1] == null) { - return [point[0], null]; - } - return [point[0], point[1] - pairs[i - 1][1]]; - }); - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js deleted file mode 100644 index 5d7eab23197ef..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/divide.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('divide', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'divisor', - types: ['seriesList', 'number'], - help: i18n.translate('timelion.help.functions.divide.args.divisorHelpText', { - defaultMessage: - 'Number or series to divide by. SeriesList with multiple series will be applied label-wise.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.divideHelpText', { - defaultMessage: - 'Divides the values of one or more series in a seriesList to each position, in each series, of the input seriesList', - }), - fn: function divideFn(args) { - return reduce(args, function(a, b) { - return a / b; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js deleted file mode 100644 index 4ce2752fbf9be..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/index.js +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { first, map } from 'rxjs/operators'; -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import Datasource from '../../lib/classes/datasource'; -import buildRequest from './lib/build_request'; -import toSeriesList from './lib/agg_response_to_series_list'; - -export default new Datasource('es', { - args: [ - { - name: 'q', - types: ['string', 'null'], - multi: true, - help: i18n.translate('timelion.help.functions.es.args.qHelpText', { - defaultMessage: 'Query in lucene query string syntax', - }), - }, - { - name: 'metric', - types: ['string', 'null'], - multi: true, - help: i18n.translate('timelion.help.functions.es.args.metricHelpText', { - defaultMessage: - 'An elasticsearch metric agg: avg, sum, min, max, percentiles or cardinality, followed by a field. ' + - 'E.g., "sum:bytes", "percentiles:bytes:95,99,99.9" or just "count"', - description: - `avg, sum, min, max, percentiles and cardinality are keywords in the expression ` + - `and must not be translated. Also don't translate the examples.`, - }), - }, - { - name: 'split', - types: ['string', 'null'], - multi: true, - help: i18n.translate('timelion.help.functions.es.args.splitHelpText', { - defaultMessage: - 'An elasticsearch field to split the series on and a limit. E.g., "{hostnameSplitArg}" to get the top 10 hostnames', - values: { - hostnameSplitArg: 'hostname:10', - }, - }), - }, - { - name: 'index', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.es.args.indexHelpText', { - defaultMessage: - 'Index to query, wildcards accepted. Provide Index Pattern name for scripted fields and ' + - 'field name type ahead suggestions for metrics, split, and timefield arguments.', - description: - '"metrics", "split" and "timefield" are referring to parameter names and should not be translated.', - }), - }, - { - name: 'timefield', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.es.args.timefieldHelpText', { - defaultMessage: 'Field of type "date" to use for x-axis', - description: '"date" is a field type and should not be translated.', - }), - }, - { - name: 'kibana', - types: ['boolean', 'null'], - help: i18n.translate('timelion.help.functions.es.args.kibanaHelpText', { - defaultMessage: - 'Respect filters on Kibana dashboards. Only has an effect when using on Kibana dashboards', - }), - }, - { - name: 'interval', // You really shouldn't use this, use the interval picker instead - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.es.args.intervalHelpText', { - defaultMessage: `**DO NOT USE THIS**. It's fun for debugging fit functions, but you really should use the interval picker`, - }), - }, - ], - help: i18n.translate('timelion.help.functions.esHelpText', { - defaultMessage: 'Pull data from an elasticsearch instance', - }), - aliases: ['elasticsearch'], - fn: async function esFn(args, tlConfig) { - const config = _.defaults(_.clone(args.byName), { - q: '*', - metric: ['count'], - index: tlConfig.settings['timelion:es.default_index'], - timefield: tlConfig.settings['timelion:es.timefield'], - interval: tlConfig.time.interval, - kibana: true, - fit: 'nearest', - }); - - const findResp = await tlConfig.request.getSavedObjectsClient().find({ - type: 'index-pattern', - fields: ['title', 'fields'], - search: `"${config.index}"`, - search_fields: ['title'], - }); - const indexPatternSavedObject = findResp.saved_objects.find(savedObject => { - return savedObject.attributes.title === config.index; - }); - let scriptedFields = []; - if (indexPatternSavedObject) { - const fields = JSON.parse(indexPatternSavedObject.attributes.fields); - scriptedFields = fields.filter(field => { - return field.scripted; - }); - } - - const esShardTimeout = await tlConfig.server.newPlatform.__internals.elasticsearch.legacy.config$ - .pipe( - first(), - map(config => config.shardTimeout.asMilliseconds()) - ) - .toPromise(); - - const body = buildRequest(config, tlConfig, scriptedFields, esShardTimeout); - - const { callWithRequest } = tlConfig.server.plugins.elasticsearch.getCluster('data'); - const resp = await callWithRequest(tlConfig.request, 'search', body); - if (!resp._shards.total) { - throw new Error( - i18n.translate('timelion.serverSideErrors.esFunction.indexNotFoundErrorMessage', { - defaultMessage: 'Elasticsearch index not found: {index}', - values: { - index: config.index, - }, - }) - ); - } - return { - type: 'seriesList', - list: toSeriesList(resp.aggregations, config), - }; - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js deleted file mode 100644 index c9ffe07793803..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_body.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function buildAggBody(fieldName, scriptedFields) { - const scriptedField = scriptedFields.find(field => { - return field.name === fieldName; - }); - - if (scriptedField) { - return { - script: { - source: scriptedField.script, - lang: scriptedField.lang, - }, - }; - } - - return { - field: fieldName, - }; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js deleted file mode 100644 index a5876944db250..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/agg_response_to_series_list.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export function timeBucketsToPairs(buckets) { - const timestamps = _.pluck(buckets, 'key'); - const series = {}; - _.each(buckets, function(bucket) { - _.forOwn(bucket, function(val, key) { - if (_.isPlainObject(val)) { - if (val.values) { - _.forOwn(val.values, function(bucketValue, bucketKey) { - const k = key + ':' + bucketKey; - const v = isNaN(bucketValue) ? NaN : bucketValue; - series[k] = series[k] || []; - series[k].push(v); - }); - } else { - series[key] = series[key] || []; - series[key].push(val.value); - } - } - }); - }); - - return _.mapValues(series, function(values) { - return _.zip(timestamps, values); - }); -} - -export function flattenBucket(bucket, splitKey, path, result) { - result = result || {}; - path = path || []; - _.forOwn(bucket, function(val, key) { - if (!_.isPlainObject(val)) return; - if (_.get(val, 'meta.type') === 'split') { - _.each(val.buckets, function(bucket, bucketKey) { - if (bucket.key == null) bucket.key = bucketKey; // For handling "keyed" response formats, e.g., filters agg - flattenBucket(bucket, bucket.key, path.concat([key + ':' + bucket.key]), result); - }); - } else if (_.get(val, 'meta.type') === 'time_buckets') { - const metrics = timeBucketsToPairs(val.buckets); - _.each(metrics, function(pairs, metricName) { - result[path.concat([metricName]).join(' > ')] = { - data: pairs, - splitKey: splitKey, - }; - }); - } - }); - return result; -} - -export default function toSeriesList(aggs, config) { - return _.map(flattenBucket(aggs), function(metrics, name) { - return { - data: metrics.data, - type: 'series', - fit: config.fit, - label: name, - split: metrics.splitKey, - }; - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js deleted file mode 100644 index 2149e44125be0..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/build_request.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import moment from 'moment'; -import { buildAggBody } from './agg_body'; -import createDateAgg from './create_date_agg'; - -export default function buildRequest(config, tlConfig, scriptedFields, timeout) { - const bool = { must: [] }; - - const timeFilter = { - range: { - [config.timefield]: { - gte: moment(tlConfig.time.from).toISOString(), - lte: moment(tlConfig.time.to).toISOString(), - format: 'strict_date_optional_time', - }, - }, - }; - bool.must.push(timeFilter); - - // Use the kibana filter bar filters - if (config.kibana) { - bool.filter = _.get(tlConfig, 'request.payload.extended.es.filter'); - } - - const aggs = { - q: { - meta: { type: 'split' }, - filters: { - filters: _.chain(config.q) - .map(function(q) { - return [q, { query_string: { query: q } }]; - }) - .zipObject() - .value(), - }, - aggs: {}, - }, - }; - - let aggCursor = aggs.q.aggs; - - _.each(config.split, function(clause) { - clause = clause.split(':'); - if (clause[0] && clause[1]) { - const termsAgg = buildAggBody(clause[0], scriptedFields); - termsAgg.size = parseInt(clause[1], 10); - aggCursor[clause[0]] = { - meta: { type: 'split' }, - terms: termsAgg, - aggs: {}, - }; - aggCursor = aggCursor[clause[0]].aggs; - } else { - throw new Error('`split` requires field:limit'); - } - }); - - _.assign(aggCursor, createDateAgg(config, tlConfig, scriptedFields)); - - const request = { - index: config.index, - ignore_throttled: !tlConfig.settings['search:includeFrozen'], - body: { - query: { - bool: bool, - }, - aggs: aggs, - size: 0, - }, - }; - - if (timeout) { - request.timeout = `${timeout}ms`; - } - - return request; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js deleted file mode 100644 index b8bf83116b43b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/es/lib/create_date_agg.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { buildAggBody } from './agg_body'; - -export default function createDateAgg(config, tlConfig, scriptedFields) { - const dateAgg = { - time_buckets: { - meta: { type: 'time_buckets' }, - date_histogram: { - field: config.timefield, - interval: config.interval, - time_zone: tlConfig.time.timezone, - extended_bounds: { - min: tlConfig.time.from, - max: tlConfig.time.to, - }, - min_doc_count: 0, - }, - }, - }; - - dateAgg.time_buckets.aggs = {}; - _.each(config.metric, function(metric) { - metric = metric.split(':'); - if (metric[0] === 'count') { - // This is pretty lame, but its how the "doc_count" metric has to be implemented at the moment - // It simplifies the aggregation tree walking code considerably - dateAgg.time_buckets.aggs[metric] = { - bucket_script: { - buckets_path: '_count', - script: { source: '_value', lang: 'expression' }, - }, - }; - } else if (metric[0] && metric[1]) { - const metricName = metric[0] + '(' + metric[1] + ')'; - dateAgg.time_buckets.aggs[metricName] = {}; - dateAgg.time_buckets.aggs[metricName][metric[0]] = buildAggBody(metric[1], scriptedFields); - if (metric[0] === 'percentiles' && metric[2]) { - let percentList = metric[2].split(','); - percentList = percentList.map(x => parseFloat(x)); - dateAgg.time_buckets.aggs[metricName][metric[0]].percents = percentList; - } - } else { - throw new Error('`metric` requires metric:field or simply count'); - } - }); - - return dateAgg; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js deleted file mode 100644 index 06b8c1e81b4c4..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/first.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('first', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - ], - help: i18n.translate('timelion.help.functions.firstHelpText', { - defaultMessage: `This is an internal function that simply returns the input seriesList. Don't use this`, - }), - fn: function firstFn(args) { - return alter(args, function(eachSeries) { - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js deleted file mode 100644 index c47f8dc84908c..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/fit.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; -import loadFunctions from '../lib/load_functions.js'; -const fitFunctions = loadFunctions('fit_functions'); - -export default new Chainable('fit', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'mode', - types: ['string'], - help: i18n.translate('timelion.help.functions.fit.args.modeHelpText', { - defaultMessage: - 'The algorithm to use for fitting the series to the target. One of: {fitFunctions}', - values: { - fitFunctions: _.keys(fitFunctions).join(', '), - }, - }), - suggestions: _.keys(fitFunctions).map(key => { - return { name: key }; - }), - }, - ], - help: i18n.translate('timelion.help.functions.fitHelpText', { - defaultMessage: 'Fills null values using a defined fit function', - }), - fn: function absFn(args) { - return alter(args, function(eachSeries, mode) { - const noNulls = eachSeries.data.filter(item => item[1] === 0 || item[1]); - - if (noNulls.length === 0) { - return eachSeries; - } - - eachSeries.data = fitFunctions[mode](noNulls, eachSeries.data); - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js deleted file mode 100644 index a80dd2f3ff29e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/graphite.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import fetch from 'node-fetch'; -import moment from 'moment'; -import Datasource from '../lib/classes/datasource'; - -export default new Datasource('graphite', { - args: [ - { - name: 'metric', // _test-data.users.*.data - types: ['string'], - help: i18n.translate('timelion.help.functions.graphite.args.metricHelpText', { - defaultMessage: 'Graphite metric to pull, e.g., {metricExample}', - values: { - metricExample: '_test-data.users.*.data', - }, - }), - }, - ], - help: i18n.translate('timelion.help.functions.graphiteHelpText', { - defaultMessage: `[experimental] Pull data from graphite. Configure your graphite server in Kibana's Advanced Settings`, - }), - fn: function graphite(args, tlConfig) { - const config = args.byName; - - const time = { - min: moment(tlConfig.time.from).format('HH:mm[_]YYYYMMDD'), - max: moment(tlConfig.time.to).format('HH:mm[_]YYYYMMDD'), - }; - const allowedUrls = tlConfig.server.config().get('timelion.graphiteUrls'); - const configuredUrl = tlConfig.settings['timelion:graphite.url']; - if (!allowedUrls.includes(configuredUrl)) { - throw new Error( - i18n.translate('timelion.help.functions.notAllowedGraphiteUrl', { - defaultMessage: `This graphite URL is not configured on the kibana.yml file. - Please configure your graphite server list in the kibana.yml file under 'timelion.graphiteUrls' and - select one from Kibana's Advanced Settings`, - }) - ); - } - - const URL = - tlConfig.settings['timelion:graphite.url'] + - '/render/' + - '?format=json' + - '&from=' + - time.min + - '&until=' + - time.max + - '&target=' + - config.metric; - - return fetch(URL) - .then(function(resp) { - return resp.json(); - }) - .then(function(resp) { - const list = _.map(resp, function(series) { - const data = _.map(series.datapoints, function(point) { - return [point[1] * 1000, point[0]]; - }); - return { - data: data, - type: 'series', - fit: 'nearest', // TODO make this customizable - label: series.target, - }; - }); - - return { - type: 'seriesList', - list: list, - }; - }) - .catch(function(e) { - throw e; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js deleted file mode 100644 index 5a461e833b75a..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/hide.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('hide', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'hide', - types: ['boolean', 'null'], - help: i18n.translate('timelion.help.functions.hide.args.hideHelpText', { - defaultMessage: 'Hide or unhide the series', - }), - }, - ], - help: i18n.translate('timelion.help.functions.hideHelpText', { - defaultMessage: 'Hide the series by default', - }), - fn: function hideFn(args) { - return alter(args, function(eachSeries, hide) { - eachSeries._hide = hide == null ? true : hide; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js deleted file mode 100644 index 970d146c45b91..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/index.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import Chainable from '../../lib/classes/chainable'; -import ses from './lib/ses'; -import des from './lib/des'; -import tes from './lib/tes'; -import toMilliseconds from '../../lib/to_milliseconds'; - -export default new Chainable('holt', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'alpha', - types: ['number'], - help: i18n.translate('timelion.help.functions.holt.args.alphaHelpText', { - defaultMessage: ` - Smoothing weight from 0 to 1. - Increasing alpha will make the new series more closely follow the original. - Lowering it will make the series smoother`, - }), - }, - { - name: 'beta', - types: ['number'], - help: i18n.translate('timelion.help.functions.holt.args.betaHelpText', { - defaultMessage: ` - Trending weight from 0 to 1. - Increasing beta will make rising/falling lines continue to rise/fall longer. - Lowering it will make the function learn the new trend faster`, - }), - }, - { - name: 'gamma', - types: ['number'], - help: i18n.translate('timelion.help.functions.holt.args.gammaHelpText', { - defaultMessage: ` - Seasonal weight from 0 to 1. Does your data look like a wave? - Increasing this will give recent seasons more importance, thus changing the wave form faster. - Lowering it will reduce the importance of new seasons, making history more important. - `, - }), - }, - { - name: 'season', - types: ['string'], - help: i18n.translate('timelion.help.functions.holt.args.seasonHelpText', { - defaultMessage: - 'How long is the season, e.g., 1w if your pattern repeats weekly. (Only useful with gamma)', - description: - '"1w" is an expression value and should not be translated. "gamma" is a parameter name and should not be translated.', - }), - }, - { - name: 'sample', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.holt.args.sampleHelpText', { - defaultMessage: ` - The number of seasons to sample before starting to "predict" in a seasonal series. - (Only useful with gamma, Default: all)`, - description: '"gamma" and "all" are parameter names and values and must not be translated.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.holtHelpText', { - defaultMessage: ` - Sample the beginning of a series and use it to forecast what should happen - via several optional parameters. In general, this doesn't really predict the - future, but predicts what should be happening right now according to past data, - which can be useful for anomaly detection. Note that nulls will be filled with forecasted values.`, - description: '"null" is a data value here and must not be translated.', - }), - fn: function expsmoothFn(args, tlConfig) { - const newSeries = _.cloneDeep(args.byName.inputSeries); - - const alpha = args.byName.alpha; - const beta = args.byName.beta; - const gamma = args.byName.gamma; - - _.each(newSeries.list, function(series) { - const sample = args.byName.sample || series.data.length; // If we use length it should simply never predict - - // Single exponential smoothing - // This is basically a weighted moving average in which the older - // points exponentially degrade relative to the alpha, e.g.: - // 0.8^1, 0.8^2, 0.8^3, etc - - const times = _.map(series.data, 0); - let points = _.map(series.data, 1); - - if (alpha != null && beta == null && gamma == null) { - points = ses(points, alpha); - } - - if (alpha != null && beta != null && gamma == null) { - points = des(points, alpha, beta); - } - - if (alpha != null && beta != null && gamma != null) { - if (!sample || !args.byName.season || sample < 2) { - throw new Error( - i18n.translate('timelion.serverSideErrors.holtFunction.missingParamsErrorMessage', { - defaultMessage: 'Must specify a season length and a sample size >= 2', - }) - ); - } - const season = Math.round( - toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval) - ); - points = tes(points, alpha, beta, gamma, season, sample); - } - - _.assign(series.data, _.zip(times, points)); - }); - - return newSeries; - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js deleted file mode 100644 index a1fe4b1f1f52c..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/des.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; - -export default function des(points, alpha, beta) { - let level; - let prevLevel; - let trend; - let prevTrend; - let unknownCount = 0; - - if (points.length < 2) { - throw new Error( - i18n.translate('timelion.serverSideErrors.holtFunction.notEnoughPointsErrorMessage', { - defaultMessage: 'You need at least 2 points to use double exponential smoothing', - }) - ); - } - - const smoothedPoints = _.map( - points, - (point, i) => { - if (i === 0) { - return point; - } - - if (i === 1) { - // Establish initial values for level and trend; - level = points[0]; - trend = points[1] - points[0]; // This is sort of a lame way to do this - } - - if (point == null) { - unknownCount++; - } else { - unknownCount = 0; - // These 2 variables are not required, but are used for clarity. - prevLevel = level; - prevTrend = trend; - level = alpha * point + (1 - alpha) * (prevLevel + prevTrend); - trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; - } - - return level + unknownCount * trend; - }, - [] - ); - - return smoothedPoints; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js deleted file mode 100644 index 5fe4eddc4ab15..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/ses.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - Single exponential smoothing. Assuming even interval -*/ - -import _ from 'lodash'; - -export default function ses(points, alpha) { - let origin; - let level; - - const smoothedPoints = _.reduce( - points, - (result, point, i) => { - if (i === 0) { - origin = point; - level = point; - } else { - // In the case that point[1] is null, we keep origin the same - // and forecast the point based on the previous smoothed point - if (point != null) { - origin = point; - } - if (origin == null) { - level = null; - } else { - const prevSmoothed = result[i - 1]; - level = alpha * origin + (1 - alpha) * prevSmoothed; - } - } - - result.push(level); - return result; - }, - [] - ); - - return smoothedPoints; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js deleted file mode 100644 index cafb32614db74..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/holt/lib/tes.js +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// Frequency = number of points per season -// Season = 1 hump - -/* -Hourly data might have: - - Daily seasonality (frequency=24) - - Weekly seasonality (frequency=24×7=168) - - Annual seasonality (frequency=24×365.25=8766) -*/ - -import _ from 'lodash'; - -// Uh, I don't think this will work when you have nulls in the initial seasonal components -function initSeasonalComponents(samplePoints, seasonLength) { - const sampledSeasonCount = samplePoints.length / seasonLength; - let currentSeason = []; - const seasonalAverages = _.reduce( - samplePoints, - (result, point, i) => { - currentSeason.push(point); - // If this is the end of the season, add it to the result; - if (i % seasonLength === seasonLength - 1) { - result.push(_.sum(currentSeason) / seasonLength); - currentSeason = []; - } - - return result; - }, - [] - ); - - const seasonals = _.times(seasonLength, i => { - let sumOfValsOverAvg = 0; - _.times(sampledSeasonCount, j => { - sumOfValsOverAvg += samplePoints[seasonLength * j + i] - seasonalAverages[j]; - }); - - return sumOfValsOverAvg / sampledSeasonCount; - }); - - return seasonals; -} - -// This is different from the DES method of establishing trend because it looks for -// the difference in points between seasons -function initTrend(samplePoints, seasonLength) { - let sum = 0; - _.times(seasonLength, i => { - sum += (samplePoints[i + seasonLength] - samplePoints[i]) / seasonLength; - }); - return sum / seasonLength; -} - -export default function tes(points, alpha, beta, gamma, seasonLength, seasonsToSample) { - const samplePoints = points.slice(0, seasonLength * seasonsToSample); - const seasonals = initSeasonalComponents(samplePoints, seasonLength); - let level; - let prevLevel; - let trend; - let prevTrend; - let unknownCount = 0; - - const result = _.map(points, (point, i) => { - const seasonalPosition = i % seasonLength; - // For the first samplePoints.length we use the actual points - // After that we switch to the forecast - if (i === 0) { - trend = initTrend(points, seasonLength); - level = points[0]; - return points[0]; - } - - // Beta isn't actually used once we're forecasting? - if (point == null || i >= samplePoints.length) { - unknownCount++; - // Don't know this point, make it up! - return level + unknownCount * trend + seasonals[seasonalPosition]; - } else { - unknownCount = 0; - // These 2 variables are not required, but are used for clarity. - prevLevel = level; - prevTrend = trend; - level = alpha * (point - seasonals[seasonalPosition]) + (1 - alpha) * (prevLevel + prevTrend); - trend = beta * (level - prevLevel) + (1 - beta) * prevTrend; - seasonals[seasonalPosition] = - gamma * (point - level) + (1 - gamma) * seasonals[seasonalPosition]; - return level + trend + seasonals[seasonalPosition]; - } - }); - - return result; -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js deleted file mode 100644 index 1e4782e5a381e..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/label.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('label', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'label', - types: ['string'], - help: i18n.translate('timelion.help.functions.label.args.labelHelpText', { - defaultMessage: - 'Legend value for series. You can use $1, $2, etc, in the string to match up with the regex capture groups', - description: '"$1" and "$2" are part of the expression and must not be translated.', - }), - }, - { - name: 'regex', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.label.args.regexHelpText', { - defaultMessage: 'A regex with capture group support', - }), - }, - ], - help: i18n.translate('timelion.help.functions.labelHelpText', { - defaultMessage: 'Change the label of the series. Use %s to reference the existing label', - }), - fn: function labelFn(args) { - const config = args.byName; - return alter(args, function(eachSeries) { - if (config.regex) { - eachSeries.label = eachSeries.label.replace(new RegExp(config.regex), config.label); - } else { - eachSeries.label = config.label; - } - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js deleted file mode 100644 index b467318686729..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/legend.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; - -export default new Chainable('legend', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'position', - types: ['string', 'boolean', 'null'], - help: i18n.translate('timelion.help.functions.legend.args.positionHelpText', { - defaultMessage: - 'Corner to place the legend in: nw, ne, se, or sw. You can also pass false to disable the legend', - description: '"nw", "ne", "se", "sw" and "false" are keywords and must not be translated.', - }), - suggestions: [ - { - name: 'false', - help: i18n.translate( - 'timelion.help.functions.legend.args.position.suggestions.falseHelpText', - { - defaultMessage: 'disable legend', - } - ), - }, - { - name: 'nw', - help: i18n.translate( - 'timelion.help.functions.legend.args.position.suggestions.nwHelpText', - { - defaultMessage: 'place legend in north west corner', - } - ), - }, - { - name: 'ne', - help: i18n.translate( - 'timelion.help.functions.legend.args.position.suggestions.neHelpText', - { - defaultMessage: 'place legend in north east corner', - } - ), - }, - { - name: 'se', - help: i18n.translate( - 'timelion.help.functions.legend.args.position.suggestions.seHelpText', - { - defaultMessage: 'place legend in south east corner', - } - ), - }, - { - name: 'sw', - help: i18n.translate( - 'timelion.help.functions.legend.args.position.suggestions.swHelpText', - { - defaultMessage: 'place legend in south west corner', - } - ), - }, - ], - }, - { - name: 'columns', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.legend.args.columnsHelpText', { - defaultMessage: 'Number of columns to divide the legend into', - }), - }, - { - name: 'showTime', - types: ['boolean'], - help: i18n.translate('timelion.help.functions.legend.args.showTimeHelpText', { - defaultMessage: 'Show time value in legend when hovering over graph. Default: true', - }), - }, - { - name: 'timeFormat', - types: ['string'], - help: i18n.translate('timelion.help.functions.legend.args.timeFormatHelpText', { - defaultMessage: 'moment.js format pattern. Default: {defaultTimeFormat}', - values: { - defaultTimeFormat: DEFAULT_TIME_FORMAT, - }, - }), - }, - ], - help: i18n.translate('timelion.help.functions.legendHelpText', { - defaultMessage: 'Set the position and style of the legend on the plot', - }), - fn: function legendFn(args) { - return alter(args, function( - eachSeries, - position, - columns, - showTime = true, - timeFormat = DEFAULT_TIME_FORMAT - ) { - eachSeries._global = eachSeries._global || {}; - eachSeries._global.legend = eachSeries._global.legend || {}; - eachSeries._global.legend.noColumns = columns; - eachSeries._global.legend.showTime = showTime; - eachSeries._global.legend.timeFormat = timeFormat; - - if (position === false) { - eachSeries._global.legend.show = false; - eachSeries._global.legend.showTime = false; - } else { - eachSeries._global.legend.position = position; - } - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js deleted file mode 100644 index 39b8893692619..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/lines.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('lines', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'width', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.lines.args.widthHelpText', { - defaultMessage: 'Line thickness', - }), - }, - { - name: 'fill', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.lines.args.fillHelpText', { - defaultMessage: 'Number between 0 and 10. Use for making area charts', - }), - }, - { - name: 'stack', - types: ['boolean', 'null'], - help: i18n.translate('timelion.help.functions.lines.args.stackHelpText', { - defaultMessage: 'Stack lines, often misleading. At least use some fill if you use this.', - }), - }, - { - name: 'show', - types: ['number', 'boolean', 'null'], - help: i18n.translate('timelion.help.functions.lines.args.showHelpText', { - defaultMessage: 'Show or hide lines', - }), - }, - { - name: 'steps', - types: ['number', 'boolean', 'null'], - help: i18n.translate('timelion.help.functions.lines.args.stepsHelpText', { - defaultMessage: 'Show line as step, e.g., do not interpolate between points', - }), - }, - ], - help: i18n.translate('timelion.help.functions.linesHelpText', { - defaultMessage: 'Show the seriesList as lines', - }), - fn: function linesFn(args) { - return alter(args, function(eachSeries, width, fill, stack, show, steps) { - eachSeries.lines = eachSeries.lines || {}; - - // Defaults - if (eachSeries.lines.lineWidth == null) eachSeries.lines.lineWidth = 3; - - if (width != null) eachSeries.lines.lineWidth = width; - if (fill != null) eachSeries.lines.fill = fill / 10; - if (stack != null) eachSeries.stack = stack; - if (show != null) eachSeries.lines.show = show; - if (steps != null) eachSeries.lines.steps = steps; - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js deleted file mode 100644 index 4dc356cb9e7df..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/log.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('log', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'base', - types: ['number'], - help: i18n.translate('timelion.help.functions.log.args.baseHelpText', { - defaultMessage: 'Set logarithmic base, 10 by default', - }), - }, - ], - help: i18n.translate('timelion.help.functions.logHelpText', { - defaultMessage: - 'Return the logarithm value of each value in the series list (default base: 10)', - }), - fn: function logFn(args) { - const config = args.byName; - return alter(args, function(eachSeries) { - const data = _.map(eachSeries.data, function(point) { - return [point[0], Math.log(point[1]) / Math.log(config.base || 10)]; - }); - eachSeries.data = data; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js deleted file mode 100644 index 26680df7df1c4..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/max.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('max', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'value', - types: ['seriesList', 'number'], - help: i18n.translate('timelion.help.functions.max.args.valueHelpText', { - defaultMessage: - 'Sets the point to whichever is higher, the existing value, or the one passed. ' + - 'If passing a seriesList it must contain exactly 1 series.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.maxHelpText', { - defaultMessage: - 'Maximum values of one or more series in a seriesList to each position, in each series, of the input seriesList', - }), - fn: function maxFn(args) { - return reduce(args, function(a, b) { - return Math.max(a, b); - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js deleted file mode 100644 index a3dc912d1d14b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/min.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('min', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'value', - types: ['seriesList', 'number'], - help: i18n.translate('timelion.help.functions.min.args.valueHelpText', { - defaultMessage: - 'Sets the point to whichever is lower, the existing value, or the one passed. ' + - 'If passing a seriesList it must contain exactly 1 series.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.minHelpText', { - defaultMessage: - 'Minimum values of one or more series in a seriesList to each position, in each series, of the input seriesList', - }), - fn: function minFn(args) { - return reduce(args, function(a, b) { - return Math.min(a, b); - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js deleted file mode 100644 index 361cd1f9dfb67..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingaverage.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; -import toMS from '../lib/to_milliseconds.js'; - -const validPositions = ['left', 'right', 'center']; -const defaultPosition = 'center'; - -export default new Chainable('movingaverage', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'window', - types: ['number', 'string'], - help: i18n.translate('timelion.help.functions.movingaverage.args.windowHelpText', { - defaultMessage: - 'Number of points, or a date math expression (eg 1d, 1M) to average over. If a date math expression ' + - 'is specified, the function will get as close as possible given the currently select interval. ' + - 'If the date math expression is not evenly divisible by the interval the results may appear abnormal.', - }), - }, - { - name: 'position', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.movingaverage.args.positionHelpText', { - defaultMessage: - 'Position of the averaged points relative to the result time. One of: {validPositions}', - values: { - validPositions: validPositions.join(', '), - }, - }), - suggestions: validPositions.map(position => { - const suggestion = { name: position }; - if (position === defaultPosition) { - suggestion.help = 'default'; - } - return suggestion; - }), - }, - ], - aliases: ['mvavg'], - help: i18n.translate('timelion.help.functions.movingaverageHelpText', { - defaultMessage: - 'Calculate the moving average over a given window. Nice for smoothing noisy series', - }), - fn: function movingaverageFn(args, tlConfig) { - return alter(args, function(eachSeries, _window, _position) { - // _window always needs to be a number, if isn't we have to make it into one. - if (typeof _window !== 'number') { - // Ok, I guess its a datemath expression - const windowMilliseconds = toMS(_window); - - // calculate how many buckets that _window represents - const intervalMilliseconds = toMS(tlConfig.time.interval); - - // Round, floor, ceil? We're going with round because it splits the difference. - _window = Math.round(windowMilliseconds / intervalMilliseconds) || 1; - } - - _position = _position || defaultPosition; - if (!_.contains(validPositions, _position)) { - throw new Error( - i18n.translate( - 'timelion.serverSideErrors.movingaverageFunction.notValidPositionErrorMessage', - { - defaultMessage: 'Valid positions are: {validPositions}', - values: { - validPositions: validPositions.join(', '), - }, - } - ) - ); - } - - const pairs = eachSeries.data; - const pairsLen = pairs.length; - eachSeries.label = eachSeries.label + ' mvavg=' + _window; - - function toPoint(point, pairSlice) { - const average = - _.chain(pairSlice) - .map(1) - .reduce(function(memo, num) { - return memo + num; - }) - .value() / _window; - - return [point[0], average]; - } - - if (_position === 'center') { - const windowLeft = Math.floor(_window / 2); - const windowRight = _window - windowLeft; - eachSeries.data = _.map(pairs, function(point, i) { - if (i < windowLeft || i > pairsLen - windowRight) return [point[0], null]; - return toPoint(point, pairs.slice(i - windowLeft, i + windowRight)); - }); - } else if (_position === 'left') { - eachSeries.data = _.map(pairs, function(point, i) { - const cursor = i + 1; - if (cursor < _window) return [point[0], null]; - return toPoint(point, pairs.slice(cursor - _window, cursor)); - }); - } else if (_position === 'right') { - eachSeries.data = _.map(pairs, function(point, i) { - if (i > pairsLen - _window) return [point[0], null]; - return toPoint(point, pairs.slice(i, i + _window)); - }); - } - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js deleted file mode 100644 index 318bfd1d06300..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/movingstd.js +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -const positions = ['left', 'right', 'center']; -const defaultPosition = positions[0]; - -export default new Chainable('movingstd', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'window', - types: ['number'], - help: i18n.translate('timelion.help.functions.movingstd.args.windowHelpText', { - defaultMessage: 'Number of points to compute the standard deviation over.', - }), - }, - { - name: 'position', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.movingstd.args.positionHelpText', { - defaultMessage: - 'Position of the window slice relative to the result time. Options are {positions}. Default: {defaultPosition}', - values: { - positions: positions.join(', '), - defaultPosition, - }, - }), - }, - ], - aliases: ['mvstd'], - help: i18n.translate('timelion.help.functions.movingstdHelpText', { - defaultMessage: - 'Calculate the moving standard deviation over a given window. Uses naive two-pass algorithm. ' + - 'Rounding errors may become more noticeable with very long series, or series with very large numbers.', - }), - fn: function movingstdFn(args) { - return alter(args, function(eachSeries, _window, _position) { - _position = _position || defaultPosition; - - if (!_.contains(positions, _position)) { - throw new Error( - i18n.translate( - 'timelion.serverSideErrors.movingstdFunction.notValidPositionErrorMessage', - { - defaultMessage: 'Valid positions are: {validPositions}', - values: { - validPositions: positions.join(', '), - }, - } - ) - ); - } - - const pairs = eachSeries.data; - const pairsLen = pairs.length; - eachSeries.label = eachSeries.label + ' mvstd=' + _window; - - function toPoint(point, pairSlice) { - const average = - _.chain(pairSlice) - .map(1) - .reduce(function(memo, num) { - return memo + num; - }) - .value() / _window; - - const variance = - _.chain(pairSlice) - .map(function(point) { - return Math.pow(point[1] - average, 2); - }) - .reduce(function(memo, num) { - return memo + num; - }) - .value() / - (_window - 1); - - return [point[0], Math.sqrt(variance)]; - } - - if (_position === 'center') { - const windowLeft = Math.floor(_window / 2); - const windowRight = _window - windowLeft; - eachSeries.data = _.map(pairs, function(point, i) { - if (i < windowLeft || i >= pairsLen - windowRight) return [point[0], null]; - return toPoint(point, pairs.slice(i - windowLeft, i + windowRight)); - }); - } else if (_position === 'left') { - eachSeries.data = _.map(pairs, function(point, i) { - if (i < _window) return [point[0], null]; - return toPoint(point, pairs.slice(i - _window, i)); - }); - } else if (_position === 'right') { - eachSeries.data = _.map(pairs, function(point, i) { - if (i >= pairsLen - _window) return [point[0], null]; - return toPoint(point, pairs.slice(i, i + _window)); - }); - } - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js deleted file mode 100644 index 3bab685b530e8..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/multiply.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('multiply', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'multiplier', - types: ['seriesList', 'number'], - help: i18n.translate('timelion.help.functions.multiply.args.multiplierHelpText', { - defaultMessage: - 'Number or series by which to multiply. SeriesList with multiple series will be applied label-wise.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.multiplyHelpText', { - defaultMessage: - 'Multiply the values of one or more series in a seriesList to each position, in each series, of the input seriesList', - }), - fn: function multiplyFn(args) { - return reduce(args, function(a, b) { - return a * b; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js deleted file mode 100644 index f2c0472c70ab2..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/points.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -const validSymbols = ['triangle', 'cross', 'square', 'diamond', 'circle']; -const defaultSymbol = 'circle'; - -export default new Chainable('points', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'radius', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.points.args.radiusHelpText', { - defaultMessage: 'Size of points', - }), - }, - { - name: 'weight', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.points.args.weightHelpText', { - defaultMessage: 'Thickness of line around point', - }), - }, - { - name: 'fill', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.points.args.fillHelpText', { - defaultMessage: 'Number between 0 and 10 representing opacity of fill', - }), - }, - { - name: 'fillColor', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.points.args.fillColorHelpText', { - defaultMessage: 'Color with which to fill point', - }), - }, - { - name: 'symbol', - help: i18n.translate('timelion.help.functions.points.args.symbolHelpText', { - defaultMessage: 'point symbol. One of: {validSymbols}', - values: { - validSymbols: validSymbols.join(', '), - }, - }), - types: ['string', 'null'], - suggestions: validSymbols.map(symbol => { - const suggestion = { name: symbol }; - if (symbol === defaultSymbol) { - suggestion.help = 'default'; - } - return suggestion; - }), - }, - { - name: 'show', - types: ['boolean', 'null'], - help: i18n.translate('timelion.help.functions.points.args.showHelpText', { - defaultMessage: 'Show points or not', - }), - }, - ], - help: i18n.translate('timelion.help.functions.pointsHelpText', { - defaultMessage: 'Show the series as points', - }), - fn: function pointsFn(args) { - return alter(args, function(eachSeries, radius, weight, fill, fillColor, symbol, show) { - eachSeries.points = eachSeries.points || {}; - eachSeries.points.radius = radius == null ? undefined : radius; - - if (fill) { - eachSeries.points.fillColor = fillColor == null ? false : fillColor; - } - - if (fill != null) { - eachSeries.points.fill = fill / 10; - } - - if (weight != null) { - eachSeries.points.lineWidth = weight; - } - - symbol = symbol || defaultSymbol; - if (!_.contains(validSymbols, symbol)) { - throw new Error( - i18n.translate('timelion.serverSideErrors.pointsFunction.notValidSymbolErrorMessage', { - defaultMessage: 'Valid symbols are: {validSymbols}', - values: { - validSymbols: validSymbols.join(', '), - }, - }) - ); - } - - eachSeries.points.symbol = symbol; - - eachSeries.points.show = show == null ? true : show; - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js deleted file mode 100644 index 756fb067f2335..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/precision.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('precision', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'precision', - types: ['number'], - help: i18n.translate('timelion.help.functions.precision.args.precisionHelpText', { - defaultMessage: 'Number of digits to round each value to', - }), - }, - ], - help: i18n.translate('timelion.help.functions.precisionHelpText', { - defaultMessage: 'number of digits to round the decimal portion of the value to', - }), - fn: async function precisionFn(args) { - await alter(args, function(eachSeries, precision) { - eachSeries._meta = eachSeries._meta || {}; - eachSeries._meta.precision = precision; - return eachSeries; - }); - - return reduce(args, function(a, b) { - return parseInt(a * Math.pow(10, b), 10) / Math.pow(10, b); - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js deleted file mode 100644 index 0ec8534d8fff2..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/props.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; -import _ from 'lodash'; - -function unflatten(data) { - if (Object(data) !== data || Array.isArray(data)) return data; - - const regex = new RegExp(/\.?([^.\[\]]+)|\[(\d+)\]/g); - const result = {}; - _.each(data, function(val, p) { - let cur = result; - let prop = ''; - let m; - while ((m = regex.exec(p))) { - cur = (cur.hasOwnProperty(prop) && cur[prop]) || (cur[prop] = m[2] ? [] : {}); - prop = m[2] || m[1]; - } - cur[prop] = data[p]; - }); - - return result[''] || result; -} - -export default new Chainable('props', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'global', - types: ['boolean', 'null'], - help: i18n.translate('timelion.help.functions.props.args.globalHelpText', { - defaultMessage: 'Set props on the seriesList vs on each series', - }), - }, - ], - extended: { - types: ['seriesList', 'number', 'string', 'boolean', 'null'], - // Extended args can not currently be multivalued, - // multi: false is not required and is shown here for demonstration purposes - multi: false, - }, - // extended means you can pass arguments that aren't listed. They just won't be in the ordered array - // They will be passed as args._extended:{} - help: i18n.translate('timelion.help.functions.propsHelpText', { - defaultMessage: - 'Use at your own risk, sets arbitrary properties on the series. For example {example}', - values: { - example: '.props(label=bears!)', - }, - }), - fn: function firstFn(args) { - const properties = unflatten(_.omit(args.byName, 'inputSeries', 'global')); - - if (args.byName.global) { - _.assign(args.byName.inputSeries, properties); - return args.byName.inputSeries; - } else { - return alter(args, function(eachSeries) { - _.assign(eachSeries, properties); - return eachSeries; - }); - } - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js deleted file mode 100644 index fd7de05464da3..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/quandl.js +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import fetch from 'node-fetch'; -import moment from 'moment'; -fetch.Promise = require('bluebird'); - -import Datasource from '../lib/classes/datasource'; - -export default new Datasource('quandl', { - dataSource: true, - args: [ - { - name: 'code', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.quandl.args.codeHelpText', { - defaultMessage: 'The quandl code to plot. You can find these on quandl.com.', - }), - }, - { - name: 'position', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.quandl.args.positionHelpText', { - defaultMessage: - 'Some quandl sources return multiple series, which one should I use? 1 based index.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.quandlHelpText', { - defaultMessage: ` - [experimental] - Pull data from quandl.com using the quandl code. Set {quandlKeyField} to your free API key in Kibana's - Advanced Settings. The API has a really low rate limit without a key.`, - values: { - quandlKeyField: '"timelion:quandl.key"', - }, - }), - fn: function quandlFn(args, tlConfig) { - const intervalMap = { - '1d': 'daily', - '1w': 'weekly', - '1M': 'monthly', - '1y': 'annual', - }; - - const config = _.defaults(args.byName, { - code: 'WIKI/AAPL', - position: 1, - interval: intervalMap[tlConfig.time.interval], - apikey: tlConfig.settings['timelion:quandl.key'], - }); - - if (!config.interval) { - throw new Error( - i18n.translate('timelion.serverSideErrors.quandlFunction.unsupportedIntervalErrorMessage', { - defaultMessage: - 'quandl() unsupported interval: {interval}. quandl() supports: {intervals}', - values: { - interval: tlConfig.time.interval, - intervals: _.keys(intervalMap).join(', '), - }, - }) - ); - } - - const time = { - min: moment.utc(tlConfig.time.from).format('YYYY-MM-DD'), - max: moment.utc(tlConfig.time.to).format('YYYY-MM-DD'), - }; - - // POSITIONS - // 1. open - // 2. high - // 3. low - // 4. close - // 5. volume - - const URL = - 'https://www.quandl.com/api/v1/datasets/' + - config.code + - '.json' + - '?sort_order=asc' + - '&trim_start=' + - time.min + - '&trim_end=' + - time.max + - '&collapse=' + - config.interval + - '&auth_token=' + - config.apikey; - - return fetch(URL) - .then(function(resp) { - return resp.json(); - }) - .then(function(resp) { - const data = _.map(resp.data, function(bucket) { - return [moment(bucket[0]).valueOf(), bucket[config.position]]; - }); - - return { - type: 'seriesList', - list: [ - { - data: data, - type: 'series', - fit: 'nearest', - label: resp.name, - }, - ], - }; - }) - .catch(function(e) { - throw e; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js deleted file mode 100644 index 5305d0b20cd26..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/range.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('range', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'min', - types: ['number'], - help: i18n.translate('timelion.help.functions.range.args.minHelpText', { - defaultMessage: 'New minimum value', - }), - }, - { - name: 'max', - types: ['number'], - help: i18n.translate('timelion.help.functions.range.args.maxHelpText', { - defaultMessage: 'New maximum value', - }), - }, - ], - help: i18n.translate('timelion.help.functions.rangeHelpText', { - defaultMessage: 'Changes the max and min of a series while keeping the same shape', - }), - fn: function range(args) { - return alter(args, function(eachSeries) { - const values = _.map(eachSeries.data, 1); - const min = _.min(values); - const max = _.max(values); - - // newvalue= (max'-min')/(max-min)*(value-min)+min'. - const data = _.map(eachSeries.data, function(point) { - const val = - ((args.byName.max - args.byName.min) / (max - min)) * (point[1] - min) + args.byName.min; - return [point[0], val]; - }); - eachSeries.data = data; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js deleted file mode 100644 index 778c91d30f2cb..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/scale_interval.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import toMS from '../lib/to_milliseconds.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('scale_interval', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'interval', - types: ['string'], - help: i18n.translate('timelion.help.functions.scaleInterval.args.intervalHelpText', { - defaultMessage: - 'The new interval in date math notation, e.g., 1s for 1 second. 1m, 5m, 1M, 1w, 1y, etc.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.scaleIntervalHelpText', { - defaultMessage: - 'Changes scales a value (usually a sum or a count) to a new interval. For example, as a per-second rate', - }), - fn: function scaleIntervalFn(args, tlConfig) { - const currentInterval = toMS(tlConfig.time.interval); - const scaleInterval = toMS(args.byName.interval); - - return alter(args, function(eachSeries) { - const data = _.map(eachSeries.data, function(point) { - return [point[0], (point[1] / currentInterval) * scaleInterval]; - }); - eachSeries.data = data; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js deleted file mode 100644 index 405f595cd4d32..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/static.js +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import Datasource from '../lib/classes/datasource'; -import Bluebird from 'bluebird'; - -export default new Datasource('static', { - aliases: ['value'], - args: [ - { - name: 'value', // _test-data.users.*.data - types: ['number', 'string'], - help: i18n.translate('timelion.help.functions.static.args.valueHelpText', { - defaultMessage: - 'The single value to to display, you can also pass several values and I will interpolate them evenly across your time range.', - }), - }, - { - name: 'label', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.static.args.labelHelpText', { - defaultMessage: - 'A quick way to set the label for the series. You could also use the .label() function', - }), - }, - ], - help: i18n.translate('timelion.help.functions.staticHelpText', { - defaultMessage: 'Draws a single value across the chart', - }), - fn: function staticFn(args, tlConfig) { - let data; - const target = tlConfig.getTargetSeries(); - if (typeof args.byName.value === 'string') { - const points = args.byName.value.split(':'); - const begin = _.first(target)[0]; - const end = _.last(target)[0]; - const step = (end - begin) / (points.length - 1); - data = _.map(points, function(point, i) { - return [begin + i * step, parseFloat(point)]; - }); - } else { - data = _.map(target, function(bucket) { - return [bucket[0], args.byName.value]; - }); - } - - return Bluebird.resolve({ - type: 'seriesList', - list: [ - { - data: data, - type: 'series', - label: args.byName.label == null ? String(args.byName.value) : args.byName.label, - fit: args.byName.fit || 'average', - }, - ], - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js deleted file mode 100644 index c2d83e5d24a61..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/subtract.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('subtract', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'term', - types: ['seriesList', 'number'], - help: i18n.translate('timelion.help.functions.subtract.args.termHelpText', { - defaultMessage: - 'Number or series to subtract from input. SeriesList with multiple series will be applied label-wise.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.subtractHelpText', { - defaultMessage: - 'Subtract the values of one or more series in a seriesList to each position, in each series, of the input seriesList', - }), - fn: function subtractFn(args) { - return reduce(args, function(a, b) { - return a - b; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js deleted file mode 100644 index 269c44badc90c..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/sum.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import reduce from '../lib/reduce.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('sum', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'term', - types: ['seriesList', 'number'], - help: i18n.translate('timelion.help.functions.sum.args.termHelpText', { - defaultMessage: - 'Number or series to sum with the input series. SeriesList with multiple series will be applied label-wise.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.sumHelpText', { - defaultMessage: - 'Adds the values of one or more series in a seriesList to each position, in each series, of the input seriesList', - }), - aliases: ['add', 'plus'], - fn: function sumFn(args) { - return reduce(args, function(a, b) { - return a + b; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js deleted file mode 100644 index 30202b682cf1b..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/title.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('title', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'title', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.title.args.titleHelpText', { - defaultMessage: 'Title for the plot.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.titleHelpText', { - defaultMessage: - 'Adds a title to the top of the plot. If called on more than 1 seriesList the last call will be used.', - }), - fn: function hideFn(args) { - return alter(args, function(eachSeries, title) { - eachSeries._title = title; - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js deleted file mode 100644 index a194e6c4c161c..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/index.js +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import Chainable from '../../lib/classes/chainable'; -import { linear, log } from './lib/regress'; - -const validRegressions = { - linear: 'linear', - log: 'logarithmic', -}; - -export default new Chainable('trend', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'mode', - types: ['string'], - help: i18n.translate('timelion.help.functions.trend.args.modeHelpText', { - defaultMessage: - 'The algorithm to use for generating the trend line. One of: {validRegressions}', - values: { - validRegressions: _.keys(validRegressions).join(', '), - }, - }), - suggestions: _.keys(validRegressions).map(key => { - return { name: key, help: validRegressions[key] }; - }), - }, - { - name: 'start', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.trend.args.startHelpText', { - defaultMessage: - 'Where to start calculating from the beginning or end. For example -10 would start ' + - 'calculating 10 points from the end, +15 would start 15 points from the beginning. Default: 0', - }), - }, - { - name: 'end', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.trend.args.endHelpText', { - defaultMessage: - 'Where to stop calculating from the beginning or end. For example -10 would stop ' + - 'calculating 10 points from the end, +15 would stop 15 points from the beginning. Default: 0', - }), - }, - ], - help: i18n.translate('timelion.help.functions.trendHelpText', { - defaultMessage: 'Draws a trend line using a specified regression algorithm', - }), - fn: function absFn(args) { - const newSeries = _.cloneDeep(args.byName.inputSeries); - - _.each(newSeries.list, function(series) { - const length = series.data.length; - let start = args.byName.start == null ? 0 : args.byName.start; - let end = args.byName.end == null ? length : args.byName.end; - start = start >= 0 ? start : length + start; - end = end > 0 ? end : length + end; - - const subset = series.data.slice(start, end); - - const result = args.byName.mode === 'log' ? log(subset) : linear(subset); - - _.each(series.data, function(point) { - point[1] = null; - }); - - _.each(result, function(point, i) { - series.data[start + i] = point; - }); - }); - return newSeries; - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js deleted file mode 100644 index da72e92be9312..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trend/lib/regress.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Algorithms from - * copyright(c) 2013 Tom Alexander - * Licensed under the MIT license. - */ - -import _ from 'lodash'; - -function sum(data, fn) { - return _.reduce( - data, - function(sum, d) { - return sum + (d[1] == null ? 0 : fn(d)); - }, - 0 - ); -} - -function count(data) { - return _.filter(data, function(d) { - return d[1] == null ? false : true; - }).length; -} - -function mapTuples(data, fn) { - return _.map(data, function(d) { - return [d[0], fn(d)]; - }); -} - -export function linear(data) { - const xSum = sum(data, d => { - return d[0]; - }); - const ySum = sum(data, d => { - return d[1]; - }); - const xSqSum = sum(data, d => { - return d[0] * d[0]; - }); - const xySum = sum(data, d => { - return d[0] * d[1]; - }); - const observations = count(data); - - const gradient = (observations * xySum - xSum * ySum) / (observations * xSqSum - xSum * xSum); - - const intercept = ySum / observations - (gradient * xSum) / observations; - - return mapTuples(data, d => { - return d[0] * gradient + intercept; - }); -} - -export function log(data) { - const logXSum = sum(data, d => { - return Math.log(d[0]); - }); - const yLogXSum = sum(data, d => { - return d[1] * Math.log(d[0]); - }); - const ySum = sum(data, d => { - return d[1]; - }); - const logXsqSum = sum(data, d => { - return Math.pow(Math.log(d[0]), 2); - }); - const observations = count(data); - - const b = - (observations * yLogXSum - ySum * logXSum) / (observations * logXsqSum - logXSum * logXSum); - - const a = (ySum - b * logXSum) / observations; - - return mapTuples(data, d => { - return a + b * Math.log(d[0]); - }); -} diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js deleted file mode 100644 index 7c4ecbbe89e1f..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/trim.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import alter from '../lib/alter.js'; -import _ from 'lodash'; -import Chainable from '../lib/classes/chainable'; - -export default new Chainable('trim', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'start', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.trim.args.startHelpText', { - defaultMessage: 'Buckets to trim from the beginning of the series. Default: 1', - }), - }, - { - name: 'end', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.trim.args.endHelpText', { - defaultMessage: 'Buckets to trim from the end of the series. Default: 1', - }), - }, - ], - help: i18n.translate('timelion.help.functions.trimHelpText', { - defaultMessage: - 'Set N buckets at the start or end of a series to null to fit the "partial bucket issue"', - }), - fn: function conditionFn(args) { - const config = args.byName; - if (config.start == null) config.start = 1; - if (config.end == null) config.end = 1; - - return alter(args, function(eachSeries) { - _.times(config.start, function(i) { - eachSeries.data[i][1] = null; - }); - - _.times(config.end, function(i) { - eachSeries.data[eachSeries.data.length - 1 - i][1] = null; - }); - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js deleted file mode 100644 index 3630d6c956b68..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import fetch from 'node-fetch'; -import moment from 'moment'; -import Datasource from '../lib/classes/datasource'; - -export default new Datasource('worldbank', { - args: [ - { - name: 'code', // countries/all/indicators/SP.POP.TOTL - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.worldbank.args.codeHelpText', { - defaultMessage: - 'Worldbank API path. This is usually everything after the domain, before the querystring. E.g.: {apiPathExample}.', - values: { - apiPathExample: '/en/countries/ind;chn/indicators/DPANUSSPF', - }, - }), - }, - ], - aliases: ['wb'], - help: i18n.translate('timelion.help.functions.worldbankHelpText', { - defaultMessage: ` - [experimental] - Pull data from {worldbankUrl} using path to series. - The worldbank provides mostly yearly data, and often has no data for the current year. - Try {offsetQuery} if you get no data for recent time ranges.`, - values: { - worldbankUrl: 'http://data.worldbank.org/', - offsetQuery: 'offset=-1y', - }, - }), - fn: function worldbank(args, tlConfig) { - // http://api.worldbank.org/en/countries/ind;chn/indicators/DPANUSSPF?date=2000:2006&MRV=5 - - const config = _.defaults(args.byName, { - code: 'countries/wld/indicators/SP.POP.TOTL', - }); - - const time = { - min: moment(tlConfig.time.from).format('YYYY'), - max: moment(tlConfig.time.to).format('YYYY'), - }; - - const URL = - 'http://api.worldbank.org/' + - config.code + - '?date=' + - time.min + - ':' + - time.max + - '&format=json' + - '&per_page=1000'; - - return fetch(URL) - .then(function(resp) { - return resp.json(); - }) - .then(function(resp) { - let hasData = false; - - const respSeries = resp[1]; - - const deduped = {}; - let description; - _.each(respSeries, function(bucket) { - if (bucket.value != null) hasData = true; - description = bucket.country.value + ' ' + bucket.indicator.value; - deduped[bucket.date] = bucket.value; - }); - - const data = _.compact( - _.map(deduped, function(val, date) { - // Discard nulls - if (val == null) return; - return [moment(date, 'YYYY').valueOf(), Number(val)]; - }) - ); - - if (!hasData) { - throw new Error( - i18n.translate('timelion.serverSideErrors.worldbankFunction.noDataErrorMessage', { - defaultMessage: 'Worldbank request succeeded, but there was no data for {code}', - values: { - code: config.code, - }, - }) - ); - } - - return { - type: 'seriesList', - list: [ - { - data: data, - type: 'series', - label: description, - _meta: { - worldbank_request: URL, - }, - }, - ], - }; - }) - .catch(function(e) { - throw e; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js deleted file mode 100644 index dc9a3b4a67b33..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/worldbank_indicators.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import worldbank from './worldbank.js'; -import Bluebird from 'bluebird'; -import Datasource from '../lib/classes/datasource'; - -export default new Datasource('worldbank_indicators', { - args: [ - { - name: 'country', // countries/all/indicators/SP.POP.TOTL - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.worldbankIndicators.args.countryHelpText', { - defaultMessage: `Worldbank country identifier. Usually the country's 2 letter code`, - }), - }, - { - name: 'indicator', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.worldbankIndicators.args.indicatorHelpText', { - defaultMessage: - `The indicator code to use. You'll have to look this up on {worldbankUrl}. ` + - 'Often pretty obtuse. E.g., {indicatorExample} is population', - values: { - worldbankUrl: 'data.worldbank.org', - indicatorExample: 'SP.POP.TOTL', - }, - }), - }, - ], - aliases: ['wbi'], - help: i18n.translate('timelion.help.functions.worldbankIndicatorsHelpText', { - defaultMessage: ` - [experimental] - Pull data from {worldbankUrl} using the country name and indicator. The worldbank provides - mostly yearly data, and often has no data for the current year. Try {offsetQuery} if you get no data for recent - time ranges.`, - values: { - worldbankUrl: 'http://data.worldbank.org/', - offsetQuery: 'offset=-1y', - }, - }), - fn: function worldbankIndicators(args, tlConfig) { - const config = _.defaults(args.byName, { - country: 'wld', - indicator: 'SP.POP.TOTL', - }); - - const countries = config.country.split(':'); - const seriesLists = _.map(countries, function(country) { - const code = 'countries/' + country + '/indicators/' + config.indicator; - const wbArgs = [code]; - wbArgs.byName = { code: code }; - return worldbank.timelionFn(wbArgs, tlConfig); - }); - - return Bluebird.map(seriesLists, function(seriesList) { - return seriesList.list[0]; - }).then(function(list) { - return { - type: 'seriesList', - list: list, - }; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js b/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js deleted file mode 100644 index 2653ea398aa14..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/series_functions/yaxis.js +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import _ from 'lodash'; -import alter from '../lib/alter.js'; -import Chainable from '../lib/classes/chainable'; -const tickFormatters = { - bits: 'bits', - 'bits/s': 'bits/s', - bytes: 'bytes', - 'bytes/s': 'bytes/s', - currency: 'currency(:ISO 4217 currency code)', - percent: 'percent', - custom: 'custom(:prefix:suffix)', -}; - -export default new Chainable('yaxis', { - args: [ - { - name: 'inputSeries', - types: ['seriesList'], - }, - { - name: 'yaxis', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.yaxisHelpText', { - defaultMessage: - 'The numbered y-axis to plot this series on, e.g., .yaxis(2) for a 2nd y-axis.', - }), - }, - { - name: 'min', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.minHelpText', { - defaultMessage: 'Min value', - }), - }, - { - name: 'max', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.maxHelpText', { - defaultMessage: 'Max value', - }), - }, - { - name: 'position', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.positionHelpText', { - defaultMessage: 'left or right', - }), - }, - { - name: 'label', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.labelHelpText', { - defaultMessage: 'Label for axis', - }), - }, - { - name: 'color', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.colorHelpText', { - defaultMessage: 'Color of axis label', - }), - }, - { - name: 'units', - types: ['string', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.unitsHelpText', { - defaultMessage: 'The function to use for formatting y-axis labels. One of: {formatters}', - values: { - formatters: _.values(tickFormatters).join(', '), - }, - }), - suggestions: _.keys(tickFormatters).map(key => { - return { name: key, help: tickFormatters[key] }; - }), - }, - { - name: 'tickDecimals', - types: ['number', 'null'], - help: i18n.translate('timelion.help.functions.yaxis.args.tickDecimalsHelpText', { - defaultMessage: 'The number of decimal places for the y-axis tick labels.', - }), - }, - ], - help: i18n.translate('timelion.help.functions.yaxisHelpText', { - defaultMessage: - 'Configures a variety of y-axis options, the most important likely being the ability to add an Nth (eg 2nd) y-axis', - }), - fn: function yaxisFn(args) { - return alter(args, function( - eachSeries, - yaxis, - min, - max, - position, - label, - color, - units, - tickDecimals - ) { - yaxis = yaxis || 1; - - eachSeries.yaxis = yaxis; - eachSeries._global = eachSeries._global || {}; - - eachSeries._global.yaxes = eachSeries._global.yaxes || []; - eachSeries._global.yaxes[yaxis - 1] = eachSeries._global.yaxes[yaxis - 1] || {}; - - const myAxis = eachSeries._global.yaxes[yaxis - 1]; - myAxis.position = position || (yaxis % 2 ? 'left' : 'right'); - myAxis.min = min; - myAxis.max = max; - myAxis.axisLabelFontSizePixels = 11; - myAxis.axisLabel = label; - myAxis.axisLabelColour = color; - myAxis.axisLabelUseCanvas = true; - - if (tickDecimals) { - myAxis.tickDecimals = tickDecimals < 0 ? 0 : tickDecimals; - } - - if (units) { - const unitTokens = units.split(':'); - const unitType = unitTokens[0]; - if (!tickFormatters[unitType]) { - throw new Error( - i18n.translate( - 'timelion.serverSideErrors.yaxisFunction.notSupportedUnitTypeErrorMessage', - { - defaultMessage: '{units} is not a supported unit type.', - values: { units }, - } - ) - ); - } - if (unitType === 'currency') { - const threeLetterCode = /^[A-Za-z]{3}$/; - const currency = unitTokens[1]; - if (currency && !threeLetterCode.test(currency)) { - throw new Error( - i18n.translate( - 'timelion.serverSideErrors.yaxisFunction.notValidCurrencyFormatErrorMessage', - { - defaultMessage: 'Currency must be a three letter code', - } - ) - ); - } - } - - myAxis.units = { - type: unitType, - prefix: unitTokens[1] || '', - suffix: unitTokens[2] || '', - }; - - if (unitType === 'percent') { - // jquery.flot uses axis.tickDecimals to generate tick values - // need 2 extra decimal places to preserve precision when percent shifts value to left - myAxis.units.tickDecimalsShift = 2; - if (tickDecimals) { - myAxis.tickDecimals += myAxis.units.tickDecimalsShift; - } else { - myAxis.tickDecimals = myAxis.units.tickDecimalsShift; - } - } - } - - return eachSeries; - }); - }, -}); diff --git a/src/legacy/core_plugins/vis_type_timelion/server/types.ts b/src/legacy/core_plugins/vis_type_timelion/server/types.ts deleted file mode 100644 index e612bc14a0daa..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/server/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { - TimelionFunctionInterface, - TimelionFunctionConfig, - TimelionFunctionArgs, - TimelionFunctionArgsSuggestion, - TimelionFunctionArgsTypes, -} from './lib/classes/timelion_function'; - -export { TimelionRequestQuery } from './routes/run'; From 2091a0caeafd831579a6e5b1a7c0ef4265548bab Mon Sep 17 00:00:00 2001 From: sulemanof Date: Sat, 21 Dec 2019 16:42:18 +0300 Subject: [PATCH 23/59] Change plugin start --- .../components/timelion_expression_input.tsx | 15 +- .../timelion_expression_input_helpers.js | 8 +- .../directives/timelion_expression_input.js | 6 +- .../arg_value_suggestions.js | 188 ------------------ .../core_plugins/timelion/public/legacy.ts | 2 +- .../public/panels/timechart/schema.ts | 6 +- .../core_plugins/timelion/public/plugin.ts | 25 ++- .../public/services/arg_value_suggestions.ts | 11 +- .../services/{types.ts => plugin_services.ts} | 14 +- .../timelion/public/vis/index.tsx | 21 +- .../public/vis/timelion_request_handler.ts | 6 +- 11 files changed, 53 insertions(+), 249 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js rename src/legacy/core_plugins/timelion/public/services/{types.ts => plugin_services.ts} (63%) diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx index 7e9051e012b03..612050bb2f5cb 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useCallback, useRef } from 'react'; +import React, { useEffect, useCallback, useRef, useMemo } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; @@ -25,7 +25,7 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { TimelionServices } from '../services/types'; +import { getArgValueSuggestions } from '../services/arg_value_suggestions'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); @@ -37,7 +37,8 @@ interface TimelionExpressionInputProps { function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputProps) { const functionList = useRef([]); - const kibana = useKibana(); + const kibana = useKibana(); + const argValueSuggestions = useMemo(getArgValueSuggestions, []); const provideCompletionItems = useCallback( async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { @@ -56,7 +57,7 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro // it's important to offset the cursor position on 1 point left // because of PEG parser starts the line with 0, but monaco with 1 position.column - 1, - kibana.services.argValueSuggestions + argValueSuggestions ); return { @@ -67,7 +68,7 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro : [], }; }, - [kibana.services.argValueSuggestions] + [argValueSuggestions] ); const provideHover = useCallback( @@ -78,7 +79,7 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro // it's important to offset the cursor position on 1 point left // because of PEG parser starts the line with 0, but monaco with 1 position.column - 1, - kibana.services.argValueSuggestions + argValueSuggestions ); return { @@ -89,7 +90,7 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro : [], }; }, - [kibana.services.argValueSuggestions] + [argValueSuggestions] ); useEffect(() => { diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index b90f5932b5b09..a4e23ec544894 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; import grammar from 'raw-loader!../../chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { ArgValueSuggestionsProvider } from '../timelion_expression_suggestions/arg_value_suggestions'; +import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; describe('Timelion expression suggestions', () => { describe('getSuggestions', () => { @@ -44,11 +44,7 @@ describe('Timelion expression suggestions', () => { }; const functionList = [func1, myFunc2]; let Parser; - const privateStub = () => { - return {}; - }; - const indexPatternsStub = {}; - const argValueSuggestions = ArgValueSuggestionsProvider(privateStub, indexPatternsStub); // eslint-disable-line new-cap + const argValueSuggestions = getArgValueSuggestions(); beforeEach(function() { Parser = PEG.generate(grammar); }); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 137dd6b82046d..449c0489fea25 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -52,11 +52,11 @@ import { insertAtLocation, } from './timelion_expression_input_helpers'; import { comboBoxKeyCodes } from '@elastic/eui'; -import { ArgValueSuggestionsProvider } from './timelion_expression_suggestions/arg_value_suggestions'; +import { getArgValueSuggestions } from '../services/arg_value_suggestions'; const Parser = PEG.generate(grammar); -export function TimelionExpInput($http, $timeout, Private) { +export function TimelionExpInput($http, $timeout) { return { restrict: 'E', scope: { @@ -68,7 +68,7 @@ export function TimelionExpInput($http, $timeout, Private) { replace: true, template: timelionExpressionInputTemplate, link: function(scope, elem) { - const argValueSuggestions = Private(ArgValueSuggestionsProvider); + const argValueSuggestions = getArgValueSuggestions(); const expressionInput = elem.find('[data-expression-input]'); const functionReference = {}; let suggestibleFunctionLocation = {}; diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js deleted file mode 100644 index e698a69401a37..0000000000000 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { npStart } from 'ui/new_platform'; - -export function ArgValueSuggestionsProvider() { - const { indexPatterns } = npStart.plugins.data; - const { client: savedObjectsClient } = npStart.core.savedObjects; - - async function getIndexPattern(functionArgs) { - const indexPatternArg = functionArgs.find(argument => { - return argument.name === 'index'; - }); - if (!indexPatternArg) { - // index argument not provided - return; - } - const indexPatternTitle = _.get(indexPatternArg, 'value.text'); - - const resp = await savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], - search: `"${indexPatternTitle}"`, - search_fields: ['title'], - perPage: 10, - }); - const indexPatternSavedObject = resp.savedObjects.find(savedObject => { - return savedObject.attributes.title === indexPatternTitle; - }); - if (!indexPatternSavedObject) { - // index argument does not match an index pattern - return; - } - - return await indexPatterns.get(indexPatternSavedObject.id); - } - - function containsFieldName(partial, field) { - if (!partial) { - return true; - } - return field.name.includes(partial); - } - - // Argument value suggestion handlers requiring custom client side code - // Could not put with function definition since functions are defined on server - const customHandlers = { - es: { - index: async function(partial) { - const search = partial ? `${partial}*` : '*'; - const resp = await savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'type'], - search: `${search}`, - search_fields: ['title'], - perPage: 25, - }); - return resp.savedObjects - .filter(savedObject => !savedObject.get('type')) - .map(savedObject => { - return { name: savedObject.attributes.title }; - }); - }, - metric: async function(partial, functionArgs) { - if (!partial || !partial.includes(':')) { - return [ - { name: 'avg:' }, - { name: 'cardinality:' }, - { name: 'count' }, - { name: 'max:' }, - { name: 'min:' }, - { name: 'percentiles:' }, - { name: 'sum:' }, - ]; - } - - const indexPattern = await getIndexPattern(functionArgs); - if (!indexPattern) { - return []; - } - - const valueSplit = partial.split(':'); - return indexPattern.fields - .filter(field => { - return ( - field.aggregatable && - 'number' === field.type && - containsFieldName(valueSplit[1], field) - ); - }) - .map(field => { - return { name: `${valueSplit[0]}:${field.name}`, help: field.type }; - }); - }, - split: async function(partial, functionArgs) { - const indexPattern = await getIndexPattern(functionArgs); - if (!indexPattern) { - return []; - } - - return indexPattern.fields - .filter(field => { - return ( - field.aggregatable && - ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) && - containsFieldName(partial, field) - ); - }) - .map(field => { - return { name: field.name, help: field.type }; - }); - }, - timefield: async function(partial, functionArgs) { - const indexPattern = await getIndexPattern(functionArgs); - if (!indexPattern) { - return []; - } - - return indexPattern.fields - .filter(field => { - return 'date' === field.type && containsFieldName(partial, field); - }) - .map(field => { - return { name: field.name }; - }); - }, - }, - }; - - return { - /** - * @param {string} functionName - user provided function name containing argument - * @param {string} argName - user provided argument name - * @return {boolean} true when dynamic suggestion handler provided for function argument - */ - hasDynamicSuggestionsForArgument: (functionName, argName) => { - return customHandlers[functionName] && customHandlers[functionName][argName]; - }, - - /** - * @param {string} functionName - user provided function name containing argument - * @param {string} argName - user provided argument name - * @param {object} functionArgs - user provided function arguments parsed ahead of current argument - * @param {string} partial - user provided argument value - * @return {array} array of dynamic suggestions matching partial - */ - getDynamicSuggestionsForArgument: async ( - functionName, - argName, - functionArgs, - partialInput = '' - ) => { - return await customHandlers[functionName][argName](partialInput, functionArgs); - }, - - /** - * @param {string} partial - user provided argument value - * @param {array} staticSuggestions - argument value suggestions - * @return {array} array of static suggestions matching partial - */ - getStaticSuggestionsForInput: (partialInput = '', staticSuggestions = []) => { - if (partialInput) { - return staticSuggestions.filter(suggestion => { - return suggestion.name.includes(partialInput); - }); - } - - return staticSuggestions; - }, - }; -} diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index d989a68d40eeb..1cf6bb65cdc02 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -37,4 +37,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 432430b0083e8..0bbda4bf3646f 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -34,11 +34,7 @@ import { generateTicksProvider } from './tick_generator'; const DEBOUNCE_DELAY = 50; export function timechartFn(dependencies: TimelionVisualizationDependencies) { - const { - $rootScope, - $compile, - core: { uiSettings }, - } = dependencies; + const { $rootScope, $compile, uiSettings } = dependencies; return function() { return { diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 6c85804632f6d..42f0ee3ad4725 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -16,7 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; +import { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + IUiSettingsClient, + HttpSetup, +} from 'kibana/public'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; import { PluginsStart } from 'ui/new_platform/new_platform'; @@ -26,10 +33,12 @@ import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; +import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { - core: CoreSetup; + uiSettings: IUiSettingsClient; + http: HttpSetup; timelionPanels: Map; timefilter: TimefilterContract; } @@ -53,13 +62,14 @@ export class TimelionPlugin implements Plugin, void> { } public async setup( - core: CoreSetup, + core: CoreSetup, { __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies ) { const timelionPanels: Map = new Map(); const dependencies: TimelionVisualizationDependencies = { - core, + uiSettings: core.uiSettings, + http: core.http, timelionPanels, timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), @@ -68,7 +78,7 @@ export class TimelionPlugin implements Plugin, void> { this.registerPanels(dependencies); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(await getTimelionVisualization(dependencies)); + visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { @@ -77,12 +87,15 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start(core: CoreStart) { + public start(core: CoreStart, plugins: PluginsStart) { const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); if (timelionUiEnabled === false) { core.chrome.navLinks.update('timelion', { hidden: true }); } + + setIndexPatterns(plugins.data.indexPatterns); + setSavedObjectsClient(core.savedObjects.client); } public stop(): void {} diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts index 998562098719d..8d133de51f6d9 100644 --- a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts +++ b/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts @@ -18,9 +18,8 @@ */ import { get } from 'lodash'; -import { SavedObjectsClientContract } from 'kibana/public'; -import { IndexPatternsContract } from 'src/plugins/data/public'; import { TimelionFunctionArgs } from '../../common/types'; +import { getIndexPatterns, getSavedObjectsClient } from './plugin_services'; export interface Location { min: number; @@ -41,10 +40,10 @@ export interface FunctionArg { }; } -export function getArgValueSuggestions( - indexPatterns: IndexPatternsContract, - savedObjectsClient: SavedObjectsClientContract -) { +export function getArgValueSuggestions() { + const indexPatterns = getIndexPatterns(); + const savedObjectsClient = getSavedObjectsClient(); + async function getIndexPattern(functionArgs: FunctionArg[]) { const indexPatternArg = functionArgs.find(({ name }) => name === 'index'); if (!indexPatternArg) { diff --git a/src/legacy/core_plugins/timelion/public/services/types.ts b/src/legacy/core_plugins/timelion/public/services/plugin_services.ts similarity index 63% rename from src/legacy/core_plugins/timelion/public/services/types.ts rename to src/legacy/core_plugins/timelion/public/services/plugin_services.ts index 135bf953d5a50..5ba4ee5e47983 100644 --- a/src/legacy/core_plugins/timelion/public/services/types.ts +++ b/src/legacy/core_plugins/timelion/public/services/plugin_services.ts @@ -17,8 +17,14 @@ * under the License. */ -import { ArgValueSuggestions } from './arg_value_suggestions'; +import { IndexPatternsContract } from 'src/plugins/data/public'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; -export interface TimelionServices { - argValueSuggestions: ArgValueSuggestions; -} +export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( + 'IndexPatterns' +); + +export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter< + SavedObjectsClientContract +>('SavedObjectsClient'); diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/timelion/public/vis/index.tsx index 4ad0433d7613d..1edcb0a5ce71c 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.tsx +++ b/src/legacy/core_plugins/timelion/public/vis/index.tsx @@ -30,22 +30,13 @@ import { TimelionVisualizationDependencies } from '../plugin'; import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; import { TimelionOptions } from './timelion_options'; import { VisParams } from '../timelion_vis_fn'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; export const TIMELION_VIS_NAME = 'timelion'; -export async function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { - const { core } = dependencies; - const { http, uiSettings } = core; +export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { + const { http, uiSettings } = dependencies; const timelionRequestHandler = getTimelionRequestHandler(dependencies); - const [coreStart, pluginsStart] = await core.getStartServices(); - - const argValueSuggestions = getArgValueSuggestions( - pluginsStart.data.indexPatterns, - coreStart.savedObjects.client - ); - // return the visType object, which kibana will use to display and configure new // Vis object of this type. return { @@ -65,13 +56,7 @@ export async function getTimelionVisualization(dependencies: TimelionVisualizati }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( - + ), diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 74f51da1ebd76..14cd3d0083e6a 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -47,11 +47,7 @@ export interface TimelionSuccessResponse { } export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { - core: { uiSettings, http }, - timefilter, - } = dependencies; - + const { uiSettings, http, timefilter } = dependencies; const timezone = timezoneProvider(uiSettings)(); return async function({ From d673890360ddcbdaaca61371efabbff9d8b6ea69 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 21 Dec 2019 17:04:48 +0300 Subject: [PATCH 24/59] Remove vis from timelion plugin --- .../core_plugins/timelion/public/app.js | 2 - .../core_plugins/timelion/public/plugin.ts | 2 - .../timelion/public/timelion_vis_fn.ts | 2 +- .../timelion/public/vis/get_inner_angular.ts | 59 ---------- .../core_plugins/timelion/public/vis/index.ts | 63 ----------- .../timelion/public/vis/legacy_imports.ts | 32 ------ .../public/vis/timelion_controller.ts | 106 ------------------ .../public/vis/timelion_request_handler.ts | 4 +- .../timelion/public/vis/timelion_vis.html | 3 - .../public/vis/timelion_vis_legacy_module.ts | 49 -------- .../public/vis/timelion_vis_params.html | 27 ----- .../core_plugins/vis_type_timelion/index.ts | 2 +- .../public}/_timelion_vis.scss | 0 .../public/index.scss} | 0 14 files changed, 5 insertions(+), 346 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts delete mode 100644 src/legacy/core_plugins/timelion/public/vis/index.ts delete mode 100644 src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts delete mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts delete mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_vis.html delete mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts delete mode 100644 src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html rename src/legacy/core_plugins/{timelion/public/vis => vis_type_timelion/public}/_timelion_vis.scss (100%) rename src/legacy/core_plugins/{timelion/public/vis/_index.scss => vis_type_timelion/public/index.scss} (100%) diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 7ef722ee3a277..084e497761e43 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -59,8 +59,6 @@ document.title = 'Timelion - Kibana'; const app = require('ui/modules').get('apps/timelion', []); -require('./vis'); - require('ui/routes').enable(); require('ui/routes').when('/:id?', { diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 97f9b614f475f..631edf8cd2dd0 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -28,7 +28,6 @@ import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; -import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; @@ -78,7 +77,6 @@ export class TimelionPlugin implements Plugin, void> { this.registerPanels(dependencies); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - // visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts index 474f464a550cd..ad7d713517e40 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts @@ -22,9 +22,9 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; import { getTimelionRequestHandler } from './vis/timelion_request_handler'; import { TimelionVisualizationDependencies } from './plugin'; -import { TIMELION_VIS_NAME } from './vis'; const name = 'timelion_vis'; +const TIMELION_VIS_NAME = 'timelion'; interface Arguments { expression: string; diff --git a/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts b/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts deleted file mode 100644 index 8773370c3b05b..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/get_inner_angular.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// inner angular imports -// these are necessary to bootstrap the local angular. -// They can stay even after NP cutover -import angular from 'angular'; -import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -import { CoreStart, LegacyCoreStart } from 'kibana/public'; -import { - watchMultiDecorator, - KbnAccessibleClickProvider, - configureAppAngularModule, -} from './legacy_imports'; - -const thirdPartyAngularDependencies = ['ngSanitize', 'ui.bootstrap']; - -export function getAngularModule(name: string, core: CoreStart) { - const uiModule = getInnerAngular(name, core); - configureAppAngularModule(uiModule, core as LegacyCoreStart, true); - return uiModule; -} - -let initialized = false; - -export function getInnerAngular(name = 'kibana/timelion_vis', core: CoreStart) { - if (!initialized) { - createLocalI18nModule(); - initialized = true; - } - return angular - .module(name, [...thirdPartyAngularDependencies, 'timelionVisI18n']) - .config(watchMultiDecorator) - .directive('kbnAccessibleClick', KbnAccessibleClickProvider); -} - -function createLocalI18nModule() { - angular - .module('timelionVisI18n', []) - .provider('i18n', I18nProvider) - .filter('i18n', i18nFilter) - .directive('i18nId', i18nDirective); -} diff --git a/src/legacy/core_plugins/timelion/public/vis/index.ts b/src/legacy/core_plugins/timelion/public/vis/index.ts deleted file mode 100644 index cbab37548068d..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { DefaultEditorSize } from './legacy_imports'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import visConfigTemplate from './timelion_vis.html'; -import editorConfigTemplate from './timelion_vis_params.html'; -import { TimelionVisualizationDependencies } from '../plugin'; -import { TimelionVisController } from './timelion_controller'; - -export const TIMELION_VIS_NAME = 'timelion'; - -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { - const timelionRequestHandler = getTimelionRequestHandler(dependencies); - - // return the visType object, which kibana will use to display and configure new - // Vis object of this type. - return { - name: TIMELION_VIS_NAME, - title: 'Timelion', - icon: 'visTimelion', - description: i18n.translate('timelion.timelionDescription', { - defaultMessage: 'Build time-series using functional expressions', - }), - visualization: TimelionVisController, - visConfig: { - defaults: { - expression: '.es(*)', - interval: 'auto', - }, - template: visConfigTemplate, - }, - editorConfig: { - optionsTemplate: editorConfigTemplate, - defaultSize: DefaultEditorSize.MEDIUM, - }, - requestHandler: timelionRequestHandler, - responseHandler: 'none', - options: { - showIndexSelection: false, - showQueryBar: false, - showFilterBar: false, - }, - }; -} diff --git a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts b/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts deleted file mode 100644 index 8ec629f4982bf..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/legacy_imports.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { npStart } from 'ui/new_platform'; - -// @ts-ignore -export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; - -// @ts-ignore -export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; -export { configureAppAngularModule } from 'ui/legacy_compat'; -// @ts-ignore -export { DefaultEditorSize } from 'ui/vis/editor_size'; -// @ts-ignore -export { timezoneProvider } from 'ui/vis/lib/timezone'; -export { VisParams } from 'ui/vis'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts deleted file mode 100644 index 5c4cff1a058f4..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_controller.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; -import $ from 'jquery'; - -import { Vis, VisParams } from '../../../visualizations/public'; -import { npStart } from './legacy_imports'; -import { getServices } from '../kibana_services'; -import { getAngularModule } from './get_inner_angular'; -import { initTimelionVisLegacyModule } from './timelion_vis_legacy_module'; - -const innerAngularName = 'kibana/timelion_vis'; - -export class TimelionVisController { - private timelionVisModule: IModule | undefined; - private injector: auto.IInjectorService | undefined; - el: JQuery; - vis: Vis; - $rootScope: IRootScopeService | null = null; - $scope: (IScope & { [key: string]: any }) | undefined; - $compile: ICompileService | undefined; - - constructor(domeElement: Element, vis: Vis) { - this.el = $(domeElement); - this.vis = vis; - } - - getInjector() { - if (!this.injector) { - const mountpoint = document.createElement('div'); - mountpoint.setAttribute('class', 'visChart'); - this.injector = angular.bootstrap(mountpoint, [innerAngularName]); - this.el.append(mountpoint); - } - - return this.injector; - } - - initLocalAngular() { - if (!this.timelionVisModule) { - this.timelionVisModule = getAngularModule(innerAngularName, npStart.core); - initTimelionVisLegacyModule(this.timelionVisModule, getServices().timelionPanels); - } - } - - async render(esResponse: object, visParams: VisParams, status: { [key: string]: boolean }) { - this.initLocalAngular(); - - return new Promise(async (resolve, reject) => { - if (!this.$rootScope) { - const $injector = this.getInjector(); - this.$rootScope = $injector.get('$rootScope'); - this.$compile = $injector.get('$compile'); - } - const updateScope = () => { - if (!this.$scope) { - return; - } - - this.$scope.vis = this.vis; - this.$scope.visState = this.vis.getState(); - this.$scope.esResponse = esResponse; - this.$scope.visParams = visParams; - this.$scope.renderComplete = resolve; - this.$scope.renderFailed = reject; - this.$scope.resize = Date.now(); - this.$scope.updateStatus = status; - this.$scope.$apply(); - }; - - if (!this.$scope && this.$compile) { - this.$scope = this.$rootScope.$new(); - this.$scope.uiState = this.vis.getUiState(); - updateScope(); - this.el.find('div').append(this.$compile(this.vis.type.visConfig.template)(this.$scope)); - this.$scope.$apply(); - } else { - updateScope(); - } - }); - } - - destroy() { - if (this.$rootScope) { - this.$rootScope.$destroy(); - this.$rootScope = null; - } - } -} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 67f571ef99225..14cd3d0083e6a 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -17,11 +17,13 @@ * under the License. */ +// @ts-ignore +import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; +import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; -import { timezoneProvider, VisParams } from './legacy_imports'; interface Stats { cacheCount: number; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html deleted file mode 100644 index 8bfb9e91a2523..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts deleted file mode 100644 index 37a198611a07a..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_legacy_module.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IModule } from 'angular'; - -import { Panel } from '../panels/panel'; -// @ts-ignore -import { Chart } from '../directives/chart/chart'; -// @ts-ignore -import { TimelionInterval } from '../directives/timelion_interval/timelion_interval'; -// @ts-ignore -import { TimelionExpInput } from '../directives/timelion_expression_input'; -// @ts-ignore -import { TimelionExpressionSuggestions } from '../directives/timelion_expression_suggestions/timelion_expression_suggestions'; - -/** @internal */ -export const initTimelionVisLegacyModule = ( - angularIns: IModule, - timelionPanels: Map -): void => { - angularIns - .controller('TimelionVisController', function($scope: any) { - $scope.$on('timelionChartRendered', (event: any) => { - event.stopPropagation(); - $scope.renderComplete(); - }); - }) - .constant('timelionPanels', timelionPanels) - .directive('chart', Chart) - .directive('timelionInterval', TimelionInterval) - .directive('timelionExpressionSuggestions', TimelionExpressionSuggestions) - .directive('timelionExpressionInput', TimelionExpInput); -}; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html deleted file mode 100644 index 9f2d2094fb1f7..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html +++ /dev/null @@ -1,27 +0,0 @@ -
-
- -
- -
-
- -
-
- -
- - -
- -
diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts index 4beba9627ed85..b6134884b993b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -27,7 +27,7 @@ const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: Legac require: ['kibana', 'elasticsearch', 'visualizations', 'data'], publicDir: resolve(__dirname, 'public'), uiExports: { - // styleSheetPaths: resolve(__dirname, 'public/index.scss'), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars: server => ({}), }, diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss diff --git a/src/legacy/core_plugins/timelion/public/vis/_index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_index.scss rename to src/legacy/core_plugins/vis_type_timelion/public/index.scss From cb4716a381dc4c5674bc381f9aa22a79b5e4b77c Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 21 Dec 2019 17:29:30 +0300 Subject: [PATCH 25/59] Rename class --- .../vis_type_timelion/public/components/panel.tsx | 5 +++-- .../vis_type_timelion/public/helpers/panel_utils.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index b28033a10eca8..8638ce33cea2d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -104,7 +104,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp canvasNode.find('div.legend table').append(caption); - setLegendValueNumbers(canvasNode.find('.legendValueNumber')); + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); // legend has been re-created. Apply focus on legend element when previously set if (focusedSeries || focusedSeries === 0) { const $legendLabels = canvasNode.find('div.legend table .legendLabel>span'); @@ -176,7 +176,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp setPlot($.plot(canvasElem, compact(updatedSeries), options)); renderComplete(); - const legend = $(canvasElem).find('.legendValue'); + const legend = $(canvasElem).find('.ngLegendValue'); if (legend) { legend.click(toggleSeries); legend.focus(focusSeries); @@ -309,6 +309,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp .off('plotselected') .off('plothover') .off('mouseleave'); + $chart .on('plotselected', plotSelectedHandler) .on('plothover', plotHoverHandler) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index c60798925f981..bcbbc85e6ba9f 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -176,11 +176,11 @@ function buildOptions( const labelSpan = document.createElement('span'); const numberSpan = document.createElement('span'); - wrapperSpan.setAttribute('class', 'legendValue'); + wrapperSpan.setAttribute('class', 'ngLegendValue'); wrapperSpan.setAttribute(SERIES_ID_ATTR, series._id); labelSpan.appendChild(document.createTextNode(label)); - numberSpan.setAttribute('class', 'legendValueNumber'); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); wrapperSpan.appendChild(labelSpan); wrapperSpan.appendChild(numberSpan); From daf056e7afaaacc7f4b97fd675e8cc5957d711fa Mon Sep 17 00:00:00 2001 From: sulemanof Date: Sat, 21 Dec 2019 18:02:57 +0300 Subject: [PATCH 26/59] Mock services --- .../__tests__/timelion_expression_input_helpers.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index a4e23ec544894..231330b898edb 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -22,8 +22,14 @@ import PEG from 'pegjs'; import grammar from 'raw-loader!../../chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; +import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services'; describe('Timelion expression suggestions', () => { + setIndexPatterns({}); + setSavedObjectsClient({}); + + const argValueSuggestions = getArgValueSuggestions(); + describe('getSuggestions', () => { const func1 = { name: 'func1', @@ -44,7 +50,6 @@ describe('Timelion expression suggestions', () => { }; const functionList = [func1, myFunc2]; let Parser; - const argValueSuggestions = getArgValueSuggestions(); beforeEach(function() { Parser = PEG.generate(grammar); }); From 21bd6d63a6afa447344b02f2a3b4d21fc1fe9fcc Mon Sep 17 00:00:00 2001 From: sulemanof Date: Mon, 23 Dec 2019 16:02:24 +0300 Subject: [PATCH 27/59] Fix other comments --- .../components/timelion_expression_input.tsx | 22 ++++++------ .../public/components/timelion_interval.tsx | 36 ++++++++++++++----- .../timelion/public/vis/_timelion_editor.scss | 12 ++++--- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx index 612050bb2f5cb..8331030eee982 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -18,8 +18,8 @@ */ import React, { useEffect, useCallback, useRef, useMemo } from 'react'; -import { EuiFormRow } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import { EuiFormLabel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; @@ -102,13 +102,10 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro }, [kibana.services.http]); return ( - +
+ + +
- +
); } diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx index df8f7471eb71b..6294e51e54788 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx @@ -17,10 +17,12 @@ * under the License. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; + import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; +import { isValidEsInterval } from '../../../../core_plugins/data/common'; const intervalOptions = [ { @@ -80,20 +82,26 @@ interface TimelionIntervalProps { } function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProps) { - const onCustomInterval = (customValue: string) => { - setValue(customValue.trim()); - }; + const onCustomInterval = useCallback( + (customValue: string) => { + setValue(customValue.trim()); + }, + [setValue] + ); - const onChange = (opts: Array>) => { - setValue((opts[0] && opts[0].value) || ''); - }; + const onChange = useCallback( + (opts: Array>) => { + setValue((opts[0] && opts[0].value) || ''); + }, + [setValue] + ); const selectedOptions = useMemo( () => [intervalOptions.find(op => op.value === value) || { label: value, value }], [value] ); - const isValid = !!value; + const isValid = intervalOptions.some(int => int.value === value) || isValidEsInterval(value); useValidation(setValidity, isValid); @@ -101,10 +109,20 @@ function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProp Date: Mon, 23 Dec 2019 16:59:20 +0300 Subject: [PATCH 28/59] Remove duplicate files --- .eslintignore | 2 +- .i18nrc.json | 2 +- .../core_plugins/timelion/public/legacy.ts | 2 - .../timelion/public/panels/timechart/flot.js | 26 - .../public/panels/timechart/schema.ts | 8 +- .../public/panels/timechart/tick_generator.ts | 61 - .../panels/timechart/xaxis_formatter.ts | 54 - .../core_plugins/timelion/public/plugin.ts | 4 +- .../public/services/tick_formatters.ts | 90 - .../{vis => }/timelion_request_handler.ts | 4 +- .../timelion/public/timelion_vis_fn.ts | 2 +- .../webpackShims/jquery.flot.axislabels.js | 462 --- .../webpackShims/jquery.flot.crosshair.js | 176 - .../public/webpackShims/jquery.flot.js | 3168 ----------------- .../webpackShims/jquery.flot.selection.js | 360 -- .../public/webpackShims/jquery.flot.stack.js | 188 - .../public/webpackShims/jquery.flot.symbol.js | 71 - .../public/webpackShims/jquery.flot.time.js | 432 --- .../core_plugins/vis_type_timelion/index.ts | 2 - 19 files changed, 10 insertions(+), 5104 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/panels/timechart/flot.js delete mode 100644 src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts delete mode 100644 src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts delete mode 100644 src/legacy/core_plugins/timelion/public/services/tick_formatters.ts rename src/legacy/core_plugins/timelion/public/{vis => }/timelion_request_handler.ts (97%) delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js delete mode 100644 src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js diff --git a/.eslintignore b/.eslintignore index 90155ca9cb681..d23fff0dcc662 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,7 +16,7 @@ bower_components /src/legacy/core_plugins/console/public/webpackShims /src/legacy/core_plugins/console/public/tests/webpackShims /src/legacy/ui/public/utils/decode_geo_hash.js -/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.* +/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.* /src/core/lib/kbn_internal_native_observable /packages/*/target /packages/eslint-config-kibana diff --git a/.i18nrc.json b/.i18nrc.json index 4bc0f773ee8b5..c3a6ea6e63374 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -33,7 +33,7 @@ "statusPage": "src/legacy/core_plugins/status_page", "telemetry": "src/legacy/core_plugins/telemetry", "tileMap": "src/legacy/core_plugins/tile_map", - "timelion": "src/legacy/core_plugins/timelion", + "timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index d989a68d40eeb..891b378e1e616 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -20,12 +20,10 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; const setupPlugins: Readonly = { - visualizations, data: npSetup.plugins.data, expressions: npSetup.plugins.expressions, diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/flot.js b/src/legacy/core_plugins/timelion/public/panels/timechart/flot.js deleted file mode 100644 index d6ca6d96c34ef..0000000000000 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/flot.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -require('jquery.flot'); -require('jquery.flot.time'); -require('jquery.flot.symbol'); -require('jquery.flot.crosshair'); -require('jquery.flot.selection'); -require('jquery.flot.stack'); -require('jquery.flot.axislabels'); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 04b27c4020ce3..bb048d2d027fb 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -17,7 +17,7 @@ * under the License. */ -import './flot'; +import '../../../../vis_type_timelion/public/flot'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment-timezone'; @@ -27,9 +27,9 @@ import observeResize from '../../lib/observe_resize'; // @ts-ignore import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../services/tick_formatters'; -import { xaxisFormatterProvider } from './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; +import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; +import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; +import { generateTicksProvider } from '../../../../vis_type_timelion/public/helpers/tick_generator'; const DEBOUNCE_DELAY = 50; diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts deleted file mode 100644 index f7d696a0316db..0000000000000 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function generateTicksProvider() { - function floorInBase(n: any, base: any) { - return base * Math.floor(n / base); - } - - function generateTicks(axis: any) { - const returnTicks = []; - let tickSize = 2; - let delta = axis.delta; - let steps = 0; - let tickVal; - let tickCount = 0; - - // Count the steps - while (Math.abs(delta) >= 1024) { - steps++; - delta /= 1024; - } - - // Set the tick size relative to the remaining delta - while (tickSize <= 1024) { - if (delta <= tickSize) { - break; - } - tickSize *= 2; - } - axis.tickSize = tickSize * Math.pow(1024, steps); - - // Calculate the new ticks - const tickMin = floorInBase(axis.min, axis.tickSize); - do { - tickVal = tickMin + tickCount++ * axis.tickSize; - returnTicks.push(tickVal); - } while (tickVal < axis.max); - - return returnTicks; - } - - return function(axis: any) { - return generateTicks(axis); - }; -} diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts deleted file mode 100644 index db3408dae33db..0000000000000 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import moment from 'moment'; - -import { i18n } from '@kbn/i18n'; - -export function xaxisFormatterProvider(config: any) { - function getFormat(esInterval: any) { - const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/); - - if (parts == null || parts[1] == null || parts[2] == null) { - throw new Error( - i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { - defaultMessage: 'Unknown interval', - }) - ); - } - - const interval = moment.duration(Number(parts[1]), parts[2]); - - // Cribbed from Kibana's TimeBuckets class - const rules = config.get('dateFormat:scaled'); - - for (let i = rules.length - 1; i >= 0; i--) { - const rule = rules[i]; - if (!rule[0] || interval >= moment.duration(rule[0])) { - return rule[1]; - } - } - - return config.get('dateFormat'); - } - - return function(esInterval: any) { - return getFormat(esInterval); - }; -} diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 631edf8cd2dd0..7c29bd8051a60 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -26,7 +26,6 @@ import { } from 'kibana/public'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; @@ -44,7 +43,6 @@ export interface TimelionVisualizationDependencies extends LegacyDependenciesPlu /** @internal */ export interface TimelionPluginSetupDependencies { expressions: ReturnType; - visualizations: VisualizationsSetup; data: DataPublicPluginSetup; // Temporary solution @@ -61,7 +59,7 @@ export class TimelionPlugin implements Plugin, void> { public async setup( core: CoreSetup, - { __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies + { __LEGACY, expressions, data }: TimelionPluginSetupDependencies ) { const timelionPanels: Map = new Map(); diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts deleted file mode 100644 index 2c78d2423cc06..0000000000000 --- a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -function baseTickFormatter(value: any, axis: any) { - const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - const formatted = '' + Math.round(value * factor) / factor; - - // If tickDecimals was specified, ensure that we have exactly that - // much precision; otherwise default to the value's own precision. - - if (axis.tickDecimals != null) { - const decimal = formatted.indexOf('.'); - const precision = decimal === -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return ( - (precision ? formatted : formatted + '.') + - ('' + factor).substr(1, axis.tickDecimals - precision) - ); - } - } - - return formatted; -} - -function unitFormatter(divisor: any, units: any) { - return (val: any) => { - let index = 0; - const isNegative = val < 0; - val = Math.abs(val); - while (val >= divisor && index < units.length) { - val /= divisor; - index++; - } - const value = (Math.round(val * 100) / 100) * (isNegative ? -1 : 1); - return `${value}${units[index]}`; - }; -} - -export function tickFormatters() { - const formatters = { - bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), - 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), - bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), - 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: any, axis: any) { - return val.toLocaleString('en', { - style: 'currency', - currency: axis.options.units.prefix || 'USD', - }); - }, - percent(val: any, axis: any) { - let precision = - _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); - // toFixed only accepts values between 0 and 20 - if (precision < 0) { - precision = 0; - } else if (precision > 20) { - precision = 20; - } - - return (val * 100).toFixed(precision) + '%'; - }, - custom(val: any, axis: any) { - const formattedVal = baseTickFormatter(val, axis); - const prefix = axis.options.units.prefix; - const suffix = axis.options.units.suffix; - return prefix + formattedVal + suffix; - }, - }; - - return formatters; -} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/timelion_request_handler.ts similarity index 97% rename from src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts rename to src/legacy/core_plugins/timelion/public/timelion_request_handler.ts index 14cd3d0083e6a..647ce9e9c07b5 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/timelion_request_handler.ts @@ -22,8 +22,8 @@ import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; -import { TimelionVisualizationDependencies } from '../plugin'; -import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; +import { TimelionVisualizationDependencies } from './plugin'; +import { TimeRange, esFilters, esQuery, Query } from '../../../../plugins/data/public'; interface Stats { cacheCount: number; diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts index ad7d713517e40..e2b946c0294cd 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts @@ -20,7 +20,7 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; -import { getTimelionRequestHandler } from './vis/timelion_request_handler'; +import { getTimelionRequestHandler } from './timelion_request_handler'; import { TimelionVisualizationDependencies } from './plugin'; const name = 'timelion_vis'; diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js deleted file mode 100644 index cda8038953c76..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js +++ /dev/null @@ -1,462 +0,0 @@ -/* -Axis Labels Plugin for flot. -http://github.com/markrcote/flot-axislabels -Original code is Copyright (c) 2010 Xuan Luo. -Original code was released under the GPLv3 license by Xuan Luo, September 2010. -Original code was rereleased under the MIT license by Xuan Luo, April 2012. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -(function ($) { - var options = { - axisLabels: { - show: true - } - }; - - function canvasSupported() { - return !!document.createElement('canvas').getContext; - } - - function canvasTextSupported() { - if (!canvasSupported()) { - return false; - } - var dummy_canvas = document.createElement('canvas'); - var context = dummy_canvas.getContext('2d'); - return typeof context.fillText == 'function'; - } - - function css3TransitionSupported() { - var div = document.createElement('div'); - return typeof div.style.MozTransition != 'undefined' // Gecko - || typeof div.style.OTransition != 'undefined' // Opera - || typeof div.style.webkitTransition != 'undefined' // WebKit - || typeof div.style.transition != 'undefined'; - } - - - function AxisLabel(axisName, position, padding, plot, opts) { - this.axisName = axisName; - this.position = position; - this.padding = padding; - this.plot = plot; - this.opts = opts; - this.width = 0; - this.height = 0; - } - - AxisLabel.prototype.cleanup = function() { - }; - - - CanvasAxisLabel.prototype = new AxisLabel(); - CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; - function CanvasAxisLabel(axisName, position, padding, plot, opts) { - AxisLabel.prototype.constructor.call(this, axisName, position, padding, - plot, opts); - } - - CanvasAxisLabel.prototype.calculateSize = function() { - if (!this.opts.axisLabelFontSizePixels) - this.opts.axisLabelFontSizePixels = 14; - if (!this.opts.axisLabelFontFamily) - this.opts.axisLabelFontFamily = 'sans-serif'; - - var textWidth = this.opts.axisLabelFontSizePixels + this.padding; - var textHeight = this.opts.axisLabelFontSizePixels + this.padding; - if (this.position == 'left' || this.position == 'right') { - this.width = this.opts.axisLabelFontSizePixels + this.padding; - this.height = 0; - } else { - this.width = 0; - this.height = this.opts.axisLabelFontSizePixels + this.padding; - } - }; - - CanvasAxisLabel.prototype.draw = function(box) { - if (!this.opts.axisLabelColour) - this.opts.axisLabelColour = 'black'; - var ctx = this.plot.getCanvas().getContext('2d'); - ctx.save(); - ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + - this.opts.axisLabelFontFamily; - ctx.fillStyle = this.opts.axisLabelColour; - var width = ctx.measureText(this.opts.axisLabel).width; - var height = this.opts.axisLabelFontSizePixels; - var x, y, angle = 0; - if (this.position == 'top') { - x = box.left + box.width/2 - width/2; - y = box.top + height*0.72; - } else if (this.position == 'bottom') { - x = box.left + box.width/2 - width/2; - y = box.top + box.height - height*0.72; - } else if (this.position == 'left') { - x = box.left + height*0.72; - y = box.height/2 + box.top + width/2; - angle = -Math.PI/2; - } else if (this.position == 'right') { - x = box.left + box.width - height*0.72; - y = box.height/2 + box.top - width/2; - angle = Math.PI/2; - } - ctx.translate(x, y); - ctx.rotate(angle); - ctx.fillText(this.opts.axisLabel, 0, 0); - ctx.restore(); - }; - - - HtmlAxisLabel.prototype = new AxisLabel(); - HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; - function HtmlAxisLabel(axisName, position, padding, plot, opts) { - AxisLabel.prototype.constructor.call(this, axisName, position, - padding, plot, opts); - this.elem = null; - } - - HtmlAxisLabel.prototype.calculateSize = function() { - var elem = $('
' + - this.opts.axisLabel + '
'); - this.plot.getPlaceholder().append(elem); - // store height and width of label itself, for use in draw() - this.labelWidth = elem.outerWidth(true); - this.labelHeight = elem.outerHeight(true); - elem.remove(); - - this.width = this.height = 0; - if (this.position == 'left' || this.position == 'right') { - this.width = this.labelWidth + this.padding; - } else { - this.height = this.labelHeight + this.padding; - } - }; - - HtmlAxisLabel.prototype.cleanup = function() { - if (this.elem) { - this.elem.remove(); - } - }; - - HtmlAxisLabel.prototype.draw = function(box) { - this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); - this.elem = $('
' - + this.opts.axisLabel + '
'); - this.plot.getPlaceholder().append(this.elem); - if (this.position == 'top') { - this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + - 'px'); - this.elem.css('top', box.top + 'px'); - } else if (this.position == 'bottom') { - this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + - 'px'); - this.elem.css('top', box.top + box.height - this.labelHeight + - 'px'); - } else if (this.position == 'left') { - this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + - 'px'); - this.elem.css('left', box.left + 'px'); - } else if (this.position == 'right') { - this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + - 'px'); - this.elem.css('left', box.left + box.width - this.labelWidth + - 'px'); - } - }; - - - CssTransformAxisLabel.prototype = new HtmlAxisLabel(); - CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; - function CssTransformAxisLabel(axisName, position, padding, plot, opts) { - HtmlAxisLabel.prototype.constructor.call(this, axisName, position, - padding, plot, opts); - } - - CssTransformAxisLabel.prototype.calculateSize = function() { - HtmlAxisLabel.prototype.calculateSize.call(this); - this.width = this.height = 0; - if (this.position == 'left' || this.position == 'right') { - this.width = this.labelHeight + this.padding; - } else { - this.height = this.labelHeight + this.padding; - } - }; - - CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { - var stransforms = { - '-moz-transform': '', - '-webkit-transform': '', - '-o-transform': '', - '-ms-transform': '' - }; - if (x != 0 || y != 0) { - var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; - stransforms['-moz-transform'] += stdTranslate; - stransforms['-webkit-transform'] += stdTranslate; - stransforms['-o-transform'] += stdTranslate; - stransforms['-ms-transform'] += stdTranslate; - } - if (degrees != 0) { - var rotation = degrees / 90; - var stdRotate = ' rotate(' + degrees + 'deg)'; - stransforms['-moz-transform'] += stdRotate; - stransforms['-webkit-transform'] += stdRotate; - stransforms['-o-transform'] += stdRotate; - stransforms['-ms-transform'] += stdRotate; - } - var s = 'top: 0; left: 0; '; - for (var prop in stransforms) { - if (stransforms[prop]) { - s += prop + ':' + stransforms[prop] + ';'; - } - } - s += ';'; - return s; - }; - - CssTransformAxisLabel.prototype.calculateOffsets = function(box) { - var offsets = { x: 0, y: 0, degrees: 0 }; - if (this.position == 'bottom') { - offsets.x = box.left + box.width/2 - this.labelWidth/2; - offsets.y = box.top + box.height - this.labelHeight; - } else if (this.position == 'top') { - offsets.x = box.left + box.width/2 - this.labelWidth/2; - offsets.y = box.top; - } else if (this.position == 'left') { - offsets.degrees = -90; - offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; - offsets.y = box.height/2 + box.top; - } else if (this.position == 'right') { - offsets.degrees = 90; - offsets.x = box.left + box.width - this.labelWidth/2 - - this.labelHeight/2; - offsets.y = box.height/2 + box.top; - } - offsets.x = Math.round(offsets.x); - offsets.y = Math.round(offsets.y); - - return offsets; - }; - - CssTransformAxisLabel.prototype.draw = function(box) { - this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); - var offsets = this.calculateOffsets(box); - this.elem = $('
' + this.opts.axisLabel + '
'); - this.plot.getPlaceholder().append(this.elem); - }; - - - IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); - IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; - function IeTransformAxisLabel(axisName, position, padding, plot, opts) { - CssTransformAxisLabel.prototype.constructor.call(this, axisName, - position, padding, - plot, opts); - this.requiresResize = false; - } - - IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { - // I didn't feel like learning the crazy Matrix stuff, so this uses - // a combination of the rotation transform and CSS positioning. - var s = ''; - if (degrees != 0) { - var rotation = degrees/90; - while (rotation < 0) { - rotation += 4; - } - s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; - // see below - this.requiresResize = (this.position == 'right'); - } - if (x != 0) { - s += 'left: ' + x + 'px; '; - } - if (y != 0) { - s += 'top: ' + y + 'px; '; - } - return s; - }; - - IeTransformAxisLabel.prototype.calculateOffsets = function(box) { - var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( - this, box); - // adjust some values to take into account differences between - // CSS and IE rotations. - if (this.position == 'top') { - // FIXME: not sure why, but placing this exactly at the top causes - // the top axis label to flip to the bottom... - offsets.y = box.top + 1; - } else if (this.position == 'left') { - offsets.x = box.left; - offsets.y = box.height/2 + box.top - this.labelWidth/2; - } else if (this.position == 'right') { - offsets.x = box.left + box.width - this.labelHeight; - offsets.y = box.height/2 + box.top - this.labelWidth/2; - } - return offsets; - }; - - IeTransformAxisLabel.prototype.draw = function(box) { - CssTransformAxisLabel.prototype.draw.call(this, box); - if (this.requiresResize) { - this.elem = this.plot.getPlaceholder().find("." + this.axisName + - "Label"); - // Since we used CSS positioning instead of transforms for - // translating the element, and since the positioning is done - // before any rotations, we have to reset the width and height - // in case the browser wrapped the text (specifically for the - // y2axis). - this.elem.css('width', this.labelWidth); - this.elem.css('height', this.labelHeight); - } - }; - - - function init(plot) { - plot.hooks.processOptions.push(function (plot, options) { - - if (!options.axisLabels.show) - return; - - // This is kind of a hack. There are no hooks in Flot between - // the creation and measuring of the ticks (setTicks, measureTickLabels - // in setupGrid() ) and the drawing of the ticks and plot box - // (insertAxisLabels in setupGrid() ). - // - // Therefore, we use a trick where we run the draw routine twice: - // the first time to get the tick measurements, so that we can change - // them, and then have it draw it again. - var secondPass = false; - - var axisLabels = {}; - var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; - - var defaultPadding = 2; // padding between axis and tick labels - plot.hooks.draw.push(function (plot, ctx) { - var hasAxisLabels = false; - if (!secondPass) { - // MEASURE AND SET OPTIONS - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options // Flot 0.7 - || plot.getOptions()[axisName]; // Flot 0.6 - - // Handle redraws initiated outside of this plug-in. - if (axisName in axisLabels) { - axis.labelHeight = axis.labelHeight - - axisLabels[axisName].height; - axis.labelWidth = axis.labelWidth - - axisLabels[axisName].width; - opts.labelHeight = axis.labelHeight; - opts.labelWidth = axis.labelWidth; - axisLabels[axisName].cleanup(); - delete axisLabels[axisName]; - } - - if (!opts || !opts.axisLabel || !axis.show) - return; - - hasAxisLabels = true; - var renderer = null; - - if (!opts.axisLabelUseHtml && - navigator.appName == 'Microsoft Internet Explorer') { - var ua = navigator.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); - if (re.exec(ua) != null) { - rv = parseFloat(RegExp.$1); - } - if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { - renderer = CssTransformAxisLabel; - } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { - renderer = IeTransformAxisLabel; - } else if (opts.axisLabelUseCanvas) { - renderer = CanvasAxisLabel; - } else { - renderer = HtmlAxisLabel; - } - } else { - if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { - renderer = HtmlAxisLabel; - } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { - renderer = CanvasAxisLabel; - } else { - renderer = CssTransformAxisLabel; - } - } - - var padding = opts.axisLabelPadding === undefined ? - defaultPadding : opts.axisLabelPadding; - - axisLabels[axisName] = new renderer(axisName, - axis.position, padding, - plot, opts); - - // flot interprets axis.labelHeight and .labelWidth as - // the height and width of the tick labels. We increase - // these values to make room for the axis label and - // padding. - - axisLabels[axisName].calculateSize(); - - // AxisLabel.height and .width are the size of the - // axis label and padding. - // Just set opts here because axis will be sorted out on - // the redraw. - - opts.labelHeight = axis.labelHeight + - axisLabels[axisName].height; - opts.labelWidth = axis.labelWidth + - axisLabels[axisName].width; - }); - - // If there are axis labels, re-draw with new label widths and - // heights. - - if (hasAxisLabels) { - secondPass = true; - plot.setupGrid(); - plot.draw(); - } - } else { - secondPass = false; - // DRAW - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options // Flot 0.7 - || plot.getOptions()[axisName]; // Flot 0.6 - if (!opts || !opts.axisLabel || !axis.show) - return; - - axisLabels[axisName].draw(axis.box); - }); - } - }); - }); - } - - - $.plot.plugins.push({ - init: init, - options: options, - name: 'axisLabels', - version: '2.0' - }); -})(jQuery); diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js deleted file mode 100644 index 5111695e3d12c..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js +++ /dev/null @@ -1,176 +0,0 @@ -/* Flot plugin for showing crosshairs when the mouse hovers over the plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - - crosshair: { - mode: null or "x" or "y" or "xy" - color: color - lineWidth: number - } - -Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical -crosshair that lets you trace the values on the x axis, "y" enables a -horizontal crosshair and "xy" enables them both. "color" is the color of the -crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of -the drawn lines (default is 1). - -The plugin also adds four public methods: - - - setCrosshair( pos ) - - Set the position of the crosshair. Note that this is cleared if the user - moves the mouse. "pos" is in coordinates of the plot and should be on the - form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple - axes), which is coincidentally the same format as what you get from a - "plothover" event. If "pos" is null, the crosshair is cleared. - - - clearCrosshair() - - Clear the crosshair. - - - lockCrosshair(pos) - - Cause the crosshair to lock to the current location, no longer updating if - the user moves the mouse. Optionally supply a position (passed on to - setCrosshair()) to move it to. - - Example usage: - - var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; - $("#graph").bind( "plothover", function ( evt, position, item ) { - if ( item ) { - // Lock the crosshair to the data point being hovered - myFlot.lockCrosshair({ - x: item.datapoint[ 0 ], - y: item.datapoint[ 1 ] - }); - } else { - // Return normal crosshair operation - myFlot.unlockCrosshair(); - } - }); - - - unlockCrosshair() - - Free the crosshair to move again after locking it. -*/ - -(function ($) { - var options = { - crosshair: { - mode: null, // one of null, "x", "y" or "xy", - color: "rgba(170, 0, 0, 0.80)", - lineWidth: 1 - } - }; - - function init(plot) { - // position of crosshair in pixels - var crosshair = { x: -1, y: -1, locked: false }; - - plot.setCrosshair = function setCrosshair(pos) { - if (!pos) - crosshair.x = -1; - else { - var o = plot.p2c(pos); - crosshair.x = Math.max(0, Math.min(o.left, plot.width())); - crosshair.y = Math.max(0, Math.min(o.top, plot.height())); - } - - plot.triggerRedrawOverlay(); - }; - - plot.clearCrosshair = plot.setCrosshair; // passes null for pos - - plot.lockCrosshair = function lockCrosshair(pos) { - if (pos) - plot.setCrosshair(pos); - crosshair.locked = true; - }; - - plot.unlockCrosshair = function unlockCrosshair() { - crosshair.locked = false; - }; - - function onMouseOut(e) { - if (crosshair.locked) - return; - - if (crosshair.x != -1) { - crosshair.x = -1; - plot.triggerRedrawOverlay(); - } - } - - function onMouseMove(e) { - if (crosshair.locked) - return; - - if (plot.getSelection && plot.getSelection()) { - crosshair.x = -1; // hide the crosshair while selecting - return; - } - - var offset = plot.offset(); - crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); - crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); - plot.triggerRedrawOverlay(); - } - - plot.hooks.bindEvents.push(function (plot, eventHolder) { - if (!plot.getOptions().crosshair.mode) - return; - - eventHolder.mouseout(onMouseOut); - eventHolder.mousemove(onMouseMove); - }); - - plot.hooks.drawOverlay.push(function (plot, ctx) { - var c = plot.getOptions().crosshair; - if (!c.mode) - return; - - var plotOffset = plot.getPlotOffset(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - if (crosshair.x != -1) { - var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; - - ctx.strokeStyle = c.color; - ctx.lineWidth = c.lineWidth; - ctx.lineJoin = "round"; - - ctx.beginPath(); - if (c.mode.indexOf("x") != -1) { - var drawX = Math.floor(crosshair.x) + adj; - ctx.moveTo(drawX, 0); - ctx.lineTo(drawX, plot.height()); - } - if (c.mode.indexOf("y") != -1) { - var drawY = Math.floor(crosshair.y) + adj; - ctx.moveTo(0, drawY); - ctx.lineTo(plot.width(), drawY); - } - ctx.stroke(); - } - ctx.restore(); - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mouseout", onMouseOut); - eventHolder.unbind("mousemove", onMouseMove); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'crosshair', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js deleted file mode 100644 index 5d613037cf234..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js +++ /dev/null @@ -1,3168 +0,0 @@ -/* JavaScript plotting library for jQuery, version 0.8.3. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -*/ - -// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience - -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ -(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); - -// the actual Flot code -(function($) { - - // Cache the prototype hasOwnProperty for faster access - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM - // operation produces the same effect as detach, i.e. removing the element - // without touching its jQuery data. - - // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+. - - if (!$.fn.detach) { - $.fn.detach = function() { - return this.each(function() { - if (this.parentNode) { - this.parentNode.removeChild( this ); - } - }); - }; - } - - /////////////////////////////////////////////////////////////////////////// - // The Canvas object is a wrapper around an HTML5 tag. - // - // @constructor - // @param {string} cls List of classes to apply to the canvas. - // @param {element} container Element onto which to append the canvas. - // - // Requiring a container is a little iffy, but unfortunately canvas - // operations don't work unless the canvas is attached to the DOM. - - function Canvas(cls, container) { - - var element = container.children("." + cls)[0]; - - if (element == null) { - - element = document.createElement("canvas"); - element.className = cls; - - $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) - .appendTo(container); - - // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas - - if (!element.getContext) { - if (window.G_vmlCanvasManager) { - element = window.G_vmlCanvasManager.initElement(element); - } else { - throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); - } - } - } - - this.element = element; - - var context = this.context = element.getContext("2d"); - - // Determine the screen's ratio of physical to device-independent - // pixels. This is the ratio between the canvas width that the browser - // advertises and the number of pixels actually present in that space. - - // The iPhone 4, for example, has a device-independent width of 320px, - // but its screen is actually 640px wide. It therefore has a pixel - // ratio of 2, while most normal devices have a ratio of 1. - - var devicePixelRatio = window.devicePixelRatio || 1, - backingStoreRatio = - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || 1; - - this.pixelRatio = devicePixelRatio / backingStoreRatio; - - // Size the canvas to match the internal dimensions of its container - - this.resize(container.width(), container.height()); - - // Collection of HTML div layers for text overlaid onto the canvas - - this.textContainer = null; - this.text = {}; - - // Cache of text fragments and metrics, so we can avoid expensively - // re-calculating them when the plot is re-rendered in a loop. - - this._textCache = {}; - } - - // Resizes the canvas to the given dimensions. - // - // @param {number} width New width of the canvas, in pixels. - // @param {number} width New height of the canvas, in pixels. - - Canvas.prototype.resize = function(width, height) { - - if (width <= 0 || height <= 0) { - throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); - } - - var element = this.element, - context = this.context, - pixelRatio = this.pixelRatio; - - // Resize the canvas, increasing its density based on the display's - // pixel ratio; basically giving it more pixels without increasing the - // size of its element, to take advantage of the fact that retina - // displays have that many more pixels in the same advertised space. - - // Resizing should reset the state (excanvas seems to be buggy though) - - if (this.width != width) { - element.width = width * pixelRatio; - element.style.width = width + "px"; - this.width = width; - } - - if (this.height != height) { - element.height = height * pixelRatio; - element.style.height = height + "px"; - this.height = height; - } - - // Save the context, so we can reset in case we get replotted. The - // restore ensure that we're really back at the initial state, and - // should be safe even if we haven't saved the initial state yet. - - context.restore(); - context.save(); - - // Scale the coordinate space to match the display density; so even though we - // may have twice as many pixels, we still want lines and other drawing to - // appear at the same size; the extra pixels will just make them crisper. - - context.scale(pixelRatio, pixelRatio); - }; - - // Clears the entire canvas area, not including any overlaid HTML text - - Canvas.prototype.clear = function() { - this.context.clearRect(0, 0, this.width, this.height); - }; - - // Finishes rendering the canvas, including managing the text overlay. - - Canvas.prototype.render = function() { - - var cache = this._textCache; - - // For each text layer, add elements marked as active that haven't - // already been rendered, and remove those that are no longer active. - - for (var layerKey in cache) { - if (hasOwnProperty.call(cache, layerKey)) { - - var layer = this.getTextLayer(layerKey), - layerCache = cache[layerKey]; - - layer.hide(); - - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length == 0) { - delete styleCache[key]; - } - } - } - } - } - - layer.show(); - } - } - }; - - // Creates (if necessary) and returns the text overlay container. - // - // @param {string} classes String of space-separated CSS classes used to - // uniquely identify the text layer. - // @return {object} The jQuery-wrapped text-layer div. - - Canvas.prototype.getTextLayer = function(classes) { - - var layer = this.text[classes]; - - // Create the text layer if it doesn't exist - - if (layer == null) { - - // Create the text layer container, if it doesn't exist - - if (this.textContainer == null) { - this.textContainer = $("
") - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0, - 'font-size': "smaller", - color: "#545454" - }) - .insertAfter(this.element); - } - - layer = this.text[classes] = $("
") - .addClass(classes) - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0 - }) - .appendTo(this.textContainer); - } - - return layer; - }; - - // Creates (if necessary) and returns a text info object. - // - // The object looks like this: - // - // { - // width: Width of the text's wrapper div. - // height: Height of the text's wrapper div. - // element: The jQuery-wrapped HTML div containing the text. - // positions: Array of positions at which this text is drawn. - // } - // - // The positions array contains objects that look like this: - // - // { - // active: Flag indicating whether the text should be visible. - // rendered: Flag indicating whether the text is currently visible. - // element: The jQuery-wrapped HTML div containing the text. - // x: X coordinate at which to draw the text. - // y: Y coordinate at which to draw the text. - // } - // - // Each position after the first receives a clone of the original element. - // - // The idea is that that the width, height, and general 'identity' of the - // text is constant no matter where it is placed; the placements are a - // secondary property. - // - // Canvas maintains a cache of recently-used text info objects; getTextInfo - // either returns the cached element or creates a new entry. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {string} text Text string to retrieve info for. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @return {object} a text info object. - - Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such - - text = "" + text; - - // If the font is a font-spec object, generate a CSS font definition - - if (typeof font === "object") { - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; - } else { - textStyle = font; - } - - // Retrieve (or create) the cache for the text's layer and styles - - layerCache = this._textCache[layer]; - - if (layerCache == null) { - layerCache = this._textCache[layer] = {}; - } - - styleCache = layerCache[textStyle]; - - if (styleCache == null) { - styleCache = layerCache[textStyle] = {}; - } - - info = styleCache[text]; - - // If we can't find a matching element in our cache, create a new one - - if (info == null) { - - var element = $("
").html(text) - .css({ - position: "absolute", - 'max-width': width, - top: -9999 - }) - .appendTo(this.getTextLayer(layer)); - - if (typeof font === "object") { - element.css({ - font: textStyle, - color: font.color - }); - } else if (typeof font === "string") { - element.addClass(font); - } - - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), - element: element, - positions: [] - }; - - element.detach(); - } - - return info; - }; - - // Adds a text string to the canvas text overlay. - // - // The text isn't drawn immediately; it is marked as rendering, which will - // result in its addition to the canvas on the next render pass. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number} x X coordinate at which to draw the text. - // @param {number} y Y coordinate at which to draw the text. - // @param {string} text Text string to draw. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @param {string=} halign Horizontal alignment of the text; either "left", - // "center" or "right". - // @param {string=} valign Vertical alignment of the text; either "top", - // "middle" or "bottom". - - Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - - var info = this.getTextInfo(layer, text, font, angle, width), - positions = info.positions; - - // Tweak the div's position to match the text's alignment - - if (halign == "center") { - x -= info.width / 2; - } else if (halign == "right") { - x -= info.width; - } - - if (valign == "middle") { - y -= info.height / 2; - } else if (valign == "bottom") { - y -= info.height; - } - - // Determine whether this text already exists at this position. - // If so, mark it for inclusion in the next render pass. - - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = true; - return; - } - } - - // If the text doesn't exist at this position, create a new entry - - // For the very first position we'll re-use the original element, - // while for subsequent ones we'll clone it. - - position = { - active: true, - rendered: false, - element: positions.length ? info.element.clone() : info.element, - x: x, - y: y - }; - - positions.push(position); - - // Move the element to its final position within the container - - position.element.css({ - top: Math.round(y), - left: Math.round(x), - 'text-align': halign // In case the text wraps - }); - }; - - // Removes one or more text strings from the canvas text overlay. - // - // If no parameters are given, all text within the layer is removed. - // - // Note that the text is not immediately removed; it is simply marked as - // inactive, which will result in its removal on the next render pass. - // This avoids the performance penalty for 'clear and redraw' behavior, - // where we potentially get rid of all text on a layer, but will likely - // add back most or all of it later, as when redrawing axes, for example. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number=} x X coordinate of the text. - // @param {number=} y Y coordinate of the text. - // @param {string=} text Text string to remove. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which the text is rotated, in degrees. - // Angle is currently unused, it will be implemented in the future. - - Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { - if (text == null) { - var layerCache = this._textCache[layer]; - if (layerCache != null) { - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - var positions = styleCache[key].positions; - for (var i = 0, position; position = positions[i]; i++) { - position.active = false; - } - } - } - } - } - } - } else { - var positions = this.getTextInfo(layer, text, font, angle).positions; - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = false; - } - } - } - }; - - /////////////////////////////////////////////////////////////////////////// - // The top-level container for the entire plot. - - function Plot(placeholder, data_, options_, plugins) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of columns in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85, // set to 0 to avoid background - sorted: null // default to no legend sorting - }, - xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis - inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - reserveSpace: null, // whether to reserve space even if axis isn't shown - tickLength: null, // size in pixels of ticks, or "full" for whole line - alignTicksWithAxis: null, // axis number or null for no sync - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null // number or [number, "unit"] - }, - yaxis: { - autoscaleMargin: 0.02, - position: "left" // or "right" - }, - xaxes: [], - yaxes: [], - series: { - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff", - symbol: "circle" // or callback - }, - lines: { - // we don't put in show: false so we can see - // whether lines were actively disabled - lineWidth: 2, // in pixels - fill: false, - fillColor: null, - steps: false - // Omit 'zero', so we can later default its value to - // match that of the 'fill' option. - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left", // "left", "right", or "center" - horizontal: false, - zero: true - }, - shadowSize: 3, - highlightColor: null - }, - grid: { - show: true, - aboveData: false, - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - borderColor: null, // set if different from the grid color - tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" - margin: 0, // distance from the canvas edge to the grid - labelMargin: 5, // in pixels - axisMargin: 8, // in pixels - borderWidth: 2, // in pixels - minBorderMargin: null, // in pixels, null means taken from points radius - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - interaction: { - redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow - }, - hooks: {} - }, - surface = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - xaxes = [], yaxes = [], - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - plotWidth = 0, plotHeight = 0, - hooks = { - processOptions: [], - processRawData: [], - processDatapoints: [], - processOffset: [], - drawBackground: [], - drawSeries: [], - draw: [], - bindEvents: [], - drawOverlay: [], - shutdown: [] - }, - plot = this; - - // public functions - plot.setData = setData; - plot.setupGrid = setupGrid; - plot.draw = draw; - plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return surface.element; }; - plot.getPlotOffset = function() { return plotOffset; }; - plot.width = function () { return plotWidth; }; - plot.height = function () { return plotHeight; }; - plot.offset = function () { - var o = eventHolder.offset(); - o.left += plotOffset.left; - o.top += plotOffset.top; - return o; - }; - plot.getData = function () { return series; }; - plot.getAxes = function () { - var res = {}, i; - $.each(xaxes.concat(yaxes), function (_, axis) { - if (axis) - res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; - }); - return res; - }; - plot.getXAxes = function () { return xaxes; }; - plot.getYAxes = function () { return yaxes; }; - plot.c2p = canvasToAxisCoords; - plot.p2c = axisToCanvasCoords; - plot.getOptions = function () { return options; }; - plot.highlight = highlight; - plot.unhighlight = unhighlight; - plot.triggerRedrawOverlay = triggerRedrawOverlay; - plot.pointOffset = function(point) { - return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) - }; - }; - plot.shutdown = shutdown; - plot.destroy = function () { - shutdown(); - placeholder.removeData("plot").empty(); - - series = []; - options = null; - surface = null; - overlay = null; - eventHolder = null; - ctx = null; - octx = null; - xaxes = []; - yaxes = []; - hooks = null; - highlights = []; - plot = null; - }; - plot.resize = function () { - var width = placeholder.width(), - height = placeholder.height(); - surface.resize(width, height); - overlay.resize(width, height); - }; - - // public attributes - plot.hooks = hooks; - - // initialize - initPlugins(plot); - parseOptions(options_); - setupCanvases(); - setData(data_); - setupGrid(); - draw(); - bindEvents(); - - - function executeHooks(hook, args) { - args = [plot].concat(args); - for (var i = 0; i < hook.length; ++i) - hook[i].apply(this, args); - } - - function initPlugins() { - - // References to key classes, allowing plugins to modify them - - var classes = { - Canvas: Canvas - }; - - for (var i = 0; i < plugins.length; ++i) { - var p = plugins[i]; - p.init(plot, classes); - if (p.options) - $.extend(true, options, p.options); - } - } - - function parseOptions(opts) { - - $.extend(true, options, opts); - - // $.extend merges arrays, rather than replacing them. When less - // colors are provided than the size of the default palette, we - // end up with those colors plus the remaining defaults, which is - // not expected behavior; avoid it by replacing them here. - - if (opts && opts.colors) { - options.colors = opts.colors; - } - - if (options.xaxis.color == null) - options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - if (options.yaxis.color == null) - options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility - options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; - if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility - options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; - - if (options.grid.borderColor == null) - options.grid.borderColor = options.grid.color; - if (options.grid.tickColor == null) - options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // Fill in defaults for axis options, including any unspecified - // font-spec fields, if a font-spec was provided. - - // If no x/y axis options were provided, create one of each anyway, - // since the rest of the code assumes that they exist. - - var i, axisOptions, axisCount, - fontSize = placeholder.css("font-size"), - fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, - fontDefaults = { - style: placeholder.css("font-style"), - size: Math.round(0.8 * fontSizeDefault), - variant: placeholder.css("font-variant"), - weight: placeholder.css("font-weight"), - family: placeholder.css("font-family") - }; - - axisCount = options.xaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.xaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.xaxis, axisOptions); - options.xaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - axisCount = options.yaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.yaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.yaxis, axisOptions); - options.yaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.x2axis) { - options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); - options.xaxes[1].position = "top"; - // Override the inherit to allow the axis to auto-scale - if (options.x2axis.min == null) { - options.xaxes[1].min = null; - } - if (options.x2axis.max == null) { - options.xaxes[1].max = null; - } - } - if (options.y2axis) { - options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); - options.yaxes[1].position = "right"; - // Override the inherit to allow the axis to auto-scale - if (options.y2axis.min == null) { - options.yaxes[1].min = null; - } - if (options.y2axis.max == null) { - options.yaxes[1].max = null; - } - } - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - if (options.lines) - $.extend(true, options.series.lines, options.lines); - if (options.points) - $.extend(true, options.series.points, options.points); - if (options.bars) - $.extend(true, options.series.bars, options.bars); - if (options.shadowSize != null) - options.series.shadowSize = options.shadowSize; - if (options.highlightColor != null) - options.series.highlightColor = options.highlightColor; - - // save options on axes for future reference - for (i = 0; i < options.xaxes.length; ++i) - getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; - for (i = 0; i < options.yaxes.length; ++i) - getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; - - // add hooks from options - for (var n in hooks) - if (options.hooks[n] && options.hooks[n].length) - hooks[n] = hooks[n].concat(options.hooks[n]); - - executeHooks(hooks.processOptions, [options]); - } - - function setData(d) { - series = parseData(d); - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s = $.extend(true, {}, options.series); - - if (d[i].data != null) { - s.data = d[i].data; // move the data instead of deep-copy - delete d[i].data; - - $.extend(true, s, d[i]); - - d[i].data = s.data; - } - else - s.data = d[i]; - res.push(s); - } - - return res; - } - - function axisNumber(obj, coord) { - var a = obj[coord + "axis"]; - if (typeof a == "object") // if we got a real axis, extract number - a = a.n; - if (typeof a != "number") - a = 1; // default to first axis - return a; - } - - function allAxes() { - // return flat array without annoying null entries - return $.grep(xaxes.concat(yaxes), function (a) { return a; }); - } - - function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes - var res = {}, i, axis; - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) - res["x" + axis.n] = axis.c2p(pos.left); - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) - res["y" + axis.n] = axis.c2p(pos.top); - } - - if (res.x1 !== undefined) - res.x = res.x1; - if (res.y1 !== undefined) - res.y = res.y1; - - return res; - } - - function axisToCanvasCoords(pos) { - // get canvas coords from the first pair of x/y found in pos - var res = {}, i, axis, key; - - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) { - key = "x" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "x"; - - if (pos[key] != null) { - res.left = axis.p2c(pos[key]); - break; - } - } - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) { - key = "y" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "y"; - - if (pos[key] != null) { - res.top = axis.p2c(pos[key]); - break; - } - } - } - - return res; - } - - function getOrCreateAxis(axes, number) { - if (!axes[number - 1]) - axes[number - 1] = { - n: number, // save the number for future reference - direction: axes == xaxes ? "x" : "y", - options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) - }; - - return axes[number - 1]; - } - - function fillInSeriesOptions() { - - var neededColors = series.length, maxIndex = -1, i; - - // Subtract the number of series that already have fixed colors or - // color indexes from the number that we still need to generate. - - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - neededColors--; - if (typeof sc == "number" && sc > maxIndex) { - maxIndex = sc; - } - } - } - - // If any of the series have fixed color indexes, then we need to - // generate at least as many colors as the highest index. - - if (neededColors <= maxIndex) { - neededColors = maxIndex + 1; - } - - // Generate all the colors, using first the option colors and then - // variations on those colors once they're exhausted. - - var c, colors = [], colorPool = options.colors, - colorPoolSize = colorPool.length, variation = 0; - - for (i = 0; i < neededColors; i++) { - - c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); - - // Each time we exhaust the colors in the pool we adjust - // a scaling factor used to produce more variations on - // those colors. The factor alternates negative/positive - // to produce lighter/darker colors. - - // Reset the variation after every few cycles, or else - // it will end up producing only white or black colors. - - if (i % colorPoolSize == 0 && i) { - if (variation >= 0) { - if (variation < 0.5) { - variation = -variation - 0.2; - } else variation = 0; - } else variation = -variation; - } - - colors[i] = c.scale('rgb', 1 + variation); - } - - // Finalize the series options, filling in their colors - - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // turn on lines automatically in case nothing is set - if (s.lines.show == null) { - var v, show = true; - for (v in s) - if (s[v] && s[v].show) { - show = false; - break; - } - if (show) - s.lines.show = true; - } - - // If nothing was provided for lines.zero, default it to match - // lines.fill, since areas by default should extend to zero. - - if (s.lines.zero == null) { - s.lines.zero = !!s.lines.fill; - } - - // setup axes - s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); - s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - fakeInfinity = Number.MAX_VALUE, - i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p, - data, format; - - function updateAxis(axis, min, max) { - if (min < axis.datamin && min != -fakeInfinity) - axis.datamin = min; - if (max > axis.datamax && max != fakeInfinity) - axis.datamax = max; - } - - $.each(allAxes(), function (_, axis) { - // init axis - axis.datamin = topSentry; - axis.datamax = bottomSentry; - axis.used = false; - }); - - for (i = 0; i < series.length; ++i) { - s = series[i]; - s.datapoints = { points: [] }; - - executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); - } - - // first pass: clean and copy data - for (i = 0; i < series.length; ++i) { - s = series[i]; - - data = s.data; - format = s.datapoints.format; - - if (!format) { - format = []; - // find out how to copy - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); - format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - s.datapoints.format = format; - } - - if (s.datapoints.pointsize != null) - continue; // already filled in - - s.datapoints.pointsize = format.length; - - ps = s.datapoints.pointsize; - points = s.datapoints.points; - - var insertSteps = s.lines.show && s.lines.steps; - s.xaxis.used = s.yaxis.used = true; - - for (j = k = 0; j < data.length; ++j, k += ps) { - p = data[j]; - - var nullify = p == null; - if (!nullify) { - for (m = 0; m < ps; ++m) { - val = p[m]; - f = format[m]; - - if (f) { - if (f.number && val != null) { - val = +val; // convert to number - if (isNaN(val)) - val = null; - else if (val == Infinity) - val = fakeInfinity; - else if (val == -Infinity) - val = -fakeInfinity; - } - - if (val == null) { - if (f.required) - nullify = true; - - if (f.defaultValue != null) - val = f.defaultValue; - } - } - - points[k + m] = val; - } - } - - if (nullify) { - for (m = 0; m < ps; ++m) { - val = points[k + m]; - if (val != null) { - f = format[m]; - // extract min/max info - if (f.autoscale !== false) { - if (f.x) { - updateAxis(s.xaxis, val, val); - } - if (f.y) { - updateAxis(s.yaxis, val, val); - } - } - } - points[k + m] = null; - } - } - else { - // a little bit of line specific stuff that - // perhaps shouldn't be here, but lacking - // better means... - if (insertSteps && k > 0 - && points[k - ps] != null - && points[k - ps] != points[k] - && points[k - ps + 1] != points[k + 1]) { - // copy the point to make room for a middle point - for (m = 0; m < ps; ++m) - points[k + ps + m] = points[k + m]; - - // middle point has same y - points[k + 1] = points[k - ps + 1]; - - // we've added a point, better reflect that - k += ps; - } - } - } - } - - // give the hooks a chance to run - for (i = 0; i < series.length; ++i) { - s = series[i]; - - executeHooks(hooks.processDatapoints, [ s, s.datapoints]); - } - - // second pass: find datamax/datamin for auto-scaling - for (i = 0; i < series.length; ++i) { - s = series[i]; - points = s.datapoints.points; - ps = s.datapoints.pointsize; - format = s.datapoints.format; - - var xmin = topSentry, ymin = topSentry, - xmax = bottomSentry, ymax = bottomSentry; - - for (j = 0; j < points.length; j += ps) { - if (points[j] == null) - continue; - - for (m = 0; m < ps; ++m) { - val = points[j + m]; - f = format[m]; - if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) - continue; - - if (f.x) { - if (val < xmin) - xmin = val; - if (val > xmax) - xmax = val; - } - if (f.y) { - if (val < ymin) - ymin = val; - if (val > ymax) - ymax = val; - } - } - } - - if (s.bars.show) { - // make sure we got room for the bar on the dancing floor - var delta; - - switch (s.bars.align) { - case "left": - delta = 0; - break; - case "right": - delta = -s.bars.barWidth; - break; - default: - delta = -s.bars.barWidth / 2; - } - - if (s.bars.horizontal) { - ymin += delta; - ymax += delta + s.bars.barWidth; - } - else { - xmin += delta; - xmax += delta + s.bars.barWidth; - } - } - - updateAxis(s.xaxis, xmin, xmax); - updateAxis(s.yaxis, ymin, ymax); - } - - $.each(allAxes(), function (_, axis) { - if (axis.datamin == topSentry) - axis.datamin = null; - if (axis.datamax == bottomSentry) - axis.datamax = null; - }); - } - - function setupCanvases() { - - // Make sure the placeholder is clear of everything except canvases - // from a previous plot in this container that we'll try to re-use. - - placeholder.css("padding", 0) // padding messes up the positioning - .children().filter(function(){ - return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); - }).remove(); - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - surface = new Canvas("flot-base", placeholder); - overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features - - ctx = surface.context; - octx = overlay.context; - - // define which element we're listening for events on - eventHolder = $(overlay.element).unbind(); - - // If we're re-using a plot object, shut down the old one - - var existing = placeholder.data("plot"); - - if (existing) { - existing.shutdown(); - overlay.clear(); - } - - // save in case we get replotted - placeholder.data("plot", plot); - } - - function bindEvents() { - // bind events - if (options.grid.hoverable) { - eventHolder.mousemove(onMouseMove); - - // Use bind, rather than .mouseleave, because we officially - // still support jQuery 1.2.6, which doesn't define a shortcut - // for mouseenter or mouseleave. This was a bug/oversight that - // was fixed somewhere around 1.3.x. We can return to using - // .mouseleave when we drop support for 1.2.6. - - eventHolder.bind("mouseleave", onMouseLeave); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - - executeHooks(hooks.bindEvents, [eventHolder]); - } - - function shutdown() { - if (redrawTimeout) - clearTimeout(redrawTimeout); - - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mouseleave", onMouseLeave); - eventHolder.unbind("click", onClick); - - executeHooks(hooks.shutdown, [eventHolder]); - } - - function setTransformationHelpers(axis) { - // set helper functions on the axis, assumes plot area - // has been computed already - - function identity(x) { return x; } - - var s, m, t = axis.options.transform || identity, - it = axis.options.inverseTransform; - - // precompute how much the axis is scaling a point - // in canvas space - if (axis.direction == "x") { - s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); - m = Math.min(t(axis.max), t(axis.min)); - } - else { - s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); - s = -s; - m = Math.max(t(axis.max), t(axis.min)); - } - - // data point to canvas coordinate - if (t == identity) // slight optimization - axis.p2c = function (p) { return (p - m) * s; }; - else - axis.p2c = function (p) { return (t(p) - m) * s; }; - // canvas coordinate to data point - if (!it) - axis.c2p = function (c) { return m + c / s; }; - else - axis.c2p = function (c) { return it(m + c / s); }; - } - - function measureTickLabels(axis) { - - var opts = axis.options, - ticks = axis.ticks || [], - labelWidth = opts.labelWidth || 0, - labelHeight = opts.labelHeight || 0, - maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = opts.font || "flot-tick-label tickLabel"; - - for (var i = 0; i < ticks.length; ++i) { - - var t = ticks[i]; - - if (!t.label) - continue; - - var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - - labelWidth = Math.max(labelWidth, info.width); - labelHeight = Math.max(labelHeight, info.height); - } - - axis.labelWidth = opts.labelWidth || labelWidth; - axis.labelHeight = opts.labelHeight || labelHeight; - } - - function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset; this first phase only looks at one - // dimension per axis, the other dimension depends on the - // other axes so will have to wait - - var lw = axis.labelWidth, - lh = axis.labelHeight, - pos = axis.options.position, - isXAxis = axis.direction === "x", - tickLength = axis.options.tickLength, - axisMargin = options.grid.axisMargin, - padding = options.grid.labelMargin, - innermost = true, - outermost = true, - first = true, - found = false; - - // Determine the axis's position in its direction and on its side - - $.each(isXAxis ? xaxes : yaxes, function(i, a) { - if (a && (a.show || a.reserveSpace)) { - if (a === axis) { - found = true; - } else if (a.options.position === pos) { - if (found) { - outermost = false; - } else { - innermost = false; - } - } - if (!found) { - first = false; - } - } - }); - - // The outermost axis on each side has no margin - - if (outermost) { - axisMargin = 0; - } - - // The ticks for the first axis in each direction stretch across - - if (tickLength == null) { - tickLength = first ? "full" : 5; - } - - if (!isNaN(+tickLength)) - padding += +tickLength; - - if (isXAxis) { - lh += padding; - - if (pos == "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; - } - else { - axis.box = { top: plotOffset.top + axisMargin, height: lh }; - plotOffset.top += lh + axisMargin; - } - } - else { - lw += padding; - - if (pos == "left") { - axis.box = { left: plotOffset.left + axisMargin, width: lw }; - plotOffset.left += lw + axisMargin; - } - else { - plotOffset.right += lw + axisMargin; - axis.box = { left: surface.width - plotOffset.right, width: lw }; - } - } - - // save for future reference - axis.position = pos; - axis.tickLength = tickLength; - axis.box.padding = padding; - axis.innermost = innermost; - } - - function allocateAxisBoxSecondPhase(axis) { - // now that all axis boxes have been placed in one - // dimension, we can set the remaining dimension coordinates - if (axis.direction == "x") { - axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; - } - else { - axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; - } - } - - function adjustLayoutForThingsStickingOut() { - // possibly adjust plot offset to ensure everything stays - // inside the canvas and isn't clipped off - - var minMargin = options.grid.minBorderMargin, - axis, i; - - // check stuff from the plot (FIXME: this should just read - // a value from the series, otherwise it's impossible to - // customize) - if (minMargin == null) { - minMargin = 0; - for (i = 0; i < series.length; ++i) - minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); - } - - var margins = { - left: minMargin, - right: minMargin, - top: minMargin, - bottom: minMargin - }; - - // check axis labels, note we don't check the actual - // labels but instead use the overall width/height to not - // jump as much around with replots - $.each(allAxes(), function (_, axis) { - if (axis.reserveSpace && axis.ticks && axis.ticks.length) { - if (axis.direction === "x") { - margins.left = Math.max(margins.left, axis.labelWidth / 2); - margins.right = Math.max(margins.right, axis.labelWidth / 2); - } else { - margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); - margins.top = Math.max(margins.top, axis.labelHeight / 2); - } - } - }); - - plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); - plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); - plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); - plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); - } - - function setupGrid() { - var i, axes = allAxes(), showGrid = options.grid.show; - - // Initialize the plot's offset from the edge of the canvas - - for (var a in plotOffset) { - var margin = options.grid.margin || 0; - plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; - } - - executeHooks(hooks.processOffset, [plotOffset]); - - // If the grid is visible, add its border width to the offset - - for (var a in plotOffset) { - if(typeof(options.grid.borderWidth) == "object") { - plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; - } - else { - plotOffset[a] += showGrid ? options.grid.borderWidth : 0; - } - } - - $.each(axes, function (_, axis) { - var axisOpts = axis.options; - axis.show = axisOpts.show == null ? axis.used : axisOpts.show; - axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; - setRange(axis); - }); - - if (showGrid) { - - var allocatedAxes = $.grep(axes, function (axis) { - return axis.show || axis.reserveSpace; - }); - - $.each(allocatedAxes, function (_, axis) { - // make the ticks - setupTickGeneration(axis); - setTicks(axis); - snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis - measureTickLabels(axis); - }); - - // with all dimensions calculated, we can compute the - // axis bounding boxes, start from the outside - // (reverse order) - for (i = allocatedAxes.length - 1; i >= 0; --i) - allocateAxisBoxFirstPhase(allocatedAxes[i]); - - // make sure we've got enough space for things that - // might stick out - adjustLayoutForThingsStickingOut(); - - $.each(allocatedAxes, function (_, axis) { - allocateAxisBoxSecondPhase(axis); - }); - } - - plotWidth = surface.width - plotOffset.left - plotOffset.right; - plotHeight = surface.height - plotOffset.bottom - plotOffset.top; - - // now we got the proper plot dimensions, we can compute the scaling - $.each(axes, function (_, axis) { - setTransformationHelpers(axis); - }); - - if (showGrid) { - drawAxisLabels(); - } - - insertLegend(); - } - - function setRange(axis) { - var opts = axis.options, - min = +(opts.min != null ? opts.min : axis.datamin), - max = +(opts.max != null ? opts.max : axis.datamax), - delta = max - min; - - if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; - - if (opts.min == null) - min -= widen; - // always widen max if we couldn't widen min to ensure we - // don't fall into min == max which doesn't work - if (opts.max == null || opts.min != null) - max += widen; - } - else { - // consider autoscaling - var margin = opts.autoscaleMargin; - if (margin != null) { - if (opts.min == null) { - min -= delta * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin != null && axis.datamin >= 0) - min = 0; - } - if (opts.max == null) { - max += delta * margin; - if (max > 0 && axis.datamax != null && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function setupTickGeneration(axis) { - var opts = axis.options; - - // estimate number of ticks - var noTicks; - if (typeof opts.ticks == "number" && opts.ticks > 0) - noTicks = opts.ticks; - else - // heuristic based on the model a*sqrt(x) fitted to - // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); - - var delta = (axis.max - axis.min) / noTicks, - dec = -Math.floor(Math.log(delta) / Math.LN10), - maxDec = opts.tickDecimals; - - if (maxDec != null && dec > maxDec) { - dec = maxDec; - } - - var magn = Math.pow(10, -dec), - norm = delta / magn, // norm is between 1.0 and 10.0 - size; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) { - size = opts.minTickSize; - } - - axis.delta = delta; - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; - - // Time mode was moved to a plug-in in 0.8, and since so many people use it - // we'll add an especially friendly reminder to make sure they included it. - - if (opts.mode == "time" && !axis.tickGenerator) { - throw new Error("Time mode requires the flot.time plugin."); - } - - // Flot supports base-10 axes; any other mode else is handled by a plug-in, - // like flot.time.js. - - if (!axis.tickGenerator) { - - axis.tickGenerator = function (axis) { - - var ticks = [], - start = floorInBase(axis.min, axis.tickSize), - i = 0, - v = Number.NaN, - prev; - - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push(v); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - axis.tickFormatter = function (value, axis) { - - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - var formatted = "" + Math.round(value * factor) / factor; - - // If tickDecimals was specified, ensure that we have exactly that - // much precision; otherwise default to the value's own precision. - - if (axis.tickDecimals != null) { - var decimal = formatted.indexOf("."); - var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - - return formatted; - }; - } - - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - - if (opts.alignTicksWithAxis != null) { - var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; - if (otherAxis && otherAxis.used && otherAxis != axis) { - // consider snapping min/max to outermost nice ticks - var niceTicks = axis.tickGenerator(axis); - if (niceTicks.length > 0) { - if (opts.min == null) - axis.min = Math.min(axis.min, niceTicks[0]); - if (opts.max == null && niceTicks.length > 1) - axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); - } - - axis.tickGenerator = function (axis) { - // copy ticks, scaled to this axis - var ticks = [], v, i; - for (i = 0; i < otherAxis.ticks.length; ++i) { - v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); - v = axis.min + v * (axis.max - axis.min); - ticks.push(v); - } - return ticks; - }; - - // we might need an extra decimal since forced - // ticks don't necessarily fit naturally - if (!axis.mode && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), - ts = axis.tickGenerator(axis); - - // only proceed if the tick interval rounded - // with an extra decimal doesn't give us a - // zero at end - if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) - axis.tickDecimals = extraDec; - } - } - } - } - - function setTicks(axis) { - var oticks = axis.options.ticks, ticks = []; - if (oticks == null || (typeof oticks == "number" && oticks > 0)) - ticks = axis.tickGenerator(axis); - else if (oticks) { - if ($.isFunction(oticks)) - // generate the ticks - ticks = oticks(axis); - else - ticks = oticks; - } - - // clean up/labelify the supplied ticks, copy them over - var i, v; - axis.ticks = []; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = +t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = +t; - if (label == null) - label = axis.tickFormatter(v, axis); - if (!isNaN(v)) - axis.ticks.push({ v: v, label: label }); - } - } - - function snapRangeToTicks(axis, ticks) { - if (axis.options.autoscaleMargin && ticks.length > 0) { - // snap to ticks - if (axis.options.min == null) - axis.min = Math.min(axis.min, ticks[0].v); - if (axis.options.max == null && ticks.length > 1) - axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); - } - } - - function draw() { - - surface.clear(); - - executeHooks(hooks.drawBackground, [ctx]); - - var grid = options.grid; - - // draw background, if any - if (grid.show && grid.backgroundColor) - drawBackground(); - - if (grid.show && !grid.aboveData) { - drawGrid(); - } - - for (var i = 0; i < series.length; ++i) { - executeHooks(hooks.drawSeries, [ctx, series[i]]); - drawSeries(series[i]); - } - - executeHooks(hooks.draw, [ctx]); - - if (grid.show && grid.aboveData) { - drawGrid(); - } - - surface.render(); - - // A draw implies that either the axes or data have changed, so we - // should probably update the overlay highlights as well. - - triggerRedrawOverlay(); - } - - function extractRange(ranges, coord) { - var axis, from, to, key, axes = allAxes(); - - for (var i = 0; i < axes.length; ++i) { - axis = axes[i]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? xaxes[0] : yaxes[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function drawBackground() { - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); - ctx.fillRect(0, 0, plotWidth, plotHeight); - ctx.restore(); - } - - function drawGrid() { - var i, axes, bw, bc; - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw markings - var markings = options.grid.markings; - if (markings) { - if ($.isFunction(markings)) { - axes = plot.getAxes(); - // xmin etc. is backwards compatibility, to be - // removed in the future - axes.xmin = axes.xaxis.min; - axes.xmax = axes.xaxis.max; - axes.ymin = axes.yaxis.min; - axes.ymax = axes.yaxis.max; - - markings = markings(axes); - } - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - var xequal = xrange.from === xrange.to, - yequal = yrange.from === yrange.to; - - if (xequal && yequal) { - continue; - } - - // then draw - xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); - xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); - yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); - yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); - - if (xequal || yequal) { - var lineWidth = m.lineWidth || options.grid.markingsLineWidth, - subPixel = lineWidth % 2 ? 0.5 : 0; - ctx.beginPath(); - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = lineWidth; - if (xequal) { - ctx.moveTo(xrange.to + subPixel, yrange.from); - ctx.lineTo(xrange.to + subPixel, yrange.to); - } else { - ctx.moveTo(xrange.from, yrange.to + subPixel); - ctx.lineTo(xrange.to, yrange.to + subPixel); - } - ctx.stroke(); - } else { - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(xrange.from, yrange.to, - xrange.to - xrange.from, - yrange.from - yrange.to); - } - } - } - - // draw the ticks - axes = allAxes(); - bw = options.grid.borderWidth; - - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box, - t = axis.tickLength, x, y, xoff, yoff; - if (!axis.show || axis.ticks.length == 0) - continue; - - ctx.lineWidth = 1; - - // find the edges - if (axis.direction == "x") { - x = 0; - if (t == "full") - y = (axis.position == "top" ? 0 : plotHeight); - else - y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); - } - else { - y = 0; - if (t == "full") - x = (axis.position == "left" ? 0 : plotWidth); - else - x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); - } - - // draw tick bar - if (!axis.innermost) { - ctx.strokeStyle = axis.options.color; - ctx.beginPath(); - xoff = yoff = 0; - if (axis.direction == "x") - xoff = plotWidth + 1; - else - yoff = plotHeight + 1; - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") { - y = Math.floor(y) + 0.5; - } else { - x = Math.floor(x) + 0.5; - } - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - ctx.stroke(); - } - - // draw ticks - - ctx.strokeStyle = axis.options.tickColor; - - ctx.beginPath(); - for (i = 0; i < axis.ticks.length; ++i) { - var v = axis.ticks[i].v; - - xoff = yoff = 0; - - if (isNaN(v) || v < axis.min || v > axis.max - // skip those lying on the axes if we got a border - || (t == "full" - && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) - && (v == axis.min || v == axis.max))) - continue; - - if (axis.direction == "x") { - x = axis.p2c(v); - yoff = t == "full" ? -plotHeight : t; - - if (axis.position == "top") - yoff = -yoff; - } - else { - y = axis.p2c(v); - xoff = t == "full" ? -plotWidth : t; - - if (axis.position == "left") - xoff = -xoff; - } - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") - x = Math.floor(x) + 0.5; - else - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - } - - ctx.stroke(); - } - - - // draw border - if (bw) { - // If either borderWidth or borderColor is an object, then draw the border - // line by line instead of as one rectangle - bc = options.grid.borderColor; - if(typeof bw == "object" || typeof bc == "object") { - if (typeof bw !== "object") { - bw = {top: bw, right: bw, bottom: bw, left: bw}; - } - if (typeof bc !== "object") { - bc = {top: bc, right: bc, bottom: bc, left: bc}; - } - - if (bw.top > 0) { - ctx.strokeStyle = bc.top; - ctx.lineWidth = bw.top; - ctx.beginPath(); - ctx.moveTo(0 - bw.left, 0 - bw.top/2); - ctx.lineTo(plotWidth, 0 - bw.top/2); - ctx.stroke(); - } - - if (bw.right > 0) { - ctx.strokeStyle = bc.right; - ctx.lineWidth = bw.right; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); - ctx.lineTo(plotWidth + bw.right / 2, plotHeight); - ctx.stroke(); - } - - if (bw.bottom > 0) { - ctx.strokeStyle = bc.bottom; - ctx.lineWidth = bw.bottom; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); - ctx.lineTo(0, plotHeight + bw.bottom / 2); - ctx.stroke(); - } - - if (bw.left > 0) { - ctx.strokeStyle = bc.left; - ctx.lineWidth = bw.left; - ctx.beginPath(); - ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); - ctx.lineTo(0- bw.left/2, 0); - ctx.stroke(); - } - } - else { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); - } - } - - ctx.restore(); - } - - function drawAxisLabels() { - - $.each(allAxes(), function (_, axis) { - var box = axis.box, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = axis.options.font || "flot-tick-label tickLabel", - tick, x, y, halign, valign; - - // Remove text before checking for axis.show and ticks.length; - // otherwise plugins, like flot-tickrotor, that draw their own - // tick labels will end up with both theirs and the defaults. - - surface.removeText(layer); - - if (!axis.show || axis.ticks.length == 0) - return; - - for (var i = 0; i < axis.ticks.length; ++i) { - - tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - - if (axis.direction == "x") { - halign = "center"; - x = plotOffset.left + axis.p2c(tick.v); - if (axis.position == "bottom") { - y = box.top + box.padding; - } else { - y = box.top + box.height - box.padding; - valign = "bottom"; - } - } else { - valign = "middle"; - y = plotOffset.top + axis.p2c(tick.v); - if (axis.position == "left") { - x = box.left + box.width - box.padding; - halign = "right"; - } else { - x = box.left + box.padding; - } - } - - surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); - } - }); - } - - function drawSeries(series) { - if (series.lines.show) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - prevx = null, prevy = null; - - ctx.beginPath(); - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1], - x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); - } - ctx.stroke(); - } - - function plotLineArea(datapoints, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - bottom = Math.min(Math.max(0, axisy.min), axisy.max), - i = 0, top, areaOpen = false, - ypos = 1, segmentStart = 0, segmentEnd = 0; - - // we process each segment in two turns, first forward - // direction to sketch out top, then once we hit the - // end we go backwards to sketch the bottom - while (true) { - if (ps > 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 2; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth, - sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (lw > 0 && sw > 0) { - // draw shadow as a thick and thin line with transparency - ctx.lineWidth = sw; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - // position shadow at angle from the mid of line - var angle = Math.PI/18; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); - ctx.lineWidth = sw/2; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); - if (fillStyle) { - ctx.fillStyle = fillStyle; - plotLineArea(series.datapoints, series.xaxis, series.yaxis); - } - - if (lw > 0) - plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var x = points[i], y = points[i + 1]; - if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - x = axisx.p2c(x); - y = axisy.p2c(y) + offset; - if (symbol == "circle") - ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); - else - symbol(ctx, x, y, radius, shadow); - ctx.closePath(); - - if (fillStyle) { - ctx.fillStyle = fillStyle; - ctx.fill(); - } - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.points.lineWidth, - sw = series.shadowSize, - radius = series.points.radius, - symbol = series.points.symbol; - - // If the user sets the line width to 0, we change it to a very - // small value. A line width of 0 seems to force the default of 1. - // Doing the conditional here allows the shadow setting to still be - // optional even with a lineWidth of 0. - - if( lw == 0 ) - lw = 0.0001; - - if (lw > 0 && sw > 0) { - // draw shadow in two steps - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPoints(series.datapoints, radius, null, w + w/2, true, - series.xaxis, series.yaxis, symbol); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPoints(series.datapoints, radius, null, w/2, true, - series.xaxis, series.yaxis, symbol); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - plotPoints(series.datapoints, radius, - getFillStyle(series.points, series.color), 0, false, - series.xaxis, series.yaxis, symbol); - ctx.restore(); - } - - function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { - var left, right, bottom, top, - drawLeft, drawRight, drawTop, drawBottom, - tmp; - - // in horizontal mode, we start the bar from the left - // instead of from the bottom so it appears to be - // horizontal rather than vertical - if (horizontal) { - drawBottom = drawRight = drawTop = true; - drawLeft = false; - left = b; - right = x; - top = y + barLeft; - bottom = y + barRight; - - // account for negative bars - if (right < left) { - tmp = right; - right = left; - left = tmp; - drawLeft = true; - drawRight = false; - } - } - else { - drawLeft = drawRight = drawTop = true; - drawBottom = false; - left = x + barLeft; - right = x + barRight; - bottom = b; - top = y; - - // account for negative bars - if (top < bottom) { - tmp = top; - top = bottom; - bottom = tmp; - drawBottom = true; - drawTop = false; - } - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - // fill the bar - if (fillStyleCallback) { - c.fillStyle = fillStyleCallback(bottom, top); - c.fillRect(left, top, right - left, bottom - top) - } - - // draw outline - if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { - c.beginPath(); - - // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom); - if (drawLeft) - c.lineTo(left, top); - else - c.moveTo(left, top); - if (drawTop) - c.lineTo(right, top); - else - c.moveTo(right, top); - if (drawRight) - c.lineTo(right, bottom); - else - c.moveTo(right, bottom); - if (drawBottom) - c.lineTo(left, bottom); - else - c.moveTo(left, bottom); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // FIXME: figure out a way to add shadows (for instance along the right edge) - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - - var barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); - ctx.restore(); - } - - function getFillStyle(filloptions, seriesColor, bottom, top) { - var fill = filloptions.fill; - if (!fill) - return null; - - if (filloptions.fillColor) - return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - - var c = $.color.parse(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - return c.toString(); - } - - function insertLegend() { - - if (options.legend.container != null) { - $(options.legend.container).html(""); - } else { - placeholder.find(".legend").remove(); - } - - if (!options.legend.show) { - return; - } - - var fragments = [], entries = [], rowStarted = false, - lf = options.legend.labelFormatter, s, label; - - // Build a list of legend entries, with each having a label and a color - - for (var i = 0; i < series.length; ++i) { - s = series[i]; - if (s.label) { - label = lf ? lf(s.label, s) : s.label; - if (label) { - entries.push({ - label: label, - color: s.color - }); - } - } - } - - // Sort the legend using either the default or a custom comparator - - if (options.legend.sorted) { - if ($.isFunction(options.legend.sorted)) { - entries.sort(options.legend.sorted); - } else if (options.legend.sorted == "reverse") { - entries.reverse(); - } else { - var ascending = options.legend.sorted != "descending"; - entries.sort(function(a, b) { - return a.label == b.label ? 0 : ( - (a.label < b.label) != ascending ? 1 : -1 // Logical XOR - ); - }); - } - } - - // Generate markup for the list of entries, in their final order - - for (var i = 0; i < entries.length; ++i) { - - var entry = entries[i]; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push(''); - fragments.push(''); - rowStarted = true; - } - - fragments.push( - '
' + - '' + entry.label + '' - ); - } - - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '' + fragments.join("") + '
'; - if (options.legend.container != null) - $(options.legend.container).html(table); - else { - var pos = "", - p = options.legend.position, - m = options.legend.margin; - if (m[0] == null) - m = [m, m]; - if (p.charAt(0) == "n") - pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; - var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - c = options.grid.backgroundColor; - if (c && typeof c == "string") - c = $.color.parse(c); - else - c = $.color.extract(legend, 'background-color'); - c.a = 1; - c = c.toString(); - } - var div = legend.children(); - $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } - } - } - - - // interactive features - - var highlights = [], - redrawTimeout = null; - - // returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY, seriesFilter) { - var maxDistance = options.grid.mouseActiveRadius, - smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j, ps; - - for (i = series.length - 1; i >= 0; --i) { - if (!seriesFilter(series[i])) - continue; - - var s = series[i], - axisx = s.xaxis, - axisy = s.yaxis, - points = s.datapoints.points, - mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale; - - ps = s.datapoints.pointsize; - // with inverse transforms, we can't use the maxx/maxy - // optimization, sadly - if (axisx.options.inverseTransform) - maxx = Number.MAX_VALUE; - if (axisy.options.inverseTransform) - maxy = Number.MAX_VALUE; - - if (s.lines.show || s.points.show) { - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1]; - if (x == null) - continue; - - // For points and lines, the cursor must be within a - // certain distance to the data point - if (x - mx > maxx || x - mx < -maxx || - y - my > maxy || y - my < -maxy) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scales of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; // we save the sqrt - - // use <= to ensure last point takes precedence - // (last generally means on top of) - if (dist < smallestDistance) { - smallestDistance = dist; - item = [i, j / ps]; - } - } - } - - if (s.bars.show && !item) { // no other point can be nearby - - var barLeft, barRight; - - switch (s.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -s.bars.barWidth; - break; - default: - barLeft = -s.bars.barWidth / 2; - } - - barRight = barLeft + s.bars.barWidth; - - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1], b = points[j + 2]; - if (x == null) - continue; - - // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && - my >= y + barLeft && my <= y + barRight) : - (mx >= x + barLeft && mx <= x + barRight && - my >= Math.min(b, y) && my <= Math.max(b, y))) - item = [i, j / ps]; - } - } - } - - if (item) { - i = item[0]; - j = item[1]; - ps = series[i].datapoints.pointsize; - - return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - return null; - } - - function onMouseMove(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return s["hoverable"] != false; }); - } - - function onMouseLeave(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return false; }); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e, - function (s) { return s["clickable"] != false; }); - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event, seriesFilter) { - var offset = eventHolder.offset(), - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top, - pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); - - pos.pageX = event.pageX; - pos.pageY = event.pageY; - - var item = findNearbyItem(canvasX, canvasY, seriesFilter); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); - } - - if (options.grid.autoHighlight) { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && - !(item && h.series == item.series && - h.point[0] == item.datapoint[0] && - h.point[1] == item.datapoint[1])) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, eventname); - } - - placeholder.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - var t = options.interaction.redrawOverlayInterval; - if (t == -1) { // skip event queue - drawOverlay(); - return; - } - - if (!redrawTimeout) - redrawTimeout = setTimeout(drawOverlay, t); - } - - function drawOverlay() { - redrawTimeout = null; - - // draw highlights - octx.save(); - overlay.clear(); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - executeHooks(hooks.drawOverlay, [octx]); - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (s == null && point == null) { - highlights = []; - triggerRedrawOverlay(); - return; - } - - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis, - highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = highlightColor; - var radius = 1.5 * pointRadius; - x = axisx.p2c(x); - y = axisy.p2c(y); - - octx.beginPath(); - if (series.points.symbol == "circle") - octx.arc(x, y, radius, 0, 2 * Math.PI, false); - else - series.points.symbol(octx, x, y, radius, false); - octx.closePath(); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), - fillStyle = highlightColor, - barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = highlightColor; - - drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); - } - - function getColorOrGradient(spec, bottom, top, defaultColor) { - if (typeof spec == "string") - return spec; - else { - // assume this is a gradient spec; IE currently only - // supports a simple vertical gradient properly, so that's - // what we support too - var gradient = ctx.createLinearGradient(0, top, 0, bottom); - - for (var i = 0, l = spec.colors.length; i < l; ++i) { - var c = spec.colors[i]; - if (typeof c != "string") { - var co = $.color.parse(defaultColor); - if (c.brightness != null) - co = co.scale('rgb', c.brightness); - if (c.opacity != null) - co.a *= c.opacity; - c = co.toString(); - } - gradient.addColorStop(i / (l - 1), c); - } - - return gradient; - } - } - } - - // Add the plot function to the top level of the jQuery object - - $.plot = function(placeholder, data, options) { - //var t0 = new Date(); - var plot = new Plot($(placeholder), data, options, $.plot.plugins); - //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); - return plot; - }; - - $.plot.version = "0.8.3"; - - $.plot.plugins = []; - - // Also add the plot function as a chainable property - - $.fn.plot = function(data, options) { - return this.each(function() { - $.plot(this, data, options); - }); - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - -})(jQuery); diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js deleted file mode 100644 index c8707b30f4e6f..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js +++ /dev/null @@ -1,360 +0,0 @@ -/* Flot plugin for selecting regions of a plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - -selection: { - mode: null or "x" or "y" or "xy", - color: color, - shape: "round" or "miter" or "bevel", - minSize: number of pixels -} - -Selection support is enabled by setting the mode to one of "x", "y" or "xy". -In "x" mode, the user will only be able to specify the x range, similarly for -"y" mode. For "xy", the selection becomes a rectangle where both ranges can be -specified. "color" is color of the selection (if you need to change the color -later on, you can get to it with plot.getOptions().selection.color). "shape" -is the shape of the corners of the selection. - -"minSize" is the minimum size a selection can be in pixels. This value can -be customized to determine the smallest size a selection can be and still -have the selection rectangle be displayed. When customizing this value, the -fact that it refers to pixels, not axis units must be taken into account. -Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 -minute, setting "minSize" to 1 will not make the minimum selection size 1 -minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent -"plotunselected" events from being fired when the user clicks the mouse without -dragging. - -When selection support is enabled, a "plotselected" event will be emitted on -the DOM element you passed into the plot function. The event handler gets a -parameter with the ranges selected on the axes, like this: - - placeholder.bind( "plotselected", function( event, ranges ) { - alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) - // similar for yaxis - with multiple axes, the extra ones are in - // x2axis, x3axis, ... - }); - -The "plotselected" event is only fired when the user has finished making the -selection. A "plotselecting" event is fired during the process with the same -parameters as the "plotselected" event, in case you want to know what's -happening while it's happening, - -A "plotunselected" event with no arguments is emitted when the user clicks the -mouse to remove the selection. As stated above, setting "minSize" to 0 will -destroy this behavior. - -The plugin also adds the following methods to the plot object: - -- setSelection( ranges, preventEvent ) - - Set the selection rectangle. The passed in ranges is on the same form as - returned in the "plotselected" event. If the selection mode is "x", you - should put in either an xaxis range, if the mode is "y" you need to put in - an yaxis range and both xaxis and yaxis if the selection mode is "xy", like - this: - - setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); - - setSelection will trigger the "plotselected" event when called. If you don't - want that to happen, e.g. if you're inside a "plotselected" handler, pass - true as the second parameter. If you are using multiple axes, you can - specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of - xaxis, the plugin picks the first one it sees. - -- clearSelection( preventEvent ) - - Clear the selection rectangle. Pass in true to avoid getting a - "plotunselected" event. - -- getSelection() - - Returns the current selection in the same format as the "plotselected" - event. If there's currently no selection, the function returns null. - -*/ - -(function ($) { - function init(plot) { - var selection = { - first: { x: -1, y: -1}, second: { x: -1, y: -1}, - show: false, - active: false - }; - - // FIXME: The drag handling implemented here should be - // abstracted out, there's some similar code from a library in - // the navigation plugin, this should be massaged a bit to fit - // the Flot cases here better and reused. Doing this would - // make this plugin much slimmer. - var savedhandlers = {}; - - var mouseUpHandler = null; - - function onMouseMove(e) { - if (selection.active) { - updateSelection(e); - - plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); - } - } - - function onMouseDown(e) { - if (e.which != 1) // only accept left-click - return; - - // cancel out any text selections - document.body.focus(); - - // prevent text selection and drag in old-school browsers - if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { - savedhandlers.onselectstart = document.onselectstart; - document.onselectstart = function () { return false; }; - } - if (document.ondrag !== undefined && savedhandlers.ondrag == null) { - savedhandlers.ondrag = document.ondrag; - document.ondrag = function () { return false; }; - } - - setSelectionPos(selection.first, e); - - selection.active = true; - - // this is a bit silly, but we have to use a closure to be - // able to whack the same handler again - mouseUpHandler = function (e) { onMouseUp(e); }; - - $(document).one("mouseup", mouseUpHandler); - } - - function onMouseUp(e) { - mouseUpHandler = null; - - // revert drag stuff for old-school browsers - if (document.onselectstart !== undefined) - document.onselectstart = savedhandlers.onselectstart; - if (document.ondrag !== undefined) - document.ondrag = savedhandlers.ondrag; - - // no more dragging - selection.active = false; - updateSelection(e); - - if (selectionIsSane()) - triggerSelectedEvent(); - else { - // this counts as a clear - plot.getPlaceholder().trigger("plotunselected", [ ]); - plot.getPlaceholder().trigger("plotselecting", [ null ]); - } - - return false; - } - - function getSelection() { - if (!selectionIsSane()) - return null; - - if (!selection.show) return null; - - var r = {}, c1 = selection.first, c2 = selection.second; - $.each(plot.getAxes(), function (name, axis) { - if (axis.used) { - var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); - r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; - } - }); - return r; - } - - function triggerSelectedEvent() { - var r = getSelection(); - - plot.getPlaceholder().trigger("plotselected", [ r ]); - - // backwards-compat stuff, to be removed in future - if (r.xaxis && r.yaxis) - plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); - } - - function clamp(min, value, max) { - return value < min ? min: (value > max ? max: value); - } - - function setSelectionPos(pos, e) { - var o = plot.getOptions(); - var offset = plot.getPlaceholder().offset(); - var plotOffset = plot.getPlotOffset(); - pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); - pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); - - if (o.selection.mode == "y") - pos.x = pos == selection.first ? 0 : plot.width(); - - if (o.selection.mode == "x") - pos.y = pos == selection.first ? 0 : plot.height(); - } - - function updateSelection(pos) { - if (pos.pageX == null) - return; - - setSelectionPos(selection.second, pos); - if (selectionIsSane()) { - selection.show = true; - plot.triggerRedrawOverlay(); - } - else - clearSelection(true); - } - - function clearSelection(preventEvent) { - if (selection.show) { - selection.show = false; - plot.triggerRedrawOverlay(); - if (!preventEvent) - plot.getPlaceholder().trigger("plotunselected", [ ]); - } - } - - // function taken from markings support in Flot - function extractRange(ranges, coord) { - var axis, from, to, key, axes = plot.getAxes(); - - for (var k in axes) { - axis = axes[k]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function setSelection(ranges, preventEvent) { - var axis, range, o = plot.getOptions(); - - if (o.selection.mode == "y") { - selection.first.x = 0; - selection.second.x = plot.width(); - } - else { - range = extractRange(ranges, "x"); - - selection.first.x = range.axis.p2c(range.from); - selection.second.x = range.axis.p2c(range.to); - } - - if (o.selection.mode == "x") { - selection.first.y = 0; - selection.second.y = plot.height(); - } - else { - range = extractRange(ranges, "y"); - - selection.first.y = range.axis.p2c(range.from); - selection.second.y = range.axis.p2c(range.to); - } - - selection.show = true; - plot.triggerRedrawOverlay(); - if (!preventEvent && selectionIsSane()) - triggerSelectedEvent(); - } - - function selectionIsSane() { - var minSize = plot.getOptions().selection.minSize; - return Math.abs(selection.second.x - selection.first.x) >= minSize && - Math.abs(selection.second.y - selection.first.y) >= minSize; - } - - plot.clearSelection = clearSelection; - plot.setSelection = setSelection; - plot.getSelection = getSelection; - - plot.hooks.bindEvents.push(function(plot, eventHolder) { - var o = plot.getOptions(); - if (o.selection.mode != null) { - eventHolder.mousemove(onMouseMove); - eventHolder.mousedown(onMouseDown); - } - }); - - - plot.hooks.drawOverlay.push(function (plot, ctx) { - // draw selection - if (selection.show && selectionIsSane()) { - var plotOffset = plot.getPlotOffset(); - var o = plot.getOptions(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var c = $.color.parse(o.selection.color); - - ctx.strokeStyle = c.scale('a', 0.8).toString(); - ctx.lineWidth = 1; - ctx.lineJoin = o.selection.shape; - ctx.fillStyle = c.scale('a', 0.4).toString(); - - var x = Math.min(selection.first.x, selection.second.x) + 0.5, - y = Math.min(selection.first.y, selection.second.y) + 0.5, - w = Math.abs(selection.second.x - selection.first.x) - 1, - h = Math.abs(selection.second.y - selection.first.y) - 1; - - ctx.fillRect(x, y, w, h); - ctx.strokeRect(x, y, w, h); - - ctx.restore(); - } - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mousedown", onMouseDown); - - if (mouseUpHandler) - $(document).unbind("mouseup", mouseUpHandler); - }); - - } - - $.plot.plugins.push({ - init: init, - options: { - selection: { - mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac", - shape: "round", // one of "round", "miter", or "bevel" - minSize: 5 // minimum number of pixels - } - }, - name: 'selection', - version: '1.1' - }); -})(jQuery); diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js deleted file mode 100644 index 0d91c0f3c0160..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js +++ /dev/null @@ -1,188 +0,0 @@ -/* Flot plugin for stacking data sets rather than overlaying them. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin assumes the data is sorted on x (or y if stacking horizontally). -For line charts, it is assumed that if a line has an undefined gap (from a -null point), then the line above it should have the same gap - insert zeros -instead of "null" if you want another behaviour. This also holds for the start -and end of the chart. Note that stacking a mix of positive and negative values -in most instances doesn't make sense (so it looks weird). - -Two or more series are stacked when their "stack" attribute is set to the same -key (which can be any number or string or just "true"). To specify the default -stack, you can set the stack option like this: - - series: { - stack: null/false, true, or a key (number/string) - } - -You can also specify it for a single series, like this: - - $.plot( $("#placeholder"), [{ - data: [ ... ], - stack: true - }]) - -The stacking order is determined by the order of the data series in the array -(later series end up on top of the previous). - -Internally, the plugin modifies the datapoints in each series, adding an -offset to the y value. For line series, extra data points are inserted through -interpolation. If there's a second y value, it's also adjusted (e.g for bar -charts or filled areas). - -*/ - -(function ($) { - var options = { - series: { stack: null } // or number/string - }; - - function init(plot) { - function findMatchingSeries(s, allseries) { - var res = null; - for (var i = 0; i < allseries.length; ++i) { - if (s == allseries[i]) - break; - - if (allseries[i].stack == s.stack) - res = allseries[i]; - } - - return res; - } - - function stackData(plot, s, datapoints) { - if (s.stack == null || s.stack === false) - return; - - var other = findMatchingSeries(s, plot.getData()); - if (!other) - return; - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - horizontal = s.bars.horizontal, - withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), - withsteps = withlines && s.lines.steps, - fromgap = true, - keyOffset = horizontal ? 1 : 0, - accumulateOffset = horizontal ? 0 : 1, - i = 0, j = 0, l, m; - - while (true) { - if (i >= points.length) - break; - - l = newpoints.length; - - if (points[i] == null) { - // copy gaps - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - i += ps; - } - else if (j >= otherpoints.length) { - // for lines, we can't use the rest of the points - if (!withlines) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - } - i += ps; - } - else if (otherpoints[j] == null) { - // oops, got a gap - for (m = 0; m < ps; ++m) - newpoints.push(null); - fromgap = true; - j += otherps; - } - else { - // cases where we actually got two points - px = points[i + keyOffset]; - py = points[i + accumulateOffset]; - qx = otherpoints[j + keyOffset]; - qy = otherpoints[j + accumulateOffset]; - bottom = 0; - - if (px == qx) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - newpoints[l + accumulateOffset] += qy; - bottom = qy; - - i += ps; - j += otherps; - } - else if (px > qx) { - // we got past point below, might need to - // insert interpolated extra point - if (withlines && i > 0 && points[i - ps] != null) { - intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); - newpoints.push(qx); - newpoints.push(intery + qy); - for (m = 2; m < ps; ++m) - newpoints.push(points[i + m]); - bottom = qy; - } - - j += otherps; - } - else { // px < qx - if (fromgap && withlines) { - // if we come from a gap, we just skip this point - i += ps; - continue; - } - - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - // we might be able to interpolate a point below, - // this can give us a better y - if (withlines && j > 0 && otherpoints[j - otherps] != null) - bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); - - newpoints[l + accumulateOffset] += bottom; - - i += ps; - } - - fromgap = false; - - if (l != newpoints.length && withbottom) - newpoints[l + 2] += bottom; - } - - // maintain the line steps invariant - if (withsteps && l != newpoints.length && l > 0 - && newpoints[l] != null - && newpoints[l] != newpoints[l - ps] - && newpoints[l + 1] != newpoints[l - ps + 1]) { - for (m = 0; m < ps; ++m) - newpoints[l + ps + m] = newpoints[l + m]; - newpoints[l + 1] = newpoints[l - ps + 1]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push(stackData); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'stack', - version: '1.2' - }); -})(jQuery); diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js deleted file mode 100644 index 79f634971b6fa..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js +++ /dev/null @@ -1,71 +0,0 @@ -/* Flot plugin that adds some extra symbols for plotting points. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The symbols are accessed as strings through the standard symbol options: - - series: { - points: { - symbol: "square" // or "diamond", "triangle", "cross" - } - } - -*/ - -(function ($) { - function processRawData(plot, series, datapoints) { - // we normalize the area of each symbol so it is approximately the - // same as a circle of the given radius - - var handlers = { - square: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.rect(x - size, y - size, size + size, size + size); - }, - diamond: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) - var size = radius * Math.sqrt(Math.PI / 2); - ctx.moveTo(x - size, y); - ctx.lineTo(x, y - size); - ctx.lineTo(x + size, y); - ctx.lineTo(x, y + size); - ctx.lineTo(x - size, y); - }, - triangle: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) - var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); - var height = size * Math.sin(Math.PI / 3); - ctx.moveTo(x - size/2, y + height/2); - ctx.lineTo(x + size/2, y + height/2); - if (!shadow) { - ctx.lineTo(x, y - height/2); - ctx.lineTo(x - size/2, y + height/2); - } - }, - cross: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.moveTo(x - size, y - size); - ctx.lineTo(x + size, y + size); - ctx.moveTo(x - size, y + size); - ctx.lineTo(x + size, y - size); - } - }; - - var s = series.points.symbol; - if (handlers[s]) - series.points.symbol = handlers[s]; - } - - function init(plot) { - plot.hooks.processDatapoints.push(processRawData); - } - - $.plot.plugins.push({ - init: init, - name: 'symbols', - version: '1.0' - }); -})(jQuery); diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js b/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js deleted file mode 100644 index 34c1d121259a2..0000000000000 --- a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js +++ /dev/null @@ -1,432 +0,0 @@ -/* Pretty handling of time axes. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Set axis.mode to "time" to enable. See the section "Time series data" in -API.txt for details. - -*/ - -(function($) { - - var options = { - xaxis: { - timezone: null, // "browser" for local to the client or timezone for timezone-js - timeformat: null, // format string to use - twelveHourClock: false, // 12 or 24 time in time mode - monthNames: null // list of names of months - } - }; - - // round to nearby lower multiple of base - - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - // Returns a string with the date d formatted according to fmt. - // A subset of the Open Group's strftime format is supported. - - function formatDate(d, fmt, monthNames, dayNames) { - - if (typeof d.strftime == "function") { - return d.strftime(fmt); - } - - var leftPad = function(n, pad) { - n = "" + n; - pad = "" + (pad == null ? "0" : pad); - return n.length == 1 ? pad + n : n; - }; - - var r = []; - var escape = false; - var hours = d.getHours(); - var isAM = hours < 12; - - if (monthNames == null) { - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - } - - if (dayNames == null) { - dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - } - - var hours12; - - if (hours > 12) { - hours12 = hours - 12; - } else if (hours == 0) { - hours12 = 12; - } else { - hours12 = hours; - } - - for (var i = 0; i < fmt.length; ++i) { - - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'a': c = "" + dayNames[d.getDay()]; break; - case 'b': c = "" + monthNames[d.getMonth()]; break; - case 'd': c = leftPad(d.getDate()); break; - case 'e': c = leftPad(d.getDate(), " "); break; - case 'h': // For back-compat with 0.7; remove in 1.0 - case 'H': c = leftPad(hours); break; - case 'I': c = leftPad(hours12); break; - case 'l': c = leftPad(hours12, " "); break; - case 'm': c = leftPad(d.getMonth() + 1); break; - case 'M': c = leftPad(d.getMinutes()); break; - // quarters not in Open Group's strftime specification - case 'q': - c = "" + (Math.floor(d.getMonth() / 3) + 1); break; - case 'S': c = leftPad(d.getSeconds()); break; - case 'y': c = leftPad(d.getFullYear() % 100); break; - case 'Y': c = "" + d.getFullYear(); break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case 'w': c = "" + d.getDay(); break; - } - r.push(c); - escape = false; - } else { - if (c == "%") { - escape = true; - } else { - r.push(c); - } - } - } - - return r.join(""); - } - - // To have a consistent view of time-based data independent of which time - // zone the client happens to be in we need a date-like object independent - // of time zones. This is done through a wrapper that only calls the UTC - // versions of the accessor methods. - - function makeUtcWrapper(d) { - - function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { - sourceObj[sourceMethod] = function() { - return targetObj[targetMethod].apply(targetObj, arguments); - }; - }; - - var utc = { - date: d - }; - - // support strftime, if found - - if (d.strftime != undefined) { - addProxyMethod(utc, "strftime", d, "strftime"); - } - - addProxyMethod(utc, "getTime", d, "getTime"); - addProxyMethod(utc, "setTime", d, "setTime"); - - var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; - - for (var p = 0; p < props.length; p++) { - addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); - addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); - } - - return utc; - }; - - // select time zone strategy. This returns a date-like object tied to the - // desired timezone - - function dateGenerator(ts, opts) { - if (opts.timezone == "browser") { - return new Date(ts); - } else if (!opts.timezone || opts.timezone == "utc") { - return makeUtcWrapper(new Date(ts)); - } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { - var d = new timezoneJS.Date(); - // timezone-js is fickle, so be sure to set the time zone before - // setting the time. - d.setTimezone(opts.timezone); - d.setTime(ts); - return d; - } else { - return makeUtcWrapper(new Date(ts)); - } - } - - // map of app. size of time units in milliseconds - - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "quarter": 3 * 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - - var baseSpec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"] - ]; - - // we don't know which variant(s) we'll need yet, but generating both is - // cheap - - var specMonths = baseSpec.concat([[3, "month"], [6, "month"], - [1, "year"]]); - var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], - [1, "year"]]); - - function init(plot) { - plot.hooks.processOptions.push(function (plot, options) { - $.each(plot.getAxes(), function(axisName, axis) { - - var opts = axis.options; - - if (opts.mode == "time") { - axis.tickGenerator = function(axis) { - - var ticks = []; - var d = dateGenerator(axis.min, opts); - var minSize = 0; - - // make quarter use a possibility if quarters are - // mentioned in either of these options - - var spec = (opts.tickSize && opts.tickSize[1] === - "quarter") || - (opts.minTickSize && opts.minTickSize[1] === - "quarter") ? specQuarters : specMonths; - - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") { - minSize = opts.tickSize; - } else { - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; - } - } - - for (var i = 0; i < spec.length - 1; ++i) { - if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { - break; - } - } - - var size = spec[i][0]; - var unit = spec[i][1]; - - // special-case the possibility of several years - - if (unit == "year") { - - // if given a minTickSize in years, just use it, - // ensuring that it's an integer - - if (opts.minTickSize != null && opts.minTickSize[1] == "year") { - size = Math.floor(opts.minTickSize[0]); - } else { - - var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); - var norm = (axis.delta / timeUnitSize.year) / magn; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - } - - // minimum size for years is 1 - - if (size < 1) { - size = 1; - } - } - - axis.tickSize = opts.tickSize || [size, unit]; - var tickSize = axis.tickSize[0]; - unit = axis.tickSize[1]; - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") { - d.setSeconds(floorInBase(d.getSeconds(), tickSize)); - } else if (unit == "minute") { - d.setMinutes(floorInBase(d.getMinutes(), tickSize)); - } else if (unit == "hour") { - d.setHours(floorInBase(d.getHours(), tickSize)); - } else if (unit == "month") { - d.setMonth(floorInBase(d.getMonth(), tickSize)); - } else if (unit == "quarter") { - d.setMonth(3 * floorInBase(d.getMonth() / 3, - tickSize)); - } else if (unit == "year") { - d.setFullYear(floorInBase(d.getFullYear(), tickSize)); - } - - // reset smaller components - - d.setMilliseconds(0); - - if (step >= timeUnitSize.minute) { - d.setSeconds(0); - } - if (step >= timeUnitSize.hour) { - d.setMinutes(0); - } - if (step >= timeUnitSize.day) { - d.setHours(0); - } - if (step >= timeUnitSize.day * 4) { - d.setDate(1); - } - if (step >= timeUnitSize.month * 2) { - d.setMonth(floorInBase(d.getMonth(), 3)); - } - if (step >= timeUnitSize.quarter * 2) { - d.setMonth(floorInBase(d.getMonth(), 6)); - } - if (step >= timeUnitSize.year) { - d.setMonth(0); - } - - var carry = 0; - var v = Number.NaN; - var prev; - - do { - - prev = v; - v = d.getTime(); - ticks.push(v); - - if (unit == "month" || unit == "quarter") { - if (tickSize < 1) { - - // a bit complicated - we'll divide the - // month/quarter up but we need to take - // care of fractions so we don't end up in - // the middle of a day - - d.setDate(1); - var start = d.getTime(); - d.setMonth(d.getMonth() + - (unit == "quarter" ? 3 : 1)); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getHours(); - d.setHours(0); - } else { - d.setMonth(d.getMonth() + - tickSize * (unit == "quarter" ? 3 : 1)); - } - } else if (unit == "year") { - d.setFullYear(d.getFullYear() + tickSize); - } else { - d.setTime(v + step); - } - } while (v < axis.max && v != prev); - - return ticks; - }; - - axis.tickFormatter = function (v, axis) { - - var d = dateGenerator(v, axis.options); - - // first check global format - - if (opts.timeformat != null) { - return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); - } - - // possibly use quarters if quarters are mentioned in - // any of these places - - var useQuarters = (axis.options.tickSize && - axis.options.tickSize[1] == "quarter") || - (axis.options.minTickSize && - axis.options.minTickSize[1] == "quarter"); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; - var fmt; - - if (t < timeUnitSize.minute) { - fmt = hourCode + ":%M:%S" + suffix; - } else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) { - fmt = hourCode + ":%M" + suffix; - } else { - fmt = "%b %d " + hourCode + ":%M" + suffix; - } - } else if (t < timeUnitSize.month) { - fmt = "%b %d"; - } else if ((useQuarters && t < timeUnitSize.quarter) || - (!useQuarters && t < timeUnitSize.year)) { - if (span < timeUnitSize.year) { - fmt = "%b"; - } else { - fmt = "%b %Y"; - } - } else if (useQuarters && t < timeUnitSize.year) { - if (span < timeUnitSize.year) { - fmt = "Q%q"; - } else { - fmt = "Q%q %Y"; - } - } else { - fmt = "%Y"; - } - - var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); - - return rt; - }; - } - }); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'time', - version: '1.0' - }); - - // Time-axis support used to be in Flot core, which exposed the - // formatDate function on the plot object. Various plugins depend - // on the function, so we need to re-expose it here. - - $.plot.formatDate = formatDate; - $.plot.dateGenerator = dateGenerator; - -})(jQuery); diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts index b6134884b993b..ddb26a1dd66c8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -17,7 +17,6 @@ * under the License. */ import { resolve } from 'path'; -import { Legacy } from 'kibana'; import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; @@ -31,7 +30,6 @@ const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: Legac hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars: server => ({}), }, - init: (server: Legacy.Server) => ({}), config(Joi: any) { return Joi.object({ enabled: Joi.boolean().default(true), From f04c8f3ef99d0dd60fdcf3f9993b3c4a4e3a8676 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 23 Dec 2019 19:27:43 +0300 Subject: [PATCH 29/59] Convert test to jest --- .../timelion/public/__tests__/index.js | 21 ---- .../public/helpers/tick_formatters.test.ts} | 109 +++++++++--------- .../public/helpers/tick_generator.test.ts} | 21 ++-- 3 files changed, 64 insertions(+), 87 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/__tests__/index.js rename src/legacy/core_plugins/{timelion/public/__tests__/services/tick_formatters.js => vis_type_timelion/public/helpers/tick_formatters.test.ts} (56%) rename src/legacy/core_plugins/{timelion/public/__tests__/_tick_generator.js => vis_type_timelion/public/helpers/tick_generator.test.ts} (72%) diff --git a/src/legacy/core_plugins/timelion/public/__tests__/index.js b/src/legacy/core_plugins/timelion/public/__tests__/index.js deleted file mode 100644 index 9cd61c3665a1a..0000000000000 --- a/src/legacy/core_plugins/timelion/public/__tests__/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './_tick_generator.js'; -describe('Timelion', function() {}); diff --git a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts similarity index 56% rename from src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 40840c4cd2610..01734f2f5888a 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -17,124 +17,123 @@ * under the License. */ -import expect from '@kbn/expect'; -import { tickFormatters } from '../../services/tick_formatters'; +import { tickFormatters } from './tick_formatters'; describe('Tick Formatters', function() { - let formatters; + let formatters: any; beforeEach(function() { formatters = tickFormatters(); }); describe('Bits mode', function() { - let bitFormatter; + let bitFormatter: any; beforeEach(function() { bitFormatter = formatters.bits; }); it('is a function', function() { - expect(bitFormatter).to.be.a('function'); + expect(bitFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitFormatter(7)).to.equal('7b'); - expect(bitFormatter(4 * 1000)).to.equal('4kb'); - expect(bitFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb'); - expect(bitFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb'); + expect(bitFormatter(7)).toEqual('7b'); + expect(bitFormatter(4 * 1000)).toEqual('4kb'); + expect(bitFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb'); + expect(bitFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb'); }); it('formats negative values with b/kb/mb/gb', () => { - expect(bitFormatter(-7)).to.equal('-7b'); - expect(bitFormatter(-4 * 1000)).to.equal('-4kb'); - expect(bitFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb'); - expect(bitFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb'); + expect(bitFormatter(-7)).toEqual('-7b'); + expect(bitFormatter(-4 * 1000)).toEqual('-4kb'); + expect(bitFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb'); + expect(bitFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb'); }); }); describe('Bits/s mode', function() { - let bitsFormatter; + let bitsFormatter: any; beforeEach(function() { bitsFormatter = formatters['bits/s']; }); it('is a function', function() { - expect(bitsFormatter).to.be.a('function'); + expect(bitsFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitsFormatter(7)).to.equal('7b/s'); - expect(bitsFormatter(4 * 1000)).to.equal('4kb/s'); - expect(bitsFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb/s'); - expect(bitsFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb/s'); + expect(bitsFormatter(7)).toEqual('7b/s'); + expect(bitsFormatter(4 * 1000)).toEqual('4kb/s'); + expect(bitsFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb/s'); + expect(bitsFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb/s'); }); it('formats negative values with b/kb/mb/gb', function() { - expect(bitsFormatter(-7)).to.equal('-7b/s'); - expect(bitsFormatter(-4 * 1000)).to.equal('-4kb/s'); - expect(bitsFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb/s'); - expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb/s'); + expect(bitsFormatter(-7)).toEqual('-7b/s'); + expect(bitsFormatter(-4 * 1000)).toEqual('-4kb/s'); + expect(bitsFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb/s'); + expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb/s'); }); }); describe('Bytes mode', function() { - let byteFormatter; + let byteFormatter: any; beforeEach(function() { byteFormatter = formatters.bytes; }); it('is a function', function() { - expect(byteFormatter).to.be.a('function'); + expect(byteFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(byteFormatter(10)).to.equal('10B'); - expect(byteFormatter(10 * 1024)).to.equal('10KB'); - expect(byteFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB'); - expect(byteFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB'); + expect(byteFormatter(10)).toEqual('10B'); + expect(byteFormatter(10 * 1024)).toEqual('10KB'); + expect(byteFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB'); + expect(byteFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(byteFormatter(-10)).to.equal('-10B'); - expect(byteFormatter(-10 * 1024)).to.equal('-10KB'); - expect(byteFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB'); - expect(byteFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB'); + expect(byteFormatter(-10)).toEqual('-10B'); + expect(byteFormatter(-10 * 1024)).toEqual('-10KB'); + expect(byteFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB'); + expect(byteFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB'); }); }); describe('Bytes/s mode', function() { - let bytesFormatter; + let bytesFormatter: any; beforeEach(function() { bytesFormatter = formatters['bytes/s']; }); it('is a function', function() { - expect(bytesFormatter).to.be.a('function'); + expect(bytesFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(bytesFormatter(10)).to.equal('10B/s'); - expect(bytesFormatter(10 * 1024)).to.equal('10KB/s'); - expect(bytesFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB/s'); - expect(bytesFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB/s'); + expect(bytesFormatter(10)).toEqual('10B/s'); + expect(bytesFormatter(10 * 1024)).toEqual('10KB/s'); + expect(bytesFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB/s'); + expect(bytesFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB/s'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(bytesFormatter(-10)).to.equal('-10B/s'); - expect(bytesFormatter(-10 * 1024)).to.equal('-10KB/s'); - expect(bytesFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB/s'); - expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB/s'); + expect(bytesFormatter(-10)).toEqual('-10B/s'); + expect(bytesFormatter(-10 * 1024)).toEqual('-10KB/s'); + expect(bytesFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB/s'); + expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB/s'); }); }); describe('Currency mode', function() { - let currencyFormatter; + let currencyFormatter: any; beforeEach(function() { currencyFormatter = formatters.currency; }); it('is a function', function() { - expect(currencyFormatter).to.be.a('function'); + expect(currencyFormatter).toEqual(expect.any(Function)); }); it('formats with $ by default', function() { @@ -143,7 +142,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(currencyFormatter(10.2, axis)).to.equal('$10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('$10.20'); }); it('accepts currency in ISO 4217', function() { @@ -155,18 +154,18 @@ describe('Tick Formatters', function() { }, }; - expect(currencyFormatter(10.2, axis)).to.equal('CNÂ¥10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('CNÂ¥10.20'); }); }); describe('Percent mode', function() { - let percentFormatter; + let percentFormatter: any; beforeEach(function() { percentFormatter = formatters.percent; }); it('is a function', function() { - expect(percentFormatter).to.be.a('function'); + expect(percentFormatter).toEqual(expect.any(Function)); }); it('formats with %', function() { @@ -175,7 +174,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(percentFormatter(0.1234, axis)).to.equal('12%'); + expect(percentFormatter(0.1234, axis)).toEqual('12%'); }); it('formats with % with decimal precision', function() { @@ -189,18 +188,18 @@ describe('Tick Formatters', function() { }, }, }; - expect(percentFormatter(0.12345, axis)).to.equal('12.345%'); + expect(percentFormatter(0.12345, axis)).toEqual('12.345%'); }); }); describe('Custom mode', function() { - let customFormatter; + let customFormatter: any; beforeEach(function() { customFormatter = formatters.custom; }); it('is a function', function() { - expect(customFormatter).to.be.a('function'); + expect(customFormatter).toEqual(expect.any(Function)); }); it('accepts prefix and suffix', function() { @@ -214,7 +213,7 @@ describe('Tick Formatters', function() { tickDecimals: 1, }; - expect(customFormatter(10.2, axis)).to.equal('prefix10.2suffix'); + expect(customFormatter(10.2, axis)).toEqual('prefix10.2suffix'); }); it('correctly renders small values', function() { @@ -228,7 +227,7 @@ describe('Tick Formatters', function() { tickDecimals: 3, }; - expect(customFormatter(0.00499999999999999, axis)).to.equal('prefix0.005suffix'); + expect(customFormatter(0.00499999999999999, axis)).toEqual('prefix0.005suffix'); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts similarity index 72% rename from src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts index e42657374af4c..d1d959dee9501 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts @@ -17,11 +17,10 @@ * under the License. */ -import expect from '@kbn/expect'; -import { generateTicksProvider } from '../panels/timechart/tick_generator'; +import { generateTicksProvider } from './tick_generator'; describe('Tick Generator', function() { - let generateTicks; + let generateTicks: any; beforeEach(function() { generateTicks = generateTicksProvider(); @@ -29,7 +28,7 @@ describe('Tick Generator', function() { describe('generateTicksProvider()', function() { it('should return a function', function() { - expect(generateTicks).to.be.a('function'); + expect(generateTicks).toEqual(expect.any(Function)); }); }); @@ -58,14 +57,14 @@ describe('Tick Generator', function() { let n = 1; while (Math.pow(2, n) < axis.delta) n++; const expectedDelta = Math.pow(2, n); - const expectedNr = parseInt((axis.max - axis.min) / expectedDelta) + 2; - expect(ticks instanceof Array).to.be(true); - expect(ticks.length).to.be(expectedNr); - expect(ticks[0]).to.equal(axis.min); - expect(ticks[parseInt(ticks.length / 2)]).to.equal( - axis.min + expectedDelta * parseInt(ticks.length / 2) + const expectedNr = Math.floor((axis.max - axis.min) / expectedDelta) + 2; + expect(ticks instanceof Array).toBeTruthy(); + expect(ticks.length).toBe(expectedNr); + expect(ticks[0]).toEqual(axis.min); + expect(ticks[Math.floor(ticks.length / 2)]).toEqual( + axis.min + expectedDelta * Math.floor(ticks.length / 2) ); - expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1)); + expect(ticks[ticks.length - 1]).toEqual(axis.min + expectedDelta * (ticks.length - 1)); }); }); }); From 306ce100ffd6b9d9fef440f32947f659849abdd0 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 24 Dec 2019 12:17:54 +0300 Subject: [PATCH 30/59] Remove kibana_services from timelion --- .../timelion/public/kibana_services.ts | 38 ------------------- .../core_plugins/timelion/public/plugin.ts | 2 - .../core_plugins/vis_type_timelion/index.ts | 1 + 3 files changed, 1 insertion(+), 40 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/kibana_services.ts diff --git a/src/legacy/core_plugins/timelion/public/kibana_services.ts b/src/legacy/core_plugins/timelion/public/kibana_services.ts deleted file mode 100644 index 93d9ad7c39c5a..0000000000000 --- a/src/legacy/core_plugins/timelion/public/kibana_services.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { Panel } from './panels/panel'; - -export interface TimelionKibanaServices { - timelionPanels: Map; -} -let services: TimelionKibanaServices | null = null; - -export function setServices(newServices: TimelionKibanaServices) { - services = newServices; -} - -export function getServices() { - if (!services) { - throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the timelion app?' - ); - } - return services; -} diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 7c29bd8051a60..e671107e85651 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -30,7 +30,6 @@ import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; -import { setServices } from './kibana_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { @@ -63,7 +62,6 @@ export class TimelionPlugin implements Plugin, void> { ) { const timelionPanels: Map = new Map(); - setServices({ timelionPanels }); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, http: core.http, diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts index ddb26a1dd66c8..97e8d178fde51 100644 --- a/src/legacy/core_plugins/vis_type_timelion/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + import { resolve } from 'path'; import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; From 97acfda5adb3534762bd4b85638031c923e952a7 Mon Sep 17 00:00:00 2001 From: Maryia Lapata Date: Tue, 24 Dec 2019 13:28:36 +0300 Subject: [PATCH 31/59] Delete visualize_app.ts.~LOCAL --- visualize_app.ts.~LOCAL | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 visualize_app.ts.~LOCAL diff --git a/visualize_app.ts.~LOCAL b/visualize_app.ts.~LOCAL deleted file mode 100644 index ce44a5051d20d..0000000000000 --- a/visualize_app.ts.~LOCAL +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { VisualizeKibanaServices } from './kibana_services'; - -// @ts-ignore -import { initEditorDirective } from './editor/editor'; -// @ts-ignore -import { initListingDirective } from './listing/visualize_listing'; - -export function initVisualizeAppDirective(app: any, deps: VisualizeKibanaServices) { - initEditorDirective(app, deps); - initListingDirective(app); -} From d0b2018ebc42182148975c508f747f240131f66b Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 24 Dec 2019 16:27:47 +0300 Subject: [PATCH 32/59] Refactoring --- .../timelion/public/directives/_index.scss | 1 - .../public/directives/chart/_index.scss | 1 - .../core_plugins/timelion/public/index.scss | 1 - .../core_plugins/timelion/public/legacy.ts | 3 - .../core_plugins/timelion/public/plugin.ts | 15 +-- .../public/timelion_request_handler.ts | 111 ------------------ .../timelion/public/timelion_vis_fn.ts | 93 --------------- .../public/components}/_chart.scss | 0 .../public/components/chart.tsx | 7 +- .../public/components/panel.tsx | 100 +++++++++------- .../public/components/timelion_vis.tsx | 1 - .../public/helpers/panel_utils.ts | 25 ++-- .../vis_type_timelion/public/index.scss | 1 + .../vis_type_timelion/public/timechart.tsx | 6 +- 14 files changed, 84 insertions(+), 281 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/directives/chart/_index.scss delete mode 100644 src/legacy/core_plugins/timelion/public/timelion_request_handler.ts delete mode 100644 src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts rename src/legacy/core_plugins/{timelion/public/directives/chart => vis_type_timelion/public/components}/_chart.scss (100%) diff --git a/src/legacy/core_plugins/timelion/public/directives/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index 6ee2f81539032..cd46a1a0a369e 100644 --- a/src/legacy/core_plugins/timelion/public/directives/_index.scss +++ b/src/legacy/core_plugins/timelion/public/directives/_index.scss @@ -1,6 +1,5 @@ @import './timelion_expression_input'; @import './cells/index'; -@import './chart/index'; @import './timelion_expression_suggestions/index'; @import './timelion_help/index'; @import './timelion_interval/index'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss b/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss deleted file mode 100644 index 33c188decd4f1..0000000000000 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './chart'; diff --git a/src/legacy/core_plugins/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index f6123f4052156..ebf000d160b54 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -12,4 +12,3 @@ @import './app'; @import './directives/index'; -@import './vis/index'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 891b378e1e616..63030fcbce387 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -24,9 +24,6 @@ import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; const setupPlugins: Readonly = { - data: npSetup.plugins.data, - expressions: npSetup.plugins.expressions, - // Temporary solution // It will be removed when all dependent services are migrated to the new platform. __LEGACY: new LegacyDependenciesPlugin(), diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index e671107e85651..24d7be1ce4cd0 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -24,9 +24,6 @@ import { IUiSettingsClient, HttpSetup, } from 'kibana/public'; -import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; -import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; -import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; @@ -36,14 +33,10 @@ export interface TimelionVisualizationDependencies extends LegacyDependenciesPlu uiSettings: IUiSettingsClient; http: HttpSetup; timelionPanels: Map; - timefilter: TimefilterContract; } /** @internal */ export interface TimelionPluginSetupDependencies { - expressions: ReturnType; - data: DataPublicPluginSetup; - // Temporary solution __LEGACY: LegacyDependenciesPlugin; } @@ -56,23 +49,17 @@ export class TimelionPlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup( - core: CoreSetup, - { __LEGACY, expressions, data }: TimelionPluginSetupDependencies - ) { + public async setup(core: CoreSetup, { __LEGACY }: TimelionPluginSetupDependencies) { const timelionPanels: Map = new Map(); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, http: core.http, timelionPanels, - timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), }; this.registerPanels(dependencies); - - expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { diff --git a/src/legacy/core_plugins/timelion/public/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/timelion_request_handler.ts deleted file mode 100644 index 647ce9e9c07b5..0000000000000 --- a/src/legacy/core_plugins/timelion/public/timelion_request_handler.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// @ts-ignore -import { timezoneProvider } from 'ui/vis/lib/timezone'; -import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { VisParams } from 'ui/vis'; -import { i18n } from '@kbn/i18n'; -import { TimelionVisualizationDependencies } from './plugin'; -import { TimeRange, esFilters, esQuery, Query } from '../../../../plugins/data/public'; - -interface Stats { - cacheCount: number; - invokeTime: number; - queryCount: number; - queryTime: number; - sheetTime: number; -} - -interface Sheet { - list: Array>; - render: Record; - type: string; -} - -export interface TimelionSuccessResponse { - sheet: Sheet[]; - stats: Stats; - visType: string; - type: KIBANA_CONTEXT_NAME; -} - -export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; - const timezone = timezoneProvider(uiSettings)(); - - return async function({ - timeRange, - filters, - query, - visParams, - }: { - timeRange: TimeRange; - filters: esFilters.Filter[]; - query: Query; - visParams: VisParams; - forceFetch?: boolean; - }): Promise { - const expression = visParams.expression; - - if (!expression) { - throw new Error( - i18n.translate('timelion.emptyExpressionErrorMessage', { - defaultMessage: 'Timelion error: No expression provided', - }) - ); - } - - const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); - - // parse the time range client side to make sure it behaves like other charts - const timeRangeBounds = timefilter.calculateBounds(timeRange); - - try { - return await http.post('../api/timelion/run', { - body: JSON.stringify({ - sheet: [expression], - extended: { - es: { - filter: esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs), - }, - }, - time: { - from: timeRangeBounds.min, - to: timeRangeBounds.max, - interval: visParams.interval, - timezone, - }, - }), - }); - } catch (e) { - if (e && e.body) { - const err = new Error( - `${i18n.translate('timelion.requestHandlerErrorTitle', { - defaultMessage: 'Timelion request error', - })}: ${e.body.title} ${e.body.message}` - ); - err.stack = e.stack; - throw err; - } else { - throw e; - } - } - }; -} diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts deleted file mode 100644 index e2b946c0294cd..0000000000000 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import { TimelionVisualizationDependencies } from './plugin'; - -const name = 'timelion_vis'; -const TIMELION_VIS_NAME = 'timelion'; - -interface Arguments { - expression: string; - interval: any; -} - -interface RenderValue { - visData: Context; - visType: 'timelion'; - visParams: VisParams; -} - -type Context = KibanaContext | null; -type VisParams = Arguments; -type Return = Promise>; - -export const getTimelionVisualizationConfig = ( - dependencies: TimelionVisualizationDependencies -): ExpressionFunction => ({ - name, - type: 'render', - context: { - types: ['kibana_context', 'null'], - }, - help: i18n.translate('timelion.function.help', { - defaultMessage: 'Timelion visualization', - }), - args: { - expression: { - types: ['string'], - aliases: ['_'], - default: '".es(*)"', - help: '', - }, - interval: { - types: ['string', 'null'], - default: 'auto', - help: '', - }, - }, - async fn(context, args) { - const timelionRequestHandler = getTimelionRequestHandler(dependencies); - - const visParams = { expression: args.expression, interval: args.interval }; - - const response = await timelionRequestHandler({ - timeRange: get(context, 'timeRange'), - query: get(context, 'query'), - filters: get(context, 'filters'), - visParams, - forceFetch: true, - }); - - response.visType = TIMELION_VIS_NAME; - - return { - type: 'render', - as: 'visualization', - value: { - visParams, - visType: TIMELION_VIS_NAME, - visData: response, - }, - }; - }, -}); diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index 2614ac6b87552..de4c1008f8a92 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -23,21 +23,20 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { getServices } from '../kibana_services'; import { Sheet } from '../helpers/timelion_request_handler'; +import { PanelProps } from './panel'; interface ChartComponentProp { - className?: string; interval: string; renderComplete(): void; seriesList: Sheet; - search?(): void; } -function ChartComponent({ seriesList, interval, search, renderComplete }: ChartComponentProp) { +function ChartComponent({ seriesList, interval, renderComplete }: ChartComponentProp) { if (!seriesList) { return null; } - const panelScope = { seriesList, interval, search, renderComplete } as any; + const panelScope: PanelProps = { seriesList, interval, renderComplete }; panelScope.seriesList.render = seriesList.render || { type: 'timechart', }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index 8638ce33cea2d..4eda11dbd10cf 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -31,28 +31,50 @@ import { getServices } from '../kibana_services'; import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from '../helpers/panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; -interface PanelProps { - name: string; +export interface PanelProps { interval: string; seriesList: Sheet; renderComplete(): void; } +interface Position { + x: number; + x1: number; + y: number; + y1: number; + pageX: number; + pageY: number; +} + +interface Range { + to: number; + from: number; +} + +interface Ranges { + xaxis: Range; + yaxis: Range; +} + const DEBOUNCE_DELAY = 50; // ensure legend is the same height with or without a caption so legend items do not move around const emptyCaption = '
'; -function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProps) { +function Panel({ interval, seriesList, renderComplete }: PanelProps) { const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const [canvasElem, setCanvasElem] = useState(); const [chartElem, setChartElem] = useState(); - const [originalColorMap, setOriginalColorMap] = useState(new Map()); + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); const [highlightedSeries, setHighlightedSeries] = useState(); const [focusedSeries, setFocusedSeries] = useState(); const [plot, setPlot] = useState(); + // Used to toggle the series, and for displaying values on hover + const [legendValueNumbers, setLegendValueNumbers] = useState(); + const [legendCaption, setLegendCaption] = useState(); + const canvasRef = useCallback(node => { if (node !== null) { setCanvasElem(node); @@ -67,10 +89,10 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp useEffect( () => () => { - const nodeJQ = $(chartElem); - nodeJQ.off('plotselected'); - nodeJQ.off('plothover'); - nodeJQ.off('mouseleave'); + $(chartElem) + .off('plotselected') + .off('plothover') + .off('mouseleave'); }, [chartElem] ); @@ -90,10 +112,6 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp ); }, [seriesList.list]); - // Used to toggle the series, and for displaying values on hover - const [legendValueNumbers, setLegendValueNumbers] = useState(); - const [legendCaption, setLegendCaption] = useState(); - useEffect(() => { if (plot && get(plot.getData(), '[0]._global.legend.showTime', true)) { const caption = $(''); @@ -101,14 +119,15 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp setLegendCaption(caption); const canvasNode = $(canvasElem); - canvasNode.find('div.legend table').append(caption); - setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + // legend has been re-created. Apply focus on legend element when previously set if (focusedSeries || focusedSeries === 0) { - const $legendLabels = canvasNode.find('div.legend table .legendLabel>span'); - $legendLabels.get(focusedSeries).focus(); + canvasNode + .find('div.legend table .legendLabel>span') + .get(focusedSeries) + .focus(); } } }, [plot, focusedSeries, canvasElem]); @@ -121,8 +140,8 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp } setHighlightedSeries(id); - setChart( - chart.map((series: Series, seriesIndex: number) => { + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { const color = seriesIndex === id ? originalColorMap.get(series) // color it like it was @@ -132,7 +151,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp }) ); }, DEBOUNCE_DELAY), - [highlightedSeries, chart, originalColorMap] + [highlightedSeries, originalColorMap] ); const focusSeries = useCallback( @@ -144,28 +163,25 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp [highlightSeries] ); - const toggleSeries = useCallback( - ({ currentTarget }: JQuery.TriggeredEvent) => { - const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); - setChart( - chart.map((series: Series, seriesIndex: number) => { - return seriesIndex === id ? { ...series, _hide: !series._hide } : { ...series }; - }) - ); - }, - [chart] - ); + const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + return seriesIndex === id ? { ...series, _hide: !series._hide } : { ...series }; + }) + ); + }, []); const options = useMemo( () => buildOptions( + interval, getServices().timefilter, - intervalProp, getServices().uiSettings, chartElem && chartElem.clientWidth, seriesList.render.grid ), - [seriesList.render.grid, intervalProp, chartElem] + [seriesList.render.grid, interval, chartElem] ); const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); @@ -202,16 +218,16 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp setHighlightedSeries(null); setFocusedSeries(null); - setChart( - chart.map((series: Series) => { + setChart(chartState => + chartState.map((series: Series) => { return { ...series, color: originalColorMap.get(series) }; // reset the colors }) ); - }, [chart, originalColorMap, highlightedSeries]); + }, [originalColorMap, highlightedSeries]); // Shamelessly borrowed from the flotCrosshairs example const setLegendNumbers = useCallback( - (pos: any) => { + (pos: Position) => { unhighlightSeries(); const axes = plot.getAxes(); @@ -230,9 +246,11 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp const useNearestPoint = series.lines.show && !series.lines.steps; const precision = get(series, '_meta.precision', 2); - if (series._hide) continue; + if (series._hide) { + continue; + } - const currentPoint = series.data.find((point: any, index: number) => { + const currentPoint = series.data.find((point: [number, number], index: number) => { if (index + 1 === series.data.length) { return true; } @@ -272,13 +290,13 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp if (legendCaption) { legendCaption.html(emptyCaption); } - each(legendValueNumbers, (num: any) => { + each(legendValueNumbers, (num: Node) => { $(num).empty(); }); }, [legendCaption, legendValueNumbers]); const plotHoverHandler = useCallback( - (event: any, pos: any) => { + (event: JQuery.TriggeredEvent, pos: Position) => { if (!plot) { return; } @@ -295,7 +313,7 @@ function Panel({ interval: intervalProp, seriesList, renderComplete }: PanelProp clearLegendNumbers(); }, [plot, clearLegendNumbers]); - const plotSelectedHandler = useCallback((event: any, ranges: any) => { + const plotSelectedHandler = useCallback((event: JQuery.TriggeredEvent, ranges: Ranges) => { getServices().timefilter.setTime({ from: moment(ranges.xaxis.from), to: moment(ranges.xaxis.to), diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx index 872479e527ee1..c42778de62112 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -40,7 +40,6 @@ function TimelionVisComponent(props: TimelionVisComponentProp) {
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index bcbbc85e6ba9f..cd439f72a9a96 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -59,8 +59,12 @@ function buildSeriesData(chart: Series[], options: object) { merge(options, series._global, (objVal, srcVal) => { // This is kind of gross, it means that you can't replace a global value with a null // best you can do is an empty string. Deal with it. - if (objVal == null) return srcVal; - if (srcVal == null) return objVal; + if (objVal == null) { + return srcVal; + } + if (srcVal == null) { + return objVal; + } }); } @@ -101,10 +105,15 @@ interface IOptions { backgroundColor: string; position: string; labelBoxBorderColor: string; - labelFormatter(label: string, series: any): string; + labelFormatter(label: string, series: { _id: number }): string; }; } +interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} + const colors = [ '#01A4A4', '#C66', @@ -119,14 +128,14 @@ const colors = [ ]; function buildOptions( - timefilter: TimefilterContract, intervalValue: string, + timefilter: TimefilterContract, uiSettings: IUiSettingsClient, clientWidth = 0, showGrid?: boolean ) { // Get the X-axis tick format - const time = timefilter.getBounds() as any; + const time: TimeRangeBounds = timefilter.getBounds(); const interval = calculateInterval( time.min.valueOf(), time.max.valueOf(), @@ -171,7 +180,7 @@ function buildOptions( backgroundColor: 'rgb(255,255,255,0)', position: 'nw', labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter(label: string, series: any) { + labelFormatter(label: string, series: { _id: number }) { const wrapperSpan = document.createElement('span'); const labelSpan = document.createElement('span'); const numberSpan = document.createElement('span'); @@ -191,9 +200,9 @@ function buildOptions( }; if (options.yaxes) { - options.yaxes.forEach((yaxis: any) => { + options.yaxes.forEach(yaxis => { if (yaxis && yaxis.units) { - const formatters = tickFormatters() as any; + const formatters = tickFormatters(); yaxis.tickFormatter = formatters[yaxis.units.type]; const byteModes = ['bytes', 'bytes/s']; if (byteModes.includes(yaxis.units.type)) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss index e44b6336d33c1..4aa4c2b0e9130 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.scss @@ -1 +1,2 @@ @import './timelion_vis'; +@import './components/chart'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx index aae80d5731e76..4c1b22983517e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx @@ -19,12 +19,12 @@ import React, { ReactElement } from 'react'; -import { Panel } from './components/panel'; +import { Panel, PanelProps } from './components/panel'; -export type IPanelWrapper = (props: any) => ReactElement; +export type IPanelWrapper = (props: PanelProps) => ReactElement; function getTimeChart(): [string, IPanelWrapper] { - return ['timechart', (props: any) => ]; + return ['timechart', (props: PanelProps) => ]; } export { getTimeChart }; From 78f448e44a28cac0971286c91b395227e894d5c0 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 3 Jan 2020 11:49:21 +0300 Subject: [PATCH 33/59] Fix TS --- .../public/components/panel.tsx | 2 +- .../public/helpers/panel_utils.ts | 18 ++++++++++++------ .../public/helpers/tick_formatters.ts | 12 +++++------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index 4eda11dbd10cf..dc36c027ff428 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -187,7 +187,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); useEffect(() => { - if (canvasElem) { + if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { // @ts-ignore setPlot($.plot(canvasElem, compact(updatedSeries), options)); renderComplete(); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index cd439f72a9a96..4734ac944e4d0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -18,7 +18,7 @@ */ import { cloneDeep, defaults, merge } from 'lodash'; -import moment from 'moment-timezone'; +import moment, { Moment } from 'moment-timezone'; import { TimefilterContract } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'kibana/public'; @@ -82,7 +82,13 @@ interface IOptions { ticks: number; tickFormatter(val: number): string; }; - yaxes?: []; + yaxes?: [ + { + units: { type: string }; + tickFormatter: ((val: number) => string) | ((val: number, axis: any) => string); + tickGenerator(axis: any): number[]; + } + ]; selection: { mode: string; color: string; @@ -137,8 +143,8 @@ function buildOptions( // Get the X-axis tick format const time: TimeRangeBounds = timefilter.getBounds(); const interval = calculateInterval( - time.min.valueOf(), - time.max.valueOf(), + time.min && time.min.valueOf(), + time.max && time.max.valueOf(), uiSettings.get('timelion:target_buckets') || 200, intervalValue, uiSettings.get('timelion:min_interval') || '1ms' @@ -186,7 +192,7 @@ function buildOptions( const numberSpan = document.createElement('span'); wrapperSpan.setAttribute('class', 'ngLegendValue'); - wrapperSpan.setAttribute(SERIES_ID_ATTR, series._id); + wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); labelSpan.appendChild(document.createTextNode(label)); numberSpan.setAttribute('class', 'ngLegendValueNumber'); @@ -203,7 +209,7 @@ function buildOptions( options.yaxes.forEach(yaxis => { if (yaxis && yaxis.units) { const formatters = tickFormatters(); - yaxis.tickFormatter = formatters[yaxis.units.type]; + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; const byteModes = ['bytes', 'bytes/s']; if (byteModes.includes(yaxis.units.type)) { yaxis.tickGenerator = generateTicksProvider(); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts index 84da20da45f3c..050be63253252 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -41,7 +41,7 @@ function baseTickFormatter(value: any, axis: any) { } function unitFormatter(divisor: any, units: any) { - return (val: any) => { + return (val: number) => { let index = 0; const isNegative = val < 0; val = Math.abs(val); @@ -55,18 +55,18 @@ function unitFormatter(divisor: any, units: any) { } export function tickFormatters() { - const formatters = { + return { bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: any, axis: any) { + currency(val: number, axis: any) { return val.toLocaleString('en', { style: 'currency', currency: axis.options.units.prefix || 'USD', }); }, - percent(val: any, axis: any) { + percent(val: number, axis: any) { let precision = get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 @@ -78,13 +78,11 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: any, axis: any) { + custom(val: number, axis: any) { const formattedVal = baseTickFormatter(val, axis); const prefix = axis.options.units.prefix; const suffix = axis.options.units.suffix; return prefix + formattedVal + suffix; }, }; - - return formatters; } From e8fbfa78913aec6bfbbe43e64d7b7368d23c3645 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 3 Jan 2020 15:38:12 +0300 Subject: [PATCH 34/59] Refactoring, TS --- .../timelion/public/directives/_index.scss | 2 + .../core_plugins/vis_type_timelion/index.ts | 2 + .../public/components/panel.tsx | 4 +- .../public/components/timelion_vis.tsx | 4 +- .../public/helpers/panel_utils.ts | 129 +++++++++--------- .../public/helpers/tick_formatters.ts | 18 +-- .../public/helpers/tick_generator.ts | 16 +-- .../public/helpers/xaxis_formatter.ts | 9 +- .../public/kibana_services.ts | 1 + .../vis_type_timelion/public/legacy.ts | 3 +- .../public/legacy_imports.ts | 10 +- .../vis_type_timelion/public/plugin.ts | 2 +- .../public/timelion_vis_fn.ts | 2 +- 13 files changed, 104 insertions(+), 98 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/directives/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index cd46a1a0a369e..1b768e6d8a07a 100644 --- a/src/legacy/core_plugins/timelion/public/directives/_index.scss +++ b/src/legacy/core_plugins/timelion/public/directives/_index.scss @@ -3,3 +3,5 @@ @import './timelion_expression_suggestions/index'; @import './timelion_help/index'; @import './timelion_interval/index'; + +@import '../../../vis_type_timelion/public/components/chart' diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts index 97e8d178fde51..4664bebb4f38a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -18,6 +18,7 @@ */ import { resolve } from 'path'; +import { Legacy } from 'kibana'; import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; @@ -31,6 +32,7 @@ const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: Legac hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars: server => ({}), }, + init: (server: Legacy.Server) => ({}), config(Joi: any) { return Joi.object({ enabled: Joi.boolean().default(true), diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index dc36c027ff428..561a3f8995174 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -266,7 +266,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { if (y != null && legendValueNumbers) { let label = y.toFixed(precision); if (series.yaxis.tickFormatter) { - label = series.yaxis.tickFormatter(label, series.yaxis); + label = series.yaxis.tickFormatter(Number(label), series.yaxis); } legendValueNumbers.eq(i).text(`(${label})`); } else { @@ -335,7 +335,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { } }, [chartElem, plotSelectedHandler, plotHoverHandler, mouseLeaveHandler]); - const title: string = last(compact(map(chart, '_title'))) || ''; + const title: string = useMemo(() => last(compact(map(chart, '_title'))) || '', [chart]); return (
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx index c42778de62112..7356d4a7a95dd 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -20,14 +20,14 @@ import React from 'react'; import { IUiSettingsClient } from 'kibana/public'; -import { Vis } from 'ui/vis'; +import { Vis } from '../legacy_imports'; import { ChartComponent } from './chart'; import { VisParams } from '../timelion_vis_fn'; import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; interface TimelionVisComponentProp { config: IUiSettingsClient; - renderComplete: () => void; + renderComplete(): void; updateStatus: object; vis: Vis; visData: TimelionSuccessResponse; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index 4734ac944e4d0..f2971e00ea0f0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -30,7 +30,73 @@ import { xaxisFormatterProvider } from './xaxis_formatter'; import { generateTicksProvider } from './tick_generator'; import { Series } from './timelion_request_handler'; -function buildSeriesData(chart: Series[], options: object) { +export interface Axis { + delta?: number; + max?: number; + min?: number; + mode: string; + options?: { + units: { prefix: string; suffix: string }; + }; + tickSize?: number; + ticks: number; + tickLength: number; + timezone: string; + tickDecimals?: number; + tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); + tickGenerator?(axis: Axis): number[]; + units?: { type: string }; +} +interface IOptions { + colors: string[]; + crosshair: { + color: string; + lineWidth: number; + mode: string; + }; + grid: { + autoHighlight: boolean; + borderColor: string | null; + borderWidth: number; + hoverable: boolean; + margin: number; + show?: boolean; + }; + legend: { + backgroundColor: string; + labelBoxBorderColor: string; + labelFormatter(label: string, series: { _id: number }): string; + position: string; + }; + selection: { + color: string; + mode: string; + }; + xaxis: Axis; + yaxes?: Axis[]; +} + +interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} + +const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; + +const SERIES_ID_ATTR = 'data-series-id'; + +function buildSeriesData(chart: Series[], options: IOptions) { return chart.map((series: Series, seriesIndex: number) => { const newSeries: Series = cloneDeep( defaults(series, { @@ -72,67 +138,6 @@ function buildSeriesData(chart: Series[], options: object) { }); } -const SERIES_ID_ATTR = 'data-series-id'; - -interface IOptions { - xaxis: { - mode: string; - tickLength: number; - timezone: string; - ticks: number; - tickFormatter(val: number): string; - }; - yaxes?: [ - { - units: { type: string }; - tickFormatter: ((val: number) => string) | ((val: number, axis: any) => string); - tickGenerator(axis: any): number[]; - } - ]; - selection: { - mode: string; - color: string; - }; - crosshair: { - mode: string; - color: string; - lineWidth: number; - }; - colors: string[]; - grid: { - show?: boolean; - borderWidth: number; - borderColor: string | null; - margin: number; - hoverable: boolean; - autoHighlight: boolean; - }; - legend: { - backgroundColor: string; - position: string; - labelBoxBorderColor: string; - labelFormatter(label: string, series: { _id: number }): string; - }; -} - -interface TimeRangeBounds { - min: Moment | undefined; - max: Moment | undefined; -} - -const colors = [ - '#01A4A4', - '#C66', - '#D0D102', - '#616161', - '#00A1CB', - '#32742C', - '#F18D05', - '#113F8C', - '#61AE24', - '#D70060', -]; - function buildOptions( intervalValue: string, timefilter: TimefilterContract, diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts index 050be63253252..c80f9c3ed5f4b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -19,7 +19,9 @@ import { get } from 'lodash'; -function baseTickFormatter(value: any, axis: any) { +import { Axis } from './panel_utils'; + +function baseTickFormatter(value: number, axis: Axis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -40,7 +42,7 @@ function baseTickFormatter(value: any, axis: any) { return formatted; } -function unitFormatter(divisor: any, units: any) { +function unitFormatter(divisor: number, units: string[]) { return (val: number) => { let index = 0; const isNegative = val < 0; @@ -60,13 +62,13 @@ export function tickFormatters() { 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: number, axis: any) { + currency(val: number, axis: Axis) { return val.toLocaleString('en', { style: 'currency', - currency: axis.options.units.prefix || 'USD', + currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: number, axis: any) { + percent(val: number, axis: Axis) { let precision = get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 @@ -78,10 +80,10 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: number, axis: any) { + custom(val: number, axis: Axis) { const formattedVal = baseTickFormatter(val, axis); - const prefix = axis.options.units.prefix; - const suffix = axis.options.units.suffix; + const prefix = axis && axis.options && axis.options.units.prefix; + const suffix = axis && axis.options && axis.options.units.suffix; return prefix + formattedVal + suffix; }, }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts index f7d696a0316db..6321ad01418ac 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts @@ -17,15 +17,17 @@ * under the License. */ +import { Axis } from './panel_utils'; + export function generateTicksProvider() { - function floorInBase(n: any, base: any) { + function floorInBase(n: number, base: number) { return base * Math.floor(n / base); } - function generateTicks(axis: any) { + function generateTicks(axis: Axis) { const returnTicks = []; let tickSize = 2; - let delta = axis.delta; + let delta = axis.delta || 0; let steps = 0; let tickVal; let tickCount = 0; @@ -46,16 +48,14 @@ export function generateTicksProvider() { axis.tickSize = tickSize * Math.pow(1024, steps); // Calculate the new ticks - const tickMin = floorInBase(axis.min, axis.tickSize); + const tickMin = floorInBase(axis.min || 0, axis.tickSize); do { tickVal = tickMin + tickCount++ * axis.tickSize; returnTicks.push(tickVal); - } while (tickVal < axis.max); + } while (tickVal < (axis.max || 0)); return returnTicks; } - return function(axis: any) { - return generateTicks(axis); - }; + return (axis: Axis) => generateTicks(axis); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts index db3408dae33db..5350b1cb26957 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts @@ -20,12 +20,13 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; -export function xaxisFormatterProvider(config: any) { +export function xaxisFormatterProvider(config: IUiSettingsClient) { function getFormat(esInterval: any) { const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/); - if (parts == null || parts[1] == null || parts[2] == null) { + if (parts === null || parts[1] === null || parts[2] === null) { throw new Error( i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { defaultMessage: 'Unknown interval', @@ -48,7 +49,5 @@ export function xaxisFormatterProvider(config: any) { return config.get('dateFormat'); } - return function(esInterval: any) { - return getFormat(esInterval); - }; + return (esInterval: any) => getFormat(esInterval); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts index 90ddbed0e1760..6a916884334a5 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts @@ -27,6 +27,7 @@ export interface TimelionKibanaServices { uiSettings: IUiSettingsClient; timefilter: TimefilterContract; } + let services: TimelionKibanaServices | null = null; export function setServices(newServices: TimelionKibanaServices) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts index 4b2e03a8e35bc..77739222f10d0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -18,7 +18,8 @@ */ import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; + +import { npSetup, npStart } from './legacy_imports'; import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; import { TimelionVisSetupDependencies } from './plugin'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts index 8ec629f4982bf..fd4882bb15414 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts @@ -17,16 +17,10 @@ * under the License. */ -export { npStart } from 'ui/new_platform'; +export { npSetup, npStart } from 'ui/new_platform'; -// @ts-ignore -export { watchMultiDecorator } from 'ui/directives/watch_multi/watch_multi'; - -// @ts-ignore -export { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; -export { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore export { DefaultEditorSize } from 'ui/vis/editor_size'; // @ts-ignore export { timezoneProvider } from 'ui/vis/lib/timezone'; -export { VisParams } from 'ui/vis'; +export { VisParams, Vis } from 'ui/vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 1fa6c6ed17c90..c88a0d1f7278b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -40,7 +40,7 @@ type TimelionVisCoreSetup = CoreSetup; export interface TimelionVisDependencies { uiSettings: IUiSettingsClient; http: HttpSetup; - timelionPanels: Map; + timelionPanels: Map; timefilter: TimefilterContract; } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 7006b3f1e15e1..f3ba14e4cdc95 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -27,7 +27,7 @@ const name = 'timelion_vis'; interface Arguments { expression: string; - interval: any; + interval: string | null; } export interface RenderValue { From 5c5c49eab102cd045b97967f777cf20f8f5d4fbd Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 3 Jan 2020 15:46:02 +0300 Subject: [PATCH 35/59] Import eui variables --- .../vis_type_timelion/public/components/_chart.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss index 39713cd05ab37..91a214ab5ba7d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss @@ -1,3 +1,5 @@ +@import '@elastic/eui/src/components/header/variables'; + .timChart { height: 100%; width: 100%; From 59b41498454cbe8313dc8ad1cd8a7ba29ad3d5ad Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 3 Jan 2020 15:50:51 +0300 Subject: [PATCH 36/59] Import styling constants --- .../vis_type_timelion/public/components/_chart.scss | 2 -- src/legacy/core_plugins/vis_type_timelion/public/index.scss | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss index 91a214ab5ba7d..39713cd05ab37 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss @@ -1,5 +1,3 @@ -@import '@elastic/eui/src/components/header/variables'; - .timChart { height: 100%; width: 100%; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss index 4aa4c2b0e9130..45579b0f0c3e7 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.scss @@ -1,2 +1,4 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + @import './timelion_vis'; @import './components/chart'; From 8c83862e1b7d9bf5b42d964bac9de5ba11b71d43 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 4 Jan 2020 12:25:08 +0300 Subject: [PATCH 37/59] Move react components to vis_type_timelion --- .../timelion_expression_input_helpers.js | 7 ++++-- .../directives/timelion_expression_input.js | 2 +- .../public/components/index.ts | 25 +++++++++++++++++++ .../components/timelion_expression_input.tsx | 4 +-- .../timelion_expression_input_helpers.ts | 4 +-- .../public/components/timelion_interval.tsx | 0 .../public/helpers}/arg_value_suggestions.ts | 2 +- .../public/helpers}/plugin_services.ts | 0 .../vis_type_timelion/public/plugin.ts | 5 +--- .../vis_type_timelion/public/timechart.tsx | 2 +- .../public/timelion_options.tsx | 2 +- .../public/timelion_vis_type.tsx | 2 +- 12 files changed, 40 insertions(+), 15 deletions(-) create mode 100644 src/legacy/core_plugins/vis_type_timelion/public/components/index.ts rename src/legacy/core_plugins/{timelion => vis_type_timelion}/public/components/timelion_expression_input.tsx (95%) rename src/legacy/core_plugins/{timelion => vis_type_timelion}/public/components/timelion_expression_input_helpers.ts (98%) rename src/legacy/core_plugins/{timelion => vis_type_timelion}/public/components/timelion_interval.tsx (100%) rename src/legacy/core_plugins/{timelion/public/services => vis_type_timelion/public/helpers}/arg_value_suggestions.ts (98%) rename src/legacy/core_plugins/{timelion/public/services => vis_type_timelion/public/helpers}/plugin_services.ts (100%) diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index 231330b898edb..0706f745cc30b 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -21,8 +21,11 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; import grammar from 'raw-loader!../../chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; -import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services'; +import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; +import { + setIndexPatterns, + setSavedObjectsClient, +} from '../../../../vis_type_timelion/public/helpers/plugin_services'; describe('Timelion expression suggestions', () => { setIndexPatterns({}); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 449c0489fea25..c748346b2efcb 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -52,7 +52,7 @@ import { insertAtLocation, } from './timelion_expression_input_helpers'; import { comboBoxKeyCodes } from '@elastic/eui'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts new file mode 100644 index 0000000000000..e3955174765d6 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './chart'; +export * from './panel'; +export * from './timelion_expression_input'; +export * from './timelion_expression_input_helpers'; +export * from './timelion_interval'; +export * from './timelion_vis'; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx similarity index 95% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index 8331030eee982..0441c6f1a33d0 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -24,8 +24,8 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; -import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../../timelion/common/types'; +import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index fc90c276eeca2..3b306903d81dc 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -25,8 +25,8 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import grammar from 'raw-loader!../chain.peg'; import { i18n } from '@kbn/i18n'; -import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../../timelion/common/types'; +import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx similarity index 100% rename from src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts similarity index 98% rename from src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts index 8d133de51f6d9..5ed35f68a5acf 100644 --- a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { TimelionFunctionArgs } from '../../common/types'; +import { TimelionFunctionArgs } from '../../../timelion/common/types'; import { getIndexPatterns, getSavedObjectsClient } from './plugin_services'; export interface Location { diff --git a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/plugin_services.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 3b38ad8fdb279..f06133696f92e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -34,10 +34,7 @@ import { setServices } from './kibana_services'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; -import { - setIndexPatterns, - setSavedObjectsClient, -} from '../../timelion/public/services/plugin_services'; +import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; type TimelionVisCoreSetup = CoreSetup; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx index 4c1b22983517e..5e56f8c0730f2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx @@ -19,7 +19,7 @@ import React, { ReactElement } from 'react'; -import { Panel, PanelProps } from './components/panel'; +import { Panel, PanelProps } from './components'; export type IPanelWrapper = (props: PanelProps) => ReactElement; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 789fbd7036706..455293aab9e1d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { VisOptionsProps } from 'ui/vis/editors/default'; import { VisParams } from './timelion_vis_fn'; -import { TimelionInterval, TimelionExpressionInput } from '../../timelion/public/components'; +import { TimelionInterval, TimelionExpressionInput } from './components'; function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 2e53a636971e4..9d536e94eb9b1 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -24,7 +24,7 @@ import { VisOptionsProps } from 'ui/vis/editors/default'; import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; import { DefaultEditorSize } from './legacy_imports'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; -import { TimelionVisComponent } from './components/timelion_vis'; +import { TimelionVisComponent } from './components'; import { TimelionOptions } from './timelion_options'; import { VisParams } from './timelion_vis_fn'; import { TimelionVisDependencies } from './plugin'; From c055ebcb210019da53a1ec483f8516fa23ac11ef Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 4 Jan 2020 17:21:22 +0300 Subject: [PATCH 38/59] Fix TS --- .../timelion/public/components/index.ts | 21 ------------------- .../core_plugins/timelion/public/legacy.ts | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/components/index.ts diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/timelion/public/components/index.ts deleted file mode 100644 index 8d7d32a3ba262..0000000000000 --- a/src/legacy/core_plugins/timelion/public/components/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './timelion_expression_input'; -export * from './timelion_interval'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 7980291e2d462..63030fcbce387 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -32,4 +32,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +export const start = pluginInstance.start(npStart.core); From 3b067481363a642ea593f0b4c1db6834d000a69c Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 4 Jan 2020 17:22:06 +0300 Subject: [PATCH 39/59] Move ui imports to legacy_imports.ts --- .../vis_type_timelion/public/components/timelion_interval.tsx | 2 +- .../core_plugins/vis_type_timelion/public/legacy_imports.ts | 3 +++ src/legacy/core_plugins/vis_type_timelion/public/plugin.ts | 3 ++- .../core_plugins/vis_type_timelion/public/timelion_options.tsx | 2 +- .../vis_type_timelion/public/timelion_vis_type.tsx | 3 +-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx index 6294e51e54788..4bfa5d424ed85 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -21,8 +21,8 @@ import React, { useMemo, useCallback } from 'react'; import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; import { isValidEsInterval } from '../../../../core_plugins/data/common'; +import { useValidation } from '../legacy_imports'; const intervalOptions = [ { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts index fd4882bb15414..8d1156862d27e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts @@ -18,9 +18,12 @@ */ export { npSetup, npStart } from 'ui/new_platform'; +export { PluginsStart } from 'ui/new_platform/new_platform'; // @ts-ignore export { DefaultEditorSize } from 'ui/vis/editor_size'; // @ts-ignore export { timezoneProvider } from 'ui/vis/lib/timezone'; export { VisParams, Vis } from 'ui/vis'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +export { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index f06133696f92e..217a57d0d480c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -25,9 +25,10 @@ import { IUiSettingsClient, HttpSetup, } from 'kibana/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; + +import { PluginsStart } from './legacy_imports'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimeChart, IPanelWrapper } from './timechart'; import { setServices } from './kibana_services'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 455293aab9e1d..be6829a76ac58 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,7 +20,7 @@ import React, { useCallback } from 'react'; import { EuiPanel } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from './legacy_imports'; import { VisParams } from './timelion_vis_fn'; import { TimelionInterval, TimelionExpressionInput } from './components'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 9d536e94eb9b1..4be0f17c0dd10 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -20,9 +20,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'ui/vis/editors/default'; import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; -import { DefaultEditorSize } from './legacy_imports'; +import { DefaultEditorSize, VisOptionsProps } from './legacy_imports'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; import { TimelionVisComponent } from './components'; import { TimelionOptions } from './timelion_options'; From 55aeab2cd74d099c9c4c2bd08379f9174cd6652b Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Sat, 4 Jan 2020 17:46:38 +0300 Subject: [PATCH 40/59] Move chain.peg to vis_type_timelion --- .../__tests__/timelion_expression_input_helpers.js | 2 +- .../timelion/public/directives/timelion_expression_input.js | 2 +- .../core_plugins/timelion/server/handlers/lib/parse_sheet.js | 5 ++++- .../{timelion => vis_type_timelion}/public/chain.peg | 0 4 files changed, 6 insertions(+), 3 deletions(-) rename src/legacy/core_plugins/{timelion => vis_type_timelion}/public/chain.peg (100%) diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index 0706f745cc30b..ea2d44bcaefe0 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../../chain.peg'; +import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; import { diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index c748346b2efcb..def1db260dba7 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -43,7 +43,7 @@ import _ from 'lodash'; import $ from 'jquery'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../chain.peg'; +import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; import timelionExpressionInputTemplate from './timelion_expression_input.html'; import { SUGGESTION_TYPE, diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js index 74ef76d1a50cd..4957d3cb78b85 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js @@ -21,7 +21,10 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs'; import path from 'path'; import _ from 'lodash'; -const grammar = fs.readFileSync(path.resolve(__dirname, '../../../public/chain.peg'), 'utf8'); +const grammar = fs.readFileSync( + path.resolve(__dirname, '../../../../vis_type_timelion/public/chain.peg'), + 'utf8' +); import PEG from 'pegjs'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/chain.peg b/src/legacy/core_plugins/vis_type_timelion/public/chain.peg similarity index 100% rename from src/legacy/core_plugins/timelion/public/chain.peg rename to src/legacy/core_plugins/vis_type_timelion/public/chain.peg From c06cf84ff237cf161fad94d94f8862f2decffa1a Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 8 Jan 2020 11:23:54 +0300 Subject: [PATCH 41/59] Fix path --- .../timelion/public/directives/timelion_expression_input.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index def1db260dba7..1fec243a277f8 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -43,7 +43,7 @@ import _ from 'lodash'; import $ from 'jquery'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; +import grammar from 'raw-loader!../../../vis_type_timelion/public/chain.peg'; import timelionExpressionInputTemplate from './timelion_expression_input.html'; import { SUGGESTION_TYPE, From ae1f360e64aa62caf16bf151d7de95438398abc3 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 9 Jan 2020 12:44:02 +0300 Subject: [PATCH 42/59] Use KibanaContext instead kibana_services.ts --- .../public/components/chart.tsx | 4 +- .../public/components/panel.tsx | 37 +++++++++++++------ .../public/components/timelion_vis.tsx | 2 +- .../helpers/timelion_request_handler.ts | 9 +++-- .../vis_type_timelion/public/plugin.ts | 9 ++--- .../{kibana_services.ts => services.ts} | 23 ++++-------- .../public/timelion_vis_fn.ts | 12 +++--- .../public/timelion_vis_type.tsx | 10 +++-- 8 files changed, 56 insertions(+), 50 deletions(-) rename src/legacy/core_plugins/vis_type_timelion/public/{kibana_services.ts => services.ts} (58%) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index de4c1008f8a92..d01e59682254c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiFormErrorText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getServices } from '../kibana_services'; +import { getPanels } from '../services'; import { Sheet } from '../helpers/timelion_request_handler'; import { PanelProps } from './panel'; @@ -41,7 +41,7 @@ function ChartComponent({ seriesList, interval, renderComplete }: ChartComponent type: 'timechart', }; - const panelSchema = getServices().timelionPanels.get(panelScope.seriesList.render.type); + const panelSchema = getPanels().get(panelScope.seriesList.render.type); if (!panelSchema) { return ( diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index 561a3f8995174..6cfa0e7633b24 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -22,12 +22,14 @@ import $ from 'jquery'; import moment from 'moment-timezone'; import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; +import { CoreStart } from 'src/core/public'; +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; +import { useKibana } from '../../../../../plugins/kibana_react/public'; import '../flot'; - // @ts-ignore import { DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; -import { getServices } from '../kibana_services'; import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from '../helpers/panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; @@ -56,11 +58,17 @@ interface Ranges { yaxis: Range; } +interface ITimelionVisPluginServices extends Partial { + timefilter: TimefilterContract; + uiSettings: IUiSettingsClient; +} + const DEBOUNCE_DELAY = 50; // ensure legend is the same height with or without a caption so legend items do not move around const emptyCaption = '
'; function Panel({ interval, seriesList, renderComplete }: PanelProps) { + const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const [canvasElem, setCanvasElem] = useState(); const [chartElem, setChartElem] = useState(); @@ -176,12 +184,12 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { () => buildOptions( interval, - getServices().timefilter, - getServices().uiSettings, + kibana.services.timefilter, + kibana.services.uiSettings, chartElem && chartElem.clientWidth, seriesList.render.grid ), - [seriesList.render.grid, interval, chartElem] + [seriesList.render.grid, interval, chartElem, kibana.services] ); const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); @@ -209,7 +217,9 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { renderComplete, ]); - moment.tz.setDefault(getServices().uiSettings.get('dateFormat:tz')); + useEffect(() => { + moment.tz.setDefault(kibana.services.uiSettings.get('dateFormat:tz')); + }, [kibana.services.uiSettings]); const unhighlightSeries = useCallback(() => { if (highlightedSeries === null) { @@ -313,12 +323,15 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { clearLegendNumbers(); }, [plot, clearLegendNumbers]); - const plotSelectedHandler = useCallback((event: JQuery.TriggeredEvent, ranges: Ranges) => { - getServices().timefilter.setTime({ - from: moment(ranges.xaxis.from), - to: moment(ranges.xaxis.to), - }); - }, []); + const plotSelectedHandler = useCallback( + (event: JQuery.TriggeredEvent, ranges: Ranges) => { + kibana.services.timefilter.setTime({ + from: moment(ranges.xaxis.from), + to: moment(ranges.xaxis.to), + }); + }, + [kibana.services.timefilter] + ); useEffect(() => { if (chartElem) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx index 7356d4a7a95dd..ae55e11380b78 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -25,7 +25,7 @@ import { ChartComponent } from './chart'; import { VisParams } from '../timelion_vis_fn'; import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; -interface TimelionVisComponentProp { +export interface TimelionVisComponentProp { config: IUiSettingsClient; renderComplete(): void; updateStatus: object; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 1acb191c4a95d..964b3e1b4270b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -21,7 +21,7 @@ import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; import { i18n } from '@kbn/i18n'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; import { timezoneProvider, VisParams } from '../legacy_imports'; -import { getServices } from '../kibana_services'; +import { TimelionVisDependencies } from '../plugin'; interface Stats { cacheCount: number; @@ -61,8 +61,11 @@ export interface TimelionSuccessResponse { type: KIBANA_CONTEXT_NAME; } -export function getTimelionRequestHandler() { - const { uiSettings, http, timefilter } = getServices(); +export function getTimelionRequestHandler({ + uiSettings, + http, + timefilter, +}: TimelionVisDependencies) { const timezone = timezoneProvider(uiSettings)(); return async function({ diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index 217a57d0d480c..a4f830567c487 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -31,7 +31,7 @@ import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/publ import { PluginsStart } from './legacy_imports'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimeChart, IPanelWrapper } from './timechart'; -import { setServices } from './kibana_services'; +import { setPanels } from './services'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; @@ -43,7 +43,6 @@ type TimelionVisCoreSetup = CoreSetup; export interface TimelionVisDependencies { uiSettings: IUiSettingsClient; http: HttpSetup; - timelionPanels: Map; timefilter: TimefilterContract; } @@ -67,14 +66,11 @@ export class TimelionVisPlugin implements Plugin { const dependencies: TimelionVisDependencies = { uiSettings: core.uiSettings, http: core.http, - timelionPanels, timefilter: data.query.timefilter.timefilter, }; - setServices(dependencies); - this.registerPanels(timelionPanels); - expressions.registerFunction(getTimelionVisualizationConfig); + expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); } @@ -82,6 +78,7 @@ export class TimelionVisPlugin implements Plugin { const [name, timeChartPanel] = getTimeChart(); timelionPanels.set(name, timeChartPanel); + setPanels(timelionPanels); } public start(core: CoreStart, plugins: PluginsStart) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/services.ts similarity index 58% rename from src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts rename to src/legacy/core_plugins/vis_type_timelion/public/services.ts index 6a916884334a5..54cc437a39ee2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/kibana_services.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/services.ts @@ -17,28 +17,19 @@ * under the License. */ -import { IUiSettingsClient, HttpSetup } from 'kibana/public'; -import { TimefilterContract } from 'src/plugins/data/public'; import { IPanelWrapper } from './timechart'; -export interface TimelionKibanaServices { - http: HttpSetup; - timelionPanels: Map; - uiSettings: IUiSettingsClient; - timefilter: TimefilterContract; -} - -let services: TimelionKibanaServices | null = null; +let timelionPanels: Map | null = null; -export function setServices(newServices: TimelionKibanaServices) { - services = newServices; +export function setPanels(panels: Map) { + timelionPanels = panels; } -export function getServices() { - if (!services) { +export function getPanels() { + if (!timelionPanels) { throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the timelion vis?' + 'Timelion panels not set - are you trying to import them from outside of the timelion vis?' ); } - return services; + return timelionPanels; } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 8ad2347bd0a02..f0bc119f6c165 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -22,6 +22,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; import { TIMELION_VIS_NAME } from './timelion_vis_type'; +import { TimelionVisDependencies } from './plugin'; const name = 'timelion_vis'; @@ -40,12 +41,9 @@ type Context = KibanaContext | null; export type VisParams = Arguments; type Return = Promise>; -export const getTimelionVisualizationConfig = (): ExpressionFunction< - typeof name, - Context, - Arguments, - Return -> => ({ +export const getTimelionVisualizationConfig = ( + deps: TimelionVisDependencies +): ExpressionFunction => ({ name, type: 'render', context: { @@ -68,7 +66,7 @@ export const getTimelionVisualizationConfig = (): ExpressionFunction< }, }, async fn(context, args) { - const timelionRequestHandler = getTimelionRequestHandler(); + const timelionRequestHandler = getTimelionRequestHandler(deps); const visParams = { expression: args.expression, interval: args.interval }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 4be0f17c0dd10..51540eea0223c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; import { DefaultEditorSize, VisOptionsProps } from './legacy_imports'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; -import { TimelionVisComponent } from './components'; +import { TimelionVisComponent, TimelionVisComponentProp } from './components'; import { TimelionOptions } from './timelion_options'; import { VisParams } from './timelion_vis_fn'; import { TimelionVisDependencies } from './plugin'; @@ -32,7 +32,7 @@ export const TIMELION_VIS_NAME = 'timelion'; export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) { const { http, uiSettings } = dependencies; - const timelionRequestHandler = getTimelionRequestHandler(); + const timelionRequestHandler = getTimelionRequestHandler(dependencies); // return the visType object, which kibana will use to display and configure new // Vis object of this type. @@ -48,7 +48,11 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) expression: '.es(*)', interval: 'auto', }, - component: TimelionVisComponent, + component: (props: TimelionVisComponentProp) => ( + + + + ), }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( From 7ef81204032f5dc5ef387c1368ae11f7f984922b Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 9 Jan 2020 13:00:32 +0300 Subject: [PATCH 43/59] Refactoring --- .../timelion/public/components/index.ts | 21 -- .../components/timelion_expression_input.tsx | 146 --------- .../timelion_expression_input_helpers.ts | 291 ------------------ .../public/components/timelion_interval.tsx | 144 --------- .../core_plugins/timelion/public/index.scss | 1 - .../core_plugins/timelion/public/legacy.ts | 2 +- .../core_plugins/timelion/public/plugin.ts | 9 +- .../public/components/_index.scss | 1 + .../_timelion_expression_input.scss | 0 .../components/timelion_expression_input.tsx | 4 +- .../vis_type_timelion/public/index.scss | 2 +- .../public/timelion_vis_fn.ts | 6 +- 12 files changed, 9 insertions(+), 618 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/components/index.ts delete mode 100644 src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx delete mode 100644 src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts delete mode 100644 src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx rename src/legacy/core_plugins/{timelion => vis_type_timelion}/public/components/_index.scss (67%) rename src/legacy/core_plugins/{timelion => vis_type_timelion}/public/components/_timelion_expression_input.scss (100%) diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/timelion/public/components/index.ts deleted file mode 100644 index 8d7d32a3ba262..0000000000000 --- a/src/legacy/core_plugins/timelion/public/components/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './timelion_expression_input'; -export * from './timelion_interval'; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx deleted file mode 100644 index c937fda54a0e1..0000000000000 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { useEffect, useCallback, useRef, useMemo } from 'react'; -import { EuiFormLabel } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; - -import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; -import { suggest, getSuggestion } from './timelion_expression_input_helpers'; -import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; - -const LANGUAGE_ID = 'timelion_expression'; -monacoEditor.languages.register({ id: LANGUAGE_ID }); - -interface TimelionExpressionInputProps { - value: string; - setValue(value: string): void; -} - -function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputProps) { - const functionList = useRef([]); - const kibana = useKibana(); - const argValueSuggestions = useMemo(getArgValueSuggestions, []); - - const provideCompletionItems = useCallback( - async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { - const text = model.getValue(); - const wordUntil = model.getWordUntilPosition(position); - const wordRange = new monacoEditor.Range( - position.lineNumber, - wordUntil.startColumn, - position.lineNumber, - wordUntil.endColumn - ); - - const suggestions = await suggest( - text, - functionList.current, - // it's important to offset the cursor position on 1 point left - // because of PEG parser starts the line with 0, but monaco with 1 - position.column - 1, - argValueSuggestions - ); - - return { - suggestions: suggestions - ? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => - getSuggestion(s, suggestions.type, wordRange) - ) - : [], - }; - }, - [argValueSuggestions] - ); - - const provideHover = useCallback( - async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { - const suggestions = await suggest( - model.getValue(), - functionList.current, - // it's important to offset the cursor position on 1 point left - // because of PEG parser starts the line with 0, but monaco with 1 - position.column - 1, - argValueSuggestions - ); - - return { - contents: suggestions - ? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => ({ - value: s.help, - })) - : [], - }; - }, - [argValueSuggestions] - ); - - useEffect(() => { - if (kibana.services.http) { - kibana.services.http.get('../api/timelion/functions').then(data => { - functionList.current = data; - }); - } - }, [kibana.services.http]); - - return ( -
- - - -
- -
-
- ); -} - -export { TimelionExpressionInput }; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts deleted file mode 100644 index 7d4aa391045e7..0000000000000 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get, startsWith } from 'lodash'; -import PEG from 'pegjs'; -import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; - -// @ts-ignore -import grammar from 'raw-loader!../chain.peg'; - -import { i18n } from '@kbn/i18n'; -import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { - ArgValueSuggestions, - FunctionArg, - Location, -} from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; - -const Parser = PEG.generate(grammar); - -export enum SUGGESTION_TYPE { - ARGUMENTS = 'arguments', - ARGUMENT_VALUE = 'argument_value', - FUNCTIONS = 'functions', -} - -function inLocation(cursorPosition: number, location: Location) { - return cursorPosition >= location.min && cursorPosition <= location.max; -} - -function getArgumentsHelp( - functionHelp: ITimelionFunction | undefined, - functionArgs: FunctionArg[] = [] -) { - if (!functionHelp) { - return []; - } - - // Do not provide 'inputSeries' as argument suggestion for chainable functions - const argsHelp = functionHelp.chainable ? functionHelp.args.slice(1) : functionHelp.args.slice(0); - - // ignore arguments that are already provided in function declaration - const functionArgNames = functionArgs.map(arg => arg.name); - return argsHelp.filter(arg => !functionArgNames.includes(arg.name)); -} - -async function extractSuggestionsFromParsedResult( - result: ReturnType, - cursorPosition: number, - functionList: ITimelionFunction[], - argValueSuggestions: ArgValueSuggestions -) { - const activeFunc = result.functions.find(({ location }: { location: Location }) => - inLocation(cursorPosition, location) - ); - - if (!activeFunc) { - return; - } - - const functionHelp = functionList.find(({ name }) => name === activeFunc.function); - - if (!functionHelp) { - return; - } - - // return function suggestion when cursor is outside of parentheses - // location range includes '.', function name, and '('. - const openParen = activeFunc.location.min + activeFunc.function.length + 2; - if (cursorPosition < openParen) { - return { list: [functionHelp], type: SUGGESTION_TYPE.FUNCTIONS }; - } - - // return argument value suggestions when cursor is inside argument value - const activeArg = activeFunc.arguments.find((argument: FunctionArg) => { - return inLocation(cursorPosition, argument.location); - }); - if ( - activeArg && - activeArg.type === 'namedArg' && - inLocation(cursorPosition, activeArg.value.location) - ) { - const { function: functionName, arguments: functionArgs } = activeFunc; - - const { - name: argName, - value: { text: partialInput }, - } = activeArg; - - let valueSuggestions; - if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { - valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( - functionName, - argName, - functionArgs, - partialInput - ); - } else { - const { suggestions: staticSuggestions } = - functionHelp.args.find(arg => arg.name === activeArg.name) || {}; - valueSuggestions = argValueSuggestions.getStaticSuggestionsForInput( - partialInput, - staticSuggestions - ); - } - return { - list: valueSuggestions, - type: SUGGESTION_TYPE.ARGUMENT_VALUE, - }; - } - - // return argument suggestions - const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments); - const argumentSuggestions = argsHelp.filter(arg => { - if (get(activeArg, 'type') === 'namedArg') { - return startsWith(arg.name, activeArg.name); - } else if (activeArg) { - return startsWith(arg.name, activeArg.text); - } - return true; - }); - return { list: argumentSuggestions, type: SUGGESTION_TYPE.ARGUMENTS }; -} - -export async function suggest( - expression: string, - functionList: ITimelionFunction[], - cursorPosition: number, - argValueSuggestions: ArgValueSuggestions -) { - try { - const result = await Parser.parse(expression); - - return await extractSuggestionsFromParsedResult( - result, - cursorPosition, - functionList, - argValueSuggestions - ); - } catch (err) { - let message: any; - try { - // The grammar will throw an error containing a message if the expression is formatted - // correctly and is prepared to accept suggestions. If the expression is not formatted - // correctly the grammar will just throw a regular PEG SyntaxError, and this JSON.parse - // attempt will throw an error. - message = JSON.parse(err.message); - } catch (e) { - // The expression isn't correctly formatted, so JSON.parse threw an error. - return; - } - - switch (message.type) { - case 'incompleteFunction': { - let list; - if (message.function) { - // The user has start typing a function name, so we'll filter the list down to only - // possible matches. - list = functionList.filter(func => startsWith(func.name, message.function)); - } else { - // The user hasn't typed anything yet, so we'll just return the entire list. - list = functionList; - } - return { list, type: SUGGESTION_TYPE.FUNCTIONS }; - } - case 'incompleteArgument': { - const { currentFunction: functionName, currentArgs: functionArgs } = message; - const functionHelp = functionList.find(func => func.name === functionName); - return { - list: getArgumentsHelp(functionHelp, functionArgs), - type: SUGGESTION_TYPE.ARGUMENTS, - }; - } - case 'incompleteArgumentValue': { - const { name: argName, currentFunction: functionName, currentArgs: functionArgs } = message; - let valueSuggestions = []; - if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { - valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( - functionName, - argName, - functionArgs - ); - } else { - const functionHelp = functionList.find(func => func.name === functionName); - if (functionHelp) { - const argHelp = functionHelp.args.find(arg => arg.name === argName); - if (argHelp && argHelp.suggestions) { - valueSuggestions = argHelp.suggestions; - } - } - } - return { - list: valueSuggestions, - type: SUGGESTION_TYPE.ARGUMENT_VALUE, - }; - } - } - } -} - -export function getSuggestion( - suggestion: ITimelionFunction | TimelionFunctionArgs, - type: SUGGESTION_TYPE, - range: monacoEditor.Range -): monacoEditor.languages.CompletionItem { - let kind: monacoEditor.languages.CompletionItemKind = - monacoEditor.languages.CompletionItemKind.Method; - let insertText: string = suggestion.name; - let insertTextRules: monacoEditor.languages.CompletionItem['insertTextRules']; - let detail: string = ''; - let command: monacoEditor.languages.CompletionItem['command']; - - switch (type) { - case SUGGESTION_TYPE.ARGUMENTS: - command = { - title: 'Trigger Suggestion Dialog', - id: 'editor.action.triggerSuggest', - }; - kind = monacoEditor.languages.CompletionItemKind.Property; - insertText = `${insertText}=`; - detail = `${i18n.translate( - 'timelion.expressionSuggestions.argument.description.acceptsText', - { - defaultMessage: 'Accepts', - } - )}: ${(suggestion as TimelionFunctionArgs).types}`; - - break; - case SUGGESTION_TYPE.FUNCTIONS: - command = { - title: 'Trigger Suggestion Dialog', - id: 'editor.action.triggerSuggest', - }; - kind = monacoEditor.languages.CompletionItemKind.Function; - insertText = `${insertText}($0)`; - insertTextRules = monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet; - detail = `(${ - (suggestion as ITimelionFunction).chainable - ? i18n.translate('timelion.expressionSuggestions.func.description.chainableHelpText', { - defaultMessage: 'Chainable', - }) - : i18n.translate('timelion.expressionSuggestions.func.description.dataSourceHelpText', { - defaultMessage: 'Data source', - }) - })`; - - break; - case SUGGESTION_TYPE.ARGUMENT_VALUE: - const param = suggestion.name.split(':'); - - if (param.length === 1 || param[1]) { - insertText = `${param.length === 1 ? insertText : param[1]},`; - } - - command = { - title: 'Trigger Suggestion Dialog', - id: 'editor.action.triggerSuggest', - }; - kind = monacoEditor.languages.CompletionItemKind.Property; - detail = suggestion.help || ''; - - break; - } - - return { - detail, - insertText, - insertTextRules, - kind, - label: suggestion.name, - documentation: suggestion.help, - command, - range, - }; -} diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx deleted file mode 100644 index 6294e51e54788..0000000000000 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { useMemo, useCallback } from 'react'; -import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; -import { isValidEsInterval } from '../../../../core_plugins/data/common'; - -const intervalOptions = [ - { - label: i18n.translate('timelion.vis.interval.auto', { - defaultMessage: 'Auto', - }), - value: 'auto', - }, - { - label: i18n.translate('timelion.vis.interval.second', { - defaultMessage: '1 second', - }), - value: '1s', - }, - { - label: i18n.translate('timelion.vis.interval.minute', { - defaultMessage: '1 minute', - }), - value: '1m', - }, - { - label: i18n.translate('timelion.vis.interval.hour', { - defaultMessage: '1 hour', - }), - value: '1h', - }, - { - label: i18n.translate('timelion.vis.interval.day', { - defaultMessage: '1 day', - }), - value: '1d', - }, - { - label: i18n.translate('timelion.vis.interval.week', { - defaultMessage: '1 week', - }), - value: '1w', - }, - { - label: i18n.translate('timelion.vis.interval.month', { - defaultMessage: '1 month', - }), - value: '1M', - }, - { - label: i18n.translate('timelion.vis.interval.year', { - defaultMessage: '1 year', - }), - value: '1y', - }, -]; - -interface TimelionIntervalProps { - value: string; - setValue(value: string): void; - setValidity(valid: boolean): void; -} - -function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProps) { - const onCustomInterval = useCallback( - (customValue: string) => { - setValue(customValue.trim()); - }, - [setValue] - ); - - const onChange = useCallback( - (opts: Array>) => { - setValue((opts[0] && opts[0].value) || ''); - }, - [setValue] - ); - - const selectedOptions = useMemo( - () => [intervalOptions.find(op => op.value === value) || { label: value, value }], - [value] - ); - - const isValid = intervalOptions.some(int => int.value === value) || isValidEsInterval(value); - - useValidation(setValidity, isValid); - - return ( - - - - ); -} - -export { TimelionInterval }; diff --git a/src/legacy/core_plugins/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index feee18f81ea85..ebf000d160b54 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -11,5 +11,4 @@ // timChart__legend-isLoading @import './app'; -@import './components/index'; @import './directives/index'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 7980291e2d462..63030fcbce387 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -32,4 +32,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index b8db145ff99db..636b8bf8e128a 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -22,17 +22,14 @@ import { Plugin, PluginInitializerContext, IUiSettingsClient, - HttpSetup, } from 'kibana/public'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; -import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { uiSettings: IUiSettingsClient; - http: HttpSetup; timelionPanels: Map; } @@ -55,7 +52,6 @@ export class TimelionPlugin implements Plugin, void> { const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, - http: core.http, timelionPanels, ...(await __LEGACY.setup(core, timelionPanels)), }; @@ -69,15 +65,12 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start(core: CoreStart, plugins: PluginsStart) { + public start(core: CoreStart) { const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); if (timelionUiEnabled === false) { core.chrome.navLinks.update('timelion', { hidden: true }); } - - setIndexPatterns(plugins.data.indexPatterns); - setSavedObjectsClient(core.savedObjects.client); } public stop(): void {} diff --git a/src/legacy/core_plugins/timelion/public/components/_index.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss similarity index 67% rename from src/legacy/core_plugins/timelion/public/components/_index.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss index f2458a367e176..f52c36e61e349 100644 --- a/src/legacy/core_plugins/timelion/public/components/_index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss @@ -1 +1,2 @@ +@import './chart'; @import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index 0441c6f1a33d0..3a939a48ce07c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -102,11 +102,11 @@ function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputPro }, [kibana.services.http]); return ( -
+
-
+
>; export const getTimelionVisualizationConfig = ( - deps: TimelionVisDependencies + dependencies: TimelionVisDependencies ): ExpressionFunction => ({ name, type: 'render', @@ -66,7 +66,7 @@ export const getTimelionVisualizationConfig = ( }, }, async fn(context, args) { - const timelionRequestHandler = getTimelionRequestHandler(deps); + const timelionRequestHandler = getTimelionRequestHandler(dependencies); const visParams = { expression: args.expression, interval: args.interval }; From 1c7e6c975f05030347b0265d1bb7034a7de0e6e2 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 9 Jan 2020 14:28:51 +0300 Subject: [PATCH 44/59] Refactoring --- .../public/services/plugin_services.ts | 30 ------------------- .../public/components/index.ts | 2 -- .../public/timelion_options.tsx | 2 +- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 src/legacy/core_plugins/timelion/public/services/plugin_services.ts diff --git a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts b/src/legacy/core_plugins/timelion/public/services/plugin_services.ts deleted file mode 100644 index 5ba4ee5e47983..0000000000000 --- a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexPatternsContract } from 'src/plugins/data/public'; -import { SavedObjectsClientContract } from 'kibana/public'; -import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; - -export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( - 'IndexPatterns' -); - -export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter< - SavedObjectsClientContract ->('SavedObjectsClient'); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts index e3955174765d6..e0b27fd1d7741 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -17,9 +17,7 @@ * under the License. */ -export * from './chart'; export * from './panel'; export * from './timelion_expression_input'; -export * from './timelion_expression_input_helpers'; export * from './timelion_interval'; export * from './timelion_vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 455293aab9e1d..be6829a76ac58 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,7 +20,7 @@ import React, { useCallback } from 'react'; import { EuiPanel } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from './legacy_imports'; import { VisParams } from './timelion_vis_fn'; import { TimelionInterval, TimelionExpressionInput } from './components'; From 9724c09408370163c4b7b492adab46493e611e39 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 13 Jan 2020 17:36:06 +0300 Subject: [PATCH 45/59] Add @types/flot --- package.json | 1 + .../public/components/_chart.scss | 1 + .../public/helpers/panel_utils.ts | 56 +------------------ tsconfig.json | 3 +- yarn.lock | 9 ++- 5 files changed, 14 insertions(+), 56 deletions(-) diff --git a/package.json b/package.json index 0ed74dd65d1ab..4ad6a72f97cb5 100644 --- a/package.json +++ b/package.json @@ -135,6 +135,7 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/flot": "^0.0.31", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", "@types/node-forge": "^0.9.0", diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss index 39713cd05ab37..c4d591bc82cad 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss @@ -33,6 +33,7 @@ .ngLegendValue { color: $euiTextColor; + cursor: pointer; &:focus, &:hover { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index f2971e00ea0f0..e656c760eb1b8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -25,9 +25,7 @@ import { IUiSettingsClient } from 'kibana/public'; // @ts-ignore import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; -import { tickFormatters } from './tick_formatters'; import { xaxisFormatterProvider } from './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; import { Series } from './timelion_request_handler'; export interface Axis { @@ -47,34 +45,6 @@ export interface Axis { tickGenerator?(axis: Axis): number[]; units?: { type: string }; } -interface IOptions { - colors: string[]; - crosshair: { - color: string; - lineWidth: number; - mode: string; - }; - grid: { - autoHighlight: boolean; - borderColor: string | null; - borderWidth: number; - hoverable: boolean; - margin: number; - show?: boolean; - }; - legend: { - backgroundColor: string; - labelBoxBorderColor: string; - labelFormatter(label: string, series: { _id: number }): string; - position: string; - }; - selection: { - color: string; - mode: string; - }; - xaxis: Axis; - yaxes?: Axis[]; -} interface TimeRangeBounds { min: Moment | undefined; @@ -96,7 +66,7 @@ const colors = [ const SERIES_ID_ATTR = 'data-series-id'; -function buildSeriesData(chart: Series[], options: IOptions) { +function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { return chart.map((series: Series, seriesIndex: number) => { const newSeries: Series = cloneDeep( defaults(series, { @@ -159,7 +129,7 @@ function buildOptions( const tickLetterWidth = 7; const tickPadding = 45; - const options: IOptions = { + const options: jquery.flot.plotOptions = { xaxis: { mode: 'time', tickLength: 5, @@ -169,15 +139,6 @@ function buildOptions( // Use moment to format ticks so we get timezone correction tickFormatter: (val: number) => moment(val).format(format), }, - selection: { - mode: 'x', - color: '#ccc', - }, - crosshair: { - mode: 'x', - color: '#C66', - lineWidth: 2, - }, colors, grid: { show: showGrid, @@ -210,19 +171,6 @@ function buildOptions( }, }; - if (options.yaxes) { - options.yaxes.forEach(yaxis => { - if (yaxis && yaxis.units) { - const formatters = tickFormatters(); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicksProvider(); - } - } - }); - } - return options; } diff --git a/tsconfig.json b/tsconfig.json index a2da9c127e7ba..8d9e2359ba97f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -51,7 +51,8 @@ "types": [ "node", "jest", - "react" + "react", + "flot" ] }, "include": [ diff --git a/yarn.lock b/yarn.lock index 96bb533120aa7..1ffbba115cdeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3495,6 +3495,13 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af" integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ== +"@types/flot@^0.0.31": + version "0.0.31" + resolved "https://registry.yarnpkg.com/@types/flot/-/flot-0.0.31.tgz#0daca37c6c855b69a0a7e2e37dd0f84b3db8c8c1" + integrity sha512-X+RcMQCqPlQo8zPT6cUFTd/PoYBShMQlHUeOXf05jWlfYnvLuRmluB9z+2EsOKFgUzqzZve5brx+gnFxBaHEUw== + dependencies: + "@types/jquery" "*" + "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -3698,7 +3705,7 @@ resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" integrity sha512-JxZ0NP8NuB0BJOXi1KvAA6rySLTPmhOy4n2gzSFq/IFM3LNFm0h+2Vn/bPPgEYlWqzS2NPeLgKqfm75baX+Hog== -"@types/jquery@^3.3.31": +"@types/jquery@*", "@types/jquery@^3.3.31": version "3.3.31" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b" integrity sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg== From 663394564022e0cd7fea10038d54f5958e08f5b8 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 13:12:11 +0300 Subject: [PATCH 46/59] Fix issue with hovered series color --- .../public/components/panel.tsx | 183 ++++++++++-------- .../public/helpers/panel_utils.ts | 17 +- 2 files changed, 111 insertions(+), 89 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index 6cfa0e7633b24..a490522023c05 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -73,9 +73,9 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { const [canvasElem, setCanvasElem] = useState(); const [chartElem, setChartElem] = useState(); - const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); - const [highlightedSeries, setHighlightedSeries] = useState(); + const [highlightedSeries, setHighlightedSeries] = useState(null); const [focusedSeries, setFocusedSeries] = useState(); const [plot, setPlot] = useState(); @@ -105,41 +105,6 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { [chartElem] ); - useEffect(() => { - setChart( - seriesList.list.map((series: Series, seriesIndex: number) => { - const newSeries = { ...series }; - if (!newSeries.color) { - const colorIndex = seriesIndex % colors.length; - newSeries.color = colors[colorIndex]; - } - // setting originalColorMap - setOriginalColorMap(stateMap => new Map(stateMap.set(newSeries, newSeries.color))); - return newSeries; - }) - ); - }, [seriesList.list]); - - useEffect(() => { - if (plot && get(plot.getData(), '[0]._global.legend.showTime', true)) { - const caption = $(''); - caption.html(emptyCaption); - setLegendCaption(caption); - - const canvasNode = $(canvasElem); - canvasNode.find('div.legend table').append(caption); - setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); - - // legend has been re-created. Apply focus on legend element when previously set - if (focusedSeries || focusedSeries === 0) { - canvasNode - .find('div.legend table .legendLabel>span') - .get(focusedSeries) - .focus(); - } - } - }, [plot, focusedSeries, canvasElem]); - const highlightSeries = useCallback( debounce(({ currentTarget }: JQuery.TriggeredEvent) => { const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); @@ -150,16 +115,16 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { setHighlightedSeries(id); setChart(chartState => chartState.map((series: Series, seriesIndex: number) => { - const color = + series.color = seriesIndex === id ? originalColorMap.get(series) // color it like it was : 'rgba(128,128,128,0.1)'; // mark as grey - return { ...series, color }; + return series; }) ); }, DEBOUNCE_DELAY), - [highlightedSeries, originalColorMap] + [originalColorMap, highlightedSeries] ); const focusSeries = useCallback( @@ -173,53 +138,86 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + setChart(chartState => chartState.map((series: Series, seriesIndex: number) => { - return seriesIndex === id ? { ...series, _hide: !series._hide } : { ...series }; + if (seriesIndex === id) { + series._hide = !series._hide; + } + return series; }) ); }, []); - const options = useMemo( - () => - buildOptions( - interval, - kibana.services.timefilter, - kibana.services.uiSettings, - chartElem && chartElem.clientWidth, - seriesList.render.grid - ), - [seriesList.render.grid, interval, chartElem, kibana.services] + const updateCaption = useCallback( + (plotData: any) => { + if (get(plotData, '[0]._global.legend.showTime', true)) { + const caption = $(''); + caption.html(emptyCaption); + setLegendCaption(caption); + + const canvasNode = $(canvasElem); + canvasNode.find('div.legend table').append(caption); + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + + const legend = $(canvasElem).find('.ngLegendValue'); + if (legend) { + legend.click(toggleSeries); + legend.focus(focusSeries); + legend.mouseover(highlightSeries); + } + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + canvasNode + .find('div.legend table .legendLabel>span') + .get(focusedSeries) + .focus(); + } + } + }, + [focusedSeries, canvasElem, toggleSeries, focusSeries, highlightSeries] ); - const updatedSeries = useMemo(() => buildSeriesData(chart, options), [chart, options]); + const updatePlot = useCallback( + (chartValue: Series[], grid?: boolean) => { + if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + chartElem && chartElem.clientWidth, + grid + ); + const updatedSeries = buildSeriesData(chartValue, options); + const newPlot = $.plot(canvasElem, updatedSeries, options); + setPlot(newPlot); + renderComplete(); - useEffect(() => { - if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { - // @ts-ignore - setPlot($.plot(canvasElem, compact(updatedSeries), options)); - renderComplete(); - - const legend = $(canvasElem).find('.ngLegendValue'); - if (legend) { - legend.click(toggleSeries); - legend.focus(focusSeries); - legend.mouseover(highlightSeries); + updateCaption(newPlot.getData()); } - } - }, [ - canvasElem, - options, - updatedSeries, - toggleSeries, - focusSeries, - highlightSeries, - renderComplete, - ]); + }, + [canvasElem, chartElem, renderComplete, kibana.services, interval, updateCaption] + ); + + useEffect(() => { + updatePlot(chart, seriesList.render.grid); + }, [chart, updatePlot, seriesList.render.grid]); useEffect(() => { - moment.tz.setDefault(kibana.services.uiSettings.get('dateFormat:tz')); - }, [kibana.services.uiSettings]); + const colorsSet: Array<[Series, string]> = []; + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + colorsSet.push([newSeries, newSeries.color]); + return newSeries; + }); + setChart(newChart); + setOriginalColorMap(new Map(colorsSet)); + }, [seriesList.list, seriesList.render.grid]); const unhighlightSeries = useCallback(() => { if (highlightedSeries === null) { @@ -228,9 +226,11 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { setHighlightedSeries(null); setFocusedSeries(null); + setChart(chartState => chartState.map((series: Series) => { - return { ...series, color: originalColorMap.get(series) }; // reset the colors + series.color = originalColorMap.get(series); // reset the colors + return series; }) ); }, [originalColorMap, highlightedSeries]); @@ -284,7 +284,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { } } }, - [unhighlightSeries, legendCaption, legendValueNumbers, plot] + [plot, legendValueNumbers, unhighlightSeries, legendCaption] ); const debouncedSetLegendNumbers = useCallback( @@ -335,20 +335,31 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { useEffect(() => { if (chartElem) { - const $chart = $(chartElem); - $chart + $(chartElem) .off('plotselected') - .off('plothover') - .off('mouseleave'); + .on('plotselected', plotSelectedHandler); + } + }, [chartElem, plotSelectedHandler]); - $chart - .on('plotselected', plotSelectedHandler) - .on('plothover', plotHoverHandler) + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('mouseleave') .on('mouseleave', mouseLeaveHandler); } - }, [chartElem, plotSelectedHandler, plotHoverHandler, mouseLeaveHandler]); + }, [chartElem, mouseLeaveHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plothover') + .on('plothover', plotHoverHandler); + } + }, [chartElem, plotHoverHandler]); - const title: string = useMemo(() => last(compact(map(chart, '_title'))) || '', [chart]); + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); return (
diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index e656c760eb1b8..e001fbf9b4bdb 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -17,7 +17,7 @@ * under the License. */ -import { cloneDeep, defaults, merge } from 'lodash'; +import { cloneDeep, defaults, merge, compact } from 'lodash'; import moment, { Moment } from 'moment-timezone'; import { TimefilterContract } from 'src/plugins/data/public'; @@ -67,7 +67,7 @@ const colors = [ const SERIES_ID_ATTR = 'data-series-id'; function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { - return chart.map((series: Series, seriesIndex: number) => { + const seriesData = chart.map((series: Series, seriesIndex: number) => { const newSeries: Series = cloneDeep( defaults(series, { shadowSize: 0, @@ -106,6 +106,8 @@ function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { return newSeries; }); + + return compact(seriesData); } function buildOptions( @@ -139,6 +141,15 @@ function buildOptions( // Use moment to format ticks so we get timezone correction tickFormatter: (val: number) => moment(val).format(format), }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, colors, grid: { show: showGrid, @@ -169,7 +180,7 @@ function buildOptions( return wrapperSpan.outerHTML; }, }, - }; + } as jquery.flot.plotOptions; return options; } From 0fbafeb43f7890e1db8d4308720e27ef268b5093 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 13:57:38 +0300 Subject: [PATCH 47/59] Update renovate.json5 --- renovate.json5 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/renovate.json5 b/renovate.json5 index 560403046b0a5..0735b030b4c9b 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -273,6 +273,14 @@ '@types/file-saver', ], }, + { + groupSlug: 'flot', + groupName: 'flot related packages', + packageNames: [ + 'flot', + '@types/flot', + ], + }, { groupSlug: 'getopts', groupName: 'getopts related packages', From 6a9c010e70d6235ff9350ec084c7737f696c3351 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 14:11:44 +0300 Subject: [PATCH 48/59] Pass timelionPanels as dependencies --- .../public/components/chart.tsx | 6 ++-- .../public/components/panel.tsx | 11 ++---- .../vis_type_timelion/public/plugin.ts | 15 +++----- .../vis_type_timelion/public/services.ts | 35 ------------------- 4 files changed, 11 insertions(+), 56 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/services.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index d01e59682254c..1f8f2e8cb5ad4 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -21,8 +21,9 @@ import React from 'react'; import { EuiFormErrorText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getPanels } from '../services'; +import { useKibana } from '../../../../../plugins/kibana_react/public'; import { Sheet } from '../helpers/timelion_request_handler'; +import { TimelionVisDependencies } from '../plugin'; import { PanelProps } from './panel'; interface ChartComponentProp { @@ -32,6 +33,7 @@ interface ChartComponentProp { } function ChartComponent({ seriesList, interval, renderComplete }: ChartComponentProp) { + const kibana = useKibana(); if (!seriesList) { return null; } @@ -41,7 +43,7 @@ function ChartComponent({ seriesList, interval, renderComplete }: ChartComponent type: 'timechart', }; - const panelSchema = getPanels().get(panelScope.seriesList.render.type); + const panelSchema = kibana.services.timelionPanels.get(panelScope.seriesList.render.type); if (!panelSchema) { return ( diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index a490522023c05..75f677c48ae04 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -22,9 +22,6 @@ import $ from 'jquery'; import moment from 'moment-timezone'; import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; -import { CoreStart } from 'src/core/public'; -import { TimefilterContract } from 'src/plugins/data/public'; -import { IUiSettingsClient } from 'kibana/public'; import { useKibana } from '../../../../../plugins/kibana_react/public'; import '../flot'; // @ts-ignore @@ -32,6 +29,7 @@ import { DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from '../helpers/panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { TimelionVisDependencies } from '../plugin'; export interface PanelProps { interval: string; @@ -58,17 +56,12 @@ interface Ranges { yaxis: Range; } -interface ITimelionVisPluginServices extends Partial { - timefilter: TimefilterContract; - uiSettings: IUiSettingsClient; -} - const DEBOUNCE_DELAY = 50; // ensure legend is the same height with or without a caption so legend items do not move around const emptyCaption = '
'; function Panel({ interval, seriesList, renderComplete }: PanelProps) { - const kibana = useKibana(); + const kibana = useKibana(); const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); const [canvasElem, setCanvasElem] = useState(); const [chartElem, setChartElem] = useState(); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index a4f830567c487..b70705074d83a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -31,7 +31,6 @@ import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/publ import { PluginsStart } from './legacy_imports'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimeChart, IPanelWrapper } from './timechart'; -import { setPanels } from './services'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; @@ -40,10 +39,11 @@ import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_servic type TimelionVisCoreSetup = CoreSetup; /** @internal */ -export interface TimelionVisDependencies { +export interface TimelionVisDependencies extends Partial { uiSettings: IUiSettingsClient; http: HttpSetup; timefilter: TimefilterContract; + timelionPanels: Map; } /** @internal */ @@ -62,25 +62,20 @@ export class TimelionVisPlugin implements Plugin { { expressions, visualizations, data }: TimelionVisSetupDependencies ) { const timelionPanels: Map = new Map(); + const [name, timeChartPanel] = getTimeChart(); + timelionPanels.set(name, timeChartPanel); const dependencies: TimelionVisDependencies = { uiSettings: core.uiSettings, http: core.http, timefilter: data.query.timefilter.timefilter, + timelionPanels, }; - this.registerPanels(timelionPanels); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); } - private registerPanels(timelionPanels: Map) { - const [name, timeChartPanel] = getTimeChart(); - - timelionPanels.set(name, timeChartPanel); - setPanels(timelionPanels); - } - public start(core: CoreStart, plugins: PluginsStart) { setIndexPatterns(plugins.data.indexPatterns); setSavedObjectsClient(core.savedObjects.client); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/services.ts b/src/legacy/core_plugins/vis_type_timelion/public/services.ts deleted file mode 100644 index 54cc437a39ee2..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/services.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IPanelWrapper } from './timechart'; - -let timelionPanels: Map | null = null; - -export function setPanels(panels: Map) { - timelionPanels = panels; -} - -export function getPanels() { - if (!timelionPanels) { - throw new Error( - 'Timelion panels not set - are you trying to import them from outside of the timelion vis?' - ); - } - return timelionPanels; -} From a37506c7dc97e0b2417f20fd034c7dc395a084f3 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 14:30:03 +0300 Subject: [PATCH 49/59] Move common folder to vis_type_timelion --- .../public/panels/timechart/schema.ts | 3 +-- .../timelion/server/handlers/chain_runner.js | 2 +- .../server/lib/classes/timelion_function.d.ts | 2 +- .../server/series_functions/legend.js | 2 +- .../common/lib/calculate_interval.ts} | 24 +++++++++++++++---- .../common/lib/index.ts} | 0 .../common/types.ts | 0 .../public/components/panel.tsx | 3 +-- .../components/timelion_expression_input.tsx | 4 ++-- .../timelion_expression_input_helpers.ts | 2 +- .../public/helpers/arg_value_suggestions.ts | 2 +- .../public/helpers/panel_utils.ts | 3 +-- 12 files changed, 29 insertions(+), 18 deletions(-) rename src/legacy/core_plugins/{timelion/common/lib/calculate_interval.js => vis_type_timelion/common/lib/calculate_interval.ts} (84%) rename src/legacy/core_plugins/{timelion/common/lib/index.js => vis_type_timelion/common/lib/index.ts} (100%) rename src/legacy/core_plugins/{timelion => vis_type_timelion}/common/types.ts (100%) diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 260e472eb6754..8244b5dbfd39e 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -24,8 +24,7 @@ import moment from 'moment-timezone'; import { timefilter } from 'ui/timefilter'; // @ts-ignore import observeResize from '../../lib/observe_resize'; -// @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../../vis_type_timelion/common/lib'; import { TimelionVisualizationDependencies } from '../../plugin'; import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; diff --git a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js index 9514e479d36f4..9056362cb723a 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js +++ b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js @@ -26,7 +26,7 @@ import parseSheet from './lib/parse_sheet.js'; import repositionArguments from './lib/reposition_arguments.js'; import indexArguments from './lib/index_arguments.js'; import validateTime from './lib/validate_time.js'; -import { calculateInterval } from '../../common/lib'; +import { calculateInterval } from '../../../vis_type_timelion/common/lib'; export default function chainRunner(tlConfig) { const preprocessChain = require('./lib/preprocess_chain')(tlConfig); diff --git a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 798902aa133de..08358b9d81f78 100644 --- a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts +++ b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TimelionFunctionArgs } from '../../../common/types'; +import { TimelionFunctionArgs } from '../../../../vis_type_timelion/common/types'; export interface TimelionFunctionInterface extends TimelionFunctionConfig { chainable: boolean; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/legend.js b/src/legacy/core_plugins/timelion/server/series_functions/legend.js index b467318686729..fd9ff53a1391f 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/legend.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/legend.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import Chainable from '../lib/classes/chainable'; -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { DEFAULT_TIME_FORMAT } from '../../../vis_type_timelion/common/lib'; export default new Chainable('legend', { args: [ diff --git a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts similarity index 84% rename from src/legacy/core_plugins/timelion/common/lib/calculate_interval.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts index 7c6b3c2816e67..d4bce7ae36b3e 100644 --- a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts @@ -17,11 +17,12 @@ * under the License. */ -import toMS from '../../server/lib/to_milliseconds.js'; +// @ts-ignore +import toMS from '../../../timelion/server/lib/to_milliseconds.js'; // Totally cribbed this from Kibana 3. // I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. -function roundInterval(interval) { +function roundInterval(interval: number) { switch (true) { case interval <= 500: // <= 0.5s return '100ms'; @@ -58,9 +59,22 @@ function roundInterval(interval) { } } -export function calculateInterval(from, to, size, interval, min) { - if (interval !== 'auto') return interval; +export function calculateInterval( + from: number, + to: number, + size: number, + interval: string, + min: string +) { + if (interval !== 'auto') { + return interval; + } + const dateMathInterval = roundInterval((to - from) / size); - if (toMS(dateMathInterval) < toMS(min)) return min; + + if (toMS(dateMathInterval) < toMS(min)) { + return min; + } + return dateMathInterval; } diff --git a/src/legacy/core_plugins/timelion/common/lib/index.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts similarity index 100% rename from src/legacy/core_plugins/timelion/common/lib/index.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts diff --git a/src/legacy/core_plugins/timelion/common/types.ts b/src/legacy/core_plugins/vis_type_timelion/common/types.ts similarity index 100% rename from src/legacy/core_plugins/timelion/common/types.ts rename to src/legacy/core_plugins/vis_type_timelion/common/types.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index 75f677c48ae04..de6b63f918328 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -24,8 +24,7 @@ import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; import { useKibana } from '../../../../../plugins/kibana_react/public'; import '../flot'; -// @ts-ignore -import { DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from '../helpers/panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index 3a939a48ce07c..fa79e4eb6871a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -24,8 +24,8 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; -import { ITimelionFunction, TimelionFunctionArgs } from '../../../timelion/common/types'; -import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; +import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index 3b306903d81dc..5aa05fb16466b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -25,7 +25,7 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import grammar from 'raw-loader!../chain.peg'; import { i18n } from '@kbn/i18n'; -import { ITimelionFunction, TimelionFunctionArgs } from '../../../timelion/common/types'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts index 5ed35f68a5acf..8d133de51f6d9 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { TimelionFunctionArgs } from '../../../timelion/common/types'; +import { TimelionFunctionArgs } from '../../common/types'; import { getIndexPatterns, getSavedObjectsClient } from './plugin_services'; export interface Location { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index e001fbf9b4bdb..d3b27ff42da4c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -23,8 +23,7 @@ import moment, { Moment } from 'moment-timezone'; import { TimefilterContract } from 'src/plugins/data/public'; import { IUiSettingsClient } from 'kibana/public'; -// @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../timelion/common/lib'; +import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from './xaxis_formatter'; import { Series } from './timelion_request_handler'; From 687bd7e0e7911acfda7e8e6676fd6abddb2b7d4d Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 14:37:17 +0300 Subject: [PATCH 50/59] Move back tick_formatters.ts --- .../timelion/public/panels/timechart/schema.ts | 2 +- .../public/services}/tick_formatters.test.ts | 0 .../public/services}/tick_formatters.ts | 10 ++++------ .../vis_type_timelion/public/helpers/panel_utils.ts | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) rename src/legacy/core_plugins/{vis_type_timelion/public/helpers => timelion/public/services}/tick_formatters.test.ts (100%) rename src/legacy/core_plugins/{vis_type_timelion/public/helpers => timelion/public/services}/tick_formatters.ts (93%) diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 8244b5dbfd39e..cf6212c028d74 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -26,7 +26,7 @@ import { timefilter } from 'ui/timefilter'; import observeResize from '../../lib/observe_resize'; import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../../vis_type_timelion/common/lib'; import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; +import { tickFormatters } from '../../services/tick_formatters'; import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; import { generateTicksProvider } from '../../../../vis_type_timelion/public/helpers/tick_generator'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts b/src/legacy/core_plugins/timelion/public/services/tick_formatters.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts rename to src/legacy/core_plugins/timelion/public/services/tick_formatters.test.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts b/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts similarity index 93% rename from src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts rename to src/legacy/core_plugins/timelion/public/services/tick_formatters.ts index c80f9c3ed5f4b..ff4eeda56089c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts +++ b/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts @@ -19,9 +19,7 @@ import { get } from 'lodash'; -import { Axis } from './panel_utils'; - -function baseTickFormatter(value: number, axis: Axis) { +function baseTickFormatter(value: number, axis: any) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -62,13 +60,13 @@ export function tickFormatters() { 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: number, axis: Axis) { + currency(val: number, axis: any) { return val.toLocaleString('en', { style: 'currency', currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: number, axis: Axis) { + percent(val: number, axis: any) { let precision = get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 @@ -80,7 +78,7 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: number, axis: Axis) { + custom(val: number, axis: any) { const formattedVal = baseTickFormatter(val, axis); const prefix = axis && axis.options && axis.options.units.prefix; const suffix = axis && axis.options && axis.options.units.suffix; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index d3b27ff42da4c..fb4ad1b18c2f0 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -119,8 +119,8 @@ function buildOptions( // Get the X-axis tick format const time: TimeRangeBounds = timefilter.getBounds(); const interval = calculateInterval( - time.min && time.min.valueOf(), - time.max && time.max.valueOf(), + (time.min && time.min.valueOf()) || 0, + (time.max && time.max.valueOf()) || 0, uiSettings.get('timelion:target_buckets') || 200, intervalValue, uiSettings.get('timelion:min_interval') || '1ms' From fbf9508e43424b5565290d707c35d9341a9c2284 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 14:51:45 +0300 Subject: [PATCH 51/59] Rename styles file --- .../vis_type_timelion/public/components/_index.scss | 2 +- .../public/components/{_chart.scss => _panel.scss} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/legacy/core_plugins/vis_type_timelion/public/components/{_chart.scss => _panel.scss} (100%) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss index f52c36e61e349..1d887f43ff9a1 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss @@ -1,2 +1,2 @@ -@import './chart'; +@import './panel'; @import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss similarity index 100% rename from src/legacy/core_plugins/vis_type_timelion/public/components/_chart.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss From b34185aa8e08b9f38c6883c072f5c4ddd62bd1d4 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 15:07:09 +0300 Subject: [PATCH 52/59] Refactoring --- .../vis_type_timelion/public/helpers/panel_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index fb4ad1b18c2f0..8b0dabcce237b 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -130,7 +130,7 @@ function buildOptions( const tickLetterWidth = 7; const tickPadding = 45; - const options: jquery.flot.plotOptions = { + const options = { xaxis: { mode: 'time', tickLength: 5, From 31f5be94cea42e82259bace19c5193cf6991ad6e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 14 Jan 2020 17:04:20 +0300 Subject: [PATCH 53/59] Update _index.scss --- src/legacy/core_plugins/timelion/public/directives/_index.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/legacy/core_plugins/timelion/public/directives/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index 1b768e6d8a07a..cd46a1a0a369e 100644 --- a/src/legacy/core_plugins/timelion/public/directives/_index.scss +++ b/src/legacy/core_plugins/timelion/public/directives/_index.scss @@ -3,5 +3,3 @@ @import './timelion_expression_suggestions/index'; @import './timelion_help/index'; @import './timelion_interval/index'; - -@import '../../../vis_type_timelion/public/components/chart' From a30016fde39371f5c434606684d4673c235f0b05 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 15 Jan 2020 18:53:59 +0300 Subject: [PATCH 54/59] Move to_milliseconds to common --- .../server/handlers/lib/validate_time.js | 2 +- .../server/series_functions/holt/index.js | 6 ++--- .../server/series_functions/movingaverage.js | 2 +- .../server/series_functions/scale_interval.js | 2 +- .../common/lib/calculate_interval.ts | 9 ++++--- .../vis_type_timelion/common/lib/index.ts | 2 ++ .../common/lib/to_milliseconds.ts} | 27 ++++++++++--------- 7 files changed, 27 insertions(+), 23 deletions(-) rename src/legacy/core_plugins/{timelion/server/lib/to_milliseconds.js => vis_type_timelion/common/lib/to_milliseconds.ts} (70%) diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js index 8b1f8998557be..db924e33be5e9 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import toMS from '../../lib/to_milliseconds.js'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default function validateTime(time, tlConfig) { const span = moment.duration(moment(time.to).diff(moment(time.from))).asMilliseconds(); diff --git a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js index 970d146c45b91..0cc41df933e8c 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js @@ -23,7 +23,7 @@ import Chainable from '../../lib/classes/chainable'; import ses from './lib/ses'; import des from './lib/des'; import tes from './lib/tes'; -import toMilliseconds from '../../lib/to_milliseconds'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default new Chainable('holt', { args: [ @@ -125,9 +125,7 @@ export default new Chainable('holt', { }) ); } - const season = Math.round( - toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval) - ); + const season = Math.round(toMS(args.byName.season) / toMS(tlConfig.time.interval)); points = tes(points, alpha, beta, gamma, season, sample); } diff --git a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js index 361cd1f9dfb67..a4b458991c1bc 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; const validPositions = ['left', 'right', 'center']; const defaultPosition = 'center'; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js index 778c91d30f2cb..b604015624dfd 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts index d4bce7ae36b3e..328c634ea5153 100644 --- a/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts @@ -17,8 +17,7 @@ * under the License. */ -// @ts-ignore -import toMS from '../../../timelion/server/lib/to_milliseconds.js'; +import { toMS } from './to_milliseconds'; // Totally cribbed this from Kibana 3. // I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. @@ -70,9 +69,11 @@ export function calculateInterval( return interval; } - const dateMathInterval = roundInterval((to - from) / size); + const dateMathInterval: string = roundInterval((to - from) / size); + const dateMathIntervalMs = toMS(dateMathInterval); + const minMs = toMS(min); - if (toMS(dateMathInterval) < toMS(min)) { + if (dateMathIntervalMs !== undefined && minMs !== undefined && dateMathIntervalMs < minMs) { return min; } diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts index 927331043f0b3..1901b8224f607 100644 --- a/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts @@ -18,4 +18,6 @@ */ export { calculateInterval } from './calculate_interval'; +export { toMS } from './to_milliseconds'; + export const DEFAULT_TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss.SSS'; diff --git a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts similarity index 70% rename from src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts index 0d62d848daba5..33e2bc392cf2b 100644 --- a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts @@ -17,12 +17,14 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +import { transform, keys } from 'lodash'; +import moment, { unitOfTime } from 'moment'; +type Units = unitOfTime.Base | unitOfTime._quarter; +type Values = { [key in Units]: number }; // map of moment's short/long unit ids and elasticsearch's long unit ids // to their value in milliseconds -const vals = _.transform( +const vals = transform( [ ['ms', 'milliseconds', 'millisecond'], ['s', 'seconds', 'second', 'sec'], @@ -33,26 +35,27 @@ const vals = _.transform( ['M', 'months', 'month'], ['quarter'], ['y', 'years', 'year'], - ], - function(vals, units) { - const normal = moment.normalizeUnits(units[0]); + ] as any, + // @ts-ignore + function(values: Values, units: Units[]) { + const normal = moment.normalizeUnits(units[0]) as Units; const val = moment.duration(1, normal).asMilliseconds(); - [].concat(normal, units).forEach(function(unit) { - vals[unit] = val; + ([] as Units[]).concat(normal, units).forEach((unit: Units) => { + values[unit] = val; }); }, - {} + {} as Values ); // match any key from the vals object preceded by an optional number -const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); +const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + keys(vals).join('|') + ')$'); -export default function(expr) { +export function toMS(expr: string) { const match = expr.match(parseRE); if (match) { if (match[2] === 'M' && match[1] !== '1') { throw new Error('Invalid interval. 1M is only valid monthly interval.'); } - return parseFloat(match[1] || 1) * vals[match[2]]; + return parseFloat(match[1] || '1') * vals[match[2]]; } } From 426f3762b51900df005f9cea51befb31b6cf0302 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 15 Jan 2020 20:24:22 +0300 Subject: [PATCH 55/59] Revert yaxes formatting --- .../timelion/public/panels/timechart/schema.ts | 2 +- .../public/helpers/panel_utils.ts | 15 +++++++++++++++ .../public/helpers}/tick_formatters.test.ts | 0 .../public/helpers}/tick_formatters.ts | 10 ++++++---- 4 files changed, 22 insertions(+), 5 deletions(-) rename src/legacy/core_plugins/{timelion/public/services => vis_type_timelion/public/helpers}/tick_formatters.test.ts (100%) rename src/legacy/core_plugins/{timelion/public/services => vis_type_timelion/public/helpers}/tick_formatters.ts (93%) diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index cf6212c028d74..57ee99f5268b0 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -25,8 +25,8 @@ import { timefilter } from 'ui/timefilter'; // @ts-ignore import observeResize from '../../lib/observe_resize'; import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../../vis_type_timelion/common/lib'; +import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../services/tick_formatters'; import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; import { generateTicksProvider } from '../../../../vis_type_timelion/public/helpers/tick_generator'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index 8b0dabcce237b..e57439df10c82 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -26,6 +26,8 @@ import { IUiSettingsClient } from 'kibana/public'; import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from './xaxis_formatter'; import { Series } from './timelion_request_handler'; +import { tickFormatters } from './tick_formatters'; +import { generateTicksProvider } from './tick_generator'; export interface Axis { delta?: number; @@ -103,6 +105,19 @@ function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { }); } + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + return newSeries; }); diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.test.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/tick_formatters.test.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts similarity index 93% rename from src/legacy/core_plugins/timelion/public/services/tick_formatters.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts index ff4eeda56089c..c80f9c3ed5f4b 100644 --- a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -19,7 +19,9 @@ import { get } from 'lodash'; -function baseTickFormatter(value: number, axis: any) { +import { Axis } from './panel_utils'; + +function baseTickFormatter(value: number, axis: Axis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -60,13 +62,13 @@ export function tickFormatters() { 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: number, axis: any) { + currency(val: number, axis: Axis) { return val.toLocaleString('en', { style: 'currency', currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: number, axis: any) { + percent(val: number, axis: Axis) { let precision = get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 @@ -78,7 +80,7 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: number, axis: any) { + custom(val: number, axis: Axis) { const formattedVal = baseTickFormatter(val, axis); const prefix = axis && axis.options && axis.options.units.prefix; const suffix = axis && axis.options && axis.options.units.suffix; From 66f706c83a2ab5c4374436d37f5919ff0631e475 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 15 Jan 2020 20:28:20 +0300 Subject: [PATCH 56/59] Refactoring --- .../public/helpers/panel_utils.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index e57439df10c82..7e8b906fb95e2 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -105,22 +105,22 @@ function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { }); } - if (options.yaxes) { - options.yaxes.forEach((yaxis: Axis) => { - if (yaxis && yaxis.units) { - const formatters = tickFormatters(); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicksProvider(); - } - } - }); - } - return newSeries; }); + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + return compact(seriesData); } From c34477c3bfe39035384619e3b1f6e9fec9786ba6 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 15 Jan 2020 20:45:22 +0300 Subject: [PATCH 57/59] Refactoring --- .../public/components/panel.tsx | 24 ++++++++++++++++++- .../public/helpers/panel_utils.ts | 17 +------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index de6b63f918328..dc4202cbbbf9e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -26,8 +26,16 @@ import { useKibana } from '../../../../../plugins/kibana_react/public'; import '../flot'; import { DEFAULT_TIME_FORMAT } from '../../common/lib'; -import { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors } from '../helpers/panel_utils'; +import { + buildSeriesData, + buildOptions, + SERIES_ID_ATTR, + colors, + Axis, +} from '../helpers/panel_utils'; import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { tickFormatters } from '../helpers/tick_formatters'; +import { generateTicksProvider } from '../helpers/tick_generator'; import { TimelionVisDependencies } from '../plugin'; export interface PanelProps { @@ -182,6 +190,20 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { grid ); const updatedSeries = buildSeriesData(chartValue, options); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + const newPlot = $.plot(canvasElem, updatedSeries, options); setPlot(newPlot); renderComplete(); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts index 7e8b906fb95e2..db29d9112be8e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -26,8 +26,6 @@ import { IUiSettingsClient } from 'kibana/public'; import { calculateInterval } from '../../common/lib'; import { xaxisFormatterProvider } from './xaxis_formatter'; import { Series } from './timelion_request_handler'; -import { tickFormatters } from './tick_formatters'; -import { generateTicksProvider } from './tick_generator'; export interface Axis { delta?: number; @@ -108,19 +106,6 @@ function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { return newSeries; }); - if (options.yaxes) { - options.yaxes.forEach((yaxis: Axis) => { - if (yaxis && yaxis.units) { - const formatters = tickFormatters(); - yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; - const byteModes = ['bytes', 'bytes/s']; - if (byteModes.includes(yaxis.units.type)) { - yaxis.tickGenerator = generateTicksProvider(); - } - } - }); - } - return compact(seriesData); } @@ -194,7 +179,7 @@ function buildOptions( return wrapperSpan.outerHTML; }, }, - } as jquery.flot.plotOptions; + } as jquery.flot.plotOptions & { yaxes?: Axis[] }; return options; } From 630da840e3e3e365692f0cc3f31377f54abe3bf2 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 16 Jan 2020 09:55:13 +0300 Subject: [PATCH 58/59] Use Panel directly --- .../public/components/chart.tsx | 32 +++---------------- .../public/components/index.ts | 1 - .../public/components/panel.tsx | 8 ++--- .../helpers/timelion_request_handler.ts | 3 +- .../vis_type_timelion/public/plugin.ts | 7 ---- .../vis_type_timelion/public/timechart.tsx | 30 ----------------- 6 files changed, 9 insertions(+), 72 deletions(-) delete mode 100644 src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index 1f8f2e8cb5ad4..a8b03bdbc8b7e 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -18,13 +18,9 @@ */ import React from 'react'; -import { EuiFormErrorText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../../../plugins/kibana_react/public'; import { Sheet } from '../helpers/timelion_request_handler'; -import { TimelionVisDependencies } from '../plugin'; -import { PanelProps } from './panel'; +import { Panel } from './panel'; interface ChartComponentProp { interval: string; @@ -32,32 +28,12 @@ interface ChartComponentProp { seriesList: Sheet; } -function ChartComponent({ seriesList, interval, renderComplete }: ChartComponentProp) { - const kibana = useKibana(); - if (!seriesList) { +function ChartComponent(props: ChartComponentProp) { + if (!props.seriesList) { return null; } - const panelScope: PanelProps = { seriesList, interval, renderComplete }; - panelScope.seriesList.render = seriesList.render || { - type: 'timechart', - }; - - const panelSchema = kibana.services.timelionPanels.get(panelScope.seriesList.render.type); - - if (!panelSchema) { - return ( - - - - ); - } - - return panelSchema(panelScope); + return ; } export { ChartComponent }; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts index e0b27fd1d7741..c70caab8dd70c 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -17,7 +17,6 @@ * under the License. */ -export * from './panel'; export * from './timelion_expression_input'; export * from './timelion_interval'; export * from './timelion_vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx index dc4202cbbbf9e..6095ba28443b8 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -38,7 +38,7 @@ import { tickFormatters } from '../helpers/tick_formatters'; import { generateTicksProvider } from '../helpers/tick_generator'; import { TimelionVisDependencies } from '../plugin'; -export interface PanelProps { +interface PanelProps { interval: string; seriesList: Sheet; renderComplete(): void; @@ -215,8 +215,8 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { ); useEffect(() => { - updatePlot(chart, seriesList.render.grid); - }, [chart, updatePlot, seriesList.render.grid]); + updatePlot(chart, seriesList.render && seriesList.render.grid); + }, [chart, updatePlot, seriesList.render]); useEffect(() => { const colorsSet: Array<[Series, string]> = []; @@ -231,7 +231,7 @@ function Panel({ interval, seriesList, renderComplete }: PanelProps) { }); setChart(newChart); setOriginalColorMap(new Map(colorsSet)); - }, [seriesList.list, seriesList.render.grid]); + }, [seriesList.list]); const unhighlightSeries = useCallback(() => { if (highlightedSeries === null) { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 964b3e1b4270b..de066b474d987 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -47,8 +47,7 @@ export interface Series { export interface Sheet { list: Series[]; - render: { - type: string; + render?: { grid?: boolean; }; type: string; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts index b70705074d83a..69a2ad3c1351a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -30,7 +30,6 @@ import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/publ import { PluginsStart } from './legacy_imports'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; -import { getTimeChart, IPanelWrapper } from './timechart'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; @@ -43,7 +42,6 @@ export interface TimelionVisDependencies extends Partial { uiSettings: IUiSettingsClient; http: HttpSetup; timefilter: TimefilterContract; - timelionPanels: Map; } /** @internal */ @@ -61,15 +59,10 @@ export class TimelionVisPlugin implements Plugin { core: TimelionVisCoreSetup, { expressions, visualizations, data }: TimelionVisSetupDependencies ) { - const timelionPanels: Map = new Map(); - const [name, timeChartPanel] = getTimeChart(); - timelionPanels.set(name, timeChartPanel); - const dependencies: TimelionVisDependencies = { uiSettings: core.uiSettings, http: core.http, timefilter: data.query.timefilter.timefilter, - timelionPanels, }; expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx deleted file mode 100644 index 5e56f8c0730f2..0000000000000 --- a/src/legacy/core_plugins/vis_type_timelion/public/timechart.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { ReactElement } from 'react'; - -import { Panel, PanelProps } from './components'; - -export type IPanelWrapper = (props: PanelProps) => ReactElement; - -function getTimeChart(): [string, IPanelWrapper] { - return ['timechart', (props: PanelProps) => ]; -} - -export { getTimeChart }; From 99ecd4a260dfd49ffe7a132d976f19e6090ba7d1 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 17 Jan 2020 12:33:20 +0300 Subject: [PATCH 59/59] Refactoring of to_milliseconds.ts --- .../common/lib/to_milliseconds.ts | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts index 33e2bc392cf2b..f6fcb08b48b25 100644 --- a/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts @@ -17,35 +17,35 @@ * under the License. */ -import { transform, keys } from 'lodash'; +import { keys } from 'lodash'; import moment, { unitOfTime } from 'moment'; type Units = unitOfTime.Base | unitOfTime._quarter; type Values = { [key in Units]: number }; + // map of moment's short/long unit ids and elasticsearch's long unit ids // to their value in milliseconds -const vals = transform( - [ - ['ms', 'milliseconds', 'millisecond'], - ['s', 'seconds', 'second', 'sec'], - ['m', 'minutes', 'minute', 'min'], - ['h', 'hours', 'hour'], - ['d', 'days', 'day'], - ['w', 'weeks', 'week'], - ['M', 'months', 'month'], - ['quarter'], - ['y', 'years', 'year'], - ] as any, - // @ts-ignore - function(values: Values, units: Units[]) { - const normal = moment.normalizeUnits(units[0]) as Units; - const val = moment.duration(1, normal).asMilliseconds(); - ([] as Units[]).concat(normal, units).forEach((unit: Units) => { - values[unit] = val; - }); - }, - {} as Values -); +const unitMappings = [ + ['ms', 'milliseconds', 'millisecond'], + ['s', 'seconds', 'second', 'sec'], + ['m', 'minutes', 'minute', 'min'], + ['h', 'hours', 'hour'], + ['d', 'days', 'day'], + ['w', 'weeks', 'week'], + ['M', 'months', 'month'], + ['quarter'], + ['y', 'years', 'year'], +] as Units[][]; + +const vals = {} as Values; +unitMappings.forEach(units => { + const normal = moment.normalizeUnits(units[0]) as Units; + const val = moment.duration(1, normal).asMilliseconds(); + ([] as Units[]).concat(normal, units).forEach((unit: Units) => { + vals[unit] = val; + }); +}); + // match any key from the vals object preceded by an optional number const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + keys(vals).join('|') + ')$'); @@ -56,6 +56,6 @@ export function toMS(expr: string) { throw new Error('Invalid interval. 1M is only valid monthly interval.'); } - return parseFloat(match[1] || '1') * vals[match[2]]; + return parseFloat(match[1] || '1') * vals[match[2] as Units]; } }