mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Vis] Move Timelion Vis to vis_type_timelion (#52069)
* Deangularize timelion vis * Refactoring * Fix path * Update timelion_controller.ts * Remove unused deps * Create vis_type_timelion * Create ChartComponent * Render chart in react * Reactify timelion editor * Change translation ids * Use hooks * Add @types/pegjs into renovate.json5 * Add validation, add hover suggestions * Style fixes * Change plugin setup, use kibana context * Update * Fix ticks * Fix plotselected listener * Fix plothover handler * Add TS for options * Update TS * Restructuring * Change plugin start * Remove vis from timelion plugin * Rename class * Mock services * Fix other comments * Remove duplicate files * Convert test to jest * Remove kibana_services from timelion * Delete visualize_app.ts.~LOCAL * Refactoring * Fix TS * Refactoring, TS * Import eui variables * Import styling constants * Move react components to vis_type_timelion * Fix TS * Move ui imports to legacy_imports.ts * Move chain.peg to vis_type_timelion * Fix path * Use KibanaContext instead kibana_services.ts * Refactoring * Refactoring * Add @types/flot * Fix issue with hovered series color * Update renovate.json5 * Pass timelionPanels as dependencies * Move common folder to vis_type_timelion * Move back tick_formatters.ts * Rename styles file * Refactoring * Update _index.scss * Move to_milliseconds to common * Revert yaxes formatting * Refactoring * Refactoring * Use Panel directly * Refactoring of to_milliseconds.ts Co-authored-by: Daniil Suleiman <31325372+sulemanof@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
856c85b400
commit
9e07a427c7
69 changed files with 1105 additions and 229 deletions
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -281,6 +281,14 @@
|
|||
'@types/file-saver',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'flot',
|
||||
groupName: 'flot related packages',
|
||||
packageNames: [
|
||||
'flot',
|
||||
'@types/flot',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'getopts',
|
||||
groupName: 'getopts related packages',
|
||||
|
|
|
@ -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?', {
|
||||
|
|
|
@ -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({});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
@import './chart';
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -11,6 +11,4 @@
|
|||
// timChart__legend-isLoading
|
||||
|
||||
@import './app';
|
||||
@import './components/index';
|
||||
@import './directives/index';
|
||||
@import './vis/index';
|
||||
|
|
|
@ -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<TimelionPluginSetupDependencies> = {
|
||||
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<TimelionPluginSetupDependencies> = {
|
|||
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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<string, Panel>;
|
||||
timefilter: TimefilterContract;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
|
||||
// Temporary solution
|
||||
__LEGACY: LegacyDependenciesPlugin;
|
||||
}
|
||||
|
@ -61,24 +47,16 @@ export class TimelionPlugin implements Plugin<Promise<void>, void> {
|
|||
this.initializerContext = initializerContext;
|
||||
}
|
||||
|
||||
public async setup(
|
||||
core: CoreSetup,
|
||||
{ __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies
|
||||
) {
|
||||
public async setup(core: CoreSetup, { __LEGACY }: TimelionPluginSetupDependencies) {
|
||||
const timelionPanels: Map<string, Panel> = 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<Promise<void>, 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 {}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
@import './timelion_vis';
|
||||
@import './timelion_editor';
|
|
@ -1,3 +0,0 @@
|
|||
<div ng-controller="TimelionVisController" class="timVis">
|
||||
<div chart="esResponse.sheet[0]" rerender-trigger="renderComplete" class="timChart" interval="visState.params.interval"></div>
|
||||
</div>
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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';
|
|
@ -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;
|
||||
});
|
||||
},
|
||||
{}
|
||||
);
|
||||
// match any key from the vals object preceded by an optional number
|
||||
const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$');
|
||||
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[][];
|
||||
|
||||
export default function(expr) {
|
||||
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('|') + ')$');
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
44
src/legacy/core_plugins/vis_type_timelion/index.ts
Normal file
44
src/legacy/core_plugins/vis_type_timelion/index.ts
Normal file
|
@ -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;
|
4
src/legacy/core_plugins/vis_type_timelion/package.json
Normal file
4
src/legacy/core_plugins/vis_type_timelion/package.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "timelion_vis",
|
||||
"version": "kibana"
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
@import './panel';
|
||||
@import './timelion_expression_input';
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
.ngLegendValue {
|
||||
color: $euiTextColor;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
|
@ -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 <Panel {...props} />;
|
||||
}
|
||||
|
||||
export { ChartComponent };
|
|
@ -19,3 +19,4 @@
|
|||
|
||||
export * from './timelion_expression_input';
|
||||
export * from './timelion_interval';
|
||||
export * from './timelion_vis';
|
|
@ -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 = '<br>';
|
||||
|
||||
function Panel({ interval, seriesList, renderComplete }: PanelProps) {
|
||||
const kibana = useKibana<TimelionVisDependencies>();
|
||||
const [chart, setChart] = useState(() => cloneDeep(seriesList.list));
|
||||
const [canvasElem, setCanvasElem] = useState();
|
||||
const [chartElem, setChartElem] = useState();
|
||||
|
||||
const [originalColorMap, setOriginalColorMap] = useState(() => new Map<Series, string>());
|
||||
|
||||
const [highlightedSeries, setHighlightedSeries] = useState<number | null>(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 class="timChart__legendCaption"></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 (
|
||||
<div ref={elementRef} className="timChart">
|
||||
<div className="chart-top-title">{title}</div>
|
||||
<div ref={canvasRef} className="chart-canvas" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { Panel };
|
|
@ -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 });
|
|
@ -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);
|
||||
|
|
@ -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 = [
|
||||
{
|
|
@ -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 (
|
||||
<div className="timVis">
|
||||
<ChartComponent
|
||||
seriesList={props.visData.sheet[0]}
|
||||
renderComplete={props.renderComplete}
|
||||
interval={props.vis.getState().params.interval}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { TimelionVisComponent };
|
|
@ -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 };
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
|
@ -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<Record<string, unknown>>;
|
||||
render: Record<string, unknown>;
|
||||
export interface Series {
|
||||
_global?: boolean;
|
||||
_hide?: boolean;
|
||||
_id?: number;
|
||||
_title?: string;
|
||||
color?: string;
|
||||
data: Array<Record<number, number>>;
|
||||
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({
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@import 'src/legacy/ui/public/styles/styling_constants';
|
||||
|
||||
@import './timelion_vis';
|
||||
@import './timelion_editor';
|
||||
@import './components/index';
|
|
@ -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);
|
||||
}
|
37
src/legacy/core_plugins/vis_type_timelion/public/legacy.ts
Normal file
37
src/legacy/core_plugins/vis_type_timelion/public/legacy.ts
Normal file
|
@ -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<TimelionVisSetupDependencies> = {
|
||||
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);
|
|
@ -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';
|
76
src/legacy/core_plugins/vis_type_timelion/public/plugin.ts
Normal file
76
src/legacy/core_plugins/vis_type_timelion/public/plugin.ts
Normal file
|
@ -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<TimelionVisSetupDependencies>;
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionVisDependencies extends Partial<CoreStart> {
|
||||
uiSettings: IUiSettingsClient;
|
||||
http: HttpSetup;
|
||||
timefilter: TimefilterContract;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionVisSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
data: DataPublicPluginSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class TimelionVisPlugin implements Plugin<void, void> {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<VisParams>) {
|
||||
const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [
|
|
@ -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<Render<RenderValue>>;
|
||||
|
||||
export const getTimelionVisualizationConfig = (
|
||||
dependencies: TimelionVisualizationDependencies
|
||||
dependencies: TimelionVisDependencies
|
||||
): ExpressionFunction<typeof name, Context, Arguments, Return> => ({
|
||||
name,
|
||||
type: 'render',
|
|
@ -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) => (
|
||||
<KibanaContextProvider services={{ ...dependencies }}>
|
||||
<TimelionVisComponent {...props} />
|
||||
</KibanaContextProvider>
|
||||
),
|
||||
},
|
||||
editorConfig: {
|
||||
optionsTemplate: (props: VisOptionsProps<VisParams>) => (
|
|
@ -51,7 +51,8 @@
|
|||
"types": [
|
||||
"node",
|
||||
"jest",
|
||||
"react"
|
||||
"react",
|
||||
"flot"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
|
|
@ -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==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue