mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Cloudwatch: Dynamic labels autocomplete (#49794)
* add completeable interface * add basic labels language * render monaco editor for label field * align styling in math expression field * add unit tests * fix broken test * remove unused import * use theme * remove comment * pr feedback * fix broken imports * improve test * make it possible to override code editor styles * use input styles and align border styles
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import type * as monacoType from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
|
||||
import { CodeEditor, getInputStyles, Monaco, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { DynamicLabelsCompletionItemProvider } from '../dynamic-labels/CompletionItemProvider';
|
||||
import language from '../dynamic-labels/definition';
|
||||
import { TRIGGER_SUGGEST } from '../monarch/commands';
|
||||
import { registerLanguage } from '../monarch/register';
|
||||
|
||||
const dynamicLabelsCompletionItemProvider = new DynamicLabelsCompletionItemProvider();
|
||||
|
||||
export interface Props {
|
||||
onChange: (query: string) => void;
|
||||
onRunQuery: () => void;
|
||||
label: string;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export function DynamicLabelsField({ label, width, onChange, onRunQuery }: Props) {
|
||||
const theme = useTheme2();
|
||||
const styles = getInputStyles({ theme, width });
|
||||
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();
|
||||
});
|
||||
|
||||
const containerDiv = containerRef.current;
|
||||
containerDiv !== null && editor.layout({ width: containerDiv.clientWidth, height: containerDiv.clientHeight });
|
||||
},
|
||||
[onChange, onRunQuery]
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className={cx(styles.wrapper)}>
|
||||
<CodeEditor
|
||||
containerStyles={css`
|
||||
border: 1px solid ${theme.colors.action.disabledBackground};
|
||||
&:hover {
|
||||
border-color: ${theme.components.input.borderColor};
|
||||
}
|
||||
`}
|
||||
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',
|
||||
overviewRulerLanes: 0,
|
||||
scrollbar: {
|
||||
vertical: 'hidden',
|
||||
horizontal: 'hidden',
|
||||
},
|
||||
suggestFontSize: 12,
|
||||
padding: {
|
||||
top: 6,
|
||||
},
|
||||
}}
|
||||
language={language.id}
|
||||
value={label}
|
||||
onBlur={(value) => {
|
||||
if (value !== label) {
|
||||
onChange(value);
|
||||
onRunQuery();
|
||||
}
|
||||
}}
|
||||
onBeforeEditorMount={(monaco: Monaco) =>
|
||||
registerLanguage(monaco, language, dynamicLabelsCompletionItemProvider)
|
||||
}
|
||||
onEditorDidMount={onEditorMount}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export function MathExpressionQueryField({
|
||||
const updateElementHeight = () => {
|
||||
const containerDiv = containerRef.current;
|
||||
if (containerDiv !== null && editor.getContentHeight() < 200) {
|
||||
const pixelHeight = editor.getContentHeight();
|
||||
const pixelHeight = Math.max(32, editor.getContentHeight());
|
||||
containerDiv.style.height = `${pixelHeight}px`;
|
||||
containerDiv.style.width = '100%';
|
||||
const pixelWidth = containerDiv.clientWidth;
|
||||
@@ -68,6 +68,9 @@ export function MathExpressionQueryField({
|
||||
},
|
||||
suggestFontSize: 12,
|
||||
wordWrap: 'on',
|
||||
padding: {
|
||||
top: 6,
|
||||
},
|
||||
}}
|
||||
language={language.id}
|
||||
value={query}
|
||||
|
||||
@@ -4,6 +4,7 @@ import selectEvent from 'react-select-event';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import * as ui from '@grafana/ui';
|
||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
|
||||
import { CustomVariableModel, initialVariableModelState } from '../../../../../features/variables/types';
|
||||
@@ -12,6 +13,13 @@ import { CloudWatchJsonData, MetricEditorMode, MetricQueryType } from '../../typ
|
||||
|
||||
import { MetricsQueryEditor, Props } from './MetricsQueryEditor';
|
||||
|
||||
jest.mock('@grafana/ui', () => ({
|
||||
...jest.requireActual<typeof ui>('@grafana/ui'),
|
||||
CodeEditor: function CodeEditor({ value }: { value: string }) {
|
||||
return <pre>{value}</pre>;
|
||||
},
|
||||
}));
|
||||
|
||||
const setup = () => {
|
||||
const instanceSettings = {
|
||||
jsonData: { defaultRegion: 'us-east-1' },
|
||||
@@ -173,9 +181,7 @@ describe('QueryEditor', () => {
|
||||
|
||||
expect(screen.getByText('Label')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Alias')).toBeNull();
|
||||
expect(screen.getByLabelText('Label - optional')).toHaveValue(
|
||||
"Period: ${PROP('Period')} InstanceId: ${PROP('Dim.InstanceId')}"
|
||||
);
|
||||
expect(screen.getByText("Period: ${PROP('Period')} InstanceId: ${PROP('Dim.InstanceId')}"));
|
||||
|
||||
config.featureToggles.cloudWatchDynamicLabels = originalValue;
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
MetricQueryType,
|
||||
MetricStat,
|
||||
} from '../../types';
|
||||
import { DynamicLabelsField } from '../DynamicLabelsField';
|
||||
import QueryHeader from '../QueryHeader';
|
||||
|
||||
import { Alias } from './Alias';
|
||||
@@ -138,14 +139,12 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
optional
|
||||
tooltip="Change time series legend name using Dynamic labels. See documentation for details."
|
||||
>
|
||||
<Input
|
||||
id={`${query.refId}-cloudwatch-metric-query-editor-label`}
|
||||
onBlur={onRunQuery}
|
||||
value={preparedQuery.label ?? ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onChange({ ...preparedQuery, label: event.target.value })
|
||||
}
|
||||
/>
|
||||
<DynamicLabelsField
|
||||
width={52}
|
||||
onRunQuery={onRunQuery}
|
||||
label={preparedQuery.label ?? ''}
|
||||
onChange={(label) => props.onChange({ ...query, label })}
|
||||
></DynamicLabelsField>
|
||||
</EditorField>
|
||||
) : (
|
||||
<EditorField
|
||||
|
||||
Reference in New Issue
Block a user