mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki, Prometheus: Remember preferred editor (#48580)
* Loki: Remember default editor * Loki: Add tests * Prometheus: Set default editor type * Fix and refactor tests * Remove unused import
This commit is contained in:
@@ -11,7 +11,7 @@ import { LokiQueryEditorProps } from '../../components/types';
|
||||
import { LokiQuery } from '../../types';
|
||||
import { lokiQueryModeller } from '../LokiQueryModeller';
|
||||
import { buildVisualQueryFromString } from '../parsing';
|
||||
import { getQueryWithDefaults } from '../state';
|
||||
import { changeEditorMode, getQueryWithDefaults } from '../state';
|
||||
|
||||
import { LokiQueryBuilderContainer } from './LokiQueryBuilderContainer';
|
||||
import { LokiQueryBuilderExplained } from './LokiQueryBuilderExplained';
|
||||
@@ -26,9 +26,8 @@ export const LokiQueryEditorSelector = React.memo<LokiQueryEditorProps>((props)
|
||||
const query = getQueryWithDefaults(props.query);
|
||||
|
||||
const onEditorModeChange = useCallback(
|
||||
(newMetricEditorMode: QueryEditorMode) => {
|
||||
const change = { ...query, editorMode: newMetricEditorMode };
|
||||
if (newMetricEditorMode === QueryEditorMode.Builder) {
|
||||
(newEditorMode: QueryEditorMode) => {
|
||||
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 loose some data.
|
||||
if (result.errors.length) {
|
||||
@@ -36,7 +35,7 @@ export const LokiQueryEditorSelector = React.memo<LokiQueryEditorProps>((props)
|
||||
return;
|
||||
}
|
||||
}
|
||||
onChange(change);
|
||||
changeEditorMode(query, newEditorMode, onChange);
|
||||
},
|
||||
[onChange, query]
|
||||
);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { QueryEditorMode } from '../../prometheus/querybuilder/shared/types';
|
||||
|
||||
import { changeEditorMode, getQueryWithDefaults } from './state';
|
||||
|
||||
describe('getQueryWithDefaults(', () => {
|
||||
it('should set defaults', () => {
|
||||
expect(getQueryWithDefaults({ refId: 'A' } as any)).toEqual({
|
||||
editorMode: 'builder',
|
||||
expr: '',
|
||||
queryType: 'range',
|
||||
refId: 'A',
|
||||
});
|
||||
});
|
||||
|
||||
it('changing editor mode with blank query should change default', () => {
|
||||
changeEditorMode({ refId: 'A', expr: '' }, QueryEditorMode.Code, (query) => {
|
||||
expect(query.editorMode).toBe(QueryEditorMode.Code);
|
||||
});
|
||||
|
||||
expect(getQueryWithDefaults({ refId: 'A' } as any).editorMode).toEqual(QueryEditorMode.Code);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { render, RenderResult } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { noop } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
@@ -43,6 +42,7 @@ function setup(app: CoreApp): RenderResult & { onRunQuery: jest.Mock } {
|
||||
createQuery: jest.fn((q) => q),
|
||||
getInitHints: () => [],
|
||||
getPrometheusTime: jest.fn((date, roundup) => 123),
|
||||
getQueryHints: jest.fn(() => []),
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
@@ -96,24 +96,4 @@ describe('PromQueryEditorByApp', () => {
|
||||
expect(getByTestId('QueryEditorModeToggle')).toBeInTheDocument();
|
||||
expect(queryByTestId(alertingTestIds.editor)).toBeNull();
|
||||
});
|
||||
|
||||
it('should not run query onBlur in explore', async () => {
|
||||
const { getByTestId, onRunQuery } = setup(CoreApp.Explore);
|
||||
|
||||
const input = getByTestId('dummy-code-input');
|
||||
expect(input).toBeInTheDocument();
|
||||
await userEvent.type(input, 'metric');
|
||||
input.blur();
|
||||
expect(onRunQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should run query onBlur in dashboard', async () => {
|
||||
const { getByTestId, onRunQuery } = setup(CoreApp.Dashboard);
|
||||
|
||||
const input = getByTestId('dummy-code-input');
|
||||
expect(input).toBeInTheDocument();
|
||||
await userEvent.type(input, 'metric');
|
||||
input.blur();
|
||||
expect(onRunQuery).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +1,46 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { getByTestId, render, screen } from '@testing-library/react';
|
||||
// @ts-ignore
|
||||
import RCCascader from 'rc-cascader';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
|
||||
import { DataSourceInstanceSettings, PanelData, LoadingState, DataFrame } from '@grafana/data';
|
||||
import { PanelData, LoadingState, DataFrame, CoreApp } from '@grafana/data';
|
||||
|
||||
import { PrometheusDatasource } from '../datasource';
|
||||
import PromQlLanguageProvider from '../language_provider';
|
||||
import { PromOptions } from '../types';
|
||||
|
||||
import PromQueryField from './PromQueryField';
|
||||
|
||||
// the monaco-based editor uses lazy-loading and that does not work
|
||||
// well with this test, and we do not need the monaco-related
|
||||
// functionality in this test anyway, so we mock it out.
|
||||
jest.mock('./monaco-query-field/MonacoQueryFieldWrapper', () => {
|
||||
const fakeQueryField = () => <div>prometheus query field</div>;
|
||||
jest.mock('./monaco-query-field/MonacoQueryFieldLazy', () => {
|
||||
const fakeQueryField = (props: any) => {
|
||||
return <input onBlur={props.onBlur} data-testid={'dummy-code-input'} type={'text'} />;
|
||||
};
|
||||
return {
|
||||
MonacoQueryFieldWrapper: fakeQueryField,
|
||||
MonacoQueryFieldLazy: fakeQueryField,
|
||||
};
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
datasource: {
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
getLabelKeys: () => [],
|
||||
metrics: [],
|
||||
},
|
||||
getInitHints: () => [],
|
||||
} as unknown as PrometheusDatasource,
|
||||
query: {
|
||||
expr: '',
|
||||
refId: '',
|
||||
},
|
||||
onRunQuery: () => {},
|
||||
onChange: () => {},
|
||||
history: [],
|
||||
};
|
||||
|
||||
describe('PromQueryField', () => {
|
||||
beforeAll(() => {
|
||||
// @ts-ignore
|
||||
@@ -27,97 +48,37 @@ describe('PromQueryField', () => {
|
||||
});
|
||||
|
||||
it('renders metrics chooser regularly if lookups are not disabled in the datasource settings', () => {
|
||||
const datasource = {
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
getLabelKeys: () => [],
|
||||
metrics: [],
|
||||
},
|
||||
getInitHints: () => [],
|
||||
} as unknown as DataSourceInstanceSettings<PromOptions>;
|
||||
|
||||
const queryField = render(
|
||||
<PromQueryField
|
||||
// @ts-ignore
|
||||
datasource={datasource}
|
||||
query={{ expr: '', refId: '' }}
|
||||
onRunQuery={() => {}}
|
||||
onChange={() => {}}
|
||||
history={[]}
|
||||
/>
|
||||
);
|
||||
const queryField = render(<PromQueryField {...defaultProps} />);
|
||||
|
||||
expect(queryField.getAllByRole('button')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a disabled metrics chooser if lookups are disabled in datasource settings', () => {
|
||||
const datasource = {
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
getLabelKeys: () => [],
|
||||
metrics: [],
|
||||
},
|
||||
getInitHints: () => [],
|
||||
} as unknown as DataSourceInstanceSettings<PromOptions>;
|
||||
const queryField = render(
|
||||
<PromQueryField
|
||||
// @ts-ignore
|
||||
datasource={{ ...datasource, lookupsDisabled: true }}
|
||||
query={{ expr: '', refId: '' }}
|
||||
onRunQuery={() => {}}
|
||||
onChange={() => {}}
|
||||
history={[]}
|
||||
/>
|
||||
);
|
||||
const props = defaultProps;
|
||||
props.datasource.lookupsDisabled = true;
|
||||
const queryField = render(<PromQueryField {...props} />);
|
||||
|
||||
const bcButton = queryField.getByRole('button');
|
||||
expect(bcButton).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders an initial hint if no data and initial hint provided', () => {
|
||||
const datasource = {
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
getLabelKeys: () => [],
|
||||
metrics: [],
|
||||
},
|
||||
getInitHints: () => [{ label: 'Initial hint', type: 'INFO' }],
|
||||
} as unknown as DataSourceInstanceSettings<PromOptions>;
|
||||
render(
|
||||
<PromQueryField
|
||||
// @ts-ignore
|
||||
datasource={{ ...datasource, lookupsDisabled: true }}
|
||||
query={{ expr: '', refId: '' }}
|
||||
onRunQuery={() => {}}
|
||||
onChange={() => {}}
|
||||
history={[]}
|
||||
/>
|
||||
);
|
||||
const props = defaultProps;
|
||||
props.datasource.lookupsDisabled = true;
|
||||
props.datasource.getInitHints = () => [{ label: 'Initial hint', type: 'INFO' }];
|
||||
render(<PromQueryField {...props} />);
|
||||
|
||||
expect(screen.getByText('Initial hint')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders query hint if data, query hint and initial hint provided', () => {
|
||||
const datasource = {
|
||||
languageProvider: {
|
||||
start: () => Promise.resolve([]),
|
||||
syntax: () => {},
|
||||
getLabelKeys: () => [],
|
||||
metrics: [],
|
||||
},
|
||||
getInitHints: () => [{ label: 'Initial hint', type: 'INFO' }],
|
||||
getQueryHints: () => [{ label: 'Query hint', type: 'INFO' }],
|
||||
} as unknown as DataSourceInstanceSettings<PromOptions>;
|
||||
const props = defaultProps;
|
||||
props.datasource.lookupsDisabled = true;
|
||||
props.datasource.getInitHints = () => [{ label: 'Initial hint', type: 'INFO' }];
|
||||
props.datasource.getQueryHints = () => [{ label: 'Query hint', type: 'INFO' }];
|
||||
render(
|
||||
<PromQueryField
|
||||
// @ts-ignore
|
||||
datasource={{ ...datasource }}
|
||||
query={{ expr: '', refId: '' }}
|
||||
onRunQuery={() => {}}
|
||||
onChange={() => {}}
|
||||
history={[]}
|
||||
{...props}
|
||||
data={
|
||||
{
|
||||
series: [{ name: 'test name' }] as DataFrame[],
|
||||
@@ -126,6 +87,7 @@ describe('PromQueryField', () => {
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Query hint')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Initial hint')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -140,11 +102,12 @@ describe('PromQueryField', () => {
|
||||
const metrics = ['foo', 'bar'];
|
||||
const queryField = render(
|
||||
<PromQueryField
|
||||
// @ts-ignore
|
||||
datasource={{
|
||||
languageProvider: makeLanguageProvider({ metrics: [metrics] }),
|
||||
getInitHints: () => [],
|
||||
}}
|
||||
datasource={
|
||||
{
|
||||
languageProvider: makeLanguageProvider({ metrics: [metrics] }),
|
||||
getInitHints: () => [],
|
||||
} as unknown as PrometheusDatasource
|
||||
}
|
||||
{...defaultProps}
|
||||
/>
|
||||
);
|
||||
@@ -164,6 +127,28 @@ describe('PromQueryField', () => {
|
||||
let labelBrowser = screen.getByRole('button');
|
||||
expect(labelBrowser.textContent).toContain('Loading');
|
||||
});
|
||||
|
||||
it('should not run query onBlur in explore', async () => {
|
||||
const onRunQuery = jest.fn();
|
||||
const { container } = render(<PromQueryField {...defaultProps} app={CoreApp.Explore} onRunQuery={onRunQuery} />);
|
||||
|
||||
const input = getByTestId(container, 'dummy-code-input');
|
||||
expect(input).toBeInTheDocument();
|
||||
await userEvent.type(input, 'metric');
|
||||
input.blur();
|
||||
expect(onRunQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should run query onBlur in dashboard', async () => {
|
||||
const onRunQuery = jest.fn();
|
||||
const { container } = render(<PromQueryField {...defaultProps} app={CoreApp.Dashboard} onRunQuery={onRunQuery} />);
|
||||
|
||||
const input = getByTestId(container, 'dummy-code-input');
|
||||
expect(input).toBeInTheDocument();
|
||||
await userEvent.type(input, 'metric');
|
||||
input.blur();
|
||||
expect(onRunQuery).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function makeLanguageProvider(options: { metrics: string[][] }) {
|
||||
|
||||
@@ -60,19 +60,6 @@ describe('PromQueryEditorSelector', () => {
|
||||
expectCodeEditor();
|
||||
});
|
||||
|
||||
it('shows code if new query', async () => {
|
||||
render(
|
||||
<PromQueryEditorSelector
|
||||
{...defaultProps}
|
||||
query={{
|
||||
refId: 'A',
|
||||
expr: '',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expectCodeEditor();
|
||||
});
|
||||
|
||||
it('shows code editor when code mode is set', async () => {
|
||||
renderWithMode(QueryEditorMode.Code);
|
||||
expectCodeEditor();
|
||||
|
||||
@@ -6,7 +6,7 @@ import { changeEditorMode, getQueryWithDefaults } from './state';
|
||||
describe('getQueryWithDefaults(', () => {
|
||||
it('should set defaults', () => {
|
||||
expect(getQueryWithDefaults({ refId: 'A' } as any, CoreApp.Dashboard)).toEqual({
|
||||
editorMode: 'code',
|
||||
editorMode: 'builder',
|
||||
expr: '',
|
||||
legendFormat: '__auto',
|
||||
range: true,
|
||||
@@ -16,7 +16,7 @@ describe('getQueryWithDefaults(', () => {
|
||||
|
||||
it('should set both range and instant to true when in Explore', () => {
|
||||
expect(getQueryWithDefaults({ refId: 'A' } as any, CoreApp.Explore)).toEqual({
|
||||
editorMode: 'code',
|
||||
editorMode: 'builder',
|
||||
expr: '',
|
||||
legendFormat: '__auto',
|
||||
range: true,
|
||||
@@ -25,7 +25,7 @@ describe('getQueryWithDefaults(', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('Changing editor mode with blank query should change default', () => {
|
||||
it('changing editor mode with blank query should change default', () => {
|
||||
changeEditorMode({ refId: 'A', expr: '' }, QueryEditorMode.Code, (query) => {
|
||||
expect(query.editorMode).toBe(QueryEditorMode.Code);
|
||||
});
|
||||
|
||||
@@ -16,7 +16,6 @@ export function changeEditorMode(query: PromQuery, editorMode: QueryEditorMode,
|
||||
onChange({ ...query, editorMode });
|
||||
}
|
||||
|
||||
// @ts-ignore Will be used after builder is out of beta
|
||||
function getDefaultEditorMode(expr: string) {
|
||||
// If we already have an expression default to code view
|
||||
if (expr != null && expr !== '') {
|
||||
@@ -41,8 +40,7 @@ export function getQueryWithDefaults(query: PromQuery, app: CoreApp | undefined)
|
||||
let result = query;
|
||||
|
||||
if (!query.editorMode) {
|
||||
// Default to Code mode until we are out of beta with the builder, then use getDefaultEditorMode.
|
||||
result = { ...query, editorMode: QueryEditorMode.Code };
|
||||
result = { ...query, editorMode: getDefaultEditorMode(query.expr) };
|
||||
}
|
||||
|
||||
if (query.expr == null) {
|
||||
|
||||
Reference in New Issue
Block a user