diff --git a/.eslintignore b/.eslintignore index e5b17567b562cf..c4fb806b6d394c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -16,7 +16,7 @@ src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/moc /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 6986d36e8e94f5..73acf92cda1491 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/package.json b/package.json index 9d2068b02bb934..430ab9e1ba77d7 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,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/renovate.json5 b/renovate.json5 index 7f67fae8941104..5af62d0acef854 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -281,6 +281,14 @@ '@types/file-saver', ], }, + { + groupSlug: 'flot', + groupName: 'flot related packages', + packageNames: [ + 'flot', + '@types/flot', + ], + }, { groupSlug: 'getopts', groupName: 'getopts related packages', diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 7ef722ee3a277c..084e497761e438 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/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index 231330b898edb5..ea2d44bcaefe07 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,10 +19,13 @@ 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 '../../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/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index 6ee2f81539032f..cd46a1a0a369e7 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 33c188decd4f17..00000000000000 --- 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/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 449c0489fea251..1fec243a277f85 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, @@ -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/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index 7ccc6c300bc40b..ebf000d160b546 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -11,6 +11,4 @@ // timChart__legend-isLoading @import './app'; -@import './components/index'; @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 1cf6bb65cdc029..63030fcbce3873 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -20,15 +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, - // Temporary solution // It will be removed when all dependent services are migrated to the new platform. __LEGACY: new LegacyDependenciesPlugin(), @@ -37,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/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 0bbda4bf3646fc..57ee99f5268b08 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -17,19 +17,18 @@ * under the License. */ -import './flot'; +import '../../../../vis_type_timelion/public/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 { 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 './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; +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/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 42f0ee3ad47258..636b8bf8e128aa 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -22,33 +22,19 @@ import { 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'; -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'; -import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { uiSettings: IUiSettingsClient; - http: HttpSetup; timelionPanels: Map; - timefilter: TimefilterContract; } /** @internal */ export interface TimelionPluginSetupDependencies { - expressions: ReturnType; - visualizations: VisualizationsSetup; - data: DataPublicPluginSetup; - // Temporary solution __LEGACY: LegacyDependenciesPlugin; } @@ -61,24 +47,16 @@ export class TimelionPlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup( - core: CoreSetup, - { __LEGACY, expressions, visualizations, 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)); - visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { @@ -87,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/vis/_index.scss b/src/legacy/core_plugins/timelion/public/vis/_index.scss deleted file mode 100644 index 17a2018f7a56a8..00000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './timelion_vis'; -@import './timelion_editor'; 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 8bfb9e91a2523e..00000000000000 --- 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/server/handlers/chain_runner.js b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js index 9514e479d36f48..9056362cb723a2 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/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js index 74ef76d1a50cde..4957d3cb78b85e 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/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js index 8b1f8998557be5..db924e33be5e9b 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/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 798902aa133dee..08358b9d81f781 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/holt/index.js b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js index 970d146c45b917..0cc41df933e8c8 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/legend.js b/src/legacy/core_plugins/timelion/server/series_functions/legend.js index b4673186867296..fd9ff53a1391f9 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/server/series_functions/movingaverage.js b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js index 361cd1f9dfb670..a4b458991c1bc0 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 778c91d30f2cb2..b604015624dfd6 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/timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts similarity index 78% 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 7c6b3c2816e67e..328c634ea51537 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,11 @@ * under the License. */ -import toMS from '../../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. -function roundInterval(interval) { +function roundInterval(interval: number) { switch (true) { case interval <= 500: // <= 0.5s return '100ms'; @@ -58,9 +58,24 @@ function roundInterval(interval) { } } -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; +export function calculateInterval( + from: number, + to: number, + size: number, + interval: string, + min: string +) { + if (interval !== 'auto') { + return interval; + } + + const dateMathInterval: string = roundInterval((to - from) / size); + const dateMathIntervalMs = toMS(dateMathInterval); + const minMs = toMS(min); + + if (dateMathIntervalMs !== undefined && minMs !== undefined && dateMathIntervalMs < minMs) { + 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 95% rename from src/legacy/core_plugins/timelion/common/lib/index.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts index 927331043f0b37..1901b8224f6075 100644 --- a/src/legacy/core_plugins/timelion/common/lib/index.js +++ 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 55% 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 0d62d848daba5b..f6fcb08b48b25b 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,42 +17,45 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +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'], - ], - 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 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('|') + ')$'); +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] as Units]; } } 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/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts new file mode 100644 index 00000000000000..4664bebb4f38a2 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -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 { 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', 'visualizations', 'data'], + 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 00000000000000..9b09f98ce6cafa --- /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/timelion/public/vis/_timelion_editor.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss 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/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 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 f2458a367e1768..1d887f43ff9a1c 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 './panel'; @import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss similarity index 97% rename from src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss index 39713cd05ab37d..c4d591bc82cad9 100644 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss @@ -33,6 +33,7 @@ .ngLegendValue { color: $euiTextColor; + cursor: pointer; &:focus, &:hover { 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/chart.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx new file mode 100644 index 00000000000000..a8b03bdbc8b7e5 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -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 React from 'react'; + +import { Sheet } from '../helpers/timelion_request_handler'; +import { Panel } from './panel'; + +interface ChartComponentProp { + interval: string; + renderComplete(): void; + seriesList: Sheet; +} + +function ChartComponent(props: ChartComponentProp) { + if (!props.seriesList) { + return null; + } + + return ; +} + +export { ChartComponent }; diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts similarity index 96% rename from src/legacy/core_plugins/timelion/public/components/index.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/index.ts index 8d7d32a3ba2627..c70caab8dd70cf 100644 --- a/src/legacy/core_plugins/timelion/public/components/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -19,3 +19,4 @@ 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 new file mode 100644 index 00000000000000..6095ba28443b8d --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -0,0 +1,386 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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, { useState, useEffect, useMemo, useCallback } from 'react'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; + +import { useKibana } from '../../../../../plugins/kibana_react/public'; +import '../flot'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; + +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'; + +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, seriesList, renderComplete }: PanelProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const [canvasElem, setCanvasElem] = useState(); + const [chartElem, setChartElem] = useState(); + + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); + + const [highlightedSeries, setHighlightedSeries] = useState(null); + 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); + } + }, []); + + const elementRef = useCallback(node => { + if (node !== null) { + setChartElem(node); + } + }, []); + + useEffect( + () => () => { + $(chartElem) + .off('plotselected') + .off('plothover') + .off('mouseleave'); + }, + [chartElem] + ); + + const highlightSeries = useCallback( + debounce(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + if (highlightedSeries === id) { + return; + } + + setHighlightedSeries(id); + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + series.color = + seriesIndex === id + ? originalColorMap.get(series) // color it like it was + : 'rgba(128,128,128,0.1)'; // mark as grey + + return series; + }) + ); + }, DEBOUNCE_DELAY), + [originalColorMap, highlightedSeries] + ); + + const focusSeries = useCallback( + (event: JQuery.TriggeredEvent) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); + setFocusedSeries(id); + highlightSeries(event); + }, + [highlightSeries] + ); + + const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + if (seriesIndex === id) { + series._hide = !series._hide; + } + return series; + }) + ); + }, []); + + 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 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); + + 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(); + + updateCaption(newPlot.getData()); + } + }, + [canvasElem, chartElem, renderComplete, kibana.services, interval, updateCaption] + ); + + useEffect(() => { + updatePlot(chart, seriesList.render && seriesList.render.grid); + }, [chart, updatePlot, seriesList.render]); + + useEffect(() => { + 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]); + + const unhighlightSeries = useCallback(() => { + if (highlightedSeries === null) { + return; + } + + setHighlightedSeries(null); + setFocusedSeries(null); + + setChart(chartState => + chartState.map((series: Series) => { + series.color = originalColorMap.get(series); // reset the colors + return series; + }) + ); + }, [originalColorMap, highlightedSeries]); + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = useCallback( + (pos: Position) => { + 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: [number, number], 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(Number(label), series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + }, + [plot, legendValueNumbers, unhighlightSeries, legendCaption] + ); + + 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: Node) => { + $(num).empty(); + }); + }, [legendCaption, legendValueNumbers]); + + const plotHoverHandler = useCallback( + (event: JQuery.TriggeredEvent, pos: Position) => { + if (!plot) { + return; + } + plot.setCrosshair(pos); + debouncedSetLegendNumbers(pos); + }, + [plot, debouncedSetLegendNumbers] + ); + const mouseLeaveHandler = useCallback(() => { + if (!plot) { + return; + } + plot.clearCrosshair(); + clearLegendNumbers(); + }, [plot, clearLegendNumbers]); + + 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) { + $(chartElem) + .off('plotselected') + .on('plotselected', plotSelectedHandler); + } + }, [chartElem, plotSelectedHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('mouseleave') + .on('mouseleave', mouseLeaveHandler); + } + }, [chartElem, mouseLeaveHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plothover') + .on('plothover', plotHoverHandler); + } + }, [chartElem, plotHoverHandler]); + + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); + + return ( +
+
{title}
+
+
+ ); +} + +export { Panel }; 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 98% 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 c695d09ca822bd..fa79e4eb6871a9 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 @@ -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 { getArgValueSuggestions } from '../services/arg_value_suggestions'; +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/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 fc90c276eeca2f..5aa05fb16466bb 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 @@ -26,7 +26,7 @@ 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 { 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 98% 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 index 6294e51e54788d..4bfa5d424ed852 100644 --- a/src/legacy/core_plugins/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/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx new file mode 100644 index 00000000000000..ae55e11380b78e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -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 React from 'react'; + +import { IUiSettingsClient } from 'kibana/public'; +import { Vis } from '../legacy_imports'; +import { ChartComponent } from './chart'; +import { VisParams } from '../timelion_vis_fn'; +import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; + +export interface TimelionVisComponentProp { + config: IUiSettingsClient; + renderComplete(): void; + updateStatus: object; + vis: Vis; + visData: TimelionSuccessResponse; + visParams: VisParams; +} + +function TimelionVisComponent(props: TimelionVisComponentProp) { + return ( +
+ +
+ ); +} + +export { TimelionVisComponent }; diff --git a/src/legacy/core_plugins/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/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/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts similarity index 100% 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 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 new file mode 100644 index 00000000000000..db29d9112be8e8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -0,0 +1,187 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { cloneDeep, defaults, merge, compact } from 'lodash'; +import moment, { Moment } from 'moment-timezone'; + +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; + +import { calculateInterval } from '../../common/lib'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { Series } from './timelion_request_handler'; + +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 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: jquery.flot.plotOptions) { + const seriesData = 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; + }); + + return compact(seriesData); +} + +function buildOptions( + intervalValue: string, + timefilter: TimefilterContract, + uiSettings: IUiSettingsClient, + clientWidth = 0, + showGrid?: boolean +) { + // Get the X-axis tick format + const time: TimeRangeBounds = timefilter.getBounds(); + const interval = calculateInterval( + (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' + ); + const format = xaxisFormatterProvider(uiSettings)(interval); + + const tickLetterWidth = 7; + const tickPadding = 45; + + const options = { + 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: { _id: number }) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + + return options; +} + +export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; 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/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 40840c4cd2610e..01734f2f5888a2 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/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts similarity index 79% 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 2c78d2423cc06f..c80f9c3ed5f4b1 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 @@ -17,9 +17,11 @@ * under the License. */ -import _ from 'lodash'; +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,8 +42,8 @@ function baseTickFormatter(value: any, axis: any) { return formatted; } -function unitFormatter(divisor: any, units: any) { - return (val: any) => { +function unitFormatter(divisor: number, units: string[]) { + return (val: number) => { let index = 0; const isNegative = val < 0; val = Math.abs(val); @@ -55,20 +57,20 @@ 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: Axis) { return val.toLocaleString('en', { style: 'currency', - currency: axis.options.units.prefix || 'USD', + currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: any, axis: any) { + percent(val: number, axis: Axis) { 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; @@ -78,13 +80,11 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: any, 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; }, }; - - return formatters; } 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 e42657374af4cc..d1d959dee9501d 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)); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts similarity index 82% rename from src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts index f7d696a0316db1..6321ad01418ac0 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/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/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts similarity index 83% rename from src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 14cd3d0083e6ab..de066b474d987f 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -17,13 +17,11 @@ * 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'; +import { TimelionVisDependencies } from '../plugin'; interface Stats { cacheCount: number; @@ -33,9 +31,25 @@ 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?: { + grid?: boolean; + }; type: string; } @@ -46,8 +60,11 @@ export interface TimelionSuccessResponse { type: KIBANA_CONTEXT_NAME; } -export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; +export function getTimelionRequestHandler({ + uiSettings, + http, + timefilter, +}: TimelionVisDependencies) { const timezone = timezoneProvider(uiSettings)(); return async function({ diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts similarity index 86% rename from src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts index db3408dae33db4..5350b1cb26957c 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/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/index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss new file mode 100644 index 00000000000000..313f14a8acf69b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.scss @@ -0,0 +1,5 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './timelion_vis'; +@import './timelion_editor'; +@import './components/index'; diff --git a/src/legacy/core_plugins/timelion/public/__tests__/index.js b/src/legacy/core_plugins/vis_type_timelion/public/index.ts similarity index 76% rename from src/legacy/core_plugins/timelion/public/__tests__/index.js rename to src/legacy/core_plugins/vis_type_timelion/public/index.ts index 9cd61c3665a1a6..98cc35877094e3 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.ts @@ -17,5 +17,9 @@ * under the License. */ -import './_tick_generator.js'; -describe('Timelion', function() {}); +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/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts new file mode 100644 index 00000000000000..9935f3d92f6bd1 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -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 { PluginInitializerContext } from 'kibana/public'; + +import { npSetup, npStart } from './legacy_imports'; + +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +import { TimelionVisSetupDependencies } from './plugin'; +import { plugin } from '.'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); 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 00000000000000..8d1156862d27ee --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts @@ -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. + */ + +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 new file mode 100644 index 00000000000000..69a2ad3c1351ae --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -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. + */ + +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 './legacy_imports'; +import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; + +import { getTimelionVisualizationConfig } from './timelion_vis_fn'; +import { getTimelionVisDefinition } from './timelion_vis_type'; +import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; + +type TimelionVisCoreSetup = CoreSetup; + +/** @internal */ +export interface TimelionVisDependencies extends Partial { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timefilter: TimefilterContract; +} + +/** @internal */ +export interface TimelionVisSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export class TimelionVisPlugin implements Plugin { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: TimelionVisCoreSetup, + { expressions, visualizations, data }: TimelionVisSetupDependencies + ) { + const dependencies: TimelionVisDependencies = { + uiSettings: core.uiSettings, + http: core.http, + timefilter: data.query.timefilter.timefilter, + }; + + expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); + visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); + } + + public start(core: CoreStart, plugins: PluginsStart) { + setIndexPatterns(plugins.data.indexPatterns); + setSavedObjectsClient(core.savedObjects.client); + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx similarity index 89% rename from src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 527fcc3bc6ce87..be6829a76ac588 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,9 +20,9 @@ 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 } from '../components'; +import { VisOptionsProps } from './legacy_imports'; +import { VisParams } from './timelion_vis_fn'; +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/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts similarity index 91% rename from src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 206f9f5d8368da..8a517b6cecbc73 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -20,9 +20,9 @@ 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 { TimelionVisualizationDependencies } from './plugin'; -import { TIMELION_VIS_NAME } from './vis'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TIMELION_VIS_NAME } from './timelion_vis_type'; +import { TimelionVisDependencies } from './plugin'; const name = 'timelion_vis'; @@ -42,7 +42,7 @@ export type VisParams = Arguments; type Return = Promise>; export const getTimelionVisualizationConfig = ( - dependencies: TimelionVisualizationDependencies + dependencies: TimelionVisDependencies ): ExpressionFunction => ({ name, type: 'render', diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx similarity index 73% rename from src/legacy/core_plugins/timelion/public/vis/index.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 11d1a7385c408c..51540eea0223c9 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -20,20 +20,17 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { DefaultEditorSize } from '../../../visualizations/public'; -import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import visConfigTemplate from './timelion_vis.html'; -import { TimelionVisualizationDependencies } from '../plugin'; -// @ts-ignore -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; +import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; +import { DefaultEditorSize, VisOptionsProps } from './legacy_imports'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TimelionVisComponent, TimelionVisComponentProp } from './components'; import { TimelionOptions } from './timelion_options'; -import { VisParams } from '../timelion_vis_fn'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionVisDependencies } from './plugin'; export const TIMELION_VIS_NAME = 'timelion'; -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { +export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) { const { http, uiSettings } = dependencies; const timelionRequestHandler = getTimelionRequestHandler(dependencies); @@ -46,13 +43,16 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe description: i18n.translate('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - visualization: AngularVisController, visConfig: { defaults: { expression: '.es(*)', interval: 'auto', }, - template: visConfigTemplate, + component: (props: TimelionVisComponentProp) => ( + + + + ), }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js diff --git a/tsconfig.json b/tsconfig.json index a2da9c127e7ba6..8d9e2359ba97f2 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 034a2ed08b4e68..b534a7a4fcb799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4398,6 +4398,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" @@ -4601,7 +4608,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==