* Move index.js to index.ts * Migrate Vis, interpreter, home and 2 hacks to setup() and start() * Move Timechart hack to start() * Add featureCatalogueRegistryProvider to an interface * Add types to a server param * Move some .js to .ts * Add ExpressionFunction<> to interpreter * Remove Feature Provider * Remove extra export * Add an interface to a timelion response * Add an inteface to the panel * Add IPrivate interface * Make nit notes * Edit uiCapabilities() type * Shim Timelion plugin * Shim start() plugin method * Change InternalCoreStart to LegacyCoreStart * Move Angular dependencies to a separate module * Change visualizations import path due to recent changes * Rename directives * Take a common property out * Get rid of require in schema * Use core.uiSettings * Refactor timelion request handler * Remove Private from tests * Remove redundant dependencies from tests * Update visualizations paths * Change expressions paths due to expessions movement * Refactoring according to reviews * Add a comment over the uiCapabilities field * Edit the comment * Ignore uiCapabilities issue * Take angular controller out * Get rid of * Get rid of config * Get rid of config in start * Unwrap handler from redundant promise * Move npSetup npStart dependencies in a high level * Rename some details * Fix reviews * fix CI * Take visFactory out
This commit is contained in:
parent
48ebe3d8c3
commit
e20fdcb74c
|
@ -19,28 +19,32 @@
|
|||
|
||||
import { resolve } from 'path';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Legacy } from 'kibana';
|
||||
import { LegacyPluginApi, LegacyPluginInitializer } from 'src/legacy/plugin_discovery/types';
|
||||
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
|
||||
import { plugin } from './server';
|
||||
import { CustomCoreSetup } from './server/plugin';
|
||||
|
||||
const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel', {
|
||||
defaultMessage: 'experimental',
|
||||
});
|
||||
|
||||
export default function (kibana) {
|
||||
return new kibana.Plugin({
|
||||
const timelionPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) =>
|
||||
new Plugin({
|
||||
require: ['kibana', 'elasticsearch'],
|
||||
|
||||
config(Joi) {
|
||||
config(Joi: any) {
|
||||
return Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
ui: Joi.object({
|
||||
enabled: Joi.boolean().default(false),
|
||||
}).default(),
|
||||
graphiteUrls: Joi.array().items(
|
||||
Joi.string().uri({ scheme: ['http', 'https'] }),
|
||||
).default([]),
|
||||
graphiteUrls: Joi.array()
|
||||
.items(Joi.string().uri({ scheme: ['http', 'https'] }))
|
||||
.default([]),
|
||||
}).default();
|
||||
},
|
||||
|
||||
// @ts-ignore
|
||||
// https://github.com/elastic/kibana/pull/44039#discussion_r326582255
|
||||
uiCapabilities() {
|
||||
return {
|
||||
timelion: {
|
||||
|
@ -48,7 +52,7 @@ export default function (kibana) {
|
|||
},
|
||||
};
|
||||
},
|
||||
|
||||
publicDir: resolve(__dirname, 'public'),
|
||||
uiExports: {
|
||||
app: {
|
||||
title: 'Timelion',
|
||||
|
@ -58,11 +62,7 @@ export default function (kibana) {
|
|||
main: 'plugins/timelion/app',
|
||||
},
|
||||
styleSheetPaths: resolve(__dirname, 'public/index.scss'),
|
||||
hacks: [
|
||||
'plugins/timelion/hacks/toggle_app_link_in_nav',
|
||||
'plugins/timelion/lib/panel_registry',
|
||||
'plugins/timelion/panels/timechart/timechart',
|
||||
],
|
||||
hacks: [resolve(__dirname, 'public/legacy')],
|
||||
injectDefaultVars(server) {
|
||||
const config = server.config();
|
||||
|
||||
|
@ -71,13 +71,6 @@ export default function (kibana) {
|
|||
kbnIndex: config.get('kibana.index'),
|
||||
};
|
||||
},
|
||||
visTypes: [
|
||||
'plugins/timelion/vis',
|
||||
],
|
||||
interpreter: ['plugins/timelion/timelion_vis_fn'],
|
||||
home: [
|
||||
'plugins/timelion/register_feature',
|
||||
],
|
||||
mappings: require('./mappings.json'),
|
||||
uiSettingDefaults: {
|
||||
'timelion:showTutorial': {
|
||||
|
@ -159,17 +152,19 @@ export default function (kibana) {
|
|||
value: '1ms',
|
||||
description: i18n.translate('timelion.uiSettings.minimumIntervalDescription', {
|
||||
defaultMessage: 'The smallest interval that will be calculated when using "auto"',
|
||||
description: '"auto" is a technical value in that context, that should not be translated.',
|
||||
description:
|
||||
'"auto" is a technical value in that context, that should not be translated.',
|
||||
}),
|
||||
category: ['timelion'],
|
||||
},
|
||||
'timelion:graphite.url': {
|
||||
name: i18n.translate('timelion.uiSettings.graphiteURLLabel', {
|
||||
defaultMessage: 'Graphite URL',
|
||||
description: 'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite',
|
||||
description:
|
||||
'The URL should be in the form of https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite',
|
||||
}),
|
||||
value: (server) => {
|
||||
const urls = server.config().get('timelion.graphiteUrls');
|
||||
value: (server: Legacy.Server) => {
|
||||
const urls = server.config().get('timelion.graphiteUrls') as string[];
|
||||
if (urls.length === 0) {
|
||||
return null;
|
||||
} else {
|
||||
|
@ -177,11 +172,12 @@ export default function (kibana) {
|
|||
}
|
||||
},
|
||||
description: i18n.translate('timelion.uiSettings.graphiteURLDescription', {
|
||||
defaultMessage: '{experimentalLabel} The <a href="https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite" target="_blank" rel="noopener">URL</a> of your graphite host',
|
||||
defaultMessage:
|
||||
'{experimentalLabel} The <a href="https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite" target="_blank" rel="noopener">URL</a> of your graphite host',
|
||||
values: { experimentalLabel: `<em>[${experimentalLabel}]</em>` },
|
||||
}),
|
||||
type: 'select',
|
||||
options: (server) => (server.config().get('timelion.graphiteUrls')),
|
||||
options: (server: Legacy.Server) => server.config().get('timelion.graphiteUrls'),
|
||||
category: ['timelion'],
|
||||
},
|
||||
'timelion:quandl.key': {
|
||||
|
@ -197,11 +193,13 @@ export default function (kibana) {
|
|||
},
|
||||
},
|
||||
},
|
||||
init: (server) => {
|
||||
const initializerContext = {};
|
||||
const core = { http: { server } };
|
||||
init: (server: Legacy.Server) => {
|
||||
const initializerContext = {} as PluginInitializerContext;
|
||||
const core = { http: { server } } as CoreSetup & CustomCoreSetup;
|
||||
|
||||
plugin(initializerContext).setup(core);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default timelionPluginInitializer;
|
|
@ -18,48 +18,53 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import { generateTicksProvider } from '../panels/timechart/tick_generator';
|
||||
|
||||
describe('Tick Generator', function () {
|
||||
|
||||
let generateTicks;
|
||||
const axes = [
|
||||
{
|
||||
min: 0,
|
||||
max: 5000,
|
||||
delta: 100
|
||||
},
|
||||
{
|
||||
min: 0,
|
||||
max: 50000,
|
||||
delta: 2000
|
||||
},
|
||||
{
|
||||
min: 4096,
|
||||
max: 6000,
|
||||
delta: 250
|
||||
}
|
||||
];
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
generateTicks = Private(require('plugins/timelion/panels/timechart/tick_generator'));
|
||||
}));
|
||||
|
||||
it('returns a function', function () {
|
||||
expect(generateTicks).to.be.a('function');
|
||||
beforeEach(function () {
|
||||
generateTicks = generateTicksProvider();
|
||||
});
|
||||
|
||||
axes.forEach(axis => {
|
||||
it(`generates ticks from ${axis.min} to ${axis.max}`, function () {
|
||||
const ticks = generateTicks(axis);
|
||||
let n = 1;
|
||||
while (Math.pow(2, n) < axis.delta) n++;
|
||||
const expectedDelta = Math.pow(2, n);
|
||||
const expectedNr = parseInt((axis.max - axis.min) / expectedDelta) + 2;
|
||||
expect(ticks instanceof Array).to.be(true);
|
||||
expect(ticks.length).to.be(expectedNr);
|
||||
expect(ticks[0]).to.equal(axis.min);
|
||||
expect(ticks[parseInt(ticks.length / 2)]).to.equal(axis.min + expectedDelta * parseInt(ticks.length / 2));
|
||||
expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1));
|
||||
describe('generateTicksProvider()', function () {
|
||||
it('should return a function', function () {
|
||||
expect(generateTicks).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateTicks()', function () {
|
||||
const axes = [
|
||||
{
|
||||
min: 0,
|
||||
max: 5000,
|
||||
delta: 100
|
||||
},
|
||||
{
|
||||
min: 0,
|
||||
max: 50000,
|
||||
delta: 2000
|
||||
},
|
||||
{
|
||||
min: 4096,
|
||||
max: 6000,
|
||||
delta: 250
|
||||
}
|
||||
];
|
||||
|
||||
axes.forEach(axis => {
|
||||
it(`generates ticks from ${axis.min} to ${axis.max}`, function () {
|
||||
const ticks = generateTicks(axis);
|
||||
let n = 1;
|
||||
while (Math.pow(2, n) < axis.delta) n++;
|
||||
const expectedDelta = Math.pow(2, n);
|
||||
const expectedNr = parseInt((axis.max - axis.min) / expectedDelta) + 2;
|
||||
expect(ticks instanceof Array).to.be(true);
|
||||
expect(ticks.length).to.be(expectedNr);
|
||||
expect(ticks[0]).to.equal(axis.min);
|
||||
expect(ticks[parseInt(ticks.length / 2)]).to.equal(axis.min + expectedDelta * parseInt(ticks.length / 2));
|
||||
expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,20 +18,19 @@
|
|||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import ngMock from 'ng_mock';
|
||||
import { tickFormatters } from '../../services/tick_formatters';
|
||||
|
||||
describe('Tick Formatters', function () {
|
||||
let formatters;
|
||||
|
||||
let tickFormatters;
|
||||
|
||||
beforeEach(ngMock.module('kibana'));
|
||||
beforeEach(ngMock.inject(function (Private) {
|
||||
tickFormatters = Private(require('plugins/timelion/services/tick_formatters'));
|
||||
}));
|
||||
beforeEach(function () {
|
||||
formatters = tickFormatters();
|
||||
});
|
||||
|
||||
describe('Bits mode', function () {
|
||||
let bitFormatter;
|
||||
beforeEach(function () {
|
||||
bitFormatter = tickFormatters.bits;
|
||||
bitFormatter = formatters.bits;
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
@ -56,7 +55,7 @@ describe('Tick Formatters', function () {
|
|||
describe('Bits/s mode', function () {
|
||||
let bitsFormatter;
|
||||
beforeEach(function () {
|
||||
bitsFormatter = tickFormatters['bits/s'];
|
||||
bitsFormatter = formatters['bits/s'];
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
@ -81,7 +80,7 @@ describe('Tick Formatters', function () {
|
|||
describe('Bytes mode', function () {
|
||||
let byteFormatter;
|
||||
beforeEach(function () {
|
||||
byteFormatter = tickFormatters.bytes;
|
||||
byteFormatter = formatters.bytes;
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
@ -106,7 +105,7 @@ describe('Tick Formatters', function () {
|
|||
describe('Bytes/s mode', function () {
|
||||
let bytesFormatter;
|
||||
beforeEach(function () {
|
||||
bytesFormatter = tickFormatters['bytes/s'];
|
||||
bytesFormatter = formatters['bytes/s'];
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
@ -131,7 +130,7 @@ describe('Tick Formatters', function () {
|
|||
describe('Currency mode', function () {
|
||||
let currencyFormatter;
|
||||
beforeEach(function () {
|
||||
currencyFormatter = tickFormatters.currency;
|
||||
currencyFormatter = formatters.currency;
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
@ -163,7 +162,7 @@ describe('Tick Formatters', function () {
|
|||
describe('Percent mode', function () {
|
||||
let percentFormatter;
|
||||
beforeEach(function () {
|
||||
percentFormatter = tickFormatters.percent;
|
||||
percentFormatter = formatters.percent;
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
@ -197,7 +196,7 @@ describe('Tick Formatters', function () {
|
|||
describe('Custom mode', function () {
|
||||
let customFormatter;
|
||||
beforeEach(function () {
|
||||
customFormatter = tickFormatters.custom;
|
||||
customFormatter = formatters.custom;
|
||||
});
|
||||
|
||||
it('is a function', function () {
|
||||
|
|
|
@ -17,55 +17,52 @@
|
|||
* under the License.
|
||||
*/
|
||||
import panelRegistryProvider from '../../lib/panel_registry';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
require('ui/modules')
|
||||
.get('apps/timelion', [])
|
||||
.directive('chart', function (Private) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
seriesList: '=chart', // The flot object, data, config and all
|
||||
search: '=', // The function to execute to kick off a search
|
||||
interval: '=', // Required for formatting x-axis ticks
|
||||
rerenderTrigger: '=',
|
||||
},
|
||||
link: function ($scope, $elem) {
|
||||
export function Chart(Private) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
seriesList: '=chart', // The flot object, data, config and all
|
||||
search: '=', // The function to execute to kick off a search
|
||||
interval: '=', // Required for formatting x-axis ticks
|
||||
rerenderTrigger: '=',
|
||||
},
|
||||
link: function ($scope, $elem) {
|
||||
|
||||
const panelRegistry = Private(panelRegistryProvider);
|
||||
let panelScope = $scope.$new(true);
|
||||
const panelRegistry = Private(panelRegistryProvider);
|
||||
let panelScope = $scope.$new(true);
|
||||
|
||||
function render() {
|
||||
panelScope.$destroy();
|
||||
function render() {
|
||||
panelScope.$destroy();
|
||||
|
||||
if (!$scope.seriesList) return;
|
||||
if (!$scope.seriesList) return;
|
||||
|
||||
$scope.seriesList.render = $scope.seriesList.render || {
|
||||
type: 'timechart'
|
||||
};
|
||||
$scope.seriesList.render = $scope.seriesList.render || {
|
||||
type: 'timechart'
|
||||
};
|
||||
|
||||
const panelSchema = panelRegistry.byName[$scope.seriesList.render.type];
|
||||
const panelSchema = panelRegistry.byName[$scope.seriesList.render.type];
|
||||
|
||||
if (!panelSchema) {
|
||||
$elem.text(
|
||||
i18n.translate('timelion.chart.seriesList.noSchemaWarning', {
|
||||
defaultMessage: 'No such panel type: {renderType}',
|
||||
values: { renderType: $scope.seriesList.render.type },
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
panelScope = $scope.$new(true);
|
||||
panelScope.seriesList = $scope.seriesList;
|
||||
panelScope.interval = $scope.interval;
|
||||
panelScope.search = $scope.search;
|
||||
|
||||
panelSchema.render(panelScope, $elem);
|
||||
if (!panelSchema) {
|
||||
$elem.text(
|
||||
i18n.translate('timelion.chart.seriesList.noSchemaWarning', {
|
||||
defaultMessage: 'No such panel type: {renderType}',
|
||||
values: { renderType: $scope.seriesList.render.type },
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.$watchGroup(['seriesList', 'rerenderTrigger'], render);
|
||||
panelScope = $scope.$new(true);
|
||||
panelScope.seriesList = $scope.seriesList;
|
||||
panelScope.interval = $scope.interval;
|
||||
panelScope.search = $scope.search;
|
||||
|
||||
panelSchema.render(panelScope, $elem);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$scope.$watchGroup(['seriesList', 'rerenderTrigger'], render);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,9 +43,7 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import PEG from 'pegjs';
|
||||
|
||||
import grammar from 'raw-loader!../chain.peg';
|
||||
import './timelion_expression_suggestions/timelion_expression_suggestions';
|
||||
import timelionExpressionInputTemplate from './timelion_expression_input.html';
|
||||
import {
|
||||
SUGGESTION_TYPE,
|
||||
|
@ -57,9 +55,8 @@ import { comboBoxKeyCodes } from '@elastic/eui';
|
|||
import { ArgValueSuggestionsProvider } from './timelion_expression_suggestions/arg_value_suggestions';
|
||||
|
||||
const Parser = PEG.generate(grammar);
|
||||
const app = require('ui/modules').get('apps/timelion', []);
|
||||
|
||||
app.directive('timelionExpressionInput', function ($http, $timeout, Private) {
|
||||
export function TimelionExpInput($http, $timeout, Private) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
|
@ -276,4 +273,4 @@ app.directive('timelionExpressionInput', function ($http, $timeout, Private) {
|
|||
init();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,9 +19,7 @@
|
|||
|
||||
import template from './timelion_expression_suggestions.html';
|
||||
|
||||
const app = require('ui/modules').get('apps/timelion', []);
|
||||
|
||||
app.directive('timelionExpressionSuggestions', () => {
|
||||
export function TimelionExpressionSuggestions() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
|
@ -38,4 +36,4 @@ app.directive('timelionExpressionSuggestions', () => {
|
|||
scope.onMouseDown = e => e.preventDefault();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
|
||||
const app = require('ui/modules').get('apps/timelion', []);
|
||||
import template from './timelion_interval.html';
|
||||
|
||||
app.directive('timelionInterval', function ($timeout) {
|
||||
export function TimelionInterval($timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
|
@ -81,4 +79,5 @@ app.directive('timelionInterval', function ($timeout) {
|
|||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { PluginInitializerContext } from 'kibana/public';
|
||||
import { TimelionPlugin as Plugin } from './plugin';
|
||||
|
||||
const timelionUiEnabled = npStart.core.injectedMetadata.getInjectedVar('timelionUiEnabled');
|
||||
if (timelionUiEnabled === false) {
|
||||
npStart.core.chrome.navLinks.update('timelion', { hidden: true });
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new Plugin(initializerContext);
|
||||
}
|
47
src/legacy/core_plugins/timelion/public/legacy.ts
Normal file
47
src/legacy/core_plugins/timelion/public/legacy.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'kibana/public';
|
||||
import { npSetup, npStart } from 'ui/new_platform';
|
||||
import { plugin } from '.';
|
||||
import { setup as visualizations } from '../../visualizations/public/legacy';
|
||||
import { TimelionPluginSetupDependencies, TimelionPluginStartDependencies } from './plugin';
|
||||
// @ts-ignore
|
||||
import panelRegistry from './lib/panel_registry';
|
||||
import { LegacyDependenciesPlugin } from './shim';
|
||||
|
||||
// Temporary solution
|
||||
// It will be removed when all dependent services are migrated to the new platform.
|
||||
const __LEGACY = new LegacyDependenciesPlugin();
|
||||
|
||||
const setupPlugins: Readonly<TimelionPluginSetupDependencies> = {
|
||||
visualizations,
|
||||
expressions: npSetup.plugins.expressions,
|
||||
__LEGACY,
|
||||
};
|
||||
|
||||
const startPlugins: Readonly<TimelionPluginStartDependencies> = {
|
||||
panelRegistry,
|
||||
__LEGACY,
|
||||
};
|
||||
|
||||
const pluginInstance = plugin({} as PluginInitializerContext);
|
||||
|
||||
export const setup = pluginInstance.setup(npSetup.core, setupPlugins);
|
||||
export const start = pluginInstance.start(npStart.core, startPlugins);
|
45
src/legacy/core_plugins/timelion/public/panels/panel.ts
Normal file
45
src/legacy/core_plugins/timelion/public/panels/panel.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
interface PanelConfig {
|
||||
help?: string;
|
||||
render?: Function;
|
||||
}
|
||||
|
||||
export class Panel {
|
||||
name: string;
|
||||
help: string;
|
||||
render: Function | undefined;
|
||||
|
||||
constructor(name: string, config: PanelConfig) {
|
||||
this.name = name;
|
||||
this.help = config.help || '';
|
||||
this.render = config.render;
|
||||
|
||||
if (!config.render) {
|
||||
throw new Error(
|
||||
i18n.translate('timelion.panels.noRenderFunctionErrorMessage', {
|
||||
defaultMessage: 'Panel must have a rendering function',
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,29 +17,36 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
require('./flot');
|
||||
import './flot';
|
||||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment-timezone';
|
||||
import observeResize from '../../lib/observe_resize';
|
||||
import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
// @ts-ignore
|
||||
import observeResize from '../../lib/observe_resize';
|
||||
// @ts-ignore
|
||||
import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib';
|
||||
import { TimelionStartDependencies } from '../../plugin';
|
||||
import { tickFormatters } from '../../services/tick_formatters';
|
||||
import { xaxisFormatterProvider } from './xaxis_formatter';
|
||||
import { generateTicksProvider } from './tick_generator';
|
||||
|
||||
const DEBOUNCE_DELAY = 50;
|
||||
|
||||
export default function timechartFn(Private, config, $rootScope, $compile) {
|
||||
return function () {
|
||||
export function timechartFn(dependencies: TimelionStartDependencies) {
|
||||
const { $rootScope, $compile, uiSettings } = dependencies;
|
||||
return function() {
|
||||
return {
|
||||
help: 'Draw a timeseries chart',
|
||||
render: function ($scope, $elem) {
|
||||
render($scope: any, $elem: any) {
|
||||
const template = '<div class="chart-top-title"></div><div class="chart-canvas"></div>';
|
||||
const tickFormatters = require('plugins/timelion/services/tick_formatters')();
|
||||
const getxAxisFormatter = Private(require('plugins/timelion/panels/timechart/xaxis_formatter'));
|
||||
const generateTicks = Private(require('plugins/timelion/panels/timechart/tick_generator'));
|
||||
const formatters = tickFormatters() as any;
|
||||
const getxAxisFormatter = xaxisFormatterProvider(uiSettings);
|
||||
const generateTicks = generateTicksProvider();
|
||||
|
||||
// TODO: I wonder if we should supply our own moment that sets this every time?
|
||||
// could just use angular's injection to provide a moment service?
|
||||
moment.tz.setDefault(config.get('dateFormat:tz'));
|
||||
moment.tz.setDefault(uiSettings.get('dateFormat:tz'));
|
||||
|
||||
const render = $scope.seriesList.render || {};
|
||||
|
||||
|
@ -47,12 +54,12 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
$scope.interval = $scope.interval;
|
||||
$scope.search = $scope.search || _.noop;
|
||||
|
||||
let legendValueNumbers;
|
||||
let legendCaption;
|
||||
let legendValueNumbers: any;
|
||||
let legendCaption: any;
|
||||
const debouncedSetLegendNumbers = _.debounce(setLegendNumbers, DEBOUNCE_DELAY, {
|
||||
maxWait: DEBOUNCE_DELAY,
|
||||
leading: true,
|
||||
trailing: false
|
||||
trailing: false,
|
||||
});
|
||||
// ensure legend is the same height with or without a caption so legend items do not move around
|
||||
const emptyCaption = '<br>';
|
||||
|
@ -65,12 +72,12 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
},
|
||||
selection: {
|
||||
mode: 'x',
|
||||
color: '#ccc'
|
||||
color: '#ccc',
|
||||
},
|
||||
crosshair: {
|
||||
mode: 'x',
|
||||
color: '#C66',
|
||||
lineWidth: 2
|
||||
lineWidth: 2,
|
||||
},
|
||||
grid: {
|
||||
show: render.grid,
|
||||
|
@ -78,13 +85,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
borderColor: null,
|
||||
margin: 10,
|
||||
hoverable: true,
|
||||
autoHighlight: false
|
||||
autoHighlight: false,
|
||||
},
|
||||
legend: {
|
||||
backgroundColor: 'rgb(255,255,255,0)',
|
||||
position: 'nw',
|
||||
labelBoxBorderColor: 'rgb(255,255,255,0)',
|
||||
labelFormatter: function (label, series) {
|
||||
labelFormatter(label: any, series: any) {
|
||||
const wrapperSpan = document.createElement('span');
|
||||
const labelSpan = document.createElement('span');
|
||||
const numberSpan = document.createElement('span');
|
||||
|
@ -103,13 +110,24 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
wrapperSpan.appendChild(numberSpan);
|
||||
|
||||
return wrapperSpan.outerHTML;
|
||||
}
|
||||
},
|
||||
},
|
||||
colors: ['#01A4A4', '#C66', '#D0D102', '#616161', '#00A1CB', '#32742C', '#F18D05', '#113F8C', '#61AE24', '#D70060']
|
||||
colors: [
|
||||
'#01A4A4',
|
||||
'#C66',
|
||||
'#D0D102',
|
||||
'#616161',
|
||||
'#00A1CB',
|
||||
'#32742C',
|
||||
'#F18D05',
|
||||
'#113F8C',
|
||||
'#61AE24',
|
||||
'#D70060',
|
||||
],
|
||||
};
|
||||
|
||||
const originalColorMap = new Map();
|
||||
$scope.chart.forEach((series, seriesIndex) => {
|
||||
$scope.chart.forEach((series: any, seriesIndex: any) => {
|
||||
if (!series.color) {
|
||||
const colorIndex = seriesIndex % defaultOptions.colors.length;
|
||||
series.color = defaultOptions.colors[colorIndex];
|
||||
|
@ -117,8 +135,8 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
originalColorMap.set(series, series.color);
|
||||
});
|
||||
|
||||
let highlightedSeries;
|
||||
let focusedSeries;
|
||||
let highlightedSeries: any;
|
||||
let focusedSeries: any;
|
||||
function unhighlightSeries() {
|
||||
if (highlightedSeries === null) {
|
||||
return;
|
||||
|
@ -126,18 +144,18 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
|
||||
highlightedSeries = null;
|
||||
focusedSeries = null;
|
||||
$scope.chart.forEach((series) => {
|
||||
$scope.chart.forEach((series: any) => {
|
||||
series.color = originalColorMap.get(series); // reset the colors
|
||||
});
|
||||
drawPlot($scope.chart);
|
||||
}
|
||||
$scope.highlightSeries = _.debounce(function (id) {
|
||||
$scope.highlightSeries = _.debounce(function(id: any) {
|
||||
if (highlightedSeries === id) {
|
||||
return;
|
||||
}
|
||||
|
||||
highlightedSeries = id;
|
||||
$scope.chart.forEach((series, seriesIndex) => {
|
||||
$scope.chart.forEach((series: any, seriesIndex: any) => {
|
||||
if (seriesIndex !== id) {
|
||||
series.color = 'rgba(128,128,128,0.1)'; // mark as grey
|
||||
} else {
|
||||
|
@ -146,58 +164,57 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
});
|
||||
drawPlot($scope.chart);
|
||||
}, DEBOUNCE_DELAY);
|
||||
$scope.focusSeries = function (id) {
|
||||
$scope.focusSeries = function(id: any) {
|
||||
focusedSeries = id;
|
||||
$scope.highlightSeries(id);
|
||||
};
|
||||
|
||||
$scope.toggleSeries = function (id) {
|
||||
$scope.toggleSeries = function(id: any) {
|
||||
const series = $scope.chart[id];
|
||||
series._hide = !series._hide;
|
||||
drawPlot($scope.chart);
|
||||
};
|
||||
|
||||
const cancelResize = observeResize($elem, function () {
|
||||
const cancelResize = observeResize($elem, function() {
|
||||
drawPlot($scope.chart);
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
$scope.$on('$destroy', function() {
|
||||
cancelResize();
|
||||
$elem.off('plothover');
|
||||
$elem.off('plotselected');
|
||||
$elem.off('mouseleave');
|
||||
});
|
||||
|
||||
$elem.on('plothover', function (event, pos, item) {
|
||||
$elem.on('plothover', function(event: any, pos: any, item: any) {
|
||||
$rootScope.$broadcast('timelionPlotHover', event, pos, item);
|
||||
});
|
||||
|
||||
$elem.on('plotselected', function (event, ranges) {
|
||||
$elem.on('plotselected', function(event: any, ranges: any) {
|
||||
timefilter.setTime({
|
||||
from: moment(ranges.xaxis.from),
|
||||
to: moment(ranges.xaxis.to),
|
||||
mode: 'absolute',
|
||||
});
|
||||
});
|
||||
|
||||
$elem.on('mouseleave', function () {
|
||||
$elem.on('mouseleave', function() {
|
||||
$rootScope.$broadcast('timelionPlotLeave');
|
||||
});
|
||||
|
||||
$scope.$on('timelionPlotHover', function (angularEvent, flotEvent, pos) {
|
||||
$scope.$on('timelionPlotHover', function(angularEvent: any, flotEvent: any, pos: any) {
|
||||
if (!$scope.plot) return;
|
||||
$scope.plot.setCrosshair(pos);
|
||||
debouncedSetLegendNumbers(pos);
|
||||
});
|
||||
|
||||
$scope.$on('timelionPlotLeave', function () {
|
||||
$scope.$on('timelionPlotLeave', function() {
|
||||
if (!$scope.plot) return;
|
||||
$scope.plot.clearCrosshair();
|
||||
clearLegendNumbers();
|
||||
});
|
||||
|
||||
// Shamelessly borrowed from the flotCrosshairs example
|
||||
function setLegendNumbers(pos) {
|
||||
function setLegendNumbers(pos: any) {
|
||||
unhighlightSeries();
|
||||
|
||||
const plot = $scope.plot;
|
||||
|
@ -211,10 +228,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
let j;
|
||||
const dataset = plot.getData();
|
||||
if (legendCaption) {
|
||||
legendCaption.text(moment(pos.x).format(_.get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)));
|
||||
legendCaption.text(
|
||||
moment(pos.x).format(
|
||||
_.get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)
|
||||
)
|
||||
);
|
||||
}
|
||||
for (i = 0; i < dataset.length; ++i) {
|
||||
|
||||
const series = dataset[i];
|
||||
const precision = _.get(series, '_meta.precision', 2);
|
||||
|
||||
|
@ -248,13 +268,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
if (legendCaption) {
|
||||
legendCaption.html(emptyCaption);
|
||||
}
|
||||
_.each(legendValueNumbers, function (num) {
|
||||
_.each(legendValueNumbers, function(num) {
|
||||
$(num).empty();
|
||||
});
|
||||
}
|
||||
|
||||
let legendScope = $scope.$new();
|
||||
function drawPlot(plotConfig) {
|
||||
function drawPlot(plotConfig: any) {
|
||||
if (!$('.chart-canvas', $elem).length) $elem.html(template);
|
||||
const canvasElem = $('.chart-canvas', $elem);
|
||||
|
||||
|
@ -264,56 +284,63 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
return;
|
||||
}
|
||||
|
||||
const title = _(plotConfig).map('_title').compact().last();
|
||||
const title = _(plotConfig)
|
||||
.map('_title')
|
||||
.compact()
|
||||
.last() as any;
|
||||
$('.chart-top-title', $elem).text(title == null ? '' : title);
|
||||
|
||||
const options = _.cloneDeep(defaultOptions);
|
||||
const options = _.cloneDeep(defaultOptions) as any;
|
||||
|
||||
// Get the X-axis tick format
|
||||
const time = timefilter.getBounds();
|
||||
const time = timefilter.getBounds() as any;
|
||||
const interval = calculateInterval(
|
||||
time.min.valueOf(),
|
||||
time.max.valueOf(),
|
||||
config.get('timelion:target_buckets') || 200,
|
||||
uiSettings.get('timelion:target_buckets') || 200,
|
||||
$scope.interval,
|
||||
config.get('timelion:min_interval') || '1ms',
|
||||
uiSettings.get('timelion:min_interval') || '1ms'
|
||||
);
|
||||
const format = getxAxisFormatter(interval);
|
||||
|
||||
// Use moment to format ticks so we get timezone correction
|
||||
options.xaxis.tickFormatter = function (val) {
|
||||
options.xaxis.tickFormatter = function(val: any) {
|
||||
return moment(val).format(format);
|
||||
};
|
||||
|
||||
// Calculate how many ticks can fit on the axis
|
||||
const tickLetterWidth = 7;
|
||||
const tickPadding = 45;
|
||||
options.xaxis.ticks = Math.floor($elem.width() / ((format.length * tickLetterWidth) + tickPadding));
|
||||
options.xaxis.ticks = Math.floor(
|
||||
$elem.width() / (format.length * tickLetterWidth + tickPadding)
|
||||
);
|
||||
|
||||
const series = _.map(plotConfig, function (series, index) {
|
||||
series = _.cloneDeep(_.defaults(series, {
|
||||
shadowSize: 0,
|
||||
lines: {
|
||||
lineWidth: 3
|
||||
}
|
||||
}));
|
||||
series._id = index;
|
||||
const series = _.map(plotConfig, function(serie: any, index) {
|
||||
serie = _.cloneDeep(
|
||||
_.defaults(serie, {
|
||||
shadowSize: 0,
|
||||
lines: {
|
||||
lineWidth: 3,
|
||||
},
|
||||
})
|
||||
);
|
||||
serie._id = index;
|
||||
|
||||
if (series.color) {
|
||||
if (serie.color) {
|
||||
const span = document.createElement('span');
|
||||
span.style.color = series.color;
|
||||
series.color = span.style.color;
|
||||
span.style.color = serie.color;
|
||||
serie.color = span.style.color;
|
||||
}
|
||||
|
||||
if (series._hide) {
|
||||
series.data = [];
|
||||
series.stack = false;
|
||||
//series.color = "#ddd";
|
||||
series.label = '(hidden) ' + series.label;
|
||||
if (serie._hide) {
|
||||
serie.data = [];
|
||||
serie.stack = false;
|
||||
// serie.color = "#ddd";
|
||||
serie.label = '(hidden) ' + serie.label;
|
||||
}
|
||||
|
||||
if (series._global) {
|
||||
_.merge(options, series._global, function (objVal, srcVal) {
|
||||
if (serie._global) {
|
||||
_.merge(options, serie._global, function(objVal, srcVal) {
|
||||
// This is kind of gross, it means that you can't replace a global value with a null
|
||||
// best you can do is an empty string. Deal with it.
|
||||
if (objVal == null) return srcVal;
|
||||
|
@ -321,13 +348,13 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
});
|
||||
}
|
||||
|
||||
return series;
|
||||
return serie;
|
||||
});
|
||||
|
||||
if (options.yaxes) {
|
||||
options.yaxes.forEach(yaxis => {
|
||||
options.yaxes.forEach((yaxis: any) => {
|
||||
if (yaxis && yaxis.units) {
|
||||
yaxis.tickFormatter = tickFormatters[yaxis.units.type];
|
||||
yaxis.tickFormatter = formatters[yaxis.units.type];
|
||||
const byteModes = ['bytes', 'bytes/s'];
|
||||
if (byteModes.includes(yaxis.units.type)) {
|
||||
yaxis.tickGenerator = generateTicks;
|
||||
|
@ -336,6 +363,7 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
});
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
$scope.plot = $.plot(canvasElem, _.compact(series), options);
|
||||
|
||||
if ($scope.plot) {
|
||||
|
@ -346,7 +374,7 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
legendScope = $scope.$new();
|
||||
// Used to toggle the series, and for displaying values on hover
|
||||
legendValueNumbers = canvasElem.find('.ngLegendValueNumber');
|
||||
_.each(canvasElem.find('.ngLegendValue'), function (elem) {
|
||||
_.each(canvasElem.find('.ngLegendValue'), function(elem) {
|
||||
$compile(elem)(legendScope);
|
||||
});
|
||||
|
||||
|
@ -363,7 +391,7 @@ export default function timechartFn(Private, config, $rootScope, $compile) {
|
|||
}
|
||||
}
|
||||
$scope.$watch('chart', drawPlot);
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
|
@ -17,13 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export default function generateTicksProvider() {
|
||||
|
||||
function floorInBase(n, base) {
|
||||
export function generateTicksProvider() {
|
||||
function floorInBase(n: any, base: any) {
|
||||
return base * Math.floor(n / base);
|
||||
}
|
||||
|
||||
function generateTicks(axis) {
|
||||
function generateTicks(axis: any) {
|
||||
const returnTicks = [];
|
||||
let tickSize = 2;
|
||||
let delta = axis.delta;
|
||||
|
@ -31,13 +30,13 @@ export default function generateTicksProvider() {
|
|||
let tickVal;
|
||||
let tickCount = 0;
|
||||
|
||||
//Count the steps
|
||||
// Count the steps
|
||||
while (Math.abs(delta) >= 1024) {
|
||||
steps++;
|
||||
delta /= 1024;
|
||||
}
|
||||
|
||||
//Set the tick size relative to the remaining delta
|
||||
// Set the tick size relative to the remaining delta
|
||||
while (tickSize <= 1024) {
|
||||
if (delta <= tickSize) {
|
||||
break;
|
||||
|
@ -46,17 +45,17 @@ export default function generateTicksProvider() {
|
|||
}
|
||||
axis.tickSize = tickSize * Math.pow(1024, steps);
|
||||
|
||||
//Calculate the new ticks
|
||||
// Calculate the new ticks
|
||||
const tickMin = floorInBase(axis.min, axis.tickSize);
|
||||
do {
|
||||
tickVal = tickMin + (tickCount++) * axis.tickSize;
|
||||
tickVal = tickMin + tickCount++ * axis.tickSize;
|
||||
returnTicks.push(tickVal);
|
||||
} while (tickVal < axis.max);
|
||||
|
||||
return returnTicks;
|
||||
}
|
||||
|
||||
return function (axis) {
|
||||
return function(axis: any) {
|
||||
return generateTicks(axis);
|
||||
};
|
||||
}
|
|
@ -17,12 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import Panel from '../panel';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import panelRegistry from '../../lib/panel_registry';
|
||||
import { timechartFn } from './schema';
|
||||
import { Panel } from '../panel';
|
||||
import { TimelionStartDependencies } from '../../plugin';
|
||||
|
||||
panelRegistry.register(function timeChartProvider(Private) {
|
||||
export function getTimeChart(dependencies: TimelionStartDependencies) {
|
||||
// Schema is broken out so that it may be extended for use in other plugins
|
||||
// Its also easier to test.
|
||||
return new Panel('timechart', Private(require('./schema'))(), i18n);
|
||||
});
|
||||
return new Panel('timechart', timechartFn(dependencies)());
|
||||
}
|
|
@ -21,13 +21,12 @@ import moment from 'moment';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export default function xaxisFormatterProvider(config) {
|
||||
|
||||
function getFormat(esInterval) {
|
||||
export function xaxisFormatterProvider(config: any) {
|
||||
function getFormat(esInterval: any) {
|
||||
const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/);
|
||||
|
||||
if (parts == null || parts[1] == null || parts[2] == null) {
|
||||
throw new Error (
|
||||
throw new Error(
|
||||
i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', {
|
||||
defaultMessage: 'Unknown interval',
|
||||
})
|
||||
|
@ -49,7 +48,7 @@ export default function xaxisFormatterProvider(config) {
|
|||
return config.get('dateFormat');
|
||||
}
|
||||
|
||||
return function (esInterval) {
|
||||
return function(esInterval: any) {
|
||||
return getFormat(esInterval);
|
||||
};
|
||||
}
|
100
src/legacy/core_plugins/timelion/public/plugin.ts
Normal file
100
src/legacy/core_plugins/timelion/public/plugin.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
LegacyCoreStart,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
UiSettingsClientContract,
|
||||
HttpSetup,
|
||||
} from 'kibana/public';
|
||||
import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public';
|
||||
import { VisualizationsSetup } from '../../visualizations/public/np_ready';
|
||||
import { getTimelionVisualizationConfig } from './timelion_vis_fn';
|
||||
import { getTimelionVisualization } from './vis';
|
||||
import { getTimeChart } from './panels/timechart/timechart';
|
||||
import { LegacyDependenciesPlugin, LegacyDependenciesPluginStart } from './shim';
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionPluginSetupDependencies {
|
||||
expressions: ReturnType<ExpressionsPlugin['setup']>;
|
||||
visualizations: VisualizationsSetup;
|
||||
|
||||
// Temporary solution
|
||||
__LEGACY: LegacyDependenciesPlugin;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionPluginStartDependencies {
|
||||
panelRegistry: any;
|
||||
|
||||
// Temporary solution
|
||||
__LEGACY: LegacyDependenciesPlugin;
|
||||
}
|
||||
|
||||
/** @private */
|
||||
export interface TimelionVisualizationDependencies {
|
||||
uiSettings: UiSettingsClientContract;
|
||||
http: HttpSetup;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type TimelionStartDependencies = Pick<TimelionVisualizationDependencies, 'uiSettings'> &
|
||||
LegacyDependenciesPluginStart;
|
||||
|
||||
/** @internal */
|
||||
export class TimelionPlugin
|
||||
implements
|
||||
Plugin<Promise<void>, void, TimelionPluginSetupDependencies, TimelionPluginStartDependencies> {
|
||||
initializerContext: PluginInitializerContext;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.initializerContext = initializerContext;
|
||||
}
|
||||
|
||||
public async setup(core: CoreSetup, plugins: TimelionPluginSetupDependencies) {
|
||||
const dependencies: TimelionVisualizationDependencies = {
|
||||
uiSettings: core.uiSettings,
|
||||
http: core.http,
|
||||
};
|
||||
|
||||
plugins.__LEGACY.setup();
|
||||
plugins.expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies));
|
||||
plugins.visualizations.types.registerVisualization(() =>
|
||||
getTimelionVisualization(dependencies)
|
||||
);
|
||||
}
|
||||
|
||||
public async start(core: CoreStart & LegacyCoreStart, plugins: TimelionPluginStartDependencies) {
|
||||
const dependencies: TimelionStartDependencies = {
|
||||
uiSettings: core.uiSettings,
|
||||
...(await plugins.__LEGACY.start()),
|
||||
};
|
||||
const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled');
|
||||
|
||||
if (timelionUiEnabled === false) {
|
||||
core.chrome.navLinks.update('timelion', { hidden: true });
|
||||
}
|
||||
|
||||
plugins.panelRegistry.register(() => getTimeChart(dependencies));
|
||||
}
|
||||
|
||||
public stop(): void {}
|
||||
}
|
|
@ -17,14 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
FeatureCatalogueRegistryProvider,
|
||||
FeatureCatalogueCategory,
|
||||
} from 'ui/registry/feature_catalogue';
|
||||
|
||||
import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
FeatureCatalogueRegistryProvider.register(() => {
|
||||
export const registerFeature = () => {
|
||||
return {
|
||||
id: 'timelion',
|
||||
title: 'Timelion',
|
||||
|
@ -37,4 +33,4 @@ FeatureCatalogueRegistryProvider.register(() => {
|
|||
showOnHomePage: false,
|
||||
category: FeatureCatalogueCategory.DATA,
|
||||
};
|
||||
});
|
||||
};
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
|
||||
function baseTickFormatter(value, axis) {
|
||||
function baseTickFormatter(value: any, axis: any) {
|
||||
const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
|
||||
const formatted = '' + Math.round(value * factor) / factor;
|
||||
|
||||
|
@ -30,15 +30,18 @@ function baseTickFormatter(value, axis) {
|
|||
const decimal = formatted.indexOf('.');
|
||||
const precision = decimal === -1 ? 0 : formatted.length - decimal - 1;
|
||||
if (precision < axis.tickDecimals) {
|
||||
return (precision ? formatted : formatted + '.') + ('' + factor).substr(1, axis.tickDecimals - precision);
|
||||
return (
|
||||
(precision ? formatted : formatted + '.') +
|
||||
('' + factor).substr(1, axis.tickDecimals - precision)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function unitFormatter(divisor, units) {
|
||||
return (val) => {
|
||||
function unitFormatter(divisor: any, units: any) {
|
||||
return (val: any) => {
|
||||
let index = 0;
|
||||
const isNegative = val < 0;
|
||||
val = Math.abs(val);
|
||||
|
@ -46,22 +49,26 @@ function unitFormatter(divisor, units) {
|
|||
val /= divisor;
|
||||
index++;
|
||||
}
|
||||
const value = Math.round(val * 100) / 100 * (isNegative ? -1 : 1);
|
||||
const value = (Math.round(val * 100) / 100) * (isNegative ? -1 : 1);
|
||||
return `${value}${units[index]}`;
|
||||
};
|
||||
}
|
||||
|
||||
export default function tickFormatters() {
|
||||
const formatters = {
|
||||
'bits': unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']),
|
||||
export function tickFormatters() {
|
||||
const formatters = {
|
||||
bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']),
|
||||
'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']),
|
||||
'bytes': unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']),
|
||||
bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']),
|
||||
'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']),
|
||||
'currency': function (val, axis) {
|
||||
return val.toLocaleString('en', { style: 'currency', currency: axis.options.units.prefix || 'USD' });
|
||||
currency(val: any, axis: any) {
|
||||
return val.toLocaleString('en', {
|
||||
style: 'currency',
|
||||
currency: axis.options.units.prefix || 'USD',
|
||||
});
|
||||
},
|
||||
'percent': function (val, axis) {
|
||||
let precision = _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0);
|
||||
percent(val: any, axis: any) {
|
||||
let precision =
|
||||
_.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0);
|
||||
// toFixed only accepts values between 0 and 20
|
||||
if (precision < 0) {
|
||||
precision = 0;
|
||||
|
@ -71,12 +78,12 @@ export default function tickFormatters() {
|
|||
|
||||
return (val * 100).toFixed(precision) + '%';
|
||||
},
|
||||
'custom': function (val, axis) {
|
||||
custom(val: any, axis: any) {
|
||||
const formattedVal = baseTickFormatter(val, axis);
|
||||
const prefix = axis.options.units.prefix;
|
||||
const suffix = axis.options.units.suffix;
|
||||
return prefix + formattedVal + suffix;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return formatters;
|
|
@ -17,22 +17,4 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export default function Panel(name, config) {
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.help = config.help || '';
|
||||
|
||||
this.render = config.render;
|
||||
|
||||
if (!config.render) {
|
||||
throw new Error (
|
||||
i18n.translate('timelion.panels.noRenderFunctionErrorMessage', {
|
||||
defaultMessage: 'Panel must have a rendering function'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
export * from './legacy_dependencies_plugin';
|
|
@ -17,18 +17,28 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { Plugin } from 'kibana/public';
|
||||
import { initTimelionLegacyModule } from './timelion_legacy_module';
|
||||
|
||||
import '../directives/chart/chart';
|
||||
import '../directives/timelion_interval/timelion_interval';
|
||||
import 'ui/state_management/app_state';
|
||||
/** @internal */
|
||||
export interface LegacyDependenciesPluginStart {
|
||||
$rootScope: any;
|
||||
$compile: any;
|
||||
}
|
||||
|
||||
import { uiModules } from 'ui/modules';
|
||||
export class LegacyDependenciesPlugin
|
||||
implements Plugin<void, Promise<LegacyDependenciesPluginStart>> {
|
||||
public setup() {
|
||||
initTimelionLegacyModule();
|
||||
}
|
||||
|
||||
uiModules
|
||||
.get('kibana/timelion_vis', ['kibana'])
|
||||
.controller('TimelionVisController', function ($scope) {
|
||||
$scope.$on('timelionChartRendered', event => {
|
||||
event.stopPropagation();
|
||||
$scope.renderComplete();
|
||||
});
|
||||
});
|
||||
public async start() {
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
|
||||
return {
|
||||
$rootScope: $injector.get('$rootScope'),
|
||||
$compile: $injector.get('$compile'),
|
||||
} as LegacyDependenciesPluginStart;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import 'ngreact';
|
||||
import 'brace/mode/hjson';
|
||||
import 'brace/ext/searchbox';
|
||||
import 'ui/accessibility/kbn_ui_ace_keyboard_mode';
|
||||
import 'ui/vis/map/service_settings';
|
||||
|
||||
import { once } from 'lodash';
|
||||
// @ts-ignore
|
||||
import { uiModules } from 'ui/modules';
|
||||
// @ts-ignore
|
||||
import { Chart } from '../directives/chart/chart';
|
||||
// @ts-ignore
|
||||
import { TimelionInterval } from '../directives/timelion_interval/timelion_interval';
|
||||
// @ts-ignore
|
||||
import { TimelionExpInput } from '../directives/timelion_expression_input';
|
||||
// @ts-ignore
|
||||
import { TimelionExpressionSuggestions } from '../directives/timelion_expression_suggestions/timelion_expression_suggestions';
|
||||
|
||||
/** @internal */
|
||||
export const initTimelionLegacyModule = once((): void => {
|
||||
require('ui/state_management/app_state');
|
||||
|
||||
uiModules
|
||||
.get('apps/timelion', [])
|
||||
.controller('TimelionVisController', function($scope: any) {
|
||||
$scope.$on('timelionChartRendered', (event: any) => {
|
||||
event.stopPropagation();
|
||||
$scope.renderComplete();
|
||||
});
|
||||
})
|
||||
.directive('chart', Chart)
|
||||
.directive('timelionInterval', TimelionInterval)
|
||||
.directive('timelionExpressionSuggestions', TimelionExpressionSuggestions)
|
||||
.directive('timelionExpressionInput', TimelionExpInput);
|
||||
});
|
|
@ -17,51 +17,65 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { functionsRegistry } from 'plugins/interpreter/registries';
|
||||
import { get } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimelionRequestHandlerProvider } from './vis/timelion_request_handler';
|
||||
import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public';
|
||||
import { getTimelionRequestHandler, TimelionSuccessResponse } from './vis/timelion_request_handler';
|
||||
import { TimelionVisualizationDependencies } from './plugin';
|
||||
|
||||
const name = 'timelion_vis';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
interface Arguments {
|
||||
expression: string;
|
||||
interval: any;
|
||||
}
|
||||
|
||||
export const timelionVis = () => ({
|
||||
name: 'timelion_vis',
|
||||
interface RenderValue {
|
||||
visData: Context;
|
||||
visType: typeof name;
|
||||
visParams: VisParams;
|
||||
}
|
||||
|
||||
type Context = KibanaContext | null;
|
||||
type VisParams = Arguments;
|
||||
type Return = Promise<Render<RenderValue>>;
|
||||
|
||||
export const getTimelionVisualizationConfig = (
|
||||
dependencies: TimelionVisualizationDependencies
|
||||
): ExpressionFunction<typeof name, Context, Arguments, Return> => ({
|
||||
name,
|
||||
type: 'render',
|
||||
context: {
|
||||
types: [
|
||||
'kibana_context',
|
||||
'null',
|
||||
],
|
||||
types: ['kibana_context', 'null'],
|
||||
},
|
||||
help: i18n.translate('timelion.function.help', {
|
||||
defaultMessage: 'Timelion visualization'
|
||||
defaultMessage: 'Timelion visualization',
|
||||
}),
|
||||
args: {
|
||||
expression: {
|
||||
types: ['string'],
|
||||
aliases: ['_'],
|
||||
default: '".es(*)"',
|
||||
help: '',
|
||||
},
|
||||
interval: {
|
||||
types: ['string', 'null'],
|
||||
default: 'auto',
|
||||
}
|
||||
help: '',
|
||||
},
|
||||
},
|
||||
async fn(context, args) {
|
||||
const $injector = await chrome.dangerouslyGetActiveInjector();
|
||||
const Private = $injector.get('Private');
|
||||
const timelionRequestHandler = Private(TimelionRequestHandlerProvider).handler;
|
||||
const timelionRequestHandler = getTimelionRequestHandler(dependencies);
|
||||
|
||||
const visParams = { expression: args.expression, interval: args.interval };
|
||||
|
||||
const response = await timelionRequestHandler({
|
||||
timeRange: get(context, 'timeRange', null),
|
||||
query: get(context, 'query', null),
|
||||
filters: get(context, 'filters', null),
|
||||
const response = (await timelionRequestHandler({
|
||||
timeRange: get(context, 'timeRange'),
|
||||
query: get(context, 'query'),
|
||||
filters: get(context, 'filters'),
|
||||
visParams,
|
||||
forceFetch: true,
|
||||
visParams: visParams,
|
||||
});
|
||||
})) as TimelionSuccessResponse;
|
||||
|
||||
response.visType = 'timelion';
|
||||
|
||||
|
@ -70,11 +84,9 @@ export const timelionVis = () => ({
|
|||
as: 'visualization',
|
||||
value: {
|
||||
visParams,
|
||||
visType: 'timelion',
|
||||
visType: name,
|
||||
visData: response,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
functionsRegistry.register(timelionVis);
|
|
@ -17,25 +17,19 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { visFactory } from 'ui/vis/vis_factory';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';
|
||||
import { TimelionRequestHandlerProvider } from './timelion_request_handler';
|
||||
// @ts-ignore
|
||||
import { DefaultEditorSize } from 'ui/vis/editor_size';
|
||||
import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type';
|
||||
|
||||
// we also need to load the controller and directive used by the template
|
||||
import './timelion_vis_controller';
|
||||
import '../directives/timelion_expression_input';
|
||||
|
||||
import { visFactory } from '../../../visualizations/public';
|
||||
import { getTimelionRequestHandler } from './timelion_request_handler';
|
||||
import visConfigTemplate from './timelion_vis.html';
|
||||
import editorConfigTemplate from './timelion_vis_params.html';
|
||||
import { TimelionVisualizationDependencies } from '../plugin';
|
||||
// @ts-ignore
|
||||
import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type';
|
||||
|
||||
// register the provider with the visTypes registry so that other know it exists
|
||||
VisTypesRegistryProvider.register(TimelionVisProvider);
|
||||
|
||||
export default function TimelionVisProvider(Private) {
|
||||
const timelionRequestHandler = Private(TimelionRequestHandlerProvider);
|
||||
export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) {
|
||||
const timelionRequestHandler = getTimelionRequestHandler(dependencies);
|
||||
|
||||
// return the visType object, which kibana will use to display and configure new
|
||||
// Vis object of this type.
|
||||
|
@ -50,7 +44,7 @@ export default function TimelionVisProvider(Private) {
|
|||
visConfig: {
|
||||
defaults: {
|
||||
expression: '.es(*)',
|
||||
interval: 'auto'
|
||||
interval: 'auto',
|
||||
},
|
||||
template: visConfigTemplate,
|
||||
},
|
||||
|
@ -58,7 +52,7 @@ export default function TimelionVisProvider(Private) {
|
|||
optionsTemplate: editorConfigTemplate,
|
||||
defaultSize: DefaultEditorSize.MEDIUM,
|
||||
},
|
||||
requestHandler: timelionRequestHandler.handler,
|
||||
requestHandler: timelionRequestHandler,
|
||||
responseHandler: 'none',
|
||||
options: {
|
||||
showIndexSelection: false,
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query';
|
||||
import { timezoneProvider } from 'ui/vis/lib/timezone';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const TimelionRequestHandlerProvider = function (Private, $http, config) {
|
||||
const timezone = Private(timezoneProvider)();
|
||||
|
||||
return {
|
||||
name: 'timelion',
|
||||
handler: function ({ timeRange, filters, query, visParams }) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const expression = visParams.expression;
|
||||
if (!expression) return;
|
||||
const esQueryConfigs = getEsQueryConfig(config);
|
||||
const httpResult = $http.post('../api/timelion/run', {
|
||||
sheet: [expression],
|
||||
extended: {
|
||||
es: {
|
||||
filter: buildEsQuery(undefined, query, filters, esQueryConfigs)
|
||||
}
|
||||
},
|
||||
time: _.extend(timeRange, {
|
||||
interval: visParams.interval,
|
||||
timezone: timezone
|
||||
}),
|
||||
})
|
||||
.then(resp => resp.data)
|
||||
.catch(resp => { throw resp.data; });
|
||||
|
||||
httpResult
|
||||
.then(function (resp) {
|
||||
resolve(resp);
|
||||
})
|
||||
.catch(function (resp) {
|
||||
const err = new Error(resp.message);
|
||||
err.stack = resp.stack;
|
||||
toastNotifications.addError(err, {
|
||||
title: i18n.translate('timelion.requestHandlerErrorTitle', {
|
||||
defaultMessage: 'Timelion request error',
|
||||
}),
|
||||
});
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export { TimelionRequestHandlerProvider };
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { buildEsQuery, getEsQueryConfig, Filter } from '@kbn/es-query';
|
||||
// @ts-ignore
|
||||
import { timezoneProvider } from 'ui/vis/lib/timezone';
|
||||
import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public';
|
||||
import { Query } from 'src/legacy/core_plugins/data/public';
|
||||
import { TimeRange } from 'src/plugins/data/public';
|
||||
import { VisParams } from 'ui/vis';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { TimelionVisualizationDependencies } from '../plugin';
|
||||
|
||||
interface Stats {
|
||||
cacheCount: number;
|
||||
invokeTime: number;
|
||||
queryCount: number;
|
||||
queryTime: number;
|
||||
sheetTime: number;
|
||||
}
|
||||
|
||||
interface Sheet {
|
||||
list: Array<Record<string, unknown>>;
|
||||
render: Record<string, unknown>;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface TimelionSuccessResponse {
|
||||
sheet: Sheet[];
|
||||
stats: Stats;
|
||||
visType: string;
|
||||
type: KIBANA_CONTEXT_NAME;
|
||||
}
|
||||
|
||||
export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) {
|
||||
const { uiSettings, http } = dependencies;
|
||||
const timezone = timezoneProvider(uiSettings)();
|
||||
|
||||
return async function({
|
||||
timeRange,
|
||||
filters,
|
||||
query,
|
||||
visParams,
|
||||
}: {
|
||||
timeRange: TimeRange;
|
||||
filters: Filter[];
|
||||
query: Query;
|
||||
visParams: VisParams;
|
||||
forceFetch?: boolean;
|
||||
}): Promise<TimelionSuccessResponse | void> {
|
||||
const expression = visParams.expression;
|
||||
|
||||
if (!expression) return;
|
||||
|
||||
const esQueryConfigs = getEsQueryConfig(uiSettings);
|
||||
|
||||
try {
|
||||
return await http.post('../api/timelion/run', {
|
||||
body: JSON.stringify({
|
||||
sheet: [expression],
|
||||
extended: {
|
||||
es: {
|
||||
filter: buildEsQuery(undefined, query, filters, esQueryConfigs),
|
||||
},
|
||||
},
|
||||
time: { ...timeRange, interval: visParams.interval, timezone },
|
||||
}),
|
||||
});
|
||||
} catch (e) {
|
||||
const err = new Error(e.data.message);
|
||||
|
||||
err.stack = e.data.stack;
|
||||
|
||||
toastNotifications.addError(err, {
|
||||
title: i18n.translate('timelion.requestHandlerErrorTitle', {
|
||||
defaultMessage: 'Timelion request error',
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
|
@ -73,6 +73,7 @@ export interface LegacyPluginOptions {
|
|||
visTypes: string[];
|
||||
embeddableActions?: string[];
|
||||
embeddableFactories?: string[];
|
||||
uiSettingDefaults?: Record<string, any>;
|
||||
}>;
|
||||
uiCapabilities?: Capabilities;
|
||||
publicDir: any;
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Filter } from '@kbn/es-query';
|
|||
import { TimeRange, Query } from 'src/plugins/data/public';
|
||||
|
||||
const name = 'kibana_context';
|
||||
export type KIBANA_CONTEXT_NAME = 'kibana_context';
|
||||
|
||||
export interface KibanaContext {
|
||||
type: typeof name;
|
||||
|
|
Loading…
Reference in a new issue