Skip to content

Commit

Permalink
[Vis: Default editor] Reactify the timelion editor (elastic#52990)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed Jan 9, 2020
1 parent d5939c4 commit ecddfd8
Show file tree
Hide file tree
Showing 27 changed files with 875 additions and 93 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,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",
Expand Down
8 changes: 8 additions & 0 deletions renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,14 @@
'@types/parse-link-header',
],
},
{
groupSlug: 'pegjs',
groupName: 'pegjs related packages',
packageNames: [
'pegjs',
'@types/pegjs',
],
},
{
groupSlug: 'pngjs',
groupName: 'pngjs related packages',
Expand Down
46 changes: 46 additions & 0 deletions src/legacy/core_plugins/timelion/common/types.ts
Original file line number Diff line number Diff line change
@@ -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[];
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import './timelion_expression_input';
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions src/legacy/core_plugins/timelion/public/components/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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 };
Loading

0 comments on commit ecddfd8

Please sign in to comment.