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:
Ivana Huckova
2022-05-03 14:50:13 +02:00
committed by GitHub
parent 4661c9ca47
commit 769be876a5
7 changed files with 102 additions and 131 deletions

View File

@@ -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]
);

View File

@@ -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);
});
});

View File

@@ -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();
});
});

View File

@@ -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[][] }) {

View File

@@ -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();

View File

@@ -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);
});

View File

@@ -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) {