mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Reactify timelion editor * Change translation ids * Add @types/pegjs into renovate.json5 * Add validation, add hover suggestions * Style fixes * Change plugin setup, use kibana context * Change plugin start * Mock services * Fix other comments * Build renovate config * Fix some classnames and SASS file structure Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> # Conflicts: # renovate.json5
This commit is contained in:
parent
7479e2b487
commit
34dbe7139b
27 changed files with 875 additions and 93 deletions
|
@ -336,6 +336,7 @@
|
|||
"@types/mustache": "^0.8.31",
|
||||
"@types/node": "^10.12.27",
|
||||
"@types/opn": "^5.1.0",
|
||||
"@types/pegjs": "^0.10.1",
|
||||
"@types/pngjs": "^3.3.2",
|
||||
"@types/podium": "^1.0.0",
|
||||
"@types/prop-types": "^15.5.3",
|
||||
|
|
|
@ -500,6 +500,14 @@
|
|||
'@types/opn',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'pegjs',
|
||||
groupName: 'pegjs related packages',
|
||||
packageNames: [
|
||||
'pegjs',
|
||||
'@types/pegjs',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupSlug: 'pngjs',
|
||||
groupName: 'pngjs related packages',
|
||||
|
|
46
src/legacy/core_plugins/timelion/common/types.ts
Normal file
46
src/legacy/core_plugins/timelion/common/types.ts
Normal file
|
@ -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.
|
||||
*/
|
||||
|
||||
type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null';
|
||||
|
||||
interface TimelionFunctionArgsSuggestion {
|
||||
name: string;
|
||||
help: string;
|
||||
}
|
||||
|
||||
export interface TimelionFunctionArgs {
|
||||
name: string;
|
||||
help?: string;
|
||||
multi?: boolean;
|
||||
types: TimelionFunctionArgsTypes[];
|
||||
suggestions?: TimelionFunctionArgsSuggestion[];
|
||||
}
|
||||
|
||||
export interface ITimelionFunction {
|
||||
aliases: string[];
|
||||
args: TimelionFunctionArgs[];
|
||||
name: string;
|
||||
help: string;
|
||||
chainable: boolean;
|
||||
extended: boolean;
|
||||
isAlias: boolean;
|
||||
argsByName: {
|
||||
[key: string]: TimelionFunctionArgs[];
|
||||
};
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
@import './timelion_expression_input';
|
|
@ -0,0 +1,18 @@
|
|||
.timExpressionInput {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: $euiSize;
|
||||
}
|
||||
|
||||
.timExpressionInput__editor {
|
||||
height: 100%;
|
||||
padding-top: $euiSizeS;
|
||||
}
|
||||
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
.timExpressionInput__editor {
|
||||
height: $euiSize * 15;
|
||||
max-height: $euiSize * 15;
|
||||
}
|
||||
}
|
21
src/legacy/core_plugins/timelion/public/components/index.ts
Normal file
21
src/legacy/core_plugins/timelion/public/components/index.ts
Normal file
|
@ -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 * from './timelion_expression_input';
|
||||
export * from './timelion_interval';
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { EuiFormLabel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public';
|
||||
import { suggest, getSuggestion } from './timelion_expression_input_helpers';
|
||||
import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types';
|
||||
import { getArgValueSuggestions } from '../services/arg_value_suggestions';
|
||||
|
||||
const LANGUAGE_ID = 'timelion_expression';
|
||||
monacoEditor.languages.register({ id: LANGUAGE_ID });
|
||||
|
||||
interface TimelionExpressionInputProps {
|
||||
value: string;
|
||||
setValue(value: string): void;
|
||||
}
|
||||
|
||||
function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputProps) {
|
||||
const functionList = useRef([]);
|
||||
const kibana = useKibana();
|
||||
const argValueSuggestions = useMemo(getArgValueSuggestions, []);
|
||||
|
||||
const provideCompletionItems = useCallback(
|
||||
async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => {
|
||||
const text = model.getValue();
|
||||
const wordUntil = model.getWordUntilPosition(position);
|
||||
const wordRange = new monacoEditor.Range(
|
||||
position.lineNumber,
|
||||
wordUntil.startColumn,
|
||||
position.lineNumber,
|
||||
wordUntil.endColumn
|
||||
);
|
||||
|
||||
const suggestions = await suggest(
|
||||
text,
|
||||
functionList.current,
|
||||
// it's important to offset the cursor position on 1 point left
|
||||
// because of PEG parser starts the line with 0, but monaco with 1
|
||||
position.column - 1,
|
||||
argValueSuggestions
|
||||
);
|
||||
|
||||
return {
|
||||
suggestions: suggestions
|
||||
? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) =>
|
||||
getSuggestion(s, suggestions.type, wordRange)
|
||||
)
|
||||
: [],
|
||||
};
|
||||
},
|
||||
[argValueSuggestions]
|
||||
);
|
||||
|
||||
const provideHover = useCallback(
|
||||
async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => {
|
||||
const suggestions = await suggest(
|
||||
model.getValue(),
|
||||
functionList.current,
|
||||
// it's important to offset the cursor position on 1 point left
|
||||
// because of PEG parser starts the line with 0, but monaco with 1
|
||||
position.column - 1,
|
||||
argValueSuggestions
|
||||
);
|
||||
|
||||
return {
|
||||
contents: suggestions
|
||||
? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => ({
|
||||
value: s.help,
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
},
|
||||
[argValueSuggestions]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (kibana.services.http) {
|
||||
kibana.services.http.get('../api/timelion/functions').then(data => {
|
||||
functionList.current = data;
|
||||
});
|
||||
}
|
||||
}, [kibana.services.http]);
|
||||
|
||||
return (
|
||||
<div className="timExpressionInput">
|
||||
<EuiFormLabel>
|
||||
<FormattedMessage id="timelion.vis.expressionLabel" defaultMessage="Timelion expression" />
|
||||
</EuiFormLabel>
|
||||
<div className="timExpressionInput__editor">
|
||||
<CodeEditor
|
||||
languageId={LANGUAGE_ID}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
suggestionProvider={{
|
||||
triggerCharacters: ['.', ',', '(', '=', ':'],
|
||||
provideCompletionItems,
|
||||
}}
|
||||
hoverProvider={{ provideHover }}
|
||||
options={{
|
||||
fixedOverflowWidgets: true,
|
||||
fontSize: 14,
|
||||
folding: false,
|
||||
lineNumbers: 'off',
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
wordBasedSuggestions: false,
|
||||
wordWrap: 'on',
|
||||
wrappingIndent: 'indent',
|
||||
}}
|
||||
languageConfiguration={{
|
||||
autoClosingPairs: [
|
||||
{
|
||||
open: '(',
|
||||
close: ')',
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { TimelionExpressionInput };
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* 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, startsWith } from 'lodash';
|
||||
import PEG from 'pegjs';
|
||||
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
|
||||
// @ts-ignore
|
||||
import grammar from 'raw-loader!../chain.peg';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types';
|
||||
import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions';
|
||||
|
||||
const Parser = PEG.generate(grammar);
|
||||
|
||||
export enum SUGGESTION_TYPE {
|
||||
ARGUMENTS = 'arguments',
|
||||
ARGUMENT_VALUE = 'argument_value',
|
||||
FUNCTIONS = 'functions',
|
||||
}
|
||||
|
||||
function inLocation(cursorPosition: number, location: Location) {
|
||||
return cursorPosition >= location.min && cursorPosition <= location.max;
|
||||
}
|
||||
|
||||
function getArgumentsHelp(
|
||||
functionHelp: ITimelionFunction | undefined,
|
||||
functionArgs: FunctionArg[] = []
|
||||
) {
|
||||
if (!functionHelp) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Do not provide 'inputSeries' as argument suggestion for chainable functions
|
||||
const argsHelp = functionHelp.chainable ? functionHelp.args.slice(1) : functionHelp.args.slice(0);
|
||||
|
||||
// ignore arguments that are already provided in function declaration
|
||||
const functionArgNames = functionArgs.map(arg => arg.name);
|
||||
return argsHelp.filter(arg => !functionArgNames.includes(arg.name));
|
||||
}
|
||||
|
||||
async function extractSuggestionsFromParsedResult(
|
||||
result: ReturnType<typeof Parser.parse>,
|
||||
cursorPosition: number,
|
||||
functionList: ITimelionFunction[],
|
||||
argValueSuggestions: ArgValueSuggestions
|
||||
) {
|
||||
const activeFunc = result.functions.find(({ location }: { location: Location }) =>
|
||||
inLocation(cursorPosition, location)
|
||||
);
|
||||
|
||||
if (!activeFunc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const functionHelp = functionList.find(({ name }) => name === activeFunc.function);
|
||||
|
||||
if (!functionHelp) {
|
||||
return;
|
||||
}
|
||||
|
||||
// return function suggestion when cursor is outside of parentheses
|
||||
// location range includes '.', function name, and '('.
|
||||
const openParen = activeFunc.location.min + activeFunc.function.length + 2;
|
||||
if (cursorPosition < openParen) {
|
||||
return { list: [functionHelp], type: SUGGESTION_TYPE.FUNCTIONS };
|
||||
}
|
||||
|
||||
// return argument value suggestions when cursor is inside argument value
|
||||
const activeArg = activeFunc.arguments.find((argument: FunctionArg) => {
|
||||
return inLocation(cursorPosition, argument.location);
|
||||
});
|
||||
if (
|
||||
activeArg &&
|
||||
activeArg.type === 'namedArg' &&
|
||||
inLocation(cursorPosition, activeArg.value.location)
|
||||
) {
|
||||
const { function: functionName, arguments: functionArgs } = activeFunc;
|
||||
|
||||
const {
|
||||
name: argName,
|
||||
value: { text: partialInput },
|
||||
} = activeArg;
|
||||
|
||||
let valueSuggestions;
|
||||
if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) {
|
||||
valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument(
|
||||
functionName,
|
||||
argName,
|
||||
functionArgs,
|
||||
partialInput
|
||||
);
|
||||
} else {
|
||||
const { suggestions: staticSuggestions } =
|
||||
functionHelp.args.find(arg => arg.name === activeArg.name) || {};
|
||||
valueSuggestions = argValueSuggestions.getStaticSuggestionsForInput(
|
||||
partialInput,
|
||||
staticSuggestions
|
||||
);
|
||||
}
|
||||
return {
|
||||
list: valueSuggestions,
|
||||
type: SUGGESTION_TYPE.ARGUMENT_VALUE,
|
||||
};
|
||||
}
|
||||
|
||||
// return argument suggestions
|
||||
const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments);
|
||||
const argumentSuggestions = argsHelp.filter(arg => {
|
||||
if (get(activeArg, 'type') === 'namedArg') {
|
||||
return startsWith(arg.name, activeArg.name);
|
||||
} else if (activeArg) {
|
||||
return startsWith(arg.name, activeArg.text);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return { list: argumentSuggestions, type: SUGGESTION_TYPE.ARGUMENTS };
|
||||
}
|
||||
|
||||
export async function suggest(
|
||||
expression: string,
|
||||
functionList: ITimelionFunction[],
|
||||
cursorPosition: number,
|
||||
argValueSuggestions: ArgValueSuggestions
|
||||
) {
|
||||
try {
|
||||
const result = await Parser.parse(expression);
|
||||
|
||||
return await extractSuggestionsFromParsedResult(
|
||||
result,
|
||||
cursorPosition,
|
||||
functionList,
|
||||
argValueSuggestions
|
||||
);
|
||||
} catch (err) {
|
||||
let message: any;
|
||||
try {
|
||||
// The grammar will throw an error containing a message if the expression is formatted
|
||||
// correctly and is prepared to accept suggestions. If the expression is not formatted
|
||||
// correctly the grammar will just throw a regular PEG SyntaxError, and this JSON.parse
|
||||
// attempt will throw an error.
|
||||
message = JSON.parse(err.message);
|
||||
} catch (e) {
|
||||
// The expression isn't correctly formatted, so JSON.parse threw an error.
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case 'incompleteFunction': {
|
||||
let list;
|
||||
if (message.function) {
|
||||
// The user has start typing a function name, so we'll filter the list down to only
|
||||
// possible matches.
|
||||
list = functionList.filter(func => startsWith(func.name, message.function));
|
||||
} else {
|
||||
// The user hasn't typed anything yet, so we'll just return the entire list.
|
||||
list = functionList;
|
||||
}
|
||||
return { list, type: SUGGESTION_TYPE.FUNCTIONS };
|
||||
}
|
||||
case 'incompleteArgument': {
|
||||
const { currentFunction: functionName, currentArgs: functionArgs } = message;
|
||||
const functionHelp = functionList.find(func => func.name === functionName);
|
||||
return {
|
||||
list: getArgumentsHelp(functionHelp, functionArgs),
|
||||
type: SUGGESTION_TYPE.ARGUMENTS,
|
||||
};
|
||||
}
|
||||
case 'incompleteArgumentValue': {
|
||||
const { name: argName, currentFunction: functionName, currentArgs: functionArgs } = message;
|
||||
let valueSuggestions = [];
|
||||
if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) {
|
||||
valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument(
|
||||
functionName,
|
||||
argName,
|
||||
functionArgs
|
||||
);
|
||||
} else {
|
||||
const functionHelp = functionList.find(func => func.name === functionName);
|
||||
if (functionHelp) {
|
||||
const argHelp = functionHelp.args.find(arg => arg.name === argName);
|
||||
if (argHelp && argHelp.suggestions) {
|
||||
valueSuggestions = argHelp.suggestions;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
list: valueSuggestions,
|
||||
type: SUGGESTION_TYPE.ARGUMENT_VALUE,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getSuggestion(
|
||||
suggestion: ITimelionFunction | TimelionFunctionArgs,
|
||||
type: SUGGESTION_TYPE,
|
||||
range: monacoEditor.Range
|
||||
): monacoEditor.languages.CompletionItem {
|
||||
let kind: monacoEditor.languages.CompletionItemKind =
|
||||
monacoEditor.languages.CompletionItemKind.Method;
|
||||
let insertText: string = suggestion.name;
|
||||
let insertTextRules: monacoEditor.languages.CompletionItem['insertTextRules'];
|
||||
let detail: string = '';
|
||||
let command: monacoEditor.languages.CompletionItem['command'];
|
||||
|
||||
switch (type) {
|
||||
case SUGGESTION_TYPE.ARGUMENTS:
|
||||
command = {
|
||||
title: 'Trigger Suggestion Dialog',
|
||||
id: 'editor.action.triggerSuggest',
|
||||
};
|
||||
kind = monacoEditor.languages.CompletionItemKind.Property;
|
||||
insertText = `${insertText}=`;
|
||||
detail = `${i18n.translate(
|
||||
'timelion.expressionSuggestions.argument.description.acceptsText',
|
||||
{
|
||||
defaultMessage: 'Accepts',
|
||||
}
|
||||
)}: ${(suggestion as TimelionFunctionArgs).types}`;
|
||||
|
||||
break;
|
||||
case SUGGESTION_TYPE.FUNCTIONS:
|
||||
command = {
|
||||
title: 'Trigger Suggestion Dialog',
|
||||
id: 'editor.action.triggerSuggest',
|
||||
};
|
||||
kind = monacoEditor.languages.CompletionItemKind.Function;
|
||||
insertText = `${insertText}($0)`;
|
||||
insertTextRules = monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet;
|
||||
detail = `(${
|
||||
(suggestion as ITimelionFunction).chainable
|
||||
? i18n.translate('timelion.expressionSuggestions.func.description.chainableHelpText', {
|
||||
defaultMessage: 'Chainable',
|
||||
})
|
||||
: i18n.translate('timelion.expressionSuggestions.func.description.dataSourceHelpText', {
|
||||
defaultMessage: 'Data source',
|
||||
})
|
||||
})`;
|
||||
|
||||
break;
|
||||
case SUGGESTION_TYPE.ARGUMENT_VALUE:
|
||||
const param = suggestion.name.split(':');
|
||||
|
||||
if (param.length === 1 || param[1]) {
|
||||
insertText = `${param.length === 1 ? insertText : param[1]},`;
|
||||
}
|
||||
|
||||
command = {
|
||||
title: 'Trigger Suggestion Dialog',
|
||||
id: 'editor.action.triggerSuggest',
|
||||
};
|
||||
kind = monacoEditor.languages.CompletionItemKind.Property;
|
||||
detail = suggestion.help || '';
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
detail,
|
||||
insertText,
|
||||
insertTextRules,
|
||||
kind,
|
||||
label: suggestion.name,
|
||||
documentation: suggestion.help,
|
||||
command,
|
||||
range,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { useValidation } from 'ui/vis/editors/default/controls/agg_utils';
|
||||
import { isValidEsInterval } from '../../../../core_plugins/data/common';
|
||||
|
||||
const intervalOptions = [
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.auto', {
|
||||
defaultMessage: 'Auto',
|
||||
}),
|
||||
value: 'auto',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.second', {
|
||||
defaultMessage: '1 second',
|
||||
}),
|
||||
value: '1s',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.minute', {
|
||||
defaultMessage: '1 minute',
|
||||
}),
|
||||
value: '1m',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.hour', {
|
||||
defaultMessage: '1 hour',
|
||||
}),
|
||||
value: '1h',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.day', {
|
||||
defaultMessage: '1 day',
|
||||
}),
|
||||
value: '1d',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.week', {
|
||||
defaultMessage: '1 week',
|
||||
}),
|
||||
value: '1w',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.month', {
|
||||
defaultMessage: '1 month',
|
||||
}),
|
||||
value: '1M',
|
||||
},
|
||||
{
|
||||
label: i18n.translate('timelion.vis.interval.year', {
|
||||
defaultMessage: '1 year',
|
||||
}),
|
||||
value: '1y',
|
||||
},
|
||||
];
|
||||
|
||||
interface TimelionIntervalProps {
|
||||
value: string;
|
||||
setValue(value: string): void;
|
||||
setValidity(valid: boolean): void;
|
||||
}
|
||||
|
||||
function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProps) {
|
||||
const onCustomInterval = useCallback(
|
||||
(customValue: string) => {
|
||||
setValue(customValue.trim());
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const onChange = useCallback(
|
||||
(opts: Array<EuiComboBoxOptionProps<string>>) => {
|
||||
setValue((opts[0] && opts[0].value) || '');
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
const selectedOptions = useMemo(
|
||||
() => [intervalOptions.find(op => op.value === value) || { label: value, value }],
|
||||
[value]
|
||||
);
|
||||
|
||||
const isValid = intervalOptions.some(int => int.value === value) || isValidEsInterval(value);
|
||||
|
||||
useValidation(setValidity, isValid);
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
compressed
|
||||
fullWidth
|
||||
helpText={i18n.translate('timelion.vis.selectIntervalHelpText', {
|
||||
defaultMessage:
|
||||
'Select an option or create a custom value. Examples: 30s, 20m, 24h, 2d, 1w, 1M',
|
||||
})}
|
||||
isInvalid={!isValid}
|
||||
error={
|
||||
!isValid &&
|
||||
i18n.translate('timelion.vis.invalidIntervalErrorMessage', {
|
||||
defaultMessage: 'Invalid interval format.',
|
||||
})
|
||||
}
|
||||
label={i18n.translate('timelion.vis.intervalLabel', {
|
||||
defaultMessage: 'Interval',
|
||||
})}
|
||||
>
|
||||
<EuiComboBox
|
||||
compressed
|
||||
fullWidth
|
||||
isInvalid={!isValid}
|
||||
onChange={onChange}
|
||||
onCreateOption={onCustomInterval}
|
||||
options={intervalOptions}
|
||||
selectedOptions={selectedOptions}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
placeholder={i18n.translate('timelion.vis.selectIntervalPlaceholder', {
|
||||
defaultMessage: 'Select an interval',
|
||||
})}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
);
|
||||
}
|
||||
|
||||
export { TimelionInterval };
|
|
@ -21,9 +21,15 @@ import expect from '@kbn/expect';
|
|||
import PEG from 'pegjs';
|
||||
import grammar from 'raw-loader!../../chain.peg';
|
||||
import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers';
|
||||
import { ArgValueSuggestionsProvider } from '../timelion_expression_suggestions/arg_value_suggestions';
|
||||
import { getArgValueSuggestions } from '../../services/arg_value_suggestions';
|
||||
import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services';
|
||||
|
||||
describe('Timelion expression suggestions', () => {
|
||||
setIndexPatterns({});
|
||||
setSavedObjectsClient({});
|
||||
|
||||
const argValueSuggestions = getArgValueSuggestions();
|
||||
|
||||
describe('getSuggestions', () => {
|
||||
const func1 = {
|
||||
name: 'func1',
|
||||
|
@ -44,11 +50,6 @@ describe('Timelion expression suggestions', () => {
|
|||
};
|
||||
const functionList = [func1, myFunc2];
|
||||
let Parser;
|
||||
const privateStub = () => {
|
||||
return {};
|
||||
};
|
||||
const indexPatternsStub = {};
|
||||
const argValueSuggestions = ArgValueSuggestionsProvider(privateStub, indexPatternsStub); // eslint-disable-line new-cap
|
||||
beforeEach(function() {
|
||||
Parser = PEG.generate(grammar);
|
||||
});
|
||||
|
|
|
@ -52,11 +52,11 @@ import {
|
|||
insertAtLocation,
|
||||
} from './timelion_expression_input_helpers';
|
||||
import { comboBoxKeyCodes } from '@elastic/eui';
|
||||
import { ArgValueSuggestionsProvider } from './timelion_expression_suggestions/arg_value_suggestions';
|
||||
import { getArgValueSuggestions } from '../services/arg_value_suggestions';
|
||||
|
||||
const Parser = PEG.generate(grammar);
|
||||
|
||||
export function TimelionExpInput($http, $timeout, Private) {
|
||||
export function TimelionExpInput($http, $timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
|
@ -68,7 +68,7 @@ export function TimelionExpInput($http, $timeout, Private) {
|
|||
replace: true,
|
||||
template: timelionExpressionInputTemplate,
|
||||
link: function(scope, elem) {
|
||||
const argValueSuggestions = Private(ArgValueSuggestionsProvider);
|
||||
const argValueSuggestions = getArgValueSuggestions();
|
||||
const expressionInput = elem.find('[data-expression-input]');
|
||||
const functionReference = {};
|
||||
let suggestibleFunctionLocation = {};
|
||||
|
|
|
@ -11,5 +11,6 @@
|
|||
// timChart__legend-isLoading
|
||||
|
||||
@import './app';
|
||||
@import './components/index';
|
||||
@import './directives/index';
|
||||
@import './vis/index';
|
||||
|
|
|
@ -37,4 +37,4 @@ const setupPlugins: Readonly<TimelionPluginSetupDependencies> = {
|
|||
const pluginInstance = plugin({} as PluginInitializerContext);
|
||||
|
||||
export const setup = pluginInstance.setup(npSetup.core, setupPlugins);
|
||||
export const start = pluginInstance.start(npStart.core);
|
||||
export const start = pluginInstance.start(npStart.core, npStart.plugins);
|
||||
|
|
|
@ -35,6 +35,7 @@ const DEBOUNCE_DELAY = 50;
|
|||
|
||||
export function timechartFn(dependencies: TimelionVisualizationDependencies) {
|
||||
const { $rootScope, $compile, uiSettings } = dependencies;
|
||||
|
||||
return function() {
|
||||
return {
|
||||
help: 'Draw a timeseries chart',
|
||||
|
|
|
@ -26,12 +26,14 @@ import {
|
|||
} from 'kibana/public';
|
||||
import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public';
|
||||
import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public';
|
||||
import { PluginsStart } from 'ui/new_platform/new_platform';
|
||||
import { VisualizationsSetup } from '../../visualizations/public/np_ready/public';
|
||||
import { getTimelionVisualizationConfig } from './timelion_vis_fn';
|
||||
import { getTimelionVisualization } from './vis';
|
||||
import { getTimeChart } from './panels/timechart/timechart';
|
||||
import { Panel } from './panels/panel';
|
||||
import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim';
|
||||
import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services';
|
||||
|
||||
/** @internal */
|
||||
export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup {
|
||||
|
@ -85,12 +87,15 @@ export class TimelionPlugin implements Plugin<Promise<void>, void> {
|
|||
dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel);
|
||||
}
|
||||
|
||||
public start(core: CoreStart) {
|
||||
public start(core: CoreStart, plugins: PluginsStart) {
|
||||
const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled');
|
||||
|
||||
if (timelionUiEnabled === false) {
|
||||
core.chrome.navLinks.update('timelion', { hidden: true });
|
||||
}
|
||||
|
||||
setIndexPatterns(plugins.data.indexPatterns);
|
||||
setSavedObjectsClient(core.savedObjects.client);
|
||||
}
|
||||
|
||||
public stop(): void {}
|
||||
|
|
|
@ -17,33 +17,51 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { npStart } from 'ui/new_platform';
|
||||
import { get } from 'lodash';
|
||||
import { TimelionFunctionArgs } from '../../common/types';
|
||||
import { getIndexPatterns, getSavedObjectsClient } from './plugin_services';
|
||||
|
||||
export function ArgValueSuggestionsProvider() {
|
||||
const { indexPatterns } = npStart.plugins.data;
|
||||
const { client: savedObjectsClient } = npStart.core.savedObjects;
|
||||
export interface Location {
|
||||
min: number;
|
||||
max: number;
|
||||
}
|
||||
|
||||
async function getIndexPattern(functionArgs) {
|
||||
const indexPatternArg = functionArgs.find(argument => {
|
||||
return argument.name === 'index';
|
||||
});
|
||||
export interface FunctionArg {
|
||||
function: string;
|
||||
location: Location;
|
||||
name: string;
|
||||
text: string;
|
||||
type: string;
|
||||
value: {
|
||||
location: Location;
|
||||
text: string;
|
||||
type: string;
|
||||
value: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function getArgValueSuggestions() {
|
||||
const indexPatterns = getIndexPatterns();
|
||||
const savedObjectsClient = getSavedObjectsClient();
|
||||
|
||||
async function getIndexPattern(functionArgs: FunctionArg[]) {
|
||||
const indexPatternArg = functionArgs.find(({ name }) => name === 'index');
|
||||
if (!indexPatternArg) {
|
||||
// index argument not provided
|
||||
return;
|
||||
}
|
||||
const indexPatternTitle = _.get(indexPatternArg, 'value.text');
|
||||
const indexPatternTitle = get(indexPatternArg, 'value.text');
|
||||
|
||||
const resp = await savedObjectsClient.find({
|
||||
const { savedObjects } = await savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title'],
|
||||
search: `"${indexPatternTitle}"`,
|
||||
search_fields: ['title'],
|
||||
searchFields: ['title'],
|
||||
perPage: 10,
|
||||
});
|
||||
const indexPatternSavedObject = resp.savedObjects.find(savedObject => {
|
||||
return savedObject.attributes.title === indexPatternTitle;
|
||||
});
|
||||
const indexPatternSavedObject = savedObjects.find(
|
||||
({ attributes }) => attributes.title === indexPatternTitle
|
||||
);
|
||||
if (!indexPatternSavedObject) {
|
||||
// index argument does not match an index pattern
|
||||
return;
|
||||
|
@ -52,7 +70,7 @@ export function ArgValueSuggestionsProvider() {
|
|||
return await indexPatterns.get(indexPatternSavedObject.id);
|
||||
}
|
||||
|
||||
function containsFieldName(partial, field) {
|
||||
function containsFieldName(partial: string, field: { name: string }) {
|
||||
if (!partial) {
|
||||
return true;
|
||||
}
|
||||
|
@ -63,13 +81,13 @@ export function ArgValueSuggestionsProvider() {
|
|||
// Could not put with function definition since functions are defined on server
|
||||
const customHandlers = {
|
||||
es: {
|
||||
index: async function(partial) {
|
||||
async index(partial: string) {
|
||||
const search = partial ? `${partial}*` : '*';
|
||||
const resp = await savedObjectsClient.find({
|
||||
type: 'index-pattern',
|
||||
fields: ['title', 'type'],
|
||||
search: `${search}`,
|
||||
search_fields: ['title'],
|
||||
searchFields: ['title'],
|
||||
perPage: 25,
|
||||
});
|
||||
return resp.savedObjects
|
||||
|
@ -78,7 +96,7 @@ export function ArgValueSuggestionsProvider() {
|
|||
return { name: savedObject.attributes.title };
|
||||
});
|
||||
},
|
||||
metric: async function(partial, functionArgs) {
|
||||
async metric(partial: string, functionArgs: FunctionArg[]) {
|
||||
if (!partial || !partial.includes(':')) {
|
||||
return [
|
||||
{ name: 'avg:' },
|
||||
|
@ -109,7 +127,7 @@ export function ArgValueSuggestionsProvider() {
|
|||
return { name: `${valueSplit[0]}:${field.name}`, help: field.type };
|
||||
});
|
||||
},
|
||||
split: async function(partial, functionArgs) {
|
||||
async split(partial: string, functionArgs: FunctionArg[]) {
|
||||
const indexPattern = await getIndexPattern(functionArgs);
|
||||
if (!indexPattern) {
|
||||
return [];
|
||||
|
@ -127,7 +145,7 @@ export function ArgValueSuggestionsProvider() {
|
|||
return { name: field.name, help: field.type };
|
||||
});
|
||||
},
|
||||
timefield: async function(partial, functionArgs) {
|
||||
async timefield(partial: string, functionArgs: FunctionArg[]) {
|
||||
const indexPattern = await getIndexPattern(functionArgs);
|
||||
if (!indexPattern) {
|
||||
return [];
|
||||
|
@ -150,7 +168,10 @@ export function ArgValueSuggestionsProvider() {
|
|||
* @param {string} argName - user provided argument name
|
||||
* @return {boolean} true when dynamic suggestion handler provided for function argument
|
||||
*/
|
||||
hasDynamicSuggestionsForArgument: (functionName, argName) => {
|
||||
hasDynamicSuggestionsForArgument: <T extends keyof typeof customHandlers>(
|
||||
functionName: T,
|
||||
argName: keyof typeof customHandlers[T]
|
||||
) => {
|
||||
return customHandlers[functionName] && customHandlers[functionName][argName];
|
||||
},
|
||||
|
||||
|
@ -161,12 +182,13 @@ export function ArgValueSuggestionsProvider() {
|
|||
* @param {string} partial - user provided argument value
|
||||
* @return {array} array of dynamic suggestions matching partial
|
||||
*/
|
||||
getDynamicSuggestionsForArgument: async (
|
||||
functionName,
|
||||
argName,
|
||||
functionArgs,
|
||||
getDynamicSuggestionsForArgument: async <T extends keyof typeof customHandlers>(
|
||||
functionName: T,
|
||||
argName: keyof typeof customHandlers[T],
|
||||
functionArgs: FunctionArg[],
|
||||
partialInput = ''
|
||||
) => {
|
||||
// @ts-ignore
|
||||
return await customHandlers[functionName][argName](partialInput, functionArgs);
|
||||
},
|
||||
|
||||
|
@ -175,7 +197,10 @@ export function ArgValueSuggestionsProvider() {
|
|||
* @param {array} staticSuggestions - argument value suggestions
|
||||
* @return {array} array of static suggestions matching partial
|
||||
*/
|
||||
getStaticSuggestionsForInput: (partialInput = '', staticSuggestions = []) => {
|
||||
getStaticSuggestionsForInput: (
|
||||
partialInput = '',
|
||||
staticSuggestions: TimelionFunctionArgs['suggestions'] = []
|
||||
) => {
|
||||
if (partialInput) {
|
||||
return staticSuggestions.filter(suggestion => {
|
||||
return suggestion.name.includes(partialInput);
|
||||
|
@ -186,3 +211,5 @@ export function ArgValueSuggestionsProvider() {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type ArgValueSuggestions = ReturnType<typeof getArgValueSuggestions>;
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { IndexPatternsContract } from 'src/plugins/data/public';
|
||||
import { SavedObjectsClientContract } from 'kibana/public';
|
||||
import { createGetterSetter } from '../../../../../plugins/kibana_utils/public';
|
||||
|
||||
export const [getIndexPatterns, setIndexPatterns] = createGetterSetter<IndexPatternsContract>(
|
||||
'IndexPatterns'
|
||||
);
|
||||
|
||||
export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter<
|
||||
SavedObjectsClientContract
|
||||
>('SavedObjectsClient');
|
|
@ -28,7 +28,7 @@ const name = 'timelion_vis';
|
|||
|
||||
interface Arguments {
|
||||
expression: string;
|
||||
interval: any;
|
||||
interval: string;
|
||||
}
|
||||
|
||||
interface RenderValue {
|
||||
|
@ -38,7 +38,7 @@ interface RenderValue {
|
|||
}
|
||||
|
||||
type Context = KibanaContext | null;
|
||||
type VisParams = Arguments;
|
||||
export type VisParams = Arguments;
|
||||
type Return = Promise<Render<RenderValue>>;
|
||||
|
||||
export const getTimelionVisualizationConfig = (
|
||||
|
@ -60,7 +60,7 @@ export const getTimelionVisualizationConfig = (
|
|||
help: '',
|
||||
},
|
||||
interval: {
|
||||
types: ['string', 'null'],
|
||||
types: ['string'],
|
||||
default: 'auto',
|
||||
help: '',
|
||||
},
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
@import './timelion_vis';
|
||||
@import './timelion_editor';
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
.visEditor--timelion {
|
||||
vis-options-react-wrapper,
|
||||
.visEditorSidebar__options,
|
||||
.visEditorSidebar__timelionOptions {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.visEditor__sidebar {
|
||||
@include euiBreakpoint('xs', 's', 'm') {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,19 +17,24 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
// @ts-ignore
|
||||
import { DefaultEditorSize } from 'ui/vis/editor_size';
|
||||
import { VisOptionsProps } from 'ui/vis/editors/default';
|
||||
import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public';
|
||||
import { getTimelionRequestHandler } from './timelion_request_handler';
|
||||
import visConfigTemplate from './timelion_vis.html';
|
||||
import editorConfigTemplate from './timelion_vis_params.html';
|
||||
import { TimelionVisualizationDependencies } from '../plugin';
|
||||
// @ts-ignore
|
||||
import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type';
|
||||
import { TimelionOptions } from './timelion_options';
|
||||
import { VisParams } from '../timelion_vis_fn';
|
||||
|
||||
export const TIMELION_VIS_NAME = 'timelion';
|
||||
|
||||
export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) {
|
||||
const { http, uiSettings } = dependencies;
|
||||
const timelionRequestHandler = getTimelionRequestHandler(dependencies);
|
||||
|
||||
// return the visType object, which kibana will use to display and configure new
|
||||
|
@ -50,7 +55,11 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe
|
|||
template: visConfigTemplate,
|
||||
},
|
||||
editorConfig: {
|
||||
optionsTemplate: editorConfigTemplate,
|
||||
optionsTemplate: (props: VisOptionsProps<VisParams>) => (
|
||||
<KibanaContextProvider services={{ uiSettings, http }}>
|
||||
<TimelionOptions {...props} />
|
||||
</KibanaContextProvider>
|
||||
),
|
||||
defaultSize: DefaultEditorSize.MEDIUM,
|
||||
},
|
||||
requestHandler: timelionRequestHandler,
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
|
||||
import { VisOptionsProps } from 'ui/vis/editors/default';
|
||||
import { VisParams } from '../timelion_vis_fn';
|
||||
import { TimelionInterval, TimelionExpressionInput } from '../components';
|
||||
|
||||
function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps<VisParams>) {
|
||||
const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [
|
||||
setValue,
|
||||
]);
|
||||
const setExpressionInput = useCallback(
|
||||
(value: VisParams['expression']) => setValue('expression', value),
|
||||
[setValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel className="visEditorSidebar__timelionOptions" paddingSize="s">
|
||||
<TimelionInterval
|
||||
value={stateParams.interval}
|
||||
setValue={setInterval}
|
||||
setValidity={setValidity}
|
||||
/>
|
||||
<TimelionExpressionInput value={stateParams.expression} setValue={setExpressionInput} />
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export { TimelionOptions };
|
|
@ -1,27 +0,0 @@
|
|||
<div class="visEditorSidebar__section">
|
||||
<div class="form-group">
|
||||
<label
|
||||
for="timelionInterval"
|
||||
i18n-id="timelion.vis.intervalLabel"
|
||||
i18n-default-message="Interval"
|
||||
></label>
|
||||
<div class="form-group">
|
||||
<timelion-interval model="editorState.params.interval"></timelion-interval>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<label
|
||||
i18n-id="timelion.vis.expressionLabel"
|
||||
i18n-default-message="Timelion Expression"
|
||||
></label>
|
||||
</div>
|
||||
|
||||
<timelion-expression-input
|
||||
sheet="editorState.params.expression"
|
||||
rows="9"
|
||||
></timelion-expression-input>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -17,6 +17,8 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { TimelionFunctionArgs } from '../../../common/types';
|
||||
|
||||
export interface TimelionFunctionInterface extends TimelionFunctionConfig {
|
||||
chainable: boolean;
|
||||
originalFn: Function;
|
||||
|
@ -32,21 +34,6 @@ export interface TimelionFunctionConfig {
|
|||
args: TimelionFunctionArgs[];
|
||||
}
|
||||
|
||||
export interface TimelionFunctionArgs {
|
||||
name: string;
|
||||
help?: string;
|
||||
multi?: boolean;
|
||||
types: TimelionFunctionArgsTypes[];
|
||||
suggestions?: TimelionFunctionArgsSuggestion[];
|
||||
}
|
||||
|
||||
export type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null';
|
||||
|
||||
export interface TimelionFunctionArgsSuggestion {
|
||||
name: string;
|
||||
help: string;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default class TimelionFunction {
|
||||
constructor(name: string, config: TimelionFunctionConfig);
|
||||
|
|
|
@ -17,12 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
TimelionFunctionInterface,
|
||||
TimelionFunctionConfig,
|
||||
TimelionFunctionArgs,
|
||||
TimelionFunctionArgsSuggestion,
|
||||
TimelionFunctionArgsTypes,
|
||||
} from './lib/classes/timelion_function';
|
||||
|
||||
export { TimelionFunctionInterface, TimelionFunctionConfig } from './lib/classes/timelion_function';
|
||||
export { TimelionRequestQuery } from './routes/run';
|
||||
|
|
|
@ -78,6 +78,13 @@ export interface Props {
|
|||
*/
|
||||
hoverProvider?: monacoEditor.languages.HoverProvider;
|
||||
|
||||
/**
|
||||
* Language config provider for bracket
|
||||
* Documentation for the provider can be found here:
|
||||
* https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.languageconfiguration.html
|
||||
*/
|
||||
languageConfiguration?: monacoEditor.languages.LanguageConfiguration;
|
||||
|
||||
/**
|
||||
* Function called before the editor is mounted in the view
|
||||
*/
|
||||
|
@ -130,6 +137,13 @@ export class CodeEditor extends React.Component<Props, {}> {
|
|||
if (this.props.hoverProvider) {
|
||||
monaco.languages.registerHoverProvider(this.props.languageId, this.props.hoverProvider);
|
||||
}
|
||||
|
||||
if (this.props.languageConfiguration) {
|
||||
monaco.languages.setLanguageConfiguration(
|
||||
this.props.languageId,
|
||||
this.props.languageConfiguration
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Register the theme
|
||||
|
|
|
@ -4434,6 +4434,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a"
|
||||
integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA==
|
||||
|
||||
"@types/pegjs@^0.10.1":
|
||||
version "0.10.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/pegjs/-/pegjs-0.10.1.tgz#9a2f3961dc62430fdb21061eb0ddbd890f9e3b94"
|
||||
integrity sha512-ra8IchO9odGQmYKbm+94K58UyKCEKdZh9y0vxhG4pIpOJOBlC1C+ZtBVr6jLs+/oJ4pl+1p/4t3JtBA8J10Vvw==
|
||||
|
||||
"@types/pngjs@^3.3.2":
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue