From 7247e9fcb5abb1f15797b5d6bfb4e59cdad01241 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Thu, 11 Feb 2021 16:35:47 +0300 Subject: [PATCH] [Timelion] Communicate the index pattern to the dashboard (#90623) (#91112) * [Timelion] Communicate the index pattern to the dashboard Closes #86418 * update types / limits.yml * Update timelion_vis_type.tsx * fix typo * remove extra await Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> # Conflicts: # packages/kbn-optimizer/limits.yml --- .eslintignore | 2 +- packages/kbn-optimizer/limits.yml | 2 +- .../{public => common}/_generated_/chain.js | 0 .../vis_type_timelion/common/parser.ts | 50 +++++++++++++++++++ .../timelion_expression_input_helpers.ts | 31 ++++++------ .../public/helpers/arg_value_suggestions.ts | 43 ++++------------ .../public/timelion_vis_type.tsx | 20 +++++++- .../server/handlers/lib/parse_sheet.js | 11 ++-- tasks/config/peg.js | 4 +- 9 files changed, 102 insertions(+), 61 deletions(-) rename src/plugins/vis_type_timelion/{public => common}/_generated_/chain.js (100%) create mode 100644 src/plugins/vis_type_timelion/common/parser.ts diff --git a/.eslintignore b/.eslintignore index 5513ad1320232..ea8ab55ad7726 100644 --- a/.eslintignore +++ b/.eslintignore @@ -22,7 +22,7 @@ snapshots.js /src/core/lib/kbn_internal_native_observable /src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken /src/plugins/data/common/es_query/kuery/ast/_generated_/** -/src/plugins/vis_type_timelion/public/_generated_/** +/src/plugins/vis_type_timelion/common/_generated_/** /x-pack/legacy/plugins/**/__tests__/fixtures/** /x-pack/plugins/apm/e2e/tmp/* /x-pack/plugins/canvas/canvas_plugin diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 5c399a052485f..657aabca1e86d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -91,7 +91,7 @@ pageLoadAssetSize: visTypeMetric: 42790 visTypeTable: 95078 visTypeTagcloud: 37575 - visTypeTimelion: 51933 + visTypeTimelion: 68883 visTypeTimeseries: 155347 visTypeVega: 153861 visTypeVislib: 242982 diff --git a/src/plugins/vis_type_timelion/public/_generated_/chain.js b/src/plugins/vis_type_timelion/common/_generated_/chain.js similarity index 100% rename from src/plugins/vis_type_timelion/public/_generated_/chain.js rename to src/plugins/vis_type_timelion/common/_generated_/chain.js diff --git a/src/plugins/vis_type_timelion/common/parser.ts b/src/plugins/vis_type_timelion/common/parser.ts new file mode 100644 index 0000000000000..b6c16a6f7b4ed --- /dev/null +++ b/src/plugins/vis_type_timelion/common/parser.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// @ts-ignore +import { parse } from './_generated_/chain'; + +export interface ExpressionLocation { + min: number; + max: number; +} + +interface ExpressionItem { + name: string; + function: string; + location: ExpressionLocation; + text: string; + type: string; +} + +export interface TimelionExpressionArgument extends ExpressionItem { + value: { + location: ExpressionLocation; + type: string; + value: string; + text: string; + }; +} + +export interface TimelionExpressionFunction extends ExpressionItem { + arguments: TimelionExpressionArgument[]; +} + +export interface TimelionExpressionChain { + chain: TimelionExpressionFunction[]; + type: 'chain'; +} + +export interface ParsedExpression { + args: TimelionExpressionArgument[]; + functions: TimelionExpressionFunction[]; + tree: TimelionExpressionChain[]; + variables: Record; +} + +export const parseTimelionExpression = (input: string): ParsedExpression => parse(input); diff --git a/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts b/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index a428bc946364b..8f62abf7fe9be 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -6,16 +6,17 @@ * Side Public License, v 1. */ -import { get, startsWith } from 'lodash'; +import { startsWith } from 'lodash'; import { i18n } from '@kbn/i18n'; import { monaco } from '@kbn/monaco'; +import { + parseTimelionExpression, + ParsedExpression, + TimelionExpressionArgument, + ExpressionLocation, +} from '../../common/parser'; -import { Parser } from 'pegjs'; - -// @ts-ignore -import { parse } from '../_generated_/chain'; - -import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; +import { ArgValueSuggestions } from '../helpers/arg_value_suggestions'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; export enum SUGGESTION_TYPE { @@ -24,13 +25,13 @@ export enum SUGGESTION_TYPE { FUNCTIONS = 'functions', } -function inLocation(cursorPosition: number, location: Location) { +function inLocation(cursorPosition: number, location: ExpressionLocation) { return cursorPosition >= location.min && cursorPosition <= location.max; } function getArgumentsHelp( functionHelp: ITimelionFunction | undefined, - functionArgs: FunctionArg[] = [] + functionArgs: TimelionExpressionArgument[] = [] ) { if (!functionHelp) { return []; @@ -45,14 +46,12 @@ function getArgumentsHelp( } async function extractSuggestionsFromParsedResult( - result: ReturnType, + result: ParsedExpression, cursorPosition: number, functionList: ITimelionFunction[], argValueSuggestions: ArgValueSuggestions ) { - const activeFunc = result.functions.find(({ location }: { location: Location }) => - inLocation(cursorPosition, location) - ); + const activeFunc = result.functions.find(({ location }) => inLocation(cursorPosition, location)); if (!activeFunc) { return; @@ -72,7 +71,7 @@ async function extractSuggestionsFromParsedResult( } // return argument value suggestions when cursor is inside argument value - const activeArg = activeFunc.arguments.find((argument: FunctionArg) => { + const activeArg = activeFunc.arguments.find((argument) => { return inLocation(cursorPosition, argument.location); }); if ( @@ -112,7 +111,7 @@ async function extractSuggestionsFromParsedResult( // return argument suggestions const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments); const argumentSuggestions = argsHelp.filter((arg) => { - if (get(activeArg, 'type') === 'namedArg') { + if (activeArg?.type === 'namedArg') { return startsWith(arg.name, activeArg.name); } else if (activeArg) { return startsWith(arg.name, activeArg.text); @@ -129,7 +128,7 @@ export async function suggest( argValueSuggestions: ArgValueSuggestions ) { try { - const result = await parse(expression); + const result = parseTimelionExpression(expression); return await extractSuggestionsFromParsedResult( result, diff --git a/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts b/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts index 2fbf42f4be19b..0a989858706df 100644 --- a/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts +++ b/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts @@ -9,37 +9,19 @@ import { get } from 'lodash'; import { getIndexPatterns } from './plugin_services'; import { TimelionFunctionArgs } from '../../common/types'; +import { TimelionExpressionFunction, TimelionExpressionArgument } from '../../common/parser'; import { IndexPatternField, indexPatterns as indexPatternsUtils, KBN_FIELD_TYPES, } from '../../../data/public'; -export interface Location { - min: number; - max: number; -} - -export interface FunctionArg { - function: string; - location: Location; - name: string; - text: string; - type: string; - value: { - location: Location; - text: string; - type: string; - value: string; - }; -} - const isRuntimeField = (field: IndexPatternField) => Boolean(field.runtimeField); export function getArgValueSuggestions() { const indexPatterns = getIndexPatterns(); - async function getIndexPattern(functionArgs: FunctionArg[]) { + async function getIndexPattern(functionArgs: TimelionExpressionFunction[]) { const indexPatternArg = functionArgs.find(({ name }) => name === 'index'); if (!indexPatternArg) { // index argument not provided @@ -61,7 +43,7 @@ export function getArgValueSuggestions() { // Argument value suggestion handlers requiring custom client side code // Could not put with function definition since functions are defined on server - const customHandlers = { + const customHandlers: Record = { es: { async index(partial: string) { const search = partial ? `${partial}*` : '*'; @@ -71,7 +53,7 @@ export function getArgValueSuggestions() { name: title, })); }, - async metric(partial: string, functionArgs: FunctionArg[]) { + async metric(partial: string, functionArgs: TimelionExpressionFunction[]) { if (!partial || !partial.includes(':')) { return [ { name: 'avg:' }, @@ -101,7 +83,7 @@ export function getArgValueSuggestions() { ) .map((field) => ({ name: `${valueSplit[0]}:${field.name}`, help: field.type })); }, - async split(partial: string, functionArgs: FunctionArg[]) { + async split(partial: string, functionArgs: TimelionExpressionFunction[]) { const indexPattern = await getIndexPattern(functionArgs); if (!indexPattern) { return []; @@ -125,7 +107,7 @@ export function getArgValueSuggestions() { ) .map((field) => ({ name: field.name, help: field.type })); }, - async timefield(partial: string, functionArgs: FunctionArg[]) { + async timefield(partial: string, functionArgs: TimelionExpressionFunction[]) { const indexPattern = await getIndexPattern(functionArgs); if (!indexPattern) { return []; @@ -150,10 +132,7 @@ export function getArgValueSuggestions() { * @param {string} argName - user provided argument name * @return {boolean} true when dynamic suggestion handler provided for function argument */ - hasDynamicSuggestionsForArgument: ( - functionName: T, - argName: keyof typeof customHandlers[T] - ) => { + hasDynamicSuggestionsForArgument: (functionName: string, argName: string) => { return customHandlers[functionName] && customHandlers[functionName][argName]; }, @@ -164,10 +143,10 @@ export function getArgValueSuggestions() { * @param {string} partial - user provided argument value * @return {array} array of dynamic suggestions matching partial */ - getDynamicSuggestionsForArgument: async ( - functionName: T, - argName: keyof typeof customHandlers[T], - functionArgs: FunctionArg[], + getDynamicSuggestionsForArgument: async ( + functionName: string, + argName: string, + functionArgs: TimelionExpressionArgument[], partialInput = '' ) => { // @ts-ignore diff --git a/src/plugins/vis_type_timelion/public/timelion_vis_type.tsx b/src/plugins/vis_type_timelion/public/timelion_vis_type.tsx index b41bea96de302..2f6f3dd58f61f 100644 --- a/src/plugins/vis_type_timelion/public/timelion_vis_type.tsx +++ b/src/plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -13,8 +13,11 @@ import { DefaultEditorSize } from '../../vis_default_editor/public'; import { TimelionOptionsProps } from './timelion_options'; import { TimelionVisDependencies } from './plugin'; import { toExpressionAst } from './to_ast'; +import { getIndexPatterns } from './helpers/plugin_services'; -import { VIS_EVENT_TO_TRIGGER } from '../../visualizations/public'; +import { parseTimelionExpression } from '../common/parser'; + +import { VIS_EVENT_TO_TRIGGER, VisParams } from '../../visualizations/public'; const TimelionOptions = lazy(() => import('./timelion_options')); @@ -47,6 +50,21 @@ export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) getSupportedTriggers: () => { return [VIS_EVENT_TO_TRIGGER.applyFilter]; }, + getUsedIndexPattern: (params: VisParams) => { + try { + const args = parseTimelionExpression(params.expression)?.args ?? []; + const indexArg = args.find( + ({ type, name, function: fn }) => type === 'namedArg' && fn === 'es' && name === 'index' + ); + + if (indexArg?.value.text) { + return getIndexPatterns().find(indexArg.value.text); + } + } catch { + // timelion expression is invalid + } + return []; + }, options: { showIndexSelection: false, showQueryBar: false, diff --git a/src/plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js b/src/plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js index 67bde9d7e6daa..d1965c422e509 100644 --- a/src/plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js +++ b/src/plugins/vis_type_timelion/server/handlers/lib/parse_sheet.js @@ -7,17 +7,12 @@ */ import { i18n } from '@kbn/i18n'; -import fs from 'fs'; -import path from 'path'; -import _ from 'lodash'; -const grammar = fs.readFileSync(path.resolve(__dirname, '../../../common/chain.peg'), 'utf8'); -import PEG from 'pegjs'; -const Parser = PEG.generate(grammar); +import { parseTimelionExpression } from '../../../common/parser'; export default function parseSheet(sheet) { - return _.map(sheet, function (plot) { + return sheet.map(function (plot) { try { - return Parser.parse(plot).tree; + return parseTimelionExpression(plot).tree; } catch (e) { if (e.expected) { throw new Error( diff --git a/tasks/config/peg.js b/tasks/config/peg.js index 117af5909f23e..09da1ed81c222 100644 --- a/tasks/config/peg.js +++ b/tasks/config/peg.js @@ -15,7 +15,7 @@ module.exports = { }, }, timelion_chain: { - src: 'src/plugins/vis_type_timelion/public/chain.peg', - dest: 'src/plugins/vis_type_timelion/public/_generated_/chain.js', + src: 'src/plugins/vis_type_timelion/common/chain.peg', + dest: 'src/plugins/vis_type_timelion/common/_generated_/chain.js', }, };