diff --git a/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx b/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx index 17854499e85..a997c94760b 100644 --- a/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx +++ b/public/app/plugins/datasource/loki/components/LokiOptionFields.tsx @@ -18,7 +18,7 @@ export interface LokiOptionFieldsProps { runOnBlur?: boolean; } -const queryTypeOptions: Array> = [ +export const queryTypeOptions: Array> = [ { value: LokiQueryType.Range, label: 'Range', description: 'Run query over a range of time.' }, { value: LokiQueryType.Instant, @@ -40,7 +40,7 @@ export const DEFAULT_RESOLUTION: SelectableValue = { label: '1/1', }; -const RESOLUTION_OPTIONS: Array> = [DEFAULT_RESOLUTION].concat( +export const RESOLUTION_OPTIONS: Array> = [DEFAULT_RESOLUTION].concat( map([2, 3, 4, 5, 10], (value: number) => ({ value, label: '1/' + value, @@ -62,20 +62,6 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) { onChange({ ...rest, queryType }); } - function preprocessMaxLines(value: string): number { - if (value.length === 0) { - // empty input - falls back to dataSource.maxLines limit - return NaN; - } else if (value.length > 0 && (isNaN(+value) || +value < 0)) { - // input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative - // falls back to the limit of 0 lines - return 0; - } else { - // default case - correct input - return +value; - } - } - function onMaxLinesChange(e: React.SyntheticEvent) { if (query.maxLines !== preprocessMaxLines(e.currentTarget.value)) { onChangeQueryLimit(e.currentTarget.value); @@ -167,3 +153,17 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) { } export default memo(LokiOptionFields); + +export function preprocessMaxLines(value: string): number { + if (value.length === 0) { + // empty input - falls back to dataSource.maxLines limit + return NaN; + } else if (value.length > 0 && (isNaN(+value) || +value < 0)) { + // input with at least 1 character and that is either incorrect (value in the input field is not a number) or negative + // falls back to the limit of 0 lines + return 0; + } else { + // default case - correct input + return +value; + } +} diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx index 54a3071adff..ffdb5e70166 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilder.tsx @@ -6,7 +6,7 @@ import { OperationList } from 'app/plugins/datasource/prometheus/querybuilder/sh import { QueryBuilderLabelFilter } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; import { lokiQueryModeller } from '../LokiQueryModeller'; import { DataSourceApi, SelectableValue } from '@grafana/data'; -import { EditorRow, EditorRows } from '@grafana/experimental'; +import { EditorRow } from '@grafana/experimental'; import { QueryPreview } from './QueryPreview'; export interface Props { @@ -57,7 +57,7 @@ export const LokiQueryBuilder = React.memo(({ datasource, query, nested, }; return ( - + <> ) => @@ -84,7 +84,7 @@ export const LokiQueryBuilder = React.memo(({ datasource, query, nested, )} - + ); }); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx new file mode 100644 index 00000000000..ae015f9d3fd --- /dev/null +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { LokiQuery, LokiQueryType } from '../../types'; +import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions'; +import userEvent from '@testing-library/user-event'; + +describe('LokiQueryBuilderOptions', () => { + it('Can change query type', async () => { + const { props } = setup(); + + screen.getByTitle('Click to edit options').click(); + expect(screen.getByLabelText('Range')).toBeChecked(); + + screen.getByLabelText('Instant').click(); + + expect(props.onChange).toHaveBeenCalledWith({ + ...props.query, + queryType: LokiQueryType.Instant, + }); + }); + + it('Can change legend format', async () => { + const { props } = setup(); + + screen.getByTitle('Click to edit options').click(); + + const element = screen.getByLabelText('Legend'); + userEvent.type(element, 'asd'); + fireEvent.keyDown(element, { key: 'Enter', code: 'Enter', charCode: 13 }); + + expect(props.onChange).toHaveBeenCalledWith({ + ...props.query, + legendFormat: 'asd', + }); + }); +}); + +function setup(queryOverrides: Partial = {}) { + const props = { + query: { + refId: 'A', + expr: '', + ...queryOverrides, + }, + onRunQuery: jest.fn(), + onChange: jest.fn(), + }; + + const { container } = render(); + return { container, props }; +} diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx new file mode 100644 index 00000000000..135ec0b8f90 --- /dev/null +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderOptions.tsx @@ -0,0 +1,117 @@ +import React from 'react'; +import { EditorRow, EditorField } from '@grafana/experimental'; +import { SelectableValue } from '@grafana/data'; +import { RadioButtonGroup, Select } from '@grafana/ui'; +import { LokiQuery, LokiQueryType } from '../../types'; +import { QueryOptionGroup } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryOptionGroup'; +import { preprocessMaxLines, queryTypeOptions, RESOLUTION_OPTIONS } from '../../components/LokiOptionFields'; +import { getLegendModeLabel } from 'app/plugins/datasource/prometheus/querybuilder/components/PromQueryLegendEditor'; +import { AutoSizeInput } from 'app/plugins/datasource/prometheus/querybuilder/shared/AutoSizeInput'; +import { isMetricsQuery } from '../../datasource'; + +export interface Props { + query: LokiQuery; + onChange: (update: LokiQuery) => void; + onRunQuery: () => void; +} + +export const LokiQueryBuilderOptions = React.memo(({ query, onChange, onRunQuery }) => { + const onQueryTypeChange = (value: LokiQueryType) => { + onChange({ ...query, queryType: value }); + onRunQuery(); + }; + + const onResolutionChange = (option: SelectableValue) => { + onChange({ ...query, resolution: option.value }); + onRunQuery(); + }; + + const onLegendFormatChanged = (evt: React.FormEvent) => { + onChange({ ...query, legendFormat: evt.currentTarget.value }); + onRunQuery(); + }; + + function onMaxLinesChange(e: React.SyntheticEvent) { + const newMaxLines = preprocessMaxLines(e.currentTarget.value); + if (query.maxLines !== newMaxLines) { + onChange({ ...query, maxLines: newMaxLines }); + onRunQuery(); + } + } + + let queryType = query.queryType ?? (query.instant ? LokiQueryType.Instant : LokiQueryType.Range); + let showMaxLines = !isMetricsQuery(query.expr); + + return ( + + + + + + + + + {showMaxLines && ( + + + + )} + +