diff --git a/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx b/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx
index cadab747763..7e095b07ee7 100644
--- a/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx
+++ b/public/app/plugins/datasource/prometheus/components/monaco-query-field/MonacoQueryField.tsx
@@ -149,7 +149,7 @@ const MonacoQueryField = (props: Props) => {
const completionProvider = getCompletionProvider(monaco, dataProvider);
// completion-providers in monaco are not registered directly to editor-instances,
- // they are registerd to languages. this makes it hard for us to have
+ // they are registered to languages. this makes it hard for us to have
// separate completion-providers for every query-field-instance
// (but we need that, because they might connect to different datasources).
// the trick we do is, we wrap the callback in a "proxy",
@@ -206,6 +206,6 @@ const MonacoQueryField = (props: Props) => {
// we will lazy-load this module using React.lazy,
// and that only supports default-exports,
// so we have to default-export this, even if
-// it is agains the style-guidelines.
+// it is against the style-guidelines.
export default MonacoQueryField;
diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx
index de0d00ac6ab..f2fe0a2d8ed 100644
--- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx
+++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx
@@ -86,6 +86,14 @@ describe('PromQueryEditorSelector', () => {
refId: 'A',
expr: defaultQuery.expr,
editorMode: QueryEditorMode.Builder,
+ visualQuery: {
+ labels: [
+ { label: 'label1', op: '=', value: 'foo' },
+ { label: 'label2', op: '=', value: 'bar' },
+ ],
+ metric: 'metric',
+ operations: [],
+ },
});
});
@@ -135,6 +143,30 @@ describe('PromQueryEditorSelector', () => {
editorMode: QueryEditorMode.Explain,
});
});
+
+ it('parses query when changing to builder mode', async () => {
+ const { rerender } = renderWithProps({
+ refId: 'A',
+ expr: 'rate(test_metric{instance="host.docker.internal:3000"}[$__interval])',
+ editorMode: QueryEditorMode.Code,
+ });
+ switchToMode(QueryEditorMode.Builder);
+ rerender(
+
+ );
+
+ await screen.findByText('test_metric');
+ expect(screen.getByText('host.docker.internal:3000')).toBeInTheDocument();
+ expect(screen.getByText('Rate')).toBeInTheDocument();
+ expect(screen.getByText('$__interval')).toBeInTheDocument();
+ });
});
function renderWithMode(mode: QueryEditorMode) {
@@ -145,8 +177,8 @@ function renderWithProps(overrides?: Partial) {
const query = defaultsDeep(overrides ?? {}, cloneDeep(defaultQuery));
const onChange = jest.fn();
- render();
- return { onChange };
+ const stuff = render();
+ return { onChange, ...stuff };
}
function expectCodeEditor() {
diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx
index 31b7338b653..660ec2dc9e3 100644
--- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx
+++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx
@@ -1,8 +1,9 @@
import { css } from '@emotion/css';
import { GrafanaTheme2, LoadingState } from '@grafana/data';
import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental';
-import { Button, useStyles2 } from '@grafana/ui';
+import { Button, ConfirmModal, useStyles2 } from '@grafana/ui';
import React, { SyntheticEvent, useCallback, useState } from 'react';
+
import { PromQueryEditor } from '../../components/PromQueryEditor';
import { PromQueryEditorProps } from '../../components/types';
import { promQueryModeller } from '../PromQueryModeller';
@@ -14,15 +15,31 @@ import { PromQueryBuilder } from './PromQueryBuilder';
import { PromQueryBuilderExplained } from './PromQueryBuilderExplained';
import { PromQueryBuilderOptions } from './PromQueryBuilderOptions';
import { QueryPreview } from './QueryPreview';
+import { buildVisualQueryFromString } from '../parsing';
+import { PromQuery } from '../../types';
export const PromQueryEditorSelector = React.memo((props) => {
const { query, onChange, onRunQuery, data } = props;
const styles = useStyles2(getStyles);
const [visualQuery, setVisualQuery] = useState(query.visualQuery ?? getDefaultEmptyQuery());
+ const [parseModalOpen, setParseModalOpen] = useState(false);
+ const [pendingChange, setPendingChange] = useState(undefined);
const onEditorModeChange = useCallback(
(newMetricEditorMode: QueryEditorMode) => {
- onChange({ ...query, editorMode: newMetricEditorMode });
+ const change = { ...query, editorMode: newMetricEditorMode };
+ if (newMetricEditorMode === QueryEditorMode.Builder) {
+ const result = buildVisualQueryFromString(query.expr);
+ change.visualQuery = result.query;
+ // If there are errors, give user a chance to decide if they want to go to builder as that can loose some data.
+ if (result.errors.length) {
+ setParseModalOpen(true);
+ setPendingChange(change);
+ return;
+ }
+ setVisualQuery(change.visualQuery);
+ }
+ onChange(change);
},
[onChange, query]
);
@@ -49,6 +66,18 @@ export const PromQueryEditorSelector = React.memo((props)
return (
<>
+ {
+ setVisualQuery(pendingChange!.visualQuery!);
+ onChange(pendingChange!);
+ setParseModalOpen(false);
+ }}
+ onDismiss={() => setParseModalOpen(false)}
+ />