From 3ee450e66bd8658ca2d72b48c7423077d20d63ed Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 20 Oct 2022 11:46:48 +0200 Subject: [PATCH] Loki: Remove unused query editors (#57192) * Loki: Remove not used query editors * Move Loki editor to components and rename * Update public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx Co-authored-by: Matias Chomicki * Fix test Co-authored-by: Matias Chomicki --- .../LokiExploreQueryEditor.test.tsx | 102 -------- .../components/LokiExploreQueryEditor.tsx | 46 ---- .../loki/components/LokiQueryEditor.test.tsx | 234 +++++++++++++----- .../loki/components/LokiQueryEditor.tsx | 222 ++++++++++++----- .../components/LokiQueryEditorByApp.test.tsx | 20 +- .../loki/components/LokiQueryEditorByApp.tsx | 20 +- .../components/LokiQueryBuilder.tsx | 5 +- .../components/LokiQueryBuilderContainer.tsx | 2 + .../components/LokiQueryCodeEditor.tsx | 2 +- .../LokiQueryEditorSelector.test.tsx | 194 --------------- .../components/LokiQueryEditorSelector.tsx | 161 ------------ 11 files changed, 358 insertions(+), 650 deletions(-) delete mode 100644 public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx delete mode 100644 public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx delete mode 100644 public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx delete mode 100644 public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx deleted file mode 100644 index d4837d0ba04..00000000000 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import React from 'react'; - -import { LoadingState, PanelData, toUtc, TimeRange, HistoryItem } from '@grafana/data'; -import { TemplateSrv } from '@grafana/runtime'; - -import LokiLanguageProvider from '../LanguageProvider'; -import { LokiDatasource } from '../datasource'; -import { createLokiDatasource } from '../mocks'; -import { LokiQuery } from '../types'; - -import { LokiExploreQueryEditor, Props } from './LokiExploreQueryEditor'; - -const setup = () => { - const mockTemplateSrv: TemplateSrv = { - getVariables: jest.fn(), - replace: jest.fn(), - containsTemplate: jest.fn(), - updateTimeRange: jest.fn(), - }; - const datasource: LokiDatasource = createLokiDatasource(mockTemplateSrv); - datasource.languageProvider = new LokiLanguageProvider(datasource); - jest.spyOn(datasource, 'metadataRequest').mockResolvedValue([]); - - const onRunQuery = jest.fn(); - const onChange = jest.fn(); - const query: LokiQuery = { expr: '', refId: 'A', maxLines: 0 }; - const range: TimeRange = { - from: toUtc('2020-01-01', 'YYYY-MM-DD'), - to: toUtc('2020-01-02', 'YYYY-MM-DD'), - raw: { - from: toUtc('2020-01-01', 'YYYY-MM-DD'), - to: toUtc('2020-01-02', 'YYYY-MM-DD'), - }, - }; - const data: PanelData = { - state: LoadingState.NotStarted, - series: [], - request: { - requestId: '1', - dashboardId: 1, - interval: '1s', - intervalMs: 1000, - panelId: 1, - range: { - from: toUtc('2020-01-01', 'YYYY-MM-DD'), - to: toUtc('2020-01-02', 'YYYY-MM-DD'), - raw: { - from: toUtc('2020-01-01', 'YYYY-MM-DD'), - to: toUtc('2020-01-02', 'YYYY-MM-DD'), - }, - }, - scopedVars: {}, - targets: [], - timezone: 'GMT', - app: 'Grafana', - startTime: 0, - }, - timeRange: { - from: toUtc('2020-01-01', 'YYYY-MM-DD'), - to: toUtc('2020-01-02', 'YYYY-MM-DD'), - raw: { - from: toUtc('2020-01-01', 'YYYY-MM-DD'), - to: toUtc('2020-01-02', 'YYYY-MM-DD'), - }, - }, - }; - const history: Array> = []; - - const props: Props = { - query, - data, - range, - datasource, - history, - onChange, - onRunQuery, - }; - - render(); -}; - -describe('LokiExploreQueryEditor', () => { - let originalGetSelection: typeof window.getSelection; - beforeAll(() => { - originalGetSelection = window.getSelection; - window.getSelection = () => null; - }); - - afterAll(() => { - window.getSelection = originalGetSelection; - }); - - it('should render component without throwing an error', () => { - expect(() => setup()).not.toThrow(); - }); - - it('should render LokiQueryField with ExtraFieldElement when ExploreMode is set to Logs', async () => { - setup(); - expect(screen.getByLabelText('Loki extra field')).toBeInTheDocument(); - }); -}); diff --git a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx deleted file mode 100644 index dacf67da7ba..00000000000 --- a/public/app/plugins/datasource/loki/components/LokiExploreQueryEditor.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// Libraries -import React, { memo } from 'react'; - -// Types -import { QueryEditorProps } from '@grafana/data'; - -import { LokiDatasource } from '../datasource'; -import { LokiQuery, LokiOptions } from '../types'; - -import { LokiOptionFields } from './LokiOptionFields'; -import { LokiQueryField } from './LokiQueryField'; - -export type Props = QueryEditorProps; - -export const LokiExploreQueryEditor = memo((props: Props) => { - const { query, data, datasource, history, onChange, onRunQuery, range } = props; - - return ( - {}} - onRunQuery={onRunQuery} - history={history} - data={data} - range={range} - data-testid={testIds.editor} - ExtraFieldElement={ - - } - /> - ); -}); - -LokiExploreQueryEditor.displayName = 'LokiExploreQueryEditor'; - -export const testIds = { - editor: 'loki-editor-explore', -}; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx index a4984fcd54f..7e51ca468e8 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.test.tsx @@ -1,72 +1,194 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { cloneDeep, defaultsDeep } from 'lodash'; import React from 'react'; -import { EventBusSrv, TimeRange, toUtc } from '@grafana/data'; -import { setBackendSrv, TemplateSrv } from '@grafana/runtime'; -import { BackendSrv } from 'app/core/services/backend_srv'; -import { ContextSrv } from 'app/core/services/context_srv'; +import { DataSourcePluginMeta } from '@grafana/data'; +import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; -import { createLokiDatasource } from '../mocks'; -import { LokiQuery } from '../types'; +import { LokiDatasource } from '../datasource'; +import { EXPLAIN_LABEL_FILTER_CONTENT } from '../querybuilder/components/LokiQueryBuilderExplained'; +import { LokiQuery, LokiQueryType } from '../types'; -import { LokiQueryEditor } from './LokiQueryEditor'; +import { LokiQueryEditorSelector } from './LokiQueryEditor'; -const createMockRequestRange = (from: string, to: string): TimeRange => { +jest.mock('@grafana/runtime', () => { return { - from: toUtc(from, 'YYYY-MM-DD'), - to: toUtc(to, 'YYYY-MM-DD'), - raw: { - from: toUtc(from, 'YYYY-MM-DD'), - to: toUtc(to, 'YYYY-MM-DD'), + ...jest.requireActual('@grafana/runtime'), + reportInteraction: jest.fn(), + }; +}); + +jest.mock('app/core/store', () => { + return { + get() { + return undefined; + }, + set() {}, + getObject(key: string, defaultValue: unknown) { + return defaultValue; }, }; +}); + +const defaultQuery = { + refId: 'A', + expr: '{label1="foo", label2="bar"}', }; -const setup = (propOverrides?: object) => { - const mockTemplateSrv: TemplateSrv = { - getVariables: jest.fn(), - replace: jest.fn(), - containsTemplate: jest.fn(), - updateTimeRange: jest.fn(), - }; - const datasource = createLokiDatasource(mockTemplateSrv); - const onRunQuery = jest.fn(); +const datasource = new LokiDatasource( + { + id: 1, + uid: '', + type: 'loki', + name: 'loki-test', + access: 'proxy', + url: '', + jsonData: {}, + meta: {} as DataSourcePluginMeta, + readOnly: false, + }, + undefined, + undefined +); + +datasource.languageProvider.fetchLabels = jest.fn().mockResolvedValue([]); +datasource.getDataSamples = jest.fn().mockResolvedValue([]); + +const defaultProps = { + datasource, + query: defaultQuery, + onRunQuery: () => {}, + onChange: () => {}, +}; + +describe('LokiQueryEditorSelector', () => { + it('shows code editor if expr and nothing else', async () => { + // We opt for showing code editor for queries created before this feature was added + render(); + expectCodeEditor(); + }); + + it('shows builder if new query', async () => { + render( + + ); + await expectBuilder(); + }); + + it('shows code editor when code mode is set', async () => { + renderWithMode(QueryEditorMode.Code); + expectCodeEditor(); + }); + + it('shows builder when builder mode is set', async () => { + renderWithMode(QueryEditorMode.Builder); + await expectBuilder(); + }); + + it('changes to builder mode', async () => { + const { onChange } = renderWithMode(QueryEditorMode.Code); + await switchToMode(QueryEditorMode.Builder); + expect(onChange).toBeCalledWith({ + refId: 'A', + expr: defaultQuery.expr, + queryType: LokiQueryType.Range, + editorMode: QueryEditorMode.Builder, + }); + }); + + it('Can enable raw query', async () => { + renderWithMode(QueryEditorMode.Builder); + expect(await screen.findByLabelText('selector')).toBeInTheDocument(); + screen.getByLabelText('Raw query').click(); + expect(screen.queryByLabelText('selector')).not.toBeInTheDocument(); + }); + + it('Should show raw query by default', async () => { + renderWithProps({ + editorMode: QueryEditorMode.Builder, + expr: '{job="grafana"}', + }); + const selector = await screen.findByLabelText('selector'); + expect(selector).toBeInTheDocument(); + expect(selector.textContent).toBe('{job="grafana"}'); + }); + + it('Can enable explain', async () => { + renderWithMode(QueryEditorMode.Builder); + expect(screen.queryByText(EXPLAIN_LABEL_FILTER_CONTENT)).not.toBeInTheDocument(); + screen.getByLabelText('Explain').click(); + expect(await screen.findByText(EXPLAIN_LABEL_FILTER_CONTENT)).toBeInTheDocument(); + }); + + it('changes to code mode', async () => { + const { onChange } = renderWithMode(QueryEditorMode.Builder); + await switchToMode(QueryEditorMode.Code); + expect(onChange).toBeCalledWith({ + refId: 'A', + expr: defaultQuery.expr, + queryType: LokiQueryType.Range, + editorMode: QueryEditorMode.Code, + }); + }); + + it('parses query when changing to builder mode', async () => { + const { rerender } = renderWithProps({ + refId: 'A', + expr: 'rate({instance="host.docker.internal:3000"}[$__interval])', + editorMode: QueryEditorMode.Code, + }); + await switchToMode(QueryEditorMode.Builder); + rerender( + + ); + + await screen.findByText('host.docker.internal:3000'); + expect(screen.getByText('Rate')).toBeInTheDocument(); + expect(screen.getByText('$__interval')).toBeInTheDocument(); + }); +}); + +function renderWithMode(mode: QueryEditorMode) { + return renderWithProps({ editorMode: mode }); +} + +function renderWithProps(overrides?: Partial) { + const query = defaultsDeep(overrides ?? {}, cloneDeep(defaultQuery)); const onChange = jest.fn(); - const query: LokiQuery = { - expr: '', - refId: 'A', - legendFormat: 'My Legend', - }; + const stuff = render(); + return { onChange, ...stuff }; +} - const range = createMockRequestRange('2020-01-01', '2020-01-02'); +function expectCodeEditor() { + // Log browser shows this until log labels are loaded. + expect(screen.getByText('Loading labels...')).toBeInTheDocument(); +} - const props = { - datasource, - onChange, - onRunQuery, - query, - range, - }; +async function expectBuilder() { + expect(await screen.findByText('Label filters')).toBeInTheDocument(); +} - Object.assign(props, propOverrides); +async function switchToMode(mode: QueryEditorMode) { + const label = { + [QueryEditorMode.Code]: /Code/, + [QueryEditorMode.Builder]: /Builder/, + }[mode]; - render(); -}; - -beforeAll(() => { - const mockedBackendSrv = new BackendSrv({ - fromFetch: jest.fn(), - appEvents: new EventBusSrv(), - contextSrv: new ContextSrv(), - logout: jest.fn(), - }); - - setBackendSrv(mockedBackendSrv); -}); - -describe('LokiQueryEditor', () => { - it('should render without throwing', () => { - expect(() => setup()).not.toThrow(); - }); -}); + const switchEl = screen.getByLabelText(label); + await userEvent.click(switchEl); +} diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx index 3e2aff5db27..96db3e59b08 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditor.tsx @@ -1,71 +1,165 @@ -// Libraries -import React from 'react'; +import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react'; -// Types -import { InlineFormLabel } from '@grafana/ui'; +import { CoreApp, LoadingState } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; +import { reportInteraction } from '@grafana/runtime'; +import { Button, ConfirmModal, EditorHeader, EditorRows, FlexItem, Space } from '@grafana/ui'; +import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle'; +import { QueryHeaderSwitch } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryHeaderSwitch'; +import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; + +import { + lokiQueryEditorExplainKey, + lokiQueryEditorRawQueryKey, + useFlag, +} from '../../prometheus/querybuilder/shared/hooks/useFlag'; +import { LokiQueryBuilderContainer } from '../querybuilder/components/LokiQueryBuilderContainer'; +import { LokiQueryBuilderOptions } from '../querybuilder/components/LokiQueryBuilderOptions'; +import { LokiQueryCodeEditor } from '../querybuilder/components/LokiQueryCodeEditor'; +import { QueryPatternsModal } from '../querybuilder/components/QueryPatternsModal'; +import { buildVisualQueryFromString } from '../querybuilder/parsing'; +import { changeEditorMode, getQueryWithDefaults } from '../querybuilder/state'; +import { LokiQuery } from '../types'; -import { LokiOptionFields } from './LokiOptionFields'; -import { LokiQueryField } from './LokiQueryField'; import { LokiQueryEditorProps } from './types'; -export function LokiQueryEditor(props: LokiQueryEditorProps) { - const { query, data, datasource, onChange, onRunQuery, range } = props; - - const onLegendChange = (e: React.SyntheticEvent) => { - const nextQuery = { ...query, legendFormat: e.currentTarget.value }; - onChange(nextQuery); - }; - - const legendField = ( -
-
- - Legend - - -
-
- ); - - return ( - - - {legendField} - - } - /> - ); -} - export const testIds = { editor: 'loki-editor', }; + +export const LokiQueryEditorSelector = React.memo((props) => { + const { onChange, onRunQuery, onAddQuery, data, app, queries } = props; + const [parseModalOpen, setParseModalOpen] = useState(false); + const [queryPatternsModalOpen, setQueryPatternsModalOpen] = useState(false); + const [dataIsStale, setDataIsStale] = useState(false); + const { flag: explain, setFlag: setExplain } = useFlag(lokiQueryEditorExplainKey); + const { flag: rawQuery, setFlag: setRawQuery } = useFlag(lokiQueryEditorRawQueryKey, true); + + const query = getQueryWithDefaults(props.query); + // This should be filled in from the defaults by now. + const editorMode = query.editorMode!; + + const onExplainChange = (event: SyntheticEvent) => { + setExplain(event.currentTarget.checked); + }; + + const onEditorModeChange = useCallback( + (newEditorMode: QueryEditorMode) => { + reportInteraction('grafana_loki_editor_mode_clicked', { + newEditor: newEditorMode, + previousEditor: query.editorMode ?? '', + newQuery: !query.expr, + app: app ?? '', + }); + + if (newEditorMode === QueryEditorMode.Builder) { + const result = buildVisualQueryFromString(query.expr || ''); + // If there are errors, give user a chance to decide if they want to go to builder as that can lose some data. + if (result.errors.length) { + setParseModalOpen(true); + return; + } + } + changeEditorMode(query, newEditorMode, onChange); + }, + [onChange, query, app] + ); + + useEffect(() => { + setDataIsStale(false); + }, [data]); + + const onChangeInternal = (query: LokiQuery) => { + setDataIsStale(true); + onChange(query); + }; + + const onQueryPreviewChange = (event: SyntheticEvent) => { + const isEnabled = event.currentTarget.checked; + setRawQuery(isEnabled); + }; + + return ( + <> + { + onChange({ ...query, editorMode: QueryEditorMode.Builder }); + setParseModalOpen(false); + }} + onDismiss={() => setParseModalOpen(false)} + /> + setQueryPatternsModalOpen(false)} + query={query} + queries={queries} + app={app} + onChange={onChange} + onAddQuery={onAddQuery} + /> + + + + {editorMode === QueryEditorMode.Builder && ( + <> + + + )} + + {app !== CoreApp.Explore && ( + + )} + + + + + {editorMode === QueryEditorMode.Code && ( + + )} + {editorMode === QueryEditorMode.Builder && ( + + )} + + + + ); +}); + +LokiQueryEditorSelector.displayName = 'LokiQueryEditorSelector'; diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx index e52790a9817..2f64b8faef9 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.test.tsx @@ -1,4 +1,4 @@ -import { render, RenderResult } from '@testing-library/react'; +import { render, RenderResult, waitFor } from '@testing-library/react'; import { noop } from 'lodash'; import React from 'react'; @@ -6,7 +6,6 @@ import { CoreApp } from '@grafana/data'; import { LokiDatasource } from '../datasource'; -import { testIds as exploreTestIds } from './LokiExploreQueryEditor'; import { testIds as regularTestIds } from './LokiQueryEditor'; import { LokiQueryEditorByApp } from './LokiQueryEditorByApp'; import { testIds as alertingTestIds } from './LokiQueryEditorForAlerting'; @@ -19,6 +18,8 @@ function setup(app: CoreApp): RenderResult { getLabelKeys: () => [], metrics: [], }, + getQueryHints: () => [], + getDataSamples: () => [], } as unknown as LokiDatasource; return render( @@ -40,24 +41,23 @@ describe('LokiQueryEditorByApp', () => { expect(queryByTestId(regularTestIds.editor)).toBeNull(); }); - it('should render regular query editor for unkown apps', () => { + it('should render regular query editor for unknown apps', async () => { const { getByTestId, queryByTestId } = setup(CoreApp.Unknown); - - expect(getByTestId(regularTestIds.editor)).toBeInTheDocument(); + expect(await waitFor(() => getByTestId(regularTestIds.editor))).toBeInTheDocument(); expect(queryByTestId(alertingTestIds.editor)).toBeNull(); }); - it('should render expore query editor for explore', () => { + it('should render regular query editor for explore', async () => { const { getByTestId, queryByTestId } = setup(CoreApp.Explore); - expect(getByTestId(exploreTestIds.editor)).toBeInTheDocument(); + expect(await waitFor(() => getByTestId(regularTestIds.editor))).toBeInTheDocument(); expect(queryByTestId(alertingTestIds.editor)).toBeNull(); }); - it('should render regular query editor for dashboard', () => { - const { getByTestId, queryByTestId } = setup(CoreApp.Dashboard); + it('should render regular query editor for dashboard', async () => { + const { findByTestId, queryByTestId } = setup(CoreApp.Dashboard); - expect(getByTestId(regularTestIds.editor)).toBeInTheDocument(); + expect(await findByTestId(regularTestIds.editor)).toBeInTheDocument(); expect(queryByTestId(alertingTestIds.editor)).toBeNull(); }); }); diff --git a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx index bd3b6c91654..4b987938d6c 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryEditorByApp.tsx @@ -1,12 +1,8 @@ import React, { memo } from 'react'; import { CoreApp } from '@grafana/data'; -import { config } from '@grafana/runtime'; -import { LokiQueryEditorSelector } from '../querybuilder/components/LokiQueryEditorSelector'; - -import { LokiExploreQueryEditor } from './LokiExploreQueryEditor'; -import { LokiQueryEditor } from './LokiQueryEditor'; +import { LokiQueryEditorSelector } from './LokiQueryEditor'; import { LokiQueryEditorForAlerting } from './LokiQueryEditorForAlerting'; import { LokiQueryEditorProps } from './types'; @@ -16,17 +12,13 @@ export function LokiQueryEditorByApp(props: LokiQueryEditorProps) { switch (app) { case CoreApp.CloudAlerting: return ; - case CoreApp.Explore: - if (config.featureToggles.lokiQueryBuilder) { - return ; - } - return ; default: - if (config.featureToggles.lokiQueryBuilder) { - return ; - } - return ; + return ; } } export default memo(LokiQueryEditorByApp); + +export const testIds = { + editor: 'loki-editor', +}; diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx index 8017a2cf588..9ad7b8f43c8 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx @@ -14,6 +14,7 @@ import { QueryBuilderOperation, } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; +import { testIds } from '../../components/LokiQueryEditor'; import { LokiDatasource } from '../../datasource'; import { escapeLabelValueInSelector } from '../../languageUtils'; import logqlGrammar from '../../syntax'; @@ -107,7 +108,7 @@ export const LokiQueryBuilder = React.memo(({ datasource, query, onChange const lang = { grammar: logqlGrammar, name: 'logql' }; return ( - <> +
) => @@ -170,7 +171,7 @@ export const LokiQueryBuilder = React.memo(({ datasource, query, onChange showExplain={showExplain} /> )} - +
); }); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx index 19c9f58a364..be7d88def36 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx @@ -1,6 +1,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import React, { useEffect, useReducer } from 'react'; +import { testIds } from '../../components/LokiQueryEditor'; import { LokiDatasource } from '../../datasource'; import { LokiQuery } from '../../types'; import { lokiQueryModeller } from '../LokiQueryModeller'; @@ -64,6 +65,7 @@ export function LokiQueryBuilderContainer(props: Props) { onChange={onVisQueryChange} onRunQuery={onRunQuery} showExplain={showExplain} + data-testid={testIds.editor} /> {showRawQuery && } diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx index 265ba877a43..fdbb2c63f82 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryCodeEditor.tsx @@ -37,8 +37,8 @@ export function LokiQueryCodeEditor({ query, datasource, range, onRunQuery, onCh onBlur={onBlur} history={[]} data={data} - data-testid={testIds.editor} app={app} + data-testid={testIds.editor} /> {showExplain && } diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx deleted file mode 100644 index 7635bee7b80..00000000000 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { cloneDeep, defaultsDeep } from 'lodash'; -import React from 'react'; - -import { DataSourcePluginMeta } from '@grafana/data'; -import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; - -import { LokiDatasource } from '../../datasource'; -import { LokiQuery, LokiQueryType } from '../../types'; - -import { EXPLAIN_LABEL_FILTER_CONTENT } from './LokiQueryBuilderExplained'; -import { LokiQueryEditorSelector } from './LokiQueryEditorSelector'; - -jest.mock('@grafana/runtime', () => { - return { - ...jest.requireActual('@grafana/runtime'), - reportInteraction: jest.fn(), - }; -}); - -jest.mock('app/core/store', () => { - return { - get() { - return undefined; - }, - set() {}, - getObject(key: string, defaultValue: unknown) { - return defaultValue; - }, - }; -}); - -const defaultQuery = { - refId: 'A', - expr: '{label1="foo", label2="bar"}', -}; - -const datasource = new LokiDatasource( - { - id: 1, - uid: '', - type: 'loki', - name: 'loki-test', - access: 'proxy', - url: '', - jsonData: {}, - meta: {} as DataSourcePluginMeta, - readOnly: false, - }, - undefined, - undefined -); - -datasource.languageProvider.fetchLabels = jest.fn().mockResolvedValue([]); -datasource.getDataSamples = jest.fn().mockResolvedValue([]); - -const defaultProps = { - datasource, - query: defaultQuery, - onRunQuery: () => {}, - onChange: () => {}, -}; - -describe('LokiQueryEditorSelector', () => { - it('shows code editor if expr and nothing else', async () => { - // We opt for showing code editor for queries created before this feature was added - render(); - expectCodeEditor(); - }); - - it('shows builder if new query', async () => { - render( - - ); - await expectBuilder(); - }); - - it('shows code editor when code mode is set', async () => { - renderWithMode(QueryEditorMode.Code); - expectCodeEditor(); - }); - - it('shows builder when builder mode is set', async () => { - renderWithMode(QueryEditorMode.Builder); - await expectBuilder(); - }); - - it('changes to builder mode', async () => { - const { onChange } = renderWithMode(QueryEditorMode.Code); - await switchToMode(QueryEditorMode.Builder); - expect(onChange).toBeCalledWith({ - refId: 'A', - expr: defaultQuery.expr, - queryType: LokiQueryType.Range, - editorMode: QueryEditorMode.Builder, - }); - }); - - it('Can enable raw query', async () => { - renderWithMode(QueryEditorMode.Builder); - expect(await screen.findByLabelText('selector')).toBeInTheDocument(); - screen.getByLabelText('Raw query').click(); - expect(screen.queryByLabelText('selector')).not.toBeInTheDocument(); - }); - - it('Should show raw query by default', async () => { - renderWithProps({ - editorMode: QueryEditorMode.Builder, - expr: '{job="grafana"}', - }); - const selector = await screen.findByLabelText('selector'); - expect(selector).toBeInTheDocument(); - expect(selector.textContent).toBe('{job="grafana"}'); - }); - - it('Can enable explain', async () => { - renderWithMode(QueryEditorMode.Builder); - expect(screen.queryByText(EXPLAIN_LABEL_FILTER_CONTENT)).not.toBeInTheDocument(); - screen.getByLabelText('Explain').click(); - expect(await screen.findByText(EXPLAIN_LABEL_FILTER_CONTENT)).toBeInTheDocument(); - }); - - it('changes to code mode', async () => { - const { onChange } = renderWithMode(QueryEditorMode.Builder); - await switchToMode(QueryEditorMode.Code); - expect(onChange).toBeCalledWith({ - refId: 'A', - expr: defaultQuery.expr, - queryType: LokiQueryType.Range, - editorMode: QueryEditorMode.Code, - }); - }); - - it('parses query when changing to builder mode', async () => { - const { rerender } = renderWithProps({ - refId: 'A', - expr: 'rate({instance="host.docker.internal:3000"}[$__interval])', - editorMode: QueryEditorMode.Code, - }); - await switchToMode(QueryEditorMode.Builder); - rerender( - - ); - - await screen.findByText('host.docker.internal:3000'); - expect(screen.getByText('Rate')).toBeInTheDocument(); - expect(screen.getByText('$__interval')).toBeInTheDocument(); - }); -}); - -function renderWithMode(mode: QueryEditorMode) { - return renderWithProps({ editorMode: mode }); -} - -function renderWithProps(overrides?: Partial) { - const query = defaultsDeep(overrides ?? {}, cloneDeep(defaultQuery)); - const onChange = jest.fn(); - - const stuff = render(); - return { onChange, ...stuff }; -} - -function expectCodeEditor() { - // Log browser shows this until log labels are loaded. - expect(screen.getByText('Loading labels...')).toBeInTheDocument(); -} - -async function expectBuilder() { - expect(await screen.findByText('Label filters')).toBeInTheDocument(); -} - -async function switchToMode(mode: QueryEditorMode) { - const label = { - [QueryEditorMode.Code]: /Code/, - [QueryEditorMode.Builder]: /Builder/, - }[mode]; - - const switchEl = screen.getByLabelText(label); - await userEvent.click(switchEl); -} diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx deleted file mode 100644 index 8d0f6b9b789..00000000000 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react'; - -import { CoreApp, LoadingState } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { reportInteraction } from '@grafana/runtime'; -import { Button, ConfirmModal, EditorHeader, EditorRows, FlexItem, Space } from '@grafana/ui'; -import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle'; -import { QueryHeaderSwitch } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryHeaderSwitch'; -import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; - -import { - lokiQueryEditorExplainKey, - lokiQueryEditorRawQueryKey, - useFlag, -} from '../../../prometheus/querybuilder/shared/hooks/useFlag'; -import { LokiQueryEditorProps } from '../../components/types'; -import { LokiQuery } from '../../types'; -import { buildVisualQueryFromString } from '../parsing'; -import { changeEditorMode, getQueryWithDefaults } from '../state'; - -import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer'; -import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions'; -import { LokiQueryCodeEditor } from './LokiQueryCodeEditor'; -import { QueryPatternsModal } from './QueryPatternsModal'; - -export const LokiQueryEditorSelector = React.memo((props) => { - const { onChange, onRunQuery, onAddQuery, data, app, queries } = props; - const [parseModalOpen, setParseModalOpen] = useState(false); - const [queryPatternsModalOpen, setQueryPatternsModalOpen] = useState(false); - const [dataIsStale, setDataIsStale] = useState(false); - const { flag: explain, setFlag: setExplain } = useFlag(lokiQueryEditorExplainKey); - const { flag: rawQuery, setFlag: setRawQuery } = useFlag(lokiQueryEditorRawQueryKey, true); - - const query = getQueryWithDefaults(props.query); - // This should be filled in from the defaults by now. - const editorMode = query.editorMode!; - - const onExplainChange = (event: SyntheticEvent) => { - setExplain(event.currentTarget.checked); - }; - - const onEditorModeChange = useCallback( - (newEditorMode: QueryEditorMode) => { - reportInteraction('grafana_loki_editor_mode_clicked', { - newEditor: newEditorMode, - previousEditor: query.editorMode ?? '', - newQuery: !query.expr, - app: app ?? '', - }); - - if (newEditorMode === QueryEditorMode.Builder) { - const result = buildVisualQueryFromString(query.expr || ''); - // If there are errors, give user a chance to decide if they want to go to builder as that can lose some data. - if (result.errors.length) { - setParseModalOpen(true); - return; - } - } - changeEditorMode(query, newEditorMode, onChange); - }, - [onChange, query, app] - ); - - useEffect(() => { - setDataIsStale(false); - }, [data]); - - const onChangeInternal = (query: LokiQuery) => { - setDataIsStale(true); - onChange(query); - }; - - const onQueryPreviewChange = (event: SyntheticEvent) => { - const isEnabled = event.currentTarget.checked; - setRawQuery(isEnabled); - }; - - return ( - <> - { - onChange({ ...query, editorMode: QueryEditorMode.Builder }); - setParseModalOpen(false); - }} - onDismiss={() => setParseModalOpen(false)} - /> - setQueryPatternsModalOpen(false)} - query={query} - queries={queries} - app={app} - onChange={onChange} - onAddQuery={onAddQuery} - /> - - - - {editorMode === QueryEditorMode.Builder && ( - <> - - - )} - - {app !== CoreApp.Explore && ( - - )} - - - - - {editorMode === QueryEditorMode.Code && ( - - )} - {editorMode === QueryEditorMode.Builder && ( - - )} - - - - ); -}); - -LokiQueryEditorSelector.displayName = 'LokiQueryEditorSelector';