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)} + />