Loki: Removal of Resolution in query editors (#101860)
Loki: Remove usage of `Resolution` in Annotations
This commit is contained in:
@@ -173,9 +173,7 @@ The following options are the same for both **Builder** and **Code** mode:
|
||||
|
||||
- **Direction** - Determines the search order. **Backward** is a backward search starting at the end of the time range. **Forward** is a forward search starting at the beginning of the time range. The default is **Backward**
|
||||
|
||||
- **Step** Sets the step parameter of Loki metrics queries. The default value equals to the value of `$__interval` variable, which is calculated using the time range and the width of the graph (the number of pixels).
|
||||
|
||||
- **Resolution** Deprecated. Sets the step parameter of Loki metrics range queries. With a resolution of `1/1`, each pixel corresponds to one data point. `1/2` retrieves one data point for every other pixel, `1/10` retrieves one data point per 10 pixels, and so on. Lower resolutions perform better.
|
||||
- **Step** Sets the step parameter of Loki metrics queries. The default value equals to the value of `$__auto` variable, which is calculated using the time range and the width of the graph (the number of pixels).
|
||||
|
||||
## Create a log query
|
||||
|
||||
@@ -263,6 +261,6 @@ For more information about metric queries, refer to the [Loki metric queries doc
|
||||
[Annotations](ref:annotate-visualizations) overlay rich event information on top of graphs.
|
||||
You can add annotation queries in the Dashboard menu's Annotations view.
|
||||
|
||||
You can use any non-metric Loki query as a source for annotations.
|
||||
You can only use log queries as a source for annotations.
|
||||
Grafana automatically uses log content as annotation text and your log stream labels as tags.
|
||||
You don't need to create any additional mapping.
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// Libraries
|
||||
import { memo } from 'react';
|
||||
|
||||
import { AnnotationQuery } from '@grafana/data';
|
||||
import { EditorField, EditorRow } from '@grafana/plugin-ui';
|
||||
import { Input, Stack } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { getNormalizedLokiQuery } from '../queryUtils';
|
||||
import { LokiQuery, LokiQueryType } from '../types';
|
||||
import { LokiQuery } from '../types';
|
||||
|
||||
import { LokiOptionFields } from './LokiOptionFields';
|
||||
import { LokiQueryField } from './LokiQueryField';
|
||||
@@ -27,17 +24,11 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
|
||||
}
|
||||
|
||||
const onChangeQuery = (query: LokiQuery) => {
|
||||
// the current version of annotations only stores an optional boolean
|
||||
// field `instant` to handle the instant/range switch.
|
||||
// we need to maintain compatibility for now, so we do the same.
|
||||
// we explicitly call `getNormalizedLokiQuery` to make sure `queryType`
|
||||
// is set up correctly.
|
||||
const instant = getNormalizedLokiQuery(query).queryType === LokiQueryType.Instant;
|
||||
onAnnotationChange({
|
||||
...annotation,
|
||||
expr: query.expr,
|
||||
maxLines: query.maxLines,
|
||||
instant,
|
||||
queryType: 'range',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -60,7 +51,6 @@ export const LokiAnnotationsQueryEditor = memo(function LokiAnnotationQueryEdito
|
||||
ExtraFieldElement={
|
||||
<LokiOptionFields
|
||||
lineLimitValue={queryWithRefId?.maxLines?.toString() || ''}
|
||||
resolution={queryWithRefId.resolution || 1}
|
||||
query={queryWithRefId}
|
||||
onRunQuery={() => {}}
|
||||
onChange={onChangeQuery}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
import { LokiOptionFieldsProps, LokiOptionFields, preprocessMaxLines } from './LokiOptionFields';
|
||||
|
||||
const setup = () => {
|
||||
const lineLimitValue = '1';
|
||||
const resolution = 1;
|
||||
const query = { refId: '1', expr: 'query' };
|
||||
const onChange = jest.fn();
|
||||
const onRunQuery = jest.fn();
|
||||
|
||||
const props: LokiOptionFieldsProps = {
|
||||
lineLimitValue,
|
||||
resolution,
|
||||
query,
|
||||
onChange,
|
||||
onRunQuery,
|
||||
@@ -20,35 +18,6 @@ const setup = () => {
|
||||
return props;
|
||||
};
|
||||
|
||||
describe('Query Type Field', () => {
|
||||
it('should render a query type field', () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} />);
|
||||
expect(screen.getByTestId('queryTypeField')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have a default value of "Range"', () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} />);
|
||||
expect(screen.getByLabelText('Range')).toBeChecked();
|
||||
expect(screen.getByLabelText('Instant')).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should call onChange when value is changed', async () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} />);
|
||||
fireEvent.click(screen.getByLabelText('Instant')); // (`userEvent.click()` triggers an error, so switching here to `fireEvent`.)
|
||||
await waitFor(() => expect(props.onChange).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it('renders as expected when the query type is instant', () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} query={{ refId: '1', expr: 'query', instant: true }} />);
|
||||
expect(screen.getByLabelText('Instant')).toBeChecked();
|
||||
expect(screen.getByLabelText('Range')).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Line Limit Field', () => {
|
||||
it('should render a line limit field', () => {
|
||||
const props = setup();
|
||||
@@ -69,26 +38,6 @@ describe('Line Limit Field', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Resolution Field', () => {
|
||||
it('should render the resolution field', () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} />);
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have a default value of 1', async () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} />);
|
||||
expect(await screen.findByText('1/1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays the expected resolution value', async () => {
|
||||
const props = setup();
|
||||
render(<LokiOptionFields {...props} resolution={5} />);
|
||||
expect(await screen.findByText('1/5')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('preprocessMaxLines', () => {
|
||||
test.each([
|
||||
{ inputValue: '', expected: undefined },
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
// Libraries
|
||||
import { map } from 'lodash';
|
||||
import { memo } from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
// Types
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { InlineFormLabel, RadioButtonGroup, InlineField, Input, Select, Stack } from '@grafana/ui';
|
||||
import { InlineField, Input, Stack } from '@grafana/ui';
|
||||
|
||||
import { getLokiQueryType } from '../queryUtils';
|
||||
import { LokiQuery, LokiQueryDirection, LokiQueryType } from '../types';
|
||||
|
||||
export interface LokiOptionFieldsProps {
|
||||
lineLimitValue: string;
|
||||
resolution: number;
|
||||
query: LokiQuery;
|
||||
onChange: (value: LokiQuery) => void;
|
||||
onRunQuery: () => void;
|
||||
@@ -51,41 +46,15 @@ export function getQueryDirectionLabel(direction: LokiQueryDirection) {
|
||||
return queryDirections.find((queryDirection) => queryDirection.value === direction)?.label ?? 'Unknown';
|
||||
}
|
||||
|
||||
if (config.featureToggles.lokiExperimentalStreaming) {
|
||||
queryTypeOptions.push({
|
||||
value: LokiQueryType.Stream,
|
||||
label: 'Stream',
|
||||
description: 'Run a query and keep sending results on an interval',
|
||||
});
|
||||
}
|
||||
|
||||
export const DEFAULT_RESOLUTION: SelectableValue<number> = {
|
||||
value: 1,
|
||||
label: '1/1',
|
||||
};
|
||||
|
||||
export const RESOLUTION_OPTIONS: Array<SelectableValue<number>> = [DEFAULT_RESOLUTION].concat(
|
||||
map([2, 3, 4, 5, 10], (value: number) => ({
|
||||
value,
|
||||
label: '1/' + value,
|
||||
}))
|
||||
);
|
||||
|
||||
export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
||||
const { lineLimitValue, resolution, onRunQuery, runOnBlur, onChange } = props;
|
||||
const { lineLimitValue, onRunQuery, runOnBlur, onChange } = props;
|
||||
const query = props.query ?? {};
|
||||
const queryType = getLokiQueryType(query);
|
||||
|
||||
function onChangeQueryLimit(value: string) {
|
||||
const nextQuery = { ...query, maxLines: preprocessMaxLines(value) };
|
||||
onChange(nextQuery);
|
||||
}
|
||||
|
||||
function onQueryTypeChange(queryType: LokiQueryType) {
|
||||
const { instant, range, ...rest } = query;
|
||||
onChange({ ...rest, queryType });
|
||||
}
|
||||
|
||||
function onMaxLinesChange(e: React.SyntheticEvent<HTMLInputElement>) {
|
||||
if (query.maxLines !== preprocessMaxLines(e.currentTarget.value)) {
|
||||
onChangeQueryLimit(e.currentTarget.value);
|
||||
@@ -98,28 +67,8 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
||||
}
|
||||
}
|
||||
|
||||
function onResolutionChange(option: SelectableValue<number>) {
|
||||
const nextQuery = { ...query, resolution: option.value };
|
||||
onChange(nextQuery);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack alignItems="flex-start" gap={0.5} aria-label="Loki extra field">
|
||||
{/*Query type field*/}
|
||||
<Stack wrap="nowrap" gap={0} data-testid="queryTypeField" aria-label="Query type field">
|
||||
<InlineFormLabel width="auto">Query type</InlineFormLabel>
|
||||
|
||||
<RadioButtonGroup
|
||||
options={queryTypeOptions}
|
||||
value={queryType}
|
||||
onChange={(type: LokiQueryType) => {
|
||||
onQueryTypeChange(type);
|
||||
if (runOnBlur) {
|
||||
onRunQuery();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
{/*Line limit field*/}
|
||||
<Stack wrap="nowrap" gap={0} data-testid="lineLimitField" aria-label="Line limit field">
|
||||
<InlineField label="Line limit" tooltip={'Upper limit for number of log lines returned by query.'}>
|
||||
@@ -138,20 +87,6 @@ export function LokiOptionFields(props: LokiOptionFieldsProps) {
|
||||
}}
|
||||
/>
|
||||
</InlineField>
|
||||
<InlineField
|
||||
label="Resolution"
|
||||
tooltip={
|
||||
'Resolution 1/1 sets step parameter of Loki metrics range queries such that each pixel corresponds to one data point. For better performance, lower resolutions can be picked. 1/2 only retrieves a data point for every other pixel, and 1/10 retrieves one data point per 10 pixels.'
|
||||
}
|
||||
>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
onChange={onResolutionChange}
|
||||
options={RESOLUTION_OPTIONS}
|
||||
value={resolution}
|
||||
aria-label="Select resolution"
|
||||
/>
|
||||
</InlineField>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
+1
-23
@@ -122,35 +122,13 @@ describe('LokiQueryBuilderOptions', () => {
|
||||
});
|
||||
|
||||
it('shows correct options for metric query', async () => {
|
||||
setup({ expr: 'rate({foo="bar"}[5m]', step: '1m', resolution: 2 });
|
||||
setup({ expr: 'rate({foo="bar"}[5m]', step: '1m' });
|
||||
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();
|
||||
expect(screen.queryByText(/Direction/)).not.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 });
|
||||
|
||||
+2
-38
@@ -8,19 +8,17 @@ import {
|
||||
isValidGrafanaDuration,
|
||||
LogSortOrderChangeEvent,
|
||||
LogsSortOrder,
|
||||
SelectableValue,
|
||||
store,
|
||||
} from '@grafana/data';
|
||||
import { EditorField, EditorRow, QueryOptionGroup } from '@grafana/plugin-ui';
|
||||
import { config, getAppEvents, reportInteraction } from '@grafana/runtime';
|
||||
import { Alert, AutoSizeInput, RadioButtonGroup, Select } from '@grafana/ui';
|
||||
import { config, getAppEvents } from '@grafana/runtime';
|
||||
import { AutoSizeInput, RadioButtonGroup } from '@grafana/ui';
|
||||
|
||||
import {
|
||||
getQueryDirectionLabel,
|
||||
preprocessMaxLines,
|
||||
queryDirections,
|
||||
queryTypeOptions,
|
||||
RESOLUTION_OPTIONS,
|
||||
} from '../../components/LokiOptionFields';
|
||||
import { getLokiQueryType, isLogsQuery } from '../../queryUtils';
|
||||
import { LokiQuery, LokiQueryDirection, LokiQueryType, QueryStats } from '../../types';
|
||||
@@ -70,15 +68,6 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
[onChange, onRunQuery, query]
|
||||
);
|
||||
|
||||
const onResolutionChange = (option: SelectableValue<number>) => {
|
||||
reportInteraction('grafana_loki_resolution_clicked', {
|
||||
app,
|
||||
resolution: option.value,
|
||||
});
|
||||
onChange({ ...query, resolution: option.value });
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const onChunkRangeChange = (evt: React.FormEvent<HTMLInputElement>) => {
|
||||
const value = evt.currentTarget.value;
|
||||
if (!isValidDuration(value)) {
|
||||
@@ -209,26 +198,6 @@ export const LokiQueryBuilderOptions = React.memo<Props>(
|
||||
onCommitChange={onStepChange}
|
||||
/>
|
||||
</EditorField>
|
||||
{query.resolution !== undefined && query.resolution > 1 && (
|
||||
<>
|
||||
<EditorField
|
||||
label="Resolution"
|
||||
tooltip="Changes the step parameter of Loki metrics range queries. With a resolution of 1/1, each pixel corresponds to one data point. 1/10 retrieves one data point per 10 pixels. Lower resolutions perform better."
|
||||
>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
onChange={onResolutionChange}
|
||||
options={RESOLUTION_OPTIONS}
|
||||
value={query.resolution || 1}
|
||||
aria-label="Select resolution"
|
||||
/>
|
||||
</EditorField>
|
||||
<Alert
|
||||
severity="warning"
|
||||
title="The 'Resolution' is deprecated. Use 'Step' editor instead to change step parameter."
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{config.featureToggles.lokiQuerySplittingConfig && config.featureToggles.lokiQuerySplitting && (
|
||||
@@ -261,7 +230,6 @@ function getCollapsedInfo(
|
||||
direction: LokiQueryDirection | undefined
|
||||
): string[] {
|
||||
const queryTypeLabel = queryTypeOptions.find((x) => x.value === queryType);
|
||||
const resolutionLabel = RESOLUTION_OPTIONS.find((x) => x.value === (query.resolution ?? 1));
|
||||
|
||||
const items: string[] = [];
|
||||
|
||||
@@ -278,10 +246,6 @@ function getCollapsedInfo(
|
||||
if (query.step) {
|
||||
items.push(`Step: ${isValidStep ? query.step : 'Invalid value'}`);
|
||||
}
|
||||
|
||||
if (query.resolution) {
|
||||
items.push(`Resolution: ${resolutionLabel?.label}`);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
|
||||
Reference in New Issue
Block a user