From ef7cf4b44b6ea12b086b308999e6a0d073fa72fc Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Mon, 10 Dec 2018 17:41:25 +0100 Subject: [PATCH] use canvas pipeline in visualize (#25996) --- .../kbn-interpreter/src/plugin/types/index.js | 4 + .../src/plugin/types/kibana_context.js | 36 +++ .../src/plugin/types/kibana_table.js | 41 ++++ packages/kbn-interpreter/src/public/index.js | 2 +- .../kbn-interpreter/src/public/interpreter.js | 8 +- .../interpreter/public/functions/esaggs.js | 97 ++++++++ .../interpreter/public/functions/index.js | 40 ++++ .../public/functions/input_control.js | 50 +++++ .../interpreter/public/functions/kibana.js | 51 +++++ .../public/functions/kibana_context.js | 86 ++++++++ .../interpreter/public/functions/markdown.js | 59 +++++ .../interpreter/public/functions/metric.js | 76 +++++++ .../interpreter/public/functions/pie.js | 84 +++++++ .../interpreter/public/functions/regionmap.js | 74 +++++++ .../interpreter/public/functions/table.js | 103 +++++++++ .../interpreter/public/functions/tagcloud.js | 74 +++++++ .../interpreter/public/functions/tilemap.js | 79 +++++++ .../public/functions/timelion_vis.js | 69 ++++++ .../interpreter/public/functions/tsvb.js | 73 ++++++ .../interpreter/public/functions/vega.js | 70 ++++++ .../interpreter/public/functions/vislib.js | 93 ++++++++ .../public/functions/visualization.js | 147 +++++++++++++ .../public/load_browser_plugins.js | 16 +- .../public/discover/controllers/discover.js | 2 +- src/ui/public/vis/index.d.ts | 2 +- src/ui/public/vis/response_handlers/legacy.js | 22 +- src/ui/public/vis/vis.d.ts | 7 + .../loader/__tests__/visualize_data_loader.js | 8 - .../loader/__tests__/visualize_loader.js | 6 +- .../loader/embedded_visualize_handler.ts | 29 +-- .../visualize/loader/pipeline_data_loader.ts | 46 ++++ .../__snapshots__/build_pipeline.test.js.snap | 31 +++ .../pipeline_helpers/build_pipeline.test.js | 202 +++++++++++++++++ .../loader/pipeline_helpers/build_pipeline.ts | 207 ++++++++++++++++++ .../loader/pipeline_helpers/index.ts | 21 ++ .../loader/pipeline_helpers/run_pipeline.ts | 29 +++ test/functional/apps/visualize/_data_table.js | 4 +- .../embedding_visualizations/embed_by_id.js | 2 + .../canvas/public/components/app/index.js | 9 +- 39 files changed, 2011 insertions(+), 48 deletions(-) create mode 100644 packages/kbn-interpreter/src/plugin/types/kibana_context.js create mode 100644 packages/kbn-interpreter/src/plugin/types/kibana_table.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/esaggs.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/index.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/input_control.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/kibana.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/kibana_context.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/markdown.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/metric.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/pie.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/regionmap.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/table.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/tagcloud.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/tilemap.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/timelion_vis.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/tsvb.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/vega.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/vislib.js create mode 100644 src/legacy/core_plugins/interpreter/public/functions/visualization.js create mode 100644 src/ui/public/visualize/loader/pipeline_data_loader.ts create mode 100644 src/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap create mode 100644 src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js create mode 100644 src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts create mode 100644 src/ui/public/visualize/loader/pipeline_helpers/index.ts create mode 100644 src/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts diff --git a/packages/kbn-interpreter/src/plugin/types/index.js b/packages/kbn-interpreter/src/plugin/types/index.js index 1ae5f874835c3..ea3aa7d519891 100644 --- a/packages/kbn-interpreter/src/plugin/types/index.js +++ b/packages/kbn-interpreter/src/plugin/types/index.js @@ -29,6 +29,8 @@ import { render } from './render'; import { shape } from './shape'; import { string } from './string'; import { style } from './style'; +import { kibanaTable } from './kibana_table'; +import { kibanaContext } from './kibana_context'; export const typeSpecs = [ boolean, @@ -43,4 +45,6 @@ export const typeSpecs = [ shape, string, style, + kibanaTable, + kibanaContext, ]; diff --git a/packages/kbn-interpreter/src/plugin/types/kibana_context.js b/packages/kbn-interpreter/src/plugin/types/kibana_context.js new file mode 100644 index 0000000000000..b186ae135788d --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/kibana_context.js @@ -0,0 +1,36 @@ +/* + * 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 const kibanaContext = () => ({ + name: 'kibana_context', + from: { + null: () => { + return { + type: 'kibana_context', + }; + }, + }, + to: { + null: () => { + return { + type: 'null', + }; + }, + } +}); diff --git a/packages/kbn-interpreter/src/plugin/types/kibana_table.js b/packages/kbn-interpreter/src/plugin/types/kibana_table.js new file mode 100644 index 0000000000000..67cedbded50ae --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/kibana_table.js @@ -0,0 +1,41 @@ +/* + * 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 const kibanaTable = () => ({ + name: 'kibana_table', + serialize: context => { + context.columns.forEach(column => { + column.aggConfig = column.aggConfig.toJSON(); + }); + return context; + }, + validate: tabify => { + if (!tabify.columns) { + throw new Error('tabify must have a columns array, even if it is empty'); + } + }, + from: { + null: () => { + return { + type: 'kibana_table', + columns: [], + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/public/index.js b/packages/kbn-interpreter/src/public/index.js index de8caad1dbc5b..8c50c66ff2f2e 100644 --- a/packages/kbn-interpreter/src/public/index.js +++ b/packages/kbn-interpreter/src/public/index.js @@ -19,4 +19,4 @@ export { populateBrowserRegistries, getBrowserRegistries } from './browser_registries'; export { createSocket } from './socket'; -export { initializeInterpreter, interpretAst } from './interpreter'; +export { initializeInterpreter, interpretAst, getInitializedFunctions } from './interpreter'; diff --git a/packages/kbn-interpreter/src/public/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js index ba38df27b6f85..99bf6814deb65 100644 --- a/packages/kbn-interpreter/src/public/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -47,14 +47,18 @@ export async function initializeInterpreter() { return functionList; } +export async function getInitializedFunctions() { + return functionList; +} + // Use the above promise to seed the interpreter with the functions it can defer to -export async function interpretAst(ast, context) { +export async function interpretAst(ast, context, handlers) { // Load plugins before attempting to get functions, otherwise this gets racey return Promise.all([functionList, getBrowserRegistries()]) .then(([serverFunctionList]) => { return socketInterpreterProvider({ types: typesRegistry.toJS(), - handlers: createHandlers(socket), + handlers: { ...handlers, ...createHandlers(socket) }, functions: functionsRegistry.toJS(), referableFunctions: serverFunctionList, socket: socket, diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.js b/src/legacy/core_plugins/interpreter/public/functions/esaggs.js new file mode 100644 index 0000000000000..ba5b6c009701f --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.js @@ -0,0 +1,97 @@ +/* + * 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 { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { CourierRequestHandlerProvider } from 'ui/vis/request_handlers/courier'; +import { AggConfigs } from 'ui/vis/agg_configs'; + +// need to get rid of angular from these +import { IndexPatternsProvider } from 'ui/index_patterns'; +import { SearchSourceProvider } from 'ui/courier/search_source'; +import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; + +import chrome from 'ui/chrome'; + +const courierRequestHandlerProvider = CourierRequestHandlerProvider; +const courierRequestHandler = courierRequestHandlerProvider().handler; + +export const esaggs = () => ({ + name: 'esaggs', + type: 'kibana_table', + context: { + types: [ + 'kibana_context', + 'null', + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.esaggs.help', { defaultMessage: 'Run AggConfig aggregation' }), + args: { + index: { + types: ['string', 'null'], + default: null, + }, + metricsAtAllLevels: { + types: ['boolean'], + default: false, + }, + partialRows: { + types: ['boolean'], + default: false, + }, + aggConfigs: { + types: ['string'], + default: '""', + }, + }, + async fn(context, args, handlers) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const indexPatterns = Private(IndexPatternsProvider); + const SearchSource = Private(SearchSourceProvider); + const queryFilter = Private(FilterBarQueryFilterProvider); + + const aggConfigsState = JSON.parse(args.aggConfigs); + const indexPattern = await indexPatterns.get(args.index); + const aggs = new AggConfigs(indexPattern, aggConfigsState); + + // we should move searchSource creation inside courier request handler + const searchSource = new SearchSource(); + searchSource.setField('index', indexPattern); + + const response = await courierRequestHandler({ + searchSource: searchSource, + aggs: aggs, + timeRange: get(context, 'timeRange', null), + query: get(context, 'query', null), + filters: get(context, 'filters', null), + forceFetch: true, + isHierarchical: args.metricsAtAllLevels, + partialRows: args.partialRows, + inspectorAdapters: handlers.inspectorAdapters, + queryFilter, + }); + + return { + type: 'kibana_table', + index: args.index, + ...response, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/index.js b/src/legacy/core_plugins/interpreter/public/functions/index.js new file mode 100644 index 0000000000000..27605bf9e69c5 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/index.js @@ -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 { esaggs } from './esaggs'; +import { kibana } from './kibana'; +import { kibanaContext } from './kibana_context'; +import { vega } from './vega'; +import { timelionVis } from './timelion_vis'; +import { tsvb } from './tsvb'; +import { kibanaMarkdown } from './markdown'; +import { inputControlVis } from './input_control'; +import { metric } from './metric'; +import { kibanaPie } from './pie'; +import { regionmap } from './regionmap'; +import { tilemap } from './tilemap'; +import { kibanaTable } from './table'; +import { tagcloud } from './tagcloud'; +import { vislib } from './vislib'; +import { visualization } from './visualization'; + +export const functions = [ + esaggs, kibana, kibanaContext, vega, timelionVis, tsvb, kibanaMarkdown, inputControlVis, + metric, kibanaPie, regionmap, tilemap, kibanaTable, tagcloud, vislib, visualization +]; diff --git a/src/legacy/core_plugins/interpreter/public/functions/input_control.js b/src/legacy/core_plugins/interpreter/public/functions/input_control.js new file mode 100644 index 0000000000000..db0f5dfe27af6 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/input_control.js @@ -0,0 +1,50 @@ +/* + * 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'; + +export const inputControlVis = () => ({ + name: 'input_control_vis', + type: 'render', + context: { + types: [], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.input_control.help', { + defaultMessage: 'Input control visualization' + }), + args: { + visConfig: { + types: ['string'], + default: '"{}"', + } + }, + fn(context, args) { + const params = JSON.parse(args.visConfig); + return { + type: 'render', + as: 'visualization', + value: { + visConfig: { + type: 'input_controls_vis', + params: params + }, + } + }; + } +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana.js b/src/legacy/core_plugins/interpreter/public/functions/kibana.js new file mode 100644 index 0000000000000..faef6a660c063 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/kibana.js @@ -0,0 +1,51 @@ +/* + * 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'; + +export const kibana = () => ({ + name: 'kibana', + type: 'kibana_context', + context: {}, + help: i18n.translate('common.core_plugins.interpreter.public.functions.kibana.help', { + defaultMessage: 'Gets kibana global context' + }), + args: {}, + fn(context, args, handlers) { + const initialContext = handlers.getInitialContext ? handlers.getInitialContext() : {}; + + if (context.query) { + initialContext.query = initialContext.query.concat(context.query); + } + + if (context.filters) { + initialContext.filters = initialContext.filters.concat(context.filters); + } + + const timeRange = initialContext.timeRange || context.timeRange; + + return { + ...context, + type: 'kibana_context', + query: initialContext.query, + filters: initialContext.filters, + timeRange: timeRange, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana_context.js b/src/legacy/core_plugins/interpreter/public/functions/kibana_context.js new file mode 100644 index 0000000000000..9b6cccb773df9 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/kibana_context.js @@ -0,0 +1,86 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const kibanaContext = () => ({ + name: 'kibana_context', + type: 'kibana_context', + context: { + types: [ + 'kibana_context', + 'null', + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.kibana_context.help', { + defaultMessage: 'Updates kibana global context' + }), + args: { + q: { + types: ['string', 'null'], + aliases: ['query', '_'], + default: null, + }, + filters: { + types: ['string', 'null'], + default: '"[]"', + }, + timeRange: { + types: ['string', 'null'], + default: null, + }, + savedSearchId: { + types: ['string', 'null'], + default: null, + } + }, + async fn(context, args) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const savedSearches = $injector.get('savedSearches'); + const queryArg = args.q ? JSON.parse(args.q) : []; + let queries = Array.isArray(queryArg) ? queryArg : [queryArg]; + let filters = args.filters ? JSON.parse(args.filters) : []; + + if (args.savedSearchId) { + const savedSearch = await savedSearches.get(args.savedSearchId); + const searchQuery = savedSearch.searchSource.getField('query'); + const searchFilters = savedSearch.searchSource.getField('filter'); + queries = queries.concat(searchQuery); + filters = filters.concat(searchFilters); + } + + if (context.query) { + queries = queries.concat(context.query); + } + + if (context.filters) { + filters = filters.concat(context.filters); + } + + const timeRange = args.timeRange ? JSON.parse(args.timeRange) : context.timeRange; + + return { + type: 'kibana_context', + query: queries, + filters: filters, + timeRange: timeRange, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/markdown.js b/src/legacy/core_plugins/interpreter/public/functions/markdown.js new file mode 100644 index 0000000000000..0b1afda450bd6 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/markdown.js @@ -0,0 +1,59 @@ +/* + * 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'; + +export const kibanaMarkdown = () => ({ + name: 'kibana_markdown', + type: 'render', + context: { + types: [], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.markdown.help', { + defaultMessage: 'Markdown visualization' + }), + args: { + expression: { + types: ['string'], + aliases: [ '_' ], + default: '', + help: 'markdown', + }, + visConfig: { + types: ['string'], + default: '"{}"', + } + }, + fn(context, args) { + const params = JSON.parse(args.visConfig); + return { + type: 'render', + as: 'visualization', + value: { + visConfig: { + type: 'markdown', + params: { + markdown: args.spec, + ...params, + } + }, + } + }; + } +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/metric.js b/src/legacy/core_plugins/interpreter/public/functions/metric.js new file mode 100644 index 0000000000000..defa432f65314 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/metric.js @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; + +export const metric = () => ({ + name: 'kibana_metric', + type: 'render', + context: { + types: [ + 'kibana_table' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.metric.help', { + defaultMessage: 'Metric visualization' + }), + args: { + bucket: { + types: ['string', 'null'], + default: null, + }, + metric: { + types: ['string'], + default: '1', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + fn(context, args) { + const visConfigParams = JSON.parse(args.visConfig); + const metrics = args.metric.split(','); + metrics.forEach(metric => { + const metricColumn = context.columns.find((column, i) => + column.id === metric || column.name === metric || i === parseInt(metric)); + metricColumn.aggConfig.schema = 'metric'; + }); + if (args.bucket) { + const bucketColumn = context.columns.find((column, i) => + column.id === args.bucket || column.name === args.bucket || i === parseInt(args.bucket)); + bucketColumn.aggConfig.schema = 'segment'; + } + + return { + type: 'render', + as: 'visualization', + value: { + visData: context, + visConfig: { + type: 'metric', + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/pie.js b/src/legacy/core_plugins/interpreter/public/functions/pie.js new file mode 100644 index 0000000000000..e828164e18ee2 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/pie.js @@ -0,0 +1,84 @@ +/* + * 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 { VisTypesRegistryProvider } from 'ui/registry/vis_types'; +import { VislibSlicesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; +import chrome from 'ui/chrome'; +import { i18n } from '@kbn/i18n'; + +export const kibanaPie = () => ({ + name: 'kibana_pie', + type: 'render', + context: { + types: [ + 'kibana_table', 'null' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.pie.help', { + defaultMessage: 'Pie visualization' + }), + args: { + schemas: { + types: ['string'], + default: '"{}"', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + async fn(context, args) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const responseHandler = Private(VislibSlicesResponseHandlerProvider).handler; + const visTypes = Private(VisTypesRegistryProvider); + const visConfigParams = JSON.parse(args.visConfig); + const visType = visTypes.byName.pie; + const schemas = JSON.parse(args.schemas); + + if (context.columns) { + context.columns.forEach(column => { + column.aggConfig.aggConfigs.schemas = visType.schemas.all; + }); + + Object.keys(schemas).forEach(key => { + schemas[key].forEach(i => { + context.columns[i].aggConfig.schema = key; + }); + }); + } + + const convertedData = await responseHandler(context); + + return { + type: 'render', + as: 'visualization', + value: { + visData: convertedData, + visConfig: { + type: args.type, + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/regionmap.js b/src/legacy/core_plugins/interpreter/public/functions/regionmap.js new file mode 100644 index 0000000000000..b92122a726905 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/regionmap.js @@ -0,0 +1,74 @@ +/* + * 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'; + +export const regionmap = () => ({ + name: 'regionmap', + type: 'render', + context: { + types: [ + 'kibana_table' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.regionmap.help', { + defaultMessage: 'Regionmap visualization' + }), + args: { + bucket: { + types: ['string'], + default: '0', + }, + metric: { + types: ['string'], + default: '1', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + fn(context, args) { + const visConfigParams = JSON.parse(args.visConfig); + const metricColumn = context.columns.find((column, i) => + column.id === args.metric || column.name === args.metric || i === parseInt(args.metric) + ); + const bucketColumn = context.columns.find((column, i) => + column.id === args.bucket || column.name === args.bucket || i === parseInt(args.bucket) + ); + + metricColumn.aggConfig.schema = 'metric'; + bucketColumn.aggConfig.schema = 'segment'; + + return { + type: 'render', + as: 'visualization', + value: { + visData: context, + visConfig: { + type: 'region_map', + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/table.js b/src/legacy/core_plugins/interpreter/public/functions/table.js new file mode 100644 index 0000000000000..d35e81d6b1a74 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/table.js @@ -0,0 +1,103 @@ +/* + * 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 { LegacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; +import { i18n } from '@kbn/i18n'; + +// eslint-disable-next-line new-cap +const responseHandler = LegacyResponseHandlerProvider().handler; + +export const kibanaTable = () => ({ + name: 'kibana_table', + type: 'render', + context: { + types: [ + 'kibana_table' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.table.help', { + defaultMessage: 'Table visualization' + }), + args: { + bucket: { + types: ['string'], + }, + splitRow: { + types: ['string'], + }, + splitColumn: { + types: ['string'], + }, + metric: { + types: ['string'], + default: '1', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + async fn(context, args) { + const visConfigParams = JSON.parse(args.visConfig); + args.metric.split(',').forEach(metric => { + const metricColumn = context.columns.find((column, i) => + column.id === metric || column.name === metric || i === parseInt(metric)); + metricColumn.aggConfig.schema = 'metric'; + }); + if (args.bucket) { + args.bucket.split(',').forEach(bucket => { + const bucketColumn = context.columns.find((column, i) => + column.id === bucket || column.name === bucket || i === parseInt(bucket)); + bucketColumn.aggConfig.schema = 'bucket'; + }); + } + if (args.splitColumn) { + args.splitColumn.split(',').forEach(split => { + const splitColumn = context.columns.find((column, i) => + column.id === split || column.name === split || i === parseInt(split)); + splitColumn.aggConfig.schema = 'split'; + }); + } + if (args.splitRow) { + args.splitRow.split(',').forEach(split => { + const splitColumn = context.columns.find((column, i) => + column.id === split || column.name === split || i === parseInt(split)); + splitColumn.aggConfig.schema = 'split'; + splitColumn.aggConfig.params.row = true; + }); + } + + const convertedData = await responseHandler(context); + + return { + type: 'render', + as: 'visualization', + value: { + visData: convertedData, + visConfig: { + type: 'table', + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/tagcloud.js b/src/legacy/core_plugins/interpreter/public/functions/tagcloud.js new file mode 100644 index 0000000000000..fbad1dd39acc4 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/tagcloud.js @@ -0,0 +1,74 @@ +/* + * 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'; + +export const tagcloud = () => ({ + name: 'tagcloud', + type: 'render', + context: { + types: [ + 'kibana_table' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.tagcloud.help', { + defaultMessage: 'Tagcloud visualization' + }), + args: { + bucket: { + types: ['string'], + default: '0', + }, + metric: { + types: ['string'], + default: '1', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + fn(context, args) { + const visConfigParams = JSON.parse(args.visConfig); + const metricColumn = context.columns.find((column, i) => + column.id === args.metric || column.name === args.metric || i === parseInt(args.metric) + ); + const bucketColumn = context.columns.find((column, i) => + column.id === args.bucket || column.name === args.bucket || i === parseInt(args.bucket) + ); + + metricColumn.aggConfig.schema = 'metric'; + bucketColumn.aggConfig.schema = 'segment'; + + return { + type: 'render', + as: 'visualization', + value: { + visData: context, + visConfig: { + type: 'tag_cloud', + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/tilemap.js b/src/legacy/core_plugins/interpreter/public/functions/tilemap.js new file mode 100644 index 0000000000000..6015a8e2626e7 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/tilemap.js @@ -0,0 +1,79 @@ +/* + * 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 { makeGeoJsonResponseHandler } from 'plugins/tile_map/coordinatemap_response_handler'; +import { i18n } from '@kbn/i18n'; + +const responseHandler = makeGeoJsonResponseHandler(); + +export const tilemap = () => ({ + name: 'tilemap', + type: 'render', + context: { + types: [ + 'kibana_table' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.tilemap.help', { + defaultMessage: 'Tilemap visualization' + }), + args: { + bucket: { + types: ['string'], + default: '0', + }, + metric: { + types: ['string'], + default: '1', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + fn(context, args) { + const visConfigParams = JSON.parse(args.visConfig); + const metricColumn = context.columns.find((column, i) => + column.id === args.metric || column.name === args.metric || i === parseInt(args.metric) + ); + const bucketColumn = context.columns.find((column, i) => + column.id === args.bucket || column.name === args.bucket || i === parseInt(args.bucket) + ); + + metricColumn.aggConfig.schema = 'metric'; + bucketColumn.aggConfig.schema = 'segment'; + + const convertedData = responseHandler(context); + + return { + type: 'render', + as: 'visualization', + value: { + visData: convertedData, + visConfig: { + type: 'tile_map', + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/timelion_vis.js b/src/legacy/core_plugins/interpreter/public/functions/timelion_vis.js new file mode 100644 index 0000000000000..f45f7038cbb22 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/timelion_vis.js @@ -0,0 +1,69 @@ +/* + * 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 { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { TimelionRequestHandlerProvider } from 'plugins/timelion/vis/timelion_request_handler'; + + +import chrome from 'ui/chrome'; + +export const timelionVis = () => ({ + name: 'timelion_vis', + type: 'render', + context: { + types: [ + 'kibana_context', + 'null', + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.timelion.help', { + defaultMessage: 'Timelion visualization' + }), + args: { + expression: { + types: ['string'], + aliases: ['_'], + default: '".es(*)"', + }, + interval: { + types: ['string', 'null'], + default: 'auto', + } + }, + async fn(context, args) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const timelionRequestHandler = Private(TimelionRequestHandlerProvider).handler; + + const response = await timelionRequestHandler({ + timeRange: get(context, 'timeRange', null), + query: get(context, 'query', null), + filters: get(context, 'filters', null), + forceFetch: true, + visParams: { expression: args.expression, interval: args.interval } + }); + + return { + type: 'render', + as: 'visualization', + value: response, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/tsvb.js b/src/legacy/core_plugins/interpreter/public/functions/tsvb.js new file mode 100644 index 0000000000000..ce83dfcadb62d --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/tsvb.js @@ -0,0 +1,73 @@ +/* + * 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 { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { MetricsRequestHandlerProvider } from 'plugins/metrics/kbn_vis_types/request_handler'; +import { PersistedState } from 'ui/persisted_state'; + +import chrome from 'ui/chrome'; + + +export const tsvb = () => ({ + name: 'tsvb', + type: 'render', + context: { + types: [ + 'kibana_context', + 'null', + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.tsvb.help', { + defaultMessage: 'TSVB visualization' + }), + args: { + params: { + types: ['string'], + default: '"{}"', + }, + uiState: { + types: ['string'], + default: '"{}"', + } + }, + async fn(context, args) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const metricsRequestHandler = Private(MetricsRequestHandlerProvider).handler; + + const params = JSON.parse(args.params); + const uiStateParams = JSON.parse(args.uiState); + const uiState = new PersistedState(uiStateParams); + + const response = await metricsRequestHandler({ + timeRange: get(context, 'timeRange', null), + query: get(context, 'query', null), + filters: get(context, 'filters', null), + visParams: params, + uiState: uiState, + }); + + return { + type: 'render', + as: 'visualization', + value: response, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/vega.js b/src/legacy/core_plugins/interpreter/public/functions/vega.js new file mode 100644 index 0000000000000..52439e607c89e --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/vega.js @@ -0,0 +1,70 @@ +/* + * 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 { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { VegaRequestHandlerProvider } from 'plugins/vega/vega_request_handler'; + +export const vega = () => ({ + name: 'vega', + type: 'render', + context: { + types: [ + 'kibana_context', + 'null', + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.vega.help', { + defaultMessage: 'Vega visualization' + }), + args: { + spec: { + types: ['string'], + default: '', + }, + }, + async fn(context, args) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const vegaRequestHandler = Private(VegaRequestHandlerProvider).handler; + + const response = await vegaRequestHandler({ + timeRange: get(context, 'timeRange', null), + query: get(context, 'q', null), + filters: get(context, 'filters', null), + visParams: { spec: args.spec }, + forceFetch: true + }); + + return { + type: 'render', + as: 'visualization', + value: { + visData: response, + visConfig: { + type: 'vega', + params: { + spec: args.spec + } + }, + } + }; + } +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/vislib.js b/src/legacy/core_plugins/interpreter/public/functions/vislib.js new file mode 100644 index 0000000000000..3ff0c6f0389d1 --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/vislib.js @@ -0,0 +1,93 @@ +/* + * 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'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; +import { VislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; +import chrome from 'ui/chrome'; + +export const vislib = () => ({ + name: 'vislib', + type: 'render', + context: { + types: [ + 'kibana_table', 'null' + ], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.vislib.help', { + defaultMessage: 'Vislib visualization' + }), + args: { + type: { + types: ['string'], + default: 'metric', + }, + schemas: { + types: ['string'], + default: '"{}"', + }, + visConfig: { + types: ['string', 'null'], + default: '"{}"', + }, + }, + async fn(context, args) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const responseHandler = Private(VislibSeriesResponseHandlerProvider).handler; + const visTypes = Private(VisTypesRegistryProvider); + const visConfigParams = JSON.parse(args.visConfig); + const schemas = JSON.parse(args.schemas); + const visType = visTypes.byName[args.type || 'histogram']; + + if (context.columns) { + // assign schemas to aggConfigs + context.columns.forEach(column => { + column.aggConfig.aggConfigs.schemas = visType.schemas.all; + }); + + Object.keys(schemas).forEach(key => { + schemas[key].forEach(i => { + const schema = key.split('_'); + context.columns[i].aggConfig.schema = schema[0]; + if (schema[1] === 'row') { + context.columns[i].aggConfig.params.row = true; + } + }); + }); + } + + const convertedData = await responseHandler(context); + + return { + type: 'render', + as: 'visualization', + value: { + visData: convertedData, + visConfig: { + type: args.type, + params: visConfigParams, + }, + params: { + listenOnChange: true, + } + }, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/visualization.js b/src/legacy/core_plugins/interpreter/public/functions/visualization.js new file mode 100644 index 0000000000000..2e37871c9750e --- /dev/null +++ b/src/legacy/core_plugins/interpreter/public/functions/visualization.js @@ -0,0 +1,147 @@ +/* + * 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 { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; +import { VisRequestHandlersRegistryProvider as RequestHandlersProvider } from 'ui/registry/vis_request_handlers'; +import { VisResponseHandlersRegistryProvider as ResponseHandlerProvider } from 'ui/registry/vis_response_handlers'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; +import { IndexPatternsProvider } from 'ui/index_patterns'; +import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; +import { PersistedState } from 'ui/persisted_state'; + +function getHandler(from, type) { + if (typeof type === 'function') { + return type; + } + if (type === 'courier' || type === 'none') { + return null; + } + const handlerDesc = from.find(handler => handler.name === type); + if (!handlerDesc) { + throw new Error(`Could not find handler "${type}".`); + } + return handlerDesc.handler; +} + +export const visualization = () => ({ + name: 'visualization', + type: 'render', + context: { + types: [], + }, + help: i18n.translate('common.core_plugins.interpreter.public.functions.visualization.help', { + defaultMessage: 'A simple visualization' + }), + args: { + index: { + types: ['string', 'null'], + default: null, + }, + metricsAtAllLevels: { + types: ['boolean'], + default: false, + }, + partialRows: { + types: ['boolean'], + default: false, + }, + type: { + types: ['string'], + default: '', + }, + schemas: { + types: ['string'], + default: '"{}"', + }, + visConfig: { + types: ['string'], + default: '"{}"', + }, + uiState: { + types: ['string'], + default: '"{}"', + } + }, + async fn(context, args, handlers) { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + const requestHandlers = Private(RequestHandlersProvider); + const responseHandlers = Private(ResponseHandlerProvider); + const visTypes = Private(VisTypesRegistryProvider); + const indexPatterns = Private(IndexPatternsProvider); + const queryFilter = Private(FilterBarQueryFilterProvider); + + const visConfigParams = JSON.parse(args.visConfig); + const schemas = JSON.parse(args.schemas); + const visType = visTypes.byName[args.type || 'histogram']; + const requestHandler = getHandler(requestHandlers, visType.requestHandler); + const responseHandler = getHandler(responseHandlers, visType.responseHandler); + const indexPattern = args.index ? await indexPatterns.get(args.index) : null; + + const uiStateParams = JSON.parse(args.uiState); + const uiState = new PersistedState(uiStateParams); + + if (requestHandler) { + context = await requestHandler({ + partialRows: args.partialRows, + metricsAtAllLevels: args.metricsAtAllLevels, + index: indexPattern, + visParams: visConfigParams, + timeRange: get(context, 'timeRange', null), + query: get(context, 'query', null), + filters: get(context, 'filters', null), + uiState: uiState, + inspectorAdapters: handlers.inspectorAdapters, + queryFilter, + forceFetch: true, + }); + } + + if (responseHandler) { + if (context.columns) { + // assign schemas to aggConfigs + context.columns.forEach(column => { + column.aggConfig.aggConfigs.schemas = visType.schemas.all; + }); + + Object.keys(schemas).forEach(key => { + schemas[key].forEach(i => { + context.columns[i].aggConfig.schema = key; + }); + }); + } + + context = await responseHandler(context); + } + + return { + type: 'render', + as: 'visualization', + value: { + visData: context, + visConfig: { + type: args.type, + params: visConfigParams + }, + } + }; + } +}); diff --git a/src/legacy/core_plugins/interpreter/public/load_browser_plugins.js b/src/legacy/core_plugins/interpreter/public/load_browser_plugins.js index de550f5c6a351..1025d283bf515 100644 --- a/src/legacy/core_plugins/interpreter/public/load_browser_plugins.js +++ b/src/legacy/core_plugins/interpreter/public/load_browser_plugins.js @@ -18,8 +18,11 @@ */ import chrome from 'ui/chrome'; -import { populateBrowserRegistries } from '@kbn/interpreter/public'; +import { populateBrowserRegistries, createSocket, initializeInterpreter } from '@kbn/interpreter/public'; import { typesRegistry, functionsRegistry } from '@kbn/interpreter/common'; +import { functions } from './functions'; + +const basePath = chrome.getBasePath(); const types = { commonFunctions: functionsRegistry, @@ -27,4 +30,13 @@ const types = { types: typesRegistry }; -populateBrowserRegistries(types, chrome.getBasePath()); +function addFunction(fnDef) { + functionsRegistry.register(fnDef); +} + +functions.forEach(addFunction); + +createSocket(basePath).then(async () => { + await populateBrowserRegistries(types, basePath); + await initializeInterpreter(); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js index e8b16737af445..aceed75ee15cb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js @@ -756,7 +756,7 @@ function discoverController( Promise .resolve(responseHandler(tabifiedData)) .then(resp => { - visualizeHandler.render(resp); + visualizeHandler.render({ value: resp }); }); } diff --git a/src/ui/public/vis/index.d.ts b/src/ui/public/vis/index.d.ts index a16588c89e708..4b8295367a322 100644 --- a/src/ui/public/vis/index.d.ts +++ b/src/ui/public/vis/index.d.ts @@ -18,7 +18,7 @@ */ export { AggConfig } from './agg_config'; -export { Vis, VisProvider } from './vis'; +export { Vis, VisProvider, VisState } from './vis'; export { VisualizationController, VisType } from './vis_types/vis_type'; export * from './request_handlers'; export * from './response_handlers'; diff --git a/src/ui/public/vis/response_handlers/legacy.js b/src/ui/public/vis/response_handlers/legacy.js index c91d562f1dc72..d90e426b9fa71 100644 --- a/src/ui/public/vis/response_handlers/legacy.js +++ b/src/ui/public/vis/response_handlers/legacy.js @@ -21,6 +21,14 @@ import _ from 'lodash'; import AggConfigResult from '../../vis/agg_config_result'; import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; +const getSchema = column => { + return _.get(column, 'aggConfig.schema.name') || _.get(column, 'aggConfig.schema'); +}; + +const getType = column => { + return _.get(column, 'aggConfig.type.type') || _.get(column, 'aggConfig.type'); +}; + const LegacyResponseHandlerProvider = function () { return { @@ -30,12 +38,12 @@ const LegacyResponseHandlerProvider = function () { const converted = { tables: [] }; // check if there are buckets after the first metric - const metricsAtAllLevels = table.columns.findIndex(column => _.get(column, 'aggConfig.type.type') === 'metrics') < - _.findLastIndex(table.columns, column => _.get(column, 'aggConfig.type.type') === 'buckets'); + const metricsAtAllLevels = table.columns.findIndex(column => getType(column) === 'metrics') < + _.findLastIndex(table.columns, column => getType(column) === 'buckets'); - const splitColumn = table.columns.find(column => _.get(column, 'aggConfig.schema.name') === 'split'); - const numberOfMetrics = table.columns.filter(column => _.get(column, 'aggConfig.type.type') === 'metrics').length; - const numberOfBuckets = table.columns.filter(column => _.get(column, 'aggConfig.type.type') === 'buckets').length; + const splitColumn = table.columns.find(column => getSchema(column) === 'split'); + const numberOfMetrics = table.columns.filter(column => getType(column) === 'metrics').length; + const numberOfBuckets = table.columns.filter(column => getType(column) === 'buckets').length; const metricsPerBucket = numberOfMetrics / numberOfBuckets; if (splitColumn) { @@ -84,7 +92,7 @@ const LegacyResponseHandlerProvider = function () { column: table.columns.findIndex(c => c.id === column.id), row: rowIndex, }; - if (column.aggConfig.type.type === 'buckets') { + if (getType(column) === 'buckets') { previousSplitAgg = aggConfigResult; } return aggConfigResult; @@ -106,7 +114,7 @@ const LegacyResponseHandlerProvider = function () { column: columnIndex, row: rowIndex, }; - if (column.aggConfig.type.type === 'buckets') { + if (getType(column) === 'buckets') { previousSplitAgg = aggConfigResult; } return aggConfigResult; diff --git a/src/ui/public/vis/vis.d.ts b/src/ui/public/vis/vis.d.ts index ef2ac096f27f0..b5fb5390bf86c 100644 --- a/src/ui/public/vis/vis.d.ts +++ b/src/ui/public/vis/vis.d.ts @@ -30,3 +30,10 @@ export interface Vis { } export type VisProvider = (...dependencies: any[]) => Vis; + +export interface VisState { + title: string; + type: VisType; + params: any; + aggs: any[]; +} diff --git a/src/ui/public/visualize/loader/__tests__/visualize_data_loader.js b/src/ui/public/visualize/loader/__tests__/visualize_data_loader.js index fcd2b4b930618..4c2e31a46d7cb 100644 --- a/src/ui/public/visualize/loader/__tests__/visualize_data_loader.js +++ b/src/ui/public/visualize/loader/__tests__/visualize_data_loader.js @@ -60,14 +60,6 @@ describe('visualize data loader', () => { })); setupAndTeardownInjectorStub(); - it('should have a requestHandler', () => { - expect(visualizeDataLoader.requestHandler).to.be.a('function'); - }); - - it('should have a responseHandler', () => { - expect(visualizeDataLoader.responseHandler).to.be.a('function'); - }); - describe('fetch', () => { it('should be a function', () => { expect(visualizeDataLoader.fetch).to.be.a('function'); diff --git a/src/ui/public/visualize/loader/__tests__/visualize_loader.js b/src/ui/public/visualize/loader/__tests__/visualize_loader.js index 3f870e94d65e9..6f73ac872c539 100644 --- a/src/ui/public/visualize/loader/__tests__/visualize_loader.js +++ b/src/ui/public/visualize/loader/__tests__/visualize_loader.js @@ -32,7 +32,7 @@ import { getVisualizeLoader } from '../visualize_loader'; import { EmbeddedVisualizeHandler } from '../embedded_visualize_handler'; import { Inspector } from '../../../inspector/inspector'; import { dispatchRenderComplete } from '../../../render_complete'; -import { VisualizeDataLoader } from '../visualize_data_loader'; +import { PipelineDataLoader } from '../pipeline_data_loader'; import { PersistedState } from '../../../persisted_state'; import { DataAdapter } from '../../../inspector/adapters/data'; import { RequestAdapter } from '../../../inspector/adapters/request'; @@ -420,7 +420,7 @@ describe('visualize loader', () => { }); it('should allow updating the time range of the visualization', async () => { - const spy = sandbox.spy(VisualizeDataLoader.prototype, 'fetch'); + const spy = sandbox.spy(PipelineDataLoader.prototype, 'fetch'); const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), { timeRange: { from: 'now-7d', to: 'now' } @@ -442,7 +442,7 @@ describe('visualize loader', () => { }); it('should not set forceFetch on uiState change', async () => { - const spy = sandbox.spy(VisualizeDataLoader.prototype, 'fetch'); + const spy = sandbox.spy(PipelineDataLoader.prototype, 'fetch'); const uiState = new PersistedState(); loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), { diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/ui/public/visualize/loader/embedded_visualize_handler.ts index 39d19d12592a7..a78a1376efb74 100644 --- a/src/ui/public/visualize/loader/embedded_visualize_handler.ts +++ b/src/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -29,8 +29,9 @@ import { RenderCompleteHelper } from '../../render_complete'; import { AppState } from '../../state_management/app_state'; import { timefilter } from '../../timefilter'; import { RequestHandlerParams, Vis } from '../../vis'; +// import { VisualizeDataLoader } from './visualize_data_loader'; +import { PipelineDataLoader } from './pipeline_data_loader'; import { visualizationLoader } from './visualization_loader'; -import { VisualizeDataLoader } from './visualize_data_loader'; import { DataAdapter, RequestAdapter } from '../../inspector/adapters'; @@ -80,7 +81,7 @@ export class EmbeddedVisualizeHandler { private dataLoaderParams: RequestHandlerParams; private readonly appState?: AppState; private uiState: PersistedState; - private dataLoader: VisualizeDataLoader; + private dataLoader: PipelineDataLoader; private dataSubject: Rx.Subject; private actions: any = {}; private events$: Rx.Observable; @@ -93,16 +94,7 @@ export class EmbeddedVisualizeHandler { ) { const { searchSource, vis } = savedObject; - const { - appState, - uiState, - queryFilter, - timeRange, - filters, - query, - Private, - autoFetch, - } = params; + const { appState, uiState, queryFilter, timeRange, filters, query, autoFetch } = params; this.dataLoaderParams = { searchSource, @@ -137,7 +129,7 @@ export class EmbeddedVisualizeHandler { this.uiState.on('change', this.onUiStateChange); timefilter.on('autoRefreshFetch', this.reload); - this.dataLoader = new VisualizeDataLoader(vis, Private); + this.dataLoader = new PipelineDataLoader(vis); this.renderCompleteHelper = new RenderCompleteHelper(element); this.inspectorAdapters = this.getActiveInspectorAdapters(); this.vis.openInspector = this.openInspector; @@ -235,7 +227,14 @@ export class EmbeddedVisualizeHandler { * renders visualization with provided data * @param visData: visualization data */ - public render = (visData: any = null) => { + public render = (pipelineResponse: any = null) => { + let visData; + if (pipelineResponse) { + if (!pipelineResponse.value) { + throw new Error(pipelineResponse.error); + } + visData = pipelineResponse.value.visData || pipelineResponse.value; + } return visualizationLoader .render(this.element, this.vis, visData, this.uiState, { listenOnChange: false, @@ -378,6 +377,8 @@ export class EmbeddedVisualizeHandler { this.dataLoaderParams.forceFetch = forceFetch; this.dataLoaderParams.inspectorAdapters = this.inspectorAdapters; + this.vis.filters = { timeRange: this.dataLoaderParams.timeRange }; + return this.dataLoader.fetch(this.dataLoaderParams).then(data => { this.dataSubject.next(data); return data; diff --git a/src/ui/public/visualize/loader/pipeline_data_loader.ts b/src/ui/public/visualize/loader/pipeline_data_loader.ts new file mode 100644 index 0000000000000..ac5c7c0a09bbb --- /dev/null +++ b/src/ui/public/visualize/loader/pipeline_data_loader.ts @@ -0,0 +1,46 @@ +/* + * 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 { RequestHandlerParams, Vis } from '../../vis'; +import { buildPipeline, runPipeline } from './pipeline_helpers'; + +export class PipelineDataLoader { + constructor(private readonly vis: Vis) {} + + public async fetch(params: RequestHandlerParams): Promise { + this.vis.requestError = undefined; + this.vis.showRequestError = false; + this.vis.pipelineExpression = buildPipeline(this.vis, params); + + return await runPipeline( + this.vis.pipelineExpression, + {}, + { + getInitialContext: () => ({ + query: params.query, + timeRange: params.timeRange, + filters: params.filters + ? params.filters.filter(filter => !filter.meta.disabled) + : undefined, + }), + inspectorAdapters: params.inspectorAdapters, + } + ); + } +} diff --git a/src/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap b/src/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap new file mode 100644 index 0000000000000..41998b84691b6 --- /dev/null +++ b/src/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.js.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles input_control_vis function 1`] = `"input_control_vis visConfig='{\\"some\\":\\"nested\\",\\"data\\":{\\"here\\":true}}' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles markdown function 1`] = `"kibana_markdown expression='## hello _markdown_' visConfig='{\\"markdown\\":\\"## hello _markdown_\\",\\"foo\\":\\"bar\\"}' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with buckets 1`] = `"kibana_metric visConfig='{\\"foo\\":\\"bar\\"}' bucket='2' metric='0,1' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function without buckets 1`] = `"kibana_metric visConfig='{\\"foo\\":\\"bar\\"}' metric='0,1' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metrics/tsvb function 1`] = `"tsvb params='{\\"foo\\":\\"bar\\"}' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles pie function 1`] = `"kibana_pie visConfig='{\\"foo\\":\\"bar\\"}' schemas='{\\"baz\\":\\"qux\\"}' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with metrics and buckets 1`] = `"regionmap visConfig='{\\"foo\\":\\"bar\\"}' bucket='1,2' metric='0' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\"}' splitRow='1,2' metric='0' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function with splits and buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\"}' splitRow='2,4' bucket='3' metric='0,1' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles table function without splits or buckets 1`] = `"kibana_table visConfig='{\\"foo\\":\\"bar\\"}' metric='0,1' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tagcloud function with metrics and buckets 1`] = `"tagcloud visConfig='{\\"foo\\":\\"bar\\"}' bucket='1,2' metric='0' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function with metrics and buckets 1`] = `"tilemap visConfig='{\\"foo\\":\\"bar\\"}' bucket='1,2' metric='0' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles tile_map function without buckets 1`] = `"tilemap visConfig='{\\"foo\\":\\"bar\\"}' metric='0' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles timelion function 1`] = `"timelion_vis expression='foo' interval='bar' "`; + +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles vega function 1`] = `"vega spec='this is a test' "`; diff --git a/src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js b/src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js new file mode 100644 index 0000000000000..9290706545d99 --- /dev/null +++ b/src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.js @@ -0,0 +1,202 @@ +/* + * 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 { prepareJson, prepareString, buildPipelineVisFunction } from './build_pipeline'; + +describe('visualize loader pipeline helpers: build pipeline', () => { + describe('prepareJson', () => { + it('returns a correctly formatted key/value string', () => { + const expected = `foo='{}' `; // trailing space is expected + const actual = prepareJson('foo', {}); + expect(actual).toBe(expected); + }); + + it('stringifies provided data', () => { + const expected = `foo='{\"well\":\"hello\",\"there\":{\"friend\":true}}' `; + const actual = prepareJson('foo', { well: 'hello', there: { friend: true } }); + expect(actual).toBe(expected); + }); + + it('escapes single quotes', () => { + const expected = `foo='{\"well\":\"hello \\'hi\\'\",\"there\":{\"friend\":true}}' `; + const actual = prepareJson('foo', { well: `hello 'hi'`, there: { friend: true } }); + expect(actual).toBe(expected); + }); + }); + + describe('prepareString', () => { + it('returns a correctly formatted key/value string', () => { + const expected = `foo='bar' `; // trailing space is expected + const actual = prepareString('foo', 'bar'); + expect(actual).toBe(expected); + }); + + it('escapes single quotes', () => { + const expected = `foo='\\'bar\\'' `; + const actual = prepareString('foo', `'bar'`); + expect(actual).toBe(expected); + }); + }); + + describe('buildPipelineVisFunction', () => { + it('handles vega function', () => { + const params = { spec: 'this is a test' }; + const actual = buildPipelineVisFunction.vega({ params }); + expect(actual).toMatchSnapshot(); + }); + + it('handles input_control_vis function', () => { + const params = { + some: 'nested', + data: { + here: true + } + }; + const actual = buildPipelineVisFunction.input_control_vis({ params }); + expect(actual).toMatchSnapshot(); + }); + + it('handles metrics/tsvb function', () => { + const params = { foo: 'bar' }; + const actual = buildPipelineVisFunction.metrics({ params }); + expect(actual).toMatchSnapshot(); + }); + + it('handles timelion function', () => { + const params = { expression: 'foo', interval: 'bar' }; + const actual = buildPipelineVisFunction.timelion({ params }); + expect(actual).toMatchSnapshot(); + }); + + it('handles markdown function', () => { + const params = { markdown: '## hello _markdown_', foo: 'bar' }; + const actual = buildPipelineVisFunction.markdown({ params }); + expect(actual).toMatchSnapshot(); + }); + + describe('handles table function', () => { + const params = { foo: 'bar' }; + it('without splits or buckets', () => { + const schemas = { metric: [0, 1] }; + const actual = buildPipelineVisFunction.table({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + + it('with splits', () => { + const schemas = { + metric: [0], + split_row: [1, 2], + }; + const actual = buildPipelineVisFunction.table({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + + it('with splits and buckets', () => { + const schemas = { + metric: [0, 1], + split_row: [2, 4], + bucket: [3] + }; + const actual = buildPipelineVisFunction.table({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + }); + + describe('handles metric function', () => { + const params = { foo: 'bar' }; + it('without buckets', () => { + const schemas = { metric: [0, 1] }; + const actual = buildPipelineVisFunction.metric({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + + it('with buckets', () => { + const schemas = { + metric: [0, 1], + bucket: [2] + }; + const actual = buildPipelineVisFunction.metric({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + }); + + describe('handles tagcloud function', () => { + const params = { foo: 'bar' }; + it('with metrics and buckets', () => { + const schemas = { + metric: [0], + segment: [1, 2] + }; + const actual = buildPipelineVisFunction.tagcloud({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + + it('throws without bucket', () => { + const schemas = { metric: [0] }; + expect(() => { + buildPipelineVisFunction.tagcloud({ params }, schemas); + }).toThrow(TypeError); + }); + }); + + describe('handles region_map function', () => { + const params = { foo: 'bar' }; + it('with metrics and buckets', () => { + const schemas = { + metric: [0], + segment: [1, 2] + }; + const actual = buildPipelineVisFunction.region_map({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + + it('throws without bucket', () => { + const schemas = { metric: [0] }; + expect(() => { + buildPipelineVisFunction.region_map({ params }, schemas); + }).toThrow(TypeError); + }); + }); + + describe('handles tile_map function', () => { + const params = { foo: 'bar' }; + it('with metrics and buckets', () => { + const schemas = { + metric: [0], + segment: [1, 2] + }; + const actual = buildPipelineVisFunction.tile_map({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + + it('without buckets', () => { + const schemas = { metric: [0] }; + const actual = buildPipelineVisFunction.tile_map({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + }); + + it('handles pie function', () => { + const params = { foo: 'bar' }; + const schemas = { baz: 'qux' }; + const actual = buildPipelineVisFunction.pie({ params }, schemas); + expect(actual).toMatchSnapshot(); + }); + }); +}); diff --git a/src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts new file mode 100644 index 0000000000000..20a6b27febec4 --- /dev/null +++ b/src/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts @@ -0,0 +1,207 @@ +/* + * 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 { SearchSource } from 'ui/courier'; +import { AggConfig, Vis, VisState } from 'ui/vis'; + +interface Schemas { + metric: number[]; + [key: string]: number[]; +} + +type buildVisFunction = (visState: VisState, schemas: Schemas) => string; + +interface BuildPipelineVisFunction { + [key: string]: buildVisFunction; +} + +const vislibCharts: string[] = [ + 'area', + 'gauge', + 'goal', + 'heatmap', + 'histogram', + 'horizontal_bar', + 'line', +]; + +export const getSchemas = (vis: Vis): Schemas => { + let cnt = 0; + const schemas: Schemas = { + metric: [], + }; + const responseAggs = vis.aggs.getResponseAggs(); + const isHierarchical = vis.isHierarchical(); + const metrics = responseAggs.filter((agg: AggConfig) => agg.type.type === 'metrics'); + responseAggs.forEach((agg: AggConfig) => { + if (!agg.enabled) { + return; + } + let schemaName = agg.schema ? agg.schema.name || agg.schema : null; + if (typeof schemaName === 'object') { + schemaName = null; + } + if (!schemaName) { + return; + } + if (schemaName === 'split') { + schemaName = `split_${agg.params.row ? 'row' : 'column'}`; + } + if (!schemas[schemaName]) { + schemas[schemaName] = []; + } + if (!isHierarchical || agg.type.type !== 'metrics') { + schemas[schemaName].push(cnt++); + } + if (isHierarchical && agg.type.type !== 'metrics') { + metrics.forEach(() => { + schemas.metric.push(cnt++); + }); + } + }); + return schemas; +}; + +export const prepareJson = (variable: string, data: object): string => { + return `${variable}='${JSON.stringify(data) + .replace(/\\/g, `\\\\`) + .replace(/'/g, `\\'`)}' `; +}; + +export const prepareString = (variable: string, data: string): string => { + return `${variable}='${data.replace(/\\/g, `\\\\`).replace(/'/g, `\\'`)}' `; +}; + +export const buildPipelineVisFunction: BuildPipelineVisFunction = { + vega: visState => { + return `vega ${prepareString('spec', visState.params.spec)}`; + }, + input_control_vis: visState => { + return `input_control_vis ${prepareJson('visConfig', visState.params)}`; + }, + metrics: visState => { + return `tsvb ${prepareJson('params', visState.params)}`; + }, + timelion: visState => { + const expression = prepareString('expression', visState.params.expression); + const interval = prepareString('interval', visState.params.interval); + return `timelion_vis ${expression}${interval}`; + }, + markdown: visState => { + const expression = prepareString('expression', visState.params.markdown); + const visConfig = prepareJson('visConfig', visState.params); + return `kibana_markdown ${expression}${visConfig}`; + }, + table: (visState, schemas) => { + let pipeline = `kibana_table ${prepareJson('visConfig', visState.params)}`; + if (schemas.split_row) { + pipeline += `splitRow='${schemas.split_row.join(',')}' `; + } + if (schemas.split_column) { + pipeline += `splitColumn='${schemas.split_column.join(',')}' `; + } + if (schemas.bucket) { + pipeline += `bucket='${schemas.bucket.join(',')}' `; + } + pipeline += `metric='${schemas.metric.join(',')}' `; + return pipeline; + }, + metric: (visState, schemas) => { + let pipeline = `kibana_metric ${prepareJson('visConfig', visState.params)}`; + if (schemas.bucket) { + pipeline += `bucket='${schemas.bucket.join(',')}' `; + } + pipeline += `metric='${schemas.metric.join(',')}' `; + return pipeline; + }, + tagcloud: (visState, schemas) => { + let pipeline = `tagcloud ${prepareJson('visConfig', visState.params)}`; + pipeline += `bucket='${schemas.segment.join(',')}' `; + pipeline += `metric='${schemas.metric.join(',')}' `; + return pipeline; + }, + region_map: (visState, schemas) => { + let pipeline = `regionmap ${prepareJson('visConfig', visState.params)}`; + pipeline += `bucket='${schemas.segment.join(',')}' `; + pipeline += `metric='${schemas.metric.join(',')}' `; + return pipeline; + }, + tile_map: (visState, schemas) => { + let pipeline = `tilemap ${prepareJson('visConfig', visState.params)}`; + if (schemas.segment) { + pipeline += `bucket='${schemas.segment.join(',')}' `; + } + pipeline += `metric='${schemas.metric.join(',')}' `; + return pipeline; + }, + pie: (visState, schemas) => { + const visConfig = prepareJson('visConfig', visState.params); + const visSchemas = prepareJson('schemas', schemas); + return `kibana_pie ${visConfig}${visSchemas}`; + }, +}; + +export const buildPipeline = (vis: Vis, params: { searchSource: SearchSource }) => { + const { searchSource } = params; + const { indexPattern } = vis; + const query = searchSource.getField('query'); + const filters = searchSource.getField('filter'); + const visState = vis.getCurrentState(); + + // context + let pipeline = `kibana | kibana_context `; + if (query) { + pipeline += prepareJson('query', query); + } + if (filters) { + pipeline += prepareJson('filters', filters); + } + if (vis.savedSearchId) { + pipeline += prepareString('savedSearchId', vis.savedSearchId); + } + pipeline += '| '; + + // request handler + if (vis.type.requestHandler === 'courier') { + pipeline += `esaggs + ${prepareString('index', indexPattern.id)} + metricsAtAllLevels=${vis.isHierarchical()} + partialRows=${vis.params.showPartialRows || vis.type.requiresPartialRows || false} + ${prepareJson('aggConfigs', visState.aggs)} | `; + } + + const schemas = getSchemas(vis); + if (buildPipelineVisFunction[vis.type.name]) { + pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas); + } else if (vislibCharts.includes(vis.type.name)) { + pipeline += `vislib type='${vis.type.name}' + ${prepareJson('visConfig', visState.params)} + ${prepareJson('schemas', schemas)}`; + } else { + pipeline += `visualization type='${vis.type.name}' + ${prepareJson('visConfig', visState.params)} + metricsAtAllLevels=${vis.isHierarchical()} + partialRows=${vis.params.showPartialRows || vis.type.name === 'tile_map'} `; + if (indexPattern) { + pipeline += `${prepareString('index', indexPattern.id)}`; + } + } + + return pipeline; +}; diff --git a/src/ui/public/visualize/loader/pipeline_helpers/index.ts b/src/ui/public/visualize/loader/pipeline_helpers/index.ts new file mode 100644 index 0000000000000..69c29339a8713 --- /dev/null +++ b/src/ui/public/visualize/loader/pipeline_helpers/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { buildPipeline } from './build_pipeline'; +export { runPipeline } from './run_pipeline'; diff --git a/src/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts b/src/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts new file mode 100644 index 0000000000000..a3b98570c870f --- /dev/null +++ b/src/ui/public/visualize/loader/pipeline_helpers/run_pipeline.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore +import { fromExpression } from '@kbn/interpreter/common'; +// @ts-ignore +import { interpretAst } from '@kbn/interpreter/public'; + +export const runPipeline = async (expression: string, context: any, handlers: any) => { + const ast = fromExpression(expression); + const pipelineResponse = await interpretAst(ast, context, handlers); + return pipelineResponse; +}; diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index 5cd9b280c5550..0c3d200cf8000 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -392,9 +392,9 @@ export default function ({ getService, getPageObjects }) { ]); }); - it('should allow nesting multiple splits', async () => { + it.skip('should allow nesting multiple splits', async () => { // This test can be removed as soon as we remove the nested split table - // feature (https://github.com/elastic/kibana/issues/24560). + // feature (https://github.com/elastic/kibana/issues/24560). (7.0) await PageObjects.visualize.clickData(); await PageObjects.visualize.clickAddBucket(); await PageObjects.visualize.clickBucket('Split Table'); diff --git a/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js b/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js index e068627e5e80e..bd0866882c1fc 100644 --- a/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js +++ b/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js @@ -18,6 +18,7 @@ */ import expect from 'expect.js'; +import { delay } from 'bluebird'; export default function ({ getService }) { const testSubjects = getService('testSubjects'); @@ -36,6 +37,7 @@ export default function ({ getService }) { await retry.try(async () => { await testSubjects.waitForDeleted('visLoadingIndicator'); }); + await delay(1000); } async function getTableData() { diff --git a/x-pack/plugins/canvas/public/components/app/index.js b/x-pack/plugins/canvas/public/components/app/index.js index f5fc65e029f36..b3a9fcc79036e 100644 --- a/x-pack/plugins/canvas/public/components/app/index.js +++ b/x-pack/plugins/canvas/public/components/app/index.js @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - createSocket, - initializeInterpreter, - populateBrowserRegistries, -} from '@kbn/interpreter/public'; +import { populateBrowserRegistries, getInitializedFunctions } from '@kbn/interpreter/public'; import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { getAppReady, getBasePath } from '../../state/selectors/app'; @@ -51,10 +47,9 @@ const mapDispatchToProps = dispatch => ({ setAppReady: basePath => async () => { try { // initialize the socket and interpreter - await createSocket(basePath); loadPrivateBrowserFunctions(); await populateBrowserRegistries(types, basePath); - await initializeInterpreter(); + await getInitializedFunctions(); // set app state to ready dispatch(appReady());