diff --git a/src/legacy/core_plugins/timelion/index.js b/src/legacy/core_plugins/timelion/index.ts similarity index 80% rename from src/legacy/core_plugins/timelion/index.js rename to src/legacy/core_plugins/timelion/index.ts index 29f538dc9fcd6..77e62ed02718c 100644 --- a/src/legacy/core_plugins/timelion/index.js +++ b/src/legacy/core_plugins/timelion/index.ts @@ -19,28 +19,32 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; +import { Legacy } from 'kibana'; +import { LegacyPluginApi, LegacyPluginInitializer } from 'src/legacy/plugin_discovery/types'; +import { CoreSetup, PluginInitializerContext } from 'src/core/server'; import { plugin } from './server'; +import { CustomCoreSetup } from './server/plugin'; const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel', { defaultMessage: 'experimental', }); -export default function (kibana) { - return new kibana.Plugin({ +const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ require: ['kibana', 'elasticsearch'], - - config(Joi) { + config(Joi: any) { return Joi.object({ enabled: Joi.boolean().default(true), ui: Joi.object({ enabled: Joi.boolean().default(false), }).default(), - graphiteUrls: Joi.array().items( - Joi.string().uri({ scheme: ['http', 'https'] }), - ).default([]), + graphiteUrls: Joi.array() + .items(Joi.string().uri({ scheme: ['http', 'https'] })) + .default([]), }).default(); }, - + // @ts-ignore + // https://github.com/elastic/kibana/pull/44039#discussion_r326582255 uiCapabilities() { return { timelion: { @@ -48,7 +52,7 @@ export default function (kibana) { }, }; }, - + publicDir: resolve(__dirname, 'public'), uiExports: { app: { title: 'Timelion', @@ -58,11 +62,7 @@ export default function (kibana) { main: 'plugins/timelion/app', }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [ - 'plugins/timelion/hacks/toggle_app_link_in_nav', - 'plugins/timelion/lib/panel_registry', - 'plugins/timelion/panels/timechart/timechart', - ], + hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars(server) { const config = server.config(); @@ -71,13 +71,6 @@ export default function (kibana) { kbnIndex: config.get('kibana.index'), }; }, - visTypes: [ - 'plugins/timelion/vis', - ], - interpreter: ['plugins/timelion/timelion_vis_fn'], - home: [ - 'plugins/timelion/register_feature', - ], mappings: require('./mappings.json'), uiSettingDefaults: { 'timelion:showTutorial': { @@ -159,17 +152,19 @@ export default function (kibana) { value: '1ms', description: i18n.translate('timelion.uiSettings.minimumIntervalDescription', { defaultMessage: 'The smallest interval that will be calculated when using "auto"', - description: '"auto" is a technical value in that context, that should not be translated.', + description: + '"auto" is a technical value in that context, that should not be translated.', }), category: ['timelion'], }, 'timelion:graphite.url': { name: i18n.translate('timelion.uiSettings.graphiteURLLabel', { defaultMessage: 'Graphite URL', - description: 'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite', + description: + 'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite', }), - value: (server) => { - const urls = server.config().get('timelion.graphiteUrls'); + value: (server: Legacy.Server) => { + const urls = server.config().get('timelion.graphiteUrls') as string[]; if (urls.length === 0) { return null; } else { @@ -177,11 +172,12 @@ export default function (kibana) { } }, description: i18n.translate('timelion.uiSettings.graphiteURLDescription', { - defaultMessage: '{experimentalLabel} The URL of your graphite host', + defaultMessage: + '{experimentalLabel} The URL of your graphite host', values: { experimentalLabel: `[${experimentalLabel}]` }, }), type: 'select', - options: (server) => (server.config().get('timelion.graphiteUrls')), + options: (server: Legacy.Server) => server.config().get('timelion.graphiteUrls'), category: ['timelion'], }, 'timelion:quandl.key': { @@ -197,11 +193,13 @@ export default function (kibana) { }, }, }, - init: (server) => { - const initializerContext = {}; - const core = { http: { server } }; + init: (server: Legacy.Server) => { + const initializerContext = {} as PluginInitializerContext; + const core = { http: { server } } as CoreSetup & CustomCoreSetup; plugin(initializerContext).setup(core); }, }); -} + +// eslint-disable-next-line import/no-default-export +export default timelionPluginInitializer; diff --git a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js b/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js index 508cc5cd856d2..66ac56acc6262 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js +++ b/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js @@ -18,48 +18,53 @@ */ import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -describe('Tick Generator', function () { +import { generateTicksProvider } from '../panels/timechart/tick_generator'; +describe('Tick Generator', function () { let generateTicks; - const axes = [ - { - min: 0, - max: 5000, - delta: 100 - }, - { - min: 0, - max: 50000, - delta: 2000 - }, - { - min: 4096, - max: 6000, - delta: 250 - } - ]; - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - generateTicks = Private(require('plugins/timelion/panels/timechart/tick_generator')); - })); - it('returns a function', function () { - expect(generateTicks).to.be.a('function'); + beforeEach(function () { + generateTicks = generateTicksProvider(); + }); + + describe('generateTicksProvider()', function () { + it('should return a function', function () { + expect(generateTicks).to.be.a('function'); + }); }); - axes.forEach(axis => { - it(`generates ticks from ${axis.min} to ${axis.max}`, function () { - const ticks = generateTicks(axis); - 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)); - expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1)); + describe('generateTicks()', function () { + const axes = [ + { + min: 0, + max: 5000, + delta: 100 + }, + { + min: 0, + max: 50000, + delta: 2000 + }, + { + min: 4096, + max: 6000, + delta: 250 + } + ]; + + axes.forEach(axis => { + it(`generates ticks from ${axis.min} to ${axis.max}`, function () { + const ticks = generateTicks(axis); + 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)); + expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1)); + }); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js b/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js index 8e140b9bf992a..cce72773f6b62 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js +++ b/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js @@ -18,20 +18,19 @@ */ import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -describe('Tick Formatters', function () { +import { tickFormatters } from '../../services/tick_formatters'; - let tickFormatters; +describe('Tick Formatters', function () { + let formatters; - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - tickFormatters = Private(require('plugins/timelion/services/tick_formatters')); - })); + beforeEach(function () { + formatters = tickFormatters(); + }); describe('Bits mode', function () { let bitFormatter; beforeEach(function () { - bitFormatter = tickFormatters.bits; + bitFormatter = formatters.bits; }); it('is a function', function () { @@ -56,7 +55,7 @@ describe('Tick Formatters', function () { describe('Bits/s mode', function () { let bitsFormatter; beforeEach(function () { - bitsFormatter = tickFormatters['bits/s']; + bitsFormatter = formatters['bits/s']; }); it('is a function', function () { @@ -81,7 +80,7 @@ describe('Tick Formatters', function () { describe('Bytes mode', function () { let byteFormatter; beforeEach(function () { - byteFormatter = tickFormatters.bytes; + byteFormatter = formatters.bytes; }); it('is a function', function () { @@ -106,7 +105,7 @@ describe('Tick Formatters', function () { describe('Bytes/s mode', function () { let bytesFormatter; beforeEach(function () { - bytesFormatter = tickFormatters['bytes/s']; + bytesFormatter = formatters['bytes/s']; }); it('is a function', function () { @@ -131,7 +130,7 @@ describe('Tick Formatters', function () { describe('Currency mode', function () { let currencyFormatter; beforeEach(function () { - currencyFormatter = tickFormatters.currency; + currencyFormatter = formatters.currency; }); it('is a function', function () { @@ -163,7 +162,7 @@ describe('Tick Formatters', function () { describe('Percent mode', function () { let percentFormatter; beforeEach(function () { - percentFormatter = tickFormatters.percent; + percentFormatter = formatters.percent; }); it('is a function', function () { @@ -197,7 +196,7 @@ describe('Tick Formatters', function () { describe('Custom mode', function () { let customFormatter; beforeEach(function () { - customFormatter = tickFormatters.custom; + customFormatter = formatters.custom; }); it('is a function', function () { diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/chart.js b/src/legacy/core_plugins/timelion/public/directives/chart/chart.js index 7a898d7b3e161..0a95b489aa2e1 100644 --- a/src/legacy/core_plugins/timelion/public/directives/chart/chart.js +++ b/src/legacy/core_plugins/timelion/public/directives/chart/chart.js @@ -17,55 +17,52 @@ * under the License. */ import panelRegistryProvider from '../../lib/panel_registry'; - import { i18n } from '@kbn/i18n'; -require('ui/modules') - .get('apps/timelion', []) - .directive('chart', function (Private) { - return { - restrict: 'A', - scope: { - seriesList: '=chart', // The flot object, data, config and all - search: '=', // The function to execute to kick off a search - interval: '=', // Required for formatting x-axis ticks - rerenderTrigger: '=', - }, - link: function ($scope, $elem) { - - const panelRegistry = Private(panelRegistryProvider); - let panelScope = $scope.$new(true); - - function render() { - panelScope.$destroy(); +export function Chart(Private) { + return { + restrict: 'A', + scope: { + seriesList: '=chart', // The flot object, data, config and all + search: '=', // The function to execute to kick off a search + interval: '=', // Required for formatting x-axis ticks + rerenderTrigger: '=', + }, + link: function ($scope, $elem) { - if (!$scope.seriesList) return; + const panelRegistry = Private(panelRegistryProvider); + let panelScope = $scope.$new(true); - $scope.seriesList.render = $scope.seriesList.render || { - type: 'timechart' - }; + function render() { + panelScope.$destroy(); - const panelSchema = panelRegistry.byName[$scope.seriesList.render.type]; + if (!$scope.seriesList) return; - if (!panelSchema) { - $elem.text( - i18n.translate('timelion.chart.seriesList.noSchemaWarning', { - defaultMessage: 'No such panel type: {renderType}', - values: { renderType: $scope.seriesList.render.type }, - }) - ); - return; - } + $scope.seriesList.render = $scope.seriesList.render || { + type: 'timechart' + }; - panelScope = $scope.$new(true); - panelScope.seriesList = $scope.seriesList; - panelScope.interval = $scope.interval; - panelScope.search = $scope.search; + const panelSchema = panelRegistry.byName[$scope.seriesList.render.type]; - panelSchema.render(panelScope, $elem); + if (!panelSchema) { + $elem.text( + i18n.translate('timelion.chart.seriesList.noSchemaWarning', { + defaultMessage: 'No such panel type: {renderType}', + values: { renderType: $scope.seriesList.render.type }, + }) + ); + return; } - $scope.$watchGroup(['seriesList', 'rerenderTrigger'], render); + panelScope = $scope.$new(true); + panelScope.seriesList = $scope.seriesList; + panelScope.interval = $scope.interval; + panelScope.search = $scope.search; + + panelSchema.render(panelScope, $elem); } - }; - }); + + $scope.$watchGroup(['seriesList', 'rerenderTrigger'], render); + } + }; +} 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 498636df9250b..f7676d9267a52 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,9 +43,7 @@ import _ from 'lodash'; import $ from 'jquery'; import PEG from 'pegjs'; - import grammar from 'raw-loader!../chain.peg'; -import './timelion_expression_suggestions/timelion_expression_suggestions'; import timelionExpressionInputTemplate from './timelion_expression_input.html'; import { SUGGESTION_TYPE, @@ -57,9 +55,8 @@ import { comboBoxKeyCodes } from '@elastic/eui'; import { ArgValueSuggestionsProvider } from './timelion_expression_suggestions/arg_value_suggestions'; const Parser = PEG.generate(grammar); -const app = require('ui/modules').get('apps/timelion', []); -app.directive('timelionExpressionInput', function ($http, $timeout, Private) { +export function TimelionExpInput($http, $timeout, Private) { return { restrict: 'E', scope: { @@ -276,4 +273,4 @@ app.directive('timelionExpressionInput', function ($http, $timeout, Private) { init(); } }; -}); +} diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js index fc2bd094989ff..4ea6d19ff308b 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/timelion_expression_suggestions.js @@ -19,9 +19,7 @@ import template from './timelion_expression_suggestions.html'; -const app = require('ui/modules').get('apps/timelion', []); - -app.directive('timelionExpressionSuggestions', () => { +export function TimelionExpressionSuggestions() { return { restrict: 'E', scope: { @@ -38,4 +36,4 @@ app.directive('timelionExpressionSuggestions', () => { scope.onMouseDown = e => e.preventDefault(); } }; -}); +} diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js b/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js index 7323874e62f95..24247fd86a2ce 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_interval/timelion_interval.js @@ -19,11 +19,9 @@ import _ from 'lodash'; import $ from 'jquery'; - -const app = require('ui/modules').get('apps/timelion', []); import template from './timelion_interval.html'; -app.directive('timelionInterval', function ($timeout) { +export function TimelionInterval($timeout) { return { restrict: 'E', scope: { @@ -81,4 +79,5 @@ app.directive('timelionInterval', function ($timeout) { }); } }; -}); +} + diff --git a/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.ts b/src/legacy/core_plugins/timelion/public/index.ts similarity index 76% rename from src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.ts rename to src/legacy/core_plugins/timelion/public/index.ts index 6480ea0a69e43..6b42135884655 100644 --- a/src/legacy/core_plugins/timelion/public/hacks/toggle_app_link_in_nav.ts +++ b/src/legacy/core_plugins/timelion/public/index.ts @@ -17,9 +17,9 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; +import { PluginInitializerContext } from 'kibana/public'; +import { TimelionPlugin as Plugin } from './plugin'; -const timelionUiEnabled = npStart.core.injectedMetadata.getInjectedVar('timelionUiEnabled'); -if (timelionUiEnabled === false) { - npStart.core.chrome.navLinks.update('timelion', { hidden: true }); +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); } diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts new file mode 100644 index 0000000000000..8b8b5d4330b42 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -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 { PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; +import { plugin } from '.'; +import { setup as visualizations } from '../../visualizations/public/legacy'; +import { TimelionPluginSetupDependencies, TimelionPluginStartDependencies } from './plugin'; +// @ts-ignore +import panelRegistry from './lib/panel_registry'; +import { LegacyDependenciesPlugin } from './shim'; + +// Temporary solution +// It will be removed when all dependent services are migrated to the new platform. +const __LEGACY = new LegacyDependenciesPlugin(); + +const setupPlugins: Readonly = { + visualizations, + expressions: npSetup.plugins.expressions, + __LEGACY, +}; + +const startPlugins: Readonly = { + panelRegistry, + __LEGACY, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/core_plugins/timelion/public/panels/panel.ts b/src/legacy/core_plugins/timelion/public/panels/panel.ts new file mode 100644 index 0000000000000..3512cd96a9596 --- /dev/null +++ b/src/legacy/core_plugins/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/timelion/public/panels/timechart/schema.js b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts similarity index 71% rename from src/legacy/core_plugins/timelion/public/panels/timechart/schema.js rename to src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 20e3338ea74fd..a9b788ca93055 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.js +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -17,29 +17,36 @@ * under the License. */ -require('./flot'); +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 { timefilter } from 'ui/timefilter'; +import { TimelionStartDependencies } from '../../plugin'; +import { tickFormatters } from '../../services/tick_formatters'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { generateTicksProvider } from './tick_generator'; const DEBOUNCE_DELAY = 50; -export default function timechartFn(Private, config, $rootScope, $compile) { - return function () { +export function timechartFn(dependencies: TimelionStartDependencies) { + const { $rootScope, $compile, uiSettings } = dependencies; + return function() { return { help: 'Draw a timeseries chart', - render: function ($scope, $elem) { + render($scope: any, $elem: any) { const template = '
'; - const tickFormatters = require('plugins/timelion/services/tick_formatters')(); - const getxAxisFormatter = Private(require('plugins/timelion/panels/timechart/xaxis_formatter')); - const generateTicks = Private(require('plugins/timelion/panels/timechart/tick_generator')); + 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(config.get('dateFormat:tz')); + moment.tz.setDefault(uiSettings.get('dateFormat:tz')); const render = $scope.seriesList.render || {}; @@ -47,12 +54,12 @@ export default function timechartFn(Private, config, $rootScope, $compile) { $scope.interval = $scope.interval; $scope.search = $scope.search || _.noop; - let legendValueNumbers; - let legendCaption; + let legendValueNumbers: any; + let legendCaption: any; const debouncedSetLegendNumbers = _.debounce(setLegendNumbers, DEBOUNCE_DELAY, { maxWait: DEBOUNCE_DELAY, leading: true, - trailing: false + trailing: false, }); // ensure legend is the same height with or without a caption so legend items do not move around const emptyCaption = '
'; @@ -65,12 +72,12 @@ export default function timechartFn(Private, config, $rootScope, $compile) { }, selection: { mode: 'x', - color: '#ccc' + color: '#ccc', }, crosshair: { mode: 'x', color: '#C66', - lineWidth: 2 + lineWidth: 2, }, grid: { show: render.grid, @@ -78,13 +85,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) { borderColor: null, margin: 10, hoverable: true, - autoHighlight: false + autoHighlight: false, }, legend: { backgroundColor: 'rgb(255,255,255,0)', position: 'nw', labelBoxBorderColor: 'rgb(255,255,255,0)', - labelFormatter: function (label, series) { + labelFormatter(label: any, series: any) { const wrapperSpan = document.createElement('span'); const labelSpan = document.createElement('span'); const numberSpan = document.createElement('span'); @@ -103,13 +110,24 @@ export default function timechartFn(Private, config, $rootScope, $compile) { wrapperSpan.appendChild(numberSpan); return wrapperSpan.outerHTML; - } + }, }, - colors: ['#01A4A4', '#C66', '#D0D102', '#616161', '#00A1CB', '#32742C', '#F18D05', '#113F8C', '#61AE24', '#D70060'] + colors: [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', + ], }; const originalColorMap = new Map(); - $scope.chart.forEach((series, seriesIndex) => { + $scope.chart.forEach((series: any, seriesIndex: any) => { if (!series.color) { const colorIndex = seriesIndex % defaultOptions.colors.length; series.color = defaultOptions.colors[colorIndex]; @@ -117,8 +135,8 @@ export default function timechartFn(Private, config, $rootScope, $compile) { originalColorMap.set(series, series.color); }); - let highlightedSeries; - let focusedSeries; + let highlightedSeries: any; + let focusedSeries: any; function unhighlightSeries() { if (highlightedSeries === null) { return; @@ -126,18 +144,18 @@ export default function timechartFn(Private, config, $rootScope, $compile) { highlightedSeries = null; focusedSeries = null; - $scope.chart.forEach((series) => { + $scope.chart.forEach((series: any) => { series.color = originalColorMap.get(series); // reset the colors }); drawPlot($scope.chart); } - $scope.highlightSeries = _.debounce(function (id) { + $scope.highlightSeries = _.debounce(function(id: any) { if (highlightedSeries === id) { return; } highlightedSeries = id; - $scope.chart.forEach((series, seriesIndex) => { + $scope.chart.forEach((series: any, seriesIndex: any) => { if (seriesIndex !== id) { series.color = 'rgba(128,128,128,0.1)'; // mark as grey } else { @@ -146,58 +164,57 @@ export default function timechartFn(Private, config, $rootScope, $compile) { }); drawPlot($scope.chart); }, DEBOUNCE_DELAY); - $scope.focusSeries = function (id) { + $scope.focusSeries = function(id: any) { focusedSeries = id; $scope.highlightSeries(id); }; - $scope.toggleSeries = function (id) { + $scope.toggleSeries = function(id: any) { const series = $scope.chart[id]; series._hide = !series._hide; drawPlot($scope.chart); }; - const cancelResize = observeResize($elem, function () { + const cancelResize = observeResize($elem, function() { drawPlot($scope.chart); }); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', function() { cancelResize(); $elem.off('plothover'); $elem.off('plotselected'); $elem.off('mouseleave'); }); - $elem.on('plothover', function (event, pos, item) { + $elem.on('plothover', function(event: any, pos: any, item: any) { $rootScope.$broadcast('timelionPlotHover', event, pos, item); }); - $elem.on('plotselected', function (event, ranges) { + $elem.on('plotselected', function(event: any, ranges: any) { timefilter.setTime({ from: moment(ranges.xaxis.from), to: moment(ranges.xaxis.to), - mode: 'absolute', }); }); - $elem.on('mouseleave', function () { + $elem.on('mouseleave', function() { $rootScope.$broadcast('timelionPlotLeave'); }); - $scope.$on('timelionPlotHover', function (angularEvent, flotEvent, pos) { + $scope.$on('timelionPlotHover', function(angularEvent: any, flotEvent: any, pos: any) { if (!$scope.plot) return; $scope.plot.setCrosshair(pos); debouncedSetLegendNumbers(pos); }); - $scope.$on('timelionPlotLeave', function () { + $scope.$on('timelionPlotLeave', function() { if (!$scope.plot) return; $scope.plot.clearCrosshair(); clearLegendNumbers(); }); // Shamelessly borrowed from the flotCrosshairs example - function setLegendNumbers(pos) { + function setLegendNumbers(pos: any) { unhighlightSeries(); const plot = $scope.plot; @@ -211,10 +228,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) { let j; const dataset = plot.getData(); if (legendCaption) { - legendCaption.text(moment(pos.x).format(_.get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT))); + 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 precision = _.get(series, '_meta.precision', 2); @@ -248,13 +268,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) { if (legendCaption) { legendCaption.html(emptyCaption); } - _.each(legendValueNumbers, function (num) { + _.each(legendValueNumbers, function(num) { $(num).empty(); }); } let legendScope = $scope.$new(); - function drawPlot(plotConfig) { + function drawPlot(plotConfig: any) { if (!$('.chart-canvas', $elem).length) $elem.html(template); const canvasElem = $('.chart-canvas', $elem); @@ -264,56 +284,63 @@ export default function timechartFn(Private, config, $rootScope, $compile) { return; } - const title = _(plotConfig).map('_title').compact().last(); + const title = _(plotConfig) + .map('_title') + .compact() + .last() as any; $('.chart-top-title', $elem).text(title == null ? '' : title); - const options = _.cloneDeep(defaultOptions); + const options = _.cloneDeep(defaultOptions) as any; // Get the X-axis tick format - const time = timefilter.getBounds(); + const time = timefilter.getBounds() as any; const interval = calculateInterval( time.min.valueOf(), time.max.valueOf(), - config.get('timelion:target_buckets') || 200, + uiSettings.get('timelion:target_buckets') || 200, $scope.interval, - config.get('timelion:min_interval') || '1ms', + 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) { + 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 (series, index) { - series = _.cloneDeep(_.defaults(series, { - shadowSize: 0, - lines: { - lineWidth: 3 - } - })); - series._id = index; + options.xaxis.ticks = Math.floor( + $elem.width() / (format.length * tickLetterWidth + tickPadding) + ); - if (series.color) { + 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 = series.color; - series.color = span.style.color; + span.style.color = serie.color; + serie.color = span.style.color; } - if (series._hide) { - series.data = []; - series.stack = false; - //series.color = "#ddd"; - series.label = '(hidden) ' + series.label; + if (serie._hide) { + serie.data = []; + serie.stack = false; + // serie.color = "#ddd"; + serie.label = '(hidden) ' + serie.label; } - if (series._global) { - _.merge(options, series._global, function (objVal, srcVal) { + 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; @@ -321,13 +348,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) { }); } - return series; + return serie; }); if (options.yaxes) { - options.yaxes.forEach(yaxis => { + options.yaxes.forEach((yaxis: any) => { if (yaxis && yaxis.units) { - yaxis.tickFormatter = tickFormatters[yaxis.units.type]; + yaxis.tickFormatter = formatters[yaxis.units.type]; const byteModes = ['bytes', 'bytes/s']; if (byteModes.includes(yaxis.units.type)) { yaxis.tickGenerator = generateTicks; @@ -336,6 +363,7 @@ export default function timechartFn(Private, config, $rootScope, $compile) { }); } + // @ts-ignore $scope.plot = $.plot(canvasElem, _.compact(series), options); if ($scope.plot) { @@ -346,7 +374,7 @@ export default function timechartFn(Private, config, $rootScope, $compile) { legendScope = $scope.$new(); // Used to toggle the series, and for displaying values on hover legendValueNumbers = canvasElem.find('.ngLegendValueNumber'); - _.each(canvasElem.find('.ngLegendValue'), function (elem) { + _.each(canvasElem.find('.ngLegendValue'), function(elem) { $compile(elem)(legendScope); }); @@ -363,7 +391,7 @@ export default function timechartFn(Private, config, $rootScope, $compile) { } } $scope.$watch('chart', drawPlot); - } + }, }; }; } diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.js b/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts similarity index 81% rename from src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.js rename to src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts index 5a624964274b1..f7d696a0316db 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.js +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts @@ -17,13 +17,12 @@ * under the License. */ -export default function generateTicksProvider() { - - function floorInBase(n, base) { +export function generateTicksProvider() { + function floorInBase(n: any, base: any) { return base * Math.floor(n / base); } - function generateTicks(axis) { + function generateTicks(axis: any) { const returnTicks = []; let tickSize = 2; let delta = axis.delta; @@ -31,13 +30,13 @@ export default function generateTicksProvider() { let tickVal; let tickCount = 0; - //Count the steps + // Count the steps while (Math.abs(delta) >= 1024) { steps++; delta /= 1024; } - //Set the tick size relative to the remaining delta + // Set the tick size relative to the remaining delta while (tickSize <= 1024) { if (delta <= tickSize) { break; @@ -46,17 +45,17 @@ export default function generateTicksProvider() { } axis.tickSize = tickSize * Math.pow(1024, steps); - //Calculate the new ticks + // Calculate the new ticks const tickMin = floorInBase(axis.min, axis.tickSize); do { - tickVal = tickMin + (tickCount++) * axis.tickSize; + tickVal = tickMin + tickCount++ * axis.tickSize; returnTicks.push(tickVal); } while (tickVal < axis.max); return returnTicks; } - return function (axis) { + return function(axis: any) { return generateTicks(axis); }; } diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/timechart.js b/src/legacy/core_plugins/timelion/public/panels/timechart/timechart.ts similarity index 77% rename from src/legacy/core_plugins/timelion/public/panels/timechart/timechart.js rename to src/legacy/core_plugins/timelion/public/panels/timechart/timechart.ts index a3bee66f9d4ef..63eabd6cb0edf 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/timechart.js +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/timechart.ts @@ -17,12 +17,12 @@ * under the License. */ -import Panel from '../panel'; -import { i18n } from '@kbn/i18n'; -import panelRegistry from '../../lib/panel_registry'; +import { timechartFn } from './schema'; +import { Panel } from '../panel'; +import { TimelionStartDependencies } from '../../plugin'; -panelRegistry.register(function timeChartProvider(Private) { +export function getTimeChart(dependencies: TimelionStartDependencies) { // Schema is broken out so that it may be extended for use in other plugins // Its also easier to test. - return new Panel('timechart', Private(require('./schema'))(), i18n); -}); + return new Panel('timechart', timechartFn(dependencies)()); +} diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.js b/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts similarity index 91% rename from src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.js rename to src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts index 4e49c4eca6538..db3408dae33db 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.js +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts @@ -21,13 +21,12 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; -export default function xaxisFormatterProvider(config) { - - function getFormat(esInterval) { +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 ( + throw new Error( i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { defaultMessage: 'Unknown interval', }) @@ -49,7 +48,7 @@ export default function xaxisFormatterProvider(config) { return config.get('dateFormat'); } - return function (esInterval) { + 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 new file mode 100644 index 0000000000000..4adfac3726b95 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -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 { + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + PluginInitializerContext, + UiSettingsClientContract, + HttpSetup, +} from 'kibana/public'; +import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; +import { VisualizationsSetup } from '../../visualizations/public/np_ready'; +import { getTimelionVisualizationConfig } from './timelion_vis_fn'; +import { getTimelionVisualization } from './vis'; +import { getTimeChart } from './panels/timechart/timechart'; +import { LegacyDependenciesPlugin, LegacyDependenciesPluginStart } from './shim'; + +/** @internal */ +export interface TimelionPluginSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + + // Temporary solution + __LEGACY: LegacyDependenciesPlugin; +} + +/** @internal */ +export interface TimelionPluginStartDependencies { + panelRegistry: any; + + // Temporary solution + __LEGACY: LegacyDependenciesPlugin; +} + +/** @private */ +export interface TimelionVisualizationDependencies { + uiSettings: UiSettingsClientContract; + http: HttpSetup; +} + +/** @internal */ +export type TimelionStartDependencies = Pick & + LegacyDependenciesPluginStart; + +/** @internal */ +export class TimelionPlugin + implements + Plugin, void, TimelionPluginSetupDependencies, TimelionPluginStartDependencies> { + initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public async setup(core: CoreSetup, plugins: TimelionPluginSetupDependencies) { + const dependencies: TimelionVisualizationDependencies = { + uiSettings: core.uiSettings, + http: core.http, + }; + + plugins.__LEGACY.setup(); + plugins.expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); + plugins.visualizations.types.registerVisualization(() => + getTimelionVisualization(dependencies) + ); + } + + public async start(core: CoreStart & LegacyCoreStart, plugins: TimelionPluginStartDependencies) { + const dependencies: TimelionStartDependencies = { + uiSettings: core.uiSettings, + ...(await plugins.__LEGACY.start()), + }; + const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); + + if (timelionUiEnabled === false) { + core.chrome.navLinks.update('timelion', { hidden: true }); + } + + plugins.panelRegistry.register(() => getTimeChart(dependencies)); + } + + public stop(): void {} +} diff --git a/src/legacy/core_plugins/timelion/public/register_feature.js b/src/legacy/core_plugins/timelion/public/register_feature.ts similarity index 87% rename from src/legacy/core_plugins/timelion/public/register_feature.js rename to src/legacy/core_plugins/timelion/public/register_feature.ts index 415ed532f6a34..7dd44b58bd1d7 100644 --- a/src/legacy/core_plugins/timelion/public/register_feature.js +++ b/src/legacy/core_plugins/timelion/public/register_feature.ts @@ -17,14 +17,10 @@ * under the License. */ -import { - FeatureCatalogueRegistryProvider, - FeatureCatalogueCategory, -} from 'ui/registry/feature_catalogue'; - +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { i18n } from '@kbn/i18n'; -FeatureCatalogueRegistryProvider.register(() => { +export const registerFeature = () => { return { id: 'timelion', title: 'Timelion', @@ -37,4 +33,4 @@ FeatureCatalogueRegistryProvider.register(() => { showOnHomePage: false, category: FeatureCatalogueCategory.DATA, }; -}); +}; diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.js b/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts similarity index 70% rename from src/legacy/core_plugins/timelion/public/services/tick_formatters.js rename to src/legacy/core_plugins/timelion/public/services/tick_formatters.ts index 2d0a726ad53d0..2c78d2423cc06 100644 --- a/src/legacy/core_plugins/timelion/public/services/tick_formatters.js +++ b/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; -function baseTickFormatter(value, axis) { +function baseTickFormatter(value: any, axis: any) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -30,15 +30,18 @@ function baseTickFormatter(value, axis) { 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 ( + (precision ? formatted : formatted + '.') + + ('' + factor).substr(1, axis.tickDecimals - precision) + ); } } return formatted; } -function unitFormatter(divisor, units) { - return (val) => { +function unitFormatter(divisor: any, units: any) { + return (val: any) => { let index = 0; const isNegative = val < 0; val = Math.abs(val); @@ -46,22 +49,26 @@ function unitFormatter(divisor, units) { val /= divisor; index++; } - const value = Math.round(val * 100) / 100 * (isNegative ? -1 : 1); + const value = (Math.round(val * 100) / 100) * (isNegative ? -1 : 1); return `${value}${units[index]}`; }; } -export default function tickFormatters() { - const formatters = { - 'bits': unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), +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: 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': function (val, axis) { - return val.toLocaleString('en', { style: 'currency', currency: axis.options.units.prefix || 'USD' }); + currency(val: any, axis: any) { + return val.toLocaleString('en', { + style: 'currency', + currency: axis.options.units.prefix || 'USD', + }); }, - 'percent': function (val, axis) { - let precision = _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); + 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; @@ -71,12 +78,12 @@ export default function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - 'custom': function (val, axis) { + 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/panels/panel.js b/src/legacy/core_plugins/timelion/public/shim/index.ts similarity index 68% rename from src/legacy/core_plugins/timelion/public/panels/panel.js rename to src/legacy/core_plugins/timelion/public/shim/index.ts index f45aeb08e31fe..cfc7b62ff4f86 100644 --- a/src/legacy/core_plugins/timelion/public/panels/panel.js +++ b/src/legacy/core_plugins/timelion/public/shim/index.ts @@ -17,22 +17,4 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; - -export default function Panel(name, config) { - - 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 * from './legacy_dependencies_plugin'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_controller.js b/src/legacy/core_plugins/timelion/public/shim/legacy_dependencies_plugin.ts similarity index 55% rename from src/legacy/core_plugins/timelion/public/vis/timelion_vis_controller.js rename to src/legacy/core_plugins/timelion/public/shim/legacy_dependencies_plugin.ts index d621dd4b9cd8f..82c908e78ce7b 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_controller.js +++ b/src/legacy/core_plugins/timelion/public/shim/legacy_dependencies_plugin.ts @@ -17,18 +17,28 @@ * under the License. */ +import chrome from 'ui/chrome'; +import { Plugin } from 'kibana/public'; +import { initTimelionLegacyModule } from './timelion_legacy_module'; -import '../directives/chart/chart'; -import '../directives/timelion_interval/timelion_interval'; -import 'ui/state_management/app_state'; +/** @internal */ +export interface LegacyDependenciesPluginStart { + $rootScope: any; + $compile: any; +} -import { uiModules } from 'ui/modules'; +export class LegacyDependenciesPlugin + implements Plugin> { + public setup() { + initTimelionLegacyModule(); + } -uiModules - .get('kibana/timelion_vis', ['kibana']) - .controller('TimelionVisController', function ($scope) { - $scope.$on('timelionChartRendered', event => { - event.stopPropagation(); - $scope.renderComplete(); - }); - }); + public async start() { + const $injector = await chrome.dangerouslyGetActiveInjector(); + + return { + $rootScope: $injector.get('$rootScope'), + $compile: $injector.get('$compile'), + } as LegacyDependenciesPluginStart; + } +} diff --git a/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.ts new file mode 100644 index 0000000000000..c461973172865 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/shim/timelion_legacy_module.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 'ngreact'; +import 'brace/mode/hjson'; +import 'brace/ext/searchbox'; +import 'ui/accessibility/kbn_ui_ace_keyboard_mode'; +import 'ui/vis/map/service_settings'; + +import { once } from 'lodash'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +// @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 = once((): void => { + require('ui/state_management/app_state'); + + uiModules + .get('apps/timelion', []) + .controller('TimelionVisController', function($scope: any) { + $scope.$on('timelionChartRendered', (event: any) => { + event.stopPropagation(); + $scope.renderComplete(); + }); + }) + .directive('chart', Chart) + .directive('timelionInterval', TimelionInterval) + .directive('timelionExpressionSuggestions', TimelionExpressionSuggestions) + .directive('timelionExpressionInput', TimelionExpInput); +}); diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.js b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts similarity index 55% rename from src/legacy/core_plugins/timelion/public/timelion_vis_fn.js rename to src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts index 9161715d28022..86bc9a8088217 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.js +++ b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts @@ -17,51 +17,65 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { TimelionRequestHandlerProvider } from './vis/timelion_request_handler'; +import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; +import { getTimelionRequestHandler, TimelionSuccessResponse } from './vis/timelion_request_handler'; +import { TimelionVisualizationDependencies } from './plugin'; +const name = 'timelion_vis'; -import chrome from 'ui/chrome'; +interface Arguments { + expression: string; + interval: any; +} -export const timelionVis = () => ({ - name: 'timelion_vis', +interface RenderValue { + visData: Context; + visType: typeof name; + 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', - ], + types: ['kibana_context', 'null'], }, help: i18n.translate('timelion.function.help', { - defaultMessage: 'Timelion visualization' + defaultMessage: 'Timelion visualization', }), args: { expression: { types: ['string'], aliases: ['_'], default: '".es(*)"', + help: '', }, interval: { types: ['string', 'null'], default: 'auto', - } + help: '', + }, }, async fn(context, args) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - const timelionRequestHandler = Private(TimelionRequestHandlerProvider).handler; + const timelionRequestHandler = getTimelionRequestHandler(dependencies); const visParams = { expression: args.expression, interval: args.interval }; - const response = await timelionRequestHandler({ - timeRange: get(context, 'timeRange', null), - query: get(context, 'query', null), - filters: get(context, 'filters', null), + const response = (await timelionRequestHandler({ + timeRange: get(context, 'timeRange'), + query: get(context, 'query'), + filters: get(context, 'filters'), + visParams, forceFetch: true, - visParams: visParams, - }); + })) as TimelionSuccessResponse; response.visType = 'timelion'; @@ -70,11 +84,9 @@ export const timelionVis = () => ({ as: 'visualization', value: { visParams, - visType: 'timelion', + visType: name, visData: response, }, }; }, }); - -functionsRegistry.register(timelionVis); diff --git a/src/legacy/core_plugins/timelion/public/vis/index.js b/src/legacy/core_plugins/timelion/public/vis/index.ts similarity index 73% rename from src/legacy/core_plugins/timelion/public/vis/index.js rename to src/legacy/core_plugins/timelion/public/vis/index.ts index f06076a3600a3..e7f1c91a3393d 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.js +++ b/src/legacy/core_plugins/timelion/public/vis/index.ts @@ -17,25 +17,19 @@ * under the License. */ -import { visFactory } from 'ui/vis/vis_factory'; import { i18n } from '@kbn/i18n'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { TimelionRequestHandlerProvider } from './timelion_request_handler'; +// @ts-ignore import { DefaultEditorSize } from 'ui/vis/editor_size'; -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; - -// we also need to load the controller and directive used by the template -import './timelion_vis_controller'; -import '../directives/timelion_expression_input'; - +import { visFactory } from '../../../visualizations/public'; +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'; -// register the provider with the visTypes registry so that other know it exists -VisTypesRegistryProvider.register(TimelionVisProvider); - -export default function TimelionVisProvider(Private) { - const timelionRequestHandler = Private(TimelionRequestHandlerProvider); +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. @@ -50,7 +44,7 @@ export default function TimelionVisProvider(Private) { visConfig: { defaults: { expression: '.es(*)', - interval: 'auto' + interval: 'auto', }, template: visConfigTemplate, }, @@ -58,7 +52,7 @@ export default function TimelionVisProvider(Private) { optionsTemplate: editorConfigTemplate, defaultSize: DefaultEditorSize.MEDIUM, }, - requestHandler: timelionRequestHandler.handler, + requestHandler: timelionRequestHandler, responseHandler: 'none', options: { showIndexSelection: false, diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.js b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.js deleted file mode 100644 index 21b6a243e77d9..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.js +++ /dev/null @@ -1,71 +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 { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; -import { timezoneProvider } from 'ui/vis/lib/timezone'; -import { toastNotifications } from 'ui/notify'; -import { i18n } from '@kbn/i18n'; - -const TimelionRequestHandlerProvider = function (Private, $http, config) { - const timezone = Private(timezoneProvider)(); - - return { - name: 'timelion', - handler: function ({ timeRange, filters, query, visParams }) { - - return new Promise((resolve, reject) => { - const expression = visParams.expression; - if (!expression) return; - const esQueryConfigs = getEsQueryConfig(config); - const httpResult = $http.post('../api/timelion/run', { - sheet: [expression], - extended: { - es: { - filter: buildEsQuery(undefined, query, filters, esQueryConfigs) - } - }, - time: _.extend(timeRange, { - interval: visParams.interval, - timezone: timezone - }), - }) - .then(resp => resp.data) - .catch(resp => { throw resp.data; }); - - httpResult - .then(function (resp) { - resolve(resp); - }) - .catch(function (resp) { - const err = new Error(resp.message); - err.stack = resp.stack; - toastNotifications.addError(err, { - title: i18n.translate('timelion.requestHandlerErrorTitle', { - defaultMessage: 'Timelion request error', - }), - }); - reject(err); - }); - }); - } - }; -}; - -export { TimelionRequestHandlerProvider }; 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 new file mode 100644 index 0000000000000..1fbf371c8f91e --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { buildEsQuery, getEsQueryConfig, Filter } from '@kbn/es-query'; +// @ts-ignore +import { timezoneProvider } from 'ui/vis/lib/timezone'; +import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; +import { Query } from 'src/legacy/core_plugins/data/public'; +import { TimeRange } from 'src/plugins/data/public'; +import { VisParams } from 'ui/vis'; +import { toastNotifications } from 'ui/notify'; +import { i18n } from '@kbn/i18n'; +import { TimelionVisualizationDependencies } from '../plugin'; + +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 } = dependencies; + const timezone = timezoneProvider(uiSettings)(); + + return async function({ + timeRange, + filters, + query, + visParams, + }: { + timeRange: TimeRange; + filters: Filter[]; + query: Query; + visParams: VisParams; + forceFetch?: boolean; + }): Promise { + const expression = visParams.expression; + + if (!expression) return; + + const esQueryConfigs = getEsQueryConfig(uiSettings); + + try { + return await http.post('../api/timelion/run', { + body: JSON.stringify({ + sheet: [expression], + extended: { + es: { + filter: buildEsQuery(undefined, query, filters, esQueryConfigs), + }, + }, + time: { ...timeRange, interval: visParams.interval, timezone }, + }), + }); + } catch (e) { + const err = new Error(e.data.message); + + err.stack = e.data.stack; + + toastNotifications.addError(err, { + title: i18n.translate('timelion.requestHandlerErrorTitle', { + defaultMessage: 'Timelion request error', + }), + }); + } + }; +} diff --git a/src/legacy/plugin_discovery/types.ts b/src/legacy/plugin_discovery/types.ts index 6d7c59893dfe6..c14daa37f5706 100644 --- a/src/legacy/plugin_discovery/types.ts +++ b/src/legacy/plugin_discovery/types.ts @@ -73,6 +73,7 @@ export interface LegacyPluginOptions { visTypes: string[]; embeddableActions?: string[]; embeddableFactories?: string[]; + uiSettingDefaults?: Record; }>; uiCapabilities?: Capabilities; publicDir: any; diff --git a/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts b/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts index 92debafded76e..0c76e5e83a5ee 100644 --- a/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts +++ b/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts @@ -21,6 +21,7 @@ import { Filter } from '@kbn/es-query'; import { TimeRange, Query } from 'src/plugins/data/public'; const name = 'kibana_context'; +export type KIBANA_CONTEXT_NAME = 'kibana_context'; export interface KibanaContext { type: typeof name;