Cloudwatch: Add syntax highlighting and autocomplete for "Metric Search" (#43985)

* Create a "monarch" folder with everything you need to do syntax highlighting and autocompletion.

* Use this new monarch folder with existing cloudwatch sql.

* Add metric math syntax highlighting and autocomplete.

* Make autocomplete "smarter":
- search always inserts a string as first arg
- strings can't contain predefined functions
- operators follow the last closing )

* Add some tests for Metric Math's CompletionItemProvider.

* Fixes After CR:
- refactor CompletionItemProvider, so that it only requires args that are dynamic or outside of it's responsibility
- Update and add tests with mocked monaco
- Add more autocomplete suggestions for SEARCH expression functions
- sort keywords and give different priority from function to make more visually distinctive.

* Change QueryEditor to auto-resize and look more like the one in Prometheus.

* Add autocomplete for time periods for the third arg of Search.

* More CR fixes:
- fix missing break
- add unit tests for statementPosition
- fix broken time period
- sort time periods

* Bug fix
This commit is contained in:
Sarah Zinger
2022-02-01 22:53:32 -05:00
committed by GitHub
parent b2b584f611
commit 58a71c7e91
46 changed files with 1261 additions and 335 deletions

View File

@@ -1,28 +1,84 @@
import React from 'react';
import { Input } from '@grafana/ui';
import React, { useCallback, useRef } from 'react';
import { CodeEditor, Monaco } from '@grafana/ui';
import language from '../metric-math/definition';
import { registerLanguage } from '../monarch/register';
import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api';
import { TRIGGER_SUGGEST } from '../monarch/commands';
import { CloudWatchDatasource } from '../datasource';
export interface Props {
onChange: (query: string) => void;
onRunQuery: () => void;
expression: string;
datasource: CloudWatchDatasource;
}
export function MathExpressionQueryField({ expression: query, onChange, onRunQuery }: React.PropsWithChildren<Props>) {
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter' && (event.shiftKey || event.ctrlKey)) {
event.preventDefault();
onRunQuery();
}
};
export function MathExpressionQueryField({
expression: query,
onChange,
onRunQuery,
datasource,
}: React.PropsWithChildren<Props>) {
const containerRef = useRef<HTMLDivElement>(null);
const onEditorMount = useCallback(
(editor: monacoType.editor.IStandaloneCodeEditor, monaco: Monaco) => {
editor.onDidFocusEditorText(() => editor.trigger(TRIGGER_SUGGEST.id, TRIGGER_SUGGEST.id, {}));
editor.addCommand(monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => {
const text = editor.getValue();
onChange(text);
onRunQuery();
});
// auto resizes the editor to be the height of the content it holds
// this code comes from the Prometheus query editor.
// We may wish to consider abstracting it into the grafana/ui repo in the future
const updateElementHeight = () => {
const containerDiv = containerRef.current;
if (containerDiv !== null && editor.getContentHeight() < 200) {
const pixelHeight = editor.getContentHeight();
containerDiv.style.height = `${pixelHeight}px`;
containerDiv.style.width = '100%';
const pixelWidth = containerDiv.clientWidth;
editor.layout({ width: pixelWidth, height: pixelHeight });
}
};
editor.onDidContentSizeChange(updateElementHeight);
updateElementHeight();
},
[onChange, onRunQuery]
);
return (
<Input
name="Query"
value={query}
placeholder="Enter a math expression"
onBlur={onRunQuery}
onChange={(e) => onChange(e.currentTarget.value)}
onKeyDown={onKeyDown}
/>
<div ref={containerRef}>
<CodeEditor
monacoOptions={{
// without this setting, the auto-resize functionality causes an infinite loop, don't remove it!
scrollBeyondLastLine: false,
// These additional options are style focused and are a subset of those in the query editor in Prometheus
fontSize: 14,
lineNumbers: 'off',
renderLineHighlight: 'none',
scrollbar: {
vertical: 'hidden',
horizontal: 'hidden',
},
suggestFontSize: 12,
wordWrap: 'on',
}}
language={language.id}
value={query}
onBlur={(value) => {
if (value !== query) {
onChange(value);
}
}}
onBeforeEditorMount={(monaco: Monaco) =>
registerLanguage(monaco, language, datasource.metricMathCompletionItemProvider)
}
onEditorDidMount={onEditorMount}
/>
</div>
);
}

View File

@@ -102,6 +102,7 @@ export class MetricsQueryEditor extends PureComponent<Props, State> {
onRunQuery={onRunQuery}
expression={query.expression ?? ''}
onChange={(expression) => this.props.onChange({ ...query, expression })}
datasource={datasource}
></MathExpressionQueryField>
)}
</>

View File

@@ -3,8 +3,8 @@ import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api';
import { CodeEditor, Monaco } from '@grafana/ui';
import { CloudWatchDatasource } from '../datasource';
import language from '../cloudwatch-sql/definition';
import { TRIGGER_SUGGEST } from '../cloudwatch-sql/completion/commands';
import { registerLanguage } from '../cloudwatch-sql/register';
import { TRIGGER_SUGGEST } from '../monarch/commands';
import { registerLanguage } from '../monarch/register';
export interface Props {
region: string;
@@ -43,7 +43,7 @@ export const SQLCodeEditor: FunctionComponent<Props> = ({ region, sql, onChange,
}}
showMiniMap={false}
showLineNumbers={true}
onBeforeEditorMount={(monaco: Monaco) => registerLanguage(monaco, datasource.sqlCompletionItemProvider)}
onBeforeEditorMount={(monaco: Monaco) => registerLanguage(monaco, language, datasource.sqlCompletionItemProvider)}
onEditorDidMount={onEditorMount}
/>
);