From 3c1fe2860e55245fe877fbf5d9bbb261494e275f Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Thu, 18 Jul 2019 14:32:57 +0300 Subject: [PATCH] [Vega] Shim new platform --- src/legacy/core_plugins/vega/index.ts | 47 +++++ .../public/__tests__/vega_visualization.js | 32 +-- .../data_model/__tests__/es_query_parser.js | 7 +- .../vega/public/data_model/es_query_parser.js | 6 +- .../vega/public/data_model/vega_parser.js | 4 +- .../core_plugins/vega/public/helpers/index.js | 20 ++ .../helpers/vega_config_provider.js} | 21 +- .../vega_help_menu_directives.js => index.ts} | 21 +- src/legacy/core_plugins/vega/public/legacy.ts | 40 ++++ src/legacy/core_plugins/vega/public/plugin.ts | 75 +++++++ .../core_plugins/vega/public/shim/index.ts | 20 ++ .../public/shim/legacy_dependencies_plugin.ts | 53 +++++ .../vega/public/shim/vega_legacy_module.ts | 49 +++++ .../vega/public/vega_editor_controller.js | 111 +++++------ .../core_plugins/vega/public/vega_fn.js | 22 +-- .../vega/public/vega_request_handler.js | 23 +-- .../core_plugins/vega/public/vega_type.js | 30 ++- .../vega/public/vega_view/vega_base_view.js | 14 +- .../vega/public/vega_visualization.js | 185 +++++++++--------- 19 files changed, 521 insertions(+), 259 deletions(-) create mode 100644 src/legacy/core_plugins/vega/index.ts create mode 100644 src/legacy/core_plugins/vega/public/helpers/index.js rename src/legacy/core_plugins/vega/{index.js => public/helpers/vega_config_provider.js} (60%) rename src/legacy/core_plugins/vega/public/{help_menus/vega_help_menu_directives.js => index.ts} (60%) create mode 100644 src/legacy/core_plugins/vega/public/legacy.ts create mode 100644 src/legacy/core_plugins/vega/public/plugin.ts create mode 100644 src/legacy/core_plugins/vega/public/shim/index.ts create mode 100644 src/legacy/core_plugins/vega/public/shim/legacy_dependencies_plugin.ts create mode 100644 src/legacy/core_plugins/vega/public/shim/vega_legacy_module.ts diff --git a/src/legacy/core_plugins/vega/index.ts b/src/legacy/core_plugins/vega/index.ts new file mode 100644 index 0000000000000..b391949959593 --- /dev/null +++ b/src/legacy/core_plugins/vega/index.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; + +const vegaPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'vega', + require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({ + enableExternalUrls: server.config().get('vega.enableExternalUrls'), + }), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + enableExternalUrls: Joi.boolean().default(false), + }).default(); + }, + } as Legacy.PluginSpecOptions); + +// eslint-disable-next-line import/no-default-export +export default vegaPluginInitializer; diff --git a/src/legacy/core_plugins/vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vega/public/__tests__/vega_visualization.js index d40ba8c345681..681f4486a02d7 100644 --- a/src/legacy/core_plugins/vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vega/public/__tests__/vega_visualization.js @@ -21,7 +21,7 @@ import Promise from 'bluebird'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import $ from 'jquery'; -import { VegaVisualizationProvider } from '../vega_visualization'; +import { createVegaVisualization } from '../vega_visualization'; import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; import * as visModule from 'ui/vis'; import { ImageComparator } from 'test_utils/image_comparator'; @@ -41,33 +41,44 @@ import vegaMapImage256 from './vega_map_image_256.png'; import { VegaParser } from '../data_model/vega_parser'; import { SearchCache } from '../data_model/search_cache'; +import { visualizations } from '../../../visualizations/public'; +import { createVegaTypeDefinition } from '../vega_type'; + const THRESHOLD = 0.10; -const PIXEL_DIFF = 10; +const PIXEL_DIFF = 30; describe('VegaVisualizations', () => { - let domNode; let VegaVisualization; let Vis; let indexPattern; let vis; let imageComparator; + let vegaVisualizationDependencies; beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject((Private) => { + beforeEach(ngMock.inject((Private, $injector) => { + vegaVisualizationDependencies = { + es: $injector.get('es'), + serviceSettings: $injector.get('serviceSettings'), + uiSettings: $injector.get('config'), + }; + + visualizations.types.VisTypesRegistryProvider.register(() => + createVegaTypeDefinition(vegaVisualizationDependencies) + ); Vis = Private(visModule.VisProvider); - VegaVisualization = Private(VegaVisualizationProvider); - indexPattern = Private(LogstashIndexPatternStubProvider); + VegaVisualization = createVegaVisualization(vegaVisualizationDependencies); + indexPattern = Private(LogstashIndexPatternStubProvider); })); - describe('VegaVisualization - basics', () => { - beforeEach(async function () { setupDOM('512px', '512px'); imageComparator = new ImageComparator(); + vis = new Vis(indexPattern, { type: 'vega' }); }); @@ -77,10 +88,10 @@ describe('VegaVisualizations', () => { }); it('should show vegalite graph and update on resize', async function () { - let vegaVis; try { vegaVis = new VegaVisualization(domNode, vis); + const vegaParser = new VegaParser(vegaliteGraph, new SearchCache()); await vegaParser.parseAsync(); @@ -105,15 +116,14 @@ describe('VegaVisualizations', () => { let vegaVis; try { - vegaVis = new VegaVisualization(domNode, vis); const vegaParser = new VegaParser(vegaGraph, new SearchCache()); await vegaParser.parseAsync(); await vegaVis.render(vegaParser, vis.params, { data: true }); const mismatchedPixels = await compareImage(vegaImage512); - expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); + expect(mismatchedPixels).to.be.lessThan(PIXEL_DIFF); } finally { vegaVis.destroy(); } diff --git a/src/legacy/core_plugins/vega/public/data_model/__tests__/es_query_parser.js b/src/legacy/core_plugins/vega/public/data_model/__tests__/es_query_parser.js index 40cbbb203deb8..bb3d80c4e0830 100644 --- a/src/legacy/core_plugins/vega/public/data_model/__tests__/es_query_parser.js +++ b/src/legacy/core_plugins/vega/public/data_model/__tests__/es_query_parser.js @@ -73,19 +73,18 @@ describe('EsQueryParser.populateData', () => { beforeEach(() => { searchStub = sinon.stub(); - parser = new EsQueryParser({}, { search: searchStub }, undefined, undefined, 1234); + parser = new EsQueryParser({}, { search: searchStub }, undefined, undefined); searchStub.returns(Promise.resolve([{}, {}])); }); it('should set the timeout for each request', async () => { await parser.populateData([{ url: { body: { } }, dataObject: {} }, { url: { body: {} }, dataObject: {} }]); - expect(searchStub.firstCall.args[0][0].body.timeout).to.eql('1234ms'); - expect(searchStub.firstCall.args[0][1].body.timeout).to.eql('1234ms'); + expect(searchStub.firstCall.args[0][0].body.timeout).to.be.defined; }); it('should remove possible timeout parameters on a request', async () => { await parser.populateData([{ url: { timeout: '500h', body: { timeout: '500h' } }, dataObject: {} }]); - expect(searchStub.firstCall.args[0][0].body.timeout).to.eql('1234ms'); + expect(searchStub.firstCall.args[0][0].body.timeout).to.be.defined; expect(searchStub.firstCall.args[0][0].timeout).to.be(undefined); }); }); diff --git a/src/legacy/core_plugins/vega/public/data_model/es_query_parser.js b/src/legacy/core_plugins/vega/public/data_model/es_query_parser.js index e55d0e4893310..fb1968fd110d0 100644 --- a/src/legacy/core_plugins/vega/public/data_model/es_query_parser.js +++ b/src/legacy/core_plugins/vega/public/data_model/es_query_parser.js @@ -21,6 +21,8 @@ import _ from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { getEsShardTimeout } from '../helpers'; + const TIMEFILTER = '%timefilter%'; const AUTOINTERVAL = '%autointerval%'; const MUST_CLAUSE = '%dashboard_context-must_clause%'; @@ -36,12 +38,12 @@ const TIMEFIELD = '%timefield%'; */ export class EsQueryParser { - constructor(timeCache, searchCache, filters, onWarning, esShardTimeout) { + constructor(timeCache, searchCache, filters, onWarning) { this._timeCache = timeCache; this._searchCache = searchCache; this._filters = filters; this._onWarning = onWarning; - this._esShardTimeout = esShardTimeout; + this._esShardTimeout = getEsShardTimeout(); } // noinspection JSMethodCanBeStatic diff --git a/src/legacy/core_plugins/vega/public/data_model/vega_parser.js b/src/legacy/core_plugins/vega/public/data_model/vega_parser.js index 073f9d42d0934..ad76d424459f3 100644 --- a/src/legacy/core_plugins/vega/public/data_model/vega_parser.js +++ b/src/legacy/core_plugins/vega/public/data_model/vega_parser.js @@ -47,7 +47,7 @@ const DEFAULT_PARSER = 'elasticsearch'; export class VegaParser { - constructor(spec, searchCache, timeCache, filters, serviceSettings, esShardTimeout) { + constructor(spec, searchCache, timeCache, filters, serviceSettings) { this.spec = spec; this.hideWarnings = false; this.error = undefined; @@ -55,7 +55,7 @@ export class VegaParser { const onWarn = this._onWarning.bind(this); this._urlParsers = { - elasticsearch: new EsQueryParser(timeCache, searchCache, filters, onWarn, esShardTimeout), + elasticsearch: new EsQueryParser(timeCache, searchCache, filters, onWarn), emsfile: new EmsFileParser(serviceSettings), url: new UrlParser(onWarn), }; diff --git a/src/legacy/core_plugins/vega/public/helpers/index.js b/src/legacy/core_plugins/vega/public/helpers/index.js new file mode 100644 index 0000000000000..e9d6eb21fd3c7 --- /dev/null +++ b/src/legacy/core_plugins/vega/public/helpers/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './vega_config_provider'; diff --git a/src/legacy/core_plugins/vega/index.js b/src/legacy/core_plugins/vega/public/helpers/vega_config_provider.js similarity index 60% rename from src/legacy/core_plugins/vega/index.js rename to src/legacy/core_plugins/vega/public/helpers/vega_config_provider.js index cd6245193eff1..cc41b18479f93 100644 --- a/src/legacy/core_plugins/vega/index.js +++ b/src/legacy/core_plugins/vega/public/helpers/vega_config_provider.js @@ -17,22 +17,7 @@ * under the License. */ -import { resolve } from 'path'; +import chrome from 'ui/chrome'; -export default kibana => new kibana.Plugin({ - id: 'vega', - require: ['elasticsearch'], - - uiExports: { - visTypes: ['plugins/vega/vega_type'], - interpreter: ['plugins/vega/vega_fn'], - injectDefaultVars: server => ({ vegaConfig: server.config().get('vega') }), - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - }, - - config: (Joi) => Joi.object({ - enabled: Joi.boolean().default(true), - enableExternalUrls: Joi.boolean().default(false) - }).default(), - -}); +export const getEsShardTimeout = () => chrome.getInjected('esShardTimeout'); +export const getEnableExternalUrls = () => chrome.getInjected('enableExternalUrls'); diff --git a/src/legacy/core_plugins/vega/public/help_menus/vega_help_menu_directives.js b/src/legacy/core_plugins/vega/public/index.ts similarity index 60% rename from src/legacy/core_plugins/vega/public/help_menus/vega_help_menu_directives.js rename to src/legacy/core_plugins/vega/public/index.ts index dae4a64fe8420..34ca0e72190e4 100644 --- a/src/legacy/core_plugins/vega/public/help_menus/vega_help_menu_directives.js +++ b/src/legacy/core_plugins/vega/public/index.ts @@ -17,20 +17,9 @@ * under the License. */ +import { PluginInitializerContext } from '../../../../core/public'; +import { VegaPlugin as Plugin } from './plugin'; -import 'ngreact'; - -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -const module = uiModules.get('kibana/vega', ['react']); - -import { VegaHelpMenu } from './vega_help_menu'; -import { VegaActionsMenu } from './vega_action_menu'; - -module.directive('vegaActionsMenu', function (reactDirective) { - return reactDirective(wrapInI18nContext(VegaActionsMenu)); -}); - -module.directive('vegaHelpMenu', function (reactDirective) { - return reactDirective(wrapInI18nContext(VegaHelpMenu)); -}); +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vega/public/legacy.ts b/src/legacy/core_plugins/vega/public/legacy.ts new file mode 100644 index 0000000000000..5136046b31a97 --- /dev/null +++ b/src/legacy/core_plugins/vega/public/legacy.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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 { visualizations } from '../../visualizations/public'; +import { VegaPluginSetupDependencies } from './plugin'; +import { LegacyDependenciesPlugin } from './shim'; +import { plugin } from '.'; + +const plugins: Readonly = { + visualizations, + data: npSetup.plugins.data, + + // Temporary solution + // It will be removed when all dependent services are migrated to the new platform. + __LEGACY: new LegacyDependenciesPlugin(), +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, plugins); +export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/vega/public/plugin.ts b/src/legacy/core_plugins/vega/public/plugin.ts new file mode 100644 index 0000000000000..20274df31ab44 --- /dev/null +++ b/src/legacy/core_plugins/vega/public/plugin.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES 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, + CoreSetup, + CoreStart, + Plugin, + UiSettingsClientContract, +} from '../../../../core/public'; +import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; + +// @ts-ignore +import { createVegaFn } from './vega_fn'; +// @ts-ignore +import { createVegaTypeDefinition } from './vega_type'; +import { DataSetup } from '../../data/public'; +import { VisualizationsSetup } from '../../visualizations/public'; + +/** @private */ +interface VegaVisualizationDependencies extends LegacyDependenciesPluginSetup { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export interface VegaPluginSetupDependencies { + // TODO: Remove `any` as functionsRegistry will be added to the DataSetup. + data: DataSetup | any; + visualizations: VisualizationsSetup; + __LEGACY: LegacyDependenciesPlugin; +} + +/** @internal */ +export class VegaPlugin implements Plugin, void> { + initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public async setup( + core: CoreSetup, + { data, visualizations, __LEGACY }: VegaPluginSetupDependencies + ) { + const visualizationDependencies: Readonly = { + uiSettings: core.uiSettings, + ...(await __LEGACY.setup()), + }; + + data.expressions.registerFunction(() => createVegaFn(visualizationDependencies)); + + visualizations.types.VisTypesRegistryProvider.register(() => + createVegaTypeDefinition(visualizationDependencies) + ); + } + + public start(core: CoreStart) { + // nothing to do here yet + } +} diff --git a/src/legacy/core_plugins/vega/public/shim/index.ts b/src/legacy/core_plugins/vega/public/shim/index.ts new file mode 100644 index 0000000000000..cfc7b62ff4f86 --- /dev/null +++ b/src/legacy/core_plugins/vega/public/shim/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './legacy_dependencies_plugin'; diff --git a/src/legacy/core_plugins/vega/public/shim/legacy_dependencies_plugin.ts b/src/legacy/core_plugins/vega/public/shim/legacy_dependencies_plugin.ts new file mode 100644 index 0000000000000..53e585f16410d --- /dev/null +++ b/src/legacy/core_plugins/vega/public/shim/legacy_dependencies_plugin.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chrome from 'ui/chrome'; +import { CoreStart, Plugin } from 'kibana/public'; +import { initVegaLegacyModule } from './vega_legacy_module'; + +/** @internal */ +export interface LegacyDependenciesPluginSetup { + es: any; + serviceSettings: any; +} + +export class LegacyDependenciesPlugin + implements Plugin, void> { + public async setup() { + // Init kibana/vega AngularJS module. + initVegaLegacyModule(); + + const $injector = await chrome.dangerouslyGetActiveInjector(); + + return { + // Client of Elastic Search. + es: $injector.get('es'), + + // Settings for EMSClient. + // EMSClient, which currently lives in the tile_map vis, + // will probably end up being exposed from the future vis_type_maps plugin, + // which would register both the tile_map and the region_map vis plugins. + serviceSettings: $injector.get('serviceSettings'), + } as LegacyDependenciesPluginSetup; + } + + public start(core: CoreStart) { + // nothing to do here yet + } +} diff --git a/src/legacy/core_plugins/vega/public/shim/vega_legacy_module.ts b/src/legacy/core_plugins/vega/public/shim/vega_legacy_module.ts new file mode 100644 index 0000000000000..4dc6f03821649 --- /dev/null +++ b/src/legacy/core_plugins/vega/public/shim/vega_legacy_module.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import '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'; +import { wrapInI18nContext } from 'ui/i18n'; + +// @ts-ignore +import { VegaEditorController } from '../vega_editor_controller'; +// @ts-ignore +import { VegaHelpMenu } from '../help_menus/vega_help_menu'; +// @ts-ignore +import { VegaActionsMenu } from '../help_menus/vega_action_menu'; + +/** @internal */ +export const initVegaLegacyModule = once((): void => { + uiModules + .get('kibana/vega', ['react']) + .controller('VegaEditorController', VegaEditorController) + .directive('vegaActionsMenu', (reactDirective: any) => + reactDirective(wrapInI18nContext(VegaActionsMenu)) + ) + .directive('vegaHelpMenu', (reactDirective: any) => + reactDirective(wrapInI18nContext(VegaHelpMenu)) + ); +}); diff --git a/src/legacy/core_plugins/vega/public/vega_editor_controller.js b/src/legacy/core_plugins/vega/public/vega_editor_controller.js index 451ff536a8ba0..cd50f63ba1ae3 100644 --- a/src/legacy/core_plugins/vega/public/vega_editor_controller.js +++ b/src/legacy/core_plugins/vega/public/vega_editor_controller.js @@ -19,73 +19,68 @@ import compactStringify from 'json-stringify-pretty-compact'; import hjson from 'hjson'; -import { uiModules } from 'ui/modules'; - -import 'ui/accessibility/kbn_ui_ace_keyboard_mode'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -const module = uiModules.get('kibana/vega', ['kibana']); -module.controller('VegaEditorController', ($scope /*, kbnUiAceKeyboardModeService*/) => { - return new (class VegaEditorController { - constructor() { - $scope.aceLoaded = (editor) => { - editor.$blockScrolling = Infinity; - - const session = editor.getSession(); - session.setTabSize(2); - session.setUseSoftTabs(true); +export class VegaEditorController { + constructor($scope) { + this.$scope = $scope; + $scope.aceLoaded = (editor) => { + editor.$blockScrolling = Infinity; - this.aceEditor = editor; - }; + const session = editor.getSession(); + session.setTabSize(2); + session.setUseSoftTabs(true); - $scope.formatJson = (event) => { - this._format(event, compactStringify, { - maxLength: this._getCodeWidth(), - }); - }; + this.aceEditor = editor; + }; - $scope.formatHJson = (event) => { - this._format(event, hjson.stringify, { - condense: this._getCodeWidth(), - bracesSameLine: true, - keepWsc: true, - }); - }; - } + $scope.formatJson = (event) => { + this._format(event, compactStringify, { + maxLength: this._getCodeWidth(), + }); + }; - _getCodeWidth() { - return this.aceEditor.getSession().getWrapLimit(); - } + $scope.formatHJson = (event) => { + this._format(event, hjson.stringify, { + condense: this._getCodeWidth(), + bracesSameLine: true, + keepWsc: true, + }); + }; + } - _format(event, stringify, opts) { - event.preventDefault(); + _getCodeWidth() { + return this.aceEditor.getSession().getWrapLimit(); + } - let newSpec; - try { - const spec = hjson.parse(this.aceEditor.getSession().doc.getValue(), { legacyRoot: false, keepWsc: true }); - newSpec = stringify(spec, opts); - } catch (err) { - // This is a common case - user tries to format an invalid HJSON text - toastNotifications.addError(err, { - title: i18n.translate('vega.editor.formatError', { - defaultMessage: 'Error formatting spec', - }), - }); - return; - } + _format(event, stringify, opts) { + event.preventDefault(); - // ui-ace only accepts changes from the editor when they - // happen outside of a digest cycle - // Per @spalger, we used $$postDigest() instead of setTimeout(() => {}, 0) - // because it better described the intention. - $scope.$$postDigest(() => { - // set the new value to the session doc so that it - // is treated as an edit by ace: ace adds it to the - // undo stack and emits it as a change like all - // other edits - this.aceEditor.getSession().doc.setValue(newSpec); + let newSpec; + try { + const spec = hjson.parse(this.aceEditor.getSession().doc.getValue(), { legacyRoot: false, keepWsc: true }); + newSpec = stringify(spec, opts); + } catch (err) { + // This is a common case - user tries to format an invalid HJSON text + toastNotifications.addError(err, { + title: i18n.translate('vega.editor.formatError', { + defaultMessage: 'Error formatting spec', + }), }); + return; } - })(); -}); + + // ui-ace only accepts changes from the editor when they + // happen outside of a digest cycle + // Per @spalger, we used $$postDigest() instead of setTimeout(() => {}, 0) + // because it better described the intention. + this.$scope.$$postDigest(() => { + // set the new value to the session doc so that it + // is treated as an edit by ace: ace adds it to the + // undo stack and emits it as a change like all + // other edits + this.aceEditor.getSession().doc.setValue(newSpec); + }); + } +} diff --git a/src/legacy/core_plugins/vega/public/vega_fn.js b/src/legacy/core_plugins/vega/public/vega_fn.js index 86e6980f3d213..c18d06a4a1ee8 100644 --- a/src/legacy/core_plugins/vega/public/vega_fn.js +++ b/src/legacy/core_plugins/vega/public/vega_fn.js @@ -17,13 +17,11 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { VegaRequestHandlerProvider } from './vega_request_handler'; +import { createVegaRequestHandler } from './vega_request_handler'; -export const vega = () => ({ +export const createVegaFn = (dependencies) => ({ name: 'vega', type: 'render', context: { @@ -33,7 +31,7 @@ export const vega = () => ({ ], }, help: i18n.translate('vega.function.help', { - defaultMessage: 'Vega visualization' + defaultMessage: 'Vega visualization', }), args: { spec: { @@ -42,16 +40,14 @@ export const vega = () => ({ }, }, async fn(context, args) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - const vegaRequestHandler = Private(VegaRequestHandlerProvider).handler; + const vegaRequestHandler = createVegaRequestHandler(dependencies); const response = await vegaRequestHandler({ timeRange: get(context, 'timeRange', null), query: get(context, 'query', null), filters: get(context, 'filters', null), visParams: { spec: args.spec }, - forceFetch: true + forceFetch: true, }); return { @@ -61,11 +57,9 @@ export const vega = () => ({ visData: response, visType: 'vega', visConfig: { - spec: args.spec + spec: args.spec, }, - } + }, }; - } + }, }); - -functionsRegistry.register(vega); diff --git a/src/legacy/core_plugins/vega/public/vega_request_handler.js b/src/legacy/core_plugins/vega/public/vega_request_handler.js index 1bef41a84bcb3..1bffafded4474 100644 --- a/src/legacy/core_plugins/vega/public/vega_request_handler.js +++ b/src/legacy/core_plugins/vega/public/vega_request_handler.js @@ -16,29 +16,24 @@ * specific language governing permissions and limitations * under the License. */ +import { timefilter } from 'ui/timefilter'; +import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; import { VegaParser } from './data_model/vega_parser'; import { SearchCache } from './data_model/search_cache'; import { TimeCache } from './data_model/time_cache'; -import { timefilter } from 'ui/timefilter'; -import { buildEsQuery, getEsQueryConfig } from '@kbn/es-query'; -export function VegaRequestHandlerProvider(es, serviceSettings, config, esShardTimeout) { +export function createVegaRequestHandler({ uiSettings, es, serviceSettings }) { const searchCache = new SearchCache(es, { max: 10, maxAge: 4 * 1000 }); const timeCache = new TimeCache(timefilter, 3 * 1000); + return ({ timeRange, filters, query, visParams }) => { + timeCache.setTimeRange(timeRange); - return { - - name: 'vega', - - handler({ timeRange, filters, query, visParams }) { - timeCache.setTimeRange(timeRange); - const esQueryConfigs = getEsQueryConfig(config); - const filtersDsl = buildEsQuery(undefined, query, filters, esQueryConfigs); - const vp = new VegaParser(visParams.spec, searchCache, timeCache, filtersDsl, serviceSettings, esShardTimeout); - return vp.parseAsync(); - } + const esQueryConfigs = getEsQueryConfig(uiSettings); + const filtersDsl = buildEsQuery(undefined, query, filters, esQueryConfigs); + const vp = new VegaParser(visParams.spec, searchCache, timeCache, filtersDsl, serviceSettings); + return vp.parseAsync(); }; } diff --git a/src/legacy/core_plugins/vega/public/vega_type.js b/src/legacy/core_plugins/vega/public/vega_type.js index b59a7b9501e46..3dd3a6a23af07 100644 --- a/src/legacy/core_plugins/vega/public/vega_type.js +++ b/src/legacy/core_plugins/vega/public/vega_type.js @@ -17,30 +17,24 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; -import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { i18n } from '@kbn/i18n'; import { DefaultEditorSize } from 'ui/vis/editor_size'; import { Status } from 'ui/vis/update_status'; import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -import { VegaRequestHandlerProvider } from './vega_request_handler'; -import { VegaVisualizationProvider } from './vega_visualization'; +import { createVegaRequestHandler } from './vega_request_handler'; +import { createVegaVisualization } from './vega_visualization'; -// Editor-specific code -import 'brace/mode/hjson'; -import 'brace/ext/searchbox'; -import './vega_editor_controller'; -import './help_menus/vega_help_menu_directives'; import vegaEditorTemplate from './vega_editor_template.html'; import defaultSpec from '!!raw-loader!./default.spec.hjson'; -VisTypesRegistryProvider.register((Private) => { - const VisFactory = Private(VisFactoryProvider); - const vegaRequestHandler = Private(VegaRequestHandlerProvider).handler; - const VegaVisualization = Private(VegaVisualizationProvider); +import { visFactory } from '../../visualizations/public'; - return VisFactory.createBaseVisualization({ +export const createVegaTypeDefinition = (dependencies) => { + const requestHandler = createVegaRequestHandler(dependencies); + const visualization = createVegaVisualization(dependencies); + + return visFactory.createBaseVisualization({ name: 'vega', title: 'Vega', description: i18n.translate('vega.type.vegaDescription', { @@ -54,9 +48,9 @@ VisTypesRegistryProvider.register((Private) => { enableAutoApply: true, defaultSize: DefaultEditorSize.MEDIUM, }, - visualization: VegaVisualization, + visualization, + requestHandler, requiresUpdateStatus: [Status.DATA, Status.RESIZE], - requestHandler: vegaRequestHandler, responseHandler: 'none', options: { showIndexSelection: false, @@ -66,4 +60,4 @@ VisTypesRegistryProvider.register((Private) => { stage: 'experimental', feedbackMessage: defaultFeedbackMessage, }); -}); +}; diff --git a/src/legacy/core_plugins/vega/public/vega_view/vega_base_view.js b/src/legacy/core_plugins/vega/public/vega_view/vega_base_view.js index c8d7ce7e70db9..750ddeace3a5a 100644 --- a/src/legacy/core_plugins/vega/public/vega_view/vega_base_view.js +++ b/src/legacy/core_plugins/vega/public/vega_view/vega_base_view.js @@ -17,6 +17,7 @@ * under the License. */ +import chrome from 'ui/chrome'; import $ from 'jquery'; import moment from 'moment'; import dateMath from '@elastic/datemath'; @@ -28,6 +29,8 @@ import { i18n } from '@kbn/i18n'; import { TooltipHandler } from './vega_tooltip'; import { buildQueryFilter } from '@kbn/es-query'; +import { getEnableExternalUrls } from '../helpers/vega_config_provider'; + vega.scheme('elastic', VISUALIZATION_COLORS); // Vega's extension functions are global. When called, @@ -61,9 +64,6 @@ export function bypassExternalUrlCheck(url) { export class VegaBaseView { constructor(opts) { - // $rootScope is a temp workaround, see usage below - this._$rootScope = opts.$rootScope; - this._vegaConfig = opts.vegaConfig; this._$parentEl = $(opts.parentEl); this._parser = opts.vegaParser; this._serviceSettings = opts.serviceSettings; @@ -75,6 +75,7 @@ export class VegaBaseView { this._$messages = null; this._destroyHandlers = []; this._initialized = false; + this._enableExternalUrls = getEnableExternalUrls(); } async init() { @@ -145,7 +146,7 @@ export class VegaBaseView { // If uri has a bypass token, the uri was encoded by bypassExternalUrlCheck() above. // because user can only supply pure JSON data structure. uri = uri.url; - } else if (!this._vegaConfig.enableExternalUrls) { + } else if (!this._enableExternalUrls) { throw new Error(i18n.translate('vega.vegaParser.baseView.externalUrlsAreNotEnabledErrorMessage', { defaultMessage: 'External URLs are not enabled. Add {enableExternalUrls} to {kibanaConfigFileName}', values: { @@ -271,13 +272,14 @@ export class VegaBaseView { * @param {string} [index] as defined in Kibana, or default if missing */ async removeFilterHandler(query, index) { + const $injector = await chrome.dangerouslyGetActiveInjector(); const indexId = await this._findIndex(index); const filter = buildQueryFilter(query, indexId); // This is a workaround for the https://github.com/elastic/kibana/issues/18863 // Once fixed, replace with a direct call (no await is needed because its not async) - // this._queryfilter.removeFilter(filter); - this._$rootScope.$evalAsync(() => { + // this._queryfilter.removeFilter(filter); + $injector.get('$rootScope').$evalAsync(() => { try { this._queryfilter.removeFilter(filter); } catch (err) { diff --git a/src/legacy/core_plugins/vega/public/vega_visualization.js b/src/legacy/core_plugins/vega/public/vega_visualization.js index be97789fe82be..a4753a0e8d712 100644 --- a/src/legacy/core_plugins/vega/public/vega_visualization.js +++ b/src/legacy/core_plugins/vega/public/vega_visualization.js @@ -16,118 +16,111 @@ * specific language governing permissions and limitations * under the License. */ - -import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; import { VegaView } from './vega_view/vega_view'; import { VegaMapView } from './vega_view/vega_map_view'; -import { SavedObjectsClientProvider, findObjectByTitle } from 'ui/saved_objects'; - -// $rootScope is for the removeFilter() workaround, see vega_view/vega_base_view.js -export function VegaVisualizationProvider(Private, vegaConfig, serviceSettings, $rootScope) { - - const savedObjectsClient = Private(SavedObjectsClientProvider); - - return class VegaVisualization { - constructor(el, vis) { - this._el = el; - this._vis = vis; - } - - /** - * Find index pattern by its title, of if not given, gets default - * @param {string} [index] - * @returns {Promise} index id - */ - async findIndex(index) { - let idxObj; - if (index) { - idxObj = await findObjectByTitle(savedObjectsClient, 'index-pattern', index); - if (!idxObj) { - throw new Error(i18n.translate('vega.visualization.indexNotFoundErrorMessage', { - defaultMessage: 'Index {index} not found', - values: { index: `"${index}"` }, - })); - } - } else { - idxObj = await this._vis.API.indexPatterns.getDefault(); - if (!idxObj) { - throw new Error(i18n.translate('vega.visualization.unableToFindDefaultIndexErrorMessage', { - defaultMessage: 'Unable to find default index', - })); - } +import { findObjectByTitle } from 'ui/saved_objects'; + +export const createVegaVisualization = ({ serviceSettings }) => class VegaVisualization { + constructor(el, vis) { + this.savedObjectsClient = chrome.getSavedObjectsClient(); + this._el = el; + this._vis = vis; + } + + /** + * Find index pattern by its title, of if not given, gets default + * @param {string} [index] + * @returns {Promise} index id + */ + async findIndex(index) { + let idxObj; + if (index) { + idxObj = await findObjectByTitle(this.savedObjectsClient, 'index-pattern', index); + if (!idxObj) { + throw new Error(i18n.translate('vega.visualization.indexNotFoundErrorMessage', { + defaultMessage: 'Index {index} not found', + values: { index: `"${index}"` }, + })); } - return idxObj.id; - } - - /** - * - * @param {VegaParser} visData - * @param {*} status - * @returns {Promise} - */ - async render(visData, visParams, status) { - if (!visData && !this._vegaView) { - toastNotifications.addWarning(i18n.translate('vega.visualization.unableToRenderWithoutDataWarningMessage', { - defaultMessage: 'Unable to render without data', + } else { + idxObj = await this._vis.API.indexPatterns.getDefault(); + if (!idxObj) { + throw new Error(i18n.translate('vega.visualization.unableToFindDefaultIndexErrorMessage', { + defaultMessage: 'Unable to find default index', })); - return; } + } + return idxObj.id; + } + + /** + * + * @param {VegaParser} visData + * @param {*} status + * @returns {Promise} + */ + async render(visData, visParams, status) { + if (!visData && !this._vegaView) { + toastNotifications.addWarning(i18n.translate('vega.visualization.unableToRenderWithoutDataWarningMessage', { + defaultMessage: 'Unable to render without data', + })); + return; + } - try { + try { - await this._render(visData, status); + await this._render(visData, status); - } catch (error) { - if (this._vegaView) { - this._vegaView.onError(error); - } else { - toastNotifications.addError(error, { - title: i18n.translate('vega.visualization.renderErrorTitle', { - defaultMessage: 'Vega error', - }), - }); - } + } catch (error) { + if (this._vegaView) { + this._vegaView.onError(error); + } else { + toastNotifications.addError(error, { + title: i18n.translate('vega.visualization.renderErrorTitle', { + defaultMessage: 'Vega error', + }), + }); } } + } - async _render(vegaParser, status) { - if (vegaParser && (status.data || !this._vegaView)) { + async _render(vegaParser, status) { + if (vegaParser && (status.data || !this._vegaView)) { - // New data received, rebuild the graph - if (this._vegaView) { - await this._vegaView.destroy(); - this._vegaView = null; - } - - const vegaViewParams = { - vegaConfig, - parentEl: this._el, - vegaParser, - serviceSettings, - queryfilter: this._vis.API.queryFilter, - timefilter: this._vis.API.timeFilter, - findIndex: this.findIndex.bind(this), - $rootScope, - }; + // New data received, rebuild the graph + if (this._vegaView) { + await this._vegaView.destroy(); + this._vegaView = null; + } - if (vegaParser.useMap) { - this._vegaView = new VegaMapView(vegaViewParams); - } else { - this._vegaView = new VegaView(vegaViewParams); - } - await this._vegaView.init(); + const vegaViewParams = { + parentEl: this._el, + vegaParser, + serviceSettings, + queryfilter: this._vis.API.queryFilter, + timefilter: this._vis.API.timeFilter, + findIndex: this.findIndex.bind(this), + }; + + if (vegaParser.useMap) { + this._vegaView = new VegaMapView(vegaViewParams); + } else { + this._vegaView = new VegaView(vegaViewParams); + } + await this._vegaView.init(); - } else if (status.resize) { + } else if (status.resize) { - // the graph has been resized - await this._vegaView.resize(); + // the graph has been resized + await this._vegaView.resize(); - } } + } - destroy() { - return this._vegaView && this._vegaView.destroy(); - } - }; -} + destroy() { + return this._vegaView && this._vegaView.destroy(); + } +};