The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tes...

202 lines
7.7 KiB

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LokiQuery, LokiQueryType } from '../../types';
import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions';
describe('LokiQueryBuilderOptions', () => {
it('can change query type', async () => {
const { props } = setup();
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.getByLabelText('Range')).toBeChecked();
await userEvent.click(screen.getByLabelText('Instant'));
expect(props.onChange).toHaveBeenCalledWith({
...props.query,
queryType: LokiQueryType.Instant,
});
});
it('can change legend format', async () => {
const { props } = setup();
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// First autosize input is a Legend
const element = screen.getAllByTestId('autosize-input')[0];
await userEvent.type(element, 'asd');
await userEvent.keyboard('{enter}');
expect(props.onChange).toHaveBeenCalledWith({
...props.query,
legendFormat: 'asd',
});
});
it('can change line limit to valid value', async () => {
const { props } = setup({ expr: '{foo="bar"}' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// Second autosize input is a Line limit
const element = screen.getAllByTestId('autosize-input')[1];
await userEvent.type(element, '10');
await userEvent.keyboard('{enter}');
expect(props.onChange).toHaveBeenCalledWith({
...props.query,
maxLines: 10,
});
});
it('does not change line limit to invalid numeric value', async () => {
const { props } = setup({ expr: '{foo="bar"}' });
// We need to start with some value to be able to change it
props.query.maxLines = 10;
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// Second autosize input is a Line limit
const element = screen.getAllByTestId('autosize-input')[1];
await userEvent.type(element, '-10');
await userEvent.keyboard('{enter}');
expect(props.onChange).toHaveBeenCalledWith({
...props.query,
maxLines: undefined,
});
});
it('does not change line limit to invalid text value', async () => {
const { props } = setup({ expr: '{foo="bar"}' });
// We need to start with some value to be able to change it
props.query.maxLines = 10;
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
// Second autosize input is a Line limit
const element = screen.getAllByTestId('autosize-input')[1];
await userEvent.type(element, 'asd');
await userEvent.keyboard('{enter}');
expect(props.onChange).toHaveBeenCalledWith({
...props.query,
maxLines: undefined,
});
});
it('shows correct options for log query', async () => {
setup({ expr: '{foo="bar"}' });
expect(screen.getByText('Line limit: 20')).toBeInTheDocument();
expect(screen.getByText('Type: Range')).toBeInTheDocument();
expect(screen.queryByText(/step/i)).not.toBeInTheDocument();
});
it('shows correct options for metric query', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', step: '1m', resolution: 2 });
expect(screen.queryByText('Line limit: 20')).not.toBeInTheDocument();
expect(screen.getByText('Type: Range')).toBeInTheDocument();
expect(screen.getByText('Step: 1m')).toBeInTheDocument();
expect(screen.getByText('Resolution: 1/2')).toBeInTheDocument();
});
it('does not show resolution field if resolution is not set', async () => {
setup({ expr: 'rate({foo="bar"}[5m]' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText('Resolution')).not.toBeInTheDocument();
});
it('does not show resolution field if resolution is set to default value 1', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', resolution: 1 });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText('Resolution')).not.toBeInTheDocument();
});
it('does shows resolution field with warning if resolution is set to non-default value', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', resolution: 2 });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.getByText('Resolution')).toBeInTheDocument();
expect(
screen.getByText("The 'Resolution' is deprecated. Use 'Step' editor instead to change step parameter.")
).toBeInTheDocument();
});
it.each(['abc', 10])('shows correct options for metric query with invalid step', async (step: string | number) => {
// @ts-expect-error Expected for backward compatibility test
setup({ expr: 'rate({foo="bar"}[5m]', step });
expect(screen.queryByText('Line limit: 20')).not.toBeInTheDocument();
expect(screen.getByText('Type: Range')).toBeInTheDocument();
expect(screen.getByText('Step: Invalid value')).toBeInTheDocument();
});
it('shows error when invalid value in step', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', step: 'a' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.getByText(/Invalid step/)).toBeInTheDocument();
});
it('does not show error when valid value in step', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', step: '1m' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument();
});
it('does not show error when valid millisecond value in step', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', step: '1ms' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument();
});
it('does not show error when valid day value in step', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', step: '1d' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText(/Invalid step/)).not.toBeInTheDocument();
});
it('does not show instant type when using a log query', async () => {
setup({ expr: '{foo="bar"}', queryType: LokiQueryType.Instant });
expect(screen.queryByText(/Instant/)).not.toBeInTheDocument();
});
it('does not show instant type in the options when using a log query', async () => {
setup({ expr: '{foo="bar"}', step: '1m' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.queryByText(/Instant/)).not.toBeInTheDocument();
});
it('allows to clear step input', async () => {
setup({ expr: 'rate({foo="bar"}[5m]', step: '4s' });
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(screen.getByDisplayValue('4s')).toBeInTheDocument();
await userEvent.clear(screen.getByDisplayValue('4s'));
expect(screen.queryByDisplayValue('4s')).not.toBeInTheDocument();
});
it('should transform non duration numbers to duration', async () => {
const onChange = jest.fn();
setup({ expr: 'rate({foo="bar"}[5m]', step: '4' }, onChange);
await userEvent.click(screen.getByRole('button', { name: /Options/ }));
expect(onChange).toHaveBeenCalledWith({
refId: 'A',
expr: 'rate({foo="bar"}[5m]',
step: '4s',
});
});
});
function setup(queryOverrides: Partial<LokiQuery> = {}, onChange = jest.fn()) {
const props = {
query: {
refId: 'A',
expr: '',
...queryOverrides,
},
onRunQuery: jest.fn(),
onChange,
maxLines: 20,
queryStats: { streams: 0, chunks: 0, bytes: 0, entries: 0 },
};
const { container } = render(<LokiQueryBuilderOptions {...props} />);
return { container, props };
}